[Security Solution][Resolver] Graph Control Tests and Update Simulator Selectors (#74680)
Co-authored-by: oatkiller <robert.austin@elastic.co>
This commit is contained in:
parent
250a0b17b0
commit
c34e30ed0b
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
|
|
|
@ -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']);
|
||||
|
|
Loading…
Reference in a new issue