From 459e9d603d04ae66c72188091107322bf4b65bab Mon Sep 17 00:00:00 2001 From: Robert Austin Date: Fri, 14 Aug 2020 16:31:28 -0400 Subject: [PATCH] [Resolver] simulator tests select elements directly instead of using descendant selectors. (#75058) Our tests shouldn't rely on the DOM structure of Resolver (when its arbitrary) because that will make them brittle. If the user doesn't care about the DOM structure, then neither should our tests. Note: sometimes the user does care about the DOM structure, and in those cases the tests should as well. --- .../test_utilities/simulator/index.tsx | 22 +++++++++---- .../resolver/view/clickthrough.test.tsx | 32 +++++++++---------- .../resolver/view/process_event_dot.tsx | 3 ++ .../public/resolver/view/query_params.test.ts | 16 +++++----- .../public/resolver/view/submenu.tsx | 3 ++ 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx b/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx index 14cdc26c80f53..43a03bb771501 100644 --- a/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx +++ b/x-pack/plugins/security_solution/public/resolver/test_utilities/simulator/index.tsx @@ -175,14 +175,24 @@ export class Simulator { } /** - * Return an Enzyme ReactWrapper for any child elements of a specific processNodeElement - * - * @param entityID The entity ID of the proocess node to select in - * @param selector The selector for the child element of the process node + * The button that opens a node's submenu. */ - public processNodeChildElements(entityID: string, selector: string): ReactWrapper { + public processNodeSubmenuButton( + /** nodeID for the related node */ entityID: string + ): ReactWrapper { return this.domNodes( - `${processNodeElementSelector({ entityID })} [data-test-subj="${selector}"]` + `[data-test-subj="resolver:submenu:button"][data-test-resolver-node-id="${entityID}"]` + ); + } + + /** + * The primary button (used to select a node) which contains a label for the node as its content. + */ + public processNodePrimaryButton( + /** nodeID for the related node */ entityID: string + ): ReactWrapper { + return this.domNodes( + `[data-test-subj="resolver:node:primary-button"][data-test-resolver-node-id="${entityID}"]` ); } diff --git a/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx index 09fcd273a9c9b..3265ee8bcfca0 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx @@ -62,13 +62,13 @@ describe('Resolver, when analyzing a tree that has no ancestors and 2 children', selectedOriginCount: simulator.selectedProcessNode(entityIDs.origin).length, unselectedFirstChildCount: simulator.unselectedProcessNode(entityIDs.firstChild).length, unselectedSecondChildCount: simulator.unselectedProcessNode(entityIDs.secondChild).length, - processNodeCount: simulator.processNodeElements().length, + nodePrimaryButtonCount: simulator.testSubject('resolver:node:primary-button').length, })) ).toYieldEqualTo({ selectedOriginCount: 1, unselectedFirstChildCount: 1, unselectedSecondChildCount: 1, - processNodeCount: 3, + nodePrimaryButtonCount: 3, }); }); @@ -82,13 +82,14 @@ describe('Resolver, when analyzing a tree that has no ancestors and 2 children', }); describe("when the second child node's first button has been clicked", () => { - beforeEach(() => { - // Click the first button under the second child element. - simulator - .processNodeElements({ entityID: entityIDs.secondChild }) - .find('button') - .first() - .simulate('click'); + beforeEach(async () => { + const button = await simulator.resolveWrapper(() => + simulator.processNodePrimaryButton(entityIDs.secondChild) + ); + // Click the second child node's primary button + if (button) { + button.simulate('click'); + } }); it('should render the second child node as selected, and the origin as not selected, and the query string should indicate that the second child is selected', async () => { await expect( @@ -141,23 +142,20 @@ describe('Resolver, when analyzing a tree that has two related events for the or graphElements: simulator.testSubject('resolver:graph').length, graphLoadingElements: simulator.testSubject('resolver:graph:loading').length, graphErrorElements: simulator.testSubject('resolver:graph:error').length, - originNode: simulator.processNodeElements({ entityID: entityIDs.origin }).length, + originNodeButton: simulator.processNodePrimaryButton(entityIDs.origin).length, })) ).toYieldEqualTo({ graphElements: 1, graphLoadingElements: 0, graphErrorElements: 0, - originNode: 1, + originNodeButton: 1, }); }); it('should render a related events button', async () => { await expect( simulator.map(() => ({ - relatedEventButtons: simulator.processNodeChildElements( - entityIDs.origin, - 'resolver:submenu:button' - ).length, + relatedEventButtons: simulator.processNodeSubmenuButton(entityIDs.origin).length, })) ).toYieldEqualTo({ relatedEventButtons: 1, @@ -166,7 +164,7 @@ describe('Resolver, when analyzing a tree that has two related events for the or describe('when the related events button is clicked', () => { beforeEach(async () => { const button = await simulator.resolveWrapper(() => - simulator.processNodeChildElements(entityIDs.origin, 'resolver:submenu:button') + simulator.processNodeSubmenuButton(entityIDs.origin) ); if (button) { button.simulate('click'); @@ -183,7 +181,7 @@ describe('Resolver, when analyzing a tree that has two related events for the or describe('and when the related events button is clicked again', () => { beforeEach(async () => { const button = await simulator.resolveWrapper(() => - simulator.processNodeChildElements(entityIDs.origin, 'resolver:submenu:button') + simulator.processNodeSubmenuButton(entityIDs.origin) ); if (button) { button.simulate('click'); diff --git a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx index 2a5d91028d9f5..2bb104801866f 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx @@ -404,6 +404,8 @@ const UnstyledProcessEventDot = React.memo( }} tabIndex={-1} title={eventModel.processNameSafeVersion(event)} + data-test-subj="resolver:node:primary-button" + data-test-resolver-node-id={nodeID} > @@ -433,6 +435,7 @@ const UnstyledProcessEventDot = React.memo( menuTitle={subMenuAssets.relatedEvents.title} projectionMatrix={projectionMatrix} optionsWithActions={relatedEventStatusOrOptions} + nodeID={nodeID} /> )} diff --git a/x-pack/plugins/security_solution/public/resolver/view/query_params.test.ts b/x-pack/plugins/security_solution/public/resolver/view/query_params.test.ts index 26c25cfab2c21..a86237e0e2b45 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/query_params.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/view/query_params.test.ts @@ -34,12 +34,12 @@ describe('Resolver, when analyzing a tree that has no ancestors and 2 children', describe("when the second child node's first button has been clicked", () => { beforeEach(async () => { - const node = await simulator.resolveWrapper(() => - simulator.processNodeElements({ entityID: entityIDs.secondChild }).find('button') + const button = await simulator.resolveWrapper(() => + simulator.processNodePrimaryButton(entityIDs.secondChild) ); - if (node) { + if (button) { // Click the first button under the second child element. - node.first().simulate('click'); + button.simulate('click'); } }); const expectedSearch = urlSearch(resolverComponentInstanceID, { @@ -68,12 +68,12 @@ describe('Resolver, when analyzing a tree that has no ancestors and 2 children', }); describe("when the user clicks the second child node's button again", () => { beforeEach(async () => { - const node = await simulator.resolveWrapper(() => - simulator.processNodeElements({ entityID: entityIDs.secondChild }).find('button') + const button = await simulator.resolveWrapper(() => + simulator.processNodePrimaryButton(entityIDs.secondChild) ); - if (node) { + if (button) { // Click the first button under the second child element. - node.first().simulate('click'); + button.simulate('click'); } }); it(`should have a url search of ${urlSearch(newInstanceID, { diff --git a/x-pack/plugins/security_solution/public/resolver/view/submenu.tsx b/x-pack/plugins/security_solution/public/resolver/view/submenu.tsx index 359a4e2dafd2e..14d6470c95207 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/submenu.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/submenu.tsx @@ -137,6 +137,7 @@ const NodeSubMenuComponents = React.memo( optionsWithActions, className, projectionMatrix, + nodeID, }: { menuTitle: string; className?: string; @@ -148,6 +149,7 @@ const NodeSubMenuComponents = React.memo( * Receive the projection matrix, so we can see when the camera position changed, so we can force the submenu to reposition itself. */ projectionMatrix: Matrix3; + nodeID: string; } & { optionsWithActions?: ResolverSubmenuOptionList | string | undefined; }) => { @@ -236,6 +238,7 @@ const NodeSubMenuComponents = React.memo( iconSide="right" tabIndex={-1} data-test-subj="resolver:submenu:button" + data-test-resolver-node-id={nodeID} > {count ? : ''} {menuTitle}