diff options
Diffstat (limited to 'spec/frontend/pipeline_wizard/components/step_spec.js')
-rw-r--r-- | spec/frontend/pipeline_wizard/components/step_spec.js | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/spec/frontend/pipeline_wizard/components/step_spec.js b/spec/frontend/pipeline_wizard/components/step_spec.js new file mode 100644 index 00000000000..2289a349318 --- /dev/null +++ b/spec/frontend/pipeline_wizard/components/step_spec.js @@ -0,0 +1,227 @@ +import { parseDocument, Document } from 'yaml'; +import { omit } from 'lodash'; +import { nextTick } from 'vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import PipelineWizardStep from '~/pipeline_wizard/components/step.vue'; +import InputWrapper from '~/pipeline_wizard/components/input.vue'; +import StepNav from '~/pipeline_wizard/components/step_nav.vue'; +import { + stepInputs, + stepTemplate, + compiledYamlBeforeSetup, + compiledYamlAfterInitialLoad, + compiledYaml, +} from '../mock/yaml'; + +describe('Pipeline Wizard - Step Page', () => { + const inputs = parseDocument(stepInputs).toJS(); + let wrapper; + let input1; + let input2; + + const getInputWrappers = () => wrapper.findAllComponents(InputWrapper); + const forEachInputWrapper = (cb) => { + getInputWrappers().wrappers.forEach(cb); + }; + const getStepNav = () => { + return wrapper.findComponent(StepNav); + }; + const mockNextClick = () => { + getStepNav().vm.$emit('next'); + }; + const mockPrevClick = () => { + getStepNav().vm.$emit('back'); + }; + const expectFalsyAttributeValue = (testedWrapper, attributeName) => { + expect([false, null, undefined]).toContain(testedWrapper.attributes(attributeName)); + }; + const findInputWrappers = () => { + const inputWrappers = wrapper.findAllComponents(InputWrapper); + input1 = inputWrappers.at(0); + input2 = inputWrappers.at(1); + }; + + const createComponent = (props = {}) => { + const template = parseDocument(stepTemplate).get('template'); + const defaultProps = { + inputs, + template, + }; + wrapper = shallowMountExtended(PipelineWizardStep, { + propsData: { + ...defaultProps, + compiled: parseDocument(compiledYamlBeforeSetup), + ...props, + }, + }); + }; + + afterEach(async () => { + await wrapper.destroy(); + }); + + describe('input children', () => { + beforeEach(() => { + createComponent(); + }); + + it('mounts an inputWrapper for each input type', () => { + forEachInputWrapper((inputWrapper, i) => + expect(inputWrapper.attributes('widget')).toBe(inputs[i].widget), + ); + }); + + it('passes all unused props to the inputWrapper', () => { + const pickChildProperties = (from) => { + return omit(from, ['target', 'widget']); + }; + forEachInputWrapper((inputWrapper, i) => { + const expectedProps = pickChildProperties(inputs[i]); + Object.entries(expectedProps).forEach(([key, value]) => { + expect(inputWrapper.attributes(key.toLowerCase())).toEqual(value.toString()); + }); + }); + }); + }); + + const yamlDocument = new Document({ foo: { bar: 'baz' } }); + const yamlNode = yamlDocument.get('foo'); + + describe('prop validation', () => { + describe.each` + componentProp | required | valid | invalid + ${'inputs'} | ${true} | ${[inputs, []]} | ${[['invalid'], [null], [{}, {}]]} + ${'template'} | ${true} | ${[yamlNode]} | ${['invalid', null, { foo: 1 }, yamlDocument]} + ${'compiled'} | ${true} | ${[yamlDocument]} | ${['invalid', null, { foo: 1 }, yamlNode]} + `('testing `$componentProp` prop', ({ componentProp, required, valid, invalid }) => { + it('expects prop to be required', () => { + expect(PipelineWizardStep.props[componentProp].required).toEqual(required); + }); + + it('prop validators return false for invalid types', () => { + const validatorFunc = PipelineWizardStep.props[componentProp].validator; + invalid.forEach((invalidType) => { + expect(validatorFunc(invalidType)).toBe(false); + }); + }); + + it('prop validators return true for valid types', () => { + const validatorFunc = PipelineWizardStep.props[componentProp].validator; + valid.forEach((validType) => { + expect(validatorFunc(validType)).toBe(true); + }); + }); + }); + }); + + describe('navigation', () => { + it('shows the next button', () => { + createComponent(); + + expect(getStepNav().attributes('nextbuttonenabled')).toEqual('true'); + }); + + it('does not show a back button if hasPreviousStep is false', () => { + createComponent({ hasPreviousStep: false }); + + expectFalsyAttributeValue(getStepNav(), 'showbackbutton'); + }); + + it('shows a back button if hasPreviousStep is true', () => { + createComponent({ hasPreviousStep: true }); + + expect(getStepNav().attributes('showbackbutton')).toBe('true'); + }); + + it('lets "back" event bubble upwards', async () => { + createComponent(); + + await mockPrevClick(); + await nextTick(); + + expect(wrapper.emitted().back).toBeTruthy(); + }); + + it('lets "next" event bubble upwards', async () => { + createComponent(); + + await mockNextClick(); + await nextTick(); + + expect(wrapper.emitted().next).toBeTruthy(); + }); + }); + + describe('validation', () => { + beforeEach(() => { + createComponent({ hasNextPage: true }); + findInputWrappers(); + }); + + it('sets invalid once one input field has an invalid value', async () => { + input1.vm.$emit('update:valid', true); + input2.vm.$emit('update:valid', false); + + await mockNextClick(); + + expectFalsyAttributeValue(getStepNav(), 'nextbuttonenabled'); + }); + + it('returns to valid state once the invalid input is valid again', async () => { + input1.vm.$emit('update:valid', true); + input2.vm.$emit('update:valid', false); + + await mockNextClick(); + + expectFalsyAttributeValue(getStepNav(), 'nextbuttonenabled'); + + input2.vm.$emit('update:valid', true); + await nextTick(); + + expect(getStepNav().attributes('nextbuttonenabled')).toBe('true'); + }); + + it('passes validate state to all input wrapper children when next is clicked', async () => { + forEachInputWrapper((inputWrapper) => { + expectFalsyAttributeValue(inputWrapper, 'validate'); + }); + + await mockNextClick(); + + expect(input1.attributes('validate')).toBe('true'); + }); + + it('not emitting a valid state is considered valid', async () => { + // input1 does not emit a update:valid event + input2.vm.$emit('update:valid', true); + + await mockNextClick(); + + expect(getStepNav().attributes('nextbuttonenabled')).toBe('true'); + }); + }); + + describe('template compilation', () => { + beforeEach(() => { + createComponent(); + findInputWrappers(); + }); + + it('injects the template when an input wrapper emits a beforeUpdate:compiled event', async () => { + input1.vm.$emit('beforeUpdate:compiled'); + + expect(wrapper.vm.compiled.toString()).toBe(compiledYamlAfterInitialLoad); + }); + + it('lets the "update:compiled" event bubble upwards', async () => { + const compiled = parseDocument(compiledYaml); + + await input1.vm.$emit('update:compiled', compiled); + + const updateEvents = wrapper.emitted()['update:compiled']; + const latestUpdateEvent = updateEvents[updateEvents.length - 1]; + + expect(latestUpdateEvent[0].toString()).toBe(compiled.toString()); + }); + }); +}); |