Commit d6dc0201 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch 'nfriend-reuse-edit-release-for-new' into 'master'

Reuse the "Edit Release" form for release creation

See merge request gitlab-org/gitlab!36466
parents 9abe46ab 35f17fe0
...@@ -3,7 +3,6 @@ import { mapState, mapActions, mapGetters } from 'vuex'; ...@@ -3,7 +3,6 @@ import { mapState, mapActions, mapGetters } from 'vuex';
import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui'; import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
import { BACK_URL_PARAM } from '~/releases/constants'; import { BACK_URL_PARAM } from '~/releases/constants';
import { getParameterByName } from '~/lib/utils/common_utils'; import { getParameterByName } from '~/lib/utils/common_utils';
import AssetLinksForm from './asset_links_form.vue'; import AssetLinksForm from './asset_links_form.vue';
...@@ -22,9 +21,6 @@ export default { ...@@ -22,9 +21,6 @@ export default {
MilestoneCombobox, MilestoneCombobox,
TagField, TagField,
}, },
directives: {
autofocusonshow,
},
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
computed: { computed: {
...mapState('detail', [ ...mapState('detail', [
...@@ -40,9 +36,9 @@ export default { ...@@ -40,9 +36,9 @@ export default {
'manageMilestonesPath', 'manageMilestonesPath',
'projectId', 'projectId',
]), ]),
...mapGetters('detail', ['isValid']), ...mapGetters('detail', ['isValid', 'isExistingRelease']),
showForm() { showForm() {
return !this.isFetchingRelease && !this.fetchError; return Boolean(!this.isFetchingRelease && !this.fetchError && this.release);
}, },
subtitleText() { subtitleText() {
return sprintf( return sprintf(
...@@ -86,6 +82,9 @@ export default { ...@@ -86,6 +82,9 @@ export default {
showAssetLinksForm() { showAssetLinksForm() {
return this.glFeatures.releaseAssetLinkEditing; return this.glFeatures.releaseAssetLinkEditing;
}, },
saveButtonLabel() {
return this.isExistingRelease ? __('Save changes') : __('Create release');
},
isSaveChangesDisabled() { isSaveChangesDisabled() {
return this.isUpdatingRelease || !this.isValid; return this.isUpdatingRelease || !this.isValid;
}, },
...@@ -102,13 +101,17 @@ export default { ...@@ -102,13 +101,17 @@ export default {
]; ];
}, },
}, },
created() { mounted() {
this.fetchRelease(); // eslint-disable-next-line promise/catch-or-return
this.initializeRelease().then(() => {
// Focus the first non-disabled input element
this.$el.querySelector('input:enabled').focus();
});
}, },
methods: { methods: {
...mapActions('detail', [ ...mapActions('detail', [
'fetchRelease', 'initializeRelease',
'updateRelease', 'saveRelease',
'updateReleaseTitle', 'updateReleaseTitle',
'updateReleaseNotes', 'updateReleaseNotes',
'updateReleaseMilestones', 'updateReleaseMilestones',
...@@ -119,7 +122,7 @@ export default { ...@@ -119,7 +122,7 @@ export default {
<template> <template>
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<p class="pt-3 js-subtitle-text" v-html="subtitleText"></p> <p class="pt-3 js-subtitle-text" v-html="subtitleText"></p>
<form v-if="showForm" @submit.prevent="updateRelease()"> <form v-if="showForm" @submit.prevent="saveRelease()">
<tag-field /> <tag-field />
<gl-form-group> <gl-form-group>
<label for="release-title">{{ __('Release title') }}</label> <label for="release-title">{{ __('Release title') }}</label>
...@@ -127,8 +130,6 @@ export default { ...@@ -127,8 +130,6 @@ export default {
id="release-title" id="release-title"
ref="releaseTitleInput" ref="releaseTitleInput"
v-model="releaseTitle" v-model="releaseTitle"
v-autofocusonshow
autofocus
type="text" type="text"
class="form-control" class="form-control"
/> />
...@@ -162,8 +163,8 @@ export default { ...@@ -162,8 +163,8 @@ export default {
data-supports-quick-actions="false" data-supports-quick-actions="false"
:aria-label="__('Release notes')" :aria-label="__('Release notes')"
:placeholder="__('Write your release notes or drag your files here…')" :placeholder="__('Write your release notes or drag your files here…')"
@keydown.meta.enter="updateRelease()" @keydown.meta.enter="saveRelease()"
@keydown.ctrl.enter="updateRelease()" @keydown.ctrl.enter="saveRelease()"
></textarea> ></textarea>
</template> </template>
</markdown-field> </markdown-field>
...@@ -178,10 +179,11 @@ export default { ...@@ -178,10 +179,11 @@ export default {
category="primary" category="primary"
variant="success" variant="success"
type="submit" type="submit"
:aria-label="__('Save changes')"
:disabled="isSaveChangesDisabled" :disabled="isSaveChangesDisabled"
>{{ __('Save changes') }}</gl-button data-testid="submit-button"
> >
{{ saveButtonLabel }}
</gl-button>
<gl-button :href="cancelPath" class="js-cancel-button">{{ __('Cancel') }}</gl-button> <gl-button :href="cancelPath" class="js-cancel-button">{{ __('Cancel') }}</gl-button>
</div> </div>
</form> </form>
......
...@@ -3,76 +3,114 @@ import api from '~/api'; ...@@ -3,76 +3,114 @@ import api from '~/api';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import { import { releaseToApiJson, apiJsonToRelease } from '~/releases/util';
convertObjectPropsToCamelCase,
convertObjectPropsToSnakeCase, export const initializeRelease = ({ commit, dispatch, getters }) => {
} from '~/lib/utils/common_utils'; if (getters.isExistingRelease) {
// When editing an existing release,
export const requestRelease = ({ commit }) => commit(types.REQUEST_RELEASE); // fetch the release object from the API
export const receiveReleaseSuccess = ({ commit }, data) => return dispatch('fetchRelease');
commit(types.RECEIVE_RELEASE_SUCCESS, data); }
export const receiveReleaseError = ({ commit }, error) => {
commit(types.RECEIVE_RELEASE_ERROR, error); // When creating a new release, initialize the
createFlash(s__('Release|Something went wrong while getting the release details')); // store with an empty release object
commit(types.INITIALIZE_EMPTY_RELEASE);
return Promise.resolve();
}; };
export const fetchRelease = ({ dispatch, state }) => { export const fetchRelease = ({ commit, state }) => {
dispatch('requestRelease'); commit(types.REQUEST_RELEASE);
return api return api
.release(state.projectId, state.tagName) .release(state.projectId, state.tagName)
.then(({ data }) => { .then(({ data }) => {
const release = { commit(types.RECEIVE_RELEASE_SUCCESS, apiJsonToRelease(data));
...data,
milestones: data.milestones || [],
};
dispatch('receiveReleaseSuccess', convertObjectPropsToCamelCase(release, { deep: true }));
}) })
.catch(error => { .catch(error => {
dispatch('receiveReleaseError', error); commit(types.RECEIVE_RELEASE_ERROR, error);
createFlash(s__('Release|Something went wrong while getting the release details'));
}); });
}; };
export const updateReleaseTagName = ({ commit }, tagName) => export const updateReleaseTagName = ({ commit }, tagName) =>
commit(types.UPDATE_RELEASE_TAG_NAME, tagName); commit(types.UPDATE_RELEASE_TAG_NAME, tagName);
export const updateCreateFrom = ({ commit }, createFrom) => export const updateCreateFrom = ({ commit }, createFrom) =>
commit(types.UPDATE_CREATE_FROM, createFrom); commit(types.UPDATE_CREATE_FROM, createFrom);
export const updateReleaseTitle = ({ commit }, title) => commit(types.UPDATE_RELEASE_TITLE, title); export const updateReleaseTitle = ({ commit }, title) => commit(types.UPDATE_RELEASE_TITLE, title);
export const updateReleaseNotes = ({ commit }, notes) => commit(types.UPDATE_RELEASE_NOTES, notes); export const updateReleaseNotes = ({ commit }, notes) => commit(types.UPDATE_RELEASE_NOTES, notes);
export const updateReleaseMilestones = ({ commit }, milestones) => export const updateReleaseMilestones = ({ commit }, milestones) =>
commit(types.UPDATE_RELEASE_MILESTONES, milestones); commit(types.UPDATE_RELEASE_MILESTONES, milestones);
export const requestUpdateRelease = ({ commit }) => commit(types.REQUEST_UPDATE_RELEASE); export const addEmptyAssetLink = ({ commit }) => {
export const receiveUpdateReleaseSuccess = ({ commit, state, rootState }) => { commit(types.ADD_EMPTY_ASSET_LINK);
commit(types.RECEIVE_UPDATE_RELEASE_SUCCESS);
redirectTo(
rootState.featureFlags.releaseShowPage ? state.release._links.self : state.releasesPagePath,
);
}; };
export const receiveUpdateReleaseError = ({ commit }, error) => {
commit(types.RECEIVE_UPDATE_RELEASE_ERROR, error); export const updateAssetLinkUrl = ({ commit }, { linkIdToUpdate, newUrl }) => {
createFlash(s__('Release|Something went wrong while saving the release details')); commit(types.UPDATE_ASSET_LINK_URL, { linkIdToUpdate, newUrl });
};
export const updateAssetLinkName = ({ commit }, { linkIdToUpdate, newName }) => {
commit(types.UPDATE_ASSET_LINK_NAME, { linkIdToUpdate, newName });
};
export const updateAssetLinkType = ({ commit }, { linkIdToUpdate, newType }) => {
commit(types.UPDATE_ASSET_LINK_TYPE, { linkIdToUpdate, newType });
};
export const removeAssetLink = ({ commit }, linkIdToRemove) => {
commit(types.REMOVE_ASSET_LINK, linkIdToRemove);
}; };
export const updateRelease = ({ dispatch, state, getters }) => { export const receiveSaveReleaseSuccess = ({ commit, state, rootState }, release) => {
dispatch('requestUpdateRelease'); commit(types.RECEIVE_SAVE_RELEASE_SUCCESS);
redirectTo(rootState.featureFlags.releaseShowPage ? release._links.self : state.releasesPagePath);
};
const { release } = state; export const saveRelease = ({ commit, dispatch, getters }) => {
const milestones = release.milestones ? release.milestones.map(milestone => milestone.title) : []; commit(types.REQUEST_SAVE_RELEASE);
const updatedRelease = convertObjectPropsToSnakeCase( dispatch(getters.isExistingRelease ? 'updateRelease' : 'createRelease');
};
export const createRelease = ({ commit, dispatch, state, getters }) => {
const apiJson = releaseToApiJson(
{ {
name: release.name, ...state.release,
description: release.description, assets: {
milestones, links: getters.releaseLinksToCreate,
},
}, },
{ deep: true }, state.createFrom,
); );
return api
.createRelease(state.projectId, apiJson)
.then(({ data }) => {
dispatch('receiveSaveReleaseSuccess', apiJsonToRelease(data));
})
.catch(error => {
commit(types.RECEIVE_SAVE_RELEASE_ERROR, error);
createFlash(s__('Release|Something went wrong while creating a new release'));
});
};
export const updateRelease = ({ commit, dispatch, state, getters }) => {
const apiJson = releaseToApiJson({
...state.release,
assets: {
links: getters.releaseLinksToCreate,
},
});
let updatedRelease = null;
return ( return (
api api
.updateRelease(state.projectId, state.tagName, updatedRelease) .updateRelease(state.projectId, state.tagName, apiJson)
/** /**
* Currently, we delete all existing links and then * Currently, we delete all existing links and then
...@@ -90,54 +128,31 @@ export const updateRelease = ({ dispatch, state, getters }) => { ...@@ -90,54 +128,31 @@ export const updateRelease = ({ dispatch, state, getters }) => {
* https://gitlab.com/gitlab-org/gitlab/-/issues/208702 * https://gitlab.com/gitlab-org/gitlab/-/issues/208702
* is closed. * is closed.
*/ */
.then(({ data }) => {
// Save this response since we need it later in the Promise chain
updatedRelease = data;
.then(() => {
// Delete all links currently associated with this Release // Delete all links currently associated with this Release
return Promise.all( return Promise.all(
getters.releaseLinksToDelete.map(l => getters.releaseLinksToDelete.map(l =>
api.deleteReleaseLink(state.projectId, release.tagName, l.id), api.deleteReleaseLink(state.projectId, state.release.tagName, l.id),
), ),
); );
}) })
.then(() => { .then(() => {
// Create a new link for each link in the form // Create a new link for each link in the form
return Promise.all( return Promise.all(
getters.releaseLinksToCreate.map(l => apiJson.assets.links.map(l =>
api.createReleaseLink( api.createReleaseLink(state.projectId, state.release.tagName, l),
state.projectId,
release.tagName,
convertObjectPropsToSnakeCase(l, { deep: true }),
),
), ),
); );
}) })
.then(() => dispatch('receiveUpdateReleaseSuccess')) .then(() => {
dispatch('receiveSaveReleaseSuccess', apiJsonToRelease(updatedRelease));
})
.catch(error => { .catch(error => {
dispatch('receiveUpdateReleaseError', error); commit(types.RECEIVE_SAVE_RELEASE_ERROR, error);
createFlash(s__('Release|Something went wrong while saving the release details'));
}) })
); );
}; };
export const navigateToReleasesPage = ({ state }) => {
redirectTo(state.releasesPagePath);
};
export const addEmptyAssetLink = ({ commit }) => {
commit(types.ADD_EMPTY_ASSET_LINK);
};
export const updateAssetLinkUrl = ({ commit }, { linkIdToUpdate, newUrl }) => {
commit(types.UPDATE_ASSET_LINK_URL, { linkIdToUpdate, newUrl });
};
export const updateAssetLinkName = ({ commit }, { linkIdToUpdate, newName }) => {
commit(types.UPDATE_ASSET_LINK_NAME, { linkIdToUpdate, newName });
};
export const updateAssetLinkType = ({ commit }, { linkIdToUpdate, newType }) => {
commit(types.UPDATE_ASSET_LINK_TYPE, { linkIdToUpdate, newType });
};
export const removeAssetLink = ({ commit }, linkIdToRemove) => {
commit(types.REMOVE_ASSET_LINK, linkIdToRemove);
};
...@@ -6,7 +6,7 @@ import { hasContent } from '~/lib/utils/text_utility'; ...@@ -6,7 +6,7 @@ import { hasContent } from '~/lib/utils/text_utility';
* `false` if the app is creating a new release. * `false` if the app is creating a new release.
*/ */
export const isExistingRelease = state => { export const isExistingRelease = state => {
return Boolean(state.originalRelease); return Boolean(state.tagName);
}; };
/** /**
......
export const INITIALIZE_EMPTY_RELEASE = 'INITIALIZE_EMPTY_RELEASE';
export const REQUEST_RELEASE = 'REQUEST_RELEASE'; export const REQUEST_RELEASE = 'REQUEST_RELEASE';
export const RECEIVE_RELEASE_SUCCESS = 'RECEIVE_RELEASE_SUCCESS'; export const RECEIVE_RELEASE_SUCCESS = 'RECEIVE_RELEASE_SUCCESS';
export const RECEIVE_RELEASE_ERROR = 'RECEIVE_RELEASE_ERROR'; export const RECEIVE_RELEASE_ERROR = 'RECEIVE_RELEASE_ERROR';
...@@ -8,9 +10,9 @@ export const UPDATE_RELEASE_TITLE = 'UPDATE_RELEASE_TITLE'; ...@@ -8,9 +10,9 @@ export const UPDATE_RELEASE_TITLE = 'UPDATE_RELEASE_TITLE';
export const UPDATE_RELEASE_NOTES = 'UPDATE_RELEASE_NOTES'; export const UPDATE_RELEASE_NOTES = 'UPDATE_RELEASE_NOTES';
export const UPDATE_RELEASE_MILESTONES = 'UPDATE_RELEASE_MILESTONES'; export const UPDATE_RELEASE_MILESTONES = 'UPDATE_RELEASE_MILESTONES';
export const REQUEST_UPDATE_RELEASE = 'REQUEST_UPDATE_RELEASE'; export const REQUEST_SAVE_RELEASE = 'REQUEST_SAVE_RELEASE';
export const RECEIVE_UPDATE_RELEASE_SUCCESS = 'RECEIVE_UPDATE_RELEASE_SUCCESS'; export const RECEIVE_SAVE_RELEASE_SUCCESS = 'RECEIVE_SAVE_RELEASE_SUCCESS';
export const RECEIVE_UPDATE_RELEASE_ERROR = 'RECEIVE_UPDATE_RELEASE_ERROR'; export const RECEIVE_SAVE_RELEASE_ERROR = 'RECEIVE_SAVE_RELEASE_ERROR';
export const ADD_EMPTY_ASSET_LINK = 'ADD_EMPTY_ASSET_LINK'; export const ADD_EMPTY_ASSET_LINK = 'ADD_EMPTY_ASSET_LINK';
export const UPDATE_ASSET_LINK_URL = 'UPDATE_ASSET_LINK_URL'; export const UPDATE_ASSET_LINK_URL = 'UPDATE_ASSET_LINK_URL';
......
...@@ -7,6 +7,18 @@ const findReleaseLink = (release, id) => { ...@@ -7,6 +7,18 @@ const findReleaseLink = (release, id) => {
}; };
export default { export default {
[types.INITIALIZE_EMPTY_RELEASE](state) {
state.release = {
tagName: null,
name: '',
description: '',
milestones: [],
assets: {
links: [],
},
};
},
[types.REQUEST_RELEASE](state) { [types.REQUEST_RELEASE](state) {
state.isFetchingRelease = true; state.isFetchingRelease = true;
}, },
...@@ -39,14 +51,14 @@ export default { ...@@ -39,14 +51,14 @@ export default {
state.release.milestones = milestones; state.release.milestones = milestones;
}, },
[types.REQUEST_UPDATE_RELEASE](state) { [types.REQUEST_SAVE_RELEASE](state) {
state.isUpdatingRelease = true; state.isUpdatingRelease = true;
}, },
[types.RECEIVE_UPDATE_RELEASE_SUCCESS](state) { [types.RECEIVE_SAVE_RELEASE_SUCCESS](state) {
state.updateError = undefined; state.updateError = undefined;
state.isUpdatingRelease = false; state.isUpdatingRelease = false;
}, },
[types.RECEIVE_UPDATE_RELEASE_ERROR](state, error) { [types.RECEIVE_SAVE_RELEASE_ERROR](state, error) {
state.updateError = error; state.updateError = error;
state.isUpdatingRelease = false; state.isUpdatingRelease = false;
}, },
......
import {
convertObjectPropsToCamelCase,
convertObjectPropsToSnakeCase,
} from '~/lib/utils/common_utils';
/**
* Converts a release object into a JSON object that can sent to the public
* API to create or update a release.
* @param {Object} release The release object to convert
* @param {string} createFrom The ref to create a new tag from, if necessary
*/
export const releaseToApiJson = (release, createFrom = null) => {
const milestones = release.milestones ? release.milestones.map(milestone => milestone.title) : [];
return convertObjectPropsToSnakeCase(
{
tagName: release.tagName,
ref: createFrom,
name: release.name,
description: release.description,
milestones,
assets: release.assets,
},
{ deep: true },
);
};
/**
* Converts a JSON release object returned by the Release API
* into the structure this Vue application can work with.
* @param {Object} json The JSON object received from the release API
*/
export const apiJsonToRelease = json => {
const release = convertObjectPropsToCamelCase(json, { deep: true });
release.milestones = release.milestones || [];
return release;
};
...@@ -7093,6 +7093,9 @@ msgstr "" ...@@ -7093,6 +7093,9 @@ msgstr ""
msgid "Create project label" msgid "Create project label"
msgstr "" msgstr ""
msgid "Create release"
msgstr ""
msgid "Create requirement" msgid "Create requirement"
msgstr "" msgstr ""
...@@ -20099,6 +20102,9 @@ msgstr "" ...@@ -20099,6 +20102,9 @@ msgstr ""
msgid "Releases|New Release" msgid "Releases|New Release"
msgstr "" msgstr ""
msgid "Release|Something went wrong while creating a new release"
msgstr ""
msgid "Release|Something went wrong while getting the release details" msgid "Release|Something went wrong while getting the release details"
msgstr "" msgstr ""
......
...@@ -27,8 +27,8 @@ describe('Release edit/new component', () => { ...@@ -27,8 +27,8 @@ describe('Release edit/new component', () => {
}; };
actions = { actions = {
fetchRelease: jest.fn(), initializeRelease: jest.fn(),
updateRelease: jest.fn(), saveRelease: jest.fn(),
addEmptyAssetLink: jest.fn(), addEmptyAssetLink: jest.fn(),
}; };
...@@ -64,6 +64,8 @@ describe('Release edit/new component', () => { ...@@ -64,6 +64,8 @@ describe('Release edit/new component', () => {
glFeatures: featureFlags, glFeatures: featureFlags,
}, },
}); });
wrapper.element.querySelectorAll('input').forEach(input => jest.spyOn(input, 'focus'));
}; };
beforeEach(() => { beforeEach(() => {
...@@ -87,8 +89,18 @@ describe('Release edit/new component', () => { ...@@ -87,8 +89,18 @@ describe('Release edit/new component', () => {
factory(); factory();
}); });
it('calls fetchRelease when the component is created', () => { it('calls initializeRelease when the component is created', () => {
expect(actions.fetchRelease).toHaveBeenCalledTimes(1); expect(actions.initializeRelease).toHaveBeenCalledTimes(1);
});
it('focuses the first non-disabled input element once the page is shown', () => {
const firstEnabledInput = wrapper.element.querySelector('input:enabled');
const allInputs = wrapper.element.querySelectorAll('input');
allInputs.forEach(input => {
const expectedFocusCalls = input === firstEnabledInput ? 1 : 0;
expect(input.focus).toHaveBeenCalledTimes(expectedFocusCalls);
});
}); });
it('renders the description text at the top of the page', () => { it('renders the description text at the top of the page', () => {
...@@ -109,9 +121,9 @@ describe('Release edit/new component', () => { ...@@ -109,9 +121,9 @@ describe('Release edit/new component', () => {
expect(findSubmitButton().attributes('type')).toBe('submit'); expect(findSubmitButton().attributes('type')).toBe('submit');
}); });
it('calls updateRelease when the form is submitted', () => { it('calls saveRelease when the form is submitted', () => {
wrapper.find('form').trigger('submit'); wrapper.find('form').trigger('submit');
expect(actions.updateRelease).toHaveBeenCalledTimes(1); expect(actions.saveRelease).toHaveBeenCalledTimes(1);
}); });
}); });
...@@ -143,6 +155,34 @@ describe('Release edit/new component', () => { ...@@ -143,6 +155,34 @@ describe('Release edit/new component', () => {
}); });
}); });
describe('when creating a new release', () => {
beforeEach(() => {
factory({
store: {
modules: {
detail: {
getters: {
isExistingRelease: () => false,
},
},
},
},
});
});
it('renders the submit button with the text "Create release"', () => {
expect(findSubmitButton().text()).toBe('Create release');
});
});
describe('when editing an existing release', () => {
beforeEach(factory);
it('renders the submit button with the text "Save changes"', () => {
expect(findSubmitButton().text()).toBe('Save changes');
});
});
describe('asset links form', () => { describe('asset links form', () => {
const findAssetLinksForm = () => wrapper.find(AssetLinksForm); const findAssetLinksForm = () => wrapper.find(AssetLinksForm);
......
...@@ -9,14 +9,14 @@ describe('releases/components/tag_field', () => { ...@@ -9,14 +9,14 @@ describe('releases/components/tag_field', () => {
let store; let store;
let wrapper; let wrapper;
const createComponent = ({ originalRelease }) => { const createComponent = ({ tagName }) => {
store = createStore({ store = createStore({
modules: { modules: {
detail: createDetailModule({}), detail: createDetailModule({}),
}, },
}); });
store.state.detail.originalRelease = originalRelease; store.state.detail.tagName = tagName;
wrapper = shallowMount(TagField, { store }); wrapper = shallowMount(TagField, { store });
}; };
...@@ -31,8 +31,7 @@ describe('releases/components/tag_field', () => { ...@@ -31,8 +31,7 @@ describe('releases/components/tag_field', () => {
describe('when an existing release is being edited', () => { describe('when an existing release is being edited', () => {
beforeEach(() => { beforeEach(() => {
const originalRelease = { name: 'Version 1.0' }; createComponent({ tagName: 'v1.0' });
createComponent({ originalRelease });
}); });
it('renders the TagFieldExisting component', () => { it('renders the TagFieldExisting component', () => {
...@@ -46,7 +45,7 @@ describe('releases/components/tag_field', () => { ...@@ -46,7 +45,7 @@ describe('releases/components/tag_field', () => {
describe('when a new release is being created', () => { describe('when a new release is being created', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ originalRelease: null }); createComponent({ tagName: null });
}); });
it('renders the TagFieldNew component', () => { it('renders the TagFieldNew component', () => {
......
...@@ -3,13 +3,13 @@ import * as getters from '~/releases/stores/modules/detail/getters'; ...@@ -3,13 +3,13 @@ import * as getters from '~/releases/stores/modules/detail/getters';
describe('Release detail getters', () => { describe('Release detail getters', () => {
describe('isExistingRelease', () => { describe('isExistingRelease', () => {
it('returns true if the release is an existing release that already exists in the database', () => { it('returns true if the release is an existing release that already exists in the database', () => {
const state = { originalRelease: { name: 'The first release' } }; const state = { tagName: 'test-tag-name' };
expect(getters.isExistingRelease(state)).toBe(true); expect(getters.isExistingRelease(state)).toBe(true);
}); });
it('returns false if the release is a new release that has not yet been saved to the database', () => { it('returns false if the release is a new release that has not yet been saved to the database', () => {
const state = { originalRelease: null }; const state = { tagName: null };
expect(getters.isExistingRelease(state)).toBe(false); expect(getters.isExistingRelease(state)).toBe(false);
}); });
......
...@@ -21,6 +21,22 @@ describe('Release detail mutations', () => { ...@@ -21,6 +21,22 @@ describe('Release detail mutations', () => {
release = convertObjectPropsToCamelCase(originalRelease); release = convertObjectPropsToCamelCase(originalRelease);
}); });
describe(`${types.INITIALIZE_EMPTY_RELEASE}`, () => {
it('set state.release to an empty release object', () => {
mutations[types.INITIALIZE_EMPTY_RELEASE](state);
expect(state.release).toEqual({
tagName: null,
name: '',
description: '',
milestones: [],
assets: {
links: [],
},
});
});
});
describe(`${types.REQUEST_RELEASE}`, () => { describe(`${types.REQUEST_RELEASE}`, () => {
it('set state.isFetchingRelease to true', () => { it('set state.isFetchingRelease to true', () => {
mutations[types.REQUEST_RELEASE](state); mutations[types.REQUEST_RELEASE](state);
...@@ -96,17 +112,17 @@ describe('Release detail mutations', () => { ...@@ -96,17 +112,17 @@ describe('Release detail mutations', () => {
}); });
}); });
describe(`${types.REQUEST_UPDATE_RELEASE}`, () => { describe(`${types.REQUEST_SAVE_RELEASE}`, () => {
it('set state.isUpdatingRelease to true', () => { it('set state.isUpdatingRelease to true', () => {
mutations[types.REQUEST_UPDATE_RELEASE](state); mutations[types.REQUEST_SAVE_RELEASE](state);
expect(state.isUpdatingRelease).toBe(true); expect(state.isUpdatingRelease).toBe(true);
}); });
}); });
describe(`${types.RECEIVE_UPDATE_RELEASE_SUCCESS}`, () => { describe(`${types.RECEIVE_SAVE_RELEASE_SUCCESS}`, () => {
it('handles a successful response from the server', () => { it('handles a successful response from the server', () => {
mutations[types.RECEIVE_UPDATE_RELEASE_SUCCESS](state, release); mutations[types.RECEIVE_SAVE_RELEASE_SUCCESS](state, release);
expect(state.updateError).toBeUndefined(); expect(state.updateError).toBeUndefined();
...@@ -114,10 +130,10 @@ describe('Release detail mutations', () => { ...@@ -114,10 +130,10 @@ describe('Release detail mutations', () => {
}); });
}); });
describe(`${types.RECEIVE_UPDATE_RELEASE_ERROR}`, () => { describe(`${types.RECEIVE_SAVE_RELEASE_ERROR}`, () => {
it('handles an unsuccessful response from the server', () => { it('handles an unsuccessful response from the server', () => {
const error = { message: 'An error occurred!' }; const error = { message: 'An error occurred!' };
mutations[types.RECEIVE_UPDATE_RELEASE_ERROR](state, error); mutations[types.RECEIVE_SAVE_RELEASE_ERROR](state, error);
expect(state.isUpdatingRelease).toBe(false); expect(state.isUpdatingRelease).toBe(false);
......
import { releaseToApiJson, apiJsonToRelease } from '~/releases/util';
describe('releases/util.js', () => {
describe('releaseToApiJson', () => {
it('converts a release JavaScript object into JSON that the Release API can accept', () => {
const release = {
tagName: 'tag-name',
name: 'Release name',
description: 'Release description',
milestones: [{ id: 1, title: '13.2' }, { id: 2, title: '13.3' }],
assets: {
links: [{ url: 'https://gitlab.example.com/link', linkType: 'other' }],
},
};
const expectedJson = {
tag_name: 'tag-name',
ref: null,
name: 'Release name',
description: 'Release description',
milestones: ['13.2', '13.3'],
assets: {
links: [{ url: 'https://gitlab.example.com/link', link_type: 'other' }],
},
};
expect(releaseToApiJson(release)).toEqual(expectedJson);
});
describe('when createFrom is provided', () => {
it('adds the provided createFrom ref to the JSON as a "ref" property', () => {
const createFrom = 'main';
const release = {};
const expectedJson = {
ref: createFrom,
};
expect(releaseToApiJson(release, createFrom)).toMatchObject(expectedJson);
});
});
describe('when release.milestones is falsy', () => {
it('includes a "milestone" property in the returned result as an empty array', () => {
const release = {};
const expectedJson = {
milestones: [],
};
expect(releaseToApiJson(release)).toMatchObject(expectedJson);
});
});
});
describe('apiJsonToRelease', () => {
it('converts JSON received from the Release API into an object usable by the Vue application', () => {
const json = {
tag_name: 'tag-name',
assets: {
links: [
{
link_type: 'other',
},
],
},
};
const expectedRelease = {
tagName: 'tag-name',
assets: {
links: [
{
linkType: 'other',
},
],
},
milestones: [],
};
expect(apiJsonToRelease(json)).toEqual(expectedRelease);
});
});
});
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