diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-18 11:18:50 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-18 11:18:50 +0000 |
commit | 8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch) | |
tree | a77e7fe7a93de11213032ed4ab1f33a3db51b738 /spec/frontend/releases | |
parent | 00b35af3db1abfe813a778f643dad221aad51fca (diff) | |
download | gitlab-ce-8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781.tar.gz |
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'spec/frontend/releases')
9 files changed, 681 insertions, 13 deletions
diff --git a/spec/frontend/releases/components/app_index_spec.js b/spec/frontend/releases/components/app_index_spec.js new file mode 100644 index 00000000000..91beb5b1418 --- /dev/null +++ b/spec/frontend/releases/components/app_index_spec.js @@ -0,0 +1,150 @@ +import { range as rge } from 'lodash'; +import Vue from 'vue'; +import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; +import app from '~/releases/components/app_index.vue'; +import createStore from '~/releases/stores'; +import listModule from '~/releases/stores/modules/list'; +import api from '~/api'; +import { resetStore } from '../stores/modules/list/helpers'; +import { + pageInfoHeadersWithoutPagination, + pageInfoHeadersWithPagination, + release2 as release, + releases, +} from '../mock_data'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import waitForPromises from 'helpers/wait_for_promises'; + +describe('Releases App ', () => { + const Component = Vue.extend(app); + let store; + let vm; + let releasesPagination; + + const props = { + projectId: 'gitlab-ce', + documentationPath: 'help/releases', + illustrationPath: 'illustration/path', + }; + + beforeEach(() => { + store = createStore({ modules: { list: listModule } }); + releasesPagination = rge(21).map(index => ({ + ...convertObjectPropsToCamelCase(release, { deep: true }), + tagName: `${index}.00`, + })); + }); + + afterEach(() => { + resetStore(store); + vm.$destroy(); + }); + + describe('while loading', () => { + beforeEach(() => { + jest + .spyOn(api, 'releases') + // Need to defer the return value here to the next stack, + // otherwise the loading state disappears before our test even starts. + .mockImplementation(() => waitForPromises().then(() => ({ data: [], headers: {} }))); + vm = mountComponentWithStore(Component, { props, store }); + }); + + it('renders loading icon', () => { + expect(vm.$el.querySelector('.js-loading')).not.toBeNull(); + expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); + expect(vm.$el.querySelector('.js-success-state')).toBeNull(); + expect(vm.$el.querySelector('.gl-pagination')).toBeNull(); + + return waitForPromises(); + }); + }); + + describe('with successful request', () => { + beforeEach(() => { + jest + .spyOn(api, 'releases') + .mockResolvedValue({ data: releases, headers: pageInfoHeadersWithoutPagination }); + vm = mountComponentWithStore(Component, { props, store }); + }); + + it('renders success state', () => { + expect(vm.$el.querySelector('.js-loading')).toBeNull(); + expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); + expect(vm.$el.querySelector('.js-success-state')).not.toBeNull(); + expect(vm.$el.querySelector('.gl-pagination')).toBeNull(); + }); + }); + + describe('with successful request and pagination', () => { + beforeEach(() => { + jest + .spyOn(api, 'releases') + .mockResolvedValue({ data: releasesPagination, headers: pageInfoHeadersWithPagination }); + vm = mountComponentWithStore(Component, { props, store }); + }); + + it('renders success state', () => { + expect(vm.$el.querySelector('.js-loading')).toBeNull(); + expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); + expect(vm.$el.querySelector('.js-success-state')).not.toBeNull(); + expect(vm.$el.querySelector('.gl-pagination')).not.toBeNull(); + }); + }); + + describe('with empty request', () => { + beforeEach(() => { + jest.spyOn(api, 'releases').mockResolvedValue({ data: [], headers: {} }); + vm = mountComponentWithStore(Component, { props, store }); + }); + + it('renders empty state', () => { + expect(vm.$el.querySelector('.js-loading')).toBeNull(); + expect(vm.$el.querySelector('.js-empty-state')).not.toBeNull(); + expect(vm.$el.querySelector('.js-success-state')).toBeNull(); + expect(vm.$el.querySelector('.gl-pagination')).toBeNull(); + }); + }); + + describe('"New release" button', () => { + const findNewReleaseButton = () => vm.$el.querySelector('.js-new-release-btn'); + + beforeEach(() => { + jest.spyOn(api, 'releases').mockResolvedValue({ data: [], headers: {} }); + }); + + const factory = additionalProps => { + vm = mountComponentWithStore(Component, { + props: { + ...props, + ...additionalProps, + }, + store, + }); + }; + + describe('when the user is allowed to create a new Release', () => { + const newReleasePath = 'path/to/new/release'; + + beforeEach(() => { + factory({ newReleasePath }); + }); + + it('renders the "New release" button', () => { + expect(findNewReleaseButton()).not.toBeNull(); + }); + + it('renders the "New release" button with the correct href', () => { + expect(findNewReleaseButton().getAttribute('href')).toBe(newReleasePath); + }); + }); + + describe('when the user is not allowed to create a new Release', () => { + beforeEach(() => factory()); + + it('does not render the "New release" button', () => { + expect(findNewReleaseButton()).toBeNull(); + }); + }); + }); +}); diff --git a/spec/frontend/releases/components/asset_links_form_spec.js b/spec/frontend/releases/components/asset_links_form_spec.js index 44542868cfe..e1f8592270e 100644 --- a/spec/frontend/releases/components/asset_links_form_spec.js +++ b/spec/frontend/releases/components/asset_links_form_spec.js @@ -3,6 +3,7 @@ import { mount, createLocalVue } from '@vue/test-utils'; import AssetLinksForm from '~/releases/components/asset_links_form.vue'; import { release as originalRelease } from '../mock_data'; import * as commonUtils from '~/lib/utils/common_utils'; +import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants'; const localVue = createLocalVue(); localVue.use(Vuex); @@ -24,6 +25,7 @@ describe('Release edit component', () => { addEmptyAssetLink: jest.fn(), updateAssetLinkUrl: jest.fn(), updateAssetLinkName: jest.fn(), + updateAssetLinkType: jest.fn(), removeAssetLink: jest.fn().mockImplementation((_context, linkId) => { state.release.assets.links = state.release.assets.links.filter(l => l.id !== linkId); }), @@ -51,6 +53,11 @@ describe('Release edit component', () => { wrapper = mount(AssetLinksForm, { localVue, store, + provide: { + glFeatures: { + releaseAssetLinkType: true, + }, + }, }); }; @@ -103,7 +110,7 @@ describe('Release edit component', () => { ); }); - it('calls the "updateAssetLinName" store method when text is entered into the "Link title" input field', () => { + it('calls the "updateAssetLinkName" store method when text is entered into the "Link title" input field', () => { const linkIdToUpdate = release.assets.links[0].id; const newName = 'updated name'; @@ -121,6 +128,31 @@ describe('Release edit component', () => { undefined, ); }); + + it('calls the "updateAssetLinkType" store method when an option is selected from the "Type" dropdown', () => { + const linkIdToUpdate = release.assets.links[0].id; + const newType = ASSET_LINK_TYPE.RUNBOOK; + + expect(actions.updateAssetLinkType).not.toHaveBeenCalled(); + + wrapper.find({ ref: 'typeSelect' }).vm.$emit('change', newType); + + expect(actions.updateAssetLinkType).toHaveBeenCalledTimes(1); + expect(actions.updateAssetLinkType).toHaveBeenCalledWith( + expect.anything(), + { + linkIdToUpdate, + newType, + }, + undefined, + ); + }); + + it('selects the default asset type if no type was provided by the backend', () => { + const selected = wrapper.find({ ref: 'typeSelect' }).element.value; + + expect(selected).toBe(DEFAULT_ASSET_LINK_TYPE); + }); }); describe('validation', () => { diff --git a/spec/frontend/releases/components/release_block_assets_spec.js b/spec/frontend/releases/components/release_block_assets_spec.js new file mode 100644 index 00000000000..44b190b0d19 --- /dev/null +++ b/spec/frontend/releases/components/release_block_assets_spec.js @@ -0,0 +1,137 @@ +import { mount } from '@vue/test-utils'; +import { GlCollapse } from '@gitlab/ui'; +import ReleaseBlockAssets from '~/releases/components/release_block_assets.vue'; +import { ASSET_LINK_TYPE } from '~/releases/constants'; +import { trimText } from 'helpers/text_helper'; +import { assets } from '../mock_data'; + +describe('Release block assets', () => { + let wrapper; + let defaultProps; + + // A map of types to the expected section heading text + const sections = { + [ASSET_LINK_TYPE.IMAGE]: 'Images', + [ASSET_LINK_TYPE.PACKAGE]: 'Packages', + [ASSET_LINK_TYPE.RUNBOOK]: 'Runbooks', + [ASSET_LINK_TYPE.OTHER]: 'Other', + }; + + const createComponent = (propsData = defaultProps) => { + wrapper = mount(ReleaseBlockAssets, { + provide: { + glFeatures: { releaseAssetLinkType: true }, + }, + propsData, + }); + }; + + const findSectionHeading = type => + wrapper.findAll('h5').filter(h5 => h5.text() === sections[type]); + + beforeEach(() => { + defaultProps = { assets }; + }); + + describe('with default props', () => { + beforeEach(() => createComponent()); + + const findAccordionButton = () => wrapper.find('[data-testid="accordion-button"]'); + + it('renders an "Assets" accordion with the asset count', () => { + const accordionButton = findAccordionButton(); + + expect(accordionButton.exists()).toBe(true); + expect(trimText(accordionButton.text())).toBe('Assets 5'); + }); + + it('renders the accordion as expanded by default', () => { + const accordion = wrapper.find(GlCollapse); + + expect(accordion.exists()).toBe(true); + expect(accordion.isVisible()).toBe(true); + }); + + it('renders sources with the expected text and URL', () => { + defaultProps.assets.sources.forEach(s => { + const sourceLink = wrapper.find(`li>a[href="${s.url}"]`); + + expect(sourceLink.exists()).toBe(true); + expect(sourceLink.text()).toBe(`Source code (${s.format})`); + }); + }); + + it('renders a heading for each assets type (except sources)', () => { + Object.keys(sections).forEach(type => { + const sectionHeadings = findSectionHeading(type); + + expect(sectionHeadings).toHaveLength(1); + }); + }); + + it('renders asset links with the expected text and URL', () => { + defaultProps.assets.links.forEach(l => { + const sourceLink = wrapper.find(`li>a[href="${l.directAssetUrl}"]`); + + expect(sourceLink.exists()).toBe(true); + expect(sourceLink.text()).toBe(l.name); + }); + }); + }); + + describe("when a release doesn't have a link with a certain asset type", () => { + const typeToExclude = ASSET_LINK_TYPE.IMAGE; + + beforeEach(() => { + defaultProps.assets.links = defaultProps.assets.links.filter( + l => l.linkType !== typeToExclude, + ); + createComponent(defaultProps); + }); + + it('does not render a section heading if there are no links of that type', () => { + const sectionHeadings = findSectionHeading(typeToExclude); + + expect(sectionHeadings).toHaveLength(0); + }); + }); + + describe('external vs internal links', () => { + const containsExternalSourceIndicator = () => + wrapper.contains('[data-testid="external-link-indicator"]'); + + describe('when a link is external', () => { + beforeEach(() => { + defaultProps.assets.sources = []; + defaultProps.assets.links = [ + { + ...defaultProps.assets.links[0], + external: true, + }, + ]; + createComponent(defaultProps); + }); + + it('renders the link with an "external source" indicator', () => { + expect(containsExternalSourceIndicator()).toBe(true); + }); + }); + + describe('when a link is internal', () => { + beforeEach(() => { + defaultProps.assets.sources = []; + defaultProps.assets.links = [ + { + ...defaultProps.assets.links[0], + external: false, + }, + ]; + createComponent(defaultProps); + }); + + it('renders the link without the "external source" indicator', () => { + expect(containsExternalSourceIndicator()).toBe(false); + }); + }); + }); +}); diff --git a/spec/frontend/releases/mock_data.js b/spec/frontend/releases/mock_data.js index bd5fc86275e..b97385154bd 100644 --- a/spec/frontend/releases/mock_data.js +++ b/spec/frontend/releases/mock_data.js @@ -1,3 +1,5 @@ +import { ASSET_LINK_TYPE } from '~/releases/constants'; + export const milestones = [ { id: 50, @@ -131,3 +133,92 @@ export const release = { edit_url: 'http://0.0.0.0:3001/root/release-test/-/releases/v0.3/edit', }, }; + +export const pageInfoHeadersWithoutPagination = { + 'X-NEXT-PAGE': '', + 'X-PAGE': '1', + 'X-PER-PAGE': '20', + 'X-PREV-PAGE': '', + 'X-TOTAL': '19', + 'X-TOTAL-PAGES': '1', +}; + +export const pageInfoHeadersWithPagination = { + 'X-NEXT-PAGE': '2', + 'X-PAGE': '1', + 'X-PER-PAGE': '20', + 'X-PREV-PAGE': '', + 'X-TOTAL': '21', + 'X-TOTAL-PAGES': '2', +}; + +export const assets = { + count: 5, + sources: [ + { + format: 'zip', + url: 'https://example.gitlab.com/path/to/zip', + }, + ], + links: [ + { + linkType: ASSET_LINK_TYPE.IMAGE, + url: 'https://example.gitlab.com/path/to/image', + directAssetUrl: 'https://example.gitlab.com/path/to/image', + name: 'Example image link', + }, + { + linkType: ASSET_LINK_TYPE.PACKAGE, + url: 'https://example.gitlab.com/path/to/package', + directAssetUrl: 'https://example.gitlab.com/path/to/package', + name: 'Example package link', + }, + { + linkType: ASSET_LINK_TYPE.RUNBOOK, + url: 'https://example.gitlab.com/path/to/runbook', + directAssetUrl: 'https://example.gitlab.com/path/to/runbook', + name: 'Example runbook link', + }, + { + linkType: ASSET_LINK_TYPE.OTHER, + url: 'https://example.gitlab.com/path/to/link', + directAssetUrl: 'https://example.gitlab.com/path/to/link', + name: 'Example link', + }, + ], +}; + +export const release2 = { + name: 'Bionic Beaver', + tag_name: '18.04', + description: '## changelog\n\n* line 1\n* line2', + description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>', + author_name: 'Release bot', + author_email: 'release-bot@example.com', + created_at: '2012-05-28T05:00:00-07:00', + commit: { + id: '2695effb5807a22ff3d138d593fd856244e155e7', + short_id: '2695effb', + title: 'Initial commit', + created_at: '2017-07-26T11:08:53.000+02:00', + parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'], + message: 'Initial commit', + author: { + avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png', + id: 482476, + name: 'John Doe', + path: '/johndoe', + state: 'active', + status_tooltip_html: null, + username: 'johndoe', + web_url: 'https://gitlab.com/johndoe', + }, + authored_date: '2012-05-28T04:42:42-07:00', + committer_name: 'Jack Smith', + committer_email: 'jack@example.com', + committed_date: '2012-05-28T04:42:42-07:00', + }, + assets, +}; + +export const releases = [release, release2]; diff --git a/spec/frontend/releases/stores/modules/detail/actions_spec.js b/spec/frontend/releases/stores/modules/detail/actions_spec.js index 854f06821be..345be2acc71 100644 --- a/spec/frontend/releases/stores/modules/detail/actions_spec.js +++ b/spec/frontend/releases/stores/modules/detail/actions_spec.js @@ -10,6 +10,7 @@ import createFlash from '~/flash'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { redirectTo } from '~/lib/utils/url_utility'; import api from '~/api'; +import { ASSET_LINK_TYPE } from '~/releases/constants'; jest.mock('~/flash', () => jest.fn()); @@ -130,6 +131,54 @@ describe('Release detail actions', () => { }); }); + 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.updateAssetLinkUrl, params, state, [ + { type: types.UPDATE_ASSET_LINK_URL, payload: params }, + ]); + }); + }); + + 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.updateAssetLinkName, params, state, [ + { type: types.UPDATE_ASSET_LINK_NAME, 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, + }; + + return testAction(actions.updateAssetLinkType, params, state, [ + { type: types.UPDATE_ASSET_LINK_TYPE, 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('updateReleaseMilestones', () => { it(`commits ${types.UPDATE_RELEASE_MILESTONES} with the updated release milestones`, () => { const newReleaseMilestones = ['v0.0', 'v0.1']; diff --git a/spec/frontend/releases/stores/modules/detail/mutations_spec.js b/spec/frontend/releases/stores/modules/detail/mutations_spec.js index f3f7ca797b4..a34c1be64d9 100644 --- a/spec/frontend/releases/stores/modules/detail/mutations_spec.js +++ b/spec/frontend/releases/stores/modules/detail/mutations_spec.js @@ -3,6 +3,7 @@ import mutations from '~/releases/stores/modules/detail/mutations'; import * as types from '~/releases/stores/modules/detail/mutation_types'; import { release as originalRelease } from '../../../mock_data'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants'; describe('Release detail mutations', () => { let state; @@ -24,7 +25,7 @@ describe('Release detail mutations', () => { it('set state.isFetchingRelease to true', () => { mutations[types.REQUEST_RELEASE](state); - expect(state.isFetchingRelease).toEqual(true); + expect(state.isFetchingRelease).toBe(true); }); }); @@ -32,9 +33,9 @@ describe('Release detail mutations', () => { it('handles a successful response from the server', () => { mutations[types.RECEIVE_RELEASE_SUCCESS](state, release); - expect(state.fetchError).toEqual(undefined); + expect(state.fetchError).toBeUndefined(); - expect(state.isFetchingRelease).toEqual(false); + expect(state.isFetchingRelease).toBe(false); expect(state.release).toEqual(release); @@ -47,7 +48,7 @@ describe('Release detail mutations', () => { const error = { message: 'An error occurred!' }; mutations[types.RECEIVE_RELEASE_ERROR](state, error); - expect(state.isFetchingRelease).toEqual(false); + expect(state.isFetchingRelease).toBe(false); expect(state.release).toBeUndefined(); @@ -61,7 +62,7 @@ describe('Release detail mutations', () => { const newTitle = 'The new release title'; mutations[types.UPDATE_RELEASE_TITLE](state, newTitle); - expect(state.release.name).toEqual(newTitle); + expect(state.release.name).toBe(newTitle); }); }); @@ -71,7 +72,7 @@ describe('Release detail mutations', () => { const newNotes = 'The new release notes'; mutations[types.UPDATE_RELEASE_NOTES](state, newNotes); - expect(state.release.description).toEqual(newNotes); + expect(state.release.description).toBe(newNotes); }); }); @@ -79,7 +80,7 @@ describe('Release detail mutations', () => { it('set state.isUpdatingRelease to true', () => { mutations[types.REQUEST_UPDATE_RELEASE](state); - expect(state.isUpdatingRelease).toEqual(true); + expect(state.isUpdatingRelease).toBe(true); }); }); @@ -87,9 +88,9 @@ describe('Release detail mutations', () => { it('handles a successful response from the server', () => { mutations[types.RECEIVE_UPDATE_RELEASE_SUCCESS](state, release); - expect(state.updateError).toEqual(undefined); + expect(state.updateError).toBeUndefined(); - expect(state.isUpdatingRelease).toEqual(false); + expect(state.isUpdatingRelease).toBe(false); }); }); @@ -98,7 +99,7 @@ describe('Release detail mutations', () => { const error = { message: 'An error occurred!' }; mutations[types.RECEIVE_UPDATE_RELEASE_ERROR](state, error); - expect(state.isUpdatingRelease).toEqual(false); + expect(state.isUpdatingRelease).toBe(false); expect(state.updateError).toEqual(error); }); @@ -118,6 +119,7 @@ describe('Release detail mutations', () => { id: expect.stringMatching(/^new-link-/), url: '', name: '', + linkType: DEFAULT_ASSET_LINK_TYPE, }, ]); }); @@ -134,7 +136,7 @@ describe('Release detail mutations', () => { newUrl, }); - expect(state.release.assets.links[0].url).toEqual(newUrl); + expect(state.release.assets.links[0].url).toBe(newUrl); }); }); @@ -149,7 +151,22 @@ describe('Release detail mutations', () => { newName, }); - expect(state.release.assets.links[0].name).toEqual(newName); + expect(state.release.assets.links[0].name).toBe(newName); + }); + }); + + describe(`${types.UPDATE_ASSET_LINK_TYPE}`, () => { + it('updates an asset link with a new type', () => { + state.release = release; + + const newType = ASSET_LINK_TYPE.RUNBOOK; + + mutations[types.UPDATE_ASSET_LINK_TYPE](state, { + linkIdToUpdate: state.release.assets.links[0].id, + newType, + }); + + expect(state.release.assets.links[0].linkType).toBe(newType); }); }); diff --git a/spec/frontend/releases/stores/modules/list/actions_spec.js b/spec/frontend/releases/stores/modules/list/actions_spec.js new file mode 100644 index 00000000000..4c3af157684 --- /dev/null +++ b/spec/frontend/releases/stores/modules/list/actions_spec.js @@ -0,0 +1,131 @@ +import testAction from 'helpers/vuex_action_helper'; +import { + requestReleases, + fetchReleases, + receiveReleasesSuccess, + receiveReleasesError, +} from '~/releases/stores/modules/list/actions'; +import state from '~/releases/stores/modules/list/state'; +import * as types from '~/releases/stores/modules/list/mutation_types'; +import api from '~/api'; +import { parseIntPagination, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { pageInfoHeadersWithoutPagination, releases as originalReleases } from '../../../mock_data'; + +describe('Releases State actions', () => { + let mockedState; + let pageInfo; + let releases; + + beforeEach(() => { + mockedState = state(); + pageInfo = parseIntPagination(pageInfoHeadersWithoutPagination); + releases = convertObjectPropsToCamelCase(originalReleases, { deep: true }); + }); + + describe('requestReleases', () => { + it('should commit REQUEST_RELEASES mutation', done => { + testAction(requestReleases, null, mockedState, [{ type: types.REQUEST_RELEASES }], [], done); + }); + }); + + describe('fetchReleases', () => { + describe('success', () => { + it('dispatches requestReleases and receiveReleasesSuccess', done => { + jest.spyOn(api, 'releases').mockImplementation((id, options) => { + expect(id).toEqual(1); + expect(options.page).toEqual('1'); + return Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination }); + }); + + testAction( + fetchReleases, + { projectId: 1 }, + mockedState, + [], + [ + { + type: 'requestReleases', + }, + { + payload: { data: releases, headers: pageInfoHeadersWithoutPagination }, + type: 'receiveReleasesSuccess', + }, + ], + done, + ); + }); + + it('dispatches requestReleases and receiveReleasesSuccess on page two', done => { + jest.spyOn(api, 'releases').mockImplementation((_, options) => { + expect(options.page).toEqual('2'); + return Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination }); + }); + + testAction( + fetchReleases, + { page: '2', projectId: 1 }, + mockedState, + [], + [ + { + type: 'requestReleases', + }, + { + payload: { data: releases, headers: pageInfoHeadersWithoutPagination }, + type: 'receiveReleasesSuccess', + }, + ], + done, + ); + }); + }); + + describe('error', () => { + it('dispatches requestReleases and receiveReleasesError', done => { + jest.spyOn(api, 'releases').mockReturnValue(Promise.reject()); + + testAction( + fetchReleases, + { projectId: null }, + mockedState, + [], + [ + { + type: 'requestReleases', + }, + { + type: 'receiveReleasesError', + }, + ], + done, + ); + }); + }); + }); + + describe('receiveReleasesSuccess', () => { + it('should commit RECEIVE_RELEASES_SUCCESS mutation', done => { + testAction( + receiveReleasesSuccess, + { data: releases, headers: pageInfoHeadersWithoutPagination }, + mockedState, + [{ type: types.RECEIVE_RELEASES_SUCCESS, payload: { pageInfo, data: releases } }], + [], + done, + ); + }); + }); + + describe('receiveReleasesError', () => { + it('should commit RECEIVE_RELEASES_ERROR mutation', done => { + testAction( + receiveReleasesError, + null, + mockedState, + [{ type: types.RECEIVE_RELEASES_ERROR }], + [], + done, + ); + }); + }); +}); diff --git a/spec/frontend/releases/stores/modules/list/helpers.js b/spec/frontend/releases/stores/modules/list/helpers.js new file mode 100644 index 00000000000..435ca36047e --- /dev/null +++ b/spec/frontend/releases/stores/modules/list/helpers.js @@ -0,0 +1,6 @@ +import state from '~/releases/stores/modules/list/state'; + +// eslint-disable-next-line import/prefer-default-export +export const resetStore = store => { + store.replaceState(state()); +}; diff --git a/spec/frontend/releases/stores/modules/list/mutations_spec.js b/spec/frontend/releases/stores/modules/list/mutations_spec.js new file mode 100644 index 00000000000..3035b916ff6 --- /dev/null +++ b/spec/frontend/releases/stores/modules/list/mutations_spec.js @@ -0,0 +1,55 @@ +import state from '~/releases/stores/modules/list/state'; +import mutations from '~/releases/stores/modules/list/mutations'; +import * as types from '~/releases/stores/modules/list/mutation_types'; +import { parseIntPagination } from '~/lib/utils/common_utils'; +import { pageInfoHeadersWithoutPagination, releases } from '../../../mock_data'; + +describe('Releases Store Mutations', () => { + let stateCopy; + let pageInfo; + + beforeEach(() => { + stateCopy = state(); + pageInfo = parseIntPagination(pageInfoHeadersWithoutPagination); + }); + + describe('REQUEST_RELEASES', () => { + it('sets isLoading to true', () => { + mutations[types.REQUEST_RELEASES](stateCopy); + + expect(stateCopy.isLoading).toEqual(true); + }); + }); + + describe('RECEIVE_RELEASES_SUCCESS', () => { + beforeEach(() => { + mutations[types.RECEIVE_RELEASES_SUCCESS](stateCopy, { pageInfo, data: releases }); + }); + + it('sets is loading to false', () => { + expect(stateCopy.isLoading).toEqual(false); + }); + + it('sets hasError to false', () => { + expect(stateCopy.hasError).toEqual(false); + }); + + it('sets data', () => { + expect(stateCopy.releases).toEqual(releases); + }); + + it('sets pageInfo', () => { + expect(stateCopy.pageInfo).toEqual(pageInfo); + }); + }); + + describe('RECEIVE_RELEASES_ERROR', () => { + it('resets data', () => { + mutations[types.RECEIVE_RELEASES_ERROR](stateCopy); + + expect(stateCopy.isLoading).toEqual(false); + expect(stateCopy.releases).toEqual([]); + expect(stateCopy.pageInfo).toEqual({}); + }); + }); +}); |