Commit f2c10787 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '267536-vsa-move-custom-value-stream-fields-to-component' into 'master'

Refactor custom stage form specs

See merge request gitlab-org/gitlab!49651
parents 0e949d15 3e5bccea
...@@ -138,7 +138,7 @@ export default { ...@@ -138,7 +138,7 @@ export default {
/> />
</gl-form-group> </gl-form-group>
</div> </div>
<div v-if="endEventRequiresLabel" class="w-50 ml-1"> <div v-if="endEventRequiresLabel" class="gl-w-half gl-ml-2">
<gl-form-group <gl-form-group
data-testid="custom-stage-end-event-label" data-testid="custom-stage-end-event-label"
:label="$options.I18N.FORM_FIELD_END_EVENT_LABEL" :label="$options.I18N.FORM_FIELD_END_EVENT_LABEL"
......
...@@ -172,7 +172,7 @@ export default { ...@@ -172,7 +172,7 @@ export default {
<div v-if="isLoading"> <div v-if="isLoading">
<gl-loading-icon class="mt-4" size="md" /> <gl-loading-icon class="mt-4" size="md" />
</div> </div>
<form v-else class="custom-stage-form m-4 mt-0"> <form v-else class="custom-stage-form m-4 gl-mt-0">
<div class="gl-mb-1 gl-display-flex gl-justify-content-space-between gl-align-items-center"> <div class="gl-mb-1 gl-display-flex gl-justify-content-space-between gl-align-items-center">
<h4>{{ formTitle }}</h4> <h4>{{ formTitle }}</h4>
<gl-dropdown <gl-dropdown
...@@ -191,7 +191,7 @@ export default { ...@@ -191,7 +191,7 @@ export default {
>{{ stage.title }}</gl-dropdown-item >{{ stage.title }}</gl-dropdown-item
> >
</template> </template>
<p v-else class="mx-3 my-2">{{ $options.I18N.RECOVER_STAGES_VISIBLE }}</p> <p v-else class="gl-mx-5 gl-my-3">{{ $options.I18N.RECOVER_STAGES_VISIBLE }}</p>
</gl-dropdown> </gl-dropdown>
</div> </div>
<custom-stage-form-fields <custom-stage-form-fields
...@@ -201,7 +201,7 @@ export default { ...@@ -201,7 +201,7 @@ export default {
:events="events" :events="events"
@update="handleUpdateFields" @update="handleUpdateFields"
/> />
<div class="custom-stage-form-actions"> <div>
<gl-button <gl-button
:disabled="!isDirty" :disabled="!isDirty"
category="primary" category="primary"
...@@ -221,7 +221,7 @@ export default { ...@@ -221,7 +221,7 @@ export default {
{{ saveStageText }} {{ saveStageText }}
</gl-button> </gl-button>
</div> </div>
<div class="mt-2"> <div class="gl-mt-3">
<gl-sprintf <gl-sprintf
:message=" :message="
__( __(
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CustomStageForm Editing a custom stage isSavingCustomStage=true displays a loading icon 1`] = ` exports[`CustomStageForm Editing a custom stage isSavingCustomStage=true displays a loading icon 1`] = `
"<button data-testid=\\"save-custom-stage\\" type=\\"button\\" disabled=\\"disabled\\" class=\\"btn btn-success btn-md disabled gl-button\\"> "<gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\" data-testid=\\"save-custom-stage\\">
<!----> <gl-loading-icon-stub label=\\"Loading\\" size=\\"sm\\" color=\\"dark\\" inline=\\"true\\"></gl-loading-icon-stub>
<!----> <span class=\\"gl-button-text\\"><span class=\\"gl-spinner-container\\"><span aria-label=\\"Loading\\" class=\\"align-text-bottom gl-spinner gl-spinner-dark gl-spinner-sm\\"></span></span>
Update stage Update stage
</span></button>" </gl-button-stub>"
`; `;
exports[`CustomStageForm isSavingCustomStage=true displays a loading icon 1`] = ` exports[`CustomStageForm isSavingCustomStage=true displays a loading icon 1`] = `
"<button data-testid=\\"save-custom-stage\\" type=\\"button\\" disabled=\\"disabled\\" class=\\"btn btn-success btn-md disabled gl-button\\"> "<gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\" data-testid=\\"save-custom-stage\\">
<!----> <gl-loading-icon-stub label=\\"Loading\\" size=\\"sm\\" color=\\"dark\\" inline=\\"true\\"></gl-loading-icon-stub>
<!----> <span class=\\"gl-button-text\\"><span class=\\"gl-spinner-container\\"><span aria-label=\\"Loading\\" class=\\"align-text-bottom gl-spinner gl-spinner-dark gl-spinner-sm\\"></span></span>
Add stage Add stage
</span></button>" </gl-button-stub>"
`; `;
...@@ -91,7 +91,7 @@ describe('CustomStageFields', () => { ...@@ -91,7 +91,7 @@ describe('CustomStageFields', () => {
describe.each([ describe.each([
['Start event label', findStartEventLabel], ['Start event label', findStartEventLabel],
['End event label', findStartEventLabel], ['End event label', findEndEventLabel],
])('Default state', (field, finder) => { ])('Default state', (field, finder) => {
it(`field '${field}' is hidden by default`, () => { it(`field '${field}' is hidden by default`, () => {
expect(finder(wrapper).exists()).toBe(false); expect(finder(wrapper).exists()).toBe(false);
...@@ -172,7 +172,7 @@ describe('CustomStageFields', () => { ...@@ -172,7 +172,7 @@ describe('CustomStageFields', () => {
}); });
}); });
it('will display the start event label field if a label event is selected', () => { it('will display the end event label field if a label event is selected', () => {
expect(findEndEventLabel(wrapper).exists()).toEqual(true); expect(findEndEventLabel(wrapper).exists()).toEqual(true);
}); });
......
import { createLocalVue, mount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlSprintf, GlDropdownItem } from '@gitlab/ui';
import axios from 'axios'; import axios from 'axios';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex'; import Vuex from 'vuex';
import CustomStageForm from 'ee/analytics/cycle_analytics/components/custom_stage_form.vue'; import CustomStageForm from 'ee/analytics/cycle_analytics/components/custom_stage_form.vue';
import { initializeFormData } from 'ee/analytics/cycle_analytics/components/create_value_stream_form/utils'; import CustomStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/custom_stage_fields.vue';
import { STAGE_ACTIONS } from 'ee/analytics/cycle_analytics/constants'; import { STAGE_ACTIONS } from 'ee/analytics/cycle_analytics/constants';
import customStagesStore from 'ee/analytics/cycle_analytics/store/modules/custom_stages'; import customStagesStore from 'ee/analytics/cycle_analytics/store/modules/custom_stages';
import waitForPromises from 'helpers/wait_for_promises'; import { convertObjectPropsToSnakeCase } from '~/lib/utils/common_utils';
import {
emptyState,
formInitialData,
minimumFields,
MERGE_REQUEST_CREATED,
MERGE_REQUEST_CLOSED,
ISSUE_CREATED,
ISSUE_CLOSED,
} from './create_value_stream_form/mock_data';
import { import {
endpoints, endpoints,
groupLabels, groupLabels,
customStageEvents as events, customStageEvents as events,
labelStartEvent,
labelStopEvent,
customStageStartEvents as startEvents,
customStageStopEvents as stopEvents,
customStageFormErrors, customStageFormErrors,
} from '../mock_data'; } from '../mock_data';
const formInitialData = {
id: 74,
name: 'Cool stage pre',
startEventIdentifier: labelStartEvent.identifier,
startEventLabelId: groupLabels[0].id,
endEventIdentifier: labelStopEvent.identifier,
endEventLabelId: groupLabels[1].id,
};
const MERGE_REQUEST_CREATED = 'merge_request_created';
const MERGE_REQUEST_CLOSED = 'merge_request_closed';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
...@@ -56,9 +50,10 @@ describe('CustomStageForm', () => { ...@@ -56,9 +50,10 @@ describe('CustomStageForm', () => {
initialState = {}, initialState = {},
initialRootGetters = {}, initialRootGetters = {},
stubs = {}, stubs = {},
props = {}, props = {},
} = {}) { } = {}) {
return mount(CustomStageForm, { return shallowMount(CustomStageForm, {
localVue, localVue,
store: fakeStore({ initialState, initialRootGetters }), store: fakeStore({ initialState, initialRootGetters }),
propsData: { propsData: {
...@@ -66,7 +61,8 @@ describe('CustomStageForm', () => { ...@@ -66,7 +61,8 @@ describe('CustomStageForm', () => {
...props, ...props,
}, },
stubs: { stubs: {
'labels-selector': false, GlSprintf,
CustomStageFields,
...stubs, ...stubs,
}, },
}); });
...@@ -77,400 +73,112 @@ describe('CustomStageForm', () => { ...@@ -77,400 +73,112 @@ describe('CustomStageForm', () => {
const findEvent = ev => wrapper.emitted()[ev]; const findEvent = ev => wrapper.emitted()[ev];
const sel = { const findSubmitButton = () => wrapper.find('[data-testid="save-custom-stage"]');
name: '[data-testid="custom-stage-name"] input', const findCancelButton = () => wrapper.find('[data-testid="cancel-custom-stage"]');
startEvent: '[data-testid="custom-stage-start-event"] select', const findRecoverStageDropdown = () =>
startEventLabel: '[data-testid="custom-stage-start-event-label"]', wrapper.find('[data-testid="recover-hidden-stage-dropdown"]');
endEvent: '[data-testid="custom-stage-end-event"] select',
endEventLabel: '[data-testid="custom-stage-end-event-label"]',
submit: '[data-testid="save-custom-stage"]',
cancel: '[data-testid="cancel-custom-stage"]',
invalidFeedback: '.invalid-feedback',
recoverStageDropdown: '[data-testid="recover-hidden-stage-dropdown"]',
recoverStageDropdownTrigger: '[data-testid="recover-hidden-stage-dropdown"] .dropdown-toggle',
hiddenStageDropdownOption: '[data-testid="recover-hidden-stage-dropdown"] .dropdown-item',
};
function getDropdownOptions(_wrapper, dropdown) {
return _wrapper.find(dropdown).findAll('option');
}
function getDropdownOptionsArray(_wrapper, dropdown) {
return _wrapper
.find(dropdown)
.findAll('option')
.wrappers.map(w => w.attributes('value'));
}
function getDropdownOption(_wrapper, dropdown, index) { const findFieldErrors = field => wrapper.vm.errors[field];
return getDropdownOptions(_wrapper, dropdown).at(index);
}
function selectDropdownOption(_wrapper, dropdown, index) { const setFields = async (fields = minimumFields) => {
getDropdownOption(_wrapper, dropdown, index).setSelected(); Object.entries(fields).forEach(([field, value]) => {
} wrapper.find(CustomStageFields).vm.$emit('update', field, value);
// Valid start and end event pair: merge request created - merge request closed
const mergeRequestCreatedIndex = startEvents.findIndex(
e => e.identifier === MERGE_REQUEST_CREATED,
);
const mergeRequestCreatedDropdownIndex = mergeRequestCreatedIndex;
const mergeReqestCreatedEvent = startEvents[mergeRequestCreatedIndex];
const mergeRequestClosedDropdownIndex = mergeReqestCreatedEvent.allowedEndEvents.findIndex(
e => e === MERGE_REQUEST_CLOSED,
);
function setEventDropdowns({
startEventDropdownIndex = mergeRequestCreatedDropdownIndex,
stopEventDropdownIndex = mergeRequestClosedDropdownIndex,
} = {}) {
selectDropdownOption(wrapper, sel.startEvent, startEventDropdownIndex);
return wrapper.vm.$nextTick().then(() => {
selectDropdownOption(wrapper, sel.endEvent, stopEventDropdownIndex);
}); });
} await wrapper.vm.$nextTick();
};
const findNameField = _wrapper => _wrapper.find('[data-testid="custom-stage-name"]'); const setNameField = (value = '') => setFields({ name: value });
const findStartEventField = _wrapper => _wrapper.find('[data-testid="custom-stage-start-event"]');
function setNameField(_wrapper, value = '') { const setStartEvent = (value = MERGE_REQUEST_CREATED) =>
wrapper.find(sel.name).setValue(value); setFields({ startEventIdentifier: value });
wrapper.find(sel.name).trigger('change'); const setEndEvent = (value = MERGE_REQUEST_CLOSED) => setFields({ endEventIdentifier: value });
return _wrapper.vm.$nextTick();
}
const mockGroupLabelsRequest = () => const mockGroupLabelsRequest = () =>
new MockAdapter(axios).onGet(endpoints.groupLabels).reply(200, groupLabels); new MockAdapter(axios).onGet(endpoints.groupLabels).reply(200, groupLabels);
beforeEach(() => { beforeEach(async () => {
mock = mockGroupLabelsRequest(); mock = mockGroupLabelsRequest();
wrapper = createComponent(); wrapper = createComponent();
}); });
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
mock.restore(); mock.restore();
}); });
describe.each([ describe('Default state', () => {
['Name', sel.name, true], it('will set all fields to null', () => {
['Start event', sel.startEvent, true], expect(wrapper.vm.fields).toMatchObject(emptyState);
['End event', sel.endEvent, false],
['Submit', sel.submit, false],
['Cancel', sel.cancel, false],
])('Default state', (field, $sel, enabledState) => {
const state = enabledState ? 'enabled' : 'disabled';
it(`field '${field}' is ${state}`, () => {
const el = wrapper.find($sel);
expect(el.exists()).toEqual(true);
if (!enabledState) {
expect(el.attributes('disabled')).toEqual('disabled');
} else {
expect(el.attributes('disabled')).toBeUndefined();
}
}); });
});
describe('Helper text', () => {
it('displays the manual ordering helper text', () => { it('displays the manual ordering helper text', () => {
expect(wrapper.text()).toContain( expect(wrapper.html()).toContain(
'Note: Once a custom stage has been added you can re-order stages by dragging them into the desired position.', '<strong>Note:</strong> Once a custom stage has been added you can re-order stages by dragging them into the desired position.',
); );
}); });
}); });
describe('Name', () => { describe('Name', () => {
describe('with a reserved name', () => { describe('with a reserved name', () => {
beforeEach(() => { beforeEach(async () => {
wrapper = createComponent(); wrapper = createComponent();
return setNameField(wrapper, 'issue'); await setNameField('issue');
}); });
it('displays an error', () => { it('displays an error', () => {
expect(findNameField(wrapper).text()).toContain('Stage name already exists'); expect(findFieldErrors('name')).toContain('Stage name already exists');
});
it('clears the error when the field changes', () => {
return setNameField(wrapper, 'not an issue').then(() => {
expect(findNameField(wrapper).text()).not.toContain('Stage name already exists');
});
});
});
});
describe('Start event', () => {
describe('with events', () => {
beforeEach(() => {
wrapper = createComponent();
}); });
afterEach(() => { it('clears the error when the field changes', async () => {
wrapper.destroy(); await setNameField('not an issue');
});
it('selects events with canBeStartEvent=true for the start events dropdown', () => {
const select = wrapper.find(sel.startEvent);
events expect(findFieldErrors('name')).not.toContain('Stage name already exists');
.filter(ev => ev.canBeStartEvent)
.forEach(ev => {
expect(select.html()).toHaveHtml(
`<option value="${ev.identifier}">${ev.name}</option>`,
);
});
});
it('does not select events with canBeStartEvent=false for the start events dropdown', () => {
const select = wrapper.find(sel.startEvent);
events
.filter(ev => !ev.canBeStartEvent)
.forEach(ev => {
expect(select.html()).not.toHaveHtml(
`<option value="${ev.identifier}">${ev.name}</option>`,
);
});
});
});
describe('start event label', () => {
beforeEach(() => {
mock = mockGroupLabelsRequest();
wrapper = createComponent();
return wrapper.vm.$nextTick();
});
afterEach(() => {
wrapper.destroy();
});
it('is hidden by default', () => {
expect(wrapper.find(sel.startEventLabel).exists()).toEqual(false);
});
it('will display the start event label field if a label event is selected', () => {
wrapper.setData({
fields: {
startEventIdentifier: labelStartEvent.identifier,
},
});
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(sel.startEventLabel).exists()).toEqual(true);
});
});
it('will set the "startEventLabelId" field when selected', () => {
const selectedLabelId = groupLabels[0].id;
expect(wrapper.vm.fields.startEventLabelId).toEqual(null);
wrapper.find(sel.startEvent).setValue(labelStartEvent.identifier);
return waitForPromises()
.then(() => {
wrapper
.find(sel.startEventLabel)
.findAll('.dropdown-item')
.at(0)
.trigger('click');
return wrapper.vm.$nextTick();
})
.then(() => {
expect(wrapper.vm.fields.startEventLabelId).toEqual(selectedLabelId);
});
}); });
}); });
}); });
describe('End event', () => { describe('End event', () => {
const startEventArrayIndex = mergeRequestCreatedIndex;
const startEventDropdownIndex = startEventArrayIndex + 1;
const currAllowed = startEvents[startEventArrayIndex].allowedEndEvents;
beforeEach(() => { beforeEach(() => {
wrapper = createComponent(); wrapper = createComponent();
}); });
it('notifies that a start event needs to be selected first', () => { it('sets an error if no start event is selected', () => {
return wrapper.vm.$nextTick().then(() => { expect(findFieldErrors('endEventIdentifier')).toContain('Please select a start event first');
expect(wrapper.text()).toContain('Please select a start event first');
});
});
it('clears notification when a start event is selected', () => {
selectDropdownOption(wrapper, sel.startEvent, startEventDropdownIndex);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.text()).not.toContain('Please select a start event first');
});
});
it('is enabled when a start event is selected', () => {
const el = wrapper.find(sel.endEvent);
expect(el.attributes('disabled')).toEqual('disabled');
selectDropdownOption(wrapper, sel.startEvent, startEventDropdownIndex);
return wrapper.vm.$nextTick().then(() => {
expect(el.attributes('disabled')).toBeUndefined();
});
});
it('will update the list of end events when a start event is changed', () => {
let stopOptions = wrapper.find(sel.endEvent).findAll('option');
const selectedStartEvent = startEvents[startEventDropdownIndex];
expect(stopOptions).toHaveLength(1);
selectDropdownOption(wrapper, sel.startEvent, startEventDropdownIndex);
return wrapper.vm.$nextTick().then(() => {
stopOptions = wrapper.find(sel.endEvent);
selectedStartEvent.allowedEndEvents.forEach(identifier => {
expect(stopOptions.html()).toContain(identifier);
});
});
}); });
it('will display all the valid end events', () => { it('clears error when a start event is selected', async () => {
let stopOptions = wrapper.find(sel.endEvent).findAll('option'); await setStartEvent();
const possibleEndEvents = stopEvents.filter(ev => currAllowed.includes(ev.identifier)); expect(findFieldErrors('endEventIdentifier')).not.toContain(
'Please select a start event first',
expect(stopOptions.at(0).html()).toEqual('<option value="">Select end event</option>'); );
selectDropdownOption(wrapper, sel.startEvent, startEventDropdownIndex);
return wrapper.vm.$nextTick().then(() => {
stopOptions = wrapper.find(sel.endEvent);
possibleEndEvents.forEach(({ name, identifier }) => {
expect(stopOptions.html()).toContain(`<option value="${identifier}">${name}</option>`);
});
});
});
it('will not display end events that are not in the list of allowed end events', () => {
let stopOptions = wrapper.find(sel.endEvent).findAll('option');
const excludedEndEvents = stopEvents.filter(ev => !currAllowed.includes(ev.identifier));
expect(stopOptions.at(0).html()).toEqual('<option value="">Select end event</option>');
selectDropdownOption(wrapper, sel.startEvent, startEventArrayIndex + 1);
return wrapper.vm.$nextTick().then(() => {
stopOptions = wrapper.find(sel.endEvent);
excludedEndEvents.forEach(({ name, identifier }) => {
expect(wrapper.find(sel.endEvent).html()).not.toHaveHtml(
`<option value="${identifier}">${name}</option>`,
);
});
});
}); });
describe('with a end event selected and a change to the start event', () => { describe('with a end event selected and a change to the start event', () => {
beforeEach(() => { beforeEach(async () => {
wrapper = createComponent(); wrapper = createComponent();
await setFields(minimumFields);
wrapper.setData({
fields: {
name: 'Cool stage',
startEventIdentifier: MERGE_REQUEST_CREATED,
startEventLabelId: null,
endEventIdentifier: MERGE_REQUEST_CLOSED,
endEventLabelId: null,
},
});
});
afterEach(() => {
wrapper.destroy();
});
it('notifies that a start event needs to be selected first', () => {
wrapper.setData({ fields: { startEventIdentifier: '' } });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.text()).toContain('Please select a start event first');
});
}); });
it('will notify if the current start and end event pair is not valid', () => { it('warns that the start event changed', async () => {
selectDropdownOption(wrapper, sel.startEvent, 2); await setStartEvent('');
expect(findFieldErrors('endEventIdentifier')).toContain(
return wrapper.vm.$nextTick().then(() => { 'Please select a start event first',
expect(wrapper.find(sel.invalidFeedback).exists()).toEqual(true); );
expect(wrapper.find(sel.invalidFeedback).text()).toContain(
'Start event changed, please select a valid end event',
);
});
});
it('will update the list of end events', () => {
const preEndEvents = getDropdownOptionsArray(wrapper, sel.endEvent);
selectDropdownOption(wrapper, sel.startEvent, 2);
return wrapper.vm.$nextTick().then(() => {
const opts = getDropdownOptionsArray(wrapper, sel.endEvent);
expect(preEndEvents).not.toEqual(opts);
});
});
it('will disable the submit button until a valid endEvent is selected', () => {
selectDropdownOption(wrapper, sel.startEvent, 2);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(sel.submit).attributes('disabled')).toEqual('disabled');
});
});
});
describe('End event label', () => {
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('is hidden by default', () => {
expect(wrapper.find(sel.startEventLabel).exists()).toEqual(false);
}); });
it('will display the end event label field if a label event is selected', () => { it('warns if the current start and end event pair is not valid', async () => {
expect(wrapper.find(sel.endEventLabel).exists()).toEqual(false); await setFields({ startEventIdentifier: 'fake_event_id' });
wrapper.setData({ expect(findFieldErrors('endEventIdentifier')).toContain(
fields: { 'Start event changed, please select a valid end event',
endEventIdentifier: labelStopEvent.identifier, );
startEventIdentifier: labelStartEvent.identifier,
},
});
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(sel.endEventLabel).exists()).toEqual(true);
});
}); });
it('will set the "endEventLabelId" field when selected', () => { it('will disable the submit button until a valid endEvent is selected', async () => {
const selectedLabelId = groupLabels[1].id; expect(findSubmitButton().props('disabled')).toBe(false);
expect(wrapper.vm.fields.endEventLabelId).toEqual(null); await setEndEvent('');
expect(findSubmitButton().props('disabled')).toBe(true);
wrapper.setData({
fields: {
startEventIdentifier: labelStartEvent.identifier,
endEventIdentifier: labelStopEvent.identifier,
},
});
return waitForPromises()
.then(() => {
wrapper
.find(sel.endEventLabel)
.findAll('.dropdown-item')
.at(1)
.trigger('click');
return wrapper.vm.$nextTick();
})
.then(() => {
expect(wrapper.vm.fields.endEventLabelId).toEqual(selectedLabelId);
});
}); });
}); });
}); });
...@@ -480,80 +188,43 @@ describe('CustomStageForm', () => { ...@@ -480,80 +188,43 @@ describe('CustomStageForm', () => {
wrapper = createComponent(); wrapper = createComponent();
}); });
afterEach(() => {
wrapper.destroy();
});
it('has text `Add stage`', () => { it('has text `Add stage`', () => {
expect(wrapper.find(sel.submit).text()).toEqual('Add stage'); expect(findSubmitButton().text()).toEqual('Add stage');
});
it('is enabled when all required fields are filled', () => {
const btn = wrapper.find(sel.submit);
expect(btn.attributes('disabled')).toEqual('disabled');
wrapper.find(sel.name).setValue('Cool stage');
return setEventDropdowns().then(() => {
expect(btn.attributes('disabled')).toBeUndefined();
});
}); });
describe('with all fields set', () => { describe('with all fields set', () => {
const startEventDropdownIndex = 2; beforeEach(async () => {
const startEventArrayIndex = startEventDropdownIndex - 1;
const stopEventDropdownIndex = 1;
beforeEach(() => {
wrapper = createComponent(); wrapper = createComponent();
wrapper.find(sel.name).setValue('Cool stage'); await setFields();
return wrapper.vm.$nextTick().then(() =>
setEventDropdowns({
startEventDropdownIndex,
stopEventDropdownIndex,
}),
);
}); });
afterEach(() => { it('is enabled', () => {
wrapper.destroy(); expect(findSubmitButton().props('disabled')).toBe(false);
}); });
it(`emits a ${STAGE_ACTIONS.CREATE} event when clicked`, () => { it('does not emit an event until the button is clicked', () => {
let event = findEvent(STAGE_ACTIONS.CREATE); expect(findEvent(STAGE_ACTIONS.CREATE)).toBeUndefined();
expect(event).toBeUndefined(); });
wrapper.find(sel.submit).trigger('click'); it(`emits a ${STAGE_ACTIONS.CREATE} event when clicked`, async () => {
findSubmitButton().vm.$emit('click');
await wrapper.vm.$nextTick();
return wrapper.vm.$nextTick().then(() => { expect(findEvent(STAGE_ACTIONS.CREATE)).toHaveLength(1);
event = findEvent(STAGE_ACTIONS.CREATE);
expect(event).toBeTruthy();
expect(event).toHaveLength(1);
});
}); });
it(`${STAGE_ACTIONS.CREATE} event receives the latest data`, () => { it(`${STAGE_ACTIONS.CREATE} event receives the latest data`, async () => {
const startEv = startEvents[startEventArrayIndex]; const newData = {
const selectedStopEvent = getDropdownOption(wrapper, sel.endEvent, stopEventDropdownIndex); name: 'Cool stage',
let event = findEvent(STAGE_ACTIONS.CREATE); start_event_identifier: ISSUE_CREATED,
expect(event).toBeUndefined(); end_event_identifier: ISSUE_CLOSED,
};
const res = [ setFields(newData);
{
id: null,
name: 'Cool stage',
start_event_identifier: startEv.identifier,
start_event_label_id: null,
end_event_identifier: selectedStopEvent.attributes('value'),
end_event_label_id: null,
},
];
wrapper.find(sel.submit).trigger('click'); findSubmitButton().vm.$emit('click');
return wrapper.vm.$nextTick().then(() => { await wrapper.vm.$nextTick();
event = findEvent(STAGE_ACTIONS.CREATE);
expect(event[0]).toEqual(res); expect(findEvent(STAGE_ACTIONS.CREATE)[0][0]).toMatchObject(newData);
});
}); });
}); });
}); });
...@@ -563,283 +234,168 @@ describe('CustomStageForm', () => { ...@@ -563,283 +234,168 @@ describe('CustomStageForm', () => {
wrapper = createComponent(); wrapper = createComponent();
}); });
afterEach(() => { it('is disabled by default', async () => {
wrapper.destroy(); expect(findCancelButton().props('disabled')).toBe(true);
}); });
it('is enabled when the form is dirty', () => { it('is enabled when the form is dirty', async () => {
const btn = wrapper.find(sel.cancel); await setNameField('Cool stage');
expect(findCancelButton().props('disabled')).toBe(false);
});
expect(btn.attributes('disabled')).toEqual('disabled'); it('will reset the fields when clicked', async () => {
wrapper.find(sel.name).setValue('Cool stage'); await setFields();
return wrapper.vm.$nextTick().then(() => { findCancelButton().vm.$emit('click');
expect(btn.attributes('disabled')).toBeUndefined(); await wrapper.vm.$nextTick();
});
});
it('will reset the fields when clicked', () => { expect(wrapper.vm.fields).toMatchObject({
wrapper.setData({ name: null,
fields: { startEventIdentifier: null,
name: 'Cool stage pre', startEventLabelId: null,
startEventIdentifier: labelStartEvent.identifier, endEventIdentifier: null,
endEventIdentifier: labelStopEvent.identifier, endEventLabelId: null,
},
}); });
});
return wrapper.vm it('does not emit an event until the button is clicked', () => {
.$nextTick() expect(findEvent('cancel')).toBeUndefined();
.then(() => {
wrapper.find(sel.cancel).trigger('click');
return wrapper.vm.$nextTick();
})
.then(() => {
expect(wrapper.vm.fields).toEqual({
id: null,
name: null,
startEventIdentifier: null,
startEventLabelId: null,
endEventIdentifier: null,
endEventLabelId: null,
});
});
}); });
it('will emit the `cancel` event when clicked', () => { it('will emit the `cancel` event when clicked', async () => {
let ev = findEvent('cancel'); await setFields();
expect(ev).toBeUndefined();
wrapper.setData({ findCancelButton().vm.$emit('click');
fields: { await wrapper.vm.$nextTick();
name: 'Cool stage pre',
},
});
return wrapper.vm expect(findEvent('cancel')).toHaveLength(1);
.$nextTick()
.then(() => {
wrapper.find(sel.cancel).trigger('click');
return wrapper.vm.$nextTick();
})
.then(() => {
ev = findEvent('cancel');
expect(ev).toBeTruthy();
expect(ev).toHaveLength(1);
});
}); });
}); });
describe('isSavingCustomStage=true', () => { describe('isSavingCustomStage=true', () => {
beforeEach(() => { beforeEach(async () => {
wrapper = createComponent({ wrapper = createComponent({
initialState: { initialState: {
isSavingCustomStage: true, isSavingCustomStage: true,
}, },
}); });
return wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
}); });
it('displays a loading icon', () => { it('displays a loading icon', () => {
expect(wrapper.find(sel.submit).html()).toMatchSnapshot(); expect(findSubmitButton().html()).toMatchSnapshot();
}); });
}); });
describe('Editing a custom stage', () => { describe('Editing a custom stage', () => {
beforeEach(() => { beforeEach(async () => {
wrapper = createComponent({ wrapper = createComponent({
initialState: { initialState: {
isEditingCustomStage: true, isEditingCustomStage: true,
formInitialData, formInitialData,
}, },
}); });
return wrapper.vm.$nextTick();
}); });
afterEach(() => { it('Cancel button will reset the fields to initial state when clicked', async () => {
wrapper.destroy(); await setFields(minimumFields);
});
describe('Cancel button', () => { findCancelButton().vm.$emit('click');
it('will reset the fields to initial state when clicked', () => { await wrapper.vm.$nextTick();
wrapper.setData({
fields: {
name: 'Cool stage pre',
startEventIdentifier: labelStartEvent.identifier,
endEventIdentifier: labelStopEvent.identifier,
},
});
return wrapper.vm expect(wrapper.vm.fields).toEqual({ ...formInitialData });
.$nextTick()
.then(() => {
wrapper.find(sel.cancel).trigger('click');
return wrapper.vm.$nextTick();
})
.then(() => {
expect(wrapper.vm.fields).toEqual({
...formInitialData,
});
});
});
}); });
describe('Update stage button', () => { describe('Update stage button', () => {
it('has text `Update stage`', () => { it('has text `Update stage`', () => {
expect(wrapper.find(sel.submit).text('value')).toEqual('Update stage'); expect(findSubmitButton().text('value')).toEqual('Update stage');
}); });
it('is disabled by default', () => { it('is disabled by default', () => {
expect(wrapper.find(sel.submit).attributes('disabled')).toEqual('disabled'); expect(findSubmitButton().props('disabled')).toBe(true);
}); });
it('is enabled when a field is changed and fields are valid', () => { it('is enabled when a field is changed and fields are valid', async () => {
wrapper.setData({ await setFields(minimumFields);
fields: { expect(findSubmitButton().props('disabled')).toBe(false);
name: 'Cool updated form',
},
});
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(sel.submit).attributes('disabled')).toBeUndefined();
});
}); });
it('is disabled when a field is changed but fields are incomplete', () => { it('is disabled when a field is changed but fields are incomplete', async () => {
wrapper.setData({ await setFields({ name: '' });
fields: { expect(findSubmitButton().props('disabled')).toBe(true);
name: '', });
},
});
return wrapper.vm.$nextTick().then(() => { it('does not emit an event until the button is clicked', () => {
expect(wrapper.find(sel.submit).attributes('disabled')).toEqual('disabled'); expect(findEvent(STAGE_ACTIONS.UPDATE)).toBeUndefined();
});
}); });
it(`emits a ${STAGE_ACTIONS.UPDATE} event when clicked`, () => { it(`emits a ${STAGE_ACTIONS.UPDATE} event when clicked`, async () => {
let ev = findEvent(STAGE_ACTIONS.UPDATE); await setFields({ name: 'Cool updated form' });
expect(ev).toBeUndefined();
wrapper.setData({ findSubmitButton().vm.$emit('click');
fields: { await wrapper.vm.$nextTick();
name: 'Cool updated form',
},
});
return wrapper.vm expect(findEvent(STAGE_ACTIONS.UPDATE)).toHaveLength(1);
.$nextTick()
.then(() => {
wrapper.find(sel.submit).trigger('click');
return wrapper.vm.$nextTick();
})
.then(() => {
ev = findEvent(STAGE_ACTIONS.UPDATE);
expect(ev).toBeTruthy();
expect(ev).toHaveLength(1);
});
}); });
it('`submit` event receives the latest data', () => { it('`submit` event receives the latest data', async () => {
wrapper.setData({ await setFields({ name: 'Cool updated form' });
fields: {
name: 'Cool updated form',
},
});
return wrapper.vm findSubmitButton().vm.$emit('click');
.$nextTick() await wrapper.vm.$nextTick();
.then(() => {
wrapper.find(sel.submit).trigger('click'); const submitted = findEvent(STAGE_ACTIONS.UPDATE)[0];
return wrapper.vm.$nextTick(); expect(submitted).not.toEqual([formInitialData]);
}) expect(submitted).toEqual([
.then(() => { convertObjectPropsToSnakeCase({ ...formInitialData, name: 'Cool updated form' }),
const submitted = findEvent(STAGE_ACTIONS.UPDATE)[0]; ]);
expect(submitted).not.toEqual([formInitialData]);
expect(submitted).toEqual([
{
id: formInitialData.id,
start_event_identifier: labelStartEvent.identifier,
start_event_label_id: groupLabels[0].id,
end_event_identifier: labelStopEvent.identifier,
end_event_label_id: groupLabels[1].id,
name: 'Cool updated form',
},
]);
});
}); });
}); });
describe('isSavingCustomStage=true', () => { describe('isSavingCustomStage=true', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
initialState: { initialState: { isEditingCustomStage: true, isSavingCustomStage: true },
isEditingCustomStage: true,
isSavingCustomStage: true,
},
}); });
}); });
it('displays a loading icon', () => { it('displays a loading icon', () => {
expect(wrapper.find(sel.submit).html()).toMatchSnapshot(); expect(findSubmitButton().html()).toMatchSnapshot();
}); });
}); });
}); });
describe('With errors', () => { describe('With initial errors', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
initialState: { initialState: {
formErrors: customStageFormErrors, formErrors: customStageFormErrors,
}, },
}); });
return wrapper.vm.$nextTick();
});
afterEach(() => {
wrapper.destroy();
}); });
it('renders the errors for the relevant fields', () => { it('renders the errors for the relevant fields', () => {
expect(findNameField(wrapper).html()).toContain('is reserved'); expect(findFieldErrors('name')).toEqual(['is reserved', 'cant be blank']);
expect(findNameField(wrapper).html()).toContain('cant be blank'); expect(findFieldErrors('startEventIdentifier')).toEqual(['cant be blank']);
expect(findStartEventField(wrapper).html()).toContain('cant be blank');
}); });
}); });
describe('recover stage dropdown', () => { describe('recover stage dropdown', () => {
const formFieldStubs = {
'gl-form-group': true,
'gl-form-select': true,
'labels-selector': true,
};
beforeEach(() => {
wrapper = createComponent({
stubs: formFieldStubs,
});
});
describe('without hidden stages', () => { describe('without hidden stages', () => {
it('has the recover stage dropdown', () => { it('has the recover stage dropdown', () => {
expect(wrapper.find(sel.recoverStageDropdown).exists()).toBe(true); expect(findRecoverStageDropdown().exists()).toBe(true);
}); });
it('has no stages available to recover', () => { it('has no stages available to recover', async () => {
wrapper.find(sel.recoverStageDropdownTrigger).trigger('click'); expect(findRecoverStageDropdown().text()).toContain(
return wrapper.vm.$nextTick().then(() => { 'All default stages are currently visible',
expect(wrapper.find(sel.recoverStageDropdown).text()).toContain( );
'All default stages are currently visible',
);
});
}); });
}); });
describe('with hidden stages', () => { describe('with hidden stages', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
stubs: formFieldStubs,
initialRootGetters: { initialRootGetters: {
hiddenStages: () => [ hiddenStages: () => [
{ {
...@@ -852,168 +408,20 @@ describe('CustomStageForm', () => { ...@@ -852,168 +408,20 @@ describe('CustomStageForm', () => {
}); });
}); });
it('has stages available to recover', () => { it('has stages available to recover', async () => {
wrapper.find(sel.recoverStageDropdownTrigger).trigger('click'); const txt = findRecoverStageDropdown().text();
return wrapper.vm.$nextTick().then(() => { expect(txt).not.toContain('All default stages are currently visible');
const txt = wrapper.find(sel.recoverStageDropdown).text(); expect(txt).toContain('My default stage');
expect(txt).not.toContain('All default stages are currently visible');
expect(txt).toContain('My default stage');
});
}); });
it(`emits the ${STAGE_ACTIONS.UPDATE} action when clicking on a stage to recover`, () => { it(`emits the ${STAGE_ACTIONS.UPDATE} action when clicking on a stage to recover`, async () => {
wrapper.find(sel.recoverStageDropdownTrigger).trigger('click'); findRecoverStageDropdown()
return wrapper.vm.$nextTick().then(() => { .find(GlDropdownItem)
wrapper .vm.$emit('click');
.findAll(sel.hiddenStageDropdownOption) await wrapper.vm.$nextTick();
.at(0)
.trigger('click');
expect(wrapper.emitted()).toEqual({
[STAGE_ACTIONS.UPDATE]: [[{ hidden: false, id: 'my-stage' }]],
});
});
});
});
});
describe('initializeFormData', () => { expect(wrapper.emitted()).toEqual({
const emptyFieldState = { [STAGE_ACTIONS.UPDATE]: [[{ hidden: false, id: 'my-stage' }]],
id: null,
name: null,
startEventIdentifier: null,
startEventLabelId: null,
endEventIdentifier: null,
endEventLabelId: null,
};
const emptyErrorsState = {
id: [],
name: [],
startEventIdentifier: [],
startEventLabelId: [],
endEventIdentifier: [],
endEventLabelId: [],
};
describe('without a startEventIdentifier', () => {
it('with no errors', () => {
const res = initializeFormData({
emptyFieldState,
fields: {},
});
expect(res.fields).toEqual(emptyFieldState);
expect(res.errors).toMatchObject({
endEventIdentifier: ['Please select a start event first'],
});
});
it('with field errors', () => {
const res = initializeFormData({
emptyFieldState,
fields: {},
errors: {
name: ['is reserved'],
},
});
expect(res.fields).toEqual(emptyFieldState);
expect(res.errors).toMatchObject({
endEventIdentifier: ['Please select a start event first'],
name: ['is reserved'],
});
});
});
describe('with a startEventIdentifier', () => {
it('with no errors', () => {
const res = initializeFormData({
emptyFieldState,
fields: {
startEventIdentifier: 'start-event',
},
errors: {},
});
expect(res.fields).toEqual({
...emptyFieldState,
startEventIdentifier: 'start-event',
});
expect(res.errors).toMatchObject({
endEventIdentifier: [],
});
});
it('with field errors', () => {
const res = initializeFormData({
emptyFieldState,
fields: {
startEventIdentifier: 'start-event',
},
errors: {
name: ['is reserved'],
},
});
expect(res.fields).toEqual({
...emptyFieldState,
startEventIdentifier: 'start-event',
});
expect(res.errors).toMatchObject({
endEventIdentifier: [],
name: ['is reserved'],
});
});
});
describe('with all fields set', () => {
it('with no errors', () => {
const res = initializeFormData({
emptyFieldState,
fields: {
id: 1,
name: 'cool-stage',
startEventIdentifier: 'start-event',
endEventIdentifier: 'end-event',
startEventLabelId: 10,
endEventLabelId: 20,
},
errors: {},
});
expect(res.fields).toEqual({
id: 1,
name: 'cool-stage',
startEventIdentifier: 'start-event',
endEventIdentifier: 'end-event',
startEventLabelId: 10,
endEventLabelId: 20,
});
expect(res.errors).toEqual(emptyErrorsState);
});
it('with field errors', () => {
const res = initializeFormData({
emptyFieldState,
fields: {
id: 1,
name: 'cool-stage',
startEventIdentifier: 'start-event',
endEventIdentifier: 'end-event',
startEventLabelId: 10,
endEventLabelId: 20,
},
errors: {
name: ['is reserved'],
},
});
expect(res.fields).toEqual({
id: 1,
name: 'cool-stage',
startEventIdentifier: 'start-event',
endEventIdentifier: 'end-event',
startEventLabelId: 10,
endEventLabelId: 20,
});
expect(res.errors).toMatchObject({
name: ['is reserved'],
}); });
}); });
}); });
......
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