Skip to content

Commit

Permalink
Adds functional tests for overview, shortens time for graph to stabilize
Browse files Browse the repository at this point in the history
  • Loading branch information
Karol Maciolek committed Aug 5, 2020
1 parent 139be6b commit 2de0f0d
Show file tree
Hide file tree
Showing 13 changed files with 235 additions and 43 deletions.
Empty file removed .npmrc
Empty file.
2 changes: 1 addition & 1 deletion src/components/DependencyGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,5 @@ const drawerOpenButtonCls = css({
position: 'fixed',
top: 20,
left: 20,
zIndex: 10,
zIndex: 6,
});
2 changes: 1 addition & 1 deletion src/overview/graph-nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function createNodes(labelNodesGroup: NodeSelection<SVGGElement>, nodes:
.data(nodes)
.enter()
.append<SVGGElement>('g')
.attr('data-test-id', 'label')
.attr('data-test-id', 'node')
.attr('cursor', 'pointer')
.append('text')
.attr('font-size', BASE_FONT_SIZE)
Expand Down
1 change: 1 addition & 0 deletions src/overview/highlight-background/highlight-background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function createHighlightBackground(
return svgContainer
.append('rect')
.attr('id', ElementIds.HIGHLIGHT_BACKGROUND)
.attr('data-test-id', 'highlight-background')
.attr('width', 0)
.attr('height', 0)
.attr('x', 0)
Expand Down
4 changes: 2 additions & 2 deletions src/overview/overview.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,10 @@ export function highlightNodes(clickedNode: DependencyNode) {

if (areNodesDirectlyConnected) {
select<Element, DependencyNode>(labelElement).attr('fill', getHighLightedLabelColor);
select<Element, DependencyNode>(textElement).style('fill', Colors.WHITE);
select<Element, DependencyNode>(textElement).attr('fill', Colors.WHITE);
} else {
select<Element, DependencyNode>(labelElement).attr('fill', Colors.LIGHT_GREY);
select<Element, DependencyNode>(textElement).style('fill', Colors.BASIC_TEXT);
select<Element, DependencyNode>(textElement).attr('fill', Colors.BASIC_TEXT);
}
});
}
Expand Down
1 change: 1 addition & 0 deletions src/overview/simulation/simulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function createSimulation(nodes: DependencyNode[], links: DependencyLink[
.distance(180)
.id((node: DependencyNode) => node.name)
)
.alphaDecay(0.1)
.force('center', forceCenter(width / 2, height / 2))
.force('y', forceY(0.5))
.force('collide', forceCollide(140))
Expand Down
3 changes: 2 additions & 1 deletion src/overview/tooltip/tooltip.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { NodeSelection } from '../../components/types';
import {Colors} from '../../utils/AppConsts';
import { Colors } from '../../utils/AppConsts';
import { selectTooltip } from '../../utils/helpers/Selectors';

export function createTooltip(svgContainer: NodeSelection<SVGGElement>) {
const tooltipElement = svgContainer
.append('g')
.attr('id', 'tooltip')
.attr('data-test-id', 'tooltip')
.style('opacity', 0);
tooltipElement
.append('rect')
Expand Down
23 changes: 10 additions & 13 deletions src/utils/helpers/UserEventHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {event, select} from 'd3-selection';
import {DependencyNode, TreeNode} from '../../components/types';
import { event, select } from 'd3-selection';
import { DependencyNode, TreeNode } from '../../components/types';
import {
Colors,
ElementIds,
Expand All @@ -10,7 +10,7 @@ import {
ZOOM_DECREASE,
ZOOM_INCREASE,
} from '../AppConsts';
import {zoom, zoomIdentity} from 'd3-zoom';
import { zoom, zoomIdentity } from 'd3-zoom';
import {
selectAllNodes,
selectDetailsButtonWrapper,
Expand All @@ -21,14 +21,11 @@ import {
selectTooltipBackground,
selectTooltipText,
} from './Selectors';
import {initializeDetailsView, shutdownDetailsView} from '../../details/details-container';
import {
hideHighlightBackground,
zoomToHighLightedNodes
} from '../../overview/highlight-background/highlight-background.helpers';
import {findMaxDependencyLevel, getHighLightedLabelColor} from '../../overview/graph-nodes';
import {centerScreenToDimension, findGroupBackgroundDimension, highlightNodes} from '../../overview/overview.helpers';
import {changeZoom} from '../../zoom/zoom';
import { initializeDetailsView, shutdownDetailsView } from '../../details/details-container';
import { hideHighlightBackground, zoomToHighLightedNodes } from '../../overview/highlight-background/highlight-background.helpers';
import { findMaxDependencyLevel, getHighLightedLabelColor } from '../../overview/graph-nodes';
import { centerScreenToDimension, findGroupBackgroundDimension, highlightNodes } from '../../overview/overview.helpers';
import { changeZoom } from '../../zoom/zoom';

enum Subscriptions {
HIGHLIGHT = 'click.highlight',
Expand Down Expand Up @@ -90,7 +87,7 @@ export function subscribeToChangeHighlightRangeOnArrowKey() {
}

select<Element, DependencyNode>(labelElement).attr('fill', labelColor);
select<Element, DependencyNode>(textElement).style('fill', textColor);
select<Element, DependencyNode>(textElement).attr('fill', textColor);
});

zoomToHighLightedNodes();
Expand All @@ -115,7 +112,7 @@ export function subscribeToResetHighlight() {
}

select<Element, DependencyNode>(labelElement).attr('fill', Colors.LIGHT_GREY);
select<Element, DependencyNode>(textElement).style('fill', Colors.BASIC_TEXT);
select<Element, DependencyNode>(textElement).attr('fill', Colors.BASIC_TEXT);
});

hideHighlightBackground();
Expand Down
25 changes: 0 additions & 25 deletions test/functional/app.test.ts

This file was deleted.

18 changes: 18 additions & 0 deletions test/functional/graph.helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ElementHandle } from 'puppeteer';
import { TRANSITION_DURATION } from '../../src/utils/AppConsts';

