Commit 449a63ad authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'mrincon-base_token-props-refactor' into 'master'

Refactor base token to make it reusable

See merge request gitlab-org/gitlab!63429
parents b0610d12 1fbf344f
...@@ -71,9 +71,9 @@ export default { ...@@ -71,9 +71,9 @@ export default {
<template> <template>
<base-token <base-token
:token-config="config" :config="config"
:token-value="value" :value="value"
:token-active="active" :active="active"
:tokens-list-loading="loading" :tokens-list-loading="loading"
:token-values="authors" :token-values="authors"
:fn-active-token-value="getActiveAuthor" :fn-active-token-value="getActiveAuthor"
...@@ -81,6 +81,7 @@ export default { ...@@ -81,6 +81,7 @@ export default {
:preloaded-token-values="preloadedAuthors" :preloaded-token-values="preloadedAuthors"
:recent-token-values-storage-key="config.recentTokenValuesStorageKey" :recent-token-values-storage-key="config.recentTokenValuesStorageKey"
@fetch-token-values="fetchAuthorBySearchTerm" @fetch-token-values="fetchAuthorBySearchTerm"
v-on="$listeners"
> >
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }"> <template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
<gl-avatar <gl-avatar
......
...@@ -19,29 +19,34 @@ export default { ...@@ -19,29 +19,34 @@ export default {
GlLoadingIcon, GlLoadingIcon,
}, },
props: { props: {
tokenConfig: { config: {
type: Object, type: Object,
required: true, required: true,
}, },
tokenValue: { value: {
type: Object, type: Object,
required: true, required: true,
}, },
tokenActive: { active: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
tokensListLoading: { tokensListLoading: {
type: Boolean, type: Boolean,
required: true, required: false,
default: false,
}, },
tokenValues: { tokenValues: {
type: Array, type: Array,
required: true, required: false,
default: () => [],
}, },
fnActiveTokenValue: { fnActiveTokenValue: {
type: Function, type: Function,
required: true, required: false,
default: (tokenValues, currentTokenValue) => {
return tokenValues.find(({ value }) => value === currentTokenValue);
},
}, },
defaultTokenValues: { defaultTokenValues: {
type: Array, type: Array,
...@@ -90,9 +95,9 @@ export default { ...@@ -90,9 +95,9 @@ export default {
}, },
currentTokenValue() { currentTokenValue() {
if (this.fnCurrentTokenValue) { if (this.fnCurrentTokenValue) {
return this.fnCurrentTokenValue(this.tokenValue.data); return this.fnCurrentTokenValue(this.value.data);
} }
return this.tokenValue.data.toLowerCase(); return this.value.data.toLowerCase();
}, },
activeTokenValue() { activeTokenValue() {
return this.fnActiveTokenValue(this.tokenValues, this.currentTokenValue); return this.fnActiveTokenValue(this.tokenValues, this.currentTokenValue);
...@@ -113,11 +118,11 @@ export default { ...@@ -113,11 +118,11 @@ export default {
}, },
}, },
watch: { watch: {
tokenActive: { active: {
immediate: true, immediate: true,
handler(newValue) { handler(newValue) {
if (!newValue && !this.tokenValues.length) { if (!newValue && !this.tokenValues.length) {
this.$emit('fetch-token-values', this.tokenValue.data); this.$emit('fetch-token-values', this.value.data);
} }
}, },
}, },
...@@ -148,9 +153,11 @@ export default { ...@@ -148,9 +153,11 @@ export default {
<template> <template>
<gl-filtered-search-token <gl-filtered-search-token
:config="tokenConfig" :config="config"
v-bind="{ ...this.$parent.$props, ...this.$parent.$attrs }" :value="value"
v-on="this.$parent.$listeners" :active="active"
v-bind="$attrs"
v-on="$listeners"
@input="handleInput" @input="handleInput"
@select="handleTokenValueSelected(activeTokenValue)" @select="handleTokenValueSelected(activeTokenValue)"
> >
......
...@@ -93,15 +93,16 @@ export default { ...@@ -93,15 +93,16 @@ export default {
<template> <template>
<base-token <base-token
:token-config="config" :config="config"
:token-value="value" :value="value"
:token-active="active" :active="active"
:tokens-list-loading="loading" :tokens-list-loading="loading"
:token-values="labels" :token-values="labels"
:fn-active-token-value="getActiveLabel" :fn-active-token-value="getActiveLabel"
:default-token-values="defaultLabels" :default-token-values="defaultLabels"
:recent-token-values-storage-key="config.recentTokenValuesStorageKey" :recent-token-values-storage-key="config.recentTokenValuesStorageKey"
@fetch-token-values="fetchLabelBySearchTerm" @fetch-token-values="fetchLabelBySearchTerm"
v-on="$listeners"
> >
<template <template
#view-token="{ viewTokenProps: { inputValue, cssClasses, listeners, activeTokenValue } }" #view-token="{ viewTokenProps: { inputValue, cssClasses, listeners, activeTokenValue } }"
......
...@@ -46,6 +46,7 @@ function createComponent(options = {}) { ...@@ -46,6 +46,7 @@ function createComponent(options = {}) {
active = false, active = false,
stubs = defaultStubs, stubs = defaultStubs,
data = {}, data = {},
listeners = {},
} = options; } = options;
return mount(AuthorToken, { return mount(AuthorToken, {
propsData: { propsData: {
...@@ -62,6 +63,7 @@ function createComponent(options = {}) { ...@@ -62,6 +63,7 @@ function createComponent(options = {}) {
return { ...data }; return { ...data };
}, },
stubs, stubs,
listeners,
}); });
} }
...@@ -258,6 +260,18 @@ describe('AuthorToken', () => { ...@@ -258,6 +260,18 @@ describe('AuthorToken', () => {
expect(suggestions.at(0).text()).toBe(DEFAULT_LABEL_ANY.text); expect(suggestions.at(0).text()).toBe(DEFAULT_LABEL_ANY.text);
}); });
it('emits listeners in the base-token', () => {
const mockInput = jest.fn();
wrapper = createComponent({
listeners: {
input: mockInput,
},
});
wrapper.findComponent(BaseToken).vm.$emit('input', [{ data: 'mockData', operator: '=' }]);
expect(mockInput).toHaveBeenLastCalledWith([{ data: 'mockData', operator: '=' }]);
});
describe('when loading', () => { describe('when loading', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
......
...@@ -46,12 +46,11 @@ const defaultSlots = { ...@@ -46,12 +46,11 @@ const defaultSlots = {
}; };
const mockProps = { const mockProps = {
tokenConfig: mockLabelToken, config: mockLabelToken,
tokenValue: { data: '' }, value: { data: '' },
tokenActive: false, active: false,
tokensListLoading: false,
tokenValues: [], tokenValues: [],
fnActiveTokenValue: jest.fn(), tokensListLoading: false,
defaultTokenValues: DEFAULT_LABELS, defaultTokenValues: DEFAULT_LABELS,
recentTokenValuesStorageKey: mockStorageKey, recentTokenValuesStorageKey: mockStorageKey,
fnCurrentTokenValue: jest.fn(), fnCurrentTokenValue: jest.fn(),
...@@ -83,7 +82,7 @@ describe('BaseToken', () => { ...@@ -83,7 +82,7 @@ describe('BaseToken', () => {
wrapper = createComponent({ wrapper = createComponent({
props: { props: {
...mockProps, ...mockProps,
tokenValue: { data: `"${mockRegularLabel.title}"` }, value: { data: `"${mockRegularLabel.title}"` },
tokenValues: mockLabels, tokenValues: mockLabels,
}, },
}); });
...@@ -112,17 +111,17 @@ describe('BaseToken', () => { ...@@ -112,17 +111,17 @@ describe('BaseToken', () => {
describe('activeTokenValue', () => { describe('activeTokenValue', () => {
it('calls `fnActiveTokenValue` when it is provided', async () => { it('calls `fnActiveTokenValue` when it is provided', async () => {
const mockFnActiveTokenValue = jest.fn();
wrapper.setProps({ wrapper.setProps({
fnActiveTokenValue: mockFnActiveTokenValue,
fnCurrentTokenValue: undefined, fnCurrentTokenValue: undefined,
}); });
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
// We're disabling lint to trigger computed prop execution for this test. expect(mockFnActiveTokenValue).toHaveBeenCalledTimes(1);
// eslint-disable-next-line no-unused-vars expect(mockFnActiveTokenValue).toHaveBeenCalledWith(
const { activeTokenValue } = wrapper.vm;
expect(wrapper.vm.fnActiveTokenValue).toHaveBeenCalledWith(
mockLabels, mockLabels,
`"${mockRegularLabel.title.toLowerCase()}"`, `"${mockRegularLabel.title.toLowerCase()}"`,
); );
...@@ -131,15 +130,15 @@ describe('BaseToken', () => { ...@@ -131,15 +130,15 @@ describe('BaseToken', () => {
}); });
describe('watch', () => { describe('watch', () => {
describe('tokenActive', () => { describe('active', () => {
let wrapperWithTokenActive; let wrapperWithTokenActive;
beforeEach(() => { beforeEach(() => {
wrapperWithTokenActive = createComponent({ wrapperWithTokenActive = createComponent({
props: { props: {
...mockProps, ...mockProps,
tokenActive: true, value: { data: `"${mockRegularLabel.title}"` },
tokenValue: { data: `"${mockRegularLabel.title}"` }, active: true,
}, },
}); });
}); });
...@@ -150,7 +149,7 @@ describe('BaseToken', () => { ...@@ -150,7 +149,7 @@ describe('BaseToken', () => {
it('emits `fetch-token-values` event on the component when value of this prop is changed to false and `tokenValues` array is empty', async () => { it('emits `fetch-token-values` event on the component when value of this prop is changed to false and `tokenValues` array is empty', async () => {
wrapperWithTokenActive.setProps({ wrapperWithTokenActive.setProps({
tokenActive: false, active: false,
}); });
await wrapperWithTokenActive.vm.$nextTick(); await wrapperWithTokenActive.vm.$nextTick();
...@@ -238,7 +237,7 @@ describe('BaseToken', () => { ...@@ -238,7 +237,7 @@ describe('BaseToken', () => {
jest.runAllTimers(); jest.runAllTimers();
expect(wrapperWithNoStubs.emitted('fetch-token-values')).toBeTruthy(); expect(wrapperWithNoStubs.emitted('fetch-token-values')).toBeTruthy();
expect(wrapperWithNoStubs.emitted('fetch-token-values')[1]).toEqual(['foo']); expect(wrapperWithNoStubs.emitted('fetch-token-values')[2]).toEqual(['foo']);
}); });
}); });
}); });
......
...@@ -40,6 +40,7 @@ function createComponent(options = {}) { ...@@ -40,6 +40,7 @@ function createComponent(options = {}) {
value = { data: '' }, value = { data: '' },
active = false, active = false,
stubs = defaultStubs, stubs = defaultStubs,
listeners = {},
} = options; } = options;
return mount(LabelToken, { return mount(LabelToken, {
propsData: { propsData: {
...@@ -53,6 +54,7 @@ function createComponent(options = {}) { ...@@ -53,6 +54,7 @@ function createComponent(options = {}) {
suggestionsListClass: 'custom-class', suggestionsListClass: 'custom-class',
}, },
stubs, stubs,
listeners,
}); });
} }
...@@ -206,7 +208,7 @@ describe('LabelToken', () => { ...@@ -206,7 +208,7 @@ describe('LabelToken', () => {
expect(wrapper.find(GlDropdownDivider).exists()).toBe(false); expect(wrapper.find(GlDropdownDivider).exists()).toBe(false);
}); });
it('renders `DEFAULT_LABELS` as default suggestions', async () => { it('renders `DEFAULT_LABELS` as default suggestions', () => {
wrapper = createComponent({ wrapper = createComponent({
active: true, active: true,
config: { ...mockLabelToken }, config: { ...mockLabelToken },
...@@ -215,7 +217,6 @@ describe('LabelToken', () => { ...@@ -215,7 +217,6 @@ describe('LabelToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2); const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate'); suggestionsSegment.vm.$emit('activate');
await wrapper.vm.$nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
...@@ -224,5 +225,17 @@ describe('LabelToken', () => { ...@@ -224,5 +225,17 @@ describe('LabelToken', () => {
expect(suggestions.at(index).text()).toBe(label.text); expect(suggestions.at(index).text()).toBe(label.text);
}); });
}); });
it('emits listeners in the base-token', () => {
const mockInput = jest.fn();
wrapper = createComponent({
listeners: {
input: mockInput,
},
});
wrapper.findComponent(BaseToken).vm.$emit('input', [{ data: 'mockData', operator: '=' }]);
expect(mockInput).toHaveBeenLastCalledWith([{ data: 'mockData', operator: '=' }]);
});
}); });
}); });
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