439 lines
14 KiB
JavaScript
439 lines
14 KiB
JavaScript
import { Document, parseDocument } from 'yaml';
|
|
import { GlProgressBar } from '@gitlab/ui';
|
|
import { nextTick } from 'vue';
|
|
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
|
|
import { mockTracking } from 'helpers/tracking_helper';
|
|
import PipelineWizardWrapper, { i18n } from '~/pipeline_wizard/components/wrapper.vue';
|
|
import WizardStep from '~/pipeline_wizard/components/step.vue';
|
|
import CommitStep from '~/pipeline_wizard/components/commit.vue';
|
|
import YamlEditor from '~/pipeline_wizard/components/editor.vue';
|
|
import { sprintf } from '~/locale';
|
|
import {
|
|
steps as stepsYaml,
|
|
compiledScenario1,
|
|
compiledScenario2,
|
|
compiledScenario3,
|
|
} from '../mock/yaml';
|
|
|
|
describe('Pipeline Wizard - wrapper.vue', () => {
|
|
let wrapper;
|
|
const steps = parseDocument(stepsYaml).toJS();
|
|
|
|
const getAsYamlNode = (value) => new Document(value).contents;
|
|
const templateId = 'my-namespace/my-template';
|
|
const createComponent = (props = {}, mountFn = shallowMountExtended) => {
|
|
wrapper = mountFn(PipelineWizardWrapper, {
|
|
propsData: {
|
|
templateId,
|
|
projectPath: '/user/repo',
|
|
defaultBranch: 'main',
|
|
filename: '.gitlab-ci.yml',
|
|
steps: getAsYamlNode(steps),
|
|
...props,
|
|
},
|
|
stubs: {
|
|
CommitStep: true,
|
|
},
|
|
});
|
|
};
|
|
const getEditorContent = () => {
|
|
return wrapper.getComponent(YamlEditor).props().doc.toString();
|
|
};
|
|
const getStepWrapper = () =>
|
|
wrapper.findAllComponents(WizardStep).wrappers.find((w) => w.isVisible());
|
|
const getGlProgressBarWrapper = () => wrapper.getComponent(GlProgressBar);
|
|
const findFirstVisibleStep = () =>
|
|
wrapper.findAllComponents('[data-testid="step"]').wrappers.find((w) => w.isVisible());
|
|
const findFirstInputFieldForTarget = (target) =>
|
|
wrapper.find(`[data-input-target="${target}"]`).find('input');
|
|
|
|
describe('display', () => {
|
|
afterEach(() => {
|
|
wrapper.destroy();
|
|
});
|
|
|
|
it('shows the steps', () => {
|
|
createComponent();
|
|
|
|
expect(getStepWrapper().exists()).toBe(true);
|
|
});
|
|
|
|
it('shows the progress bar', () => {
|
|
createComponent();
|
|
|
|
const expectedMessage = sprintf(i18n.stepNofN, {
|
|
currentStep: 1,
|
|
stepCount: 3,
|
|
});
|
|
|
|
expect(wrapper.findByTestId('step-count').text()).toBe(expectedMessage);
|
|
expect(getGlProgressBarWrapper().exists()).toBe(true);
|
|
});
|
|
|
|
it('shows the editor', () => {
|
|
createComponent();
|
|
|
|
expect(wrapper.findComponent(YamlEditor).exists()).toBe(true);
|
|
});
|
|
|
|
it('shows the editor header with the default filename', () => {
|
|
createComponent();
|
|
|
|
const expectedMessage = sprintf(i18n.draft, {
|
|
filename: '.gitlab-ci.yml',
|
|
});
|
|
|
|
expect(wrapper.findByTestId('editor-header').text()).toBe(expectedMessage);
|
|
});
|
|
|
|
it('shows the editor header with a custom filename', async () => {
|
|
const filename = 'my-file.yml';
|
|
createComponent({
|
|
filename,
|
|
});
|
|
|
|
const expectedMessage = sprintf(i18n.draft, {
|
|
filename,
|
|
});
|
|
|
|
expect(wrapper.findByTestId('editor-header').text()).toBe(expectedMessage);
|
|
});
|
|
});
|
|
|
|
describe('steps', () => {
|
|
const totalSteps = steps.length + 1;
|
|
|
|
// **Note** on `expectProgressBarValue`
|
|
// Why are we expecting 50% here and not 66% or even 100%?
|
|
// The reason is mostly a UX thing.
|
|
// First, we count the commit step as an extra step, so that would
|
|
// be 66% by now (2 of 3).
|
|
// But then we add yet another one to the calc, because when we
|
|
// arrived on the second step's page, it's not *completed* (which is
|
|
// what the progress bar indicates). So in that case we're at 33%.
|
|
// Lastly, we want to start out with the progress bar not at zero,
|
|
// because UX research indicates that makes a process like this less
|
|
// intimidating, so we're always adding one step to the value bar
|
|
// (but not to the step counter. Now we're back at 50%.
|
|
describe.each`
|
|
step | navigationEventChain | expectStepNumber | expectCommitStepShown | expectStepDef | expectProgressBarValue
|
|
${'initial step'} | ${[]} | ${1} | ${false} | ${steps[0]} | ${25}
|
|
${'second step'} | ${['next']} | ${2} | ${false} | ${steps[1]} | ${50}
|
|
${'commit step'} | ${['next', 'next']} | ${3} | ${true} | ${null} | ${75}
|
|
${'stepping back'} | ${['next', 'back']} | ${1} | ${false} | ${steps[0]} | ${25}
|
|
${'clicking next>next>back'} | ${['next', 'next', 'back']} | ${2} | ${false} | ${steps[1]} | ${50}
|
|
${'clicking all the way through and back'} | ${['next', 'next', 'back', 'back']} | ${1} | ${false} | ${steps[0]} | ${25}
|
|
`(
|
|
'$step',
|
|
({
|
|
navigationEventChain,
|
|
expectStepNumber,
|
|
expectCommitStepShown,
|
|
expectStepDef,
|
|
expectProgressBarValue,
|
|
}) => {
|
|
beforeAll(async () => {
|
|
createComponent();
|
|
|
|
for (const emittedValue of navigationEventChain) {
|
|
findFirstVisibleStep().vm.$emit(emittedValue);
|
|
// We have to wait for the next step to be mounted
|
|
// before we can emit the next event, so we have to await
|
|
// inside the loop.
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await nextTick();
|
|
}
|
|
});
|
|
|
|
afterAll(() => {
|
|
wrapper.destroy();
|
|
});
|
|
|
|
if (expectCommitStepShown) {
|
|
it('does not show the step wrapper', async () => {
|
|
expect(wrapper.findComponent(WizardStep).isVisible()).toBe(false);
|
|
});
|
|
|
|
it('shows the commit step page', () => {
|
|
expect(wrapper.findComponent(CommitStep).isVisible()).toBe(true);
|
|
});
|
|
} else {
|
|
it('passes the correct step config to the step component', async () => {
|
|
expect(getStepWrapper().props('inputs')).toMatchObject(expectStepDef.inputs);
|
|
});
|
|
|
|
it('does not show the commit step page', () => {
|
|
expect(wrapper.findComponent(CommitStep).isVisible()).toBe(false);
|
|
});
|
|
}
|
|
|
|
it('updates the progress bar', () => {
|
|
expect(getGlProgressBarWrapper().attributes('value')).toBe(`${expectProgressBarValue}`);
|
|
});
|
|
|
|
it('updates the step number', () => {
|
|
const expectedMessage = sprintf(i18n.stepNofN, {
|
|
currentStep: expectStepNumber,
|
|
stepCount: totalSteps,
|
|
});
|
|
|
|
expect(wrapper.findByTestId('step-count').text()).toBe(expectedMessage);
|
|
});
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('editor overlay', () => {
|
|
beforeAll(() => {
|
|
createComponent();
|
|
});
|
|
|
|
afterAll(() => {
|
|
wrapper.destroy();
|
|
});
|
|
|
|
it('initially shows a placeholder', async () => {
|
|
const editorContent = getEditorContent();
|
|
|
|
await nextTick();
|
|
|
|
expect(editorContent).toBe('foo: $FOO\nbar: $BAR\n');
|
|
});
|
|
|
|
it('shows an overlay with help text after setup', () => {
|
|
expect(wrapper.findByTestId('placeholder-overlay').exists()).toBe(true);
|
|
expect(wrapper.findByTestId('filename').text()).toBe('.gitlab-ci.yml');
|
|
expect(wrapper.findByTestId('description').text()).toBe(i18n.overlayMessage);
|
|
});
|
|
|
|
it('does not show overlay when content has changed', async () => {
|
|
const newCompiledDoc = new Document({ faa: 'bur' });
|
|
|
|
await getStepWrapper().vm.$emit('update:compiled', newCompiledDoc);
|
|
await nextTick();
|
|
|
|
const overlay = wrapper.findByTestId('placeholder-overlay');
|
|
|
|
expect(overlay.exists()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('editor updates', () => {
|
|
beforeAll(() => {
|
|
createComponent();
|
|
});
|
|
|
|
afterAll(() => {
|
|
wrapper.destroy();
|
|
});
|
|
|
|
it('editor reflects changes', async () => {
|
|
const newCompiledDoc = new Document({ faa: 'bur' });
|
|
await getStepWrapper().vm.$emit('update:compiled', newCompiledDoc);
|
|
|
|
expect(getEditorContent()).toBe(newCompiledDoc.toString());
|
|
});
|
|
});
|
|
|
|
describe('line highlights', () => {
|
|
beforeAll(() => {
|
|
createComponent();
|
|
});
|
|
|
|
afterAll(() => {
|
|
wrapper.destroy();
|
|
});
|
|
|
|
it('highlight requests by the step get passed on to the editor', async () => {
|
|
const highlight = 'foo';
|
|
|
|
await getStepWrapper().vm.$emit('update:highlight', highlight);
|
|
|
|
expect(wrapper.getComponent(YamlEditor).props('highlight')).toBe(highlight);
|
|
});
|
|
|
|
it('removes the highlight when clicking through to the commit step', async () => {
|
|
// Simulate clicking through all steps until the last one
|
|
await Promise.all(
|
|
steps.map(async () => {
|
|
await getStepWrapper().vm.$emit('next');
|
|
await nextTick();
|
|
}),
|
|
);
|
|
|
|
expect(wrapper.getComponent(YamlEditor).props('highlight')).toBe(null);
|
|
});
|
|
});
|
|
|
|
describe('integration test', () => {
|
|
beforeAll(async () => {
|
|
createComponent({}, mountExtended);
|
|
});
|
|
|
|
it('updates the editor content after input on step 1', async () => {
|
|
findFirstInputFieldForTarget('$FOO').setValue('fooVal');
|
|
await nextTick();
|
|
|
|
expect(getEditorContent()).toBe(compiledScenario1);
|
|
});
|
|
|
|
it('updates the editor content after input on step 2', async () => {
|
|
findFirstVisibleStep().vm.$emit('next');
|
|
await nextTick();
|
|
|
|
findFirstInputFieldForTarget('$BAR').setValue('barVal');
|
|
await nextTick();
|
|
|
|
expect(getEditorContent()).toBe(compiledScenario2);
|
|
});
|
|
|
|
describe('navigating back', () => {
|
|
let inputField;
|
|
|
|
beforeAll(async () => {
|
|
findFirstVisibleStep().vm.$emit('back');
|
|
await nextTick();
|
|
|
|
inputField = findFirstInputFieldForTarget('$FOO');
|
|
});
|
|
|
|
afterAll(() => {
|
|
wrapper.destroy();
|
|
inputField = undefined;
|
|
});
|
|
|
|
it('still shows the input values from the former visit', () => {
|
|
expect(inputField.element.value).toBe('fooVal');
|
|
});
|
|
|
|
it('updates the editor content without modifying input that came from a later step', async () => {
|
|
inputField.setValue('newFooVal');
|
|
await nextTick();
|
|
|
|
expect(getEditorContent()).toBe(compiledScenario3);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when commit step done', () => {
|
|
beforeEach(() => {
|
|
createComponent();
|
|
});
|
|
|
|
afterEach(() => {
|
|
wrapper.destroy();
|
|
});
|
|
|
|
it('emits done', () => {
|
|
expect(wrapper.emitted('done')).toBeUndefined();
|
|
|
|
wrapper.findComponent(CommitStep).vm.$emit('done');
|
|
|
|
expect(wrapper.emitted('done')).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe('tracking', () => {
|
|
let trackingSpy;
|
|
const trackingCategory = `pipeline_wizard:${templateId}`;
|
|
|
|
const setUpTrackingSpy = () => {
|
|
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
|
};
|
|
|
|
it('tracks next button click event', () => {
|
|
createComponent();
|
|
setUpTrackingSpy();
|
|
findFirstVisibleStep().vm.$emit('next');
|
|
|
|
expect(trackingSpy).toHaveBeenCalledWith(trackingCategory, 'click_button', {
|
|
category: trackingCategory,
|
|
property: 'next',
|
|
label: 'pipeline_wizard_navigation',
|
|
extra: {
|
|
fromStep: 0,
|
|
toStep: 1,
|
|
},
|
|
});
|
|
});
|
|
|
|
it('tracks back button click event', () => {
|
|
createComponent();
|
|
|
|
// Navigate to step 1 without the spy set up
|
|
findFirstVisibleStep().vm.$emit('next');
|
|
|
|
// Now enable the tracking spy
|
|
setUpTrackingSpy();
|
|
|
|
findFirstVisibleStep().vm.$emit('back');
|
|
|
|
expect(trackingSpy).toHaveBeenCalledWith(trackingCategory, 'click_button', {
|
|
category: trackingCategory,
|
|
property: 'back',
|
|
label: 'pipeline_wizard_navigation',
|
|
extra: {
|
|
fromStep: 1,
|
|
toStep: 0,
|
|
},
|
|
});
|
|
});
|
|
|
|
it('tracks back button click event on the commit step', () => {
|
|
createComponent();
|
|
|
|
// Navigate to step 2 without the spy set up
|
|
findFirstVisibleStep().vm.$emit('next');
|
|
findFirstVisibleStep().vm.$emit('next');
|
|
|
|
// Now enable the tracking spy
|
|
setUpTrackingSpy();
|
|
|
|
wrapper.findComponent(CommitStep).vm.$emit('back');
|
|
|
|
expect(trackingSpy).toHaveBeenCalledWith(trackingCategory, 'click_button', {
|
|
category: trackingCategory,
|
|
property: 'back',
|
|
label: 'pipeline_wizard_navigation',
|
|
extra: {
|
|
fromStep: 2,
|
|
toStep: 1,
|
|
},
|
|
});
|
|
});
|
|
|
|
it('tracks done event on the commit step', () => {
|
|
createComponent();
|
|
|
|
// Navigate to step 2 without the spy set up
|
|
findFirstVisibleStep().vm.$emit('next');
|
|
findFirstVisibleStep().vm.$emit('next');
|
|
|
|
// Now enable the tracking spy
|
|
setUpTrackingSpy();
|
|
|
|
wrapper.findComponent(CommitStep).vm.$emit('done');
|
|
|
|
expect(trackingSpy).toHaveBeenCalledWith(trackingCategory, 'click_button', {
|
|
category: trackingCategory,
|
|
label: 'pipeline_wizard_commit',
|
|
property: 'commit',
|
|
});
|
|
});
|
|
|
|
it('tracks when editor emits touch events', () => {
|
|
createComponent();
|
|
setUpTrackingSpy();
|
|
|
|
wrapper.findComponent(YamlEditor).vm.$emit('touch');
|
|
|
|
expect(trackingSpy).toHaveBeenCalledWith(trackingCategory, 'edit', {
|
|
category: trackingCategory,
|
|
label: 'pipeline_wizard_editor_interaction',
|
|
extra: {
|
|
currentStep: 0,
|
|
},
|
|
});
|
|
});
|
|
});
|
|
});
|