summaryrefslogtreecommitdiff
path: root/spec/frontend/pipeline_wizard/components/widgets/list_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/pipeline_wizard/components/widgets/list_spec.js')
-rw-r--r--spec/frontend/pipeline_wizard/components/widgets/list_spec.js212
1 files changed, 212 insertions, 0 deletions
diff --git a/spec/frontend/pipeline_wizard/components/widgets/list_spec.js b/spec/frontend/pipeline_wizard/components/widgets/list_spec.js
new file mode 100644
index 00000000000..796356634bc
--- /dev/null
+++ b/spec/frontend/pipeline_wizard/components/widgets/list_spec.js
@@ -0,0 +1,212 @@
+import { GlFormGroup, GlFormInputGroup } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import ListWidget from '~/pipeline_wizard/components/widgets/list.vue';
+import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
+
+describe('Pipeline Wizard - List Widget', () => {
+ const defaultProps = {
+ label: 'This label',
+ description: 'some description',
+ placeholder: 'some placeholder',
+ pattern: '^[a-z]+$',
+ invalidFeedback: 'some feedback',
+ };
+ let wrapper;
+ let addStepBtn;
+
+ const findGlFormGroup = () => wrapper.findComponent(GlFormGroup);
+ const findGlFormGroupInvalidFeedback = () => findGlFormGroup().find('.invalid-feedback').text();
+ const findFirstGlFormInputGroup = () => wrapper.findComponent(GlFormInputGroup);
+ const findAllGlFormInputGroups = () => wrapper.findAllComponents(GlFormInputGroup);
+ const findGlFormInputGroupByIndex = (index) => findAllGlFormInputGroups().at(index);
+ const setValueOnInputField = (value, atIndex = 0) => {
+ return findGlFormInputGroupByIndex(atIndex).vm.$emit('input', value);
+ };
+ const findAddStepButton = () => wrapper.findByTestId('add-step-button');
+ const addStep = () => findAddStepButton().vm.$emit('click');
+
+ const createComponent = (props = {}, mountFn = shallowMountExtended) => {
+ wrapper = mountFn(ListWidget, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ addStepBtn = findAddStepButton();
+ };
+
+ describe('component setup and interface', () => {
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('prints the label inside the legend', () => {
+ createComponent();
+
+ expect(findGlFormGroup().attributes('label')).toBe(defaultProps.label);
+ });
+
+ it('prints the description inside the legend', () => {
+ createComponent();
+
+ expect(findGlFormGroup().attributes('labeldescription')).toBe(defaultProps.description);
+ });
+
+ it('sets the input field type attribute to "text"', async () => {
+ createComponent();
+
+ expect(findFirstGlFormInputGroup().attributes('type')).toBe('text');
+ });
+
+ it('passes the placeholder to the first input field', () => {
+ createComponent();
+
+ expect(findFirstGlFormInputGroup().attributes('placeholder')).toBe(defaultProps.placeholder);
+ });
+
+ it('shows a delete button on all fields if there are more than one', async () => {
+ createComponent({}, mountExtended);
+
+ await addStep();
+ await addStep();
+ const inputGroups = findAllGlFormInputGroups().wrappers;
+
+ expect(inputGroups.length).toBe(3);
+ inputGroups.forEach((inputGroup) => {
+ const button = inputGroup.find('[data-testid="remove-step-button"]');
+ expect(button.find('[data-testid="remove-icon"]').exists()).toBe(true);
+ expect(button.attributes('aria-label')).toBe('remove step');
+ });
+ });
+
+ it('null values do not cause an input event', async () => {
+ createComponent();
+
+ await addStep();
+
+ expect(wrapper.emitted('input')).toBe(undefined);
+ });
+
+ it('hides the delete button if there is only one', () => {
+ createComponent({}, mountExtended);
+
+ const inputGroups = findAllGlFormInputGroups().wrappers;
+
+ expect(inputGroups.length).toBe(1);
+ expect(wrapper.findByTestId('remove-step-button').exists()).toBe(false);
+ });
+
+ it('shows an "add step" button', () => {
+ createComponent();
+
+ expect(addStepBtn.attributes('icon')).toBe('plus');
+ expect(addStepBtn.text()).toBe('add another step');
+ });
+
+ it('the "add step" button increases the number of input fields', async () => {
+ createComponent();
+
+ expect(findAllGlFormInputGroups().wrappers.length).toBe(1);
+ await addStep();
+ expect(findAllGlFormInputGroups().wrappers.length).toBe(2);
+ });
+
+ it('does not pass the placeholder on subsequent input fields', async () => {
+ createComponent();
+
+ await addStep();
+ await addStep();
+ const nullOrUndefined = [null, undefined];
+ expect(nullOrUndefined).toContain(findAllGlFormInputGroups().at(1).attributes('placeholder'));
+ expect(nullOrUndefined).toContain(findAllGlFormInputGroups().at(2).attributes('placeholder'));
+ });
+
+ it('emits an update event on input', async () => {
+ createComponent();
+
+ const localValue = 'somevalue';
+ await setValueOnInputField(localValue);
+ await nextTick();
+
+ expect(wrapper.emitted('input')).toEqual([[[localValue]]]);
+ });
+
+ it('only emits non-null values', async () => {
+ createComponent();
+
+ await addStep();
+ await addStep();
+ await setValueOnInputField('abc', 1);
+ await nextTick();
+
+ const events = wrapper.emitted('input');
+
+ expect(events.length).toBe(1);
+ expect(events[0]).toEqual([['abc']]);
+ });
+ });
+
+ describe('form validation', () => {
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('does not show validation state when untouched', async () => {
+ createComponent({}, mountExtended);
+ expect(findGlFormGroup().classes()).not.toContain('is-valid');
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+ });
+
+ it('shows invalid state on blur', async () => {
+ createComponent({}, mountExtended);
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+ const input = findFirstGlFormInputGroup().find('input');
+ await input.setValue('invalid99');
+ await input.trigger('blur');
+ expect(input.classes()).toContain('is-invalid');
+ expect(findGlFormGroup().classes()).toContain('is-invalid');
+ });
+
+ it('shows invalid state when toggling `validate` prop', async () => {
+ createComponent({ required: true, validate: false }, mountExtended);
+ await setValueOnInputField(null);
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+ await wrapper.setProps({ validate: true });
+ expect(findGlFormGroup().classes()).toContain('is-invalid');
+ });
+
+ it.each`
+ scenario | required | values | inputFieldClasses | inputGroupClass | feedback
+ ${'shows invalid if all inputs are empty'} | ${true} | ${[null, null]} | ${['is-invalid', null]} | ${'is-invalid'} | ${'At least one entry is required'}
+ ${'is valid if at least one field has a valid entry'} | ${true} | ${[null, 'abc']} | ${[null, 'is-valid']} | ${'is-valid'} | ${expect.anything()}
+ ${'is invalid if one field has an invalid entry'} | ${true} | ${['abc', '99']} | ${['is-valid', 'is-invalid']} | ${'is-invalid'} | ${defaultProps.invalidFeedback}
+ ${'is not invalid if its not required but all values are null'} | ${false} | ${[null, null]} | ${[null, null]} | ${'is-valid'} | ${expect.anything()}
+ ${'is invalid if pattern does not match even if its not required'} | ${false} | ${['99', null]} | ${['is-invalid', null]} | ${'is-invalid'} | ${defaultProps.invalidFeedback}
+ `('$scenario', async ({ required, values, inputFieldClasses, inputGroupClass, feedback }) => {
+ createComponent({ required, validate: true }, mountExtended);
+
+ await Promise.all(
+ values.map(async (value, i) => {
+ if (i > 0) {
+ await addStep();
+ }
+ await setValueOnInputField(value, i);
+ }),
+ );
+ await nextTick();
+
+ inputFieldClasses.forEach((expected, i) => {
+ const inputWrapper = findGlFormInputGroupByIndex(i).find('input');
+ if (expected === null) {
+ expect(inputWrapper.classes()).not.toContain('is-valid');
+ expect(inputWrapper.classes()).not.toContain('is-invalid');
+ } else {
+ expect(inputWrapper.classes()).toContain(expected);
+ }
+ });
+
+ expect(findGlFormGroup().classes()).toContain(inputGroupClass);
+ expect(findGlFormGroupInvalidFeedback()).toEqual(feedback);
+ });
+ });
+});