diff options
Diffstat (limited to 'spec/frontend')
9 files changed, 362 insertions, 159 deletions
diff --git a/spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js b/spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js new file mode 100644 index 00000000000..0170ef927cf --- /dev/null +++ b/spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js @@ -0,0 +1,46 @@ +import { shallowMount } from '@vue/test-utils'; +import Popover from '~/blob/suggest_gitlab_ci_yml/components/popover.vue'; +import Cookies from 'js-cookie'; + +const popoverTarget = 'gitlab-ci-yml-selector'; +const dismissKey = 'suggest_gitlab_ci_yml_99'; + +describe('Suggest gitlab-ci.yml Popover', () => { + let wrapper; + + function createWrapper() { + wrapper = shallowMount(Popover, { + propsData: { + target: popoverTarget, + cssClass: 'js-class', + dismissKey, + }, + }); + } + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('when no dismiss cookie is set', () => { + beforeEach(() => { + createWrapper(); + }); + + it('sets popoverDismissed to false', () => { + expect(wrapper.vm.popoverDismissed).toEqual(false); + }); + }); + + describe('when the dismiss cookie is set', () => { + beforeEach(() => { + Cookies.set(dismissKey, true); + createWrapper(); + }); + + it('sets popoverDismissed to true', () => { + expect(wrapper.vm.popoverDismissed).toEqual(true); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/charts/column_spec.js b/spec/frontend/monitoring/components/charts/column_spec.js index d6a96ffbd65..f368cb7916c 100644 --- a/spec/frontend/monitoring/components/charts/column_spec.js +++ b/spec/frontend/monitoring/components/charts/column_spec.js @@ -6,56 +6,75 @@ jest.mock('~/lib/utils/icon_utils', () => ({ getSvgIconPathContent: jest.fn().mockResolvedValue('mockSvgPathContent'), })); +const yAxisName = 'Y-axis mock name'; +const yAxisFormat = 'bytes'; +const yAxisPrecistion = 3; +const dataValues = [ + [1495700554.925, '8.0390625'], + [1495700614.925, '8.0390625'], + [1495700674.925, '8.0390625'], +]; + describe('Column component', () => { - let columnChart; + let wrapper; + + const findChart = () => wrapper.find(GlColumnChart); + const chartProps = prop => findChart().props(prop); beforeEach(() => { - columnChart = shallowMount(ColumnChart, { + wrapper = shallowMount(ColumnChart, { propsData: { graphData: { + yAxis: { + name: yAxisName, + format: yAxisFormat, + precision: yAxisPrecistion, + }, metrics: [ { - x_label: 'Time', - y_label: 'Usage', result: [ { metric: {}, - values: [ - [1495700554.925, '8.0390625'], - [1495700614.925, '8.0390625'], - [1495700674.925, '8.0390625'], - ], + values: dataValues, }, ], }, ], }, - containerWidth: 100, }, }); }); afterEach(() => { - columnChart.destroy(); + wrapper.destroy(); }); describe('wrapped components', () => { describe('GitLab UI column chart', () => { - let glColumnChart; + it('is a Vue instance', () => { + expect(findChart().isVueInstance()).toBe(true); + }); - beforeEach(() => { - glColumnChart = columnChart.find(GlColumnChart); + it('receives data properties needed for proper chart render', () => { + expect(chartProps('data').values).toEqual(dataValues); }); - it('is a Vue instance', () => { - expect(glColumnChart.isVueInstance()).toBe(true); + it('passes the y axis name correctly', () => { + expect(chartProps('yAxisTitle')).toBe(yAxisName); }); - it('receives data properties needed for proper chart render', () => { - const props = glColumnChart.props(); + it('passes the y axis configuration correctly', () => { + expect(chartProps('option').yAxis).toMatchObject({ + name: yAxisName, + axisLabel: { + formatter: expect.any(Function), + }, + scale: false, + }); + }); - expect(props.data).toBe(columnChart.vm.chartData); - expect(props.option).toBe(columnChart.vm.chartOptions); + it('passes a dataZoom configuration', () => { + expect(chartProps('option').dataZoom).toBeDefined(); }); }); }); diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js index 60b1510973d..47651eca3c8 100644 --- a/spec/frontend/monitoring/mock_data.js +++ b/spec/frontend/monitoring/mock_data.js @@ -544,6 +544,12 @@ export const dashboardGitResponse = [ ...customDashboardsData, ]; +export const mockDashboardsErrorResponse = { + all_dashboards: customDashboardsData, + message: "Each 'panel_group' must define an array :panels", + status: 'error', +}; + export const graphDataPrometheusQuery = { title: 'Super Chart A2', type: 'single-stat', diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index 211950facd7..ba41a75ceec 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -30,6 +30,7 @@ import { metricsDashboardResponse, metricsDashboardViewModel, dashboardGitResponse, + mockDashboardsErrorResponse, } from '../mock_data'; jest.mock('~/flash'); @@ -257,9 +258,11 @@ describe('Monitoring store actions', () => { describe('fetchDashboard', () => { let dispatch; let state; + let commit; const response = metricsDashboardResponse; beforeEach(() => { dispatch = jest.fn(); + commit = jest.fn(); state = storeState(); state.dashboardEndpoint = '/dashboard'; }); @@ -270,6 +273,7 @@ describe('Monitoring store actions', () => { fetchDashboard( { state, + commit, dispatch, }, params, @@ -287,19 +291,21 @@ describe('Monitoring store actions', () => { describe('on failure', () => { let result; - let errorResponse; beforeEach(() => { const params = {}; result = () => { - mock.onGet(state.dashboardEndpoint).replyOnce(500, errorResponse); - return fetchDashboard({ state, dispatch }, params); + mock.onGet(state.dashboardEndpoint).replyOnce(500, mockDashboardsErrorResponse); + return fetchDashboard({ state, commit, dispatch }, params); }; }); it('dispatches a failure action', done => { - errorResponse = {}; result() .then(() => { + expect(commit).toHaveBeenCalledWith( + types.SET_ALL_DASHBOARDS, + mockDashboardsErrorResponse.all_dashboards, + ); expect(dispatch).toHaveBeenCalledWith( 'receiveMetricsDashboardFailure', new Error('Request failed with status code 500'), @@ -311,15 +317,15 @@ describe('Monitoring store actions', () => { }); it('dispatches a failure action when a message is returned', done => { - const message = 'Something went wrong with Prometheus!'; - errorResponse = { message }; result() .then(() => { expect(dispatch).toHaveBeenCalledWith( 'receiveMetricsDashboardFailure', new Error('Request failed with status code 500'), ); - expect(createFlash).toHaveBeenCalledWith(expect.stringContaining(message)); + expect(createFlash).toHaveBeenCalledWith( + expect.stringContaining(mockDashboardsErrorResponse.message), + ); done(); }) .catch(done.fail); diff --git a/spec/frontend/releases/components/app_edit_spec.js b/spec/frontend/releases/components/app_edit_spec.js index b2dbb8cc435..ac4b2b9124f 100644 --- a/spec/frontend/releases/components/app_edit_spec.js +++ b/spec/frontend/releases/components/app_edit_spec.js @@ -1,30 +1,27 @@ import Vuex from 'vuex'; import { mount } from '@vue/test-utils'; import ReleaseEditApp from '~/releases/components/app_edit.vue'; -import { release } from '../mock_data'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { release as originalRelease } from '../mock_data'; +import * as commonUtils from '~/lib/utils/common_utils'; +import { BACK_URL_PARAM } from '~/releases/constants'; describe('Release edit component', () => { let wrapper; - let releaseClone; + let release; let actions; let state; - beforeEach(() => { - gon.api_version = 'v4'; - - releaseClone = convertObjectPropsToCamelCase(release, { deep: true }); - + const factory = () => { state = { - release: releaseClone, + release, markdownDocsPath: 'path/to/markdown/docs', updateReleaseApiDocsPath: 'path/to/update/release/api/docs', + releasesPagePath: 'path/to/releases/page', }; actions = { fetchRelease: jest.fn(), updateRelease: jest.fn(), - navigateToReleasesPage: jest.fn(), }; const store = new Vuex.Store({ @@ -40,58 +37,99 @@ describe('Release edit component', () => { wrapper = mount(ReleaseEditApp, { store, }); + }; - return wrapper.vm.$nextTick(); - }); + beforeEach(() => { + gon.api_version = 'v4'; - it('calls fetchRelease when the component is created', () => { - expect(actions.fetchRelease).toHaveBeenCalledTimes(1); + release = commonUtils.convertObjectPropsToCamelCase(originalRelease, { deep: true }); }); - it('renders the description text at the top of the page', () => { - expect(wrapper.find('.js-subtitle-text').text()).toBe( - 'Releases are based on Git tags. We recommend naming tags that fit within semantic versioning, for example v1.0, v2.0-pre.', - ); + afterEach(() => { + wrapper.destroy(); + wrapper = null; }); - it('renders the correct tag name in the "Tag name" field', () => { - expect(wrapper.find('#git-ref').element.value).toBe(releaseClone.tagName); - }); + describe(`basic functionality tests: all tests unrelated to the "${BACK_URL_PARAM}" parameter`, () => { + beforeEach(() => { + factory(); + }); - it('renders the correct help text under the "Tag name" field', () => { - const helperText = wrapper.find('#tag-name-help'); - const helperTextLink = helperText.find('a'); - const helperTextLinkAttrs = helperTextLink.attributes(); - - expect(helperText.text()).toBe( - 'Changing a Release tag is only supported via Releases API. More information', - ); - expect(helperTextLink.text()).toBe('More information'); - expect(helperTextLinkAttrs.href).toBe(state.updateReleaseApiDocsPath); - expect(helperTextLinkAttrs.rel).toContain('noopener'); - expect(helperTextLinkAttrs.rel).toContain('noreferrer'); - expect(helperTextLinkAttrs.target).toBe('_blank'); - }); + it('calls fetchRelease when the component is created', () => { + expect(actions.fetchRelease).toHaveBeenCalledTimes(1); + }); - it('renders the correct release title in the "Release title" field', () => { - expect(wrapper.find('#release-title').element.value).toBe(releaseClone.name); - }); + it('renders the description text at the top of the page', () => { + expect(wrapper.find('.js-subtitle-text').text()).toBe( + 'Releases are based on Git tags. We recommend naming tags that fit within semantic versioning, for example v1.0, v2.0-pre.', + ); + }); - it('renders the release notes in the "Release notes" textarea', () => { - expect(wrapper.find('#release-notes').element.value).toBe(releaseClone.description); - }); + it('renders the correct tag name in the "Tag name" field', () => { + expect(wrapper.find('#git-ref').element.value).toBe(release.tagName); + }); + + it('renders the correct help text under the "Tag name" field', () => { + const helperText = wrapper.find('#tag-name-help'); + const helperTextLink = helperText.find('a'); + const helperTextLinkAttrs = helperTextLink.attributes(); + + expect(helperText.text()).toBe( + 'Changing a Release tag is only supported via Releases API. More information', + ); + expect(helperTextLink.text()).toBe('More information'); + expect(helperTextLinkAttrs).toEqual( + expect.objectContaining({ + href: state.updateReleaseApiDocsPath, + rel: 'noopener noreferrer', + target: '_blank', + }), + ); + }); + + it('renders the correct release title in the "Release title" field', () => { + expect(wrapper.find('#release-title').element.value).toBe(release.name); + }); + + it('renders the release notes in the "Release notes" textarea', () => { + expect(wrapper.find('#release-notes').element.value).toBe(release.description); + }); + + it('renders the "Save changes" button as type="submit"', () => { + expect(wrapper.find('.js-submit-button').attributes('type')).toBe('submit'); + }); - it('renders the "Save changes" button as type="submit"', () => { - expect(wrapper.find('.js-submit-button').attributes('type')).toBe('submit'); + it('calls updateRelease when the form is submitted', () => { + wrapper.find('form').trigger('submit'); + expect(actions.updateRelease).toHaveBeenCalledTimes(1); + }); }); - it('calls updateRelease when the form is submitted', () => { - wrapper.find('form').trigger('submit'); - expect(actions.updateRelease).toHaveBeenCalledTimes(1); + describe(`when the URL does not contain a "${BACK_URL_PARAM}" parameter`, () => { + beforeEach(() => { + factory(); + }); + + it(`renders a "Cancel" button with an href pointing to "${BACK_URL_PARAM}"`, () => { + const cancelButton = wrapper.find('.js-cancel-button'); + expect(cancelButton.attributes().href).toBe(state.releasesPagePath); + }); }); - it('calls navigateToReleasesPage when the "Cancel" button is clicked', () => { - wrapper.find('.js-cancel-button').vm.$emit('click'); - expect(actions.navigateToReleasesPage).toHaveBeenCalledTimes(1); + describe(`when the URL contains a "${BACK_URL_PARAM}" parameter`, () => { + const backUrl = 'https://example.gitlab.com/back/url'; + + beforeEach(() => { + commonUtils.getParameterByName = jest + .fn() + .mockImplementation(paramToGet => ({ [BACK_URL_PARAM]: backUrl }[paramToGet])); + + factory(); + }); + + it('renders a "Cancel" button with an href pointing to the main Releases page', () => { + const cancelButton = wrapper.find('.js-cancel-button'); + expect(cancelButton.attributes().href).toBe(backUrl); + }); }); }); diff --git a/spec/frontend/releases/components/app_show_spec.js b/spec/frontend/releases/components/app_show_spec.js new file mode 100644 index 00000000000..3dc9964c25c --- /dev/null +++ b/spec/frontend/releases/components/app_show_spec.js @@ -0,0 +1,61 @@ +import Vuex from 'vuex'; +import { shallowMount } from '@vue/test-utils'; +import ReleaseShowApp from '~/releases/components/app_show.vue'; +import { release as originalRelease } from '../mock_data'; +import { GlSkeletonLoading } from '@gitlab/ui'; +import ReleaseBlock from '~/releases/components/release_block.vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; + +describe('Release show component', () => { + let wrapper; + let release; + let actions; + + beforeEach(() => { + release = convertObjectPropsToCamelCase(originalRelease); + }); + + const factory = state => { + actions = { + fetchRelease: jest.fn(), + }; + + const store = new Vuex.Store({ + modules: { + detail: { + namespaced: true, + actions, + state, + }, + }, + }); + + wrapper = shallowMount(ReleaseShowApp, { store }); + }; + + const findLoadingSkeleton = () => wrapper.find(GlSkeletonLoading); + const findReleaseBlock = () => wrapper.find(ReleaseBlock); + + it('calls fetchRelease when the component is created', () => { + factory({ release }); + expect(actions.fetchRelease).toHaveBeenCalledTimes(1); + }); + + it('shows a loading skeleton and hides the release block while the API call is in progress', () => { + factory({ isFetchingRelease: true }); + expect(findLoadingSkeleton().exists()).toBe(true); + expect(findReleaseBlock().exists()).toBe(false); + }); + + it('hides the loading skeleton and shows the release block when the API call finishes successfully', () => { + factory({ isFetchingRelease: false }); + expect(findLoadingSkeleton().exists()).toBe(false); + expect(findReleaseBlock().exists()).toBe(true); + }); + + it('hides both the loading skeleton and the release block when the API call fails', () => { + factory({ fetchError: new Error('Uh oh') }); + expect(findLoadingSkeleton().exists()).toBe(false); + expect(findReleaseBlock().exists()).toBe(false); + }); +}); diff --git a/spec/frontend/releases/components/release_block_header_spec.js b/spec/frontend/releases/components/release_block_header_spec.js index 44f6f63fa79..9c6cbc86d3c 100644 --- a/spec/frontend/releases/components/release_block_header_spec.js +++ b/spec/frontend/releases/components/release_block_header_spec.js @@ -4,6 +4,7 @@ import { GlLink } from '@gitlab/ui'; import ReleaseBlockHeader from '~/releases/components/release_block_header.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { release as originalRelease } from '../mock_data'; +import { BACK_URL_PARAM } from '~/releases/constants'; describe('Release block header', () => { let wrapper; @@ -27,6 +28,7 @@ describe('Release block header', () => { const findHeader = () => wrapper.find('h2'); const findHeaderLink = () => findHeader().find(GlLink); + const findEditButton = () => wrapper.find('.js-edit-button'); describe('when _links.self is provided', () => { beforeEach(() => { @@ -51,4 +53,39 @@ describe('Release block header', () => { expect(findHeaderLink().exists()).toBe(false); }); }); + + describe('when _links.edit_url is provided', () => { + const currentUrl = 'https://example.gitlab.com/path'; + + beforeEach(() => { + Object.defineProperty(window, 'location', { + writable: true, + value: { + href: currentUrl, + }, + }); + + factory(); + }); + + it('renders an edit button', () => { + expect(findEditButton().exists()).toBe(true); + }); + + it('renders the edit button with the correct href', () => { + const expectedQueryParam = `${BACK_URL_PARAM}=${encodeURIComponent(currentUrl)}`; + const expectedUrl = `${release._links.editUrl}?${expectedQueryParam}`; + expect(findEditButton().attributes().href).toBe(expectedUrl); + }); + }); + + describe('when _links.edit is missing', () => { + beforeEach(() => { + factory({ _links: { editUrl: null } }); + }); + + it('does not render an edit button', () => { + expect(findEditButton().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js index ff88e3193bc..227998b0271 100644 --- a/spec/frontend/releases/components/release_block_spec.js +++ b/spec/frontend/releases/components/release_block_spec.js @@ -7,20 +7,9 @@ import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; import { release as originalRelease } from '../mock_data'; import Icon from '~/vue_shared/components/icon.vue'; -import { scrollToElement } from '~/lib/utils/common_utils'; - -const { convertObjectPropsToCamelCase } = jest.requireActual('~/lib/utils/common_utils'); - -let mockLocationHash; -jest.mock('~/lib/utils/url_utility', () => ({ - __esModule: true, - getLocationHash: jest.fn().mockImplementation(() => mockLocationHash), -})); - -jest.mock('~/lib/utils/common_utils', () => ({ - __esModule: true, - scrollToElement: jest.fn(), -})); +import * as commonUtils from '~/lib/utils/common_utils'; +import { BACK_URL_PARAM } from '~/releases/constants'; +import * as urlUtility from '~/lib/utils/url_utility'; describe('Release block', () => { let wrapper; @@ -47,7 +36,7 @@ describe('Release block', () => { beforeEach(() => { jest.spyOn($.fn, 'renderGFM'); - release = convertObjectPropsToCamelCase(originalRelease, { deep: true }); + release = commonUtils.convertObjectPropsToCamelCase(originalRelease, { deep: true }); }); afterEach(() => { @@ -61,9 +50,11 @@ describe('Release block', () => { expect(wrapper.attributes().id).toBe('v0.3'); }); - it('renders an edit button that links to the "Edit release" page', () => { + it(`renders an edit button that links to the "Edit release" page with a "${BACK_URL_PARAM}" parameter`, () => { expect(editButton().exists()).toBe(true); - expect(editButton().attributes('href')).toBe(release._links.editUrl); + expect(editButton().attributes('href')).toBe( + `${release._links.editUrl}?${BACK_URL_PARAM}=${encodeURIComponent(window.location.href)}`, + ); }); it('renders release name', () => { @@ -150,14 +141,6 @@ describe('Release block', () => { }); }); - it("does not render an edit button if release._links.editUrl isn't a string", () => { - delete release._links; - - return factory(release).then(() => { - expect(editButton().exists()).toBe(false); - }); - }); - it('does not render the milestone list if no milestones are associated to the release', () => { delete release.milestones; @@ -203,37 +186,40 @@ describe('Release block', () => { }); describe('anchor scrolling', () => { + let locationHash; + beforeEach(() => { - scrollToElement.mockClear(); + commonUtils.scrollToElement = jest.fn(); + urlUtility.getLocationHash = jest.fn().mockImplementation(() => locationHash); }); const hasTargetBlueBackground = () => wrapper.classes('bg-line-target-blue'); it('does not attempt to scroll the page if no anchor tag is included in the URL', () => { - mockLocationHash = ''; + locationHash = ''; return factory(release).then(() => { - expect(scrollToElement).not.toHaveBeenCalled(); + expect(commonUtils.scrollToElement).not.toHaveBeenCalled(); }); }); it("does not attempt to scroll the page if the anchor tag doesn't match the release's tag name", () => { - mockLocationHash = 'v0.4'; + locationHash = 'v0.4'; return factory(release).then(() => { - expect(scrollToElement).not.toHaveBeenCalled(); + expect(commonUtils.scrollToElement).not.toHaveBeenCalled(); }); }); it("attempts to scroll itself into view if the anchor tag matches the release's tag name", () => { - mockLocationHash = release.tagName; + locationHash = release.tagName; return factory(release).then(() => { - expect(scrollToElement).toHaveBeenCalledTimes(1); + expect(commonUtils.scrollToElement).toHaveBeenCalledTimes(1); - expect(scrollToElement).toHaveBeenCalledWith(wrapper.element); + expect(commonUtils.scrollToElement).toHaveBeenCalledWith(wrapper.element); }); }); it('renders with a light blue background if it is the target of the anchor', () => { - mockLocationHash = release.tagName; + locationHash = release.tagName; return factory(release).then(() => { expect(hasTargetBlueBackground()).toBe(true); @@ -241,7 +227,7 @@ describe('Release block', () => { }); it('does not render with a light blue background if it is not the target of the anchor', () => { - mockLocationHash = ''; + locationHash = ''; return factory(release).then(() => { expect(hasTargetBlueBackground()).toBe(false); diff --git a/spec/frontend/releases/stores/modules/detail/actions_spec.js b/spec/frontend/releases/stores/modules/detail/actions_spec.js index 0c2763822c9..88346083f5a 100644 --- a/spec/frontend/releases/stores/modules/detail/actions_spec.js +++ b/spec/frontend/releases/stores/modules/detail/actions_spec.js @@ -1,13 +1,14 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; +import { cloneDeep, merge } from 'lodash'; import * as actions from '~/releases/stores/modules/detail/actions'; import * as types from '~/releases/stores/modules/detail/mutation_types'; -import { release } from '../../../mock_data'; -import state from '~/releases/stores/modules/detail/state'; +import { release as originalRelease } from '../../../mock_data'; +import createState from '~/releases/stores/modules/detail/state'; import createFlash from '~/flash'; -import { redirectTo } from '~/lib/utils/url_utility'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { redirectTo } from '~/lib/utils/url_utility'; jest.mock('~/flash', () => jest.fn()); @@ -17,14 +18,14 @@ jest.mock('~/lib/utils/url_utility', () => ({ })); describe('Release detail actions', () => { - let stateClone; - let releaseClone; + let state; + let release; let mock; let error; beforeEach(() => { - stateClone = state(); - releaseClone = JSON.parse(JSON.stringify(release)); + state = createState(); + release = cloneDeep(originalRelease); mock = new MockAdapter(axios); gon.api_version = 'v4'; error = { message: 'An error occurred' }; @@ -39,7 +40,7 @@ describe('Release detail actions', () => { it(`commits ${types.SET_INITIAL_STATE} with the provided object`, () => { const initialState = {}; - return testAction(actions.setInitialState, initialState, stateClone, [ + return testAction(actions.setInitialState, initialState, state, [ { type: types.SET_INITIAL_STATE, payload: initialState }, ]); }); @@ -47,19 +48,19 @@ describe('Release detail actions', () => { describe('requestRelease', () => { it(`commits ${types.REQUEST_RELEASE}`, () => - testAction(actions.requestRelease, undefined, stateClone, [{ type: types.REQUEST_RELEASE }])); + testAction(actions.requestRelease, undefined, state, [{ type: types.REQUEST_RELEASE }])); }); describe('receiveReleaseSuccess', () => { it(`commits ${types.RECEIVE_RELEASE_SUCCESS}`, () => - testAction(actions.receiveReleaseSuccess, releaseClone, stateClone, [ - { type: types.RECEIVE_RELEASE_SUCCESS, payload: releaseClone }, + testAction(actions.receiveReleaseSuccess, release, state, [ + { type: types.RECEIVE_RELEASE_SUCCESS, payload: release }, ])); }); describe('receiveReleaseError', () => { it(`commits ${types.RECEIVE_RELEASE_ERROR}`, () => - testAction(actions.receiveReleaseError, error, stateClone, [ + testAction(actions.receiveReleaseError, error, state, [ { type: types.RECEIVE_RELEASE_ERROR, payload: error }, ])); @@ -77,24 +78,24 @@ describe('Release detail actions', () => { let getReleaseUrl; beforeEach(() => { - stateClone.projectId = '18'; - stateClone.tagName = 'v1.3'; - getReleaseUrl = `/api/v4/projects/${stateClone.projectId}/releases/${stateClone.tagName}`; + state.projectId = '18'; + state.tagName = 'v1.3'; + getReleaseUrl = `/api/v4/projects/${state.projectId}/releases/${state.tagName}`; }); it(`dispatches requestRelease and receiveReleaseSuccess with the camel-case'd release object`, () => { - mock.onGet(getReleaseUrl).replyOnce(200, releaseClone); + mock.onGet(getReleaseUrl).replyOnce(200, release); return testAction( actions.fetchRelease, undefined, - stateClone, + state, [], [ { type: 'requestRelease' }, { type: 'receiveReleaseSuccess', - payload: convertObjectPropsToCamelCase(releaseClone, { deep: true }), + payload: convertObjectPropsToCamelCase(release, { deep: true }), }, ], ); @@ -106,7 +107,7 @@ describe('Release detail actions', () => { return testAction( actions.fetchRelease, undefined, - stateClone, + state, [], [{ type: 'requestRelease' }, { type: 'receiveReleaseError', payload: expect.anything() }], ); @@ -116,7 +117,7 @@ describe('Release detail actions', () => { describe('updateReleaseTitle', () => { it(`commits ${types.UPDATE_RELEASE_TITLE} with the updated release title`, () => { const newTitle = 'The new release title'; - return testAction(actions.updateReleaseTitle, newTitle, stateClone, [ + return testAction(actions.updateReleaseTitle, newTitle, state, [ { type: types.UPDATE_RELEASE_TITLE, payload: newTitle }, ]); }); @@ -125,7 +126,7 @@ describe('Release detail actions', () => { describe('updateReleaseNotes', () => { it(`commits ${types.UPDATE_RELEASE_NOTES} with the updated release notes`, () => { const newReleaseNotes = 'The new release notes'; - return testAction(actions.updateReleaseNotes, newReleaseNotes, stateClone, [ + return testAction(actions.updateReleaseNotes, newReleaseNotes, state, [ { type: types.UPDATE_RELEASE_NOTES, payload: newReleaseNotes }, ]); }); @@ -133,25 +134,40 @@ describe('Release detail actions', () => { describe('requestUpdateRelease', () => { it(`commits ${types.REQUEST_UPDATE_RELEASE}`, () => - testAction(actions.requestUpdateRelease, undefined, stateClone, [ + testAction(actions.requestUpdateRelease, undefined, state, [ { type: types.REQUEST_UPDATE_RELEASE }, ])); }); describe('receiveUpdateReleaseSuccess', () => { it(`commits ${types.RECEIVE_UPDATE_RELEASE_SUCCESS}`, () => - testAction( - actions.receiveUpdateReleaseSuccess, - undefined, - stateClone, - [{ type: types.RECEIVE_UPDATE_RELEASE_SUCCESS }], - [{ type: 'navigateToReleasesPage' }], - )); + testAction(actions.receiveUpdateReleaseSuccess, undefined, { ...state, featureFlags: {} }, [ + { type: types.RECEIVE_UPDATE_RELEASE_SUCCESS }, + ])); + + describe('when the 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', + }, + }, + }); + + actions.receiveUpdateReleaseSuccess({ commit: jest.fn(), state: updatedState, rootState }); + + expect(redirectTo).toHaveBeenCalledTimes(1); + expect(redirectTo).toHaveBeenCalledWith(updatedState.release._links.self); + }); + + describe('when the releaseShowPage feature flag is disabled', () => {}); }); describe('receiveUpdateReleaseError', () => { it(`commits ${types.RECEIVE_UPDATE_RELEASE_ERROR}`, () => - testAction(actions.receiveUpdateReleaseError, error, stateClone, [ + testAction(actions.receiveUpdateReleaseError, error, state, [ { type: types.RECEIVE_UPDATE_RELEASE_ERROR, payload: error }, ])); @@ -169,10 +185,10 @@ describe('Release detail actions', () => { let getReleaseUrl; beforeEach(() => { - stateClone.release = releaseClone; - stateClone.projectId = '18'; - stateClone.tagName = 'v1.3'; - getReleaseUrl = `/api/v4/projects/${stateClone.projectId}/releases/${stateClone.tagName}`; + state.release = release; + state.projectId = '18'; + state.tagName = 'v1.3'; + getReleaseUrl = `/api/v4/projects/${state.projectId}/releases/${state.tagName}`; }); it(`dispatches requestUpdateRelease and receiveUpdateReleaseSuccess`, () => { @@ -181,7 +197,7 @@ describe('Release detail actions', () => { return testAction( actions.updateRelease, undefined, - stateClone, + state, [], [{ type: 'requestUpdateRelease' }, { type: 'receiveUpdateReleaseSuccess' }], ); @@ -193,7 +209,7 @@ describe('Release detail actions', () => { return testAction( actions.updateRelease, undefined, - stateClone, + state, [], [ { type: 'requestUpdateRelease' }, @@ -202,16 +218,4 @@ describe('Release detail actions', () => { ); }); }); - - describe('navigateToReleasesPage', () => { - it(`calls redirectTo() with the URL to the releases page`, () => { - const releasesPagePath = 'path/to/releases/page'; - stateClone.releasesPagePath = releasesPagePath; - - actions.navigateToReleasesPage({ state: stateClone }); - - expect(redirectTo).toHaveBeenCalledTimes(1); - expect(redirectTo).toHaveBeenCalledWith(releasesPagePath); - }); - }); }); |