export async function waitForGraphStabilization() {
await page.waitFor(500);
}

export async function waitForAllTransitions() {
await page.waitFor(TRANSITION_DURATION);
}

// Replacement of element.boundingBox() - we need to get node positions within SVG container, not relative to main frame
export async function getElementBBox(element: ElementHandle<SVGGraphicsElement>) {
return await element.evaluate(el => {
const { x, y, width, height } = el.getBBox();
return { x, y, width, height };
});
}
96 changes: 96 additions & 0 deletions test/functional/graph.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Mockiavelli } from 'mockiavelli';
import { mockGetEnvironments, mockGetServices } from './requests-mocks';
import config from '../../jest-puppeteer.config';
import { byTestId } from './selectors';
import { Overview } from './views/overview';
import { getElementBBox, waitForAllTransitions, waitForGraphStabilization } from './graph.helpers';

describe('Graph', () => {
let mockiavelli: Mockiavelli;

const overview = new Overview();

beforeEach(async () => {
await jestPuppeteer.resetPage();

mockiavelli = await Mockiavelli.setup(page);
mockGetEnvironments(mockiavelli);
mockGetServices(mockiavelli);

await page.goto(`http://localhost:${config.server.port}`);
await page.waitForSelector(byTestId('node'));
});

test('shows nodes with service name in label and version in tooltip visible on hover', async () => {
const nodes = await overview.getNodes();

expect(await overview.getNodesText(nodes)).toEqual(['service-1', 'service-2', 'service-3']);
expect(await overview.getNodesTooltipText(nodes)).toEqual(['1.1.0', '1.2.0', '1.3.0']);
});

test('highlights connected nodes on click with range expansion via arrow keys', async () => {
const nodes = await overview.getNodes();

expect(await overview.getHighlightedClickedNodeText()).toBe('');
expect(await overview.getHighlightedConnectedNodesTexts()).toEqual([]);
expect(await overview.isHighlightBackgroundVisible()).toBeFalsy();

await nodes[0].click();
expect(await overview.getHighlightedClickedNodeText()).toBe('service-1');
expect(await overview.getHighlightedConnectedNodesTexts()).toEqual(['service-2']);

expect(await overview.isHighlightBackgroundVisible()).toBeTruthy();

await nodes[0].press('ArrowRight');
expect(await overview.getHighlightedConnectedNodesTexts()).toEqual(['service-2', 'service-3']);

// press again to make sure state doesnt change if range is max already
await nodes[0].press('ArrowRight');
expect(await overview.getHighlightedConnectedNodesTexts()).toEqual(['service-2', 'service-3']);

await nodes[0].press('ArrowLeft');
expect(await overview.getHighlightedConnectedNodesTexts()).toEqual(['service-2']);

// press again to make sure state doesnt change if range is min already
await nodes[0].press('ArrowLeft');
expect(await overview.getHighlightedConnectedNodesTexts()).toEqual(['service-2']);

await overview.clickOutsideNode();

expect(await overview.getHighlightedClickedNodeText()).toBe('');
expect(await overview.getHighlightedConnectedNodesTexts()).toEqual([]);

await waitForAllTransitions();
expect(await overview.isHighlightBackgroundVisible()).toBeFalsy();
});

test('allows nodes dragging when no node is in clicked state', async () => {
const nodes = await overview.getNodes();

await waitForGraphStabilization();

let firstNodeBBox = await getElementBBox(nodes[0]);

await overview.dragNode(nodes[0]);
await waitForGraphStabilization();
expect(await getElementBBox(nodes[0])).not.toEqual(firstNodeBBox);

await nodes[0].click();
await waitForGraphStabilization();

firstNodeBBox = await getElementBBox(nodes[0]);
await overview.dragNode(nodes[0]);
expect(await getElementBBox(nodes[0])).toEqual(firstNodeBBox);

// assert that other nodes cant be moved either
const secondNodeBBox = await getElementBBox(nodes[1]);
await overview.dragNode(nodes[1]);
expect(await getElementBBox(nodes[1])).toEqual(secondNodeBBox);

await overview.clickOutsideNode();

await overview.dragNode(nodes[0]);
expect(await nodes[0].boundingBox()).not.toEqual(firstNodeBBox);
expect(await nodes[1].boundingBox()).not.toEqual(secondNodeBBox);
});
});
3 changes: 3 additions & 0 deletions test/functional/selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function byTestId(testId: string) {
return `[data-test-id="${testId}"]`;
}
100 changes: 100 additions & 0 deletions test/functional/views/overview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { byTestId } from '../selectors';
import { ElementHandle } from 'puppeteer';
import { getElementBBox } from '../graph.helpers';

