Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds functional tests for overview, shortens time for graph to stabilize #42

Merged
merged 1 commit into from
Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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'));
}
}