Commit a146987c authored by Nathan Friend's avatar Nathan Friend Committed by Paul Slaughter

Refactor release tag name field into sub-component

Extracts the "Tag name" field on the "Edit Release" page into its own
sub-component.
parent b68227ad
<script> <script>
import { mapState, mapActions, mapGetters } from 'vuex'; import { mapState, mapActions, mapGetters } from 'vuex';
import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui'; import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui';
import { escape } from 'lodash';
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 autofocusonshow from '~/vue_shared/directives/autofocusonshow';
...@@ -10,6 +9,7 @@ import { getParameterByName } from '~/lib/utils/common_utils'; ...@@ -10,6 +9,7 @@ import { getParameterByName } from '~/lib/utils/common_utils';
import AssetLinksForm from './asset_links_form.vue'; import AssetLinksForm from './asset_links_form.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import MilestoneCombobox from '~/milestones/project_milestone_combobox.vue'; import MilestoneCombobox from '~/milestones/project_milestone_combobox.vue';
import TagField from './tag_field.vue';
export default { export default {
name: 'ReleaseEditNewApp', name: 'ReleaseEditNewApp',
...@@ -20,6 +20,7 @@ export default { ...@@ -20,6 +20,7 @@ export default {
MarkdownField, MarkdownField,
AssetLinksForm, AssetLinksForm,
MilestoneCombobox, MilestoneCombobox,
TagField,
}, },
directives: { directives: {
autofocusonshow, autofocusonshow,
...@@ -55,23 +56,6 @@ export default { ...@@ -55,23 +56,6 @@ export default {
false, false,
); );
}, },
tagName() {
return this.$store.state.detail.release.tagName;
},
tagNameHintText() {
return sprintf(
__(
'Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}',
),
{
linkStart: `<a href="${escape(
this.updateReleaseApiDocsPath,
)}" target="_blank" rel="noopener noreferrer">`,
linkEnd: '</a>',
},
false,
);
},
releaseTitle: { releaseTitle: {
get() { get() {
return this.$store.state.detail.release.name; return this.$store.state.detail.release.name;
...@@ -136,22 +120,7 @@ export default { ...@@ -136,22 +120,7 @@ export default {
<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="updateRelease()">
<gl-form-group> <tag-field />
<div class="row">
<div 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>
</div>
<div id="tag-name-help" class="form-text text-muted" v-html="tagNameHintText"></div>
</gl-form-group>
<gl-form-group> <gl-form-group>
<label for="release-title">{{ __('Release title') }}</label> <label for="release-title">{{ __('Release title') }}</label>
<gl-form-input <gl-form-input
......
<script>
import { mapGetters } from 'vuex';
import TagFieldExisting from './tag_field_existing.vue';
import TagFieldNew from './tag_field_new.vue';
export default {
components: {
TagFieldExisting,
TagFieldNew,
},
computed: {
...mapGetters('detail', ['isExistingRelease']),
},
};
</script>
<template>
<tag-field-existing v-if="isExistingRelease" />
<tag-field-new v-else />
</template>
<script>
import { mapState } from 'vuex';
import { uniqueId } from 'lodash';
import { GlFormGroup, GlFormInput, GlLink, GlSprintf } from '@gitlab/ui';
export default {
name: 'TagFieldExisting',
components: { GlFormGroup, GlFormInput, GlSprintf, GlLink },
computed: {
...mapState('detail', ['release', 'updateReleaseApiDocsPath']),
inputId() {
return uniqueId('tag-name-input-');
},
helpId() {
return uniqueId('tag-name-help-');
},
},
};
</script>
<template>
<gl-form-group :label="__('Tag name')" :label-for="inputId">
<div class="row">
<div class="col-md-6 col-lg-5 col-xl-4">
<gl-form-input
:id="inputId"
:value="release.tagName"
type="text"
class="form-control"
:aria-describedby="helpId"
disabled
/>
</div>
</div>
<template #description>
<div :id="helpId" data-testid="tag-name-help">
<gl-sprintf
:message="
__(
'Changing a Release tag is only supported via Releases API. %{linkStart}More information%{linkEnd}',
)
"
>
<template #link="{ content }">
<gl-link :href="updateReleaseApiDocsPath" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</div>
</template>
</gl-form-group>
</template>
<script>
export default {
name: 'TagFieldNew',
};
</script>
<template>
<div></div>
</template>
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { hasContent } from '~/lib/utils/text_utility'; import { hasContent } from '~/lib/utils/text_utility';
/**
* @returns {Boolean} `true` if the app is editing an existing release.
* `false` if the app is creating a new release.
*/
export const isExistingRelease = state => {
return Boolean(state.originalRelease);
};
/** /**
* @param {Object} link The link to test * @param {Object} link The link to test
* @returns {Boolean} `true` if the release link is empty, i.e. it has * @returns {Boolean} `true` if the release link is empty, i.e. it has
......
...@@ -19,7 +19,12 @@ export default ({ ...@@ -19,7 +19,12 @@ export default ({
manageMilestonesPath, manageMilestonesPath,
newMilestonePath, newMilestonePath,
/**
* The name of the tag associated with the release, provided by the backend.
* When creating a new release, this value is null.
*/
tagName, tagName,
releasesPagePath, releasesPagePath,
defaultBranch, defaultBranch,
......
...@@ -34,6 +34,7 @@ describe('Release edit/new component', () => { ...@@ -34,6 +34,7 @@ describe('Release edit/new component', () => {
getters = { getters = {
isValid: () => true, isValid: () => true,
isExistingRelease: () => true,
validationErrors: () => ({ validationErrors: () => ({
assets: { assets: {
links: [], links: [],
...@@ -96,28 +97,6 @@ describe('Release edit/new component', () => { ...@@ -96,28 +97,6 @@ describe('Release edit/new component', () => {
); );
}); });
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', () => { it('renders the correct release title in the "Release title" field', () => {
expect(wrapper.find('#release-title').element.value).toBe(release.name); expect(wrapper.find('#release-title').element.value).toBe(release.name);
}); });
......
import { GlFormInput } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import TagFieldExisting from '~/releases/components/tag_field_existing.vue';
import createStore from '~/releases/stores';
import createDetailModule from '~/releases/stores/modules/detail';
const TEST_TAG_NAME = 'test-tag-name';
const TEST_DOCS_PATH = '/help/test/docs/path';
describe('releases/components/tag_field_existing', () => {
let store;
let wrapper;
const createComponent = (mountFn = shallowMount) => {
wrapper = mountFn(TagFieldExisting, {
store,
});
};
const findInput = () => wrapper.find(GlFormInput);
const findHelp = () => wrapper.find('[data-testid="tag-name-help"]');
const findHelpLink = () => {
const link = findHelp().find('a');
return {
text: link.text(),
href: link.attributes('href'),
target: link.attributes('target'),
};
};
beforeEach(() => {
store = createStore({
modules: {
detail: createDetailModule({
updateReleaseApiDocsPath: TEST_DOCS_PATH,
tagName: TEST_TAG_NAME,
}),
},
});
store.state.detail.release = {
tagName: TEST_TAG_NAME,
};
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('default', () => {
it('shows the tag name', () => {
createComponent();
expect(findInput().attributes()).toMatchObject({
disabled: '',
value: TEST_TAG_NAME,
});
});
it('shows help', () => {
createComponent(mount);
expect(findHelp().text()).toMatchInterpolatedText(
'Changing a Release tag is only supported via Releases API. More information',
);
const helpLink = findHelpLink();
expect(helpLink).toEqual({
text: 'More information',
href: TEST_DOCS_PATH,
target: '_blank',
});
});
});
});
import { shallowMount } from '@vue/test-utils';
import TagFieldNew from '~/releases/components/tag_field_new.vue';
import createStore from '~/releases/stores';
import createDetailModule from '~/releases/stores/modules/detail';
describe('releases/components/tag_field_new', () => {
let store;
let wrapper;
const createComponent = (mountFn = shallowMount) => {
wrapper = mountFn(TagFieldNew, {
store,
});
};
beforeEach(() => {
store = createStore({
modules: {
detail: createDetailModule({}),
},
});
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders a placeholder component', () => {
createComponent();
expect(wrapper.exists()).toBe(true);
});
});
import { shallowMount } from '@vue/test-utils';
import TagField from '~/releases/components/tag_field.vue';
import TagFieldNew from '~/releases/components/tag_field_new.vue';
import TagFieldExisting from '~/releases/components/tag_field_existing.vue';
import createStore from '~/releases/stores';
import createDetailModule from '~/releases/stores/modules/detail';
describe('releases/components/tag_field', () => {
let store;
let wrapper;
const createComponent = ({ originalRelease }) => {
store = createStore({
modules: {
detail: createDetailModule({}),
},
});
store.state.detail.originalRelease = originalRelease;
wrapper = shallowMount(TagField, { store });
};
const findTagFieldNew = () => wrapper.find(TagFieldNew);
const findTagFieldExisting = () => wrapper.find(TagFieldExisting);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('when an existing release is being edited', () => {
beforeEach(() => {
const originalRelease = { name: 'Version 1.0' };
createComponent({ originalRelease });
});
it('renders the TagFieldExisting component', () => {
expect(findTagFieldExisting().exists()).toBe(true);
});
it('does not render the TagFieldNew component', () => {
expect(findTagFieldNew().exists()).toBe(false);
});
});
describe('when a new release is being created', () => {
beforeEach(() => {
createComponent({ originalRelease: null });
});
it('renders the TagFieldNew component', () => {
expect(findTagFieldNew().exists()).toBe(true);
});
it('does not render the TagFieldExisting component', () => {
expect(findTagFieldExisting().exists()).toBe(false);
});
});
});
import * as getters from '~/releases/stores/modules/detail/getters'; import * as getters from '~/releases/stores/modules/detail/getters';
describe('Release detail getters', () => { describe('Release detail getters', () => {
describe('isExistingRelease', () => {
it('returns true if the release is an existing release that already exists in the database', () => {
const state = { originalRelease: { name: 'The first release' } };
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', () => {
const state = { originalRelease: null };
expect(getters.isExistingRelease(state)).toBe(false);
});
});
describe('releaseLinksToCreate', () => { describe('releaseLinksToCreate', () => {
it("returns an empty array if state.release doesn't exist", () => { it("returns an empty array if state.release doesn't exist", () => {
const state = {}; const state = {};
......
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