diff --git a/x-pack/plugins/cases/public/components/case_action_bar/index.test.tsx b/x-pack/plugins/cases/public/components/case_action_bar/index.test.tsx
index 2429e68cbefa5..4386f996608f6 100644
--- a/x-pack/plugins/cases/public/components/case_action_bar/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/case_action_bar/index.test.tsx
@@ -10,7 +10,7 @@ import { mount } from 'enzyme';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
-import { basicCase } from '../../containers/mock';
+import { basicCase, basicCaseClosed } from '../../containers/mock';
import type { CaseActionBarProps } from '.';
import { CaseActionBar } from '.';
import {
@@ -74,6 +74,18 @@ describe('CaseActionBar', () => {
);
});
+ it('should show the status as closed when the case is closed', () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find(`[data-test-subj="case-view-status-dropdown"]`).first().text()).toBe(
+ 'Closed'
+ );
+ });
+
it('should show the correct date', () => {
const wrapper = mount(
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 a2fb4a8ae1a08..59ca9a98de12e 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,248 +6,104 @@
*/
import React from 'react';
-import { waitFor, within, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
+import { waitFor, screen } from '@testing-library/react';
import type { AppMockRenderer } from '../../common/mock';
import { createAppMockRenderer } from '../../common/mock';
import '../../common/mock/match_media';
-import { useCaseViewNavigation, useUrlParams } from '../../common/navigation/hooks';
-import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors';
-import { basicCaseClosed, connectorsMock, getCaseUsersMockResponse } from '../../containers/mock';
-import type { UseGetCase } from '../../containers/use_get_case';
-import { useGetCase } from '../../containers/use_get_case';
-import { useGetCaseMetrics } from '../../containers/use_get_case_metrics';
-import { useFindCaseUserActions } from '../../containers/use_find_case_user_actions';
-import { useGetTags } from '../../containers/use_get_tags';
-import { usePostPushToService } from '../../containers/use_post_push_to_service';
-import { useGetCaseConnectors } from '../../containers/use_get_case_connectors';
-import { useUpdateCase } from '../../containers/use_update_case';
-import { useGetCaseUsers } from '../../containers/use_get_case_users';
+import { useUrlParams } from '../../common/navigation/hooks';
import { CaseViewPage } from './case_view_page';
-import {
- caseData,
- caseViewProps,
- defaultGetCase,
- defaultGetCaseMetrics,
- defaultInfiniteUseFindCaseUserActions,
- defaultUpdateCaseState,
- defaultUseFindCaseUserActions,
-} from './mocks';
+import { caseData, caseViewProps } from './mocks';
import type { CaseViewPageProps } from './types';
-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';
-import { createQueryWithMarkup } from '../../common/test_utils';
-import { useCasesFeatures } from '../../common/use_cases_features';
-import { CaseMetricsFeature } from '../../../common/types/api';
+import { waitForComponentToUpdate } from '../../common/test_utils';
+import { useCasesTitleBreadcrumbs } from '../use_breadcrumbs';
-jest.mock('../../containers/use_get_action_license');
-jest.mock('../../containers/use_update_case');
-jest.mock('../../containers/use_get_case_metrics');
-jest.mock('../../containers/use_find_case_user_actions');
-jest.mock('../../containers/use_infinite_find_case_user_actions');
-jest.mock('../../containers/use_get_case_user_actions_stats');
-jest.mock('../../containers/use_get_tags');
-jest.mock('../../containers/use_get_case');
-jest.mock('../../containers/configure/use_get_supported_action_connectors');
-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('../use_breadcrumbs');
+jest.mock('./use_on_refresh_case_view_page');
jest.mock('../../common/hooks');
-jest.mock('../connectors/resilient/api');
jest.mock('../../common/lib/kibana');
-const useFetchCaseMock = useGetCase as jest.Mock;
-const useUrlParamsMock = useUrlParams as jest.Mock;
-const useCaseViewNavigationMock = useCaseViewNavigation as jest.Mock;
-const useUpdateCaseMock = useUpdateCase as jest.Mock;
-const useFindCaseUserActionsMock = useFindCaseUserActions as jest.Mock;
-const useInfiniteFindCaseUserActionsMock = useInfiniteFindCaseUserActions as jest.Mock;
-const useGetCaseUserActionsStatsMock = useGetCaseUserActionsStats as jest.Mock;
-const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock;
-const usePostPushToServiceMock = usePostPushToService as jest.Mock;
-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;
+jest.mock('../header_page', () => ({
+ HeaderPage: jest
+ .fn()
+ .mockReturnValue({'Case view header'}
),
+}));
-const mockGetCase = (props: Partial = {}) => {
- const data = {
- ...defaultGetCase.data,
- ...props.data,
- };
+jest.mock('./metrics', () => ({
+ CaseViewMetrics: jest
+ .fn()
+ .mockReturnValue({'Case view metrics'}
),
+}));
- useFetchCaseMock.mockReturnValue({
- ...defaultGetCase,
- ...props,
- data,
- });
-};
+jest.mock('./components/case_view_activity', () => ({
+ CaseViewActivity: jest
+ .fn()
+ .mockReturnValue({'Case view activity'}
),
+}));
-export const caseProps: CaseViewPageProps = {
+jest.mock('./components/case_view_alerts', () => ({
+ CaseViewAlerts: jest
+ .fn()
+ .mockReturnValue({'Case view alerts'}
),
+}));
+
+jest.mock('./components/case_view_files', () => ({
+ CaseViewFiles: jest
+ .fn()
+ .mockReturnValue({'Case view files'}
),
+}));
+
+const useUrlParamsMock = useUrlParams as jest.Mock;
+const useCasesTitleBreadcrumbsMock = useCasesTitleBreadcrumbs as jest.Mock;
+
+const caseProps: CaseViewPageProps = {
...caseViewProps,
- caseId: caseData.id,
caseData,
fetchCase: jest.fn(),
};
-export const caseClosedProps: CaseViewPageProps = {
- ...caseProps,
- caseData: basicCaseClosed,
-};
-
-const userActionsStats = {
- total: 21,
- totalComments: 9,
- totalOtherActions: 11,
-};
-
describe('CaseViewPage', () => {
- const updateCaseProperty = defaultUpdateCaseState.mutate;
- const pushCaseToExternalService = jest.fn();
- 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();
- useUpdateCaseMock.mockReturnValue(defaultUpdateCaseState);
- useGetCaseMetricsMock.mockReturnValue(defaultGetCaseMetrics);
- useFindCaseUserActionsMock.mockReturnValue(defaultUseFindCaseUserActions);
- useInfiniteFindCaseUserActionsMock.mockReturnValue(defaultInfiniteUseFindCaseUserActions);
- useGetCaseUserActionsStatsMock.mockReturnValue({ data: userActionsStats, isLoading: false });
- usePostPushToServiceMock.mockReturnValue({
- isLoading: false,
- mutateAsync: pushCaseToExternalService,
- });
- useGetCaseConnectorsMock.mockReturnValue({
- isLoading: false,
- data: caseConnectors,
- });
- useGetConnectorsMock.mockReturnValue({ data: connectorsMock, isLoading: false });
- useGetTagsMock.mockReturnValue({ data: [], isLoading: false });
- useGetCaseUsersMock.mockReturnValue({ isLoading: false, data: caseUsers });
- useCasesFeaturesMock.mockReturnValue({
- metricsFeatures: [CaseMetricsFeature.ALERTS_COUNT],
- pushToServiceAuthorized: true,
- caseAssignmentAuthorized: true,
- isAlertsEnabled: true,
- isSyncAlertsEnabled: true,
- });
-
- appMockRenderer = createAppMockRenderer({ license: platinumLicense });
- });
-
- afterAll(() => {
- Object.defineProperty(window, 'getComputedStyle', originalGetComputedStyle);
+ useUrlParamsMock.mockReturnValue({});
+ appMockRenderer = createAppMockRenderer();
});
- it('shows the metrics section', async () => {
+ it('shows the header section', async () => {
appMockRenderer.render();
- expect(await screen.findByTestId('case-view-metrics-panel')).toBeInTheDocument();
+ expect(await screen.findByTestId('test-case-view-header')).toBeInTheDocument();
});
- it('should show closed indicators in header when case is closed', async () => {
- useUpdateCaseMock.mockImplementation(() => ({
- ...defaultUpdateCaseState,
- caseData: basicCaseClosed,
- }));
-
- appMockRenderer.render();
+ it('shows the metrics section', async () => {
+ appMockRenderer.render();
- expect(await screen.findByTestId('case-view-status-dropdown')).toHaveTextContent('Closed');
+ expect(await screen.findByTestId('test-case-view-metrics')).toBeInTheDocument();
});
- it('should push updates on button click', async () => {
- useGetCaseConnectorsMock.mockImplementation(() => ({
- isLoading: false,
- data: {
- ...caseConnectors,
- 'resilient-2': {
- ...caseConnectors['resilient-2'],
- push: { ...caseConnectors['resilient-2'].push, needsToBePushed: true },
- },
- },
- }));
-
+ it('shows the activity section', async () => {
appMockRenderer.render();
- expect(await screen.findByTestId('edit-connectors')).toBeInTheDocument();
- expect(await screen.findByTestId('push-to-external-service')).toBeInTheDocument();
-
- userEvent.click(screen.getByTestId('push-to-external-service'));
-
- await waitFor(() => {
- expect(pushCaseToExternalService).toHaveBeenCalled();
- });
+ expect(await screen.findByTestId('test-case-view-activity')).toBeInTheDocument();
});
- it('should disable the push button when connector is invalid', async () => {
+ it('should set the breadcrumbs correctly', async () => {
+ const onComponentInitialized = jest.fn();
+
appMockRenderer.render(
-
+
);
- expect(await screen.findByTestId('edit-connectors')).toBeInTheDocument();
- expect(await screen.findByTestId('push-to-external-service')).toBeDisabled();
+ await waitFor(() => {
+ expect(useCasesTitleBreadcrumbsMock).toHaveBeenCalledWith(caseProps.caseData.title);
+ });
});
it('should call onComponentInitialized on mount', async () => {
const onComponentInitialized = jest.fn();
+
appMockRenderer.render(
);
@@ -257,229 +113,21 @@ describe('CaseViewPage', () => {
});
});
- it('should show loading content when loading user actions stats', async () => {
- const useFetchAlertData = jest.fn().mockReturnValue([true]);
- useGetCaseUserActionsStatsMock.mockReturnValue({ isLoading: true });
-
- 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();
- appMockRenderer.render();
-
- userEvent.click((await screen.findAllByTestId('comment-action-show-alert-alert-action-id'))[1]);
-
- await waitFor(() => {
- expect(showAlertDetails).toHaveBeenCalledWith('alert-id-1', 'alert-index-1');
- });
- });
-
- it('should show the rule name', async () => {
- appMockRenderer.render();
-
- 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 () => {
- 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 });
- });
- });
-
- it('should show the correct connector name on the push button', async () => {
- useGetConnectorsMock.mockImplementation(() => ({ data: connectorsMock, isLoading: false }));
+ it('should call onComponentInitialized only once', async () => {
+ const onComponentInitialized = jest.fn();
- appMockRenderer.render(
-
+ const { rerender } = appMockRenderer.render(
+
);
- 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 }));
- appMockRenderer.render();
-
- 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 }));
- appMockRenderer.render();
-
- expect(await screen.findByTestId('edit-connectors')).toBeInTheDocument();
- expect(
- screen.queryByTestId('case-callout-a25a5b368b6409b179ef4b6c5168244f')
- ).not.toBeInTheDocument();
- });
- });
-
- // FLAKY: https://github.com/elastic/kibana/issues/149777
- describe.skip('Tabs', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- it('renders tabs correctly', async () => {
- 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 () => {
- 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 () => {
- useUrlParamsMock.mockReturnValue({
- urlParams: {
- tabId: CASE_VIEW_PAGE_TABS.ALERTS,
- },
- });
-
- 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 () => {
- useUrlParamsMock.mockReturnValue({
- urlParams: {
- tabId: CASE_VIEW_PAGE_TABS.ACTIVITY,
- },
- });
-
- 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 () => {
- useUrlParamsMock.mockReturnValue({
- urlParams: {
- tabId: 'what-is-love',
- },
- });
-
- 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;
- 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,
- });
- });
- });
-
- it('navigates to the alerts tab when the alerts tab is clicked', async () => {
- const navigateToCaseViewMock = useCaseViewNavigationMock().navigateToCaseView;
- 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,
- });
- });
- });
-
- it('should display the alerts tab when the feature is enabled', async () => {
- appMockRenderer = createAppMockRenderer({ features: { alerts: { enabled: true } } });
- 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 } } });
- 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 } },
- });
- 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 } } });
- appMockRenderer.render();
-
- expect(
- await screen.findByTestId('case-view-alerts-table-experimental-badge')
- ).toBeInTheDocument();
+ await waitFor(() => {
+ expect(onComponentInitialized).toHaveBeenCalled();
});
- describe('description', () => {
- it('renders the description correctly', async () => {
- appMockRenderer.render();
+ rerender();
- const description = within(await screen.findByTestId('description'));
+ await waitForComponentToUpdate();
- expect(await description.findByText(caseData.description)).toBeInTheDocument();
- });
-
- it('should display description when case is loading', async () => {
- useUpdateCaseMock.mockImplementation(() => ({
- ...defaultUpdateCaseState,
- isLoading: true,
- updateKey: 'description',
- }));
-
- appMockRenderer.render();
-
- expect(await screen.findByTestId('description')).toBeInTheDocument();
- });
- });
+ expect(onComponentInitialized).toBeCalledTimes(1);
});
});
diff --git a/x-pack/plugins/cases/public/components/case_view/case_view_page.tsx b/x-pack/plugins/cases/public/components/case_view/case_view_page.tsx
index 30af8a4a00552..4d9e3ba640449 100644
--- a/x-pack/plugins/cases/public/components/case_view/case_view_page.tsx
+++ b/x-pack/plugins/cases/public/components/case_view/case_view_page.tsx
@@ -6,7 +6,7 @@
*/
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
-import React, { useCallback, useEffect, useMemo, useRef } from 'react';
+import React, { useCallback, useEffect, useRef } from 'react';
import { CASE_VIEW_PAGE_TABS } from '../../../common/types';
import { useUrlParams } from '../../common/navigation';
import { useCasesContext } from '../cases_context/use_cases_context';
@@ -23,6 +23,14 @@ import type { CaseViewPageProps } from './types';
import { useRefreshCaseViewPage } from './use_on_refresh_case_view_page';
import { useOnUpdateField } from './use_on_update_field';
+const getActiveTabId = (tabId?: string) => {
+ if (tabId && Object.values(CASE_VIEW_PAGE_TABS).includes(tabId as CASE_VIEW_PAGE_TABS)) {
+ return tabId;
+ }
+
+ return CASE_VIEW_PAGE_TABS.ACTIVITY;
+};
+
export const CaseViewPage = React.memo(
({
caseData,
@@ -39,12 +47,7 @@ export const CaseViewPage = React.memo(
useCasesTitleBreadcrumbs(caseData.title);
- const activeTabId = useMemo(() => {
- if (urlParams.tabId && Object.values(CASE_VIEW_PAGE_TABS).includes(urlParams.tabId)) {
- return urlParams.tabId;
- }
- return CASE_VIEW_PAGE_TABS.ACTIVITY;
- }, [urlParams.tabId]);
+ const activeTabId = getActiveTabId(urlParams?.tabId);
const init = useRef(true);
const timelineUi = useTimelineContext()?.ui;
@@ -113,15 +116,12 @@ export const CaseViewPage = React.memo(
onUpdateField={onUpdateField}
/>
-
-
-
{activeTabId === CASE_VIEW_PAGE_TABS.ACTIVITY && (
{
expect(screen.queryByTestId('case-view-files-stats-badge')).not.toBeInTheDocument();
});
- it('the files tab count has a different colour if the tab is not active', async () => {
+ it('the files tab count has a different color if the tab is not active', async () => {
appMockRenderer.render();
expect(
@@ -140,7 +140,7 @@ describe('CaseViewTabs', () => {
expect(badge).toHaveTextContent('3');
});
- it('the alerts tab count has a different colour if the tab is not active', async () => {
+ it('the alerts tab count has a different color if the tab is not active', async () => {
appMockRenderer.render(
);
@@ -191,4 +191,54 @@ describe('CaseViewTabs', () => {
});
});
});
+
+ it('should display the alerts tab when the feature is enabled', async () => {
+ appMockRenderer = createAppMockRenderer({ features: { alerts: { enabled: true } } });
+
+ appMockRenderer.render(
+
+ );
+
+ 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 } } });
+
+ appMockRenderer.render(
+
+ );
+
+ expect(await screen.findByTestId('case-view-tabs')).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 } },
+ });
+
+ appMockRenderer.render(
+
+ );
+
+ expect(await screen.findByTestId('case-view-tabs')).toBeInTheDocument();
+ 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 } },
+ });
+
+ appMockRenderer.render(
+
+ );
+
+ expect(
+ await screen.findByTestId('case-view-alerts-table-experimental-badge')
+ ).toBeInTheDocument();
+ });
});
diff --git a/x-pack/plugins/cases/public/components/case_view/case_view_tabs.tsx b/x-pack/plugins/cases/public/components/case_view/case_view_tabs.tsx
index 671ce6606a141..cf5b2e7bd6802 100644
--- a/x-pack/plugins/cases/public/components/case_view/case_view_tabs.tsx
+++ b/x-pack/plugins/cases/public/components/case_view/case_view_tabs.tsx
@@ -143,7 +143,7 @@ export const CaseViewTabs = React.memo(({ caseData, activeTab
return (
<>
- {renderTabs()}
+ {renderTabs()}
>
);
diff --git a/x-pack/plugins/cases/public/components/case_view/index.tsx b/x-pack/plugins/cases/public/components/case_view/index.tsx
index 4ba3f912b9d0b..ecbde67ba15df 100644
--- a/x-pack/plugins/cases/public/components/case_view/index.tsx
+++ b/x-pack/plugins/cases/public/components/case_view/index.tsx
@@ -86,7 +86,6 @@ export const CaseView = React.memo(
{getLegacyUrlConflictCallout()}
void;
caseData: CaseUI;
}
diff --git a/x-pack/plugins/cases/public/components/description/index.test.tsx b/x-pack/plugins/cases/public/components/description/index.test.tsx
index 386d6b6e7154f..8c801b8efae95 100644
--- a/x-pack/plugins/cases/public/components/description/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/description/index.test.tsx
@@ -144,6 +144,14 @@ describe('Description', () => {
expect(screen.queryByTestId('description-edit-icon')).not.toBeInTheDocument();
});
+ it('should display description when case is loading', async () => {
+ appMockRender.render(
+
+ );
+
+ expect(await screen.findByTestId('description')).toBeInTheDocument();
+ });
+
describe('draft message', () => {
const draftStorageKey = `cases.testAppId.basic-case-id.description.markdownEditor`;
diff --git a/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx b/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx
index b68641526c46a..1f50670b05291 100644
--- a/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx
@@ -15,13 +15,15 @@ import { EditConnector } from '.';
import {
type AppMockRenderer,
createAppMockRenderer,
- readCasesPermissions,
- noPushCasesPermissions,
TestProviders,
noConnectorsCasePermission,
+ noCasesPermissions,
} from '../../common/mock';
import { basicCase, connectorsMock } from '../../containers/mock';
import { getCaseConnectorsMockResponse } from '../../common/mock/connectors';
+import type { ReturnUsePushToService } from '../use_push_to_service';
+import { usePushToService } from '../use_push_to_service';
+import { ConnectorTypes } from '../../../common';
const onSubmit = jest.fn();
const caseConnectors = getCaseConnectorsMockResponse();
@@ -34,35 +36,50 @@ const defaultProps: EditConnectorProps = {
onSubmit,
};
+jest.mock('../use_push_to_service');
+
+const handlePushToService = jest.fn();
+const usePushToServiceMock = usePushToService as jest.Mock;
+
+const errorMsg = { id: 'test-error-msg', title: 'My error msg', description: 'My error desc' };
+
+const usePushToServiceMockRes: ReturnUsePushToService = {
+ errorsMsg: [],
+ hasErrorMessages: false,
+ needsToBePushed: true,
+ hasBeenPushed: true,
+ isLoading: false,
+ hasLicenseError: false,
+ hasPushPermissions: true,
+ handlePushToService,
+};
+
describe('EditConnector ', () => {
let appMockRender: AppMockRenderer;
beforeEach(() => {
jest.clearAllMocks();
appMockRender = createAppMockRenderer();
+ usePushToServiceMock.mockReturnValue(usePushToServiceMockRes);
});
- it('Renders the none connector', async () => {
+ it('renders an error message correctly', async () => {
+ usePushToServiceMock.mockReturnValue({
+ ...usePushToServiceMockRes,
+ errorsMsg: [errorMsg],
+ hasErrorMessages: true,
+ });
+
render(
);
- expect(
- await screen.findByText(
- 'To create and update a case in an external system, select a connector.'
- )
- ).toBeInTheDocument();
-
- userEvent.click(screen.getByTestId('connector-edit-button'));
-
- await waitFor(() => {
- expect(screen.getAllByTestId('dropdown-connector-no-connector').length).toBeGreaterThan(0);
- });
+ expect(await screen.findByText(errorMsg.description)).toBeInTheDocument();
});
- it('Edit external service on submit', async () => {
+ it('calls onSubmit when changing connector', async () => {
render(
@@ -97,6 +114,31 @@ describe('EditConnector ', () => {
);
});
+ it('should call handlePushToService when pushing to an external service', async () => {
+ usePushToServiceMock.mockReturnValue({ ...usePushToServiceMockRes, needsToBePushed: true });
+ const props = {
+ ...defaultProps,
+ caseData: {
+ ...defaultProps.caseData,
+ connector: {
+ ...defaultProps.caseData.connector,
+ id: 'servicenow-1',
+ },
+ },
+ };
+
+ render(
+
+
+
+ );
+
+ expect(await screen.findByTestId('push-to-external-service')).toBeInTheDocument();
+ userEvent.click(screen.getByTestId('push-to-external-service'));
+
+ await waitFor(() => expect(handlePushToService).toHaveBeenCalled());
+ });
+
it('reverts to the initial selection if the caseData do not change', async () => {
const props = {
...defaultProps,
@@ -201,6 +243,7 @@ describe('EditConnector ', () => {
it('does not shows the callouts when is loading', async () => {
const props = { ...defaultProps, isLoading: true };
+ usePushToServiceMock.mockReturnValue({ ...usePushToServiceMockRes, errorsMsg: [errorMsg] });
render(
@@ -215,7 +258,7 @@ describe('EditConnector ', () => {
it('does not allow the connector to be edited when the user does not have write permissions', async () => {
render(
-
+
);
@@ -229,25 +272,15 @@ describe('EditConnector ', () => {
});
});
- it('display the callout message when none is selected', async () => {
- // default props has the none connector as selected
- const result = appMockRender.render();
-
- await waitFor(() => {
- expect(result.getByTestId('push-callouts')).toBeInTheDocument();
- });
- });
-
it('shows the actions permission message if the user does not have read access to actions', async () => {
appMockRender.coreStart.application.capabilities = {
...appMockRender.coreStart.application.capabilities,
actions: { save: false, show: false },
};
- const result = appMockRender.render();
- await waitFor(() => {
- expect(result.getByTestId('edit-connector-permissions-error-msg')).toBeInTheDocument();
- });
+ appMockRender.render();
+
+ expect(await screen.findByTestId('edit-connector-permissions-error-msg')).toBeInTheDocument();
});
it('does not show the actions permission message if the user has read access to actions', async () => {
@@ -256,35 +289,34 @@ describe('EditConnector ', () => {
actions: { save: true, show: true },
};
- const result = appMockRender.render();
- await waitFor(() => {
- expect(result.queryByTestId('edit-connector-permissions-error-msg')).toBe(null);
- });
+ appMockRender.render();
+
+ expect(screen.queryByTestId('edit-connector-permissions-error-msg')).not.toBeInTheDocument();
});
it('does not show the callout if the user does not have read access to actions', async () => {
const props = { ...defaultProps, connectors: [] };
+
appMockRender.coreStart.application.capabilities = {
...appMockRender.coreStart.application.capabilities,
actions: { save: false, show: false },
};
- const result = appMockRender.render();
- await waitFor(() => {
- expect(result.getByTestId('edit-connector-permissions-error-msg')).toBeInTheDocument();
- expect(result.queryByTestId('push-callouts')).toBe(null);
- });
+ appMockRender.render();
+
+ expect(await screen.findByTestId('edit-connector-permissions-error-msg')).toBeInTheDocument();
+ expect(screen.queryByTestId('push-callouts')).not.toBeInTheDocument();
});
- it('does not show the callout if the user does not have access to cases connectors', async () => {
+ it('does not show the callouts if the user does not have access to cases connectors', async () => {
+ usePushToServiceMock.mockReturnValue({ ...usePushToServiceMockRes, errorsMsg: [errorMsg] });
const props = { ...defaultProps, connectors: [] };
+
appMockRender = createAppMockRenderer({ permissions: noConnectorsCasePermission() });
- const result = appMockRender.render();
- await waitFor(() => {
- expect(result.getByTestId('edit-connector-permissions-error-msg')).toBeInTheDocument();
- expect(result.queryByTestId('push-callouts')).toBe(null);
- });
+ appMockRender.render();
+
+ expect(screen.queryByTestId('push-callouts')).toBe(null);
});
it('does not show the connectors previewer if the user does not have read access to actions', async () => {
@@ -294,16 +326,16 @@ describe('EditConnector ', () => {
actions: { save: false, show: false },
};
- const result = appMockRender.render();
- expect(result.queryByTestId('connector-fields-preview')).not.toBeInTheDocument();
+ appMockRender.render();
+ expect(screen.queryByTestId('connector-fields-preview')).not.toBeInTheDocument();
});
it('does not show the connectors previewer if the user does not have access to cases connectors', async () => {
const props = { ...defaultProps, connectors: [] };
appMockRender = createAppMockRenderer({ permissions: noConnectorsCasePermission() });
- const result = appMockRender.render();
- expect(result.queryByTestId('connector-fields-preview')).not.toBeInTheDocument();
+ appMockRender.render();
+ expect(screen.queryByTestId('connector-fields-preview')).not.toBeInTheDocument();
});
it('does not show the connectors form if the user does not have read access to actions', async () => {
@@ -313,16 +345,16 @@ describe('EditConnector ', () => {
actions: { save: false, show: false },
};
- const result = appMockRender.render();
- expect(result.queryByTestId('edit-connector-fields-form-flex-item')).not.toBeInTheDocument();
+ appMockRender.render();
+ expect(screen.queryByTestId('edit-connector-fields-form-flex-item')).not.toBeInTheDocument();
});
it('does not show the connectors form if the user does not have access to cases connectors', async () => {
const props = { ...defaultProps, connectors: [] };
appMockRender = createAppMockRenderer({ permissions: noConnectorsCasePermission() });
- const result = appMockRender.render();
- expect(result.queryByTestId('edit-connector-fields-form-flex-item')).not.toBeInTheDocument();
+ appMockRender.render();
+ expect(screen.queryByTestId('edit-connector-fields-form-flex-item')).not.toBeInTheDocument();
});
it('does not show the push button if the user does not have read access to actions', async () => {
@@ -331,32 +363,45 @@ describe('EditConnector ', () => {
actions: { save: false, show: false },
};
- const result = appMockRender.render();
- await waitFor(() => {
- expect(result.queryByTestId('push-to-external-service')).toBe(null);
- });
+ appMockRender.render();
+
+ expect(screen.queryByTestId('push-to-external-service')).not.toBeInTheDocument();
});
it('does not show the push button if the user does not have push permissions', async () => {
- appMockRender = createAppMockRenderer({ permissions: noPushCasesPermissions() });
- const result = appMockRender.render();
+ usePushToServiceMock.mockReturnValue({ ...usePushToServiceMockRes, hasPushPermissions: false });
+ appMockRender.render();
- await waitFor(() => {
- expect(result.queryByTestId('push-to-external-service')).toBe(null);
- });
+ expect(screen.queryByTestId('push-to-external-service')).not.toBeInTheDocument();
+ });
+
+ it('disable the push button when connector is invalid', async () => {
+ usePushToServiceMock.mockReturnValue({ ...usePushToServiceMockRes, needsToBePushed: true });
+
+ appMockRender.render(
+
+ );
+
+ expect(await screen.findByTestId('push-to-external-service')).toBeDisabled();
});
it('does not show the push button if the user does not have access to cases actions', async () => {
appMockRender = createAppMockRenderer({ permissions: noConnectorsCasePermission() });
- const result = appMockRender.render();
- await waitFor(() => {
- expect(result.queryByTestId('push-to-external-service')).toBe(null);
- });
+ appMockRender.render();
+
+ expect(screen.queryByTestId('push-to-external-service')).not.toBeInTheDocument();
});
it('does not show the edit connectors pencil if the user does not have read access to actions', async () => {
const props = { ...defaultProps, connectors: [] };
+
appMockRender.coreStart.application.capabilities = {
...appMockRender.coreStart.application.capabilities,
actions: { save: false, show: false },
@@ -364,10 +409,8 @@ describe('EditConnector ', () => {
appMockRender.render();
- await waitFor(() => {
- expect(screen.getByTestId('connector-edit-header')).toBeInTheDocument();
- expect(screen.queryByTestId('connector-edit-button')).not.toBeInTheDocument();
- });
+ expect(await screen.findByTestId('connector-edit-header')).toBeInTheDocument();
+ expect(screen.queryByTestId('connector-edit-button')).not.toBeInTheDocument();
});
it('does not show the edit connectors pencil if the user does not have access to case connectors', async () => {
@@ -378,21 +421,36 @@ describe('EditConnector ', () => {
appMockRender.render();
- await waitFor(() => {
- expect(screen.getByTestId('connector-edit-header')).toBeInTheDocument();
- expect(screen.queryByTestId('connector-edit-button')).not.toBeInTheDocument();
- });
+ expect(await screen.findByTestId('connector-edit-header')).toBeInTheDocument();
+ expect(screen.queryByTestId('connector-edit-button')).not.toBeInTheDocument();
});
it('does not show the edit connectors pencil if the user does not have push permissions', async () => {
const props = { ...defaultProps, connectors: [] };
- appMockRender = createAppMockRenderer({ permissions: noPushCasesPermissions() });
+ usePushToServiceMock.mockReturnValue({ ...usePushToServiceMockRes, hasPushPermissions: false });
appMockRender.render();
- await waitFor(() => {
- expect(screen.getByTestId('connector-edit-header')).toBeInTheDocument();
- expect(screen.queryByTestId('connector-edit-button')).toBe(null);
- });
+ expect(await screen.findByTestId('connector-edit-header')).toBeInTheDocument();
+ expect(screen.queryByTestId('connector-edit-button')).not.toBeInTheDocument();
+ });
+
+ it('should show the correct connector name on the push button', async () => {
+ const props = {
+ ...defaultProps,
+ caseData: {
+ ...defaultProps.caseData,
+ connector: {
+ id: 'resilient-2',
+ name: 'old name',
+ type: ConnectorTypes.resilient,
+ fields: null,
+ },
+ },
+ };
+
+ appMockRender.render();
+
+ expect(await screen.findByText('Update My Resilient connector incident')).toBeInTheDocument();
});
});
diff --git a/x-pack/plugins/cases/public/containers/use_get_case_user_actions_stats.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case_user_actions_stats.test.tsx
index ce4e453ecbbf1..b81f9d1448aa2 100644
--- a/x-pack/plugins/cases/public/containers/use_get_case_user_actions_stats.test.tsx
+++ b/x-pack/plugins/cases/public/containers/use_get_case_user_actions_stats.test.tsx
@@ -22,7 +22,7 @@ const initialData = {
isLoading: true,
};
-describe('UseGetCaseUserActionsStats', () => {
+describe('useGetCaseUserActionsStats', () => {
let appMockRender: AppMockRenderer;
beforeEach(() => {
diff --git a/x-pack/test/functional/services/cases/navigation.ts b/x-pack/test/functional/services/cases/navigation.ts
index 8d3ba0e73a24c..f0d4fb52ba5e4 100644
--- a/x-pack/test/functional/services/cases/navigation.ts
+++ b/x-pack/test/functional/services/cases/navigation.ts
@@ -22,8 +22,9 @@ export function CasesNavigationProvider({ getPageObject, getService }: FtrProvid
await common.clickAndValidate('configure-case-button', 'case-configure-title');
},
- async navigateToSingleCase(app: string = 'cases', caseId: string) {
- await common.navigateToUrlWithBrowserHistory(app, caseId);
+ async navigateToSingleCase(app: string = 'cases', caseId: string, tabId?: string) {
+ const search = tabId != null ? `?tabId=${tabId}` : '';
+ await common.navigateToUrlWithBrowserHistory(app, caseId, search);
},
};
}
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 6979c45f867ba..cab3cc82e8a4b 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
@@ -46,7 +46,10 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
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-action-bar-wrapper');
+ await testSubjects.existOrFail('case-view-tabs');
+ await testSubjects.existOrFail('case-view-tab-title-alerts');
await testSubjects.existOrFail('case-view-tab-title-activity');
await testSubjects.existOrFail('case-view-tab-title-files');
await testSubjects.existOrFail('description');
@@ -1013,11 +1016,26 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
describe('Tabs', () => {
createOneCaseBeforeDeleteAllAfter(getPageObject, getService);
+ it('renders tabs correctly', async () => {
+ await testSubjects.existOrFail('case-view-tab-title-activity');
+ await testSubjects.existOrFail('case-view-tab-title-files');
+ await testSubjects.existOrFail('case-view-tab-title-alerts');
+ });
+
it('shows the "activity" tab by default', async () => {
await testSubjects.existOrFail('case-view-tab-title-activity');
await testSubjects.existOrFail('case-view-tab-content-activity');
});
+ it("shows the 'activity' tab when clicked", async () => {
+ // Go to the files tab first
+ await testSubjects.click('case-view-tab-title-files');
+ await testSubjects.existOrFail('case-view-tab-content-files');
+
+ await testSubjects.click('case-view-tab-title-activity');
+ await testSubjects.existOrFail('case-view-tab-content-activity');
+ });
+
it("shows the 'alerts' tab when clicked", async () => {
await testSubjects.click('case-view-tab-title-alerts');
await testSubjects.existOrFail('case-view-tab-content-alerts');
@@ -1027,6 +1045,36 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
await testSubjects.click('case-view-tab-title-files');
await testSubjects.existOrFail('case-view-tab-content-files');
});
+
+ describe('Query params', () => {
+ it('renders the activity tab when the query parameter tabId=activity', async () => {
+ const theCase = await createAndNavigateToCase(getPageObject, getService);
+
+ await cases.navigation.navigateToSingleCase('cases', theCase.id, 'activity');
+ await testSubjects.existOrFail('case-view-tab-title-activity');
+ });
+
+ it('renders the activity tab when the query parameter tabId=alerts', async () => {
+ const theCase = await createAndNavigateToCase(getPageObject, getService);
+
+ await cases.navigation.navigateToSingleCase('cases', theCase.id, 'alerts');
+ await testSubjects.existOrFail('case-view-tab-title-activity');
+ });
+
+ it('renders the activity tab when the query parameter tabId=files', async () => {
+ const theCase = await createAndNavigateToCase(getPageObject, getService);
+
+ await cases.navigation.navigateToSingleCase('cases', theCase.id, 'files');
+ await testSubjects.existOrFail('case-view-tab-content-files');
+ });
+
+ it('renders the activity tab when the query parameter tabId has an unknown value', async () => {
+ const theCase = await createAndNavigateToCase(getPageObject, getService);
+
+ await cases.navigation.navigateToSingleCase('cases', theCase.id, 'fake');
+ await testSubjects.existOrFail('case-view-tab-title-activity');
+ });
+ });
});
describe('Files', () => {