Commit 9b577500 authored by Frédéric Caplette's avatar Frédéric Caplette Committed by Sarah Groff Hennigh-Palermo

Add tests for links inner component

This add tests for the link drawing in the CI
config graph.
parent 58432c3f
...@@ -40,10 +40,10 @@ export const generateLinksData = ({ links }, containerID, modifier = '') => { ...@@ -40,10 +40,10 @@ export const generateLinksData = ({ links }, containerID, modifier = '') => {
// positioned in the center of the job node by adding half the height // positioned in the center of the job node by adding half the height
// of the job pill. // of the job pill.
const paddingLeft = parseFloat( const paddingLeft = parseFloat(
window.getComputedStyle(containerEl, null).getPropertyValue('padding-left'), window.getComputedStyle(containerEl, null).getPropertyValue('padding-left') || 0,
); );
const paddingTop = parseFloat( const paddingTop = parseFloat(
window.getComputedStyle(containerEl, null).getPropertyValue('padding-top'), window.getComputedStyle(containerEl, null).getPropertyValue('padding-top') || 0,
); );
const sourceNodeX = sourceNodeCoordinates.right - containerCoordinates.x - paddingLeft; const sourceNodeX = sourceNodeCoordinates.right - containerCoordinates.x - paddingLeft;
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Links Inner component with a large number of needs matches snapshot and has expected path 1`] = `
"<div class=\\"gl-display-flex gl-relative\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute\\">
<path d=\\"M202,118L42,118C72,118,72,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M202,118L52,118C82,118,82,148,112,148\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M222,138L62,138C92,138,92,158,122,158\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M212,128L72,128C102,128,102,168,132,168\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
<path d=\\"M232,148L82,148C112,148,112,178,142,178\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
</svg> </div>"
`;
exports[`Links Inner component with a parallel need matches snapshot and has expected path 1`] = `
"<div class=\\"gl-display-flex gl-relative\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute\\">
<path d=\\"M192,108L22,108C52,108,52,118,82,118\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
</svg> </div>"
`;
exports[`Links Inner component with one need matches snapshot and has expected path 1`] = `
"<div class=\\"gl-display-flex gl-relative\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute\\">
<path d=\\"M202,118L42,118C72,118,72,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path>
</svg> </div>"
`;
import { shallowMount } from '@vue/test-utils';
import { setHTMLFixture } from 'helpers/fixtures';
import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
import { createJobsHash } from '~/pipelines/utils';
import {
jobRect,
largePipelineData,
parallelNeedData,
pipelineData,
pipelineDataWithNoNeeds,
rootRect,
} from '../pipeline_graph/mock_data';
describe('Links Inner component', () => {
const containerId = 'pipeline-graph-container';
const defaultProps = {
containerId,
containerMeasurements: { width: 1019, height: 445 },
pipelineId: 1,
pipelineData: [],
};
let wrapper;
const createComponent = (props) => {
wrapper = shallowMount(LinksInner, {
propsData: { ...defaultProps, ...props },
});
};
const findLinkSvg = () => wrapper.find('#link-svg');
const findAllLinksPath = () => findLinkSvg().findAll('path');
// We create fixture so that each job has an empty div that represent
// the JobPill in the DOM. Each `JobPill` would have different coordinates,
// so we increment their coordinates on each iteration to simulat different positions.
const setFixtures = ({ stages }) => {
const jobs = createJobsHash(stages);
const arrayOfJobs = Object.keys(jobs);
const linksHtmlElements = arrayOfJobs.map((job) => {
return `<div id=${job}-${defaultProps.pipelineId} />`;
});
setHTMLFixture(`<div id="${containerId}">${linksHtmlElements.join(' ')}</div>`);
// We are mocking the clientRect data of each job and the container ID.
jest
.spyOn(document.getElementById(containerId), 'getBoundingClientRect')
.mockImplementation(() => rootRect);
arrayOfJobs.forEach((job, index) => {
jest
.spyOn(
document.getElementById(`${job}-${defaultProps.pipelineId}`),
'getBoundingClientRect',
)
.mockImplementation(() => {
const newValue = 10 * index;
const { left, right, top, bottom, x, y } = jobRect;
return {
...jobRect,
left: left + newValue,
right: right + newValue,
top: top + newValue,
bottom: bottom + newValue,
x: x + newValue,
y: y + newValue,
};
});
});
};
afterEach(() => {
jest.restoreAllMocks();
wrapper.destroy();
wrapper = null;
});
describe('basic SVG creation', () => {
beforeEach(() => {
createComponent();
});
it('renders an SVG of the right size', () => {
expect(findLinkSvg().exists()).toBe(true);
expect(findLinkSvg().attributes('width')).toBe(
`${defaultProps.containerMeasurements.width}px`,
);
expect(findLinkSvg().attributes('height')).toBe(
`${defaultProps.containerMeasurements.height}px`,
);
});
});
describe('no pipeline data', () => {
beforeEach(() => {
createComponent();
});
it('renders the component', () => {
expect(findLinkSvg().exists()).toBe(true);
expect(findAllLinksPath()).toHaveLength(0);
});
});
describe('pipeline data with no needs', () => {
beforeEach(() => {
createComponent({ pipelineData: pipelineDataWithNoNeeds.stages });
});
it('renders no links', () => {
expect(findLinkSvg().exists()).toBe(true);
expect(findAllLinksPath()).toHaveLength(0);
});
});
describe('with one need', () => {
beforeEach(() => {
setFixtures(pipelineData);
createComponent({ pipelineData: pipelineData.stages });
});
it('renders one link', () => {
expect(findAllLinksPath()).toHaveLength(1);
});
it('path does not contain NaN values', () => {
expect(wrapper.html()).not.toContain('NaN');
});
it('matches snapshot and has expected path', () => {
expect(wrapper.html()).toMatchSnapshot();
});
});
describe('with a parallel need', () => {
beforeEach(() => {
setFixtures(parallelNeedData);
createComponent({ pipelineData: parallelNeedData.stages });
});
it('renders only one link for all the same parallel jobs', () => {
expect(findAllLinksPath()).toHaveLength(1);
});
it('path does not contain NaN values', () => {
expect(wrapper.html()).not.toContain('NaN');
});
it('matches snapshot and has expected path', () => {
expect(wrapper.html()).toMatchSnapshot();
});
});
describe('with a large number of needs', () => {
beforeEach(() => {
setFixtures(largePipelineData);
createComponent({ pipelineData: largePipelineData.stages });
});
it('renders the correct number of links', () => {
expect(findAllLinksPath()).toHaveLength(5);
});
it('path does not contain NaN values', () => {
expect(wrapper.html()).not.toContain('NaN');
});
it('matches snapshot and has expected path', () => {
expect(wrapper.html()).toMatchSnapshot();
});
});
describe('interactions', () => {
beforeEach(() => {
setFixtures(largePipelineData);
createComponent({ pipelineData: largePipelineData.stages });
});
it('highlight needs on hover', async () => {
const firstLink = findAllLinksPath().at(0);
const defaultColorClass = 'gl-stroke-gray-200';
const hoverColorClass = 'gl-stroke-blue-400';
expect(firstLink.classes(defaultColorClass)).toBe(true);
expect(firstLink.classes(hoverColorClass)).toBe(false);
// Because there is a watcher, we need to set the props after the component
// has mounted.
await wrapper.setProps({ highlightedJob: 'test_1' });
expect(firstLink.classes(defaultColorClass)).toBe(false);
expect(firstLink.classes(hoverColorClass)).toBe(true);
});
});
});
import { createUniqueLinkId } from '~/pipelines/components/graph_shared/drawing_utils';
export const yamlString = `stages: export const yamlString = `stages:
- empty - empty
- build - build
...@@ -41,10 +39,28 @@ deploy_a: ...@@ -41,10 +39,28 @@ deploy_a:
script: echo hello script: echo hello
`; `;
const jobId1 = createUniqueLinkId('build', 'build_1'); export const pipelineDataWithNoNeeds = {
const jobId2 = createUniqueLinkId('test', 'test_1'); stages: [
const jobId3 = createUniqueLinkId('test', 'test_2'); {
const jobId4 = createUniqueLinkId('deploy', 'deploy_1'); name: 'build',
groups: [
{
name: 'build_1',
jobs: [{ script: 'echo hello', stage: 'build' }],
},
],
},
{
name: 'test',
groups: [
{
name: 'test_1',
jobs: [{ script: 'yarn test', stage: 'test' }],
},
],
},
],
};
export const pipelineData = { export const pipelineData = {
stages: [ stages: [
...@@ -54,7 +70,6 @@ export const pipelineData = { ...@@ -54,7 +70,6 @@ export const pipelineData = {
{ {
name: 'build_1', name: 'build_1',
jobs: [{ script: 'echo hello', stage: 'build' }], jobs: [{ script: 'echo hello', stage: 'build' }],
id: jobId1,
}, },
], ],
}, },
...@@ -64,12 +79,10 @@ export const pipelineData = { ...@@ -64,12 +79,10 @@ export const pipelineData = {
{ {
name: 'test_1', name: 'test_1',
jobs: [{ script: 'yarn test', stage: 'test' }], jobs: [{ script: 'yarn test', stage: 'test' }],
id: jobId2,
}, },
{ {
name: 'test_2', name: 'test_2',
jobs: [{ script: 'yarn karma', stage: 'test' }], jobs: [{ script: 'yarn karma', stage: 'test' }],
id: jobId3,
}, },
], ],
}, },
...@@ -79,7 +92,86 @@ export const pipelineData = { ...@@ -79,7 +92,86 @@ export const pipelineData = {
{ {
name: 'deploy_1', name: 'deploy_1',
jobs: [{ script: 'yarn magick', stage: 'deploy', needs: ['test_1'] }], jobs: [{ script: 'yarn magick', stage: 'deploy', needs: ['test_1'] }],
id: jobId4, },
],
},
],
};
export const parallelNeedData = {
stages: [
{
name: 'build',
groups: [
{
name: 'build_1',
parallel: 3,
jobs: [
{ script: 'echo hello', stage: 'build', name: 'build_1 1/3' },
{ script: 'echo hello', stage: 'build', name: 'build_1 2/3' },
{ script: 'echo hello', stage: 'build', name: 'build_1 3/3' },
],
},
],
},
{
name: 'test',
groups: [
{
name: 'test_1',
jobs: [{ script: 'yarn test', stage: 'test', needs: ['build_1'] }],
},
],
},
],
};
export const largePipelineData = {
stages: [
{
name: 'build',
groups: [
{
name: 'build_1',
jobs: [{ script: 'echo hello', stage: 'build' }],
},
{
name: 'build_2',
jobs: [{ script: 'echo hello', stage: 'build' }],
},
{
name: 'build_3',
jobs: [{ script: 'echo hello', stage: 'build' }],
},
],
},
{
name: 'test',
groups: [
{
name: 'test_1',
jobs: [{ script: 'yarn test', stage: 'test', needs: ['build_2'] }],
},
{
name: 'test_2',
jobs: [{ script: 'yarn karma', stage: 'test', needs: ['build_2'] }],
},
],
},
{
name: 'deploy',
groups: [
{
name: 'deploy_1',
jobs: [{ script: 'yarn magick', stage: 'deploy', needs: ['test_1'] }],
},
{
name: 'deploy_2',
jobs: [{ script: 'yarn magick', stage: 'deploy', needs: ['build_3'] }],
},
{
name: 'deploy_3',
jobs: [{ script: 'yarn magick', stage: 'deploy', needs: ['test_2'] }],
}, },
], ],
}, },
...@@ -94,9 +186,30 @@ export const singleStageData = { ...@@ -94,9 +186,30 @@ export const singleStageData = {
{ {
name: 'build_1', name: 'build_1',
jobs: [{ script: 'echo hello', stage: 'build' }], jobs: [{ script: 'echo hello', stage: 'build' }],
id: jobId1,
}, },
], ],
}, },
], ],
}; };
export const rootRect = {
bottom: 463,
height: 271,
left: 236,
right: 1252,
top: 192,
width: 1016,
x: 236,
y: 192,
};
export const jobRect = {
bottom: 312,
height: 24,
left: 308,
right: 428,
top: 288,
width: 120,
x: 308,
y: 288,
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment