From 33b00f6e2e0e5f9bf1cdf87588f0c0c7635837da Mon Sep 17 00:00:00 2001 From: stephmilovic Date: Wed, 8 Apr 2020 12:23:07 -0600 Subject: [PATCH] finish testing --- .../index.tsx => __mock__/case_data.tsx} | 133 +++++++-- .../components/all_cases/columns.test.tsx | 2 +- .../case/components/all_cases/index.test.tsx | 4 +- .../components/case_view/__mock__/index.tsx | 93 ------ .../components/case_view/actions.test.tsx | 10 +- .../case/components/case_view/index.test.tsx | 55 +--- .../case/components/tag_list/index.test.tsx | 6 +- .../user_action_tree/helpers.test.tsx | 143 ++++++++++ .../components/user_action_tree/helpers.tsx | 12 +- .../user_action_tree/index.test.tsx | 270 +++++++++++++++--- .../components/user_action_tree/index.tsx | 22 +- .../user_action_tree/user_action_item.tsx | 7 +- .../user_action_tree/user_action_markdown.tsx | 20 +- .../user_action_title.test.tsx | 57 ++++ .../user_action_tree/user_action_title.tsx | 13 +- 15 files changed, 601 insertions(+), 246 deletions(-) rename x-pack/legacy/plugins/siem/public/pages/case/components/{all_cases/__mock__/index.tsx => __mock__/case_data.tsx} (54%) delete mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.test.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx similarity index 54% rename from x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx rename to x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx index b3037e6839b79..64c6276fc1be2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/__mock__/case_data.tsx @@ -4,31 +4,105 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SortFieldCase } from '../../../../../containers/case/types'; -import { UseGetCasesState } from '../../../../../containers/case/use_get_cases'; +import { CaseProps } from '../case_view'; +import { Case, Comment, SortFieldCase } from '../../../../containers/case/types'; +import { UseGetCasesState } from '../../../../containers/case/use_get_cases'; +import { UserAction, UserActionField } from '../../../../../../../../plugins/case/common/api/cases'; + +const updateCase = jest.fn(); +const fetchCase = jest.fn(); + +const basicCaseId = 'basic-case-id'; +const basicCommentId = 'basic-comment-id'; +const basicCreatedAt = '2020-02-20T23:06:33.798Z'; +const elasticUser = { + fullName: 'Leslie Knope', + username: 'lknope', + email: 'leslie.knope@elastic.co', +}; + +export const basicComment: Comment = { + comment: 'Solve this fast!', + id: basicCommentId, + createdAt: basicCreatedAt, + createdBy: elasticUser, + pushedAt: null, + pushedBy: null, + updatedAt: '2020-02-20T23:06:33.798Z', + updatedBy: { + username: 'elastic', + }, + version: 'WzQ3LDFc', +}; + +export const basicCase: Case = { + closedAt: null, + closedBy: null, + id: basicCaseId, + comments: [basicComment], + createdAt: '2020-02-13T19:44:23.627Z', + createdBy: elasticUser, + description: 'Security banana Issue', + externalService: null, + status: 'open', + tags: ['defacement'], + title: 'Another horrible breach!!', + totalComment: 1, + updatedAt: '2020-02-19T15:02:57.995Z', + updatedBy: { + username: 'elastic', + }, + version: 'WzQ3LDFd', +}; + +export const caseProps: CaseProps = { + caseId: basicCaseId, + userCanCrud: true, + caseData: basicCase, + fetchCase, + updateCase, +}; + +export const caseClosedProps: CaseProps = { + ...caseProps, + caseData: { + ...caseProps.caseData, + closedAt: '2020-02-20T23:06:33.798Z', + closedBy: { + username: 'elastic', + }, + status: 'closed', + }, +}; + +export const basicCaseClosed: Case = { + ...caseClosedProps.caseData, +}; + +const basicAction = { + actionAt: basicCreatedAt, + actionBy: elasticUser, + oldValue: null, + newValue: 'what a cool value', + caseId: basicCaseId, + commentId: null, +}; +export const caseUserActions = [ + { + ...basicAction, + actionBy: elasticUser, + actionField: ['comment'], + action: 'create', + actionId: 'tt', + }, +]; export const useGetCasesMockState: UseGetCasesState = { data: { countClosedCases: 0, countOpenCases: 5, cases: [ - { - closedAt: null, - closedBy: null, - id: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', - createdAt: '2020-02-13T19:44:23.627Z', - createdBy: { username: 'elastic' }, - comments: [], - description: 'Security banana Issue', - externalService: null, - status: 'open', - tags: ['defacement'], - title: 'Another horrible breach', - totalComment: 0, - updatedAt: null, - updatedBy: null, - version: 'WzQ3LDFd', - }, + basicCase, { closedAt: null, closedBy: null, @@ -129,3 +203,24 @@ export const useGetCasesMockState: UseGetCasesState = { }, filterOptions: { search: '', reporters: [], tags: [], status: 'open' }, }; + +const basicPush = { + connector_id: 'connector_id', + connector_name: 'connector name', + external_id: 'external_id', + external_title: 'external title', + external_url: 'basicPush.com', + pushed_at: basicCreatedAt, + pushed_by: elasticUser, +}; +export const getUserAction = (af: UserActionField, a: UserAction) => ({ + ...basicAction, + actionId: `${af[0]}-${a}`, + actionField: af, + action: a, + commentId: af[0] === 'comment' ? basicCommentId : null, + newValue: + a === 'push-to-service' && af[0] === 'pushed' + ? JSON.stringify(basicPush) + : basicAction.newValue, +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.test.tsx index 28577898b548e..e008b94ab9e16 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.test.tsx @@ -9,7 +9,7 @@ import { mount } from 'enzyme'; import { ServiceNowColumn } from './columns'; -import { useGetCasesMockState } from './__mock__'; +import { useGetCasesMockState } from '../__mock__/case_data'; describe('ServiceNowColumn ', () => { it('Not pushed render', () => { diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx index cd1730e373829..f65736e7cd109 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx @@ -9,7 +9,7 @@ import { mount } from 'enzyme'; import moment from 'moment-timezone'; import { AllCases } from './'; import { TestProviders } from '../../../../mock'; -import { useGetCasesMockState } from './__mock__'; +import { useGetCasesMockState } from '../__mock__/case_data'; import * as i18n from './translations'; import { getEmptyTagValue } from '../../../../components/empty_value'; @@ -120,7 +120,7 @@ describe('AllCases', () => { .find(`[data-test-subj="case-table-column-createdBy"]`) .first() .text() - ).toEqual(useGetCasesMockState.data.cases[0].createdBy.username); + ).toEqual(useGetCasesMockState.data.cases[0].createdBy.fullName); expect( wrapper .find(`[data-test-subj="case-table-column-createdAt"]`) diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx deleted file mode 100644 index 0e57326707e97..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { CaseProps } from '../index'; -import { Case } from '../../../../../containers/case/types'; - -const updateCase = jest.fn(); -const fetchCase = jest.fn(); - -export const caseProps: CaseProps = { - caseId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', - userCanCrud: true, - caseData: { - closedAt: null, - closedBy: null, - id: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', - comments: [ - { - comment: 'Solve this fast!', - id: 'a357c6a0-5435-11ea-b427-fb51a1fcb7b8', - createdAt: '2020-02-20T23:06:33.798Z', - createdBy: { - fullName: 'Steph Milovic', - username: 'smilovic', - email: 'notmyrealemailfool@elastic.co', - }, - pushedAt: null, - pushedBy: null, - updatedAt: '2020-02-20T23:06:33.798Z', - updatedBy: { - username: 'elastic', - }, - version: 'WzQ3LDFd', - }, - ], - createdAt: '2020-02-13T19:44:23.627Z', - createdBy: { fullName: null, email: 'testemail@elastic.co', username: 'elastic' }, - description: 'Security banana Issue', - externalService: null, - status: 'open', - tags: ['defacement'], - title: 'Another horrible breach!!', - totalComment: 1, - updatedAt: '2020-02-19T15:02:57.995Z', - updatedBy: { - username: 'elastic', - }, - version: 'WzQ3LDFd', - }, - fetchCase, - updateCase, -}; - -export const caseClosedProps: CaseProps = { - ...caseProps, - caseData: { - ...caseProps.caseData, - closedAt: '2020-02-20T23:06:33.798Z', - closedBy: { - username: 'elastic', - }, - status: 'closed', - }, -}; - -export const data: Case = { - ...caseProps.caseData, -}; - -export const dataClosed: Case = { - ...caseClosedProps.caseData, -}; - -export const caseUserActions = [ - { - actionField: ['comment'], - action: 'create', - actionAt: '2020-03-20T17:10:09.814Z', - actionBy: { - fullName: 'Steph Milovic', - username: 'smilovic', - email: 'notmyrealemailfool@elastic.co', - }, - newValue: 'Solve this fast!', - oldValue: null, - actionId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', - caseId: '9b833a50-6acd-11ea-8fad-af86b1071bd9', - commentId: 'a357c6a0-5435-11ea-b427-fb51a1fcb7b8', - }, -]; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx index 49f5f44cba271..8a25a2121104d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/actions.test.tsx @@ -9,7 +9,7 @@ import { mount } from 'enzyme'; import { useDeleteCases } from '../../../../containers/case/use_delete_cases'; import { TestProviders } from '../../../../mock'; -import { data } from './__mock__'; +import { basicCase } from '../__mock__/case_data'; import { CaseViewActions } from './actions'; jest.mock('../../../../containers/case/use_delete_cases'); const useDeleteCasesMock = useDeleteCases as jest.Mock; @@ -34,7 +34,7 @@ describe('CaseView actions', () => { it('clicking trash toggles modal', () => { const wrapper = mount( - + ); @@ -54,12 +54,14 @@ describe('CaseView actions', () => { })); const wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeTruthy(); wrapper.find('button[data-test-subj="confirmModalConfirmButton"]').simulate('click'); - expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([{ id: data.id, title: data.title }]); + expect(handleOnDeleteConfirm.mock.calls[0][0]).toEqual([ + { id: basicCase.id, title: basicCase.title }, + ]); }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx index 81ce9b4b3d413..5056199649df0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx @@ -9,7 +9,12 @@ import { mount } from 'enzyme'; import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; import { CaseComponent, CaseView } from './'; -import { caseProps, caseClosedProps, data, dataClosed, caseUserActions } from './__mock__'; +import { + basicCaseClosed, + caseClosedProps, + caseProps, + caseUserActions, +} from '../__mock__/case_data'; import { TestProviders } from '../../../../mock'; import { useUpdateCase } from '../../../../containers/case/use_update_case'; import { useGetCase } from '../../../../containers/case/use_get_case'; @@ -29,10 +34,11 @@ describe('CaseView ', () => { const fetchCaseUserActions = jest.fn(); const fetchCase = jest.fn(); const updateCase = jest.fn(); + const data = caseProps.caseData; const defaultGetCase = { isLoading: false, isError: false, - data: caseProps.caseData, + data, updateCase, fetchCase, }; @@ -126,7 +132,7 @@ describe('CaseView ', () => { ).toEqual(data.createdAt); expect( wrapper - .find(`[data-test-subj="case-view-description"]`) + .find(`[data-test-subj="description-action"] [data-test-subj="user-action-markdown"]`) .first() .prop('raw') ).toEqual(data.description); @@ -135,7 +141,7 @@ describe('CaseView ', () => { it('should show closed indicators in header when case is closed', async () => { useUpdateCaseMock.mockImplementation(() => ({ ...defaultUpdateCaseState, - caseData: dataClosed, + caseData: basicCaseClosed, })); const wrapper = mount( @@ -151,13 +157,13 @@ describe('CaseView ', () => { .find(`[data-test-subj="case-view-closedAt"]`) .first() .prop('value') - ).toEqual(dataClosed.closedAt); + ).toEqual(basicCaseClosed.closedAt); expect( wrapper .find(`[data-test-subj="case-view-status"]`) .first() .text() - ).toEqual(dataClosed.status); + ).toEqual(basicCaseClosed.status); }); it('should dispatch update state when button is toggled', async () => { @@ -175,43 +181,6 @@ describe('CaseView ', () => { expect(updateCaseProperty).toHaveBeenCalled(); }); - it('should render comments', async () => { - const wrapper = mount( - - - - - - ); - await wait(); - expect( - wrapper - .find( - `div[data-test-subj="user-action-${data.comments[0].id}-avatar"] [data-test-subj="user-action-avatar"]` - ) - .first() - .prop('name') - ).toEqual(data.comments[0].createdBy.fullName); - - expect( - wrapper - .find( - `div[data-test-subj="user-action-${data.comments[0].id}"] [data-test-subj="user-action-title"] strong` - ) - .first() - .text() - ).toEqual(data.comments[0].createdBy.username); - - expect( - wrapper - .find( - `div[data-test-subj="user-action-${data.comments[0].id}"] [data-test-subj="markdown"]` - ) - .first() - .prop('source') - ).toEqual(data.comments[0].comment); - }); - it('should display EditableTitle isLoading', () => { useUpdateCaseMock.mockImplementation(() => ({ ...defaultUpdateCaseState, diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.test.tsx index 58c261f5d1e32..7e4dac2bd8cf1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.test.tsx @@ -32,10 +32,9 @@ describe('TagList ', () => { (useForm as jest.Mock).mockImplementation(() => ({ form: formHookMock })); }); it('Renders no tags, and then edit', () => { - const props = defaultProps; const wrapper = mount( - + ); expect( @@ -62,10 +61,9 @@ describe('TagList ', () => { ).toBeTruthy(); }); it('Edit tag on submit', async () => { - const props = defaultProps; const wrapper = mount( - + ); wrapper diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.test.tsx new file mode 100644 index 0000000000000..5c342538f0feb --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.test.tsx @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { getUserAction } from '../__mock__/case_data'; +import { getLabelTitle } from './helpers'; +import * as i18n from '../case_view/translations'; +import { mount } from 'enzyme'; + +describe('User action tree helpers', () => { + it('label title generated for update tags', () => { + const action = getUserAction(['title'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'tags', + firstIndexPushToService: 0, + index: 0, + }); + + const wrapper = mount(<>{result}); + expect( + wrapper + .find(`[data-test-subj="ua-tags-label"]`) + .first() + .text() + ).toEqual(` ${i18n.TAGS.toLowerCase()}`); + + expect( + wrapper + .find(`[data-test-subj="ua-tag"]`) + .first() + .text() + ).toEqual(action.newValue); + }); + it('label title generated for update title', () => { + const action = getUserAction(['title'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'title', + firstIndexPushToService: 0, + index: 0, + }); + + expect(result).toEqual( + `${i18n.CHANGED_FIELD.toLowerCase()} ${i18n.CASE_NAME.toLowerCase()} ${i18n.TO} "${ + action.newValue + }"` + ); + }); + it('label title generated for update description', () => { + const action = getUserAction(['description'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'description', + firstIndexPushToService: 0, + index: 0, + }); + + expect(result).toEqual(`${i18n.EDITED_FIELD} ${i18n.DESCRIPTION.toLowerCase()}`); + }); + it('label title generated for update status to open', () => { + const action = { ...getUserAction(['status'], 'update'), newValue: 'open' }; + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'status', + firstIndexPushToService: 0, + index: 0, + }); + + expect(result).toEqual(`${i18n.REOPENED_CASE.toLowerCase()} ${i18n.CASE}`); + }); + it('label title generated for update status to closed', () => { + const action = { ...getUserAction(['status'], 'update'), newValue: 'closed' }; + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'status', + firstIndexPushToService: 0, + index: 0, + }); + + expect(result).toEqual(`${i18n.CLOSED_CASE.toLowerCase()} ${i18n.CASE}`); + }); + it('label title generated for update comment', () => { + const action = getUserAction(['comment'], 'update'); + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'comment', + firstIndexPushToService: 0, + index: 0, + }); + + expect(result).toEqual(`${i18n.EDITED_FIELD} ${i18n.COMMENT.toLowerCase()}`); + }); + it('label title generated for pushed incident', () => { + const action = getUserAction(['pushed'], 'push-to-service'); + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'pushed', + firstIndexPushToService: 0, + index: 0, + }); + + const wrapper = mount(<>{result}); + expect( + wrapper + .find(`[data-test-subj="pushed-label"]`) + .first() + .text() + ).toEqual(i18n.PUSHED_NEW_INCIDENT); + expect( + wrapper + .find(`[data-test-subj="pushed-value"]`) + .first() + .prop('href') + ).toEqual(JSON.parse(action.newValue).external_url); + }); + it('label title generated for needs update incident', () => { + const action = getUserAction(['pushed'], 'push-to-service'); + const result: string | JSX.Element = getLabelTitle({ + action, + field: 'pushed', + firstIndexPushToService: 0, + index: 1, + }); + + const wrapper = mount(<>{result}); + expect( + wrapper + .find(`[data-test-subj="pushed-label"]`) + .first() + .text() + ).toEqual(i18n.UPDATE_INCIDENT); + expect( + wrapper + .find(`[data-test-subj="pushed-value"]`) + .first() + .prop('href') + ).toEqual(JSON.parse(action.newValue).external_url); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.tsx index 008f4d7048f56..d6016e540bdc0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.tsx @@ -41,14 +41,16 @@ export const getLabelTitle = ({ action, field, firstIndexPushToService, index }: const getTagsLabelTitle = (action: CaseUserActions) => ( - + {action.action === 'add' && i18n.ADDED_FIELD} {action.action === 'delete' && i18n.REMOVED_FIELD} {i18n.TAGS.toLowerCase()} {action.newValue != null && action.newValue.split(',').map(tag => ( - {tag} + + {tag} + ))} @@ -61,12 +63,12 @@ const getPushedServiceLabelTitle = ( ) => { const pushedVal = JSON.parse(action.newValue ?? '') as CaseFullExternalService; return ( - - + + {firstIndexPushToService === index ? i18n.PUSHED_NEW_INCIDENT : i18n.UPDATE_INCIDENT} - + {pushedVal?.connector_name} {pushedVal?.external_title} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx index 2bc3606ec4225..c06d3d686af88 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx @@ -7,36 +7,24 @@ import React from 'react'; import { mount } from 'enzyme'; -import { Router, routeData, mockHistory } from '../__mock__/router'; +import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; +import { getFormMock } from '../__mock__/form'; +import { useUpdateComment } from '../../../../containers/case/use_update_comment'; +import { basicCase, getUserAction } from '../__mock__/case_data'; import { UserActionTree } from './'; import { TestProviders } from '../../../../mock'; -import { UserActionField } from '../../../../../../../../plugins/case/common/api/cases'; +import { useFormMock } from '../create/index.test'; +import { wait } from '../../../../lib/helpers'; +import { act } from 'react-dom/test-utils'; +jest.mock( + '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' +); const fetchUserActions = jest.fn(); const onUpdateField = jest.fn(); const updateCase = jest.fn(); const defaultProps = { - data: { - id: '89bae2b0-74f4-11ea-a8d2-2bcd64fb2cdd', - version: 'WzcxNiwxXQ==', - comments: [], - totalComment: 0, - description: 'This looks not so good', - title: 'Bad meanie defacing data', - tags: ['defacement'], - closedAt: null, - closedBy: null, - createdAt: '2020-04-02T15:13:41.925Z', - createdBy: { - email: 'Steph.milovic@elastic.co', - fullName: 'Steph Milovic', - username: 'smilovic', - }, - externalService: null, - status: 'open', - updatedAt: null, - updatedBy: null, - }, + data: basicCase, caseUserActions: [], firstIndexPushToService: -1, isLoadingDescription: false, @@ -47,40 +35,235 @@ const defaultProps = { onUpdateField, updateCase, }; +const useUpdateCommentMock = useUpdateComment as jest.Mock; +jest.mock('../../../../containers/case/use_update_comment'); -describe.skip('UserActionTree ', () => { +const patchComment = jest.fn(); +describe('UserActionTree ', () => { + const sampleData = { + content: 'what a great comment update', + }; beforeEach(() => { + jest.clearAllMocks(); jest.resetAllMocks(); - jest.spyOn(routeData, 'useParams').mockReturnValue({ commentId: '123' }); + useUpdateCommentMock.mockImplementation(() => ({ + isLoadingIds: [], + patchComment, + })); + const formHookMock = getFormMock(sampleData); + useFormMock.mockImplementation(() => ({ form: formHookMock })); + jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); }); - it('Loading spinner when user actions loading', async () => { - const props = defaultProps; + it('Loading spinner when user actions loading', () => { const wrapper = mount( - + ); expect(wrapper.find(`[data-test-subj="user-actions-loading"]`).exists()).toBeTruthy(); }); - it.only('Renders user action items', async () => { - const commentAction = { - actionField: [('comment' as unknown) as UserActionField['comment']], - action: 'create', - actionAt: '2020-04-06T21:54:19.459Z', - actionBy: { email: '', fullName: '', username: 'casetester3' }, - newValue: 'sdfsd', - oldValue: null, - actionId: '2b825fb0-7851-11ea-8b3c-092fb98f129e', - caseId: '89bae2b0-74f4-11ea-a8d2-2bcd64fb2cdd', - commentId: '2adcf7f0-7851-11ea-8b3c-092fb98f129e', + + it('Outlines comment when update move to link is clicked', () => { + const ourActions = [getUserAction(['comment'], 'create'), getUserAction(['comment'], 'update')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + }; + const wrapper = mount( + + + + + + ); + expect( + wrapper + .find(`[data-test-subj="comment-create-action"]`) + .first() + .prop('idToOutline') + ).toEqual(''); + wrapper + .find(`[data-test-subj="comment-update-action"] [data-test-subj="move-to-link"]`) + .first() + .simulate('click'); + expect( + wrapper + .find(`[data-test-subj="comment-create-action"]`) + .first() + .prop('idToOutline') + ).toEqual(ourActions[0].commentId); + }); + + it('Switches to markdown when edit is clicked and back to panel when canceled', () => { + const ourActions = [getUserAction(['comment'], 'create')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, }; + const wrapper = mount( + + + + + + ); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(false); + wrapper + .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-ellipses"]`) + .first() + .simulate('click'); + wrapper + .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-pencil"]`) + .first() + .simulate('click'); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(true); + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-cancel-markdown"]` + ) + .first() + .simulate('click'); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(false); + }); + + it('calls update comment when comment markdown is saved', async () => { + const ourActions = [getUserAction(['comment'], 'create')]; + const props = { + ...defaultProps, + caseUserActions: ourActions, + }; + const wrapper = mount( + + + + + + ); + wrapper + .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-ellipses"]`) + .first() + .simulate('click'); + wrapper + .find(`[data-test-subj="comment-create-action"] [data-test-subj="property-actions-pencil"]`) + .first() + .simulate('click'); + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-save-markdown"]` + ) + .first() + .simulate('click'); + await act(async () => { + await wait(); + wrapper.update(); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.comments[0].id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(false); + expect(patchComment).toBeCalledWith({ + commentUpdate: sampleData.content, + caseId: props.data.id, + commentId: props.data.comments[0].id, + fetchUserActions, + updateCase, + version: props.data.comments[0].version, + }); + }); + }); + + it('calls update description when description markdown is saved', async () => { + const props = defaultProps; + const wrapper = mount( + + + + + + ); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-ellipses"]`) + .first() + .simulate('click'); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-pencil"]`) + .first() + .simulate('click'); + wrapper + .find( + `[data-test-subj="user-action-description"] [data-test-subj="user-action-save-markdown"]` + ) + .first() + .simulate('click'); + await act(async () => { + await wait(); + expect( + wrapper + .find( + `[data-test-subj="user-action-${props.data.id}"] [data-test-subj="user-action-markdown-form"]` + ) + .exists() + ).toEqual(false); + expect(onUpdateField).toBeCalledWith('description', sampleData.content); + }); + }); + + it('quotes', async () => { + const commentData = { + comment: '', + }; + const formHookMock = getFormMock(commentData); + const setFieldValue = jest.fn(); + useFormMock.mockImplementation(() => ({ form: { ...formHookMock, setFieldValue } })); + const props = defaultProps; + const wrapper = mount( + + + + + + ); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-ellipses"]`) + .first() + .simulate('click'); + wrapper + .find(`[data-test-subj="description-action"] [data-test-subj="property-actions-quote"]`) + .first() + .simulate('click'); + expect(setFieldValue).toBeCalledWith('comment', `> ${props.data.description} \n`); + }); + it('Outlines comment when url param is provided', () => { + const commentId = 'neat-comment-id'; + const ourActions = [getUserAction(['comment'], 'create')]; const props = { ...defaultProps, - caseUserActions: [commentAction], + caseUserActions: ourActions, }; + jest.spyOn(routeData, 'useParams').mockReturnValue({ commentId }); const wrapper = mount( @@ -90,8 +273,9 @@ describe.skip('UserActionTree ', () => { ); expect( wrapper - .find(`[data-test-subj="comment-action"] [data-test-subj="user-action-title"] strong`) - .text() - ).toEqual(commentAction.actionBy.username); + .find(`[data-test-subj="comment-create-action"]`) + .first() + .prop('idToOutline') + ).toEqual(commentId); }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx index d3baf2060f595..a8585a8190215 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx @@ -52,26 +52,12 @@ export const UserActionTree = React.memo( updateCase, userCanCrud, }: UserActionTreeProps) => { - // console.log('userActionTree props', { - // data: caseData, - // caseUserActions, - // fetchUserActions, - // firstIndexPushToService, - // isLoadingDescription, - // isLoadingUserActions, - // lastIndexPushToService, - // onUpdateField, - // updateCase, - // userCanCrud, - // }); const { commentId } = useParams(); const handlerTimeoutId = useRef(0); const [initLoading, setInitLoading] = useState(true); const [selectedOutlineCommentId, setSelectedOutlineCommentId] = useState(''); const { isLoadingIds, patchComment } = useUpdateComment(); - // console.log('useUpdateComment', { isLoadingIds, patchComment }) const currentUser = useCurrentUser(); - // console.log('useCurrentUser', currentUser) const [manageMarkdownEditIds, setManangeMardownEditIds] = useState([]); const [insertQuote, setInsertQuote] = useState(null); const handleManageMarkdownEditId = useCallback( @@ -87,7 +73,7 @@ export const UserActionTree = React.memo( const handleSaveComment = useCallback( ({ id, version }: { id: string; version: string }, content: string) => { - handleManageMarkdownEditId(id); + // handleManageMarkdownEditId(id); patchComment({ caseId: caseData.id, commentId: id, @@ -148,7 +134,6 @@ export const UserActionTree = React.memo( content={caseData.description} isEditable={manageMarkdownEditIds.includes(DESCRIPTION_ID)} onSaveContent={(content: string) => { - handleManageMarkdownEditId(DESCRIPTION_ID); onUpdateField(DESCRIPTION_ID, content); }} onChangeEditable={handleManageMarkdownEditId} @@ -179,7 +164,6 @@ export const UserActionTree = React.memo( } } }, [commentId, initLoading, isLoadingUserActions, isLoadingIds]); - return ( <> )} } linkId={linkId} - fullName={fullName} - username={username} - updatedAt={updatedAt} onEdit={onEdit} onQuote={onQuote} outlineComment={outlineComment} + updatedAt={updatedAt} + username={username} /> {markdown} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_markdown.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_markdown.tsx index e8503bf43375c..827fe2df120ab 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_markdown.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_markdown.tsx @@ -62,12 +62,24 @@ export const UserActionMarkdown = ({ return ( - + {i18n.CANCEL} - + {i18n.SAVE} @@ -77,7 +89,7 @@ export const UserActionMarkdown = ({ [handleCancelAction, handleSaveAction] ); return isEditable ? ( -
+ ) : ( - + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.test.tsx new file mode 100644 index 0000000000000..e2189367068ca --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.test.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import copy from 'copy-to-clipboard'; +import { Router, routeData, mockHistory } from '../__mock__/router'; +import { caseUserActions as basicUserActions } from '../__mock__/case_data'; +import { UserActionTitle } from './user_action_title'; +import { TestProviders } from '../../../../mock'; + +const outlineComment = jest.fn(); +const onEdit = jest.fn(); +const onQuote = jest.fn(); + +jest.mock('copy-to-clipboard'); +const defaultProps = { + createdAt: basicUserActions[0].actionAt, + disabled: false, + fullName: basicUserActions[0].actionBy.fullName, + id: basicUserActions[0].actionId, + isLoading: false, + labelEditAction: 'labelEditAction', + labelQuoteAction: 'labelQuoteAction', + labelTitle: <>{'cool'}, + linkId: basicUserActions[0].commentId, + onEdit, + onQuote, + outlineComment, + updatedAt: basicUserActions[0].actionAt, + username: basicUserActions[0].actionBy.username, +}; + +describe('UserActionTitle ', () => { + beforeEach(() => { + jest.resetAllMocks(); + jest.spyOn(routeData, 'useParams').mockReturnValue({ commentId: '123' }); + }); + + it('Calls copy when copy link is clicked', async () => { + const wrapper = mount( + + + + + + ); + wrapper + .find(`[data-test-subj="copy-link"]`) + .first() + .simulate('click'); + expect(copy).toBeCalledTimes(1); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx index c604bda7a7add..a1edbab7e1fa2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/user_action_title.tsx @@ -52,18 +52,18 @@ interface UserActionTitleProps { export const UserActionTitle = ({ createdAt, disabled, + fullName, id, isLoading, labelEditAction, labelQuoteAction, labelTitle, linkId, - fullName, - username, - updatedAt, onEdit, onQuote, outlineComment, + updatedAt, + username, }: UserActionTitleProps) => { const { detailName: caseId } = useParams(); const urlSearch = useGetUrlSearch(navTabs.case); @@ -94,10 +94,7 @@ export const UserActionTitle = ({ const handleAnchorLink = useCallback(() => { copy( - `${window.location.origin}${window.location.pathname}#${SiemPageName.case}/${caseId}/${id}${urlSearch}`, - { - debug: true, - } + `${window.location.origin}${window.location.pathname}#${SiemPageName.case}/${caseId}/${id}${urlSearch}` ); }, [caseId, id, urlSearch]); @@ -154,6 +151,7 @@ export const UserActionTitle = ({ {i18n.MOVE_TO_ORIGINAL_COMMENT}

}> @@ -164,6 +162,7 @@ export const UserActionTitle = ({ {i18n.COPY_REFERENCE_LINK}

}>