mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][Endpoint] Additional test coverage for the Endpoint Console (#151504)
## Summary - Adds more tests for covering functionality provided by the Response console Address TestRail tests: - 1948002 - 1948003 - 1948004 - 1948005 - 1948007 - 1948008
This commit is contained in:
parent
f2d33f13e1
commit
e0bc286a75
14 changed files with 662 additions and 79 deletions
|
@ -24,6 +24,7 @@ import type {
|
|||
} from '@testing-library/react-hooks/src/types/react';
|
||||
import type { UseBaseQueryResult } from '@tanstack/react-query';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { ExperimentalFeaturesService } from '../../experimental_features_service';
|
||||
import { applyIntersectionObserverMock } from '../intersection_observer_mock';
|
||||
import { ConsoleManager } from '../../../management/components/console';
|
||||
import type { StartPlugins, StartServices } from '../../../types';
|
||||
|
@ -42,6 +43,7 @@ import { APP_UI_ID, APP_PATH } from '../../../../common/constants';
|
|||
import { KibanaContextProvider, KibanaServices } from '../../lib/kibana';
|
||||
import { getDeepLinks } from '../../../app/deep_links';
|
||||
import { fleetGetPackageHttpMock } from '../../../management/mocks';
|
||||
import { allowedExperimentalValues } from '../../../../common/experimental_features';
|
||||
|
||||
const REAL_REACT_DOM_CREATE_PORTAL = ReactDOM.createPortal;
|
||||
|
||||
|
@ -282,7 +284,16 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
|
|||
return hookResult.current;
|
||||
};
|
||||
|
||||
ExperimentalFeaturesService.init({ experimentalFeatures: allowedExperimentalValues });
|
||||
|
||||
const setExperimentalFlag: AppContextTestRender['setExperimentalFlag'] = (flags) => {
|
||||
ExperimentalFeaturesService.init({
|
||||
experimentalFeatures: {
|
||||
...allowedExperimentalValues,
|
||||
...flags,
|
||||
},
|
||||
});
|
||||
|
||||
store.dispatch({
|
||||
type: UpdateExperimentalFeaturesTestActionType,
|
||||
payload: flags,
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ConsoleTestSetup, HelpSidePanelSelectorsAndActions } from '../mocks';
|
||||
import {
|
||||
getCommandListMock,
|
||||
getConsoleTestSetup,
|
||||
getHelpSidePanelSelectorsAndActionsMock,
|
||||
} from '../mocks';
|
||||
import React from 'react';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
|
||||
describe('When rendering the command list (help output)', () => {
|
||||
let render: ConsoleTestSetup['renderConsole'];
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
let consoleSelectors: ConsoleTestSetup['selectors'];
|
||||
let enterCommand: ConsoleTestSetup['enterCommand'];
|
||||
|
||||
beforeEach(() => {
|
||||
const testSetup = getConsoleTestSetup();
|
||||
|
||||
render = (props = {}) => (renderResult = testSetup.renderConsole(props));
|
||||
consoleSelectors = testSetup.selectors;
|
||||
enterCommand = testSetup.enterCommand;
|
||||
});
|
||||
|
||||
describe('and its displayed on the side panel', () => {
|
||||
let renderAndOpenHelpPanel: typeof render;
|
||||
let helpPanelSelectors: HelpSidePanelSelectorsAndActions;
|
||||
|
||||
beforeEach(() => {
|
||||
renderAndOpenHelpPanel = (props) => {
|
||||
render(props);
|
||||
helpPanelSelectors = getHelpSidePanelSelectorsAndActionsMock(renderResult);
|
||||
consoleSelectors.openHelpPanel();
|
||||
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should display the help panel header', () => {
|
||||
renderAndOpenHelpPanel();
|
||||
|
||||
expect(renderResult.getByTestId('test-sidePanel-header')).toHaveTextContent(
|
||||
'HelpUse the add () button to populate a response action to the text bar. Add ' +
|
||||
'additional parameters or comments as necessary.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should display the command list', () => {
|
||||
renderAndOpenHelpPanel();
|
||||
|
||||
expect(renderResult.getByTestId('test-commandList')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should close the side panel when close button is clicked', () => {
|
||||
renderAndOpenHelpPanel();
|
||||
consoleSelectors.closeHelpPanel();
|
||||
|
||||
expect(renderResult.queryByTestId('test-sidePanel')).toBeNull();
|
||||
});
|
||||
|
||||
it('should display helpful tips', () => {
|
||||
renderAndOpenHelpPanel();
|
||||
|
||||
expect(renderResult.getByTestId('test-commandList-helpfulTips')).toHaveTextContent(
|
||||
'Helpful tips:You can enter consecutive response actions — no need to wait for previous ' +
|
||||
'actions to complete.Leaving the response console does not terminate any actions that have ' +
|
||||
'been submitted.Learn moreExternal link(opens in a new tab or window) about response actions ' +
|
||||
'and using the console.'
|
||||
);
|
||||
|
||||
expect(renderResult.getByTestId('test-commandList-helpfulHintDocLink')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display common commands and parameters section', () => {
|
||||
renderAndOpenHelpPanel();
|
||||
|
||||
expect(
|
||||
renderResult.getByTestId('test-commandList-Supportingcommandsparameters')
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should group commands by group label', () => {
|
||||
renderAndOpenHelpPanel();
|
||||
const groups = helpPanelSelectors.getHelpGroupLabels();
|
||||
|
||||
expect(groups).toEqual([
|
||||
'group 1',
|
||||
'Supporting commands & parameters',
|
||||
'group 2',
|
||||
'Other commands',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should display the list of command in the expected order', () => {
|
||||
renderAndOpenHelpPanel();
|
||||
const commands = helpPanelSelectors.getHelpCommandNames('group 1');
|
||||
|
||||
expect(commands).toEqual(['cmd6 --foo', 'cmd1']);
|
||||
});
|
||||
|
||||
it('should hide command if command definition helpHidden is true', () => {
|
||||
const commands = getCommandListMock();
|
||||
commands[0].helpHidden = true;
|
||||
renderAndOpenHelpPanel({ commands });
|
||||
|
||||
expect(renderResult.queryByTestId('test-commandList-group1-cmd1')).toBeNull();
|
||||
});
|
||||
|
||||
it('should disable "add to text bar" button if command definition helpHidden is true', () => {
|
||||
const commands = getCommandListMock();
|
||||
commands[0].helpDisabled = true;
|
||||
renderAndOpenHelpPanel({ commands });
|
||||
|
||||
expect(renderResult.getByTestId('test-commandList-group1-cmd1-addToInput')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should add command to console input when [+] button is clicked', () => {
|
||||
renderAndOpenHelpPanel();
|
||||
renderResult.getByTestId('test-commandList-group1-cmd6-addToInput').click();
|
||||
expect(consoleSelectors.getInputText()).toEqual('cmd6 --foo ');
|
||||
});
|
||||
|
||||
it('should display custom help output when Command service has `getHelp()` defined', async () => {
|
||||
const HelpComponent: React.FunctionComponent = () => {
|
||||
return <div data-test-subj="custom-help">{'help output'}</div>;
|
||||
};
|
||||
render({ HelpComponent });
|
||||
enterCommand('help');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderResult.getByTestId('custom-help')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('And displayed when `help` command is entered', () => {
|
||||
it('should display custom help output when Command service has `getHelp()` defined', async () => {
|
||||
const HelpComponent: React.FunctionComponent = () => {
|
||||
return <div data-test-subj="custom-help">{'help output'}</div>;
|
||||
};
|
||||
render({ HelpComponent });
|
||||
enterCommand('help');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderResult.getByTestId('custom-help')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import React, { memo, useMemo, useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { groupBy, sortBy } from 'lodash';
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiBasicTable,
|
||||
|
@ -24,6 +23,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { sortBy } from 'lodash';
|
||||
import type { CommandDefinition } from '../types';
|
||||
import { useTestIdGenerator } from '../../../hooks/use_test_id_generator';
|
||||
import { useDataTestSubj } from '../hooks/state_selectors/use_data_test_subj';
|
||||
|
@ -33,6 +33,21 @@ import { getCommandNameWithArgs } from '../service/utils';
|
|||
import { ConsoleCodeBlock } from './console_code_block';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
||||
const otherCommandsGroupLabel = i18n.translate(
|
||||
'xpack.securitySolution.console.commandList.otherCommandsGroup.label',
|
||||
{
|
||||
defaultMessage: 'Other commands',
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Takes a string and removes all non-letters/number from it.
|
||||
* @param value
|
||||
*/
|
||||
export const convertToTestId = (value: string): string => {
|
||||
return value.replace(/[^A-Za-z0-9]/g, '');
|
||||
};
|
||||
|
||||
// @ts-expect-error TS2769
|
||||
const StyledEuiBasicTable = styled(EuiBasicTable)`
|
||||
margin-top: ${({ theme: { eui } }) => eui.euiSizeS};
|
||||
|
@ -78,12 +93,10 @@ export interface CommandListProps {
|
|||
}
|
||||
|
||||
export const CommandList = memo<CommandListProps>(({ commands, display = 'default' }) => {
|
||||
const getTestId = useTestIdGenerator(useDataTestSubj());
|
||||
const getTestId = useTestIdGenerator(useDataTestSubj('commandList'));
|
||||
const dispatch = useConsoleStateDispatch();
|
||||
const { docLinks } = useKibana().services;
|
||||
|
||||
const allowedCommands = commands.filter((command) => command.helpHidden !== true);
|
||||
|
||||
const footerMessage = useMemo(() => {
|
||||
return (
|
||||
<EuiDescriptionList
|
||||
|
@ -111,13 +124,6 @@ export const CommandList = memo<CommandListProps>(({ commands, display = 'defaul
|
|||
);
|
||||
}, []);
|
||||
|
||||
const otherCommandsGroupLabel = i18n.translate(
|
||||
'xpack.securitySolution.console.commandList.otherCommandsGroup.label',
|
||||
{
|
||||
defaultMessage: 'Other commands',
|
||||
}
|
||||
);
|
||||
|
||||
const updateInputText = useCallback(
|
||||
(text) => () => {
|
||||
dispatch({
|
||||
|
@ -136,23 +142,62 @@ export const CommandList = memo<CommandListProps>(({ commands, display = 'defaul
|
|||
);
|
||||
|
||||
const commandsByGroups = useMemo(() => {
|
||||
return Object.values(groupBy(allowedCommands, 'helpGroupLabel')).reduce<CommandDefinition[][]>(
|
||||
(acc, current) => {
|
||||
if (current[0].helpGroupPosition !== undefined) {
|
||||
// If it already exists just move it to the end
|
||||
if (acc[current[0].helpGroupPosition]) {
|
||||
acc[acc.length] = acc[current[0].helpGroupPosition];
|
||||
}
|
||||
const helpGroups = new Map<
|
||||
string,
|
||||
{ label: string; position: number; list: CommandDefinition[] }
|
||||
>();
|
||||
|
||||
acc[current[0].helpGroupPosition] = sortBy(current, 'helpCommandPosition');
|
||||
} else if (current.length) {
|
||||
acc.push(sortBy(current, 'helpCommandPosition'));
|
||||
// We only show commands that are no hidden
|
||||
const allowedCommands = commands.filter((command) => command.helpHidden !== true);
|
||||
|
||||
for (const allowedCommand of allowedCommands) {
|
||||
const { helpGroupLabel = otherCommandsGroupLabel, helpGroupPosition = Infinity } =
|
||||
allowedCommand;
|
||||
|
||||
const groupEntry = helpGroups.get(helpGroupLabel);
|
||||
|
||||
if (groupEntry) {
|
||||
groupEntry.list.push(allowedCommand);
|
||||
|
||||
// Its possible (but probably not intentionally) that the same Group Label might
|
||||
// have different positions defined (ex. one has a position, and another does not,
|
||||
// which defaults to `Infinity`. If we detect that here, then update the group
|
||||
// position. In the end, the group label will have the last explicitly defined
|
||||
// position found.
|
||||
if (
|
||||
groupEntry.position === Infinity &&
|
||||
helpGroupPosition !== undefined &&
|
||||
helpGroupPosition !== groupEntry.position
|
||||
) {
|
||||
groupEntry.position = helpGroupPosition;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}, [allowedCommands]);
|
||||
} else {
|
||||
helpGroups.set(allowedCommand.helpGroupLabel as string, {
|
||||
label: helpGroupLabel,
|
||||
position: helpGroupPosition,
|
||||
list: [allowedCommand],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by Group position and return an array of arrays with the list of commands per group
|
||||
return sortBy(Array.from(helpGroups.values()), 'position').map((group) => {
|
||||
// ensure all commands in this group have a `helpCommandPosition`. Those missing one, will
|
||||
// be set to `Infinity` so that they are moved to the end.
|
||||
const groupCommandList = group.list.map((command) => {
|
||||
if (command.helpCommandPosition === undefined) {
|
||||
return {
|
||||
...command,
|
||||
helpCommandPosition: Infinity,
|
||||
};
|
||||
}
|
||||
|
||||
return command;
|
||||
});
|
||||
|
||||
return sortBy(groupCommandList, 'helpCommandPosition');
|
||||
});
|
||||
}, [commands]);
|
||||
|
||||
const getTableItems = useCallback(
|
||||
(
|
||||
|
@ -169,24 +214,35 @@ export const CommandList = memo<CommandListProps>(({ commands, display = 'defaul
|
|||
[commandsByGroup[0]?.helpGroupLabel ?? otherCommandsGroupLabel]: command,
|
||||
}));
|
||||
},
|
||||
[otherCommandsGroupLabel]
|
||||
[]
|
||||
);
|
||||
|
||||
const getTableColumns = useCallback(
|
||||
(commandsByGroup) => {
|
||||
const groupLabel = commandsByGroup[0]?.helpGroupLabel ?? otherCommandsGroupLabel;
|
||||
const groupTestIdSuffix = convertToTestId(groupLabel);
|
||||
|
||||
return [
|
||||
{
|
||||
field: commandsByGroup[0]?.helpGroupLabel ?? otherCommandsGroupLabel,
|
||||
name: commandsByGroup[0]?.helpGroupLabel ?? otherCommandsGroupLabel,
|
||||
field: groupLabel,
|
||||
name: <div data-test-subj={getTestId('group')}>{groupLabel}</div>,
|
||||
render: (command: CommandDefinition) => {
|
||||
const commandNameWithArgs = getCommandNameWithArgs(command);
|
||||
return (
|
||||
<StyledEuiFlexGroup alignItems="center">
|
||||
<StyledEuiFlexGroup
|
||||
alignItems="center"
|
||||
data-test-subj={getTestId(`${groupTestIdSuffix}-${command.name}`)}
|
||||
>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiDescriptionList
|
||||
data-test-subj={getTestId('command')}
|
||||
listItems={[
|
||||
{
|
||||
title: <EuiBadge>{commandNameWithArgs}</EuiBadge>,
|
||||
title: (
|
||||
<EuiBadge data-test-subj={getTestId('commandName')}>
|
||||
{commandNameWithArgs}
|
||||
</EuiBadge>
|
||||
),
|
||||
description: (
|
||||
<>
|
||||
<EuiSpacer size="xs" />
|
||||
|
@ -197,7 +253,6 @@ export const CommandList = memo<CommandListProps>(({ commands, display = 'defaul
|
|||
),
|
||||
},
|
||||
]}
|
||||
data-test-subj={getTestId('commandList-command')}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{command.helpGroupLabel !== HELP_GROUPS.supporting.label &&
|
||||
|
@ -222,6 +277,9 @@ export const CommandList = memo<CommandListProps>(({ commands, display = 'defaul
|
|||
aria-label={`updateTextInputCommand-${command.name}`}
|
||||
onClick={updateInputText(`${commandNameWithArgs} `)}
|
||||
isDisabled={command.helpDisabled === true}
|
||||
data-test-subj={getTestId(
|
||||
`${groupTestIdSuffix}-${command.name}-addToInput`
|
||||
)}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
|
@ -232,7 +290,7 @@ export const CommandList = memo<CommandListProps>(({ commands, display = 'defaul
|
|||
},
|
||||
];
|
||||
},
|
||||
[getTestId, otherCommandsGroupLabel, updateInputText]
|
||||
[getTestId, updateInputText]
|
||||
);
|
||||
|
||||
const getFilteredCommands = useCallback(
|
||||
|
@ -258,7 +316,11 @@ export const CommandList = memo<CommandListProps>(({ commands, display = 'defaul
|
|||
defaultMessage="{learnMore} about response actions and using the console."
|
||||
values={{
|
||||
learnMore: (
|
||||
<EuiLink href={docLinks.links.securitySolution.responseActions} target="_blank">
|
||||
<EuiLink
|
||||
href={docLinks.links.securitySolution.responseActions}
|
||||
target="_blank"
|
||||
data-test-subj={getTestId('helpfulHintDocLink')}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.console.commandList.callout.readMoreLink"
|
||||
defaultMessage="Learn more"
|
||||
|
@ -277,6 +339,7 @@ export const CommandList = memo<CommandListProps>(({ commands, display = 'defaul
|
|||
defaultMessage="Helpful tips:"
|
||||
/>
|
||||
}
|
||||
data-test-subj={getTestId('helpfulTips')}
|
||||
>
|
||||
<ul>
|
||||
{calloutItems.map((item, index) => (
|
||||
|
@ -289,21 +352,24 @@ export const CommandList = memo<CommandListProps>(({ commands, display = 'defaul
|
|||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-test-subj={getTestId()}>
|
||||
{commandsByGroups.map((commandsByGroup, i) => (
|
||||
<StyledEuiBasicTable
|
||||
data-test-subj={getTestId(
|
||||
convertToTestId(commandsByGroup[0].helpGroupLabel ?? otherCommandsGroupLabel)
|
||||
)}
|
||||
key={`styledEuiBasicTable-${i}`}
|
||||
items={getTableItems(commandsByGroup)}
|
||||
columns={getTableColumns(commandsByGroup)}
|
||||
/>
|
||||
))}
|
||||
{callout}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-test-subj={getTestId()}>
|
||||
<EuiSpacer size="s" />
|
||||
{commandsByGroups.map((commandsByGroup) => {
|
||||
const groupLabel = commandsByGroup[0].helpGroupLabel;
|
||||
|
@ -345,7 +411,7 @@ export const CommandList = memo<CommandListProps>(({ commands, display = 'defaul
|
|||
),
|
||||
},
|
||||
]}
|
||||
data-test-subj={getTestId('commandList-command')}
|
||||
data-test-subj={getTestId('command')}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
@ -355,7 +421,7 @@ export const CommandList = memo<CommandListProps>(({ commands, display = 'defaul
|
|||
})}
|
||||
<EuiSpacer size="xl" />
|
||||
{footerMessage}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
CommandList.displayName = 'CommandList';
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { ConsoleProps } from '..';
|
||||
import type { AppContextTestRender } from '../../../../common/mock/endpoint';
|
||||
import { getConsoleTestSetup } from '../mocks';
|
||||
import { HELP_LABEL } from './console_header';
|
||||
|
||||
describe('Console header area', () => {
|
||||
let render: (props?: Partial<ConsoleProps>) => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const testSetup = getConsoleTestSetup();
|
||||
|
||||
render = (props = {}) => (renderResult = testSetup.renderConsole(props));
|
||||
});
|
||||
|
||||
it('should display the help button', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test-header-helpButton').textContent).toEqual(HELP_LABEL);
|
||||
});
|
||||
|
||||
it('should not display a title component', () => {
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('test-header-titleComponentContainer').textContent).toEqual('');
|
||||
});
|
||||
|
||||
it('should show a title component if one was provided', () => {
|
||||
render({ TitleComponent: () => <>{'header component here'}</> });
|
||||
|
||||
expect(renderResult.getByTestId('test-header-titleComponentContainer').textContent).toEqual(
|
||||
'header component here'
|
||||
);
|
||||
});
|
||||
|
||||
it('should open the side panel when help button is clicked', () => {
|
||||
render();
|
||||
renderResult.getByTestId('test-header-helpButton').click();
|
||||
|
||||
expect(renderResult.getByTestId('test-sidePanel')).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -10,11 +10,18 @@ import styled from 'styled-components';
|
|||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useDataTestSubj } from '../hooks/state_selectors/use_data_test_subj';
|
||||
import { useTestIdGenerator } from '../../../hooks/use_test_id_generator';
|
||||
import { useConsoleStateDispatch } from '../hooks/state_selectors/use_console_state_dispatch';
|
||||
import { useWithSidePanel } from '../hooks/state_selectors/use_with_side_panel';
|
||||
import type { ConsoleProps } from '..';
|
||||
|
||||
const HELP_LABEL = i18n.translate('xpack.securitySolution.console.layoutHeader.helpButtonLabel', {
|
||||
export const HELP_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.console.layoutHeader.helpButtonTitle',
|
||||
{ defaultMessage: 'Help' }
|
||||
);
|
||||
|
||||
const HELP_TOOLTIP = i18n.translate('xpack.securitySolution.console.layoutHeader.helpButtonLabel', {
|
||||
defaultMessage: 'Show help',
|
||||
});
|
||||
|
||||
|
@ -28,6 +35,7 @@ export type ConsoleHeaderProps = Pick<ConsoleProps, 'TitleComponent'>;
|
|||
export const ConsoleHeader = memo<ConsoleHeaderProps>(({ TitleComponent }) => {
|
||||
const dispatch = useConsoleStateDispatch();
|
||||
const panelCurrentlyShowing = useWithSidePanel().show;
|
||||
const getTestId = useTestIdGenerator(useDataTestSubj('header'));
|
||||
const isHelpOpen = panelCurrentlyShowing === 'help';
|
||||
|
||||
const handleHelpButtonOnClick = useCallback(() => {
|
||||
|
@ -44,7 +52,11 @@ export const ConsoleHeader = memo<ConsoleHeaderProps>(({ TitleComponent }) => {
|
|||
justifyContent="spaceBetween"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={1} className="eui-textTruncate noThemeOverrides">
|
||||
<EuiFlexItem
|
||||
grow={1}
|
||||
className="eui-textTruncate noThemeOverrides"
|
||||
data-test-subj={getTestId('titleComponentContainer')}
|
||||
>
|
||||
{TitleComponent ? <TitleComponent /> : ''}
|
||||
</EuiFlexItem>
|
||||
{!isHelpOpen && (
|
||||
|
@ -53,9 +65,10 @@ export const ConsoleHeader = memo<ConsoleHeaderProps>(({ TitleComponent }) => {
|
|||
style={{ marginLeft: 'auto' }}
|
||||
onClick={handleHelpButtonOnClick}
|
||||
iconType="help"
|
||||
title={HELP_LABEL}
|
||||
aria-label={HELP_LABEL}
|
||||
title={HELP_TOOLTIP}
|
||||
aria-label={HELP_TOOLTIP}
|
||||
isSelected={isHelpOpen}
|
||||
data-test-subj={getTestId('helpButton')}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.console.layoutHeader.helpButtonTitle"
|
||||
|
|
|
@ -25,29 +25,6 @@ describe('When a Console command is entered by the user', () => {
|
|||
render = (props = {}) => (renderResult = testSetup.renderConsole(props));
|
||||
});
|
||||
|
||||
it('should display all available commands when `help` command is entered', async () => {
|
||||
render();
|
||||
enterCommand('help');
|
||||
|
||||
expect(renderResult.getByTestId('test-helpOutput')).toBeTruthy();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderResult.getAllByTestId('test-commandList-command')).toHaveLength(commands.length);
|
||||
});
|
||||
});
|
||||
|
||||
it('should display custom help output when Command service has `getHelp()` defined', async () => {
|
||||
const HelpComponent: React.FunctionComponent = () => {
|
||||
return <div data-test-subj="custom-help">{'help output'}</div>;
|
||||
};
|
||||
render({ HelpComponent });
|
||||
enterCommand('help');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderResult.getByTestId('custom-help')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear the command output history when `clear` is entered', async () => {
|
||||
render();
|
||||
enterCommand('help');
|
||||
|
|
|
@ -9,6 +9,8 @@ import type { ReactNode } from 'react';
|
|||
import React, { memo } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { useTestIdGenerator } from '../../../../hooks/use_test_id_generator';
|
||||
import { useDataTestSubj } from '../../hooks/state_selectors/use_data_test_subj';
|
||||
|
||||
export interface SidePanelContentLayoutProps {
|
||||
children: ReactNode;
|
||||
|
@ -24,23 +26,30 @@ const StyledEuiFlexItemNoPadding = styled(EuiFlexItem)`
|
|||
*/
|
||||
export const SidePanelContentLayout = memo<SidePanelContentLayoutProps>(
|
||||
({ headerContent, children }) => {
|
||||
const getTestId = useTestIdGenerator(useDataTestSubj('sidePanel'));
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
responsive={false}
|
||||
className="eui-fullHeight"
|
||||
gutterSize="none"
|
||||
data-test-subj={getTestId()}
|
||||
>
|
||||
{headerContent && (
|
||||
<>
|
||||
<EuiFlexItem grow={false} className="layout-container">
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
className="layout-container"
|
||||
data-test-subj={getTestId('header')}
|
||||
>
|
||||
{headerContent}
|
||||
</EuiFlexItem>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
</>
|
||||
)}
|
||||
<StyledEuiFlexItemNoPadding className="eui-scrollBar eui-yScroll layout-container">
|
||||
<div>{children}</div>
|
||||
<div data-test-subj={getTestId('body')}>{children}</div>
|
||||
</StyledEuiFlexItemNoPadding>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ConsoleProps } from '../..';
|
||||
import type { AppContextTestRender } from '../../../../../common/mock/endpoint';
|
||||
import { getConsoleTestSetup } from '../../mocks';
|
||||
import { act } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
describe('When displaying the side panel', () => {
|
||||
let render: (props?: Partial<ConsoleProps>) => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
||||
beforeEach(() => {
|
||||
const testSetup = getConsoleTestSetup();
|
||||
|
||||
render = (props = {}) => {
|
||||
renderResult = testSetup.renderConsole(props);
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
describe('and displaying Help content', () => {
|
||||
let renderAndOpenHelp: typeof render;
|
||||
|
||||
beforeEach(() => {
|
||||
renderAndOpenHelp = (props) => {
|
||||
render(props);
|
||||
act(() => {
|
||||
userEvent.click(renderResult.getByTestId('test-header-helpButton'));
|
||||
});
|
||||
|
||||
expect(renderResult.getByTestId('test-sidePanel')).toBeTruthy();
|
||||
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should display the help panel content', () => {
|
||||
renderAndOpenHelp();
|
||||
|
||||
expect(renderResult.getByTestId('test-sidePanel-helpContent')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -19,11 +19,13 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useTestIdGenerator } from '../../../../hooks/use_test_id_generator';
|
||||
import { CommandList } from '../command_list';
|
||||
import { useWithCommandList } from '../../hooks/state_selectors/use_with_command_list';
|
||||
import { SidePanelContentLayout } from './side_panel_content_layout';
|
||||
import { useWithSidePanel } from '../../hooks/state_selectors/use_with_side_panel';
|
||||
import { useConsoleStateDispatch } from '../../hooks/state_selectors/use_console_state_dispatch';
|
||||
import { useDataTestSubj } from '../../hooks/state_selectors/use_data_test_subj';
|
||||
|
||||
const StyledEuiFlexGroup = styled(EuiFlexGroup)`
|
||||
padding-top: ${({ theme: { eui } }) => eui.euiPanelPaddingModifiers.paddingSmall};
|
||||
|
@ -33,6 +35,7 @@ const StyledEuiFlexGroup = styled(EuiFlexGroup)`
|
|||
export const SidePanelContentManager = memo(() => {
|
||||
const dispatch = useConsoleStateDispatch();
|
||||
const commands = useWithCommandList();
|
||||
const getTestId = useTestIdGenerator(useDataTestSubj('sidePanel'));
|
||||
const show = useWithSidePanel().show;
|
||||
|
||||
const closeHelpPanel = useCallback(() => {
|
||||
|
@ -48,7 +51,7 @@ export const SidePanelContentManager = memo(() => {
|
|||
<>
|
||||
<StyledEuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<EuiTitle size="s" data-test-subj={getTestId('headerTitle')}>
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.console.sidePanel.helpTitle"
|
||||
|
@ -63,6 +66,7 @@ export const SidePanelContentManager = memo(() => {
|
|||
iconType="cross"
|
||||
color="text"
|
||||
onClick={closeHelpPanel}
|
||||
data-test-subj={getTestId('headerCloseButton')}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</StyledEuiFlexGroup>
|
||||
|
@ -80,15 +84,19 @@ export const SidePanelContentManager = memo(() => {
|
|||
);
|
||||
}
|
||||
return null;
|
||||
}, [show, closeHelpPanel]);
|
||||
}, [show, getTestId, closeHelpPanel]);
|
||||
|
||||
const panelBody: ReactNode = useMemo(() => {
|
||||
if (show === 'help') {
|
||||
return <CommandList commands={commands} display="table" />;
|
||||
return (
|
||||
<div data-test-subj={getTestId('helpContent')}>
|
||||
<CommandList commands={commands} display="table" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [commands, show]);
|
||||
}, [commands, getTestId, show]);
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
|
|
|
@ -7,6 +7,16 @@
|
|||
|
||||
import { useConsoleStore } from '../../components/console_state/console_state';
|
||||
|
||||
export const useDataTestSubj = (): string | undefined => {
|
||||
return useConsoleStore().state.dataTestSubj;
|
||||
/**
|
||||
* Returns the `data-test-subj` that was defined when the `Console` was rendered.
|
||||
* Can optionally set a suffix on that value if one is provided
|
||||
*/
|
||||
export const useDataTestSubj = (suffix: string = ''): string => {
|
||||
const dataTestSubj = useConsoleStore().state.dataTestSubj;
|
||||
|
||||
if (!dataTestSubj) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return dataTestSubj + (suffix ? `-${suffix}` : '');
|
||||
};
|
||||
|
|
|
@ -11,6 +11,8 @@ import React, { memo, useEffect } from 'react';
|
|||
import { EuiCode } from '@elastic/eui';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { act } from '@testing-library/react';
|
||||
import { within } from '@testing-library/dom';
|
||||
import { convertToTestId } from './components/command_list';
|
||||
import { Console } from './console';
|
||||
import type {
|
||||
CommandArgumentValueSelectorProps,
|
||||
|
@ -21,7 +23,19 @@ import type {
|
|||
import type { AppContextTestRender } from '../../../common/mock/endpoint';
|
||||
import { createAppRootMockRenderer } from '../../../common/mock/endpoint';
|
||||
|
||||
export interface ConsoleTestSetup {
|
||||
interface ConsoleSelectorsAndActionsMock {
|
||||
getLeftOfCursorInputText: () => string;
|
||||
getRightOfCursorInputText: () => string;
|
||||
getInputText: () => string;
|
||||
openHelpPanel: () => void;
|
||||
closeHelpPanel: () => void;
|
||||
}
|
||||
|
||||
export interface ConsoleTestSetup
|
||||
extends Pick<
|
||||
AppContextTestRender,
|
||||
'startServices' | 'coreStart' | 'depsStart' | 'queryClient' | 'history' | 'setExperimentalFlag'
|
||||
> {
|
||||
renderConsole(props?: Partial<ConsoleProps>): ReturnType<AppContextTestRender['render']>;
|
||||
|
||||
commands: CommandDefinition[];
|
||||
|
@ -38,8 +52,54 @@ export interface ConsoleTestSetup {
|
|||
useKeyboard: boolean;
|
||||
}>
|
||||
): void;
|
||||
|
||||
selectors: ConsoleSelectorsAndActionsMock;
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of jest selectors and actions for interacting with the console
|
||||
* @param dataTestSubj
|
||||
*/
|
||||
export const getConsoleSelectorsAndActionMock = (
|
||||
renderResult: ReturnType<AppContextTestRender['render']>,
|
||||
dataTestSubj: string = 'test'
|
||||
): ConsoleTestSetup['selectors'] => {
|
||||
const getLeftOfCursorInputText: ConsoleSelectorsAndActionsMock['getLeftOfCursorInputText'] =
|
||||
() => {
|
||||
return renderResult.getByTestId(`${dataTestSubj}-cmdInput-leftOfCursor`).textContent ?? '';
|
||||
};
|
||||
const getRightOfCursorInputText: ConsoleSelectorsAndActionsMock['getRightOfCursorInputText'] =
|
||||
() => {
|
||||
return renderResult.getByTestId(`${dataTestSubj}-cmdInput-rightOfCursor`).textContent ?? '';
|
||||
};
|
||||
const getInputText: ConsoleSelectorsAndActionsMock['getInputText'] = () => {
|
||||
return getLeftOfCursorInputText() + getRightOfCursorInputText();
|
||||
};
|
||||
|
||||
const isHelpPanelOpen = (): boolean => {
|
||||
return Boolean(renderResult.queryByTestId(`${dataTestSubj}-sidePanel-helpContent`));
|
||||
};
|
||||
|
||||
const openHelpPanel: ConsoleSelectorsAndActionsMock['openHelpPanel'] = () => {
|
||||
if (!isHelpPanelOpen()) {
|
||||
renderResult.getByTestId(`${dataTestSubj}-header-helpButton`).click();
|
||||
}
|
||||
};
|
||||
const closeHelpPanel: ConsoleSelectorsAndActionsMock['closeHelpPanel'] = () => {
|
||||
if (isHelpPanelOpen()) {
|
||||
renderResult.getByTestId(`${dataTestSubj}-sidePanel-headerCloseButton`).click();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getInputText,
|
||||
getLeftOfCursorInputText,
|
||||
getRightOfCursorInputText,
|
||||
openHelpPanel,
|
||||
closeHelpPanel,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the console in the Render Result and enters the command provided
|
||||
* @param renderResult
|
||||
|
@ -75,17 +135,23 @@ export const enterConsoleCommand = (
|
|||
|
||||
export const getConsoleTestSetup = (): ConsoleTestSetup => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
const { startServices, coreStart, depsStart, queryClient, history, setExperimentalFlag } =
|
||||
mockedContext;
|
||||
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
|
||||
const commandList = getCommandListMock();
|
||||
|
||||
let testSubj: string;
|
||||
|
||||
const renderConsole: ConsoleTestSetup['renderConsole'] = ({
|
||||
prompt = '$$>',
|
||||
commands = commandList,
|
||||
'data-test-subj': dataTestSubj = 'test',
|
||||
...others
|
||||
} = {}) => {
|
||||
testSubj = dataTestSubj;
|
||||
|
||||
return (renderResult = mockedContext.render(
|
||||
<Console prompt={prompt} commands={commands} data-test-subj={dataTestSubj} {...others} />
|
||||
));
|
||||
|
@ -95,10 +161,52 @@ export const getConsoleTestSetup = (): ConsoleTestSetup => {
|
|||
enterConsoleCommand(renderResult, cmd, options);
|
||||
};
|
||||
|
||||
let selectors: ConsoleSelectorsAndActionsMock;
|
||||
const initSelectorsIfNeeded = () => {
|
||||
if (selectors) {
|
||||
return selectors;
|
||||
}
|
||||
|
||||
if (!testSubj) {
|
||||
throw new Error(`no 'dataTestSubj' provided to 'render()'!`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
selectors = getConsoleSelectorsAndActionMock(renderResult, testSubj!);
|
||||
};
|
||||
|
||||
return {
|
||||
startServices,
|
||||
coreStart,
|
||||
depsStart,
|
||||
queryClient,
|
||||
history,
|
||||
setExperimentalFlag,
|
||||
renderConsole,
|
||||
commands: commandList,
|
||||
enterCommand,
|
||||
selectors: {
|
||||
getInputText: () => {
|
||||
initSelectorsIfNeeded();
|
||||
return selectors.getInputText();
|
||||
},
|
||||
getLeftOfCursorInputText: () => {
|
||||
initSelectorsIfNeeded();
|
||||
return selectors.getLeftOfCursorInputText();
|
||||
},
|
||||
getRightOfCursorInputText: () => {
|
||||
initSelectorsIfNeeded();
|
||||
return selectors.getRightOfCursorInputText();
|
||||
},
|
||||
openHelpPanel: () => {
|
||||
initSelectorsIfNeeded();
|
||||
return selectors.openHelpPanel();
|
||||
},
|
||||
closeHelpPanel: () => {
|
||||
initSelectorsIfNeeded();
|
||||
return selectors.closeHelpPanel();
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -142,11 +250,13 @@ export const getCommandListMock = (): CommandDefinition[] => {
|
|||
name: 'cmd1',
|
||||
about: 'a command with no options',
|
||||
RenderComponent: jest.fn(RenderComponent),
|
||||
helpGroupLabel: 'group 1',
|
||||
},
|
||||
{
|
||||
name: 'cmd2',
|
||||
about: 'runs cmd 2',
|
||||
RenderComponent: jest.fn(RenderComponent),
|
||||
helpGroupLabel: 'group 2',
|
||||
args: {
|
||||
file: {
|
||||
about: 'Includes file in the run',
|
||||
|
@ -173,6 +283,7 @@ export const getCommandListMock = (): CommandDefinition[] => {
|
|||
name: 'cmd3',
|
||||
about: 'allows argument to be used multiple times',
|
||||
RenderComponent: jest.fn(RenderComponent),
|
||||
helpGroupPosition: 0,
|
||||
args: {
|
||||
foo: {
|
||||
about: 'foo stuff',
|
||||
|
@ -186,6 +297,7 @@ export const getCommandListMock = (): CommandDefinition[] => {
|
|||
about: 'all options optional, but at least one is required',
|
||||
RenderComponent: jest.fn(RenderComponent),
|
||||
mustHaveArgs: true,
|
||||
helpGroupPosition: 1,
|
||||
args: {
|
||||
foo: {
|
||||
about: 'foo stuff',
|
||||
|
@ -226,6 +338,9 @@ export const getCommandListMock = (): CommandDefinition[] => {
|
|||
mustHaveArgs: true,
|
||||
exampleUsage: 'cmd6 --foo 123',
|
||||
exampleInstruction: 'Enter --foo to execute',
|
||||
helpGroupLabel: 'group 1',
|
||||
helpGroupPosition: 0,
|
||||
helpCommandPosition: 0,
|
||||
args: {
|
||||
foo: {
|
||||
about: 'foo stuff',
|
||||
|
@ -245,6 +360,7 @@ export const getCommandListMock = (): CommandDefinition[] => {
|
|||
name: 'cmd7',
|
||||
about: 'Command with argument selector',
|
||||
RenderComponent: jest.fn(RenderComponent),
|
||||
helpGroupLabel: 'group 2',
|
||||
args: {
|
||||
foo: {
|
||||
about: 'foo stuff',
|
||||
|
@ -273,3 +389,46 @@ export const ArgumentSelectorComponentMock = memo<
|
|||
);
|
||||
});
|
||||
ArgumentSelectorComponentMock.displayName = 'ArgumentSelectorComponentMock';
|
||||
|
||||
export interface HelpSidePanelSelectorsAndActions {
|
||||
getHelpGroupLabels: () => string[];
|
||||
getHelpCommandNames: (forGroup?: string) => string[];
|
||||
}
|
||||
|
||||
export const getHelpSidePanelSelectorsAndActionsMock = (
|
||||
renderResult: ReturnType<AppContextTestRender['render']>,
|
||||
dataTestSubj: string = 'test'
|
||||
): HelpSidePanelSelectorsAndActions => {
|
||||
const getHelpGroupLabels: HelpSidePanelSelectorsAndActions['getHelpGroupLabels'] = () => {
|
||||
// FYI: we're collapsing the labels here because EUI includes mobile elements
|
||||
// in the DOM that have the same test ids
|
||||
return Array.from(
|
||||
new Set(
|
||||
renderResult
|
||||
.getAllByTestId(`${dataTestSubj}-commandList-group`)
|
||||
.map((element) => element.textContent ?? '')
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const getHelpCommandNames: HelpSidePanelSelectorsAndActions['getHelpCommandNames'] = (
|
||||
forGroup
|
||||
) => {
|
||||
let searchContainer = renderResult.container;
|
||||
|
||||
if (forGroup) {
|
||||
searchContainer = renderResult.getByTestId(
|
||||
`${dataTestSubj}-commandList-${convertToTestId(forGroup)}`
|
||||
);
|
||||
}
|
||||
|
||||
return within(searchContainer)
|
||||
.getAllByTestId(`${dataTestSubj}-commandList-commandName`)
|
||||
.map((commandEle) => commandEle.textContent ?? '');
|
||||
};
|
||||
|
||||
return {
|
||||
getHelpGroupLabels,
|
||||
getHelpCommandNames,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -118,7 +118,7 @@ const capabilitiesAndPrivilegesValidator = (command: Command): true | string =>
|
|||
return true;
|
||||
};
|
||||
|
||||
const HELP_GROUPS = Object.freeze({
|
||||
export const HELP_GROUPS = Object.freeze({
|
||||
responseActions: {
|
||||
position: 0,
|
||||
label: i18n.translate('xpack.securitySolution.endpointConsoleCommands.groups.responseActions', {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ConsoleTestSetup, HelpSidePanelSelectorsAndActions } from '../../../console/mocks';
|
||||
import {
|
||||
getConsoleTestSetup,
|
||||
getHelpSidePanelSelectorsAndActionsMock,
|
||||
} from '../../../console/mocks';
|
||||
import { getEndpointConsoleCommands } from '../..';
|
||||
import { EndpointMetadataGenerator } from '../../../../../../common/endpoint/data_generators/endpoint_metadata_generator';
|
||||
import { getEndpointPrivilegesInitialStateMock } from '../../../../../common/components/user_privileges/endpoint/mocks';
|
||||
import { sortBy } from 'lodash';
|
||||
import { HELP_GROUPS } from '../console_commands_definition';
|
||||
|
||||
describe('When displaying Endpoint Response Actions', () => {
|
||||
let render: ConsoleTestSetup['renderConsole'];
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
let consoleSelectors: ConsoleTestSetup['selectors'];
|
||||
let helpPanelSelectors: HelpSidePanelSelectorsAndActions;
|
||||
|
||||
beforeEach(() => {
|
||||
const testSetup = getConsoleTestSetup();
|
||||
|
||||
testSetup.setExperimentalFlag({
|
||||
responseActionGetFileEnabled: true,
|
||||
responseActionExecuteEnabled: true,
|
||||
});
|
||||
|
||||
const endpointMetadata = new EndpointMetadataGenerator().generate();
|
||||
const commands = getEndpointConsoleCommands({
|
||||
endpointAgentId: '123',
|
||||
endpointCapabilities: endpointMetadata.Endpoint.capabilities ?? [],
|
||||
endpointPrivileges: getEndpointPrivilegesInitialStateMock(),
|
||||
});
|
||||
|
||||
consoleSelectors = testSetup.selectors;
|
||||
render = (props = { commands }) => {
|
||||
renderResult = testSetup.renderConsole(props);
|
||||
helpPanelSelectors = getHelpSidePanelSelectorsAndActionsMock(renderResult);
|
||||
|
||||
return renderResult;
|
||||
};
|
||||
});
|
||||
|
||||
it('should display expected help groups', () => {
|
||||
render();
|
||||
consoleSelectors.openHelpPanel();
|
||||
|
||||
expect(helpPanelSelectors.getHelpGroupLabels()).toEqual([
|
||||
...sortBy(Object.values(HELP_GROUPS), 'position').map((group) => group.label),
|
||||
'Supporting commands & parameters',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should display response action commands in the help panel in expected order', () => {
|
||||
render();
|
||||
consoleSelectors.openHelpPanel();
|
||||
const commands = helpPanelSelectors.getHelpCommandNames(HELP_GROUPS.responseActions.label);
|
||||
|
||||
expect(commands).toEqual([
|
||||
'isolate',
|
||||
'release',
|
||||
'status',
|
||||
'processes',
|
||||
'kill-process --pid',
|
||||
'suspend-process --pid',
|
||||
'get-file --path',
|
||||
'execute --command',
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -16,6 +16,7 @@ import { useCallback } from 'react';
|
|||
* @example
|
||||
* // `props['data-test-subj'] = 'abc';
|
||||
* const getTestId = useTestIdGenerator(props['data-test-subj']);
|
||||
* getTestId(); // abc
|
||||
* getTestId('body'); // abc-body
|
||||
* getTestId('some-other-ui-section'); // abc-some-other-ui-section
|
||||
*
|
||||
|
@ -24,11 +25,11 @@ import { useCallback } from 'react';
|
|||
* const getTestId = useTestIdGenerator(props['data-test-subj']);
|
||||
* getTestId('body'); // undefined
|
||||
*/
|
||||
export const useTestIdGenerator = (prefix?: string): ((suffix: string) => string | undefined) => {
|
||||
export const useTestIdGenerator = (prefix?: string): ((suffix?: string) => string | undefined) => {
|
||||
return useCallback(
|
||||
(suffix: string): string | undefined => {
|
||||
(suffix: string = ''): string | undefined => {
|
||||
if (prefix) {
|
||||
return `${prefix}-${suffix}`;
|
||||
return `${prefix}${suffix ? `-${suffix}` : ''}`;
|
||||
}
|
||||
},
|
||||
[prefix]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue