From 9a0596fa71eaeae2b7a17d1af9606415633cd289 Mon Sep 17 00:00:00 2001 From: Karol Maciolek Date: Tue, 4 Aug 2020 17:26:27 +0200 Subject: [PATCH] Adds functional tests for overview, shortens time for graph to stabilize --- .npmrc | 0 src/components/DependencyGraph.tsx | 2 +- src/overview/graph-nodes.ts | 2 +- .../highlight-background.ts | 1 + src/overview/overview.helpers.ts | 4 +- src/overview/simulation/simulation.ts | 1 + src/overview/tooltip/tooltip.ts | 3 +- src/utils/helpers/UserEventHelpers.ts | 23 ++-- test/functional/app.test.ts | 25 ----- test/functional/graph.helpers.ts | 18 ++++ test/functional/graph.test.ts | 96 +++++++++++++++++ test/functional/selectors.ts | 3 + test/functional/views/overview.ts | 100 ++++++++++++++++++ 13 files changed, 235 insertions(+), 43 deletions(-) delete mode 100644 .npmrc delete mode 100644 test/functional/app.test.ts create mode 100644 test/functional/graph.helpers.ts create mode 100644 test/functional/graph.test.ts create mode 100644 test/functional/selectors.ts create mode 100644 test/functional/views/overview.ts diff --git a/.npmrc b/.npmrc deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/DependencyGraph.tsx b/src/components/DependencyGraph.tsx index ebed6ea..1f1242e 100644 --- a/src/components/DependencyGraph.tsx +++ b/src/components/DependencyGraph.tsx @@ -90,5 +90,5 @@ const drawerOpenButtonCls = css({ position: 'fixed', top: 20, left: 20, - zIndex: 10, + zIndex: 6, }); diff --git a/src/overview/graph-nodes.ts b/src/overview/graph-nodes.ts index f2616a0..234add6 100644 --- a/src/overview/graph-nodes.ts +++ b/src/overview/graph-nodes.ts @@ -21,7 +21,7 @@ export function createNodes(labelNodesGroup: NodeSelection, nodes: .data(nodes) .enter() .append('g') - .attr('data-test-id', 'label') + .attr('data-test-id', 'node') .attr('cursor', 'pointer') .append('text') .attr('font-size', BASE_FONT_SIZE) diff --git a/src/overview/highlight-background/highlight-background.ts b/src/overview/highlight-background/highlight-background.ts index 5e645a9..a1be92f 100644 --- a/src/overview/highlight-background/highlight-background.ts +++ b/src/overview/highlight-background/highlight-background.ts @@ -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) diff --git a/src/overview/overview.helpers.ts b/src/overview/overview.helpers.ts index ce4fe17..09cd6db 100644 --- a/src/overview/overview.helpers.ts +++ b/src/overview/overview.helpers.ts @@ -119,10 +119,10 @@ export function highlightNodes(clickedNode: DependencyNode) { if (areNodesDirectlyConnected) { select(labelElement).attr('fill', getHighLightedLabelColor); - select(textElement).style('fill', Colors.WHITE); + select(textElement).attr('fill', Colors.WHITE); } else { select(labelElement).attr('fill', Colors.LIGHT_GREY); - select(textElement).style('fill', Colors.BASIC_TEXT); + select(textElement).attr('fill', Colors.BASIC_TEXT); } }); } diff --git a/src/overview/simulation/simulation.ts b/src/overview/simulation/simulation.ts index 5e7f7e7..5359883 100644 --- a/src/overview/simulation/simulation.ts +++ b/src/overview/simulation/simulation.ts @@ -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)) diff --git a/src/overview/tooltip/tooltip.ts b/src/overview/tooltip/tooltip.ts index 0371c10..8fdee1f 100644 --- a/src/overview/tooltip/tooltip.ts +++ b/src/overview/tooltip/tooltip.ts @@ -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) { const tooltipElement = svgContainer .append('g') .attr('id', 'tooltip') + .attr('data-test-id', 'tooltip') .style('opacity', 0); tooltipElement .append('rect') diff --git a/src/utils/helpers/UserEventHelpers.ts b/src/utils/helpers/UserEventHelpers.ts index 2f06582..c0737fe 100644 --- a/src/utils/helpers/UserEventHelpers.ts +++ b/src/utils/helpers/UserEventHelpers.ts @@ -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, @@ -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, @@ -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', @@ -90,7 +87,7 @@ export function subscribeToChangeHighlightRangeOnArrowKey() { } select(labelElement).attr('fill', labelColor); - select(textElement).style('fill', textColor); + select(textElement).attr('fill', textColor); }); zoomToHighLightedNodes(); @@ -115,7 +112,7 @@ export function subscribeToResetHighlight() { } select(labelElement).attr('fill', Colors.LIGHT_GREY); - select(textElement).style('fill', Colors.BASIC_TEXT); + select(textElement).attr('fill', Colors.BASIC_TEXT); }); hideHighlightBackground(); diff --git a/test/functional/app.test.ts b/test/functional/app.test.ts deleted file mode 100644 index 53dbd7c..0000000 --- a/test/functional/app.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Mockiavelli } from 'mockiavelli'; -import { mockGetEnvironments, mockGetServices } from './requests-mocks'; -import config from '../../jest-puppeteer.config'; - -describe('App', () => { - let mockiavelli: Mockiavelli; - - beforeEach(async () => { - await jestPuppeteer.resetPage(); - - mockiavelli = await Mockiavelli.setup(page); - mockGetEnvironments(mockiavelli); - mockGetServices(mockiavelli); - - await page.goto(`http://localhost:${config.server.port}`); - }); - - test('Basic test', async () => { - await page.waitForSelector(byTestId('label')); - }); -}); - -function byTestId(testId: string) { - return `[data-test-id="${testId}"]`; -} diff --git a/test/functional/graph.helpers.ts b/test/functional/graph.helpers.ts new file mode 100644 index 0000000..9632fef --- /dev/null +++ b/test/functional/graph.helpers.ts @@ -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) { + return await element.evaluate(el => { + const { x, y, width, height } = el.getBBox(); + return { x, y, width, height }; + }); +} diff --git a/test/functional/graph.test.ts b/test/functional/graph.test.ts new file mode 100644 index 0000000..8d246fc --- /dev/null +++ b/test/functional/graph.test.ts @@ -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(); + }); + + it('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); + }); +}); diff --git a/test/functional/selectors.ts b/test/functional/selectors.ts new file mode 100644 index 0000000..2a74652 --- /dev/null +++ b/test/functional/selectors.ts @@ -0,0 +1,3 @@ +export function byTestId(testId: string) { + return `[data-test-id="${testId}"]`; +} diff --git a/test/functional/views/overview.ts b/test/functional/views/overview.ts new file mode 100644 index 0000000..8406209 --- /dev/null +++ b/test/functional/views/overview.ts @@ -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')); + } +}