Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/pipelines/graph_shared')
-rw-r--r--spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap23
-rw-r--r--spec/frontend/pipelines/graph_shared/links_inner_spec.js197
-rw-r--r--spec/frontend/pipelines/graph_shared/links_layer_spec.js99
3 files changed, 319 insertions, 0 deletions
diff --git a/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap b/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap
new file mode 100644
index 00000000000..cf2b66dea5f
--- /dev/null
+++ b/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap
@@ -0,0 +1,23 @@
+// 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 gl-pointer-events-none\\">
+ <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 gl-pointer-events-none\\">
+ <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 gl-pointer-events-none\\">
+ <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>"
+`;
diff --git a/spec/frontend/pipelines/graph_shared/links_inner_spec.js b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
new file mode 100644
index 00000000000..6cabe2bc8a7
--- /dev/null
+++ b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
@@ -0,0 +1,197 @@
+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);
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/graph_shared/links_layer_spec.js b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
new file mode 100644
index 00000000000..0ff8583fbff
--- /dev/null
+++ b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
@@ -0,0 +1,99 @@
+import { GlAlert, GlButton } from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
+import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
+import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
+import { generateResponse, mockPipelineResponse } from '../graph/mock_data';
+
+describe('links layer component', () => {
+ let wrapper;
+
+ const findAlert = () => wrapper.find(GlAlert);
+ const findShowAnyways = () => findAlert().find(GlButton);
+ const findLinksInner = () => wrapper.find(LinksInner);
+
+ const pipeline = generateResponse(mockPipelineResponse, 'root/fungi-xoxo');
+ const containerId = `pipeline-links-container-${pipeline.id}`;
+ const slotContent = "<div>Ceci n'est pas un graphique</div>";
+
+ const tooManyStages = Array(101)
+ .fill(0)
+ .flatMap(() => pipeline.stages);
+
+ const defaultProps = {
+ containerId,
+ containerMeasurements: { width: 400, height: 400 },
+ pipelineId: pipeline.id,
+ pipelineData: pipeline.stages,
+ };
+
+ const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => {
+ wrapper = mountFn(LinksLayer, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ slots: {
+ default: slotContent,
+ },
+ stubs: {
+ 'links-inner': true,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('with data under max stages', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders the default slot', () => {
+ expect(wrapper.html()).toContain(slotContent);
+ });
+
+ it('renders the inner links component', () => {
+ expect(findLinksInner().exists()).toBe(true);
+ });
+ });
+
+ describe('with more than the max number of stages', () => {
+ describe('rendering', () => {
+ beforeEach(() => {
+ createComponent({ props: { pipelineData: tooManyStages } });
+ });
+
+ it('renders the default slot', () => {
+ expect(wrapper.html()).toContain(slotContent);
+ });
+
+ it('renders the alert component', () => {
+ expect(findAlert().exists()).toBe(true);
+ });
+
+ it('does not render the inner links component', () => {
+ expect(findLinksInner().exists()).toBe(false);
+ });
+ });
+
+ describe('interactions', () => {
+ beforeEach(() => {
+ createComponent({ mountFn: mount, props: { pipelineData: tooManyStages } });
+ });
+
+ it('renders the disable button', () => {
+ expect(findShowAnyways().exists()).toBe(true);
+ expect(findShowAnyways().text()).toBe(wrapper.vm.$options.i18n.showLinksAnyways);
+ });
+
+ it('shows links when override is clicked', async () => {
+ expect(findLinksInner().exists()).toBe(false);
+ await findShowAnyways().trigger('click');
+ expect(findLinksInner().exists()).toBe(true);
+ });
+ });
+ });
+});