export class Overview {
public async getNodes() {
await page.waitForSelector(byTestId('node'));
return page.$$(byTestId('node'));
}

public async getNodesText(nodes: ElementHandle[]) {
return Promise.all(nodes.map(this.getNodeText));
}

public async getNodesTooltipText(nodes: ElementHandle[]) {
// hover each node sequentially
let tooltipTexts = [];
for (const node of nodes) {
await node.hover();
await page.waitForSelector(byTestId('tooltip'), { visible: true });
const tooltip = await page.$(byTestId('tooltip'));
const tooltipText = await tooltip?.getProperty('textContent');
tooltipTexts.push(await tooltipText?.jsonValue());
}
return tooltipTexts;
}

public async getHighlightedClickedNodeText() {
const allNodes = await this.getNodes();

let clickedNode: ElementHandle | undefined;

for (const node of allNodes) {
const { labelColor, textColor } = await this.getNodeColors(node);

if (labelColor === '#071D49' && textColor === '#FFFFFF') {
clickedNode = node;
break;
}
}

if (clickedNode === undefined) {
return '';
}

return this.getNodeText(clickedNode);
}

public async getHighlightedConnectedNodesTexts() {
const allNodes = await this.getNodes();

const highlightedNodes = [];

for (const node of allNodes) {
const { labelColor, textColor } = await this.getNodeColors(node);

if (labelColor === '#00C29E' && textColor === '#FFFFFF') {
highlightedNodes.push(node);
}
}

return this.getNodesText(highlightedNodes);
}

public async isHighlightBackgroundVisible() {
const background = await this.getHighlightBackground();
return Number(await background?.evaluate(el => (el as SVGRectElement).style.opacity)) > 0;
}

public async clickOutsideNode() {
const background = await this.getHighlightBackground();
await background?.click();
}

public async dragNode(node: ElementHandle) {
const { x, y, width, height } = await getElementBBox(node);

await page.mouse.move(x + width / 2, y + height / 2);
await page.mouse.down();
await page.mouse.move(x - 100, y - 100);
await page.waitFor(100);
await page.mouse.up();
}

private async getNodeText(node: ElementHandle) {
const textContent = await node.getProperty('textContent');
return textContent.jsonValue();
}

private async getNodeColors(node: ElementHandle) {
const labelColor = await node.$eval('path', el => el.getAttribute('fill'));
const textColor = await node.$eval('text', el => el.getAttribute('fill'));

return { labelColor, textColor };
}

private async getHighlightBackground() {
return page.$(byTestId('highlight-background'));
}
}

0 comments on commit 2de0f0d

Please sign in to comment.