Add persisted dropdown component

- source
- tests
parent 40c19a06
...@@ -12,9 +12,17 @@ export default { ...@@ -12,9 +12,17 @@ export default {
GlLink, GlLink,
GlSprintf, GlSprintf,
}, },
data() {
return {
instructionType: 'maven',
};
},
computed: { computed: {
...mapState(['mavenHelpPath']), ...mapState(['mavenHelpPath']),
...mapGetters(['mavenInstallationXml', 'mavenInstallationCommand', 'mavenSetupXml']), ...mapGetters(['mavenInstallationXml', 'mavenInstallationCommand', 'mavenSetupXml']),
showMaven() {
return this.instructionType === 'maven';
},
}, },
i18n: { i18n: {
xmlText: s__( xmlText: s__(
...@@ -36,50 +44,51 @@ export default { ...@@ -36,50 +44,51 @@ export default {
<div> <div>
<h3 class="gl-font-lg">{{ __('Installation') }}</h3> <h3 class="gl-font-lg">{{ __('Installation') }}</h3>
<p> <template v-if="showMaven">
<gl-sprintf :message="$options.i18n.xmlText"> <p>
<template #code="{ content }"> <gl-sprintf :message="$options.i18n.xmlText">
<code>{{ content }}</code> <template #code="{ content }">
</template> <code>{{ content }}</code>
</gl-sprintf> </template>
</p> </gl-sprintf>
</p>
<code-instruction <code-instruction
:label="s__('PackageRegistry|Maven XML')" :instruction="mavenInstallationXml"
:instruction="mavenInstallationXml" :copy-text="s__('PackageRegistry|Copy Maven XML')"
:copy-text="s__('PackageRegistry|Copy Maven XML')" :tracking-action="$options.trackingActions.COPY_MAVEN_XML"
multiline :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
:tracking-action="$options.trackingActions.COPY_MAVEN_XML" multiline
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" />
/>
<code-instruction <code-instruction
:label="s__('PackageRegistry|Maven Command')" :label="s__('PackageRegistry|Maven Command')"
:instruction="mavenInstallationCommand" :instruction="mavenInstallationCommand"
:copy-text="s__('PackageRegistry|Copy Maven command')" :copy-text="s__('PackageRegistry|Copy Maven command')"
:tracking-action="$options.trackingActions.COPY_MAVEN_COMMAND" :tracking-action="$options.trackingActions.COPY_MAVEN_COMMAND"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
/> />
<h3 class="gl-font-lg">{{ __('Registry setup') }}</h3> <h3 class="gl-font-lg">{{ s__('PackageRegistry|Registry setup') }}</h3>
<p> <p>
<gl-sprintf :message="$options.i18n.setupText"> <gl-sprintf :message="$options.i18n.setupText">
<template #code="{ content }"> <template #code="{ content }">
<code>{{ content }}</code> <code>{{ content }}</code>
</template>
</gl-sprintf>
</p>
<code-instruction
:instruction="mavenSetupXml"
:copy-text="s__('PackageRegistry|Copy Maven registry XML')"
:tracking-action="$options.trackingActions.COPY_MAVEN_SETUP"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
multiline
/>
<gl-sprintf :message="$options.i18n.helpText">
<template #link="{ content }">
<gl-link :href="mavenHelpPath" target="_blank">{{ content }}</gl-link>
</template> </template>
</gl-sprintf> </gl-sprintf>
</p> </template>
<code-instruction
:instruction="mavenSetupXml"
:copy-text="s__('PackageRegistry|Copy Maven registry XML')"
multiline
:tracking-action="$options.trackingActions.COPY_MAVEN_SETUP"
:tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
/>
<gl-sprintf :message="$options.i18n.helpText">
<template #link="{ content }">
<gl-link :href="mavenHelpPath" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</div> </div>
</template> </template>
...@@ -56,27 +56,29 @@ export default { ...@@ -56,27 +56,29 @@ export default {
</script> </script>
<template> <template>
<div v-if="!multiline" class="gl-mb-3"> <div>
<label v-if="label" :for="generateFormId('instruction-input')">{{ label }}</label> <label v-if="label" :for="generateFormId('instruction-input')">{{ label }}</label>
<div class="input-group gl-mb-3"> <div v-if="!multiline" class="gl-mb-3">
<input <div class="input-group gl-mb-3">
:id="generateFormId('instruction-input')" <input
:value="instruction" :id="generateFormId('instruction-input')"
type="text" :value="instruction"
class="form-control gl-font-monospace" type="text"
data-testid="instruction-input" class="form-control gl-font-monospace"
readonly data-testid="instruction-input"
@copy="trackCopy" readonly
/> @copy="trackCopy"
<span class="input-group-append" data-testid="instruction-button" @click="trackCopy"> />
<clipboard-button :text="instruction" :title="copyText" class="input-group-text" /> <span class="input-group-append" data-testid="instruction-button" @click="trackCopy">
</span> <clipboard-button :text="instruction" :title="copyText" class="input-group-text" />
</span>
</div>
</div> </div>
</div>
<div v-else> <div v-else>
<pre class="gl-font-monospace" data-testid="multiline-instruction" @copy="trackCopy">{{ <pre class="gl-font-monospace" data-testid="multiline-instruction" @copy="trackCopy">{{
instruction instruction
}}</pre> }}</pre>
</div>
</div> </div>
</template> </template>
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
export default {
name: 'PersistedDropdownSelection',
components: {
GlDropdown,
GlDropdownItem,
LocalStorageSync,
},
props: {
options: {
type: Array,
required: true,
},
storageKey: {
type: String,
required: true,
},
},
data() {
return {
selected: null,
};
},
computed: {
dropdownText() {
const selected = this.parsedOptions.find((o) => o.selected);
return selected?.label || this.options[0].label;
},
parsedOptions() {
return this.options.map((o) => ({ ...o, selected: o.value === this.selected }));
},
},
methods: {
setSelected(value) {
this.selected = value;
this.$emit('change', value);
},
},
};
</script>
<template>
<local-storage-sync :storage-key="storageKey" :value="selected" @input="setSelected">
<gl-dropdown :text="dropdownText" lazy>
<gl-dropdown-item
v-for="option in parsedOptions"
:key="option.value"
:is-checked="option.selected"
:is-check-item="true"
@click="setSelected(option.value)"
>
{{ option.label }}
</gl-dropdown-item>
</gl-dropdown>
</local-storage-sync>
</template>
...@@ -21611,9 +21611,6 @@ msgstr "" ...@@ -21611,9 +21611,6 @@ msgstr ""
msgid "PackageRegistry|Maven Command" msgid "PackageRegistry|Maven Command"
msgstr "" msgstr ""
msgid "PackageRegistry|Maven XML"
msgstr ""
msgid "PackageRegistry|NuGet" msgid "PackageRegistry|NuGet"
msgstr "" msgstr ""
...@@ -21650,6 +21647,9 @@ msgstr "" ...@@ -21650,6 +21647,9 @@ msgstr ""
msgid "PackageRegistry|Recipe: %{recipe}" msgid "PackageRegistry|Recipe: %{recipe}"
msgstr "" msgstr ""
msgid "PackageRegistry|Registry setup"
msgstr ""
msgid "PackageRegistry|Remove package" msgid "PackageRegistry|Remove package"
msgstr "" msgstr ""
......
...@@ -17,7 +17,7 @@ exports[`MavenInstallation renders all the messages 1`] = ` ...@@ -17,7 +17,7 @@ exports[`MavenInstallation renders all the messages 1`] = `
<code-instruction-stub <code-instruction-stub
copytext="Copy Maven XML" copytext="Copy Maven XML"
instruction="foo/xml" instruction="foo/xml"
label="Maven XML" label=""
multiline="true" multiline="true"
trackingaction="copy_maven_xml" trackingaction="copy_maven_xml"
trackinglabel="code_instruction" trackinglabel="code_instruction"
......
...@@ -2,20 +2,26 @@ ...@@ -2,20 +2,26 @@
exports[`Package code instruction multiline to match the snapshot 1`] = ` exports[`Package code instruction multiline to match the snapshot 1`] = `
<div> <div>
<pre <label
class="gl-font-monospace" for="instruction-input_3"
data-testid="multiline-instruction"
> >
this is some foo_label
</label>
<div>
<pre
class="gl-font-monospace"
data-testid="multiline-instruction"
>
this is some
multiline text multiline text
</pre> </pre>
</div>
</div> </div>
`; `;
exports[`Package code instruction single line to match the default snapshot 1`] = ` exports[`Package code instruction single line to match the default snapshot 1`] = `
<div <div>
class="gl-mb-3"
>
<label <label
for="instruction-input_2" for="instruction-input_2"
> >
...@@ -23,42 +29,46 @@ exports[`Package code instruction single line to match the default snapshot 1`] ...@@ -23,42 +29,46 @@ exports[`Package code instruction single line to match the default snapshot 1`]
</label> </label>
<div <div
class="input-group gl-mb-3" class="gl-mb-3"
> >
<input <div
class="form-control gl-font-monospace" class="input-group gl-mb-3"
data-testid="instruction-input"
id="instruction-input_2"
readonly="readonly"
type="text"
/>
<span
class="input-group-append"
data-testid="instruction-button"
> >
<button <input
aria-label="Copy this value" class="form-control gl-font-monospace"
class="btn input-group-text btn-default btn-md gl-button btn-default-secondary btn-icon" data-testid="instruction-input"
data-clipboard-text="npm i @my-package" id="instruction-input_2"
title="Copy npm install command" readonly="readonly"
type="button" type="text"
/>
<span
class="input-group-append"
data-testid="instruction-button"
> >
<!----> <button
aria-label="Copy this value"
<svg class="btn input-group-text btn-default btn-md gl-button btn-default-secondary btn-icon"
aria-hidden="true" data-clipboard-text="npm i @my-package"
class="gl-button-icon gl-icon s16" title="Copy npm install command"
data-testid="copy-to-clipboard-icon" type="button"
> >
<use <!---->
href="#copy-to-clipboard"
/> <svg
</svg> aria-hidden="true"
class="gl-button-icon gl-icon s16"
<!----> data-testid="copy-to-clipboard-icon"
</button> >
</span> <use
href="#copy-to-clipboard"
/>
</svg>
<!---->
</button>
</span>
</div>
</div> </div>
</div> </div>
`; `;
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import component from '~/vue_shared/components/registry/persisted_dropdown_selection.vue';
describe('Persisted dropdown selection', () => {
let wrapper;
const defaultProps = {
storageKey: 'foo_bar',
options: [
{ value: 'maven', label: 'Maven' },
{ value: 'gradle', label: 'Gradle' },
],
};
function createComponent({ props = {}, data = {} } = {}) {
wrapper = shallowMount(component, {
propsData: {
...defaultProps,
...props,
},
data() {
return data;
},
});
}
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
afterEach(() => {
wrapper.destroy();
});
describe('local storage sync', () => {
it('uses the local storage sync component', () => {
createComponent();
expect(findLocalStorageSync().exists()).toBe(true);
});
it('passes the right props', () => {
createComponent({ data: { selected: 'foo' } });
expect(findLocalStorageSync().props()).toMatchObject({
storageKey: defaultProps.storageKey,
value: 'foo',
});
});
it('on input event updates the model and emits event', async () => {
const inputPayload = 'bar';
createComponent();
findLocalStorageSync().vm.$emit('input', inputPayload);
await nextTick();
expect(wrapper.emitted('change')).toStrictEqual([[inputPayload]]);
expect(findLocalStorageSync().props('value')).toBe(inputPayload);
});
});
describe('dropdown', () => {
it('has a dropdown component', () => {
createComponent();
expect(findDropdown().exists()).toBe(true);
});
describe('dropdown text', () => {
it('when no selection shows the first', () => {
createComponent();
expect(findDropdown().props('text')).toBe('Maven');
});
it('when an option is selected, shows that option label', () => {
createComponent({ data: { selected: defaultProps.options[1].value } });
expect(findDropdown().props('text')).toBe('Gradle');
});
});
describe('dropdown items', () => {
it('has one item for each option', () => {
createComponent();
expect(findDropdownItems()).toHaveLength(defaultProps.options.length);
});
it('binds the correct props', () => {
createComponent({ data: { selected: defaultProps.options[0].value } });
expect(findDropdownItems().at(0).props()).toMatchObject({
isChecked: true,
isCheckItem: true,
});
expect(findDropdownItems().at(1).props()).toMatchObject({
isChecked: false,
isCheckItem: true,
});
});
it('on click updates the data and emits event', async () => {
createComponent({ data: { selected: defaultProps.options[0].value } });
expect(findDropdownItems().at(0).props('isChecked')).toBe(true);
findDropdownItems().at(1).vm.$emit('click');
await nextTick();
expect(wrapper.emitted('change')).toStrictEqual([['gradle']]);
expect(findDropdownItems().at(0).props('isChecked')).toBe(false);
expect(findDropdownItems().at(1).props('isChecked')).toBe(true);
});
});
});
});
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