diff options
Diffstat (limited to 'spec/frontend/releases/stores/modules/detail/actions_spec.js')
-rw-r--r-- | spec/frontend/releases/stores/modules/detail/actions_spec.js | 615 |
1 files changed, 384 insertions, 231 deletions
diff --git a/spec/frontend/releases/stores/modules/detail/actions_spec.js b/spec/frontend/releases/stores/modules/detail/actions_spec.js index 345be2acc71..1b2a705e8f4 100644 --- a/spec/frontend/releases/stores/modules/detail/actions_spec.js +++ b/spec/frontend/releases/stores/modules/detail/actions_spec.js @@ -1,18 +1,20 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; -import { cloneDeep, merge } from 'lodash'; +import { cloneDeep } from 'lodash'; import * as actions from '~/releases/stores/modules/detail/actions'; import * as types from '~/releases/stores/modules/detail/mutation_types'; import { release as originalRelease } from '../../../mock_data'; import createState from '~/releases/stores/modules/detail/state'; -import createFlash from '~/flash'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { redirectTo } from '~/lib/utils/url_utility'; import api from '~/api'; +import httpStatus from '~/lib/utils/http_status'; import { ASSET_LINK_TYPE } from '~/releases/constants'; +import { releaseToApiJson, apiJsonToRelease } from '~/releases/util'; -jest.mock('~/flash', () => jest.fn()); +jest.mock('~/flash'); jest.mock('~/lib/utils/url_utility', () => ({ redirectTo: jest.fn(), @@ -25,15 +27,26 @@ describe('Release detail actions', () => { let mock; let error; + const setupState = (updates = {}) => { + const getters = { + isExistingRelease: true, + }; + + state = { + ...createState({ + projectId: '18', + tagName: release.tag_name, + releasesPagePath: 'path/to/releases/page', + markdownDocsPath: 'path/to/markdown/docs', + markdownPreviewPath: 'path/to/markdown/preview', + updateReleaseApiDocsPath: 'path/to/api/docs', + }), + ...getters, + ...updates, + }; + }; + beforeEach(() => { - state = createState({ - projectId: '18', - tagName: 'v1.3', - releasesPagePath: 'path/to/releases/page', - markdownDocsPath: 'path/to/markdown/docs', - markdownPreviewPath: 'path/to/markdown/preview', - updateReleaseApiDocsPath: 'path/to/api/docs', - }); release = cloneDeep(originalRelease); mock = new MockAdapter(axios); gon.api_version = 'v4'; @@ -45,284 +58,424 @@ describe('Release detail actions', () => { mock.restore(); }); - describe('requestRelease', () => { - it(`commits ${types.REQUEST_RELEASE}`, () => - testAction(actions.requestRelease, undefined, state, [{ type: types.REQUEST_RELEASE }])); - }); + describe('when creating a new release', () => { + beforeEach(() => { + setupState({ isExistingRelease: false }); + }); - describe('receiveReleaseSuccess', () => { - it(`commits ${types.RECEIVE_RELEASE_SUCCESS}`, () => - testAction(actions.receiveReleaseSuccess, release, state, [ - { type: types.RECEIVE_RELEASE_SUCCESS, payload: release }, - ])); + describe('initializeRelease', () => { + it(`commits ${types.INITIALIZE_EMPTY_RELEASE}`, () => { + testAction(actions.initializeRelease, undefined, state, [ + { type: types.INITIALIZE_EMPTY_RELEASE }, + ]); + }); + }); + + describe('saveRelease', () => { + it(`commits ${types.REQUEST_SAVE_RELEASE} and then dispatched "createRelease"`, () => { + testAction( + actions.saveRelease, + undefined, + state, + [{ type: types.REQUEST_SAVE_RELEASE }], + [{ type: 'createRelease' }], + ); + }); + }); }); - describe('receiveReleaseError', () => { - it(`commits ${types.RECEIVE_RELEASE_ERROR}`, () => - testAction(actions.receiveReleaseError, error, state, [ - { type: types.RECEIVE_RELEASE_ERROR, payload: error }, - ])); + describe('when editing an existing release', () => { + beforeEach(setupState); - it('shows a flash with an error message', () => { - actions.receiveReleaseError({ commit: jest.fn() }, error); + describe('initializeRelease', () => { + it('dispatches "fetchRelease"', () => { + testAction(actions.initializeRelease, undefined, state, [], [{ type: 'fetchRelease' }]); + }); + }); - expect(createFlash).toHaveBeenCalledTimes(1); - expect(createFlash).toHaveBeenCalledWith( - 'Something went wrong while getting the release details', - ); + describe('saveRelease', () => { + it(`commits ${types.REQUEST_SAVE_RELEASE} and then dispatched "updateRelease"`, () => { + testAction( + actions.saveRelease, + undefined, + state, + [{ type: types.REQUEST_SAVE_RELEASE }], + [{ type: 'updateRelease' }], + ); + }); }); }); - describe('fetchRelease', () => { - let getReleaseUrl; + describe('actions that behave the same whether creating a new release or editing an existing release', () => { + beforeEach(setupState); - beforeEach(() => { - state.projectId = '18'; - state.tagName = 'v1.3'; - getReleaseUrl = `/api/v4/projects/${state.projectId}/releases/${state.tagName}`; - }); + describe('fetchRelease', () => { + let getReleaseUrl; + + beforeEach(() => { + getReleaseUrl = `/api/v4/projects/${state.projectId}/releases/${state.tagName}`; + }); + + describe('when the network request to the Release API is successful', () => { + beforeEach(() => { + mock.onGet(getReleaseUrl).replyOnce(httpStatus.OK, release); + }); + + it(`commits ${types.REQUEST_RELEASE} and then commits ${types.RECEIVE_RELEASE_SUCCESS} with the converted release object`, () => { + return testAction(actions.fetchRelease, undefined, state, [ + { + type: types.REQUEST_RELEASE, + }, + { + type: types.RECEIVE_RELEASE_SUCCESS, + payload: apiJsonToRelease(release, { deep: true }), + }, + ]); + }); + }); - it(`dispatches requestRelease and receiveReleaseSuccess with the camel-case'd release object`, () => { - mock.onGet(getReleaseUrl).replyOnce(200, release); - - return testAction( - actions.fetchRelease, - undefined, - state, - [], - [ - { type: 'requestRelease' }, - { - type: 'receiveReleaseSuccess', - payload: convertObjectPropsToCamelCase(release, { deep: true }), - }, - ], - ); + describe('when the network request to the Release API fails', () => { + beforeEach(() => { + mock.onGet(getReleaseUrl).replyOnce(httpStatus.INTERNAL_SERVER_ERROR); + }); + + it(`commits ${types.REQUEST_RELEASE} and then commits ${types.RECEIVE_RELEASE_ERROR} with an error object`, () => { + return testAction(actions.fetchRelease, undefined, state, [ + { + type: types.REQUEST_RELEASE, + }, + { + type: types.RECEIVE_RELEASE_ERROR, + payload: expect.any(Error), + }, + ]); + }); + + it(`shows a flash message`, () => { + return actions.fetchRelease({ commit: jest.fn(), state }).then(() => { + expect(createFlash).toHaveBeenCalledTimes(1); + expect(createFlash).toHaveBeenCalledWith( + 'Something went wrong while getting the release details', + ); + }); + }); + }); }); - it(`dispatches requestRelease and receiveReleaseError with an error object`, () => { - mock.onGet(getReleaseUrl).replyOnce(500); + describe('updateReleaseTagName', () => { + it(`commits ${types.UPDATE_RELEASE_TAG_NAME} with the updated tag name`, () => { + const newTag = 'updated-tag-name'; + return testAction(actions.updateReleaseTagName, newTag, state, [ + { type: types.UPDATE_RELEASE_TAG_NAME, payload: newTag }, + ]); + }); + }); - return testAction( - actions.fetchRelease, - undefined, - state, - [], - [{ type: 'requestRelease' }, { type: 'receiveReleaseError', payload: expect.anything() }], - ); + describe('updateCreateFrom', () => { + it(`commits ${types.UPDATE_CREATE_FROM} with the updated ref`, () => { + const newRef = 'my-feature-branch'; + return testAction(actions.updateCreateFrom, newRef, state, [ + { type: types.UPDATE_CREATE_FROM, payload: newRef }, + ]); + }); }); - }); - describe('updateReleaseTitle', () => { - it(`commits ${types.UPDATE_RELEASE_TITLE} with the updated release title`, () => { - const newTitle = 'The new release title'; - return testAction(actions.updateReleaseTitle, newTitle, state, [ - { type: types.UPDATE_RELEASE_TITLE, payload: newTitle }, - ]); + describe('updateReleaseTitle', () => { + it(`commits ${types.UPDATE_RELEASE_TITLE} with the updated release title`, () => { + const newTitle = 'The new release title'; + return testAction(actions.updateReleaseTitle, newTitle, state, [ + { type: types.UPDATE_RELEASE_TITLE, payload: newTitle }, + ]); + }); }); - }); - describe('updateReleaseNotes', () => { - it(`commits ${types.UPDATE_RELEASE_NOTES} with the updated release notes`, () => { - const newReleaseNotes = 'The new release notes'; - return testAction(actions.updateReleaseNotes, newReleaseNotes, state, [ - { type: types.UPDATE_RELEASE_NOTES, payload: newReleaseNotes }, - ]); + describe('updateReleaseNotes', () => { + it(`commits ${types.UPDATE_RELEASE_NOTES} with the updated release notes`, () => { + const newReleaseNotes = 'The new release notes'; + return testAction(actions.updateReleaseNotes, newReleaseNotes, state, [ + { type: types.UPDATE_RELEASE_NOTES, payload: newReleaseNotes }, + ]); + }); }); - }); - describe('updateAssetLinkUrl', () => { - it(`commits ${types.UPDATE_ASSET_LINK_URL} with the updated link URL`, () => { - const params = { - linkIdToUpdate: 2, - newUrl: 'https://example.com/updated', - }; + describe('updateReleaseMilestones', () => { + it(`commits ${types.UPDATE_RELEASE_MILESTONES} with the updated release milestones`, () => { + const newReleaseMilestones = ['v0.0', 'v0.1']; + return testAction(actions.updateReleaseMilestones, newReleaseMilestones, state, [ + { type: types.UPDATE_RELEASE_MILESTONES, payload: newReleaseMilestones }, + ]); + }); + }); - return testAction(actions.updateAssetLinkUrl, params, state, [ - { type: types.UPDATE_ASSET_LINK_URL, payload: params }, - ]); + describe('addEmptyAssetLink', () => { + it(`commits ${types.ADD_EMPTY_ASSET_LINK}`, () => { + return testAction(actions.addEmptyAssetLink, undefined, state, [ + { type: types.ADD_EMPTY_ASSET_LINK }, + ]); + }); }); - }); - describe('updateAssetLinkName', () => { - it(`commits ${types.UPDATE_ASSET_LINK_NAME} with the updated link name`, () => { - const params = { - linkIdToUpdate: 2, - newName: 'Updated link name', - }; + describe('updateAssetLinkUrl', () => { + it(`commits ${types.UPDATE_ASSET_LINK_URL} with the updated link URL`, () => { + const params = { + linkIdToUpdate: 2, + newUrl: 'https://example.com/updated', + }; - return testAction(actions.updateAssetLinkName, params, state, [ - { type: types.UPDATE_ASSET_LINK_NAME, payload: params }, - ]); + return testAction(actions.updateAssetLinkUrl, params, state, [ + { type: types.UPDATE_ASSET_LINK_URL, payload: params }, + ]); + }); }); - }); - describe('updateAssetLinkType', () => { - it(`commits ${types.UPDATE_ASSET_LINK_TYPE} with the updated link type`, () => { - const params = { - linkIdToUpdate: 2, - newType: ASSET_LINK_TYPE.RUNBOOK, - }; + describe('updateAssetLinkName', () => { + it(`commits ${types.UPDATE_ASSET_LINK_NAME} with the updated link name`, () => { + const params = { + linkIdToUpdate: 2, + newName: 'Updated link name', + }; - return testAction(actions.updateAssetLinkType, params, state, [ - { type: types.UPDATE_ASSET_LINK_TYPE, payload: params }, - ]); + return testAction(actions.updateAssetLinkName, params, state, [ + { type: types.UPDATE_ASSET_LINK_NAME, payload: params }, + ]); + }); }); - }); - describe('removeAssetLink', () => { - it(`commits ${types.REMOVE_ASSET_LINK} with the ID of the asset link to remove`, () => { - const idToRemove = 2; - return testAction(actions.removeAssetLink, idToRemove, state, [ - { type: types.REMOVE_ASSET_LINK, payload: idToRemove }, - ]); + describe('updateAssetLinkType', () => { + it(`commits ${types.UPDATE_ASSET_LINK_TYPE} with the updated link type`, () => { + const params = { + linkIdToUpdate: 2, + newType: ASSET_LINK_TYPE.RUNBOOK, + }; + + return testAction(actions.updateAssetLinkType, params, state, [ + { type: types.UPDATE_ASSET_LINK_TYPE, payload: params }, + ]); + }); }); - }); - describe('updateReleaseMilestones', () => { - it(`commits ${types.UPDATE_RELEASE_MILESTONES} with the updated release milestones`, () => { - const newReleaseMilestones = ['v0.0', 'v0.1']; - return testAction(actions.updateReleaseMilestones, newReleaseMilestones, state, [ - { type: types.UPDATE_RELEASE_MILESTONES, payload: newReleaseMilestones }, - ]); + describe('removeAssetLink', () => { + it(`commits ${types.REMOVE_ASSET_LINK} with the ID of the asset link to remove`, () => { + const idToRemove = 2; + return testAction(actions.removeAssetLink, idToRemove, state, [ + { type: types.REMOVE_ASSET_LINK, payload: idToRemove }, + ]); + }); }); - }); - describe('requestUpdateRelease', () => { - it(`commits ${types.REQUEST_UPDATE_RELEASE}`, () => - testAction(actions.requestUpdateRelease, undefined, state, [ - { type: types.REQUEST_UPDATE_RELEASE }, - ])); - }); + describe('receiveSaveReleaseSuccess', () => { + it(`commits ${types.RECEIVE_SAVE_RELEASE_SUCCESS}`, () => + testAction(actions.receiveSaveReleaseSuccess, undefined, { ...state, featureFlags: {} }, [ + { type: types.RECEIVE_SAVE_RELEASE_SUCCESS }, + ])); - describe('receiveUpdateReleaseSuccess', () => { - it(`commits ${types.RECEIVE_UPDATE_RELEASE_SUCCESS}`, () => - testAction(actions.receiveUpdateReleaseSuccess, undefined, { ...state, featureFlags: {} }, [ - { type: types.RECEIVE_UPDATE_RELEASE_SUCCESS }, - ])); + describe('when the releaseShowPage feature flag is enabled', () => { + beforeEach(() => { + const rootState = { featureFlags: { releaseShowPage: true } }; + actions.receiveSaveReleaseSuccess({ commit: jest.fn(), state, rootState }, release); + }); - it('redirects to the releases page if releaseShowPage feature flag is enabled', () => { - const rootState = { featureFlags: { releaseShowPage: true } }; - const updatedState = merge({}, state, { - releasesPagePath: 'path/to/releases/page', - release: { - _links: { - self: 'path/to/self', - }, - }, + it("redirects to the release's dedicated page", () => { + expect(redirectTo).toHaveBeenCalledTimes(1); + expect(redirectTo).toHaveBeenCalledWith(release._links.self); + }); }); - actions.receiveUpdateReleaseSuccess({ commit: jest.fn(), state: updatedState, rootState }); + describe('when the releaseShowPage feature flag is disabled', () => { + beforeEach(() => { + const rootState = { featureFlags: { releaseShowPage: false } }; + actions.receiveSaveReleaseSuccess({ commit: jest.fn(), state, rootState }, release); + }); - expect(redirectTo).toHaveBeenCalledTimes(1); - expect(redirectTo).toHaveBeenCalledWith(updatedState.release._links.self); + it("redirects to the project's main Releases page", () => { + expect(redirectTo).toHaveBeenCalledTimes(1); + expect(redirectTo).toHaveBeenCalledWith(state.releasesPagePath); + }); + }); }); - describe('when the releaseShowPage feature flag is disabled', () => {}); - }); - - describe('receiveUpdateReleaseError', () => { - it(`commits ${types.RECEIVE_UPDATE_RELEASE_ERROR}`, () => - testAction(actions.receiveUpdateReleaseError, error, state, [ - { type: types.RECEIVE_UPDATE_RELEASE_ERROR, payload: error }, - ])); + describe('createRelease', () => { + let createReleaseUrl; + let releaseLinksToCreate; - it('shows a flash with an error message', () => { - actions.receiveUpdateReleaseError({ commit: jest.fn() }, error); + beforeEach(() => { + const camelCasedRelease = convertObjectPropsToCamelCase(release); - expect(createFlash).toHaveBeenCalledTimes(1); - expect(createFlash).toHaveBeenCalledWith( - 'Something went wrong while saving the release details', - ); - }); - }); + releaseLinksToCreate = camelCasedRelease.assets.links.slice(0, 1); - describe('updateRelease', () => { - let getters; - let dispatch; - let callOrder; + setupState({ + release: camelCasedRelease, + releaseLinksToCreate, + }); - beforeEach(() => { - state.release = convertObjectPropsToCamelCase(release); - state.projectId = '18'; - state.tagName = state.release.tagName; + createReleaseUrl = `/api/v4/projects/${state.projectId}/releases`; + }); - getters = { - releaseLinksToDelete: [{ id: '1' }, { id: '2' }], - releaseLinksToCreate: [{ id: 'new-link-1' }, { id: 'new-link-2' }], - }; + describe('when the network request to the Release API is successful', () => { + beforeEach(() => { + const expectedRelease = releaseToApiJson({ + ...state.release, + assets: { + links: releaseLinksToCreate, + }, + }); - dispatch = jest.fn(); + mock.onPost(createReleaseUrl, expectedRelease).replyOnce(httpStatus.CREATED, release); + }); - callOrder = []; - jest.spyOn(api, 'updateRelease').mockImplementation(() => { - callOrder.push('updateRelease'); - return Promise.resolve(); - }); - jest.spyOn(api, 'deleteReleaseLink').mockImplementation(() => { - callOrder.push('deleteReleaseLink'); - return Promise.resolve(); - }); - jest.spyOn(api, 'createReleaseLink').mockImplementation(() => { - callOrder.push('createReleaseLink'); - return Promise.resolve(); + it(`dispatches "receiveSaveReleaseSuccess" with the converted release object`, () => { + return testAction( + actions.createRelease, + undefined, + state, + [], + [ + { + type: 'receiveSaveReleaseSuccess', + payload: apiJsonToRelease(release, { deep: true }), + }, + ], + ); + }); }); - }); - it('dispatches requestUpdateRelease and receiveUpdateReleaseSuccess', () => { - return actions.updateRelease({ dispatch, state, getters }).then(() => { - expect(dispatch.mock.calls).toEqual([ - ['requestUpdateRelease'], - ['receiveUpdateReleaseSuccess'], - ]); + describe('when the network request to the Release API fails', () => { + beforeEach(() => { + mock.onPost(createReleaseUrl).replyOnce(httpStatus.INTERNAL_SERVER_ERROR); + }); + + it(`commits ${types.RECEIVE_SAVE_RELEASE_ERROR} with an error object`, () => { + return testAction(actions.createRelease, undefined, state, [ + { + type: types.RECEIVE_SAVE_RELEASE_ERROR, + payload: expect.any(Error), + }, + ]); + }); + + it(`shows a flash message`, () => { + return actions + .createRelease({ commit: jest.fn(), dispatch: jest.fn(), state, getters: {} }) + .then(() => { + expect(createFlash).toHaveBeenCalledTimes(1); + expect(createFlash).toHaveBeenCalledWith( + 'Something went wrong while creating a new release', + ); + }); + }); }); }); - it('dispatches requestUpdateRelease and receiveUpdateReleaseError with an error object', () => { - jest.spyOn(api, 'updateRelease').mockRejectedValue(error); + describe('updateRelease', () => { + let getters; + let dispatch; + let commit; + let callOrder; + + beforeEach(() => { + getters = { + releaseLinksToDelete: [{ id: '1' }, { id: '2' }], + releaseLinksToCreate: [{ id: 'new-link-1' }, { id: 'new-link-2' }], + }; + + setupState({ + release: convertObjectPropsToCamelCase(release), + ...getters, + }); - return actions.updateRelease({ dispatch, state, getters }).then(() => { - expect(dispatch.mock.calls).toEqual([ - ['requestUpdateRelease'], - ['receiveUpdateReleaseError', error], - ]); + dispatch = jest.fn(); + commit = jest.fn(); + + callOrder = []; + jest.spyOn(api, 'updateRelease').mockImplementation(() => { + callOrder.push('updateRelease'); + return Promise.resolve({ data: release }); + }); + jest.spyOn(api, 'deleteReleaseLink').mockImplementation(() => { + callOrder.push('deleteReleaseLink'); + return Promise.resolve(); + }); + jest.spyOn(api, 'createReleaseLink').mockImplementation(() => { + callOrder.push('createReleaseLink'); + return Promise.resolve(); + }); }); - }); - it('updates the Release, then deletes all existing links, and then recreates new links', () => { - return actions.updateRelease({ dispatch, state, getters }).then(() => { - expect(callOrder).toEqual([ - 'updateRelease', - 'deleteReleaseLink', - 'deleteReleaseLink', - 'createReleaseLink', - 'createReleaseLink', - ]); + describe('when the network request to the Release API is successful', () => { + it('dispatches receiveSaveReleaseSuccess', () => { + return actions.updateRelease({ commit, dispatch, state, getters }).then(() => { + expect(dispatch.mock.calls).toEqual([ + ['receiveSaveReleaseSuccess', apiJsonToRelease(release)], + ]); + }); + }); - expect(api.updateRelease.mock.calls).toEqual([ - [ - state.projectId, - state.tagName, - { - name: state.release.name, - description: state.release.description, - milestones: state.release.milestones.map(milestone => milestone.title), - }, - ], - ]); + it('updates the Release, then deletes all existing links, and then recreates new links', () => { + return actions.updateRelease({ dispatch, state, getters }).then(() => { + expect(callOrder).toEqual([ + 'updateRelease', + 'deleteReleaseLink', + 'deleteReleaseLink', + 'createReleaseLink', + 'createReleaseLink', + ]); + + expect(api.updateRelease.mock.calls).toEqual([ + [ + state.projectId, + state.tagName, + releaseToApiJson({ + ...state.release, + assets: { + links: getters.releaseLinksToCreate, + }, + }), + ], + ]); + + expect(api.deleteReleaseLink).toHaveBeenCalledTimes( + getters.releaseLinksToDelete.length, + ); + getters.releaseLinksToDelete.forEach(link => { + expect(api.deleteReleaseLink).toHaveBeenCalledWith( + state.projectId, + state.tagName, + link.id, + ); + }); + + expect(api.createReleaseLink).toHaveBeenCalledTimes( + getters.releaseLinksToCreate.length, + ); + getters.releaseLinksToCreate.forEach(link => { + expect(api.createReleaseLink).toHaveBeenCalledWith( + state.projectId, + state.tagName, + link, + ); + }); + }); + }); + }); - expect(api.deleteReleaseLink).toHaveBeenCalledTimes(getters.releaseLinksToDelete.length); - getters.releaseLinksToDelete.forEach(link => { - expect(api.deleteReleaseLink).toHaveBeenCalledWith( - state.projectId, - state.tagName, - link.id, - ); + describe('when the network request to the Release API fails', () => { + beforeEach(() => { + jest.spyOn(api, 'updateRelease').mockRejectedValue(error); + }); + + it('dispatches requestUpdateRelease and receiveUpdateReleaseError with an error object', () => { + return actions.updateRelease({ commit, dispatch, state, getters }).then(() => { + expect(commit.mock.calls).toEqual([[types.RECEIVE_SAVE_RELEASE_ERROR, error]]); + }); }); - expect(api.createReleaseLink).toHaveBeenCalledTimes(getters.releaseLinksToCreate.length); - getters.releaseLinksToCreate.forEach(link => { - expect(api.createReleaseLink).toHaveBeenCalledWith(state.projectId, state.tagName, link); + it('shows a flash message', () => { + return actions.updateRelease({ commit, dispatch, state, getters }).then(() => { + expect(createFlash).toHaveBeenCalledTimes(1); + expect(createFlash).toHaveBeenCalledWith( + 'Something went wrong while saving the release details', + ); + }); }); }); }); |