[Security Solution][Resolver] Graph Control Tests and Update Simulator Selectors (#74680)

Co-authored-by: oatkiller <robert.austin@elastic.co>
This commit is contained in:
Michael Olorunnisola 2020-08-13 14:55:43 -04:00 committed by GitHub
parent 250a0b17b0
commit c34e30ed0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 379 additions and 105 deletions

View file

@ -17,6 +17,7 @@ declare global {
namespace jest {
interface Matchers<R, T> {
toYieldEqualTo(expectedYield: T extends AsyncIterable<infer E> ? E : never): Promise<R>;
toYieldObjectEqualTo(expectedYield: unknown): Promise<R>;
}
}
}
@ -57,6 +58,70 @@ expect.extend({
}
}
// Use `pass` as set in the above loop (or initialized to `false`)
// See https://jestjs.io/docs/en/expect#custom-matchers-api and https://jestjs.io/docs/en/expect#thisutils
const message = pass
? () =>
`${this.utils.matcherHint(matcherName, undefined, undefined, options)}\n\n` +
`Expected: not ${this.utils.printExpected(expected)}\n${
this.utils.stringify(expected) !== this.utils.stringify(received[received.length - 1]!)
? `Received: ${this.utils.printReceived(received[received.length - 1])}`
: ''
}`
: () =>
`${this.utils.matcherHint(matcherName, undefined, undefined, options)}\n\nCompared ${
received.length
} yields.\n\n${received
.map(
(next, index) =>
`yield ${index + 1}:\n\n${this.utils.printDiffOrStringify(
expected,
next,
'Expected',
'Received',
this.expand
)}`
)
.join(`\n\n`)}`;
return { message, pass };
},
/**
* A custom matcher that takes an async generator and compares each value it yields to an expected value.
* This uses the same equality logic as `toMatchObject`.
* If any yielded value equals the expected value, the matcher will pass.
* If the generator ends with none of the yielded values matching, it will fail.
*/
async toYieldObjectEqualTo<T>(
this: jest.MatcherContext,
receivedIterable: AsyncIterable<T>,
expected: T
): Promise<{ pass: boolean; message: () => string }> {
// Used in printing out the pass or fail message
const matcherName = 'toSometimesYieldEqualTo';
const options: jest.MatcherHintOptions = {
comment: 'deep equality with any yielded value',
isNot: this.isNot,
promise: this.promise,
};
// The last value received: Used in printing the message
const received: T[] = [];
// Set to true if the test passes.
let pass: boolean = false;
// Async iterate over the iterable
for await (const next of receivedIterable) {
// keep track of all received values. Used in pass and fail messages
received.push(next);
// Use deep equals to compare the value to the expected value
if ((this.equals(next, expected), [this.utils.iterableEquality, this.utils.subsetEquality])) {
// If the value is equal, break
pass = true;
break;
}
}
// Use `pass` as set in the above loop (or initialized to `false`)
// See https://jestjs.io/docs/en/expect#custom-matchers-api and https://jestjs.io/docs/en/expect#thisutils
const message = pass

View file

@ -14,8 +14,9 @@ import { spyMiddlewareFactory } from '../spy_middleware_factory';
import { resolverMiddlewareFactory } from '../../store/middleware';
import { resolverReducer } from '../../store/reducer';
import { MockResolver } from './mock_resolver';
import { ResolverState, DataAccessLayer, SpyMiddleware } from '../../types';
import { ResolverState, DataAccessLayer, SpyMiddleware, SideEffectSimulator } from '../../types';
import { ResolverAction } from '../../store/actions';
import { sideEffectSimulatorFactory } from '../../view/side_effect_simulator_factory';
/**
* Test a Resolver instance using jest, enzyme, and a mock data layer.
@ -43,6 +44,11 @@ export class Simulator {
* This is used by `debugActions`.
*/
private readonly spyMiddleware: SpyMiddleware;
/**
* Simulator which allows you to explicitly simulate resize events and trigger animation frames
*/
private readonly sideEffectSimulator: SideEffectSimulator;
constructor({
dataAccessLayer,
resolverComponentInstanceID,
@ -87,11 +93,14 @@ export class Simulator {
// Used for `KibanaContextProvider`
const coreStart: CoreStart = coreMock.createStart();
this.sideEffectSimulator = sideEffectSimulatorFactory();
// Render Resolver via the `MockResolver` component, using `enzyme`.
this.wrapper = mount(
<MockResolver
resolverComponentInstanceID={this.resolverComponentInstanceID}
history={this.history}
sideEffectSimulator={this.sideEffectSimulator}
store={this.store}
coreStart={coreStart}
databaseDocumentID={databaseDocumentID}
@ -149,6 +158,18 @@ export class Simulator {
return this.domNodes(processNodeElementSelector(options));
}
/**
* 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
*/
public processNodeChildElements(entityID: string, selector: string): ReactWrapper {
return this.domNodes(
`${processNodeElementSelector({ entityID })} [data-test-subj="${selector}"]`
);
}
/**
* Return the node element with the given `entityID`.
*/
@ -174,21 +195,11 @@ export class Simulator {
}
/**
* Return an Enzyme ReactWrapper that includes the Related Events host button for a given process node
*
* @param entityID The entity ID of the proocess node to select in
* This manually runs the animation frames tied to a configurable timestamp in the future
*/
public processNodeRelatedEventButton(entityID: string): ReactWrapper {
return this.domNodes(
`${processNodeElementSelector({ entityID })} [data-test-subj="resolver:submenu:button"]`
);
}
/**
* The items in the submenu that is opened by expanding a node in the map.
*/
public processNodeSubmenuItems(): ReactWrapper {
return this.domNodes('[data-test-subj="resolver:map:node-submenu-item"]');
public runAnimationFramesTimeFromNow(time: number = 0) {
this.sideEffectSimulator.controls.time = time;
this.sideEffectSimulator.controls.provideAnimationFrame();
}
/**
@ -202,59 +213,17 @@ export class Simulator {
}
/**
* The element that shows when Resolver is waiting for the graph data.
* Given a 'data-test-subj' value, it will resolve the react wrapper or undefined if not found
*/
public graphLoadingElement(): ReactWrapper {
return this.domNodes('[data-test-subj="resolver:graph:loading"]');
public async resolve(selector: string): Promise<ReactWrapper | undefined> {
return this.resolveWrapper(() => this.domNodes(`[data-test-subj="${selector}"]`));
}
/**
* The element that shows if Resolver couldn't draw the graph.
* Given a 'data-test-subj' selector, it will return the domNode
*/
public graphErrorElement(): ReactWrapper {
return this.domNodes('[data-test-subj="resolver:graph:error"]');
}
/**
* The element where nodes get drawn.
*/
public graphElement(): ReactWrapper {
return this.domNodes('[data-test-subj="resolver:graph"]');
}
/**
* The titles of the links that select a node in the node list view.
*/
public nodeListNodeLinkText(): ReactWrapper {
return this.domNodes('[data-test-subj="resolver:node-list:node-link:title"]');
}
/**
* The icons in the links that select a node in the node list view.
*/
public nodeListNodeLinkIcons(): ReactWrapper {
return this.domNodes('[data-test-subj="resolver:node-list:node-link:icon"]');
}
/**
* Link rendered in the breadcrumbs of the node detail view. Takes the user to the node list.
*/
public nodeDetailBreadcrumbNodeListLink(): ReactWrapper {
return this.domNodes('[data-test-subj="resolver:node-detail:breadcrumbs:node-list-link"]');
}
/**
* The title element for the node detail view.
*/
public nodeDetailViewTitle(): ReactWrapper {
return this.domNodes('[data-test-subj="resolver:node-detail:title"]');
}
/**
* The icon element for the node detail title.
*/
public nodeDetailViewTitleIcon(): ReactWrapper {
return this.domNodes('[data-test-subj="resolver:node-detail:title-icon"]');
public testSubject(selector: string): ReactWrapper {
return this.domNodes(`[data-test-subj="${selector}"]`);
}
/**
@ -297,7 +266,7 @@ export class Simulator {
public async resolveWrapper(
wrapperFactory: () => ReactWrapper,
predicate: (wrapper: ReactWrapper) => boolean = (wrapper) => wrapper.length > 0
): Promise<ReactWrapper | void> {
): Promise<ReactWrapper | undefined> {
for await (const wrapper of this.map(wrapperFactory)) {
if (predicate(wrapper)) {
return wrapper;

View file

@ -6,7 +6,7 @@
/* eslint-disable react/display-name */
import React, { useMemo, useEffect, useState, useCallback } from 'react';
import React, { useEffect, useState, useCallback } from 'react';
import { Router } from 'react-router-dom';
import { I18nProvider } from '@kbn/i18n/react';
import { Provider } from 'react-redux';
@ -17,7 +17,6 @@ import { ResolverState, SideEffectSimulator, ResolverProps } from '../../types';
import { ResolverAction } from '../../store/actions';
import { ResolverWithoutProviders } from '../../view/resolver_without_providers';
import { SideEffectContext } from '../../view/side_effect_context';
import { sideEffectSimulatorFactory } from '../../view/side_effect_simulator_factory';
type MockResolverProps = {
/**
@ -38,6 +37,10 @@ type MockResolverProps = {
history: React.ComponentProps<typeof Router>['history'];
/** Pass a resolver store. See `storeFactory` and `mockDataAccessLayer` */
store: Store<ResolverState, ResolverAction>;
/**
* Pass the side effect simulator which handles animations and resizing. See `sideEffectSimulatorFactory`
*/
sideEffectSimulator: SideEffectSimulator;
/**
* All the props from `ResolverWithoutStore` can be passed. These aren't defaulted to anything (you might want to test what happens when they aren't present.)
*/
@ -66,8 +69,6 @@ export const MockResolver = React.memo((props: MockResolverProps) => {
setResolverElement(element);
}, []);
const simulator: SideEffectSimulator = useMemo(() => sideEffectSimulatorFactory(), []);
// Resize the Resolver element to match the passed in props. Resolver is size dependent.
useEffect(() => {
if (resolverElement) {
@ -84,15 +85,15 @@ export const MockResolver = React.memo((props: MockResolverProps) => {
return this;
},
};
simulator.controls.simulateElementResize(resolverElement, size);
props.sideEffectSimulator.controls.simulateElementResize(resolverElement, size);
}
}, [props.rasterWidth, props.rasterHeight, simulator.controls, resolverElement]);
}, [props.rasterWidth, props.rasterHeight, props.sideEffectSimulator.controls, resolverElement]);
return (
<I18nProvider>
<Router history={props.history}>
<KibanaContextProvider services={props.coreStart}>
<SideEffectContext.Provider value={simulator.mock}>
<SideEffectContext.Provider value={props.sideEffectSimulator.mock}>
<Provider store={props.store}>
<ResolverWithoutProviders
ref={resolverRef}

View file

@ -42,9 +42,9 @@ describe('Resolver, when analyzing a tree that has no ancestors and 2 children',
* For example, there might be no loading element at one point, and 1 graph element at one point, but never a single time when there is both 1 graph element and 0 loading elements.
*/
simulator.map(() => ({
graphElements: simulator.graphElement().length,
graphLoadingElements: simulator.graphLoadingElement().length,
graphErrorElements: simulator.graphErrorElement().length,
graphElements: simulator.testSubject('resolver:graph').length,
graphLoadingElements: simulator.testSubject('resolver:graph:loading').length,
graphErrorElements: simulator.testSubject('resolver:graph:error').length,
}))
).toYieldEqualTo({
// it should have 1 graph element, an no error or loading elements.
@ -72,8 +72,12 @@ describe('Resolver, when analyzing a tree that has no ancestors and 2 children',
});
it(`should show links to the 3 nodes (with icons) in the node list.`, async () => {
await expect(simulator.map(() => simulator.nodeListNodeLinkText().length)).toYieldEqualTo(3);
await expect(simulator.map(() => simulator.nodeListNodeLinkIcons().length)).toYieldEqualTo(3);
await expect(
simulator.map(() => simulator.testSubject('resolver:node-list:node-link:title').length)
).toYieldEqualTo(3);
await expect(
simulator.map(() => simulator.testSubject('resolver:node-list:node-link:title').length)
).toYieldEqualTo(3);
});
describe("when the second child node's first button has been clicked", () => {
@ -131,9 +135,9 @@ describe('Resolver, when analyzing a tree that has two related events for the or
beforeEach(async () => {
await expect(
simulator.map(() => ({
graphElements: simulator.graphElement().length,
graphLoadingElements: simulator.graphLoadingElement().length,
graphErrorElements: simulator.graphErrorElement().length,
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,
}))
).toYieldEqualTo({
@ -147,7 +151,10 @@ describe('Resolver, when analyzing a tree that has two related events for the or
it('should render a related events button', async () => {
await expect(
simulator.map(() => ({
relatedEventButtons: simulator.processNodeRelatedEventButton(entityIDs.origin).length,
relatedEventButtons: simulator.processNodeChildElements(
entityIDs.origin,
'resolver:submenu:button'
).length,
}))
).toYieldEqualTo({
relatedEventButtons: 1,
@ -156,7 +163,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.processNodeRelatedEventButton(entityIDs.origin)
simulator.processNodeChildElements(entityIDs.origin, 'resolver:submenu:button')
);
if (button) {
button.simulate('click');
@ -164,17 +171,19 @@ describe('Resolver, when analyzing a tree that has two related events for the or
});
it('should open the submenu and display exactly one option with the correct count', async () => {
await expect(
simulator.map(() => simulator.processNodeSubmenuItems().map((node) => node.text()))
simulator.map(() =>
simulator.testSubject('resolver:map:node-submenu-item').map((node) => node.text())
)
).toYieldEqualTo(['2 registry']);
await expect(
simulator.map(() => simulator.processNodeSubmenuItems().length)
simulator.map(() => simulator.testSubject('resolver:map:node-submenu-item').length)
).toYieldEqualTo(1);
});
});
describe('and when the related events button is clicked again', () => {
beforeEach(async () => {
const button = await simulator.resolveWrapper(() =>
simulator.processNodeRelatedEventButton(entityIDs.origin)
simulator.processNodeChildElements(entityIDs.origin, 'resolver:submenu:button')
);
if (button) {
button.simulate('click');
@ -182,7 +191,7 @@ describe('Resolver, when analyzing a tree that has two related events for the or
});
it('should close the submenu', async () => {
await expect(
simulator.map(() => simulator.processNodeSubmenuItems().length)
simulator.map(() => simulator.testSubject('resolver:map:node-submenu-item').length)
).toYieldEqualTo(0);
});
});

View file

@ -0,0 +1,221 @@
/*
* 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 { Simulator } from '../test_utilities/simulator';
import { noAncestorsTwoChildren } from '../data_access_layer/mocks/no_ancestors_two_children';
import { nudgeAnimationDuration } from '../store/camera/scaling_constants';
import '../test_utilities/extend_jest';
describe('graph controls: when relsover is loaded with an origin node', () => {
let simulator: Simulator;
let originEntityID: string;
let originNodeStyle: () => AsyncIterable<CSSStyleDeclaration | null>;
const resolverComponentInstanceID = 'graph-controls-test';
const originalPositionStyle: Readonly<{ left: string; top: string }> = {
left: '746.93132px',
top: '535.5792px',
};
const originalSizeStyle: Readonly<{ width: string; height: string }> = {
width: '360px',
height: '120px',
};
beforeEach(async () => {
const {
metadata: { databaseDocumentID, entityIDs },
dataAccessLayer,
} = noAncestorsTwoChildren();
simulator = new Simulator({
dataAccessLayer,
databaseDocumentID,
resolverComponentInstanceID,
});
originEntityID = entityIDs.origin;
originNodeStyle = () =>
simulator.map(() => {
const wrapper = simulator.processNodeElements({ entityID: originEntityID });
// `getDOMNode` can only be called on a wrapper of a single node: https://enzymejs.github.io/enzyme/docs/api/ReactWrapper/getDOMNode.html
if (wrapper.length === 1) {
return wrapper.getDOMNode<HTMLElement>().style;
}
return null;
});
});
it('should display all cardinal panning buttons and the center button', async () => {
await expect(
simulator.map(() => ({
westPanButton: simulator.testSubject('resolver:graph-controls:west-button').length,
southPanButton: simulator.testSubject('resolver:graph-controls:south-button').length,
eastPanButton: simulator.testSubject('resolver:graph-controls:east-button').length,
northPanButton: simulator.testSubject('resolver:graph-controls:north-button').length,
centerButton: simulator.testSubject('resolver:graph-controls:center-button').length,
}))
).toYieldEqualTo({
westPanButton: 1,
southPanButton: 1,
eastPanButton: 1,
northPanButton: 1,
centerButton: 1,
});
});
it('should display the zoom buttons and slider', async () => {
await expect(
simulator.map(() => ({
zoomInButton: simulator.testSubject('resolver:graph-controls:zoom-in').length,
zoomOutButton: simulator.testSubject('resolver:graph-controls:zoom-out').length,
zoomSlider: simulator.testSubject('resolver:graph-controls:zoom-slider').length,
}))
).toYieldEqualTo({
zoomInButton: 1,
zoomOutButton: 1,
zoomSlider: 1,
});
});
it("should show the origin node in it's original position", async () => {
await expect(originNodeStyle()).toYieldObjectEqualTo(originalPositionStyle);
});
describe('when the user clicks the west panning button', () => {
beforeEach(async () => {
(await simulator.resolve('resolver:graph-controls:west-button'))!.simulate('click');
simulator.runAnimationFramesTimeFromNow(nudgeAnimationDuration);
});
it('should show the origin node further left on the screen', async () => {
await expect(originNodeStyle()).toYieldObjectEqualTo({
left: '796.93132px',
top: '535.5792px',
});
});
});
describe('when the user clicks the south panning button', () => {
beforeEach(async () => {
(await simulator.resolve('resolver:graph-controls:south-button'))!.simulate('click');
simulator.runAnimationFramesTimeFromNow(nudgeAnimationDuration);
});
it('should show the origin node lower on the screen', async () => {
await expect(originNodeStyle()).toYieldObjectEqualTo({
left: '746.93132px',
top: '485.5792px',
});
});
});
describe('when the user clicks the east panning button', () => {
beforeEach(async () => {
(await simulator.resolve('resolver:graph-controls:east-button'))!.simulate('click');
simulator.runAnimationFramesTimeFromNow(nudgeAnimationDuration);
});
it('should show the origin node further right on the screen', async () => {
await expect(originNodeStyle()).toYieldObjectEqualTo({
left: '696.93132px',
top: '535.5792px',
});
});
});
describe('when the user clicks the north panning button', () => {
beforeEach(async () => {
(await simulator.resolve('resolver:graph-controls:north-button'))!.simulate('click');
simulator.runAnimationFramesTimeFromNow(nudgeAnimationDuration);
});
it('should show the origin node higher on the screen', async () => {
await expect(originNodeStyle()).toYieldObjectEqualTo({
left: '746.93132px',
top: '585.5792px',
});
});
});
describe('when the user clicks the center panning button', () => {
beforeEach(async () => {
(await simulator.resolve('resolver:graph-controls:north-button'))!.simulate('click');
simulator.runAnimationFramesTimeFromNow(nudgeAnimationDuration);
(await simulator.resolve('resolver:graph-controls:center-button'))!.simulate('click');
simulator.runAnimationFramesTimeFromNow(nudgeAnimationDuration);
});
it("should return the origin node to it's original position", async () => {
await expect(originNodeStyle()).toYieldObjectEqualTo(originalPositionStyle);
});
});
it('should show the origin node as larger on the screen', async () => {
await expect(originNodeStyle()).toYieldObjectEqualTo(originalSizeStyle);
});
describe('when the zoom in button is clicked', () => {
beforeEach(async () => {
(await simulator.resolve('resolver:graph-controls:zoom-in'))!.simulate('click');
simulator.runAnimationFramesTimeFromNow(nudgeAnimationDuration);
});
it('should show the origin node as larger on the screen', async () => {
await expect(originNodeStyle()).toYieldObjectEqualTo({
width: '427.7538290724795px',
height: '142.5846096908265px',
});
});
});
describe('when the zoom out button is clicked', () => {
beforeEach(async () => {
(await simulator.resolve('resolver:graph-controls:zoom-out'))!.simulate('click');
simulator.runAnimationFramesTimeFromNow(nudgeAnimationDuration);
});
it('should show the origin node as smaller on the screen', async () => {
await expect(originNodeStyle()).toYieldObjectEqualTo({
width: '303.0461709275204px',
height: '101.01539030917347px',
});
});
});
describe('when the slider is moved upwards', () => {
beforeEach(async () => {
await expect(originNodeStyle()).toYieldObjectEqualTo(originalSizeStyle);
(await simulator.resolve('resolver:graph-controls:zoom-slider'))!.simulate('change', {
target: { value: 0.8 },
});
simulator.runAnimationFramesTimeFromNow(nudgeAnimationDuration);
});
it('should show the origin node as large on the screen', async () => {
await expect(originNodeStyle()).toYieldObjectEqualTo({
width: '525.6000000000001px',
height: '175.20000000000005px',
});
});
});
describe('when the slider is moved downwards', () => {
beforeEach(async () => {
(await simulator.resolve('resolver:graph-controls:zoom-slider'))!.simulate('change', {
target: { value: 0.2 },
});
simulator.runAnimationFramesTimeFromNow(nudgeAnimationDuration);
});
it('should show the origin node as smaller on the screen', async () => {
await expect(originNodeStyle()).toYieldObjectEqualTo({
width: '201.60000000000002px',
height: '67.2px',
});
});
});
});

View file

@ -125,12 +125,13 @@ const GraphControlsComponent = React.memo(
className={className}
graphControlsBackground={colorMap.graphControlsBackground}
graphControlsIconColor={colorMap.graphControls}
data-test-subj="resolver:graph-controls"
>
<EuiPanel className="panning-controls" paddingSize="none" hasShadow>
<div className="panning-controls-top">
<button
className="north-button"
data-test-subj="north-button"
data-test-subj="resolver:graph-controls:north-button"
title="North"
onClick={handleNorth}
>
@ -140,7 +141,7 @@ const GraphControlsComponent = React.memo(
<div className="panning-controls-middle">
<button
className="west-button"
data-test-subj="west-button"
data-test-subj="resolver:graph-controls:west-button"
title="West"
onClick={handleWest}
>
@ -148,7 +149,7 @@ const GraphControlsComponent = React.memo(
</button>
<button
className="center-button"
data-test-subj="center-button"
data-test-subj="resolver:graph-controls:center-button"
title="Center"
onClick={handleCenterClick}
>
@ -156,7 +157,7 @@ const GraphControlsComponent = React.memo(
</button>
<button
className="east-button"
data-test-subj="east-button"
data-test-subj="resolver:graph-controls:east-button"
title="East"
onClick={handleEast}
>
@ -166,7 +167,7 @@ const GraphControlsComponent = React.memo(
<div className="panning-controls-bottom">
<button
className="south-button"
data-test-subj="south-button"
data-test-subj="resolver:graph-controls:south-button"
title="South"
onClick={handleSouth}
>
@ -175,19 +176,27 @@ const GraphControlsComponent = React.memo(
</div>
</EuiPanel>
<EuiPanel className="zoom-controls" paddingSize="none" hasShadow>
<button title="Zoom In" data-test-subj="zoom-in" onClick={handleZoomInClick}>
<button
title="Zoom In"
data-test-subj="resolver:graph-controls:zoom-in"
onClick={handleZoomInClick}
>
<EuiIcon type="plusInCircle" />
</button>
<EuiRange
className="zoom-slider"
data-test-subj="zoom-slider"
data-test-subj="resolver:graph-controls:zoom-slider"
min={0}
max={1}
step={0.01}
value={scalingFactor}
onChange={handleZoomAmountChange}
/>
<button title="Zoom Out" data-test-subj="zoom-out" onClick={handleZoomOutClick}>
<button
title="Zoom Out"
data-test-subj="resolver:graph-controls:zoom-out"
onClick={handleZoomOutClick}
>
<EuiIcon type="minusInCircle" />
</button>
</EuiPanel>

View file

@ -72,8 +72,8 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children, an
it('should show the node details for the origin', async () => {
await expect(
simulator().map(() => {
const titleWrapper = simulator().nodeDetailViewTitle();
const titleIconWrapper = simulator().nodeDetailViewTitleIcon();
const titleWrapper = simulator().testSubject('resolver:node-detail:title');
const titleIconWrapper = simulator().testSubject('resolver:node-detail:title-icon');
return {
title: titleWrapper.exists() ? titleWrapper.text() : null,
titleIcon: titleIconWrapper.exists() ? titleIconWrapper.text() : null,
@ -122,17 +122,17 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children, an
});
it('should have 3 nodes (with icons) in the node list', async () => {
await expect(simulator().map(() => simulator().nodeListNodeLinkText().length)).toYieldEqualTo(
3
);
await expect(simulator().map(() => simulator().nodeListNodeLinkIcons().length)).toYieldEqualTo(
3
);
await expect(
simulator().map(() => simulator().testSubject('resolver:node-list:node-link:title').length)
).toYieldEqualTo(3);
await expect(
simulator().map(() => simulator().testSubject('resolver:node-list:node-link:icon').length)
).toYieldEqualTo(3);
});
describe('when there is an item in the node list and its text has been clicked', () => {
beforeEach(async () => {
const nodeLinks = await simulator().resolveWrapper(() => simulator().nodeListNodeLinkText());
const nodeLinks = await simulator().resolve('resolver:node-list:node-link:title');
expect(nodeLinks).toBeTruthy();
if (nodeLinks) {
nodeLinks.first().simulate('click');
@ -158,8 +158,8 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children, an
});
describe('and when the node list link has been clicked', () => {
beforeEach(async () => {
const nodeListLink = await simulator().resolveWrapper(() =>
simulator().nodeDetailBreadcrumbNodeListLink()
const nodeListLink = await simulator().resolve(
'resolver:node-detail:breadcrumbs:node-list-link'
);
if (nodeListLink) {
nodeListLink.simulate('click');
@ -169,7 +169,7 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children, an
await expect(
simulator().map(() => {
return simulator()
.nodeListNodeLinkText()
.testSubject('resolver:node-list:node-link:title')
.map((node) => node.text());
})
).toYieldEqualTo(['c', 'd', 'e']);