Commit b370adbe authored by Martin Wortschack's avatar Martin Wortschack

Merge branch 'nfriend-add-edit-release-page' into 'master'

Add "Edit Release" page

See merge request gitlab-org/gitlab!18033
parents 920fe32e e03b1088
......@@ -36,6 +36,7 @@ const Api = {
branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch',
createBranchPath: '/api/:version/projects/:id/repository/branches',
releasesPath: '/api/:version/projects/:id/releases',
releasePath: '/api/:version/projects/:id/releases/:tag_name',
mergeRequestsPipeline: '/api/:version/projects/:id/merge_requests/:merge_request_iid/pipelines',
adminStatisticsPath: 'api/:version/application/statistics',
......@@ -391,6 +392,22 @@ const Api = {
return axios.get(url);
},
release(projectPath, tagName) {
const url = Api.buildUrl(this.releasePath)
.replace(':id', encodeURIComponent(projectPath))
.replace(':tag_name', encodeURIComponent(tagName));
return axios.get(url);
},
updateRelease(projectPath, tagName, release) {
const url = Api.buildUrl(this.releasePath)
.replace(':id', encodeURIComponent(projectPath))
.replace(':tag_name', encodeURIComponent(tagName));
return axios.put(url, release);
},
adminStatistics() {
const url = Api.buildUrl(this.adminStatisticsPath);
return axios.get(url);
......
import ZenMode from '~/zen_mode';
import initEditRelease from '~/releases/detail';
document.addEventListener('DOMContentLoaded', () => {
new ZenMode(); // eslint-disable-line no-new
initEditRelease();
});
<script>
import { mapState, mapActions } from 'vuex';
import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
export default {
name: 'ReleaseDetailApp',
components: {
GlFormInput,
GlFormGroup,
GlButton,
MarkdownField,
},
directives: {
autofocusonshow,
},
computed: {
...mapState([
'isFetchingRelease',
'fetchError',
'markdownDocsPath',
'markdownPreviewPath',
'releasesPagePath',
]),
showForm() {
return !this.isFetchingRelease && !this.fetchError;
},
subtitleText() {
return sprintf(
__(
'Releases are based on Git tags. We recommend naming tags that fit within semantic versioning, for example %{codeStart}v1.0%{codeEnd}, %{codeStart}v2.0-pre%{codeEnd}.',
),
{
codeStart: '<code>',
codeEnd: '</code>',
},
false,
);
},
tagName() {
return this.$store.state.release.tagName;
},
releaseTitle: {
get() {
return this.$store.state.release.name;
},
set(title) {
this.updateReleaseTitle(title);
},
},
releaseNotes: {
get() {
return this.$store.state.release.description;
},
set(notes) {
this.updateReleaseNotes(notes);
},
},
},
created() {
this.fetchRelease();
},
methods: {
...mapActions([
'fetchRelease',
'updateRelease',
'updateReleaseTitle',
'updateReleaseNotes',
'navigateToReleasesPage',
]),
},
};
</script>
<template>
<div class="d-flex flex-column">
<p class="pt-3 js-subtitle-text" v-html="subtitleText"></p>
<form v-if="showForm" @submit.prevent="updateRelease()">
<div class="row">
<gl-form-group class="col-md-6 col-lg-5 col-xl-4">
<label for="git-ref">{{ __('Tag name') }}</label>
<gl-form-input
id="git-ref"
v-model="tagName"
type="text"
class="form-control"
aria-describedby="tag-name-help"
disabled
/>
<div id="tag-name-help" class="form-text text-muted">
{{ __('Choose an existing tag, or create a new one') }}
</div>
</gl-form-group>
</div>
<gl-form-group>
<label for="release-title">{{ __('Release title') }}</label>
<gl-form-input
id="release-title"
ref="releaseTitleInput"
v-model="releaseTitle"
v-autofocusonshow
autofocus
type="text"
class="form-control"
/>
</gl-form-group>
<gl-form-group>
<label for="release-notes">{{ __('Release notes') }}</label>
<div class="bordered-box pr-3 pl-3">
<markdown-field
:can-attach-file="true"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:add-spacing-classes="false"
class="prepend-top-10 append-bottom-10"
>
<textarea
id="release-notes"
slot="textarea"
v-model="releaseNotes"
class="note-textarea js-gfm-input js-autosize markdown-area"
dir="auto"
data-supports-quick-actions="false"
:aria-label="__('Release notes')"
:placeholder="__('Write your release notes or drag your files here…')"
@keydown.meta.enter="updateRelease()"
@keydown.ctrl.enter="updateRelease()"
>
</textarea>
</markdown-field>
</div>
</gl-form-group>
<div class="d-flex pt-3">
<gl-button
class="mr-auto js-submit-button"
variant="success"
type="submit"
:aria-label="__('Save changes')"
>
{{ __('Save changes') }}
</gl-button>
<gl-button
class="js-cancel-button"
variant="default"
type="button"
:aria-label="__('Cancel')"
@click="navigateToReleasesPage()"
>
{{ __('Cancel') }}
</gl-button>
</div>
</form>
</div>
</template>
import Vue from 'vue';
import ReleaseDetailApp from './components/app.vue';
import createStore from './store';
export default () => {
const el = document.getElementById('js-edit-release-page');
const store = createStore(el.dataset);
store.dispatch('setInitialState', el.dataset);
return new Vue({
el,
store,
components: { ReleaseDetailApp },
render(createElement) {
return createElement('release-detail-app');
},
});
};
import * as types from './mutation_types';
import api from '~/api';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
export const setInitialState = ({ commit }, initialState) =>
commit(types.SET_INITIAL_STATE, initialState);
export const requestRelease = ({ commit }) => commit(types.REQUEST_RELEASE);
export const receiveReleaseSuccess = ({ commit }, data) =>
commit(types.RECEIVE_RELEASE_SUCCESS, data);
export const receiveReleaseError = ({ commit }, error) => {
commit(types.RECEIVE_RELEASE_ERROR, error);
createFlash(s__('Release|Something went wrong while getting the release details'));
};
export const fetchRelease = ({ dispatch, state }) => {
dispatch('requestRelease');
return api
.release(state.projectId, state.tagName)
.then(({ data: release }) => {
const camelCasedRelease = convertObjectPropsToCamelCase(release, { deep: true });
dispatch('receiveReleaseSuccess', camelCasedRelease);
})
.catch(error => {
dispatch('receiveReleaseError', error);
});
};
export const updateReleaseTitle = ({ commit }, title) => commit(types.UPDATE_RELEASE_TITLE, title);
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 }) => {
commit(types.RECEIVE_UPDATE_RELEASE_SUCCESS);
dispatch('navigateToReleasesPage');
};
export const receiveUpdateReleaseError = ({ commit }, error) => {
commit(types.RECEIVE_UPDATE_RELEASE_ERROR, error);
createFlash(s__('Release|Something went wrong while saving the release details'));
};
export const updateRelease = ({ dispatch, state }) => {
dispatch('requestUpdateRelease');
return api
.updateRelease(state.projectId, state.tagName, {
name: state.release.name,
description: state.release.description,
})
.then(() => dispatch('receiveUpdateReleaseSuccess'))
.catch(error => {
dispatch('receiveUpdateReleaseError', error);
});
};
export const navigateToReleasesPage = ({ state }) => {
redirectTo(state.releasesPagePath);
};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
export default () =>
new Vuex.Store({
actions,
mutations,
state,
});
export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
export const REQUEST_RELEASE = 'REQUEST_RELEASE';
export const RECEIVE_RELEASE_SUCCESS = 'RECEIVE_RELEASE_SUCCESS';
export const RECEIVE_RELEASE_ERROR = 'RECEIVE_RELEASE_ERROR';
export const UPDATE_RELEASE_TITLE = 'UPDATE_RELEASE_TITLE';
export const UPDATE_RELEASE_NOTES = 'UPDATE_RELEASE_NOTES';
export const REQUEST_UPDATE_RELEASE = 'REQUEST_UPDATE_RELEASE';
export const RECEIVE_UPDATE_RELEASE_SUCCESS = 'RECEIVE_UPDATE_RELEASE_SUCCESS';
export const RECEIVE_UPDATE_RELEASE_ERROR = 'RECEIVE_UPDATE_RELEASE_ERROR';
import * as types from './mutation_types';
export default {
[types.SET_INITIAL_STATE](state, initialState) {
Object.keys(state).forEach(key => {
state[key] = initialState[key];
});
},
[types.REQUEST_RELEASE](state) {
state.isFetchingRelease = true;
},
[types.RECEIVE_RELEASE_SUCCESS](state, data) {
state.fetchError = undefined;
state.isFetchingRelease = false;
state.release = data;
},
[types.RECEIVE_RELEASE_ERROR](state, error) {
state.fetchError = error;
state.isFetchingRelease = false;
state.release = undefined;
},
[types.UPDATE_RELEASE_TITLE](state, title) {
state.release.name = title;
},
[types.UPDATE_RELEASE_NOTES](state, notes) {
state.release.description = notes;
},
[types.REQUEST_UPDATE_RELEASE](state) {
state.isUpdatingRelease = true;
},
[types.RECEIVE_UPDATE_RELEASE_SUCCESS](state) {
state.updateError = undefined;
state.isUpdatingRelease = false;
},
[types.RECEIVE_UPDATE_RELEASE_ERROR](state, error) {
state.updateError = error;
state.isUpdatingRelease = false;
},
};
export default () => ({
projectId: null,
tagName: null,
releasesPagePath: null,
markdownDocsPath: null,
markdownPreviewPath: null,
release: null,
isFetchingRelease: false,
fetchError: null,
isUpdatingRelease: false,
updateError: null,
});
......@@ -19,4 +19,14 @@ module ReleasesHelper
documentation_path: help_page
}
end
def data_for_edit_release_page
{
project_id: @project.id,
tag_name: @release.tag,
markdown_preview_path: preview_markdown_path(@project),
markdown_docs_path: help_page_path('user/markdown'),
releases_page_path: project_releases_path(@project, anchor: @release.tag)
}
end
end
- page_title _('Edit Release')
#js-edit-release-page{ data: data_for_edit_release_page }
---
title: Add "Edit Release" page
merge_request: 18033
author:
type: added
......@@ -3066,6 +3066,9 @@ msgstr ""
msgid "Choose a type..."
msgstr ""
msgid "Choose an existing tag, or create a new one"
msgstr ""
msgid "Choose any color."
msgstr ""
......@@ -5787,6 +5790,9 @@ msgstr ""
msgid "Edit Pipeline Schedule %{id}"
msgstr ""
msgid "Edit Release"
msgstr ""
msgid "Edit Snippet"
msgstr ""
......@@ -13480,12 +13486,27 @@ msgstr ""
msgid "Release"
msgstr ""
msgid "Release notes"
msgstr ""
msgid "Release title"
msgstr ""
msgid "Releases"
msgstr ""
msgid "Releases are based on Git tags. We recommend naming tags that fit within semantic versioning, for example %{codeStart}v1.0%{codeEnd}, %{codeStart}v2.0-pre%{codeEnd}."
msgstr ""
msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
msgstr ""
msgid "Release|Something went wrong while getting the release details"
msgstr ""
msgid "Release|Something went wrong while saving the release details"
msgstr ""
msgid "Remember me"
msgstr ""
......@@ -15967,6 +15988,9 @@ msgstr ""
msgid "Tag list:"
msgstr ""
msgid "Tag name"
msgstr ""
msgid "Tag this commit."
msgstr ""
......@@ -18707,6 +18731,9 @@ msgstr ""
msgid "Write milestone description..."
msgstr ""
msgid "Write your release notes or drag your files here…"
msgstr ""
msgid "Wrong extern UID provided. Make sure Auth0 is configured correctly."
msgstr ""
......
import Vuex from 'vuex';
import { mount } from '@vue/test-utils';
import ReleaseDetailApp from '~/releases/detail/components/app';
import { release } from '../../mock_data';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
describe('Release detail component', () => {
let wrapper;
let releaseClone;
let actions;
beforeEach(() => {
gon.api_version = 'v4';
releaseClone = JSON.parse(JSON.stringify(convertObjectPropsToCamelCase(release)));
const state = {
release: releaseClone,
markdownDocsPath: 'path/to/markdown/docs',
};
actions = {
fetchRelease: jest.fn(),
updateRelease: jest.fn(),
navigateToReleasesPage: jest.fn(),
};
const store = new Vuex.Store({ actions, state });
wrapper = mount(ReleaseDetailApp, { store });
return wrapper.vm.$nextTick();
});
it('calls fetchRelease when the component is created', () => {
expect(actions.fetchRelease).toHaveBeenCalledTimes(1);
});
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 correct tag name in the "Tag name" field', () => {
expect(wrapper.find('#git-ref').element.value).toBe(releaseClone.tagName);
});
it('renders the correct release title in the "Release title" field', () => {
expect(wrapper.find('#release-title').element.value).toBe(releaseClone.name);
});
it('renders the release notes in the "Release notes" textarea', () => {
expect(wrapper.find('#release-notes').element.value).toBe(releaseClone.description);
});
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 navigateToReleasesPage when the "Cancel" button is clicked', () => {
wrapper.find('.js-cancel-button').vm.$emit('click');
expect(actions.navigateToReleasesPage).toHaveBeenCalledTimes(1);
});
});
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import * as actions from '~/releases/detail/store/actions';
import testAction from 'helpers/vuex_action_helper';
import * as types from '~/releases/detail/store/mutation_types';
import { release } from '../../mock_data';
import state from '~/releases/detail/store/state';
import createFlash from '~/flash';
import { redirectTo } from '~/lib/utils/url_utility';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
jest.mock('~/flash', () => jest.fn());
jest.mock('~/lib/utils/url_utility', () => ({
redirectTo: jest.fn(),
joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths,
}));
describe('Release detail actions', () => {
let stateClone;
let releaseClone;
let mock;
let error;
beforeEach(() => {
stateClone = state();
releaseClone = JSON.parse(JSON.stringify(release));
mock = new MockAdapter(axios);
gon.api_version = 'v4';
error = { message: 'An error occurred' };
createFlash.mockClear();
});
afterEach(() => {
mock.restore();
});
describe('setInitialState', () => {
it(`commits ${types.SET_INITIAL_STATE} with the provided object`, () => {
const initialState = {};
return testAction(actions.setInitialState, initialState, stateClone, [
{ type: types.SET_INITIAL_STATE, payload: initialState },
]);
});
});
describe('requestRelease', () => {
it(`commits ${types.REQUEST_RELEASE}`, () =>
testAction(actions.requestRelease, undefined, stateClone, [{ type: types.REQUEST_RELEASE }]));
});
describe('receiveReleaseSuccess', () => {
it(`commits ${types.RECEIVE_RELEASE_SUCCESS}`, () =>
testAction(actions.receiveReleaseSuccess, releaseClone, stateClone, [
{ type: types.RECEIVE_RELEASE_SUCCESS, payload: releaseClone },
]));
});
describe('receiveReleaseError', () => {
it(`commits ${types.RECEIVE_RELEASE_ERROR}`, () =>
testAction(actions.receiveReleaseError, error, stateClone, [
{ type: types.RECEIVE_RELEASE_ERROR, payload: error },
]));
it('shows a flash with an error message', () => {
actions.receiveReleaseError({ commit: jest.fn() }, error);
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createFlash).toHaveBeenCalledWith(
'Something went wrong while getting the release details',
);
});
});
describe('fetchRelease', () => {
let getReleaseUrl;
beforeEach(() => {
stateClone.projectId = '18';
stateClone.tagName = 'v1.3';
getReleaseUrl = `/api/v4/projects/${stateClone.projectId}/releases/${stateClone.tagName}`;
});
it(`dispatches requestRelease and receiveReleaseSuccess with the camel-case'd release object`, () => {
mock.onGet(getReleaseUrl).replyOnce(200, releaseClone);
return testAction(
actions.fetchRelease,
undefined,
stateClone,
[],
[
{ type: 'requestRelease' },
{
type: 'receiveReleaseSuccess',
payload: convertObjectPropsToCamelCase(releaseClone, { deep: true }),
},
],
);
});
it(`dispatches requestRelease and receiveReleaseError with an error object`, () => {
mock.onGet(getReleaseUrl).replyOnce(500);
return testAction(
actions.fetchRelease,
undefined,
stateClone,
[],
[{ type: 'requestRelease' }, { type: 'receiveReleaseError', payload: expect.anything() }],
);
});
});
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, [
{ 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, stateClone, [
{ type: types.UPDATE_RELEASE_NOTES, payload: newReleaseNotes },
]);
});
});
describe('requestUpdateRelease', () => {
it(`commits ${types.REQUEST_UPDATE_RELEASE}`, () =>
testAction(actions.requestUpdateRelease, undefined, stateClone, [
{ 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' }],
));
});
describe('receiveUpdateReleaseError', () => {
it(`commits ${types.RECEIVE_UPDATE_RELEASE_ERROR}`, () =>
testAction(actions.receiveUpdateReleaseError, error, stateClone, [
{ type: types.RECEIVE_UPDATE_RELEASE_ERROR, payload: error },
]));
it('shows a flash with an error message', () => {
actions.receiveUpdateReleaseError({ commit: jest.fn() }, error);
expect(createFlash).toHaveBeenCalledTimes(1);
expect(createFlash).toHaveBeenCalledWith(
'Something went wrong while saving the release details',
);
});
});
describe('updateRelease', () => {
let getReleaseUrl;
beforeEach(() => {
stateClone.release = releaseClone;
stateClone.projectId = '18';
stateClone.tagName = 'v1.3';
getReleaseUrl = `/api/v4/projects/${stateClone.projectId}/releases/${stateClone.tagName}`;
});
it(`dispatches requestUpdateRelease and receiveUpdateReleaseSuccess`, () => {
mock.onPut(getReleaseUrl).replyOnce(200);
return testAction(
actions.updateRelease,
undefined,
stateClone,
[],
[{ type: 'requestUpdateRelease' }, { type: 'receiveUpdateReleaseSuccess' }],
);
});
it(`dispatches requestUpdateRelease and receiveUpdateReleaseError with an error object`, () => {
mock.onPut(getReleaseUrl).replyOnce(500);
return testAction(
actions.updateRelease,
undefined,
stateClone,
[],
[
{ type: 'requestUpdateRelease' },
{ type: 'receiveUpdateReleaseError', payload: expect.anything() },
],
);
});
});
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);
});
});
});
/* eslint-disable jest/valid-describe */
/*
* ESLint disable directive ↑ can be removed once
* https://github.com/jest-community/eslint-plugin-jest/issues/203
* is resolved
*/
import state from '~/releases/detail/store/state';
import mutations from '~/releases/detail/store/mutations';
import * as types from '~/releases/detail/store/mutation_types';
import { release } from '../../mock_data';
describe('Release detail mutations', () => {
let stateClone;
let releaseClone;
beforeEach(() => {
stateClone = state();
releaseClone = JSON.parse(JSON.stringify(release));
});
describe(types.SET_INITIAL_STATE, () => {
it('populates the state with initial values', () => {
const initialState = {
projectId: '18',
tagName: 'v1.3',
releasesPagePath: 'path/to/releases/page',
markdownDocsPath: 'path/to/markdown/docs',
markdownPreviewPath: 'path/to/markdown/preview',
};
mutations[types.SET_INITIAL_STATE](stateClone, initialState);
expect(stateClone).toEqual(expect.objectContaining(initialState));
});
});
describe(types.REQUEST_RELEASE, () => {
it('set state.isFetchingRelease to true', () => {
mutations[types.REQUEST_RELEASE](stateClone);
expect(stateClone.isFetchingRelease).toEqual(true);
});
});
describe(types.RECEIVE_RELEASE_SUCCESS, () => {
it('handles a successful response from the server', () => {
mutations[types.RECEIVE_RELEASE_SUCCESS](stateClone, releaseClone);
expect(stateClone.fetchError).toEqual(undefined);
expect(stateClone.isFetchingRelease).toEqual(false);
expect(stateClone.release).toEqual(releaseClone);
});
});
describe(types.RECEIVE_RELEASE_ERROR, () => {
it('handles an unsuccessful response from the server', () => {
const error = { message: 'An error occurred!' };
mutations[types.RECEIVE_RELEASE_ERROR](stateClone, error);
expect(stateClone.isFetchingRelease).toEqual(false);
expect(stateClone.release).toBeUndefined();
expect(stateClone.fetchError).toEqual(error);
});
});
describe(types.UPDATE_RELEASE_TITLE, () => {
it("updates the release's title", () => {
stateClone.release = releaseClone;
const newTitle = 'The new release title';
mutations[types.UPDATE_RELEASE_TITLE](stateClone, newTitle);
expect(stateClone.release.name).toEqual(newTitle);
});
});
describe(types.UPDATE_RELEASE_NOTES, () => {
it("updates the release's notes", () => {
stateClone.release = releaseClone;
const newNotes = 'The new release notes';
mutations[types.UPDATE_RELEASE_NOTES](stateClone, newNotes);
expect(stateClone.release.description).toEqual(newNotes);
});
});
describe(types.REQUEST_UPDATE_RELEASE, () => {
it('set state.isUpdatingRelease to true', () => {
mutations[types.REQUEST_UPDATE_RELEASE](stateClone);
expect(stateClone.isUpdatingRelease).toEqual(true);
});
});
describe(types.RECEIVE_UPDATE_RELEASE_SUCCESS, () => {
it('handles a successful response from the server', () => {
mutations[types.RECEIVE_UPDATE_RELEASE_SUCCESS](stateClone, releaseClone);
expect(stateClone.updateError).toEqual(undefined);
expect(stateClone.isUpdatingRelease).toEqual(false);
});
});
describe(types.RECEIVE_UPDATE_RELEASE_ERROR, () => {
it('handles an unsuccessful response from the server', () => {
const error = { message: 'An error occurred!' };
mutations[types.RECEIVE_UPDATE_RELEASE_ERROR](stateClone, error);
expect(stateClone.isUpdatingRelease).toEqual(false);
expect(stateClone.updateError).toEqual(error);
});
});
});
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