diff --git a/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.tsx
similarity index 91%
rename from x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts
rename to x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.tsx
index 68ad770d0a8bf..eea1561076ae4 100644
--- a/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.ts
+++ b/x-pack/plugins/cases/public/common/lib/kibana/kibana_react.mock.tsx
@@ -30,6 +30,7 @@ interface StartServiceArgs {
export const createStartServicesMock = ({ license }: StartServiceArgs = {}): StartServices => {
const licensingPluginMock = licensingMock.createStart();
+ const triggersActionsUi = triggersActionsUiMock.createStart();
const services = {
...coreMock.createStart(),
@@ -39,7 +40,11 @@ export const createStartServicesMock = ({ license }: StartServiceArgs = {}): Sta
navigateToPrefilledEditor: jest.fn(),
},
security: securityMock.createStart(),
- triggersActionsUi: triggersActionsUiMock.createStart(),
+ triggersActionsUi: {
+ actionTypeRegistry: triggersActionsUi.actionTypeRegistry,
+ alertsTableConfigurationRegistry: triggersActionsUi.alertsTableConfigurationRegistry,
+ getAlertsStateTable: jest.fn().mockReturnValue(
),
+ },
spaces: spacesPluginMock.createStartContract(),
licensing:
license != null
diff --git a/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx b/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx
index 69a66653829bc..49af1aed3a075 100644
--- a/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx
+++ b/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx
@@ -6,10 +6,8 @@
*/
import React from 'react';
-import { act, waitFor, within, screen } from '@testing-library/react';
+import { waitFor, within, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
-import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl';
-import { ConnectorTypes } from '../../../common/api';
import type { AppMockRenderer } from '../../common/mock';
import { createAppMockRenderer } from '../../common/mock';
import '../../common/mock/match_media';
@@ -36,14 +34,13 @@ import {
defaultUseFindCaseUserActions,
} from './mocks';
import type { CaseViewPageProps } from './types';
-import { userProfiles } from '../../containers/user_profiles/api.mock';
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
import { CASE_VIEW_PAGE_TABS } from '../../../common/types';
import { getCaseConnectorsMockResponse } from '../../common/mock/connectors';
import { useInfiniteFindCaseUserActions } from '../../containers/use_infinite_find_case_user_actions';
import { useGetCaseUserActionsStats } from '../../containers/use_get_case_user_actions_stats';
-
-const mockSetTitle = jest.fn();
+import { createQueryWithMarkup } from '../../common/test_utils';
+import { useCasesFeatures } from '../../common/use_cases_features';
jest.mock('../../containers/use_get_action_license');
jest.mock('../../containers/use_update_case');
@@ -58,27 +55,14 @@ jest.mock('../../containers/use_post_push_to_service');
jest.mock('../../containers/use_get_case_connectors');
jest.mock('../../containers/use_get_case_users');
jest.mock('../../containers/user_profiles/use_bulk_get_user_profiles');
+jest.mock('../../common/use_cases_features');
jest.mock('../user_actions/timestamp', () => ({
UserActionTimestamp: () => <>>,
}));
jest.mock('../../common/navigation/hooks');
jest.mock('../../common/hooks');
jest.mock('../connectors/resilient/api');
-jest.mock('../../common/lib/kibana', () => {
- const originalModule = jest.requireActual('../../common/lib/kibana');
- return {
- ...originalModule,
- useKibana: () => {
- const { services } = originalModule.useKibana();
- return {
- services: {
- ...services,
- chrome: { setBreadcrumbs: jest.fn(), docTitle: { change: mockSetTitle } },
- },
- };
- },
- };
-});
+jest.mock('../../common/lib/kibana');
const useFetchCaseMock = useGetCase as jest.Mock;
const useUrlParamsMock = useUrlParams as jest.Mock;
@@ -93,12 +77,14 @@ const useGetCaseConnectorsMock = useGetCaseConnectors as jest.Mock;
const useGetCaseMetricsMock = useGetCaseMetrics as jest.Mock;
const useGetTagsMock = useGetTags as jest.Mock;
const useGetCaseUsersMock = useGetCaseUsers as jest.Mock;
+const useCasesFeaturesMock = useCasesFeatures as jest.Mock;
const mockGetCase = (props: Partial = {}) => {
const data = {
...defaultGetCase.data,
...props.data,
};
+
useFetchCaseMock.mockReturnValue({
...defaultGetCase,
...props,
@@ -127,11 +113,49 @@ const userActionsStats = {
describe('CaseViewPage', () => {
const updateCaseProperty = defaultUpdateCaseState.mutate;
const pushCaseToExternalService = jest.fn();
- const data = caseProps.caseData;
- let appMockRenderer: AppMockRenderer;
const caseConnectors = getCaseConnectorsMockResponse();
const caseUsers = getCaseUsersMockResponse();
+ let appMockRenderer: AppMockRenderer;
+
+ // eslint-disable-next-line prefer-object-spread
+ const originalGetComputedStyle = Object.assign({}, window.getComputedStyle);
+
+ const platinumLicense = licensingMock.createLicense({
+ license: { type: 'platinum' },
+ });
+
+ beforeAll(() => {
+ // The JSDOM implementation is too slow
+ // Especially for dropdowns that try to position themselves
+ // perf issue - https://github.com/jsdom/jsdom/issues/3234
+ Object.defineProperty(window, 'getComputedStyle', {
+ value: (el: HTMLElement) => {
+ /**
+ * This is based on the jsdom implementation of getComputedStyle
+ * https://github.com/jsdom/jsdom/blob/9dae17bf0ad09042cfccd82e6a9d06d3a615d9f4/lib/jsdom/browser/Window.js#L779-L820
+ *
+ * It is missing global style parsing and will only return styles applied directly to an element.
+ * Will not return styles that are global or from emotion
+ */
+ const declaration = new CSSStyleDeclaration();
+ const { style } = el;
+
+ Array.prototype.forEach.call(style, (property: string) => {
+ declaration.setProperty(
+ property,
+ style.getPropertyValue(property),
+ style.getPropertyPriority(property)
+ );
+ });
+
+ return declaration;
+ },
+ configurable: true,
+ writable: true,
+ });
+ });
+
beforeEach(() => {
jest.clearAllMocks();
mockGetCase();
@@ -150,54 +174,26 @@ describe('CaseViewPage', () => {
});
useGetConnectorsMock.mockReturnValue({ data: connectorsMock, isLoading: false });
useGetTagsMock.mockReturnValue({ data: [], isLoading: false });
- const license = licensingMock.createLicense({
- license: { type: 'platinum' },
- });
useGetCaseUsersMock.mockReturnValue({ isLoading: false, data: caseUsers });
-
- appMockRenderer = createAppMockRenderer({ license });
- });
-
- it('should render CaseViewPage', async () => {
- const damagedRaccoonUser = userProfiles[0].user;
- const caseDataWithDamagedRaccoon = {
- ...caseData,
- createdBy: {
- profileUid: userProfiles[0].uid,
- username: damagedRaccoonUser.username,
- fullName: damagedRaccoonUser.full_name,
- email: damagedRaccoonUser.email,
- },
- };
-
- const license = licensingMock.createLicense({
- license: { type: 'platinum' },
+ useCasesFeaturesMock.mockReturnValue({
+ metricsFeatures: ['alerts.count'],
+ pushToServiceAuthorized: true,
+ caseAssignmentAuthorized: true,
+ isAlertsEnabled: true,
+ isSyncAlertsEnabled: true,
});
- const props = { ...caseProps, caseData: caseDataWithDamagedRaccoon };
- appMockRenderer = createAppMockRenderer({ features: { metrics: ['alerts.count'] }, license });
- const result = appMockRenderer.render();
-
- expect(result.getByTestId('header-page-title')).toHaveTextContent(data.title);
- expect(result.getByTestId('case-view-status-dropdown')).toHaveTextContent('Open');
- expect(result.getByTestId('case-view-metrics-panel')).toBeInTheDocument();
- expect(
- within(result.getByTestId('case-view-tag-list')).getByTestId('tag-coke')
- ).toHaveTextContent(data.tags[0]);
-
- expect(
- within(result.getByTestId('case-view-tag-list')).getByTestId('tag-pepsi')
- ).toHaveTextContent(data.tags[1]);
+ appMockRenderer = createAppMockRenderer({ license: platinumLicense });
+ });
- expect(result.getAllByText(data.createdBy.fullName!)[0]).toBeInTheDocument();
+ afterAll(() => {
+ Object.defineProperty(window, 'getComputedStyle', originalGetComputedStyle);
+ });
- expect(
- within(result.getByTestId('description')).getByTestId('scrollable-markdown')
- ).toHaveTextContent(data.description);
+ it('shows the metrics section', async () => {
+ appMockRenderer.render();
- expect(result.getByTestId('case-view-status-action-button')).toHaveTextContent(
- 'Mark in progress'
- );
+ expect(await screen.findByTestId('case-view-metrics-panel')).toBeInTheDocument();
});
it('should show closed indicators in header when case is closed', async () => {
@@ -206,40 +202,9 @@ describe('CaseViewPage', () => {
caseData: basicCaseClosed,
}));
- const result = appMockRenderer.render();
+ appMockRenderer.render();
- expect(result.getByTestId('case-view-status-dropdown')).toHaveTextContent('Closed');
- });
-
- it('should update status', async () => {
- const result = appMockRenderer.render();
-
- const dropdown = result.getByTestId('case-view-status-dropdown');
- userEvent.click(dropdown.querySelector('button')!);
- await waitForEuiPopoverOpen();
- userEvent.click(result.getByTestId('case-view-status-dropdown-closed'));
- const updateObject = updateCaseProperty.mock.calls[0][0];
-
- await waitFor(() => {
- expect(updateCaseProperty).toHaveBeenCalledTimes(1);
- expect(updateObject.updateKey).toEqual('status');
- expect(updateObject.updateValue).toEqual('closed');
- });
- });
-
- it('should update title', async () => {
- const result = appMockRenderer.render();
- const newTitle = 'The new title';
- userEvent.click(result.getByTestId('editable-title-edit-icon'));
- userEvent.clear(result.getByTestId('editable-title-input-field'));
- userEvent.type(result.getByTestId('editable-title-input-field'), newTitle);
- userEvent.click(result.getByTestId('editable-title-submit-btn'));
-
- const updateObject = updateCaseProperty.mock.calls[0][0];
- await waitFor(() => {
- expect(updateObject.updateKey).toEqual('title');
- expect(updateObject.updateValue).toEqual(newTitle);
- });
+ expect(await screen.findByTestId('case-view-status-dropdown')).toHaveTextContent('Closed');
});
it('should push updates on button click', async () => {
@@ -254,11 +219,12 @@ describe('CaseViewPage', () => {
},
}));
- const result = appMockRenderer.render();
+ appMockRenderer.render();
- expect(result.getByTestId('push-to-external-service')).toBeInTheDocument();
+ expect(await screen.findByTestId('edit-connectors')).toBeInTheDocument();
+ expect(await screen.findByTestId('push-to-external-service')).toBeInTheDocument();
- userEvent.click(result.getByTestId('push-to-external-service'));
+ userEvent.click(screen.getByTestId('push-to-external-service'));
await waitFor(() => {
expect(pushCaseToExternalService).toHaveBeenCalled();
@@ -266,7 +232,7 @@ describe('CaseViewPage', () => {
});
it('should disable the push button when connector is invalid', async () => {
- const result = appMockRenderer.render(
+ appMockRenderer.render(
{
}}
/>
);
- await waitFor(() => {
- expect(result.getByTestId('push-to-external-service')).toBeDisabled();
- });
- });
- it('should update connector', async () => {
- const result = appMockRenderer.render(
-
- );
-
- userEvent.click(result.getByTestId('connector-edit').querySelector('button')!);
- userEvent.click(result.getByTestId('dropdown-connectors'));
- await waitForEuiPopoverOpen();
- userEvent.click(result.getByTestId('dropdown-connector-resilient-2'));
-
- await waitFor(() => {
- expect(result.getByTestId('connector-fields-resilient')).toBeInTheDocument();
- });
-
- userEvent.click(result.getByTestId('edit-connectors-submit'));
-
- await waitFor(() => {
- expect(updateCaseProperty).toHaveBeenCalledTimes(1);
- const updateObject = updateCaseProperty.mock.calls[0][0];
- expect(updateObject.updateKey).toEqual('connector');
- expect(updateObject.updateValue).toEqual({
- id: 'resilient-2',
- name: 'My Resilient connector',
- type: ConnectorTypes.resilient,
- fields: {
- incidentTypes: null,
- severityCode: null,
- },
- });
- });
+ expect(await screen.findByTestId('edit-connectors')).toBeInTheDocument();
+ expect(await screen.findByTestId('push-to-external-service')).toBeDisabled();
});
it('should call onComponentInitialized on mount', async () => {
@@ -337,22 +260,17 @@ describe('CaseViewPage', () => {
const useFetchAlertData = jest.fn().mockReturnValue([true]);
useGetCaseUserActionsStatsMock.mockReturnValue({ isLoading: true });
- const result = appMockRenderer.render(
-
- );
- await waitFor(() => {
- expect(result.getByTestId('case-view-loading-content')).toBeInTheDocument();
- expect(result.queryByTestId('user-actions-list')).not.toBeInTheDocument();
- });
+ appMockRenderer.render();
+
+ expect(await screen.findByTestId('case-view-loading-content')).toBeInTheDocument();
+ expect(screen.queryByTestId('user-actions-list')).not.toBeInTheDocument();
});
it('should call show alert details with expected arguments', async () => {
const showAlertDetails = jest.fn();
- const result = appMockRenderer.render(
-
- );
+ appMockRenderer.render();
- userEvent.click(result.getAllByTestId('comment-action-show-alert-alert-action-id')[1]);
+ userEvent.click((await screen.findAllByTestId('comment-action-show-alert-alert-action-id'))[1]);
await waitFor(() => {
expect(showAlertDetails).toHaveBeenCalledWith('alert-id-1', 'alert-index-1');
@@ -360,23 +278,23 @@ describe('CaseViewPage', () => {
});
it('should show the rule name', async () => {
- const result = appMockRenderer.render();
+ appMockRenderer.render();
- await waitFor(() => {
- expect(
- result
- .getAllByTestId('user-action-alert-comment-create-action-alert-action-id')[1]
- .querySelector('.euiCommentEvent__headerEvent')
- ).toHaveTextContent('added an alert from Awesome rule');
- });
+ expect(
+ (
+ await screen.findAllByTestId('user-action-alert-comment-create-action-alert-action-id')
+ )[1].querySelector('.euiCommentEvent__headerEvent')
+ ).toHaveTextContent('added an alert from Awesome rule');
});
it('should update settings', async () => {
- const result = appMockRenderer.render();
- userEvent.click(result.getByTestId('sync-alerts-switch'));
- const updateObject = updateCaseProperty.mock.calls[0][0];
+ appMockRenderer.render();
+
+ userEvent.click(await screen.findByTestId('sync-alerts-switch'));
await waitFor(() => {
+ const updateObject = updateCaseProperty.mock.calls[0][0];
+
expect(updateObject.updateKey).toEqual('settings');
expect(updateObject.updateValue).toEqual({ syncAlerts: false });
});
@@ -385,95 +303,57 @@ describe('CaseViewPage', () => {
it('should show the correct connector name on the push button', async () => {
useGetConnectorsMock.mockImplementation(() => ({ data: connectorsMock, isLoading: false }));
- const result = appMockRenderer.render(
+ appMockRenderer.render(
);
- await waitFor(() => {
- expect(result.getByTestId('push-to-external-service')).toHaveTextContent(
- 'My Resilient connector'
- );
- });
+ expect(await screen.findByTestId('edit-connectors')).toBeInTheDocument();
+ expect(await screen.findByText('Update My Resilient connector incident')).toBeInTheDocument();
});
describe('Callouts', () => {
+ const errorText =
+ 'The connector used to send updates to the external service has been deleted or you do not have the appropriate licenseExternal link(opens in a new tab or window) to use it. To update cases in external systems, select a different connector or create a new one.';
+
it('it shows the danger callout when a connector has been deleted', async () => {
useGetConnectorsMock.mockImplementation(() => ({ data: [], isLoading: false }));
- const result = appMockRenderer.render();
+ appMockRenderer.render();
- expect(result.container.querySelector('.euiCallOut--danger')).toBeInTheDocument();
+ expect(await screen.findByTestId('edit-connectors')).toBeInTheDocument();
+
+ const getByText = createQueryWithMarkup(screen.getByText);
+ expect(getByText(errorText)).toBeInTheDocument();
});
it('it does NOT shows the danger callout when connectors are loading', async () => {
useGetConnectorsMock.mockImplementation(() => ({ data: [], isLoading: true }));
- const result = appMockRenderer.render();
+ appMockRenderer.render();
- expect(result.container.querySelector('.euiCallOut--danger')).not.toBeInTheDocument();
+ expect(await screen.findByTestId('edit-connectors')).toBeInTheDocument();
+ expect(
+ screen.queryByTestId('case-callout-a25a5b368b6409b179ef4b6c5168244f')
+ ).not.toBeInTheDocument();
});
});
- // FLAKY: https://github.com/elastic/kibana/issues/149775
- // FLAKY: https://github.com/elastic/kibana/issues/149776
- // FLAKY: https://github.com/elastic/kibana/issues/149777
- // FLAKY: https://github.com/elastic/kibana/issues/149778
- // FLAKY: https://github.com/elastic/kibana/issues/149779
- // FLAKY: https://github.com/elastic/kibana/issues/149780
- // FLAKY: https://github.com/elastic/kibana/issues/149781
- // FLAKY: https://github.com/elastic/kibana/issues/149782
- // FLAKY: https://github.com/elastic/kibana/issues/153335
- // FLAKY: https://github.com/elastic/kibana/issues/153336
- describe.skip('Tabs', () => {
- jest.mock('@kbn/kibana-react-plugin/public', () => ({
- useKibana: () => ({
- services: {
- application: {
- capabilities: {
- fakeCases: {
- create_cases: true,
- read_cases: true,
- update_cases: true,
- delete_cases: true,
- push_cases: true,
- },
- },
- },
- cases: {
- ui: {
- getCasesContext: () => null,
- },
- helpers: {
- getUICapabilities: () => ({
- all: true,
- read: true,
- create: true,
- update: true,
- delete: true,
- push: true,
- }),
- },
- },
- notifications: {
- toasts: {
- addDanger: () => {},
- },
- },
- },
- }),
- }));
+ describe('Tabs', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
it('renders tabs correctly', async () => {
- const result = appMockRenderer.render();
- await act(async () => {
- expect(result.getByTestId('case-view-tab-title-activity')).toBeTruthy();
- expect(result.getByTestId('case-view-tab-title-alerts')).toBeTruthy();
- expect(result.getByTestId('case-view-tab-title-files')).toBeTruthy();
- });
+ appMockRenderer.render();
+
+ expect(await screen.findByRole('tablist')).toBeInTheDocument();
+
+ expect(await screen.findByTestId('case-view-tab-title-activity')).toBeInTheDocument();
+ expect(await screen.findByTestId('case-view-tab-title-alerts')).toBeInTheDocument();
+ expect(await screen.findByTestId('case-view-tab-title-files')).toBeInTheDocument();
});
it('renders the activity tab by default', async () => {
- const result = appMockRenderer.render();
- await act(async () => {
- expect(result.getByTestId('case-view-tab-content-activity')).toBeTruthy();
- });
+ appMockRenderer.render();
+ expect(await screen.findByTestId('case-view-tab-content-activity')).toBeInTheDocument();
});
it('renders the alerts tab when the query parameter tabId has alerts', async () => {
@@ -482,10 +362,11 @@ describe('CaseViewPage', () => {
tabId: CASE_VIEW_PAGE_TABS.ALERTS,
},
});
- const result = appMockRenderer.render();
- await act(async () => {
- expect(result.getByTestId('case-view-tab-content-alerts')).toBeTruthy();
- });
+
+ appMockRenderer.render();
+
+ expect(await screen.findByTestId('case-view-tab-content-alerts')).toBeInTheDocument();
+ expect(await screen.findByTestId('alerts-table')).toBeInTheDocument();
});
it('renders the activity tab when the query parameter tabId has activity', async () => {
@@ -494,10 +375,10 @@ describe('CaseViewPage', () => {
tabId: CASE_VIEW_PAGE_TABS.ACTIVITY,
},
});
- const result = appMockRenderer.render();
- await act(async () => {
- expect(result.getByTestId('case-view-tab-content-activity')).toBeTruthy();
- });
+
+ appMockRenderer.render();
+
+ expect(await screen.findByTestId('case-view-tab-content-activity')).toBeInTheDocument();
});
it('renders the activity tab when the query parameter tabId has an unknown value', async () => {
@@ -506,18 +387,20 @@ describe('CaseViewPage', () => {
tabId: 'what-is-love',
},
});
- const result = appMockRenderer.render();
- await act(async () => {
- expect(result.getByTestId('case-view-tab-content-activity')).toBeTruthy();
- expect(result.queryByTestId('case-view-tab-content-alerts')).toBeFalsy();
- });
+
+ appMockRenderer.render();
+
+ expect(await screen.findByTestId('case-view-tab-content-activity')).toBeInTheDocument();
+ expect(screen.queryByTestId('case-view-tab-content-alerts')).not.toBeInTheDocument();
});
it('navigates to the activity tab when the activity tab is clicked', async () => {
const navigateToCaseViewMock = useCaseViewNavigationMock().navigateToCaseView;
- const result = appMockRenderer.render();
- userEvent.click(result.getByTestId('case-view-tab-title-activity'));
- await act(async () => {
+ appMockRenderer.render();
+
+ userEvent.click(await screen.findByTestId('case-view-tab-title-activity'));
+
+ await waitFor(() => {
expect(navigateToCaseViewMock).toHaveBeenCalledWith({
detailName: caseData.id,
tabId: CASE_VIEW_PAGE_TABS.ACTIVITY,
@@ -527,9 +410,11 @@ describe('CaseViewPage', () => {
it('navigates to the alerts tab when the alerts tab is clicked', async () => {
const navigateToCaseViewMock = useCaseViewNavigationMock().navigateToCaseView;
- const result = appMockRenderer.render();
- userEvent.click(result.getByTestId('case-view-tab-title-alerts'));
- await act(async () => {
+ appMockRenderer.render();
+
+ userEvent.click(await screen.findByTestId('case-view-tab-title-alerts'));
+
+ await waitFor(async () => {
expect(navigateToCaseViewMock).toHaveBeenCalledWith({
detailName: caseData.id,
tabId: CASE_VIEW_PAGE_TABS.ALERTS,
@@ -539,45 +424,45 @@ describe('CaseViewPage', () => {
it('should display the alerts tab when the feature is enabled', async () => {
appMockRenderer = createAppMockRenderer({ features: { alerts: { enabled: true } } });
- const result = appMockRenderer.render();
- await act(async () => {
- expect(result.queryByTestId('case-view-tab-title-activity')).toBeTruthy();
- expect(result.queryByTestId('case-view-tab-title-alerts')).toBeTruthy();
- });
+ appMockRenderer.render();
+
+ expect(await screen.findByTestId('case-view-tab-title-activity')).toBeInTheDocument();
+ expect(await screen.findByTestId('case-view-tab-title-alerts')).toBeInTheDocument();
});
it('should not display the alerts tab when the feature is disabled', async () => {
appMockRenderer = createAppMockRenderer({ features: { alerts: { enabled: false } } });
- const result = appMockRenderer.render();
- await act(async () => {
- expect(result.queryByTestId('case-view-tab-title-activity')).toBeTruthy();
- expect(result.queryByTestId('case-view-tab-title-alerts')).toBeFalsy();
- });
+ appMockRenderer.render();
+
+ expect(await screen.findByTestId('case-view-tab-title-activity')).toBeInTheDocument();
+ expect(screen.queryByTestId('case-view-tab-title-alerts')).not.toBeInTheDocument();
});
it('should not show the experimental badge on the alerts table', async () => {
- appMockRenderer = createAppMockRenderer({ features: { alerts: { isExperimental: false } } });
- const result = appMockRenderer.render();
-
- await act(async () => {
- expect(result.queryByTestId('case-view-alerts-table-experimental-badge')).toBeFalsy();
+ appMockRenderer = createAppMockRenderer({
+ features: { alerts: { isExperimental: false } },
});
+ appMockRenderer.render();
+
+ expect(
+ screen.queryByTestId('case-view-alerts-table-experimental-badge')
+ ).not.toBeInTheDocument();
});
it('should show the experimental badge on the alerts table', async () => {
appMockRenderer = createAppMockRenderer({ features: { alerts: { isExperimental: true } } });
- const result = appMockRenderer.render();
+ appMockRenderer.render();
- await act(async () => {
- expect(result.queryByTestId('case-view-alerts-table-experimental-badge')).toBeTruthy();
- });
+ expect(
+ await screen.findByTestId('case-view-alerts-table-experimental-badge')
+ ).toBeInTheDocument();
});
describe('description', () => {
it('renders the description correctly', async () => {
appMockRenderer.render();
- const description = within(screen.getByTestId('description'));
+ const description = within(await screen.findByTestId('description'));
expect(await description.findByText(caseData.description)).toBeInTheDocument();
});
@@ -591,37 +476,7 @@ describe('CaseViewPage', () => {
appMockRenderer.render();
- await waitFor(() => {
- expect(screen.getByTestId('description')).toBeInTheDocument();
- });
- });
-
- it.skip('it should persist the draft of new comment while description is updated', async () => {
- const newComment = 'another cool comment';
-
- appMockRenderer.render();
-
- userEvent.click(await screen.findByTestId('user-actions-filter-activity-button-all'));
-
- userEvent.type(await screen.findByTestId('euiMarkdownEditorTextArea'), newComment);
-
- userEvent.click(await screen.findByTestId('description-edit-icon'));
-
- userEvent.type(screen.getAllByTestId('euiMarkdownEditorTextArea')[0], 'Edited!');
-
- userEvent.click(screen.getByTestId('editable-save-markdown'));
-
- expect(await screen.findByTestId('euiMarkdownEditorTextArea')).toHaveTextContent(
- newComment
- );
- });
- });
-
- describe('breadcrumbs', () => {
- it('should set the cases title', () => {
- appMockRenderer.render();
-
- expect(mockSetTitle).toHaveBeenCalledWith([caseProps.caseData.title, 'Cases', 'Test']);
+ expect(await screen.findByTestId('description')).toBeInTheDocument();
});
});
});
diff --git a/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.tsx b/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.tsx
index 828753d0a410b..e3b5395bbd03e 100644
--- a/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.tsx
+++ b/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.tsx
@@ -262,7 +262,7 @@ export const CaseViewActivity = ({
) : null}
-
+
{caseAssignmentAuthorized ? (
<>
diff --git a/x-pack/plugins/cases/public/components/edit_connector/index.tsx b/x-pack/plugins/cases/public/components/edit_connector/index.tsx
index c0c98ceaa6db3..875e2ce6ffe23 100644
--- a/x-pack/plugins/cases/public/components/edit_connector/index.tsx
+++ b/x-pack/plugins/cases/public/components/edit_connector/index.tsx
@@ -90,7 +90,7 @@ export const EditConnector = React.memo(
!needsToBePushed;
return (
-
+
= ({
isDisabled,
}) => {
return (
-
+
{SEVERITY_TITLE}
diff --git a/x-pack/test/functional/services/cases/single_case_view.ts b/x-pack/test/functional/services/cases/single_case_view.ts
index c3e668557af57..961ee05a33a52 100644
--- a/x-pack/test/functional/services/cases/single_case_view.ts
+++ b/x-pack/test/functional/services/cases/single_case_view.ts
@@ -58,6 +58,17 @@ export function CasesSingleViewServiceProvider({ getService, getPageObject }: Ft
});
},
+ async addComment(comment: string) {
+ const addCommentElement = await find.byCssSelector(
+ '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea'
+ );
+
+ await addCommentElement.focus();
+ await addCommentElement.type(comment);
+
+ await this.submitComment();
+ },
+
async addVisualizationToNewComment(visName: string) {
// open saved object finder
const addCommentElement = await testSubjects.find('add-comment');
diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts
index dd584a51fa979..cfa642fd25cdd 100644
--- a/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/cases/group1/view_case.ts
@@ -9,6 +9,8 @@ import expect from '@kbn/expect';
import { v4 as uuidv4 } from 'uuid';
import { CaseStatuses } from '@kbn/cases-plugin/common';
import { CaseSeverity } from '@kbn/cases-plugin/common/api';
+import { setTimeout as setTimeoutAsync } from 'timers/promises';
+
import { FtrProviderContext } from '../../../ftr_provider_context';
import {
createUsersAndRoles,
@@ -28,6 +30,29 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
const browser = getService('browser');
describe('View case', () => {
+ describe('page', () => {
+ createOneCaseBeforeDeleteAllAfter(getPageObject, getService);
+
+ it('should show the case view page correctly', async () => {
+ await testSubjects.existOrFail('case-view-title');
+ await testSubjects.existOrFail('header-page-supplements');
+
+ await testSubjects.existOrFail('case-view-tab-title-activity');
+ await testSubjects.existOrFail('case-view-tab-title-files');
+ await testSubjects.existOrFail('description');
+
+ await testSubjects.existOrFail('case-view-activity');
+
+ await testSubjects.existOrFail('case-view-assignees');
+ await testSubjects.existOrFail('sidebar-severity');
+ await testSubjects.existOrFail('case-view-user-list-reporter');
+ await testSubjects.existOrFail('case-view-user-list-participants');
+ await testSubjects.existOrFail('case-view-tag-list');
+ await testSubjects.existOrFail('cases-categories');
+ await testSubjects.existOrFail('sidebar-connectors');
+ });
+ });
+
describe('properties', () => {
createOneCaseBeforeDeleteAllAfter(getPageObject, getService);
@@ -54,6 +79,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
);
await commentArea.focus();
await commentArea.type('Test comment from automation');
+
await testSubjects.click('submit-comment');
// validate user action
@@ -182,144 +208,218 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
);
});
});
+ });
- describe('draft comments', () => {
- createOneCaseBeforeDeleteAllAfter(getPageObject, getService);
+ describe('draft comments', () => {
+ createOneCaseBeforeEachDeleteAllAfterEach(getPageObject, getService);
- it('persists new comment when status is updated in dropdown', async () => {
- const commentArea = await find.byCssSelector(
- '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea'
- );
- await commentArea.focus();
- await commentArea.type('Test comment from automation');
+ it('persists new comment when status is updated in dropdown', async () => {
+ const commentArea = await find.byCssSelector(
+ '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea'
+ );
+ await commentArea.focus();
+ await commentArea.type('Test comment from automation');
- await cases.common.changeCaseStatusViaDropdownAndVerify(CaseStatuses['in-progress']);
- // validate user action
- await find.byCssSelector(
- '[data-test-subj*="status-update-action"] [data-test-subj="case-status-badge-in-progress"]'
- );
- // validates dropdown tag
- await testSubjects.existOrFail(
- 'case-view-status-dropdown > case-status-badge-popover-button-in-progress'
- );
+ await cases.common.changeCaseStatusViaDropdownAndVerify(CaseStatuses['in-progress']);
+ // validate user action
+ await find.byCssSelector(
+ '[data-test-subj*="status-update-action"] [data-test-subj="case-status-badge-in-progress"]'
+ );
+ // validates dropdown tag
+ await testSubjects.existOrFail(
+ 'case-view-status-dropdown > case-status-badge-popover-button-in-progress'
+ );
- await testSubjects.click('submit-comment');
+ await testSubjects.click('submit-comment');
- // validate user action
- const newComment = await find.byCssSelector(
- '[data-test-subj*="comment-create-action"] [data-test-subj="scrollable-markdown"]'
- );
- expect(await newComment.getVisibleText()).equal('Test comment from automation');
- });
+ // validate user action
+ const newComment = await find.byCssSelector(
+ '[data-test-subj*="comment-create-action"] [data-test-subj="scrollable-markdown"]'
+ );
+ expect(await newComment.getVisibleText()).equal('Test comment from automation');
+ });
- it('persists new comment when case is closed through the close case button', async () => {
- const commentArea = await find.byCssSelector(
- '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea'
- );
- await commentArea.focus();
- await commentArea.type('Test comment from automation');
+ it('persists new comment when case is closed through the close case button', async () => {
+ const commentArea = await find.byCssSelector(
+ '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea'
+ );
+ await commentArea.focus();
+ await commentArea.type('Test comment from automation');
- await cases.common.changeCaseStatusViaDropdownAndVerify(CaseStatuses['in-progress']);
- await header.waitUntilLoadingHasFinished();
- await testSubjects.click('case-view-status-action-button');
- await header.waitUntilLoadingHasFinished();
+ await cases.common.changeCaseStatusViaDropdownAndVerify(CaseStatuses['in-progress']);
+ await header.waitUntilLoadingHasFinished();
+ await testSubjects.click('case-view-status-action-button');
+ await header.waitUntilLoadingHasFinished();
- await testSubjects.existOrFail(
- 'header-page-supplements > case-status-badge-popover-button-closed',
- {
- timeout: 5000,
- }
- );
+ await testSubjects.existOrFail(
+ 'header-page-supplements > case-status-badge-popover-button-closed',
+ {
+ timeout: 5000,
+ }
+ );
- // validate user action
- await find.byCssSelector(
- '[data-test-subj*="status-update-action"] [data-test-subj="case-status-badge-closed"]'
- );
- // validates dropdown tag
- await testSubjects.existOrFail(
- 'case-view-status-dropdown >case-status-badge-popover-button-closed'
- );
+ // validate user action
+ await find.byCssSelector(
+ '[data-test-subj*="status-update-action"] [data-test-subj="case-status-badge-closed"]'
+ );
+ // validates dropdown tag
+ await testSubjects.existOrFail(
+ 'case-view-status-dropdown >case-status-badge-popover-button-closed'
+ );
- await testSubjects.click('submit-comment');
+ await testSubjects.click('submit-comment');
- // validate user action
- const newComment = await find.byCssSelector(
- '[data-test-subj*="comment-create-action"] [data-test-subj="scrollable-markdown"]'
- );
- expect(await newComment.getVisibleText()).equal('Test comment from automation');
- });
+ // validate user action
+ const newComment = await find.byCssSelector(
+ '[data-test-subj*="comment-create-action"] [data-test-subj="scrollable-markdown"]'
+ );
+ expect(await newComment.getVisibleText()).equal('Test comment from automation');
+ });
- it('persists new comment to the case when user goes to case list table and comes back to the case', async () => {
- const commentArea = await find.byCssSelector(
- '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea'
- );
- await commentArea.focus();
- await commentArea.type('Test comment from automation');
+ it('persists new comment to the case when user goes to case list table and comes back to the case', async () => {
+ const comment = 'Test comment from automation';
- await testSubjects.click('backToCases');
+ let commentArea = await find.byCssSelector(
+ '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea'
+ );
+ await commentArea.focus();
+ await commentArea.type(comment);
- const caseLink = await find.byCssSelector('[data-test-subj="case-details-link"');
+ /**
+ * We need to wait for some time to
+ * give the localStorage a change to persist
+ * the comment. Otherwise, the test navigates to
+ * fast to the cases table and the comment is not
+ * persisted
+ */
+ await setTimeoutAsync(2000);
- caseLink.click();
+ await testSubjects.click('backToCases');
- await testSubjects.click('submit-comment');
+ await cases.casesTable.waitForCasesToBeListed();
+ await cases.casesTable.goToFirstListedCase();
+ await header.waitUntilLoadingHasFinished();
- // validate user action
- const newComment = await find.byCssSelector(
- '[data-test-subj*="comment-create-action"] [data-test-subj="scrollable-markdown"]'
- );
- expect(await newComment.getVisibleText()).equal('Test comment from automation');
- });
+ commentArea = await find.byCssSelector(
+ '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea'
+ );
- it('shows unsaved comment message when page is refreshed', async () => {
- await testSubjects.click('property-actions-user-action-ellipses');
+ expect(await commentArea.getVisibleText()).equal(comment);
- await header.waitUntilLoadingHasFinished();
+ await testSubjects.click('submit-comment');
- await testSubjects.click('property-actions-user-action-pencil');
+ // validate user action
+ const newComment = await find.byCssSelector(
+ '[data-test-subj*="comment-create-action"] [data-test-subj="scrollable-markdown"]'
+ );
- await header.waitUntilLoadingHasFinished();
+ expect(await newComment.getVisibleText()).equal(comment);
+ });
- const editCommentTextArea = await find.byCssSelector(
- '[data-test-subj*="editable-markdown-form"] textarea.euiMarkdownEditorTextArea'
- );
+ it('shows unsaved comment message when page is refreshed', async () => {
+ await cases.singleCase.addComment('my comment');
+ await header.waitUntilLoadingHasFinished();
- await header.waitUntilLoadingHasFinished();
+ await testSubjects.click('property-actions-user-action-ellipses');
+ await header.waitUntilLoadingHasFinished();
+ await testSubjects.click('property-actions-user-action-pencil');
+ await header.waitUntilLoadingHasFinished();
- await editCommentTextArea.focus();
- await editCommentTextArea.type('Edited comment');
+ const editCommentTextArea = await find.byCssSelector(
+ '[data-test-subj*="editable-markdown-form"] textarea.euiMarkdownEditorTextArea'
+ );
- await header.waitUntilLoadingHasFinished();
+ await header.waitUntilLoadingHasFinished();
- await browser.refresh();
+ await editCommentTextArea.focus();
+ await editCommentTextArea.type('Edited comment');
- await header.waitUntilLoadingHasFinished();
+ /**
+ * We need to wait for some time to
+ * give the localStorage a change to persist
+ * the comment. Otherwise, the test navigates to
+ * fast to the cases table and the comment is not
+ * persisted
+ */
+ await setTimeoutAsync(2000);
- await testSubjects.existOrFail('user-action-comment-unsaved-draft');
- });
+ await header.waitUntilLoadingHasFinished();
+ await browser.refresh();
- it('shows unsaved description message when page is refreshed', async () => {
- await testSubjects.click('description-edit-icon');
+ await header.waitUntilLoadingHasFinished();
- await header.waitUntilLoadingHasFinished();
+ await testSubjects.existOrFail('user-action-comment-unsaved-draft');
+ });
- const editCommentTextArea = await find.byCssSelector(
- '[data-test-subj*="editable-markdown-form"] textarea.euiMarkdownEditorTextArea'
- );
+ it('shows unsaved description message when page is refreshed', async () => {
+ await testSubjects.click('description-edit-icon');
- await header.waitUntilLoadingHasFinished();
+ await header.waitUntilLoadingHasFinished();
- await editCommentTextArea.focus();
- await editCommentTextArea.type('Edited description');
+ const editCommentTextArea = await find.byCssSelector(
+ '[data-test-subj*="editable-markdown-form"] textarea.euiMarkdownEditorTextArea'
+ );
- await header.waitUntilLoadingHasFinished();
+ await header.waitUntilLoadingHasFinished();
- await browser.refresh();
+ await editCommentTextArea.focus();
+ await editCommentTextArea.type('Edited description');
- await header.waitUntilLoadingHasFinished();
+ /**
+ * We need to wait for some time to
+ * give the localStorage a change to persist
+ * the comment. Otherwise, the test navigates to
+ * fast to the cases table and the comment is not
+ * persisted
+ */
+ await setTimeoutAsync(2000);
- await testSubjects.existOrFail('description-unsaved-draft');
- });
+ await header.waitUntilLoadingHasFinished();
+
+ await browser.refresh();
+
+ await header.waitUntilLoadingHasFinished();
+
+ await testSubjects.existOrFail('description-unsaved-draft');
+ });
+
+ /**
+ * There is this bug https://github.com/elastic/kibana/issues/157280
+ * where this test randomly reproduces thus making the test flaky.
+ * Skipping for now until we fix it.
+ */
+ it.skip('should persist the draft of new comment while description is updated', async () => {
+ let commentArea = await find.byCssSelector(
+ '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea'
+ );
+
+ await commentArea.focus();
+ await commentArea.type('Test comment from automation');
+
+ await testSubjects.click('description-edit-icon');
+
+ await header.waitUntilLoadingHasFinished();
+
+ const description = await find.byCssSelector(
+ '[data-test-subj*="editable-markdown-form"] textarea.euiMarkdownEditorTextArea'
+ );
+
+ await header.waitUntilLoadingHasFinished();
+
+ await description.focus();
+ await description.type('Edited description');
+
+ await testSubjects.click('editable-save-markdown');
+ await header.waitUntilLoadingHasFinished();
+
+ await browser.refresh();
+ await header.waitUntilLoadingHasFinished();
+
+ commentArea = await find.byCssSelector(
+ '[data-test-subj="add-comment"] textarea.euiMarkdownEditorTextArea'
+ );
+
+ expect(await commentArea.getVisibleText()).to.be('Test comment from automation');
});
});
@@ -770,6 +870,23 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
});
});
});
+
+ describe('breadcrumbs', () => {
+ after(async () => {
+ await cases.api.deleteAllCases();
+ });
+
+ it('should set the cases title', async () => {
+ const theCase = await createAndNavigateToCase(getPageObject, getService);
+ const firstBreadcrumb = await testSubjects.getVisibleText('breadcrumb first');
+ const middleBreadcrumb = await testSubjects.getVisibleText('breadcrumb');
+ const lastBreadcrumb = await testSubjects.getVisibleText('breadcrumb last');
+
+ expect(firstBreadcrumb).to.be('Management');
+ expect(middleBreadcrumb).to.be('Cases');
+ expect(lastBreadcrumb).to.be(theCase.title);
+ });
+ });
});
};
@@ -788,6 +905,21 @@ const createOneCaseBeforeDeleteAllAfter = (
});
};
+const createOneCaseBeforeEachDeleteAllAfterEach = (
+ getPageObject: FtrProviderContext['getPageObject'],
+ getService: FtrProviderContext['getService']
+) => {
+ const cases = getService('cases');
+
+ beforeEach(async () => {
+ await createAndNavigateToCase(getPageObject, getService);
+ });
+
+ afterEach(async () => {
+ await cases.api.deleteAllCases();
+ });
+};
+
const createAndNavigateToCase = async (
getPageObject: FtrProviderContext['getPageObject'],
getService: FtrProviderContext['getService']
@@ -796,8 +928,10 @@ const createAndNavigateToCase = async (
const cases = getService('cases');
await cases.navigation.navigateToApp();
- await cases.api.createNthRandomCases(1);
+ const theCase = await cases.api.createCase();
await cases.casesTable.waitForCasesToBeListed();
await cases.casesTable.goToFirstListedCase();
await header.waitUntilLoadingHasFinished();
+
+ return theCase;
};