diff options
Diffstat (limited to 'spec/frontend/analytics/cycle_analytics/store/actions_spec.js')
-rw-r--r-- | spec/frontend/analytics/cycle_analytics/store/actions_spec.js | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/spec/frontend/analytics/cycle_analytics/store/actions_spec.js b/spec/frontend/analytics/cycle_analytics/store/actions_spec.js new file mode 100644 index 00000000000..f87807804c9 --- /dev/null +++ b/spec/frontend/analytics/cycle_analytics/store/actions_spec.js @@ -0,0 +1,518 @@ +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import testAction from 'helpers/vuex_action_helper'; +import * as actions from '~/analytics/cycle_analytics/store/actions'; +import * as getters from '~/analytics/cycle_analytics/store/getters'; +import httpStatusCodes from '~/lib/utils/http_status'; +import { + allowedStages, + selectedStage, + selectedValueStream, + currentGroup, + createdAfter, + createdBefore, + initialPaginationState, + reviewEvents, +} from '../mock_data'; + +const { id: groupId, path: groupPath } = currentGroup; +const mockMilestonesPath = 'mock-milestones.json'; +const mockLabelsPath = 'mock-labels.json'; +const mockRequestPath = 'some/cool/path'; +const mockFullPath = '/namespace/-/analytics/value_stream_analytics/value_streams'; +const mockEndpoints = { + fullPath: mockFullPath, + requestPath: mockRequestPath, + labelsPath: mockLabelsPath, + milestonesPath: mockMilestonesPath, + groupId, + groupPath, +}; +const mockSetDateActionCommit = { + payload: { createdAfter, createdBefore }, + type: 'SET_DATE_RANGE', +}; + +const defaultState = { + ...getters, + selectedValueStream, + createdAfter, + createdBefore, + pagination: initialPaginationState, +}; + +describe('Project Value Stream Analytics actions', () => { + let state; + let mock; + + beforeEach(() => { + state = { ...defaultState }; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + state = {}; + }); + + const mutationTypes = (arr) => arr.map(({ type }) => type); + + describe.each` + action | payload | expectedActions | expectedMutations + ${'setDateRange'} | ${{ createdAfter, createdBefore }} | ${[{ type: 'refetchStageData' }]} | ${[mockSetDateActionCommit]} + ${'setFilters'} | ${[]} | ${[{ type: 'refetchStageData' }]} | ${[]} + ${'setSelectedStage'} | ${{ selectedStage }} | ${[{ type: 'refetchStageData' }]} | ${[{ type: 'SET_SELECTED_STAGE', payload: { selectedStage } }]} + ${'setSelectedValueStream'} | ${{ selectedValueStream }} | ${[{ type: 'fetchValueStreamStages' }]} | ${[{ type: 'SET_SELECTED_VALUE_STREAM', payload: { selectedValueStream } }]} + `('$action', ({ action, payload, expectedActions, expectedMutations }) => { + const types = mutationTypes(expectedMutations); + it(`will dispatch ${expectedActions} and commit ${types}`, () => + testAction({ + action: actions[action], + state, + payload, + expectedMutations, + expectedActions, + })); + }); + + describe('initializeVsa', () => { + const selectedAuthor = 'Author'; + const selectedMilestone = 'Milestone 1'; + const selectedAssigneeList = ['Assignee 1', 'Assignee 2']; + const selectedLabelList = ['Label 1', 'Label 2']; + const payload = { + endpoints: mockEndpoints, + selectedAuthor, + selectedMilestone, + selectedAssigneeList, + selectedLabelList, + selectedStage, + }; + const mockFilterEndpoints = { + groupEndpoint: 'foo', + labelsEndpoint: mockLabelsPath, + milestonesEndpoint: mockMilestonesPath, + projectEndpoint: '/namespace/-/analytics/value_stream_analytics/value_streams', + }; + + it('will dispatch fetchValueStreams actions and commit SET_LOADING and INITIALIZE_VSA', () => { + return testAction({ + action: actions.initializeVsa, + state: {}, + payload, + expectedMutations: [ + { type: 'INITIALIZE_VSA', payload }, + { type: 'SET_LOADING', payload: true }, + { type: 'SET_LOADING', payload: false }, + ], + expectedActions: [ + { type: 'filters/setEndpoints', payload: mockFilterEndpoints }, + { + type: 'filters/initialize', + payload: { selectedAuthor, selectedMilestone, selectedAssigneeList, selectedLabelList }, + }, + { type: 'fetchValueStreams' }, + { type: 'setInitialStage', payload: selectedStage }, + ], + }); + }); + }); + + describe('setInitialStage', () => { + beforeEach(() => { + state = { ...state, stages: allowedStages }; + }); + + describe('with a selected stage', () => { + it('will commit `SET_SELECTED_STAGE` and fetchValueStreamStageData actions', () => { + const fakeStage = { ...selectedStage, id: 'fake', name: 'fake-stae' }; + return testAction({ + action: actions.setInitialStage, + state, + payload: fakeStage, + expectedMutations: [ + { + type: 'SET_SELECTED_STAGE', + payload: fakeStage, + }, + ], + expectedActions: [{ type: 'fetchValueStreamStageData' }], + }); + }); + }); + + describe('without a selected stage', () => { + it('will select the first stage from the value stream', () => { + const [firstStage] = allowedStages; + testAction({ + action: actions.setInitialStage, + state, + payload: null, + expectedMutations: [{ type: 'SET_SELECTED_STAGE', payload: firstStage }], + expectedActions: [{ type: 'fetchValueStreamStageData' }], + }); + }); + }); + + describe('with no value stream stages available', () => { + it('will return SET_NO_ACCESS_ERROR', () => { + state = { ...state, stages: [] }; + testAction({ + action: actions.setInitialStage, + state, + payload: null, + expectedMutations: [{ type: 'SET_NO_ACCESS_ERROR' }], + expectedActions: [], + }); + }); + }); + }); + + describe('updateStageTablePagination', () => { + beforeEach(() => { + state = { ...state, selectedStage }; + }); + + it(`will dispatch the "fetchStageData" action and commit the 'SET_PAGINATION' mutation`, () => { + return testAction({ + action: actions.updateStageTablePagination, + state, + expectedMutations: [{ type: 'SET_PAGINATION' }], + expectedActions: [{ type: 'fetchStageData', payload: selectedStage.id }], + }); + }); + }); + + describe('fetchStageData', () => { + const mockStagePath = /value_streams\/\w+\/stages\/\w+\/records/; + const headers = { + 'X-Next-Page': 2, + 'X-Page': 1, + }; + + beforeEach(() => { + state = { + ...defaultState, + endpoints: mockEndpoints, + selectedStage, + }; + mock = new MockAdapter(axios); + mock.onGet(mockStagePath).reply(httpStatusCodes.OK, reviewEvents, headers); + }); + + it(`commits the 'RECEIVE_STAGE_DATA_SUCCESS' mutation`, () => + testAction({ + action: actions.fetchStageData, + state, + payload: {}, + expectedMutations: [ + { type: 'REQUEST_STAGE_DATA' }, + { type: 'RECEIVE_STAGE_DATA_SUCCESS', payload: reviewEvents }, + { type: 'SET_PAGINATION', payload: { hasNextPage: true, page: 1 } }, + ], + expectedActions: [], + })); + + describe('with a successful request, but an error in the payload', () => { + const tooMuchDataError = 'Too much data'; + + beforeEach(() => { + state = { + ...defaultState, + endpoints: mockEndpoints, + selectedStage, + }; + mock = new MockAdapter(axios); + mock.onGet(mockStagePath).reply(httpStatusCodes.OK, { error: tooMuchDataError }); + }); + + it(`commits the 'RECEIVE_STAGE_DATA_ERROR' mutation`, () => + testAction({ + action: actions.fetchStageData, + state, + payload: { error: tooMuchDataError }, + expectedMutations: [ + { type: 'REQUEST_STAGE_DATA' }, + { type: 'RECEIVE_STAGE_DATA_ERROR', payload: tooMuchDataError }, + ], + expectedActions: [], + })); + }); + + describe('with a failing request', () => { + beforeEach(() => { + state = { + ...defaultState, + endpoints: mockEndpoints, + selectedStage, + }; + mock = new MockAdapter(axios); + mock.onGet(mockStagePath).reply(httpStatusCodes.BAD_REQUEST); + }); + + it(`commits the 'RECEIVE_STAGE_DATA_ERROR' mutation`, () => + testAction({ + action: actions.fetchStageData, + state, + payload: {}, + expectedMutations: [{ type: 'REQUEST_STAGE_DATA' }, { type: 'RECEIVE_STAGE_DATA_ERROR' }], + expectedActions: [], + })); + }); + }); + + describe('fetchValueStreams', () => { + const mockValueStreamPath = /\/analytics\/value_stream_analytics\/value_streams/; + + beforeEach(() => { + state = { + endpoints: mockEndpoints, + }; + mock = new MockAdapter(axios); + mock.onGet(mockValueStreamPath).reply(httpStatusCodes.OK); + }); + + it(`commits the 'REQUEST_VALUE_STREAMS' mutation`, () => + testAction({ + action: actions.fetchValueStreams, + state, + payload: {}, + expectedMutations: [{ type: 'REQUEST_VALUE_STREAMS' }], + expectedActions: [{ type: 'receiveValueStreamsSuccess' }], + })); + + describe('with a failing request', () => { + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onGet(mockValueStreamPath).reply(httpStatusCodes.BAD_REQUEST); + }); + + it(`commits the 'RECEIVE_VALUE_STREAMS_ERROR' mutation`, () => + testAction({ + action: actions.fetchValueStreams, + state, + payload: {}, + expectedMutations: [ + { type: 'REQUEST_VALUE_STREAMS' }, + { type: 'RECEIVE_VALUE_STREAMS_ERROR', payload: httpStatusCodes.BAD_REQUEST }, + ], + expectedActions: [], + })); + }); + }); + + describe('receiveValueStreamsSuccess', () => { + const mockValueStream = { + id: 'mockDefault', + name: 'mock default', + }; + const mockValueStreams = [mockValueStream, selectedValueStream]; + it('with data, will set the first value stream', () => { + testAction({ + action: actions.receiveValueStreamsSuccess, + state, + payload: mockValueStreams, + expectedMutations: [{ type: 'RECEIVE_VALUE_STREAMS_SUCCESS', payload: mockValueStreams }], + expectedActions: [{ type: 'setSelectedValueStream', payload: mockValueStream }], + }); + }); + + it('without data, will set the default value stream', () => { + testAction({ + action: actions.receiveValueStreamsSuccess, + state, + payload: [], + expectedMutations: [{ type: 'RECEIVE_VALUE_STREAMS_SUCCESS', payload: [] }], + expectedActions: [{ type: 'setSelectedValueStream', payload: selectedValueStream }], + }); + }); + }); + + describe('fetchValueStreamStages', () => { + const mockValueStreamPath = /\/analytics\/value_stream_analytics\/value_streams/; + + beforeEach(() => { + state = { + endpoints: mockEndpoints, + selectedValueStream, + }; + mock = new MockAdapter(axios); + mock.onGet(mockValueStreamPath).reply(httpStatusCodes.OK); + }); + + it(`commits the 'REQUEST_VALUE_STREAM_STAGES' and 'RECEIVE_VALUE_STREAM_STAGES_SUCCESS' mutations`, () => + testAction({ + action: actions.fetchValueStreamStages, + state, + payload: {}, + expectedMutations: [ + { type: 'REQUEST_VALUE_STREAM_STAGES' }, + { type: 'RECEIVE_VALUE_STREAM_STAGES_SUCCESS' }, + ], + expectedActions: [], + })); + + describe('with a failing request', () => { + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onGet(mockValueStreamPath).reply(httpStatusCodes.BAD_REQUEST); + }); + + it(`commits the 'RECEIVE_VALUE_STREAM_STAGES_ERROR' mutation`, () => + testAction({ + action: actions.fetchValueStreamStages, + state, + payload: {}, + expectedMutations: [ + { type: 'REQUEST_VALUE_STREAM_STAGES' }, + { type: 'RECEIVE_VALUE_STREAM_STAGES_ERROR', payload: httpStatusCodes.BAD_REQUEST }, + ], + expectedActions: [], + })); + }); + }); + + describe('fetchStageMedians', () => { + const mockValueStreamPath = /median/; + + const stageMediansPayload = [ + { id: 'issue', value: null }, + { id: 'plan', value: null }, + { id: 'code', value: null }, + ]; + + const stageMedianError = new Error( + `Request failed with status code ${httpStatusCodes.BAD_REQUEST}`, + ); + + beforeEach(() => { + state = { + fullPath: mockFullPath, + selectedValueStream, + stages: allowedStages, + }; + mock = new MockAdapter(axios); + mock.onGet(mockValueStreamPath).reply(httpStatusCodes.OK); + }); + + it(`commits the 'REQUEST_STAGE_MEDIANS' and 'RECEIVE_STAGE_MEDIANS_SUCCESS' mutations`, () => + testAction({ + action: actions.fetchStageMedians, + state, + payload: {}, + expectedMutations: [ + { type: 'REQUEST_STAGE_MEDIANS' }, + { type: 'RECEIVE_STAGE_MEDIANS_SUCCESS', payload: stageMediansPayload }, + ], + expectedActions: [], + })); + + describe('with a failing request', () => { + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onGet(mockValueStreamPath).reply(httpStatusCodes.BAD_REQUEST); + }); + + it(`commits the 'RECEIVE_VALUE_STREAM_STAGES_ERROR' mutation`, () => + testAction({ + action: actions.fetchStageMedians, + state, + payload: {}, + expectedMutations: [ + { type: 'REQUEST_STAGE_MEDIANS' }, + { type: 'RECEIVE_STAGE_MEDIANS_ERROR', payload: stageMedianError }, + ], + expectedActions: [], + })); + }); + }); + + describe('fetchStageCountValues', () => { + const mockValueStreamPath = /count/; + const stageCountsPayload = [ + { id: 'issue', count: 1 }, + { id: 'plan', count: 2 }, + { id: 'code', count: 3 }, + ]; + + const stageCountError = new Error( + `Request failed with status code ${httpStatusCodes.BAD_REQUEST}`, + ); + + beforeEach(() => { + state = { + fullPath: mockFullPath, + selectedValueStream, + stages: allowedStages, + }; + mock = new MockAdapter(axios); + mock + .onGet(mockValueStreamPath) + .replyOnce(httpStatusCodes.OK, { count: 1 }) + .onGet(mockValueStreamPath) + .replyOnce(httpStatusCodes.OK, { count: 2 }) + .onGet(mockValueStreamPath) + .replyOnce(httpStatusCodes.OK, { count: 3 }); + }); + + it(`commits the 'REQUEST_STAGE_COUNTS' and 'RECEIVE_STAGE_COUNTS_SUCCESS' mutations`, () => + testAction({ + action: actions.fetchStageCountValues, + state, + payload: {}, + expectedMutations: [ + { type: 'REQUEST_STAGE_COUNTS' }, + { type: 'RECEIVE_STAGE_COUNTS_SUCCESS', payload: stageCountsPayload }, + ], + expectedActions: [], + })); + + describe('with a failing request', () => { + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onGet(mockValueStreamPath).reply(httpStatusCodes.BAD_REQUEST); + }); + + it(`commits the 'RECEIVE_STAGE_COUNTS_ERROR' mutation`, () => + testAction({ + action: actions.fetchStageCountValues, + state, + payload: {}, + expectedMutations: [ + { type: 'REQUEST_STAGE_COUNTS' }, + { type: 'RECEIVE_STAGE_COUNTS_ERROR', payload: stageCountError }, + ], + expectedActions: [], + })); + }); + }); + + describe('refetchStageData', () => { + it('will commit SET_LOADING and dispatch fetchValueStreamStageData actions', () => + testAction({ + action: actions.refetchStageData, + state, + payload: {}, + expectedMutations: [ + { type: 'SET_LOADING', payload: true }, + { type: 'SET_LOADING', payload: false }, + ], + expectedActions: [{ type: 'fetchValueStreamStageData' }], + })); + }); + + describe('fetchValueStreamStageData', () => { + it('will dispatch the fetchStageData, fetchStageMedians and fetchStageCountValues actions', () => + testAction({ + action: actions.fetchValueStreamStageData, + state, + payload: {}, + expectedMutations: [], + expectedActions: [ + { type: 'fetchStageData' }, + { type: 'fetchStageMedians' }, + { type: 'fetchStageCountValues' }, + ], + })); + }); +}); |