Commit 6958c41a authored by Nathan Friend's avatar Nathan Friend

Update redirect behavior of Edit Release buttons

This commit updates the redirect behavior of the buttons at the bottom
of the "Edit Release" page.
parent fdd6fc01
<script>
import { mapState, mapActions } from 'vuex';
import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui';
import { GlButton, GlLink, GlFormInput, GlFormGroup } from '@gitlab/ui';
import { escape as esc } from 'lodash';
import { __, sprintf } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
import { BACK_URL_PARAM } from '~/releases/constants';
import { getParameterByName } from '~/lib/utils/common_utils';
export default {
name: 'ReleaseEditApp',
......@@ -12,6 +14,7 @@ export default {
GlFormInput,
GlFormGroup,
GlButton,
GlLink,
MarkdownField,
},
directives: {
......@@ -74,6 +77,9 @@ export default {
this.updateReleaseNotes(notes);
},
},
cancelPath() {
return getParameterByName(BACK_URL_PARAM) || this.releasesPagePath;
},
},
created() {
this.fetchRelease();
......@@ -84,7 +90,6 @@ export default {
'updateRelease',
'updateReleaseTitle',
'updateReleaseNotes',
'navigateToReleasesPage',
]),
},
};
......@@ -157,15 +162,9 @@ export default {
>
{{ __('Save changes') }}
</gl-button>
<gl-button
class="js-cancel-button"
variant="default"
type="button"
:aria-label="__('Cancel')"
@click="navigateToReleasesPage()"
>
<gl-link :href="cancelPath" class="js-cancel-button btn btn-default">
{{ __('Cancel') }}
</gl-button>
</gl-link>
</div>
</form>
</div>
......
<script>
import { GlTooltipDirective, GlLink, GlBadge } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import { BACK_URL_PARAM } from '~/releases/constants';
import { setUrlParams } from '~/lib/utils/url_utility';
export default {
name: 'ReleaseBlockHeader',
......@@ -20,7 +22,15 @@ export default {
},
computed: {
editLink() {
return this.release._links?.editUrl;
if (this.release._links?.editUrl) {
const queryParams = {
[BACK_URL_PARAM]: window.location.href,
};
return setUrlParams(queryParams, this.release._links.editUrl);
}
return undefined;
},
selfLink() {
return this.release._links?.self;
......
/* eslint-disable import/prefer-default-export */
// This eslint-disable ^^^ can be removed when at least
// one more constant is added to this file. Currently
// constants.js files with only a single constant
// are flagged by this rule.
export const MAX_MILESTONES_TO_DISPLAY = 5;
export const BACK_URL_PARAM = 'back_url';
......@@ -6,7 +6,15 @@ import detailModule from './stores/modules/detail';
export default () => {
const el = document.getElementById('js-edit-release-page');
const store = createStore({ detail: detailModule });
const store = createStore({
modules: {
detail: detailModule,
},
featureFlags: {
releaseShowPage: Boolean(gon.features?.releaseShowPage),
},
});
store.dispatch('detail/setInitialState', el.dataset);
return new Vue({
......
......@@ -8,7 +8,11 @@ export default () => {
return new Vue({
el,
store: createStore({ list: listModule }),
store: createStore({
modules: {
list: listModule,
},
}),
render: h =>
h(ReleaseListApp, {
props: {
......
......@@ -6,7 +6,11 @@ import detailModule from './stores/modules/detail';
export default () => {
const el = document.getElementById('js-show-release-page');
const store = createStore({ detail: detailModule });
const store = createStore({
modules: {
detail: detailModule,
},
});
store.dispatch('detail/setInitialState', el.dataset);
return new Vue({
......
......@@ -3,4 +3,8 @@ import Vuex from 'vuex';
Vue.use(Vuex);
export default modules => new Vuex.Store({ modules });
export default ({ modules, featureFlags }) =>
new Vuex.Store({
modules,
state: { featureFlags },
});
......@@ -33,9 +33,11 @@ export const updateReleaseTitle = ({ commit }, title) => commit(types.UPDATE_REL
export const updateReleaseNotes = ({ commit }, notes) => commit(types.UPDATE_RELEASE_NOTES, notes);
export const requestUpdateRelease = ({ commit }) => commit(types.REQUEST_UPDATE_RELEASE);
export const receiveUpdateReleaseSuccess = ({ commit, dispatch }) => {
export const receiveUpdateReleaseSuccess = ({ commit, state, rootState }) => {
commit(types.RECEIVE_UPDATE_RELEASE_SUCCESS);
dispatch('navigateToReleasesPage');
redirectTo(
rootState.featureFlags.releaseShowPage ? state.release._links.self : state.releasesPagePath,
);
};
export const receiveUpdateReleaseError = ({ commit }, error) => {
commit(types.RECEIVE_UPDATE_RELEASE_ERROR, error);
......
......@@ -6,22 +6,27 @@ describe 'User edits Release', :js do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:release) { create(:release, project: project, name: 'The first release' ) }
let_it_be(:user) { create(:user) }
let(:show_feature_flag) { true }
before do
stub_feature_flags(release_show_page: show_feature_flag)
project.add_developer(user)
gitlab_sign_in(user)
visit edit_project_release_path(project, release)
wait_for_requests
end
def fill_out_form_and_click(button_to_click)
fill_in 'Release title', with: 'Updated Release title'
fill_in 'Release notes', with: 'Updated Release notes'
click_button button_to_click
click_link_or_button button_to_click
wait_for_requests
wait_for_all_requests
end
it 'renders the breadcrumbs' do
......@@ -42,31 +47,66 @@ describe 'User edits Release', :js do
expect(find_field('Release notes').value).to eq(release.description)
expect(page).to have_button('Save changes')
expect(page).to have_button('Cancel')
expect(page).to have_link('Cancel')
end
it 'redirects to the main Releases page without updating the Release when "Cancel" is clicked' do
it 'does not update the Release when "Cancel" is clicked' do
original_name = release.name
original_description = release.description
fill_out_form_and_click 'Cancel'
expect(current_path).to eq(project_releases_path(project))
release.reload
expect(release.name).to eq(original_name)
expect(release.description).to eq(original_description)
end
it 'updates the Release and redirects to the main Releases page when "Save changes" is clicked' do
it 'updates the Release when "Save changes" is clicked' do
fill_out_form_and_click 'Save changes'
expect(current_path).to eq(project_releases_path(project))
release.reload
expect(release.name).to eq('Updated Release title')
expect(release.description).to eq('Updated Release notes')
end
context 'when the release_show_page feature flag is disabled' do
let(:show_feature_flag) { false }
it 'redirects to the main Releases page when "Cancel" is clicked' do
fill_out_form_and_click 'Cancel'
expect(page).to have_current_path(project_releases_path(project))
end
it 'redirects to the main Releases page when "Save changes" is clicked' do
fill_out_form_and_click 'Save changes'
expect(page).to have_current_path(project_releases_path(project))
end
end
context 'when the release_show_page feature flag is enabled' do
it 'redirects to the previous page when "Cancel" is clicked when the url includes a back_url query parameter' do
back_path = project_releases_path(project, params: { page: 2 })
visit edit_project_release_path(project, release, params: { back_url: back_path })
fill_out_form_and_click 'Cancel'
expect(page).to have_current_path(back_path)
end
it 'redirects to the main Releases page when "Cancel" is clicked when the url does not include a back_url query parameter' do
fill_out_form_and_click 'Cancel'
expect(page).to have_current_path(project_releases_path(project))
end
it 'redirects to the dedicated Release page when "Save changes" is clicked' do
fill_out_form_and_click 'Save changes'
expect(page).to have_current_path(project_release_path(project, release))
end
end
end
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);
});
});
});
......@@ -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);
});
});
});
......@@ -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);
......
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);
});
});
});
......@@ -27,7 +27,7 @@ describe('Releases App ', () => {
};
beforeEach(() => {
store = createStore({ list: listModule });
store = createStore({ modules: { list: listModule } });
releasesPagination = _.range(21).map(index => ({
...convertObjectPropsToCamelCase(release, { deep: true }),
tagName: `${index}.00`,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment