Commit 88a4f6a4 authored by Frédéric Caplette's avatar Frédéric Caplette Committed by Phil Hughes

Migrate pipelines/graph component specs to jest

The pipeline graph components and the mock data
have been moved to frontend folder
and rewrote the tests with jest
parent 20d1bda1
...@@ -7,6 +7,7 @@ import ActionComponent from '~/pipelines/components/graph/action_component.vue'; ...@@ -7,6 +7,7 @@ import ActionComponent from '~/pipelines/components/graph/action_component.vue';
describe('pipeline graph action component', () => { describe('pipeline graph action component', () => {
let wrapper; let wrapper;
let mock; let mock;
const findButton = () => wrapper.find('button');
beforeEach(() => { beforeEach(() => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
...@@ -44,15 +45,15 @@ describe('pipeline graph action component', () => { ...@@ -44,15 +45,15 @@ describe('pipeline graph action component', () => {
}); });
it('should render an svg', () => { it('should render an svg', () => {
expect(wrapper.find('.ci-action-icon-wrapper')).toBeDefined(); expect(wrapper.find('.ci-action-icon-wrapper').exists()).toBe(true);
expect(wrapper.find('svg')).toBeDefined(); expect(wrapper.find('svg').exists()).toBe(true);
}); });
describe('on click', () => { describe('on click', () => {
it('emits `pipelineActionRequestComplete` after a successful request', done => { it('emits `pipelineActionRequestComplete` after a successful request', done => {
jest.spyOn(wrapper.vm, '$emit'); jest.spyOn(wrapper.vm, '$emit');
wrapper.find('button').trigger('click'); findButton().trigger('click');
waitForPromises() waitForPromises()
.then(() => { .then(() => {
...@@ -63,7 +64,7 @@ describe('pipeline graph action component', () => { ...@@ -63,7 +64,7 @@ describe('pipeline graph action component', () => {
}); });
it('renders a loading icon while waiting for request', done => { it('renders a loading icon while waiting for request', done => {
wrapper.find('button').trigger('click'); findButton().trigger('click');
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect(wrapper.find('.js-action-icon-loading').exists()).toBe(true); expect(wrapper.find('.js-action-icon-loading').exists()).toBe(true);
......
import Vue from 'vue'; import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import { mount } from '@vue/test-utils';
import PipelineStore from '~/pipelines/stores/pipeline_store'; import PipelineStore from '~/pipelines/stores/pipeline_store';
import graphComponent from '~/pipelines/components/graph/graph_component.vue'; import graphComponent from '~/pipelines/components/graph/graph_component.vue';
import stageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
import linkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
import graphJSON from './mock_data'; import graphJSON from './mock_data';
import linkedPipelineJSON from '../linked_pipelines_mock.json'; import linkedPipelineJSON from './linked_pipelines_mock_data';
import PipelinesMediator from '~/pipelines/pipeline_details_mediator'; import PipelinesMediator from '~/pipelines/pipeline_details_mediator';
describe('graph component', () => { describe('graph component', () => {
const GraphComponent = Vue.extend(graphComponent);
const store = new PipelineStore(); const store = new PipelineStore();
store.storePipeline(linkedPipelineJSON); store.storePipeline(linkedPipelineJSON);
const mediator = new PipelinesMediator({ endpoint: '' }); const mediator = new PipelinesMediator({ endpoint: '' });
let component; let wrapper;
beforeEach(() => {
setFixtures(`
<div class="layout-page"></div>
`);
});
afterEach(() => { afterEach(() => {
component.$destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('while is loading', () => { describe('while is loading', () => {
it('should render a loading icon', () => { it('should render a loading icon', () => {
component = mountComponent(GraphComponent, { wrapper = mount(graphComponent, {
isLoading: true, propsData: {
pipeline: {}, isLoading: true,
mediator, pipeline: {},
mediator,
},
}); });
expect(component.$el.querySelector('.loading-icon')).toBeDefined(); expect(wrapper.find('.gl-spinner').exists()).toBe(true);
}); });
}); });
describe('with data', () => { describe('with data', () => {
it('should render the graph', () => { it('should render the graph', () => {
component = mountComponent(GraphComponent, { wrapper = mount(graphComponent, {
isLoading: false, propsData: {
pipeline: graphJSON, isLoading: false,
mediator, pipeline: graphJSON,
mediator,
},
}); });
expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true); expect(wrapper.find('.js-pipeline-graph').exists()).toBe(true);
expect( expect(wrapper.find(stageColumnComponent).classes()).toContain('no-margin');
component.$el.querySelector('.stage-column:first-child').classList.contains('no-margin'),
).toEqual(true);
expect( expect(
component.$el.querySelector('.stage-column:nth-child(2)').classList.contains('left-margin'), wrapper
).toEqual(true); .findAll(stageColumnComponent)
.at(1)
.classes(),
).toContain('left-margin');
expect( expect(wrapper.find('.stage-column:nth-child(2) .build:nth-child(1)').classes()).toContain(
component.$el 'left-connector',
.querySelector('.stage-column:nth-child(2) .build:nth-child(1)') );
.classList.contains('left-connector'),
).toEqual(true);
expect(component.$el.querySelector('loading-icon')).toBe(null); expect(wrapper.find('.loading-icon').exists()).toBe(false);
expect(component.$el.querySelector('.stage-column-list')).toBeDefined(); expect(wrapper.find('.stage-column-list').exists()).toBe(true);
}); });
}); });
describe('when linked pipelines are present', () => { describe('when linked pipelines are present', () => {
beforeEach(() => { beforeEach(() => {
component = mountComponent(GraphComponent, { wrapper = mount(graphComponent, {
isLoading: false, propsData: {
pipeline: store.state.pipeline, isLoading: false,
mediator, pipeline: store.state.pipeline,
mediator,
},
}); });
}); });
describe('rendered output', () => { describe('rendered output', () => {
it('should include the pipelines graph', () => { it('should include the pipelines graph', () => {
expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true); expect(wrapper.find('.js-pipeline-graph').exists()).toBe(true);
}); });
it('should not include the loading icon', () => { it('should not include the loading icon', () => {
expect(component.$el.querySelector('.fa-spinner')).toBeNull(); expect(wrapper.find('.fa-spinner').exists()).toBe(false);
}); });
it('should include the stage column list', () => { it('should include the stage column list', () => {
expect(component.$el.querySelector('.stage-column-list')).not.toBeNull(); expect(wrapper.find(stageColumnComponent).exists()).toBe(true);
}); });
it('should include the no-margin class on the first child', () => { it('should include the no-margin class on the first child if there is only one job', () => {
const firstStageColumnElement = component.$el.querySelector( const firstStageColumnElement = wrapper.find(stageColumnComponent);
'.stage-column-list .stage-column',
);
expect(firstStageColumnElement.classList.contains('no-margin')).toEqual(true); expect(firstStageColumnElement.classes()).toContain('no-margin');
}); });
it('should include the has-only-one-job class on the first child', () => { it('should include the has-only-one-job class on the first child', () => {
const firstStageColumnElement = component.$el.querySelector( const firstStageColumnElement = wrapper.find('.stage-column-list .stage-column');
'.stage-column-list .stage-column',
);
expect(firstStageColumnElement.classList.contains('has-only-one-job')).toEqual(true); expect(firstStageColumnElement.classes()).toContain('has-only-one-job');
}); });
it('should include the left-margin class on the second child', () => { it('should include the left-margin class on the second child', () => {
const firstStageColumnElement = component.$el.querySelector( const firstStageColumnElement = wrapper.find('.stage-column-list .stage-column:last-child');
'.stage-column-list .stage-column:last-child',
);
expect(firstStageColumnElement.classList.contains('left-margin')).toEqual(true); expect(firstStageColumnElement.classes()).toContain('left-margin');
}); });
it('should include the js-has-linked-pipelines flag', () => { it('should include the js-has-linked-pipelines flag', () => {
expect(component.$el.querySelector('.js-has-linked-pipelines')).not.toBeNull(); expect(wrapper.find('.js-has-linked-pipelines').exists()).toBe(true);
}); });
}); });
describe('computeds and methods', () => { describe('computeds and methods', () => {
describe('capitalizeStageName', () => { describe('capitalizeStageName', () => {
it('it capitalizes the stage name', () => { it('it capitalizes the stage name', () => {
expect(component.capitalizeStageName('mystage')).toBe('Mystage'); expect(
wrapper
.findAll('.stage-column .stage-name')
.at(1)
.text(),
).toBe('Prebuild');
}); });
}); });
describe('stageConnectorClass', () => { describe('stageConnectorClass', () => {
it('it returns left-margin when there is a triggerer', () => { it('it returns left-margin when there is a triggerer', () => {
expect(component.stageConnectorClass(0, { groups: ['job'] })).toBe('no-margin'); expect(
wrapper
.findAll(stageColumnComponent)
.at(1)
.classes(),
).toContain('left-margin');
}); });
}); });
}); });
describe('linked pipelines components', () => { describe('linked pipelines components', () => {
beforeEach(() => { beforeEach(() => {
component = mountComponent(GraphComponent, { wrapper = mount(graphComponent, {
isLoading: false, propsData: {
pipeline: store.state.pipeline, isLoading: false,
mediator, pipeline: store.state.pipeline,
mediator,
},
}); });
}); });
it('should render an upstream pipelines column', () => { it('should render an upstream pipelines column at first position', () => {
expect(component.$el.querySelector('.linked-pipelines-column')).not.toBeNull(); expect(wrapper.find(linkedPipelinesColumn).exists()).toBe(true);
expect(component.$el.innerHTML).toContain('Upstream'); expect(wrapper.find('.stage-column .stage-name').text()).toBe('Upstream');
}); });
it('should render a downstream pipelines column', () => { it('should render a downstream pipelines column at last position', () => {
expect(component.$el.querySelector('.linked-pipelines-column')).not.toBeNull(); const stageColumnNames = wrapper.findAll('.stage-column .stage-name');
expect(component.$el.innerHTML).toContain('Downstream');
expect(wrapper.find(linkedPipelinesColumn).exists()).toBe(true);
expect(stageColumnNames.at(stageColumnNames.length - 1).text()).toBe('Downstream');
}); });
describe('triggered by', () => { describe('triggered by', () => {
describe('on click', () => { describe('on click', () => {
it('should emit `onClickTriggeredBy` when triggered by linked pipeline is clicked', () => { it('should emit `onClickTriggeredBy` when triggered by linked pipeline is clicked', () => {
spyOn(component, '$emit'); const btnWrapper = wrapper.find('.linked-pipeline-content');
component.$el.querySelector('#js-linked-pipeline-12').click(); btnWrapper.trigger('click');
expect(component.$emit).toHaveBeenCalledWith( btnWrapper.vm.$nextTick(() => {
'onClickTriggeredBy', expect(wrapper.emitted().onClickTriggeredBy).toEqual([
component.pipeline.triggered_by[0], store.state.pipeline.triggered_by,
); ]);
});
}); });
}); });
...@@ -169,15 +179,17 @@ describe('graph component', () => { ...@@ -169,15 +179,17 @@ describe('graph component', () => {
// expand the pipeline // expand the pipeline
store.state.pipeline.triggered_by[0].isExpanded = true; store.state.pipeline.triggered_by[0].isExpanded = true;
component = mountComponent(GraphComponent, { wrapper = mount(graphComponent, {
isLoading: false, propsData: {
pipeline: store.state.pipeline, isLoading: false,
mediator, pipeline: store.state.pipeline,
mediator,
},
}); });
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
expect(component.$el.querySelector('.js-upstream-pipeline-12')).not.toBeNull(); expect(wrapper.find('.js-upstream-pipeline-12').exists()).toBe(true);
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
...@@ -188,15 +200,22 @@ describe('graph component', () => { ...@@ -188,15 +200,22 @@ describe('graph component', () => {
describe('triggered', () => { describe('triggered', () => {
describe('on click', () => { describe('on click', () => {
it('should emit `onClickTriggered`', () => { it('should emit `onClickTriggered`', () => {
spyOn(component, '$emit'); // We have to mock this method since we do both style change and
spyOn(component, 'calculateMarginTop').and.callFake(() => '16px'); // emit and event, not mocking returns an error.
wrapper.setMethods({
handleClickedDownstream: jest.fn(() =>
wrapper.vm.$emit('onClickTriggered', ...store.state.pipeline.triggered),
),
});
const btnWrappers = wrapper.findAll('.linked-pipeline-content');
const downstreamBtnWrapper = btnWrappers.at(btnWrappers.length - 1);
component.$el.querySelector('#js-linked-pipeline-34993051').click(); downstreamBtnWrapper.trigger('click');
expect(component.$emit).toHaveBeenCalledWith( downstreamBtnWrapper.vm.$nextTick(() => {
'onClickTriggered', expect(wrapper.emitted().onClickTriggered).toEqual([store.state.pipeline.triggered]);
component.pipeline.triggered[0], });
);
}); });
}); });
...@@ -205,17 +224,17 @@ describe('graph component', () => { ...@@ -205,17 +224,17 @@ describe('graph component', () => {
// expand the pipeline // expand the pipeline
store.state.pipeline.triggered[0].isExpanded = true; store.state.pipeline.triggered[0].isExpanded = true;
component = mountComponent(GraphComponent, { wrapper = mount(graphComponent, {
isLoading: false, propsData: {
pipeline: store.state.pipeline, isLoading: false,
mediator, pipeline: store.state.pipeline,
mediator,
},
}); });
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
expect( expect(wrapper.find('.js-downstream-pipeline-34993051')).not.toBeNull();
component.$el.querySelector('.js-downstream-pipeline-34993051'),
).not.toBeNull();
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
...@@ -228,46 +247,58 @@ describe('graph component', () => { ...@@ -228,46 +247,58 @@ describe('graph component', () => {
describe('when linked pipelines are not present', () => { describe('when linked pipelines are not present', () => {
beforeEach(() => { beforeEach(() => {
const pipeline = Object.assign(linkedPipelineJSON, { triggered: null, triggered_by: null }); const pipeline = Object.assign(linkedPipelineJSON, { triggered: null, triggered_by: null });
component = mountComponent(GraphComponent, { wrapper = mount(graphComponent, {
isLoading: false, propsData: {
pipeline, isLoading: false,
mediator, pipeline,
mediator,
},
}); });
}); });
describe('rendered output', () => { describe('rendered output', () => {
it('should include the first column with a no margin', () => { it('should include the first column with a no margin', () => {
const firstColumn = component.$el.querySelector('.stage-column:first-child'); const firstColumn = wrapper.find('.stage-column');
expect(firstColumn.classList.contains('no-margin')).toEqual(true); expect(firstColumn.classes()).toContain('no-margin');
}); });
it('should not render a linked pipelines column', () => { it('should not render a linked pipelines column', () => {
expect(component.$el.querySelector('.linked-pipelines-column')).toBeNull(); expect(wrapper.find('.linked-pipelines-column').exists()).toBe(false);
}); });
}); });
describe('stageConnectorClass', () => { describe('stageConnectorClass', () => {
it('it returns left-margin when no triggerer and there is one job', () => { it('it returns no-margin when no triggerer and there is one job', () => {
expect(component.stageConnectorClass(0, { groups: ['job'] })).toBe('no-margin'); expect(wrapper.find(stageColumnComponent).classes()).toContain('no-margin');
}); });
it('it returns left-margin when no triggerer and not the first stage', () => { it('it returns left-margin when no triggerer and not the first stage', () => {
expect(component.stageConnectorClass(99, { groups: ['job'] })).toBe('left-margin'); expect(
wrapper
.findAll(stageColumnComponent)
.at(1)
.classes(),
).toContain('left-margin');
}); });
}); });
}); });
describe('capitalizeStageName', () => { describe('capitalizeStageName', () => {
it('capitalizes and escapes stage name', () => { it('capitalizes and escapes stage name', () => {
component = mountComponent(GraphComponent, { wrapper = mount(graphComponent, {
isLoading: false, propsData: {
pipeline: graphJSON, isLoading: false,
mediator, pipeline: graphJSON,
mediator,
},
}); });
expect( expect(
component.$el.querySelector('.stage-column:nth-child(2) .stage-name').textContent.trim(), wrapper
.find('.stage-column:nth-child(2) .stage-name')
.text()
.trim(),
).toEqual('Deploy &lt;img src=x onerror=alert(document.domain)&gt;'); ).toEqual('Deploy &lt;img src=x onerror=alert(document.domain)&gt;');
}); });
}); });
......
import Vue from 'vue'; import { shallowMount } from '@vue/test-utils';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import JobGroupDropdown from '~/pipelines/components/graph/job_group_dropdown.vue'; import JobGroupDropdown from '~/pipelines/components/graph/job_group_dropdown.vue';
describe('job group dropdown component', () => { describe('job group dropdown component', () => {
const Component = Vue.extend(JobGroupDropdown);
let vm;
const group = { const group = {
jobs: [ jobs: [
{ {
...@@ -66,20 +62,23 @@ describe('job group dropdown component', () => { ...@@ -66,20 +62,23 @@ describe('job group dropdown component', () => {
}, },
}; };
let wrapper;
const findButton = () => wrapper.find('button');
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
}); });
beforeEach(() => { beforeEach(() => {
vm = mountComponent(Component, { group }); wrapper = shallowMount(JobGroupDropdown, { propsData: { group } });
}); });
it('renders button with group name and size', () => { it('renders button with group name and size', () => {
expect(vm.$el.querySelector('button').textContent).toContain(group.name); expect(findButton().text()).toContain(group.name);
expect(vm.$el.querySelector('button').textContent).toContain(group.size); expect(findButton().text()).toContain(group.size);
}); });
it('renders dropdown with jobs', () => { it('renders dropdown with jobs', () => {
expect(vm.$el.querySelectorAll('.scrollable-menu>ul>li').length).toEqual(group.jobs.length); expect(wrapper.findAll('.scrollable-menu>ul>li').length).toBe(group.jobs.length);
}); });
}); });
...@@ -47,7 +47,7 @@ describe('pipeline graph job item', () => { ...@@ -47,7 +47,7 @@ describe('pipeline graph job item', () => {
expect(link.attributes('title')).toEqual(`${mockJob.name} - ${mockJob.status.label}`); expect(link.attributes('title')).toEqual(`${mockJob.name} - ${mockJob.status.label}`);
expect(wrapper.find('.js-status-icon-success')).toBeDefined(); expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true);
expect(trimText(wrapper.find('.ci-status-text').text())).toBe(mockJob.name); expect(trimText(wrapper.find('.ci-status-text').text())).toBe(mockJob.name);
...@@ -73,7 +73,7 @@ describe('pipeline graph job item', () => { ...@@ -73,7 +73,7 @@ describe('pipeline graph job item', () => {
}, },
}); });
expect(wrapper.find('.js-status-icon-success')).toBeDefined(); expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true);
expect(wrapper.find('a').exists()).toBe(false); expect(wrapper.find('a').exists()).toBe(false);
expect(trimText(wrapper.find('.ci-status-text').text())).toEqual(mockJob.name); expect(trimText(wrapper.find('.ci-status-text').text())).toEqual(mockJob.name);
...@@ -84,8 +84,8 @@ describe('pipeline graph job item', () => { ...@@ -84,8 +84,8 @@ describe('pipeline graph job item', () => {
it('it should render the action icon', () => { it('it should render the action icon', () => {
createWrapper({ job: mockJob }); createWrapper({ job: mockJob });
expect(wrapper.find('a.ci-action-icon-container')).toBeDefined(); expect(wrapper.find('.ci-action-icon-container').exists()).toBe(true);
expect(wrapper.find('i.ci-action-icon-wrapper')).toBeDefined(); expect(wrapper.find('.ci-action-icon-wrapper').exists()).toBe(true);
}); });
}); });
......
import Vue from 'vue'; import { mount } from '@vue/test-utils';
import ciIcon from '~/vue_shared/components/ci_icon.vue';
import jobNameComponent from '~/pipelines/components/graph/job_name_component.vue'; import jobNameComponent from '~/pipelines/components/graph/job_name_component.vue';
describe('job name component', () => { describe('job name component', () => {
let component; let wrapper;
const propsData = {
name: 'foo',
status: {
icon: 'status_success',
group: 'success',
},
};
beforeEach(() => { beforeEach(() => {
const JobNameComponent = Vue.extend(jobNameComponent); wrapper = mount(jobNameComponent, {
component = new JobNameComponent({ propsData,
propsData: { });
name: 'foo',
status: {
icon: 'status_success',
},
},
}).$mount();
}); });
it('should render the provided name', () => { it('should render the provided name', () => {
expect(component.$el.querySelector('.ci-status-text').textContent.trim()).toEqual('foo'); expect(
wrapper
.find('.ci-status-text')
.text()
.trim(),
).toBe(propsData.name);
}); });
it('should render an icon with the provided status', () => { it('should render an icon with the provided status', () => {
expect(component.$el.querySelector('.ci-status-icon-success')).toBeDefined(); expect(wrapper.find(ciIcon).exists()).toBe(true);
expect(component.$el.querySelector('.ci-status-icon-success svg')).toBeDefined(); expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true);
}); });
}); });
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue'; import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
import CiStatus from '~/vue_shared/components/ci_icon.vue';
import mockData from './linked_pipelines_mock_data'; import mockData from './linked_pipelines_mock_data';
const mockPipeline = mockData.triggered[0]; const mockPipeline = mockData.triggered[0];
const validTriggeredPipelineId = mockPipeline.project.id;
const invalidTriggeredPipelineId = mockPipeline.project.id + 5;
describe('Linked pipeline', () => { describe('Linked pipeline', () => {
let wrapper; let wrapper;
const findButton = () => wrapper.find('button');
const createWrapper = propsData => { const createWrapper = propsData => {
wrapper = mount(LinkedPipelineComponent, { wrapper = mount(LinkedPipelineComponent, {
...@@ -21,7 +26,7 @@ describe('Linked pipeline', () => { ...@@ -21,7 +26,7 @@ describe('Linked pipeline', () => {
describe('rendered output', () => { describe('rendered output', () => {
const props = { const props = {
pipeline: mockPipeline, pipeline: mockPipeline,
projectId: 20, projectId: invalidTriggeredPipelineId,
columnTitle: 'Downstream', columnTitle: 'Downstream',
}; };
...@@ -44,14 +49,13 @@ describe('Linked pipeline', () => { ...@@ -44,14 +49,13 @@ describe('Linked pipeline', () => {
}); });
it('should render an svg within the status container', () => { it('should render an svg within the status container', () => {
const pipelineStatusElement = wrapper.find('.js-linked-pipeline-status'); const pipelineStatusElement = wrapper.find(CiStatus);
expect(pipelineStatusElement.find('svg').exists()).toBe(true); expect(pipelineStatusElement.find('svg').exists()).toBe(true);
}); });
it('should render the pipeline status icon svg', () => { it('should render the pipeline status icon svg', () => {
expect(wrapper.find('.js-ci-status-icon-running').exists()).toBe(true); expect(wrapper.find('.ci-status-icon-failed svg').exists()).toBe(true);
expect(wrapper.find('.js-ci-status-icon-running').html()).toContain('<svg');
}); });
it('should have a ci-status child component', () => { it('should have a ci-status child component', () => {
...@@ -88,7 +92,7 @@ describe('Linked pipeline', () => { ...@@ -88,7 +92,7 @@ describe('Linked pipeline', () => {
describe('parent/child', () => { describe('parent/child', () => {
const downstreamProps = { const downstreamProps = {
pipeline: mockPipeline, pipeline: mockPipeline,
projectId: 19, projectId: validTriggeredPipelineId,
columnTitle: 'Downstream', columnTitle: 'Downstream',
}; };
...@@ -116,7 +120,7 @@ describe('Linked pipeline', () => { ...@@ -116,7 +120,7 @@ describe('Linked pipeline', () => {
describe('when isLoading is true', () => { describe('when isLoading is true', () => {
const props = { const props = {
pipeline: { ...mockPipeline, isLoading: true }, pipeline: { ...mockPipeline, isLoading: true },
projectId: 19, projectId: invalidTriggeredPipelineId,
columnTitle: 'Downstream', columnTitle: 'Downstream',
}; };
...@@ -132,7 +136,7 @@ describe('Linked pipeline', () => { ...@@ -132,7 +136,7 @@ describe('Linked pipeline', () => {
describe('on click', () => { describe('on click', () => {
const props = { const props = {
pipeline: mockPipeline, pipeline: mockPipeline,
projectId: 19, projectId: validTriggeredPipelineId,
columnTitle: 'Downstream', columnTitle: 'Downstream',
}; };
...@@ -142,18 +146,18 @@ describe('Linked pipeline', () => { ...@@ -142,18 +146,18 @@ describe('Linked pipeline', () => {
it('emits `pipelineClicked` event', () => { it('emits `pipelineClicked` event', () => {
jest.spyOn(wrapper.vm, '$emit'); jest.spyOn(wrapper.vm, '$emit');
wrapper.find('button').trigger('click'); findButton().trigger('click');
expect(wrapper.emitted().pipelineClicked).toBeTruthy(); expect(wrapper.emitted().pipelineClicked).toBeTruthy();
}); });
it('should emit `bv::hide::tooltip` to close the tooltip', () => { it('should emit `bv::hide::tooltip` to close the tooltip', () => {
jest.spyOn(wrapper.vm.$root, '$emit'); jest.spyOn(wrapper.vm.$root, '$emit');
wrapper.find('button').trigger('click'); findButton().trigger('click');
expect(wrapper.vm.$root.$emit.mock.calls[0]).toEqual([ expect(wrapper.vm.$root.$emit.mock.calls[0]).toEqual([
'bv::hide::tooltip', 'bv::hide::tooltip',
'js-linked-pipeline-132', 'js-linked-pipeline-34993051',
]); ]);
}); });
}); });
......
import Vue from 'vue'; import { shallowMount } from '@vue/test-utils';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue'; import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
import LinkedPipeline from '~/pipelines/components/graph/linked_pipeline.vue';
import mockData from './linked_pipelines_mock_data'; import mockData from './linked_pipelines_mock_data';
describe('Linked Pipelines Column', () => { describe('Linked Pipelines Column', () => {
const Component = Vue.extend(LinkedPipelinesColumn); const propsData = {
const props = {
columnTitle: 'Upstream', columnTitle: 'Upstream',
linkedPipelines: mockData.triggered, linkedPipelines: mockData.triggered,
graphPosition: 'right', graphPosition: 'right',
projectId: 19, projectId: 19,
}; };
let vm; let wrapper;
beforeEach(() => { beforeEach(() => {
vm = mountComponent(Component, props); wrapper = shallowMount(LinkedPipelinesColumn, { propsData });
}); });
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
}); });
it('renders the pipeline orientation', () => { it('renders the pipeline orientation', () => {
const titleElement = vm.$el.querySelector('.linked-pipelines-column-title'); const titleElement = wrapper.find('.linked-pipelines-column-title');
expect(titleElement.innerText).toContain(props.columnTitle); expect(titleElement.text()).toBe(propsData.columnTitle);
});
it('has the correct number of linked pipeline child components', () => {
expect(vm.$children.length).toBe(props.linkedPipelines.length);
}); });
it('renders the correct number of linked pipelines', () => { it('renders the correct number of linked pipelines', () => {
const linkedPipelineElements = vm.$el.querySelectorAll('.linked-pipeline'); const linkedPipelineElements = wrapper.findAll(LinkedPipeline);
expect(linkedPipelineElements.length).toBe(props.linkedPipelines.length); expect(linkedPipelineElements.length).toBe(propsData.linkedPipelines.length);
}); });
it('renders cross project triangle when column is upstream', () => { it('renders cross project triangle when column is upstream', () => {
expect(vm.$el.querySelector('.cross-project-triangle')).toBeDefined(); expect(wrapper.find('.cross-project-triangle').exists()).toBe(true);
}); });
}); });
This source diff could not be displayed because it is too large. You can view the blob instead.
import Vue from 'vue'; import { shallowMount } from '@vue/test-utils';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import stageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue'; import stageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
describe('stage column component', () => { describe('stage column component', () => {
let component;
const StageColumnComponent = Vue.extend(stageColumnComponent);
const mockJob = { const mockJob = {
id: 4250, id: 4250,
name: 'test', name: 'test',
...@@ -24,6 +21,8 @@ describe('stage column component', () => { ...@@ -24,6 +21,8 @@ describe('stage column component', () => {
}, },
}; };
let wrapper;
beforeEach(() => { beforeEach(() => {
const mockGroups = []; const mockGroups = [];
for (let i = 0; i < 3; i += 1) { for (let i = 0; i < 3; i += 1) {
...@@ -32,40 +31,51 @@ describe('stage column component', () => { ...@@ -32,40 +31,51 @@ describe('stage column component', () => {
mockGroups.push(mockedJob); mockGroups.push(mockedJob);
} }
component = mountComponent(StageColumnComponent, { wrapper = shallowMount(stageColumnComponent, {
title: 'foo', propsData: {
groups: mockGroups, title: 'foo',
hasTriggeredBy: false, groups: mockGroups,
hasTriggeredBy: false,
},
}); });
}); });
it('should render provided title', () => { it('should render provided title', () => {
expect(component.$el.querySelector('.stage-name').textContent.trim()).toEqual('foo'); expect(
wrapper
.find('.stage-name')
.text()
.trim(),
).toBe('foo');
}); });
it('should render the provided groups', () => { it('should render the provided groups', () => {
expect(component.$el.querySelectorAll('.builds-container > ul > li').length).toEqual(3); expect(wrapper.findAll('.builds-container > ul > li').length).toBe(
wrapper.props('groups').length,
);
}); });
describe('jobId', () => { describe('jobId', () => {
it('escapes job name', () => { it('escapes job name', () => {
component = mountComponent(StageColumnComponent, { wrapper = shallowMount(stageColumnComponent, {
groups: [ propsData: {
{ groups: [
id: 4259, {
name: '<img src=x onerror=alert(document.domain)>', id: 4259,
status: { name: '<img src=x onerror=alert(document.domain)>',
icon: 'status_success', status: {
label: 'success', icon: 'status_success',
tooltip: '<img src=x onerror=alert(document.domain)>', label: 'success',
tooltip: '<img src=x onerror=alert(document.domain)>',
},
}, },
}, ],
], title: 'test',
title: 'test', hasTriggeredBy: false,
hasTriggeredBy: false, },
}); });
expect(component.$el.querySelector('.builds-container li').getAttribute('id')).toEqual( expect(wrapper.find('.builds-container li').attributes('id')).toBe(
'ci-badge-&lt;img src=x onerror=alert(document.domain)&gt;', 'ci-badge-&lt;img src=x onerror=alert(document.domain)&gt;',
); );
}); });
...@@ -73,50 +83,54 @@ describe('stage column component', () => { ...@@ -73,50 +83,54 @@ describe('stage column component', () => {
describe('with action', () => { describe('with action', () => {
it('renders action button', () => { it('renders action button', () => {
component = mountComponent(StageColumnComponent, { wrapper = shallowMount(stageColumnComponent, {
groups: [ propsData: {
{ groups: [
id: 4259, {
name: '<img src=x onerror=alert(document.domain)>', id: 4259,
status: { name: '<img src=x onerror=alert(document.domain)>',
icon: 'status_success', status: {
label: 'success', icon: 'status_success',
tooltip: '<img src=x onerror=alert(document.domain)>', label: 'success',
tooltip: '<img src=x onerror=alert(document.domain)>',
},
}, },
],
title: 'test',
hasTriggeredBy: false,
action: {
icon: 'play',
title: 'Play all',
path: 'action',
}, },
],
title: 'test',
hasTriggeredBy: false,
action: {
icon: 'play',
title: 'Play all',
path: 'action',
}, },
}); });
expect(component.$el.querySelector('.js-stage-action')).not.toBeNull(); expect(wrapper.find('.js-stage-action').exists()).toBe(true);
}); });
}); });
describe('without action', () => { describe('without action', () => {
it('does not render action button', () => { it('does not render action button', () => {
component = mountComponent(StageColumnComponent, { wrapper = shallowMount(stageColumnComponent, {
groups: [ propsData: {
{ groups: [
id: 4259, {
name: '<img src=x onerror=alert(document.domain)>', id: 4259,
status: { name: '<img src=x onerror=alert(document.domain)>',
icon: 'status_success', status: {
label: 'success', icon: 'status_success',
tooltip: '<img src=x onerror=alert(document.domain)>', label: 'success',
tooltip: '<img src=x onerror=alert(document.domain)>',
},
}, },
}, ],
], title: 'test',
title: 'test', hasTriggeredBy: false,
hasTriggeredBy: false, },
}); });
expect(component.$el.querySelector('.js-stage-action')).toBeNull(); expect(wrapper.find('.js-stage-action').exists()).toBe(false);
}); });
}); });
}); });
import mockData from '../../../frontend/pipelines/graph/linked_pipelines_mock_data';
export default mockData;
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