Commit 817f6d4d authored by Ezekiel Kigbo's avatar Ezekiel Kigbo Committed by Illya Klymov

Move custom stage specs to folder

Moves the specs specifically for
custom stages into a separate dir
parent f35bf8cb
...@@ -70,14 +70,9 @@ export default { ...@@ -70,14 +70,9 @@ export default {
'endDate', 'endDate',
'medians', 'medians',
]), ]),
...mapState('customStages', [ // NOTE: formEvents are fetched in the same request as the list of stages (fetchGroupStagesAndEvents)
'isSavingCustomStage', // so i think its ok to bind formEvents here even though its only used as a prop to the custom-stage-form
'isCreatingCustomStage', ...mapState('customStages', ['isCreatingCustomStage', 'formEvents']),
'isEditingCustomStage',
'formEvents',
'formErrors',
'formInitialData',
]),
...mapGetters([ ...mapGetters([
'hasNoAccessError', 'hasNoAccessError',
'currentGroupPath', 'currentGroupPath',
...@@ -106,9 +101,6 @@ export default { ...@@ -106,9 +101,6 @@ export default {
isLoadingTypeOfWork() { isLoadingTypeOfWork() {
return this.isLoadingTasksByTypeChartTopLabels || this.isLoadingTasksByTypeChart; return this.isLoadingTasksByTypeChartTopLabels || this.isLoadingTasksByTypeChart;
}, },
isUpdatingCustomStage() {
return this.isEditingCustomStage && this.isSavingCustomStage;
},
hasDateRangeSet() { hasDateRangeSet() {
return this.startDate && this.endDate; return this.startDate && this.endDate;
}, },
...@@ -169,6 +161,7 @@ export default { ...@@ -169,6 +161,7 @@ export default {
this.showCreateForm(); this.showCreateForm();
}, },
onShowEditStageForm(initData = {}) { onShowEditStageForm(initData = {}) {
this.setSelectedStage(initData);
this.showEditForm(initData); this.showEditForm(initData);
}, },
onCreateCustomStage(data) { onCreateCustomStage(data) {
...@@ -299,7 +292,6 @@ export default { ...@@ -299,7 +292,6 @@ export default {
:stages="activeStages" :stages="activeStages"
:medians="medians" :medians="medians"
:is-creating-custom-stage="isCreatingCustomStage" :is-creating-custom-stage="isCreatingCustomStage"
:custom-stage-form-active="customStageFormActive"
:can-edit-stages="true" :can-edit-stages="true"
:custom-ordering="enableCustomOrdering" :custom-ordering="enableCustomOrdering"
@reorderStage="onStageReorder" @reorderStage="onStageReorder"
...@@ -311,14 +303,8 @@ export default { ...@@ -311,14 +303,8 @@ export default {
/> />
</template> </template>
<template v-if="customStageFormActive" #content> <template v-if="customStageFormActive" #content>
<gl-loading-icon v-if="isUpdatingCustomStage" class="mt-4" size="md" />
<custom-stage-form <custom-stage-form
v-else
:events="formEvents" :events="formEvents"
:is-saving-custom-stage="isSavingCustomStage"
:initial-fields="formInitialData"
:is-editing-custom-stage="isEditingCustomStage"
:errors="formErrors"
@createStage="onCreateCustomStage" @createStage="onCreateCustomStage"
@updateStage="onUpdateCustomStage" @updateStage="onUpdateCustomStage"
@clearErrors="$emit('clearFormErrors')" @clearErrors="$emit('clearFormErrors')"
......
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters, mapState } from 'vuex';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { import {
GlFormGroup, GlFormGroup,
...@@ -33,23 +33,37 @@ const defaultFields = { ...@@ -33,23 +33,37 @@ const defaultFields = {
endEventLabelId: null, endEventLabelId: null,
}; };
export const initializeFormData = ({ emptyFieldState, initialFields, errors }) => { const defaultErrors = {
const defaultErrors = initialFields?.endEventIdentifier id: [],
? { ...emptyFieldState, endEventIdentifier: null } name: [],
startEventIdentifier: [],
startEventLabelId: [],
endEventIdentifier: [],
endEventLabelId: [],
};
const ERRORS = {
START_EVENT_REQUIRED: s__('CustomCycleAnalytics|Please select a start event first'),
STAGE_NAME_EXISTS: s__('CustomCycleAnalytics|Stage name already exists'),
INVALID_EVENT_PAIRS: s__(
'CustomCycleAnalytics|Start event changed, please select a valid stop event',
),
};
export const initializeFormData = ({ emptyFieldState = defaultFields, fields, errors }) => {
const initErrors = fields?.endEventIdentifier
? defaultErrors
: { : {
...emptyFieldState, ...defaultErrors,
endEventIdentifier: endEventIdentifier: !fields?.startEventIdentifier ? [ERRORS.START_EVENT_REQUIRED] : [],
initialFields && !initialFields.startEventIdentifier
? [s__('CustomCycleAnalytics|Please select a start event first')]
: null,
}; };
return { return {
fields: { fields: {
...emptyFieldState, ...emptyFieldState,
...initialFields, ...fields,
}, },
fieldErrors: { errors: {
...defaultErrors, ...initErrors,
...errors, ...errors,
}, },
}; };
...@@ -72,42 +86,23 @@ export default { ...@@ -72,42 +86,23 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
initialFields: {
type: Object,
required: false,
default: () => {},
},
isSavingCustomStage: {
type: Boolean,
required: false,
default: false,
},
isEditingCustomStage: {
type: Boolean,
required: false,
default: false,
},
errors: {
type: Object,
required: false,
default: null,
},
}, },
data() { data() {
const { initialFields = {}, errors = null } = this;
const { fields, fieldErrors } = initializeFormData({
emptyFieldState: defaultFields,
initialFields,
errors,
});
return { return {
labelEvents: getLabelEventsIdentifiers(this.events), labelEvents: getLabelEventsIdentifiers(this.events),
fields, fields: {},
fieldErrors, errors: [],
}; };
}, },
computed: { computed: {
...mapGetters(['hiddenStages']), ...mapGetters(['hiddenStages']),
...mapState('customStages', [
'isLoading',
'isSavingCustomStage',
'isEditingCustomStage',
'formInitialData',
'formErrors',
]),
startEventOptions() { startEventOptions() {
return [ return [
{ value: null, text: s__('CustomCycleAnalytics|Select start event') }, { value: null, text: s__('CustomCycleAnalytics|Select start event') },
...@@ -132,8 +127,7 @@ export default { ...@@ -132,8 +127,7 @@ export default {
}, },
hasErrors() { hasErrors() {
return ( return (
this.eventMismatchError || this.eventMismatchError || Object.values(this.errors).some(errArray => errArray?.length)
Object.values(this.fieldErrors).some(errArray => errArray?.length)
); );
}, },
isComplete() { isComplete() {
...@@ -162,7 +156,7 @@ export default { ...@@ -162,7 +156,7 @@ export default {
); );
}, },
isDirty() { isDirty() {
return !isEqual(this.initialFields, this.fields) && !isEqual(defaultFields, this.fields); return !isEqual(this.fields, this.formInitialData || defaultFields);
}, },
eventMismatchError() { eventMismatchError() {
const { const {
...@@ -188,35 +182,39 @@ export default { ...@@ -188,35 +182,39 @@ export default {
}, },
}, },
watch: { watch: {
initialFields(newFields) { formInitialData(newFields = {}) {
this.fields = { this.fields = {
...defaultFields, ...defaultFields,
...newFields, ...newFields,
}; };
}, },
errors(newErrors) { formErrors(newErrors = {}) {
this.fieldErrors = { this.errors = {
...defaultFields,
...newErrors, ...newErrors,
}; };
}, },
}, },
mounted() {
this.resetFields();
},
methods: { methods: {
handleCancel() { resetFields() {
const { initialFields = {}, errors = null } = this; const { formInitialData, formErrors } = this;
const formData = initializeFormData({ const { fields, errors } = initializeFormData({
emptyFieldState: defaultFields, fields: formInitialData,
initialFields, errors: formErrors,
errors,
}); });
this.$set(this, 'fields', formData.fields); this.fields = { ...fields };
this.$set(this, 'fieldErrors', formData.fieldErrors); this.errors = { ...errors };
},
handleCancel() {
this.resetFields();
this.$emit('cancel'); this.$emit('cancel');
}, },
handleSave() { handleSave() {
const data = convertObjectPropsToSnakeCase(this.fields); const data = convertObjectPropsToSnakeCase(this.fields);
if (this.isEditingCustomStage) { if (this.isEditingCustomStage) {
const { id } = this.initialFields; const { id } = this.fields;
this.$emit(STAGE_ACTIONS.UPDATE, { ...data, id }); this.$emit(STAGE_ACTIONS.UPDATE, { ...data, id });
} else { } else {
this.$emit(STAGE_ACTIONS.CREATE, data); this.$emit(STAGE_ACTIONS.CREATE, data);
...@@ -229,31 +227,22 @@ export default { ...@@ -229,31 +227,22 @@ export default {
this.fields[key] = null; this.fields[key] = null;
}, },
hasFieldErrors(key) { hasFieldErrors(key) {
return this.fieldErrors[key]?.length > 0; return this.errors[key]?.length > 0;
}, },
fieldErrorMessage(key) { fieldErrorMessage(key) {
return this.fieldErrors[key]?.join('\n'); return this.errors[key]?.join('\n');
}, },
onUpdateNameField() { onUpdateNameField() {
if (DEFAULT_STAGE_NAMES.includes(this.fields.name.toLowerCase())) { this.errors.name = DEFAULT_STAGE_NAMES.includes(this.fields.name.toLowerCase())
this.$set(this.fieldErrors, 'name', [ ? [ERRORS.STAGE_NAME_EXISTS]
s__('CustomCycleAnalytics|Stage name already exists'), : [];
]);
} else {
this.$set(this.fieldErrors, 'name', []);
}
}, },
onUpdateStartEventField() { onUpdateStartEventField() {
const initVal = this.initialFields?.endEventIdentifier this.fields.endEventIdentifier = null;
? this.initialFields.endEventIdentifier this.errors.endEventIdentifier = [ERRORS.INVALID_EVENT_PAIRS];
: null;
this.$set(this.fields, 'endEventIdentifier', initVal);
this.$set(this.fieldErrors, 'endEventIdentifier', [
s__('CustomCycleAnalytics|Start event changed, please select a valid stop event'),
]);
}, },
onUpdateEndEventField() { onUpdateEndEventField() {
this.$set(this.fieldErrors, 'endEventIdentifier', null); this.errors.endEventIdentifier = [];
}, },
handleRecoverStage(id) { handleRecoverStage(id) {
this.$emit(STAGE_ACTIONS.UPDATE, { id, hidden: false }); this.$emit(STAGE_ACTIONS.UPDATE, { id, hidden: false });
...@@ -262,7 +251,10 @@ export default { ...@@ -262,7 +251,10 @@ export default {
}; };
</script> </script>
<template> <template>
<form class="custom-stage-form m-4 mt-0"> <div v-if="isLoading">
<gl-loading-icon class="mt-4" size="md" />
</div>
<form v-else class="custom-stage-form m-4 mt-0">
<div class="mb-1 d-flex flex-row justify-content-between"> <div class="mb-1 d-flex flex-row justify-content-between">
<h4>{{ formTitle }}</h4> <h4>{{ formTitle }}</h4>
<gl-dropdown :text="__('Recover hidden stage')" class="js-recover-hidden-stage-dropdown"> <gl-dropdown :text="__('Recover hidden stage')" class="js-recover-hidden-stage-dropdown">
...@@ -366,7 +358,6 @@ export default { ...@@ -366,7 +358,6 @@ export default {
</gl-form-group> </gl-form-group>
</div> </div>
</div> </div>
<div class="custom-stage-form-actions"> <div class="custom-stage-form-actions">
<button <button
:disabled="!isDirty" :disabled="!isDirty"
...@@ -386,7 +377,6 @@ export default { ...@@ -386,7 +377,6 @@ export default {
{{ saveStageText }} {{ saveStageText }}
</button> </button>
</div> </div>
<div class="mt-2"> <div class="mt-2">
<gl-sprintf <gl-sprintf
:message=" :message="
......
...@@ -29,10 +29,6 @@ export default { ...@@ -29,10 +29,6 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
customStageFormActive: {
type: Boolean,
required: true,
},
canEditStages: { canEditStages: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -106,7 +102,7 @@ export default { ...@@ -106,7 +102,7 @@ export default {
<add-stage-button <add-stage-button
v-if="canEditStages" v-if="canEditStages"
:class="$options.noDragClass" :class="$options.noDragClass"
:active="customStageFormActive" :active="isCreatingCustomStage"
@showform="$emit('showAddStageForm')" @showform="$emit('showAddStageForm')"
/> />
</ul> </ul>
......
...@@ -15,15 +15,18 @@ export const hideForm = ({ commit }) => { ...@@ -15,15 +15,18 @@ export const hideForm = ({ commit }) => {
}; };
export const showCreateForm = ({ commit }) => { export const showCreateForm = ({ commit }) => {
commit(types.SET_LOADING);
commit(types.SET_FORM_INITIAL_DATA);
commit(types.SHOW_CREATE_FORM); commit(types.SHOW_CREATE_FORM);
removeFlash(); removeFlash();
}; };
export const showEditForm = ({ commit, dispatch }, selectedStage = {}) => { export const showEditForm = ({ commit, dispatch }, selectedStage = {}) => {
commit(types.SET_LOADING);
commit(types.SET_FORM_INITIAL_DATA, selectedStage); commit(types.SET_FORM_INITIAL_DATA, selectedStage);
commit(types.SHOW_EDIT_FORM);
dispatch('setSelectedStage', selectedStage, { root: true }); dispatch('setSelectedStage', selectedStage, { root: true });
dispatch('clearSavingCustomStage'); dispatch('clearSavingCustomStage');
commit(types.SHOW_EDIT_FORM);
removeFlash(); removeFlash();
}; };
......
export const SET_LOADING = 'SET_LOADING';
export const SET_STAGE_EVENTS = 'SET_STAGE_EVENTS'; export const SET_STAGE_EVENTS = 'SET_STAGE_EVENTS';
export const SET_STAGE_FORM_ERRORS = 'SET_STAGE_FORM_ERRORS'; export const SET_STAGE_FORM_ERRORS = 'SET_STAGE_FORM_ERRORS';
export const SET_FORM_INITIAL_DATA = 'SET_FORM_INITIAL_DATA'; export const SET_FORM_INITIAL_DATA = 'SET_FORM_INITIAL_DATA';
export const SET_SAVING_CUSTOM_STAGE = 'SET_SAVING_CUSTOM_STAGE'; export const SET_SAVING_CUSTOM_STAGE = 'SET_SAVING_CUSTOM_STAGE';
export const CLEAR_SAVING_CUSTOM_STAGE = 'CLEAR_SAVING_CUSTOM_STAGE'; export const CLEAR_SAVING_CUSTOM_STAGE = 'CLEAR_SAVING_CUSTOM_STAGE';
export const HIDE_FORM = 'SHOW_FORM'; export const HIDE_FORM = 'HIDE_FORM';
export const SHOW_CREATE_FORM = 'SHOW_CREATE_FORM'; export const SHOW_CREATE_FORM = 'SHOW_CREATE_FORM';
export const SHOW_EDIT_FORM = 'SHOW_EDIT_FORM'; export const SHOW_EDIT_FORM = 'SHOW_EDIT_FORM';
export const CLEAR_FORM_ERRORS = 'CLEAR_FORM_ERRORS'; export const CLEAR_FORM_ERRORS = 'CLEAR_FORM_ERRORS';
......
...@@ -31,21 +31,26 @@ export default { ...@@ -31,21 +31,26 @@ export default {
state.formErrors = convertObjectPropsToCamelCase(errors, { deep: true }); state.formErrors = convertObjectPropsToCamelCase(errors, { deep: true });
}, },
[types.SET_FORM_INITIAL_DATA](state, rawStageData = null) { [types.SET_FORM_INITIAL_DATA](state, rawStageData = null) {
state.formInitialData = extractFormFields(rawStageData); state.formInitialData = rawStageData ? extractFormFields(rawStageData) : null;
}, },
[types.SET_SAVING_CUSTOM_STAGE](state) { [types.SET_SAVING_CUSTOM_STAGE](state) {
state.isSavingCustomStage = true; state.isSavingCustomStage = true;
}, },
[types.SET_LOADING](state) {
state.isLoadingCustomStage = true;
},
[types.CLEAR_SAVING_CUSTOM_STAGE](state) { [types.CLEAR_SAVING_CUSTOM_STAGE](state) {
state.isSavingCustomStage = false; state.isSavingCustomStage = false;
}, },
[types.SHOW_CREATE_FORM](state) { [types.SHOW_CREATE_FORM](state) {
state.isLoadingCustomStage = false;
state.isEditingCustomStage = false; state.isEditingCustomStage = false;
state.isCreatingCustomStage = true; state.isCreatingCustomStage = true;
state.formInitialData = null; state.formInitialData = null;
state.formErrors = null; state.formErrors = null;
}, },
[types.SHOW_EDIT_FORM](state) { [types.SHOW_EDIT_FORM](state) {
state.isLoadingCustomStage = false;
state.isCreatingCustomStage = false; state.isCreatingCustomStage = false;
state.isEditingCustomStage = true; state.isEditingCustomStage = true;
state.formErrors = null; state.formErrors = null;
......
export default () => ({ export default () => ({
isLoadingCustomStage: false,
isSavingCustomStage: false, isSavingCustomStage: false,
isCreatingCustomStage: false, isCreatingCustomStage: false,
isEditingCustomStage: false, isEditingCustomStage: false,
......
...@@ -130,8 +130,8 @@ export const rawCustomStage = { ...@@ -130,8 +130,8 @@ export const rawCustomStage = {
export const medians = stageMedians; export const medians = stageMedians;
const { events: rawCustomStageEvents } = customizableStagesAndEvents; export const rawCustomStageEvents = customizableStagesAndEvents.events;
const camelCasedStageEvents = rawCustomStageEvents.map(deepCamelCase); export const camelCasedStageEvents = rawCustomStageEvents.map(deepCamelCase);
export const customStageLabelEvents = camelCasedStageEvents.filter(ev => ev.type === 'label'); export const customStageLabelEvents = camelCasedStageEvents.filter(ev => ev.type === 'label');
export const customStageStartEvents = camelCasedStageEvents.filter(ev => ev.canBeStartEvent); export const customStageStartEvents = camelCasedStageEvents.filter(ev => ev.canBeStartEvent);
......
...@@ -4,8 +4,6 @@ import testAction from 'helpers/vuex_action_helper'; ...@@ -4,8 +4,6 @@ import testAction from 'helpers/vuex_action_helper';
import * as getters from 'ee/analytics/cycle_analytics/store/getters'; import * as getters from 'ee/analytics/cycle_analytics/store/getters';
import * as actions from 'ee/analytics/cycle_analytics/store/actions'; import * as actions from 'ee/analytics/cycle_analytics/store/actions';
import * as types from 'ee/analytics/cycle_analytics/store/mutation_types'; import * as types from 'ee/analytics/cycle_analytics/store/mutation_types';
import * as customStageActions from 'ee/analytics/cycle_analytics/store/modules/custom_stages/actions';
import * as customStageTypes from 'ee/analytics/cycle_analytics/store/modules/custom_stages/mutation_types';
import createFlash from '~/flash'; import createFlash from '~/flash';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
import { import {
...@@ -684,136 +682,6 @@ describe('Cycle analytics actions', () => { ...@@ -684,136 +682,6 @@ describe('Cycle analytics actions', () => {
}); });
}); });
describe('createStage', () => {
describe('with valid data', () => {
const customStageData = {
startEventIdentifier: 'start_event',
endEventIdentifier: 'end_event',
name: 'cool-new-stage',
};
beforeEach(() => {
state = { ...state, selectedGroup };
mock.onPost(endpoints.baseStagesEndpointstageData).reply(201, customStageData);
});
it(`dispatches the 'receiveCreateStageSuccess' action`, () =>
testAction(
customStageActions.createStage,
customStageData,
state,
[],
[
{ type: 'clearFormErrors' },
{ type: 'setSavingCustomStage' },
{
type: 'receiveCreateStageSuccess',
payload: { data: customStageData, status: 201 },
},
],
));
});
describe('with errors', () => {
const message = 'failed';
const errors = {
endEventIdentifier: ['Cant be blank'],
};
const customStageData = {
startEventIdentifier: 'start_event',
endEventIdentifier: '',
name: 'cool-new-stage',
};
beforeEach(() => {
state = { ...state, selectedGroup };
mock
.onPost(endpoints.baseStagesEndpointstageData)
.reply(httpStatusCodes.UNPROCESSABLE_ENTITY, {
message,
errors,
});
});
it(`dispatches the 'receiveCreateStageError' action`, () =>
testAction(
customStageActions.createStage,
customStageData,
state,
[],
[
{ type: 'clearFormErrors' },
{ type: 'setSavingCustomStage' },
{
type: 'receiveCreateStageError',
payload: {
data: customStageData,
errors,
message,
status: httpStatusCodes.UNPROCESSABLE_ENTITY,
},
},
],
));
});
});
describe('receiveCreateStageError', () => {
const response = {
data: { name: 'uh oh' },
};
beforeEach(() => {});
it('will commit the RECEIVE_CREATE_STAGE_ERROR mutation', () =>
testAction(
customStageActions.receiveCreateStageError,
response,
state,
[{ type: customStageTypes.RECEIVE_CREATE_STAGE_ERROR }],
[
{
type: 'setStageFormErrors',
payload: {},
},
],
));
it('will flash an error message', () => {
return customStageActions
.receiveCreateStageError(
{
dispatch: () => Promise.resolve(),
commit: () => {},
},
response,
)
.then(() => {
shouldFlashAMessage('There was a problem saving your custom stage, please try again');
});
});
describe('with a stage name error', () => {
it('will flash an error message', () => {
return customStageActions
.receiveCreateStageError(
{
dispatch: () => Promise.resolve(),
commit: () => {},
},
{
...response,
status: httpStatusCodes.UNPROCESSABLE_ENTITY,
errors: { name: ['is reserved'] },
},
)
.then(() => {
shouldFlashAMessage("'uh oh' stage already exists");
});
});
});
});
describe('initializeCycleAnalytics', () => { describe('initializeCycleAnalytics', () => {
let mockDispatch; let mockDispatch;
let mockCommit; let mockCommit;
...@@ -873,38 +741,6 @@ describe('Cycle analytics actions', () => { ...@@ -873,38 +741,6 @@ describe('Cycle analytics actions', () => {
)); ));
}); });
describe('receiveCreateStageSuccess', () => {
const response = {
data: {
title: 'COOL',
},
};
it('will dispatch fetchGroupStagesAndEvents', () =>
testAction(
customStageActions.receiveCreateStageSuccess,
response,
state,
[{ type: customStageTypes.RECEIVE_CREATE_STAGE_SUCCESS }],
[{ type: 'fetchGroupStagesAndEvents', payload: null }, { type: 'clearSavingCustomStage' }],
));
describe('with an error', () => {
it('will flash an error message', () =>
customStageActions
.receiveCreateStageSuccess(
{
dispatch: () => Promise.reject(),
commit: () => {},
},
response,
)
.then(() => {
shouldFlashAMessage('There was a problem refreshing the data, please try again');
}));
});
});
describe('reorderStage', () => { describe('reorderStage', () => {
const stageId = 'cool-stage'; const stageId = 'cool-stage';
const payload = { id: stageId, move_after_id: '2', move_before_id: '8' }; const payload = { id: stageId, move_after_id: '2', move_before_id: '8' };
......
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as actions from 'ee/analytics/cycle_analytics/store/modules/custom_stages/actions';
import * as types from 'ee/analytics/cycle_analytics/store/modules/custom_stages/mutation_types';
import createFlash from '~/flash';
import httpStatusCodes from '~/lib/utils/http_status';
import { selectedGroup, endpoints, rawCustomStage } from '../../../mock_data';
jest.mock('~/flash');
describe('Custom stage actions', () => {
let state;
let mock;
const selectedStage = rawCustomStage;
const shouldFlashAMessage = (msg, type = null) => {
const args = type ? [msg, type] : [msg];
expect(createFlash).toHaveBeenCalledWith(...args);
};
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
state = { selectedGroup: null };
});
describe('createStage', () => {
describe('with valid data', () => {
const customStageData = {
startEventIdentifier: 'start_event',
endEventIdentifier: 'end_event',
name: 'cool-new-stage',
};
beforeEach(() => {
state = { ...state, selectedGroup };
mock.onPost(endpoints.baseStagesEndpointstageData).reply(201, customStageData);
});
it(`dispatches the 'receiveCreateStageSuccess' action`, () =>
testAction(
actions.createStage,
customStageData,
state,
[],
[
{ type: 'clearFormErrors' },
{ type: 'setSavingCustomStage' },
{
type: 'receiveCreateStageSuccess',
payload: { data: customStageData, status: 201 },
},
],
));
});
describe('with errors', () => {
const message = 'failed';
const errors = {
endEventIdentifier: ['Cant be blank'],
};
const customStageData = {
startEventIdentifier: 'start_event',
endEventIdentifier: '',
name: 'cool-new-stage',
};
beforeEach(() => {
state = { ...state, selectedGroup };
mock
.onPost(endpoints.baseStagesEndpointstageData)
.reply(httpStatusCodes.UNPROCESSABLE_ENTITY, {
message,
errors,
});
});
it(`dispatches the 'receiveCreateStageError' action`, () =>
testAction(
actions.createStage,
customStageData,
state,
[],
[
{ type: 'clearFormErrors' },
{ type: 'setSavingCustomStage' },
{
type: 'receiveCreateStageError',
payload: {
data: customStageData,
errors,
message,
status: httpStatusCodes.UNPROCESSABLE_ENTITY,
},
},
],
));
});
});
describe('receiveCreateStageError', () => {
const response = {
data: { name: 'uh oh' },
};
beforeEach(() => {});
it('will commit the RECEIVE_CREATE_STAGE_ERROR mutation', () =>
testAction(
actions.receiveCreateStageError,
response,
state,
[{ type: types.RECEIVE_CREATE_STAGE_ERROR }],
[{ type: 'setStageFormErrors', payload: {} }],
));
it('will flash an error message', () => {
return actions
.receiveCreateStageError(
{
dispatch: () => Promise.resolve(),
commit: () => {},
},
response,
)
.then(() => {
shouldFlashAMessage('There was a problem saving your custom stage, please try again');
});
});
describe('with a stage name error', () => {
it('will flash an error message', () => {
return actions
.receiveCreateStageError(
{
dispatch: () => Promise.resolve(),
commit: () => {},
},
{
...response,
status: httpStatusCodes.UNPROCESSABLE_ENTITY,
errors: { name: ['is reserved'] },
},
)
.then(() => {
shouldFlashAMessage("'uh oh' stage already exists");
});
});
});
});
describe('receiveCreateStageSuccess', () => {
const response = {
data: {
title: 'COOL',
},
};
it('will dispatch fetchGroupStagesAndEvents', () =>
testAction(
actions.receiveCreateStageSuccess,
response,
state,
[{ type: types.RECEIVE_CREATE_STAGE_SUCCESS }],
[{ type: 'fetchGroupStagesAndEvents', payload: null }, { type: 'clearSavingCustomStage' }],
));
describe('with an error', () => {
it('will flash an error message', () =>
actions
.receiveCreateStageSuccess(
{
dispatch: () => Promise.reject(),
commit: () => {},
},
response,
)
.then(() => {
shouldFlashAMessage('There was a problem refreshing the data, please try again');
}));
});
});
describe('setStageFormErrors', () => {
it('commits the "SET_STAGE_FORM_ERRORS" mutation', () => {
return testAction(
actions.setStageFormErrors,
[],
state,
[{ type: types.SET_STAGE_FORM_ERRORS, payload: [] }],
[],
);
});
});
describe('clearFormErrors', () => {
it('commits the "CLEAR_FORM_ERRORS" mutation', () => {
return testAction(
actions.clearFormErrors,
[],
state,
[{ type: types.CLEAR_FORM_ERRORS }],
[],
);
});
});
describe('setStageEvents', () => {
it('commits the "SET_STAGE_EVENTS" mutation', () => {
return testAction(
actions.setStageEvents,
[],
state,
[{ type: types.SET_STAGE_EVENTS, payload: [] }],
[],
);
});
});
describe('hideForm', () => {
it('commits the "HIDE_FORM" mutation', () => {
return testAction(actions.hideForm, null, state, [{ type: types.HIDE_FORM }], []);
});
});
describe('showCreateForm', () => {
it('commits the "SHOW_CREATE_FORM" mutation', () => {
return testAction(
actions.showCreateForm,
null,
state,
[
{ type: types.SET_LOADING },
{ type: types.SET_FORM_INITIAL_DATA },
{ type: types.SHOW_CREATE_FORM },
],
[],
);
});
});
describe('showEditForm', () => {
it('commits the "SHOW_EDIT_FORM" mutation with initial data', () => {
return testAction(
actions.showEditForm,
selectedStage,
state,
[
{ type: types.SET_LOADING },
{ type: types.SET_FORM_INITIAL_DATA, payload: rawCustomStage },
{ type: types.SHOW_EDIT_FORM },
],
[{ type: 'setSelectedStage', payload: rawCustomStage }, { type: 'clearSavingCustomStage' }],
);
});
});
});
import * as getters from 'ee/analytics/cycle_analytics/store/modules/custom_stages/getters';
describe('Custom stages getters', () => {
describe.each`
state | result
${{ isCreatingCustomStage: true, isEditingCustomStage: true }} | ${true}
${{ isCreatingCustomStage: false, isEditingCustomStage: true }} | ${true}
${{ isCreatingCustomStage: true, isEditingCustomStage: false }} | ${true}
${{ isCreatingCustomStage: false, isEditingCustomStage: false }} | ${false}
`('customStageFormActive', ({ state, result }) => {
it(`with state ${state} returns ${result}`, () => {
expect(getters.customStageFormActive(state)).toEqual(result);
});
});
});
import mutations from 'ee/analytics/cycle_analytics/store/modules/custom_stages/mutations';
import * as types from 'ee/analytics/cycle_analytics/store/modules/custom_stages/mutation_types';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { rawCustomStageEvents, camelCasedStageEvents, rawCustomStage } from '../../../mock_data';
let state = null;
describe('Custom stage mutations', () => {
beforeEach(() => {
state = {};
});
afterEach(() => {
state = null;
});
it.each`
mutation | stateKey | value
${types.HIDE_FORM} | ${'isCreatingCustomStage'} | ${false}
${types.HIDE_FORM} | ${'isEditingCustomStage'} | ${false}
${types.HIDE_FORM} | ${'formErrors'} | ${null}
${types.HIDE_FORM} | ${'formInitialData'} | ${null}
${types.CLEAR_FORM_ERRORS} | ${'formErrors'} | ${null}
${types.SHOW_CREATE_FORM} | ${'isCreatingCustomStage'} | ${true}
${types.SHOW_CREATE_FORM} | ${'isEditingCustomStage'} | ${false}
${types.SHOW_CREATE_FORM} | ${'formErrors'} | ${null}
${types.SHOW_CREATE_FORM} | ${'formInitialData'} | ${null}
${types.SHOW_EDIT_FORM} | ${'isEditingCustomStage'} | ${true}
${types.SHOW_EDIT_FORM} | ${'isCreatingCustomStage'} | ${false}
${types.SHOW_EDIT_FORM} | ${'formErrors'} | ${null}
${types.RECEIVE_CREATE_STAGE_SUCCESS} | ${'formErrors'} | ${null}
${types.RECEIVE_CREATE_STAGE_SUCCESS} | ${'formInitialData'} | ${null}
${types.RECEIVE_CREATE_STAGE_ERROR} | ${'isSavingCustomStage'} | ${false}
${types.SET_SAVING_CUSTOM_STAGE} | ${'isSavingCustomStage'} | ${true}
${types.CLEAR_SAVING_CUSTOM_STAGE} | ${'isSavingCustomStage'} | ${false}
${types.SET_LOADING} | ${'isLoadingCustomStage'} | ${true}
`('$mutation will set $stateKey=$value', ({ mutation, stateKey, value }) => {
mutations[mutation](state);
expect(state[stateKey]).toEqual(value);
});
describe(`${types.SET_STAGE_EVENTS}`, () => {
it('will set formEvents', () => {
state = {};
mutations[types.SET_STAGE_EVENTS](state, rawCustomStageEvents);
expect(state.formEvents).toEqual(camelCasedStageEvents);
});
});
describe(`${types.SET_STAGE_FORM_ERRORS}`, () => {
const mockFormError = { start_identifier: ['Cant be blank'] };
it('will set formErrors', () => {
state = {};
mutations[types.SET_STAGE_FORM_ERRORS](state, mockFormError);
expect(state.formErrors).toEqual(convertObjectPropsToCamelCase(mockFormError));
});
});
describe(`${types.SET_FORM_INITIAL_DATA}`, () => {
const mockStage = {
endEventIdentifier: 'issue_first_added_to_board',
endEventLabelId: null,
id: 18,
name: 'Coolest beans stage',
startEventIdentifier: 'issue_first_mentioned_in_commit',
startEventLabelId: null,
};
it('will set formInitialData', () => {
state = {};
mutations[types.SET_FORM_INITIAL_DATA](state, rawCustomStage);
expect(state.formInitialData).toEqual(mockStage);
});
});
});
import mutations from 'ee/analytics/cycle_analytics/store/mutations'; import mutations from 'ee/analytics/cycle_analytics/store/mutations';
import * as types from 'ee/analytics/cycle_analytics/store/mutation_types'; import * as types from 'ee/analytics/cycle_analytics/store/mutation_types';
import customStageMutations from 'ee/analytics/cycle_analytics/store/modules/custom_stages/mutations';
import * as customStageTypes from 'ee/analytics/cycle_analytics/store/modules/custom_stages/mutation_types';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { import {
issueStage, issueStage,
...@@ -13,9 +10,8 @@ import { ...@@ -13,9 +10,8 @@ import {
totalStage, totalStage,
startDate, startDate,
endDate, endDate,
customizableStagesAndEvents,
selectedProjects, selectedProjects,
rawCustomStage, customizableStagesAndEvents,
} from '../mock_data'; } from '../mock_data';
let state = null; let state = null;
...@@ -70,65 +66,6 @@ describe('Cycle analytics mutations', () => { ...@@ -70,65 +66,6 @@ describe('Cycle analytics mutations', () => {
}, },
); );
describe('Custom stage mutations', () => {
beforeEach(() => {
state = {};
});
afterEach(() => {
state = null;
});
it.each`
mutation | stateKey | value
${customStageTypes.HIDE_FORM} | ${'isCreatingCustomStage'} | ${false}
${customStageTypes.HIDE_FORM} | ${'isEditingCustomStage'} | ${false}
${customStageTypes.HIDE_FORM} | ${'formErrors'} | ${null}
${customStageTypes.HIDE_FORM} | ${'formInitialData'} | ${null}
${customStageTypes.SHOW_CREATE_FORM} | ${'isCreatingCustomStage'} | ${true}
${customStageTypes.SHOW_CREATE_FORM} | ${'isEditingCustomStage'} | ${false}
${customStageTypes.SHOW_CREATE_FORM} | ${'formErrors'} | ${null}
${customStageTypes.SHOW_EDIT_FORM} | ${'isEditingCustomStage'} | ${true}
${customStageTypes.SHOW_EDIT_FORM} | ${'isCreatingCustomStage'} | ${false}
${customStageTypes.SHOW_EDIT_FORM} | ${'formErrors'} | ${null}
${customStageTypes.RECEIVE_CREATE_STAGE_ERROR} | ${'isSavingCustomStage'} | ${false}
${customStageTypes.SET_SAVING_CUSTOM_STAGE} | ${'isSavingCustomStage'} | ${true}
${customStageTypes.CLEAR_SAVING_CUSTOM_STAGE} | ${'isSavingCustomStage'} | ${false}
`('$mutation will set $stateKey=$value', ({ mutation, stateKey, value }) => {
customStageMutations[mutation](state);
expect(state[stateKey]).toEqual(value);
});
describe(`${customStageTypes.SET_STAGE_FORM_ERRORS}`, () => {
const mockFormError = { start_identifier: ['Cant be blank'] };
it('will set formErrors', () => {
state = {};
customStageMutations[customStageTypes.SET_STAGE_FORM_ERRORS](state, mockFormError);
expect(state.formErrors).toEqual(convertObjectPropsToCamelCase(mockFormError));
});
});
describe(`${customStageTypes.SET_FORM_INITIAL_DATA}`, () => {
const mockStage = {
id: 18,
name: 'Coolest beans stage',
startEventIdentifier: 'issue_first_mentioned_in_commit',
startEventLabelId: null,
endEventIdentifier: 'issue_first_added_to_board',
endEventLabelId: null,
};
it('will set formInitialData', () => {
state = {};
customStageMutations[customStageTypes.SET_FORM_INITIAL_DATA](state, rawCustomStage);
expect(state.formInitialData).toEqual(mockStage);
});
});
});
describe(`${types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS}`, () => { describe(`${types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS}`, () => {
it('will set isLoading=false and errorCode=null', () => { it('will set isLoading=false and errorCode=null', () => {
mutations[types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS](state, { mutations[types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS](state, {
......
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