Commit 10545f0a authored by mlunoe's avatar mlunoe

Feat(FilteredSearchBar): default suggestion config

Make default suggestions configurable in tokens
so that the user can specify default suggestion
e.g. when they are not supported for authors in
issues
parent 207688e5
/* eslint-disable @gitlab/require-i18n-strings */ /* eslint-disable @gitlab/require-i18n-strings */
import { __ } from '~/locale'; import { __ } from '~/locale';
export const ANY_AUTHOR = 'Any';
const DEFAULT_LABEL_NO_LABEL = { value: 'No label', text: __('No label') }; const DEFAULT_LABEL_NO_LABEL = { value: 'No label', text: __('No label') };
export const DEFAULT_LABEL_NONE = { value: 'None', text: __('None') }; export const DEFAULT_LABEL_NONE = { value: 'None', text: __('None') };
export const DEFAULT_LABEL_ANY = { value: 'Any', text: __('Any') }; export const DEFAULT_LABEL_ANY = { value: 'Any', text: __('Any') };
......
...@@ -11,10 +11,9 @@ import { debounce } from 'lodash'; ...@@ -11,10 +11,9 @@ import { debounce } from 'lodash';
import { deprecatedCreateFlash as createFlash } from '~/flash'; import { deprecatedCreateFlash as createFlash } from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { ANY_AUTHOR, DEBOUNCE_DELAY } from '../constants'; import { DEFAULT_LABEL_ANY, DEBOUNCE_DELAY } from '../constants';
export default { export default {
anyAuthor: ANY_AUTHOR,
components: { components: {
GlFilteredSearchToken, GlFilteredSearchToken,
GlAvatar, GlAvatar,
...@@ -35,6 +34,7 @@ export default { ...@@ -35,6 +34,7 @@ export default {
data() { data() {
return { return {
authors: this.config.initialAuthors || [], authors: this.config.initialAuthors || [],
defaultAuthors: this.config.defaultAuthors || [DEFAULT_LABEL_ANY],
loading: true, loading: true,
}; };
}, },
...@@ -99,10 +99,14 @@ export default { ...@@ -99,10 +99,14 @@ export default {
<span>{{ activeAuthor ? activeAuthor.name : inputValue }}</span> <span>{{ activeAuthor ? activeAuthor.name : inputValue }}</span>
</template> </template>
<template #suggestions> <template #suggestions>
<gl-filtered-search-suggestion :value="$options.anyAuthor"> <gl-filtered-search-suggestion
{{ __('Any') }} v-for="author in defaultAuthors"
:key="author.value"
:value="author.value"
>
{{ author.text }}
</gl-filtered-search-suggestion> </gl-filtered-search-suggestion>
<gl-deprecated-dropdown-divider /> <gl-deprecated-dropdown-divider v-if="defaultAuthors.length" />
<gl-loading-icon v-if="loading" /> <gl-loading-icon v-if="loading" />
<template v-else> <template v-else>
<gl-filtered-search-suggestion <gl-filtered-search-suggestion
......
...@@ -112,7 +112,7 @@ export default { ...@@ -112,7 +112,7 @@ export default {
> >
{{ label.text }} {{ label.text }}
</gl-filtered-search-suggestion> </gl-filtered-search-suggestion>
<gl-dropdown-divider /> <gl-dropdown-divider v-if="defaultLabels.length" />
<gl-loading-icon v-if="loading" /> <gl-loading-icon v-if="loading" />
<template v-else> <template v-else>
<gl-filtered-search-suggestion v-for="label in labels" :key="label.id" :value="label.title"> <gl-filtered-search-suggestion v-for="label in labels" :key="label.id" :value="label.title">
......
...@@ -95,7 +95,7 @@ export default { ...@@ -95,7 +95,7 @@ export default {
> >
{{ milestone.text }} {{ milestone.text }}
</gl-filtered-search-suggestion> </gl-filtered-search-suggestion>
<gl-dropdown-divider /> <gl-dropdown-divider v-if="defaultMilestones.length" />
<gl-loading-icon v-if="loading" /> <gl-loading-icon v-if="loading" />
<template v-else> <template v-else>
<gl-filtered-search-suggestion <gl-filtered-search-suggestion
......
...@@ -9,7 +9,7 @@ import { updateHistory, setUrlParams } from '~/lib/utils/url_utility'; ...@@ -9,7 +9,7 @@ import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
import { ANY_AUTHOR } from '~/vue_shared/components/filtered_search_bar/constants'; import { DEFAULT_LABEL_ANY } from '~/vue_shared/components/filtered_search_bar/constants';
import RequirementsTabs from './requirements_tabs.vue'; import RequirementsTabs from './requirements_tabs.vue';
import RequirementsLoading from './requirements_loading.vue'; import RequirementsLoading from './requirements_loading.vue';
...@@ -472,7 +472,7 @@ export default { ...@@ -472,7 +472,7 @@ export default {
filters.forEach(filter => { filters.forEach(filter => {
if (typeof filter === 'string') { if (typeof filter === 'string') {
textSearch = filter; textSearch = filter;
} else if (filter.value.data !== ANY_AUTHOR) { } else if (filter.value.data !== DEFAULT_LABEL_ANY.value) {
authors.push(filter.value.data); authors.push(filter.value.data);
} }
}); });
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui'; import {
GlFilteredSearchToken,
GlFilteredSearchTokenSegment,
GlFilteredSearchSuggestion,
GlNewDropdownDivider as GlDropdownDivider,
} from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as createFlash } from '~/flash'; import { deprecatedCreateFlash as createFlash } from '~/flash';
import {
DEFAULT_LABEL_NONE,
DEFAULT_LABEL_ANY,
} from '~/vue_shared/components/filtered_search_bar/constants';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
import { mockAuthorToken, mockAuthors } from '../mock_data'; import { mockAuthorToken, mockAuthors } from '../mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
const defaultStubs = {
const createComponent = ({ config = mockAuthorToken, value = { data: '' }, active = false } = {}) => Portal: true,
mount(AuthorToken, { GlFilteredSearchSuggestionList: {
template: '<div></div>',
methods: {
getValue: () => '=',
},
},
};
function createComponent(options = {}) {
const {
config = mockAuthorToken,
value = { data: '' },
active = false,
stubs = defaultStubs,
} = options;
return mount(AuthorToken, {
propsData: { propsData: {
config, config,
value, value,
...@@ -22,16 +46,9 @@ const createComponent = ({ config = mockAuthorToken, value = { data: '' }, activ ...@@ -22,16 +46,9 @@ const createComponent = ({ config = mockAuthorToken, value = { data: '' }, activ
portalName: 'fake target', portalName: 'fake target',
alignSuggestions: function fakeAlignSuggestions() {}, alignSuggestions: function fakeAlignSuggestions() {},
}, },
stubs: { stubs,
Portal: true,
GlFilteredSearchSuggestionList: {
template: '<div></div>',
methods: {
getValue: () => '=',
},
},
},
}); });
}
describe('AuthorToken', () => { describe('AuthorToken', () => {
let mock; let mock;
...@@ -139,5 +156,57 @@ describe('AuthorToken', () => { ...@@ -139,5 +156,57 @@ describe('AuthorToken', () => {
expect(tokenSegments.at(2).text()).toBe(mockAuthors[0].name); // "Administrator" expect(tokenSegments.at(2).text()).toBe(mockAuthors[0].name); // "Administrator"
}); });
}); });
it('renders provided defaultAuthors as suggestions', async () => {
const defaultAuthors = [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY];
wrapper = createComponent({
active: true,
config: { ...mockAuthorToken, defaultAuthors },
stubs: { Portal: true },
});
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
await wrapper.vm.$nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
expect(suggestions).toHaveLength(defaultAuthors.length);
defaultAuthors.forEach((label, index) => {
expect(suggestions.at(index).text()).toBe(label.text);
});
});
it('does not render divider when no defaultAuthors', async () => {
wrapper = createComponent({
active: true,
config: { ...mockAuthorToken, defaultAuthors: [] },
stubs: { Portal: true },
});
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
await wrapper.vm.$nextTick();
expect(wrapper.contains(GlFilteredSearchSuggestion)).toBe(false);
expect(wrapper.contains(GlDropdownDivider)).toBe(false);
});
it('renders `DEFAULT_LABEL_ANY` as default suggestions', async () => {
wrapper = createComponent({
active: true,
config: { ...mockAuthorToken },
stubs: { Portal: true },
});
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
await wrapper.vm.$nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
expect(suggestions).toHaveLength(1);
expect(suggestions.at(0).text()).toBe(DEFAULT_LABEL_ANY.text);
});
}); });
}); });
...@@ -3,6 +3,7 @@ import { ...@@ -3,6 +3,7 @@ import {
GlFilteredSearchToken, GlFilteredSearchToken,
GlFilteredSearchSuggestion, GlFilteredSearchSuggestion,
GlFilteredSearchTokenSegment, GlFilteredSearchTokenSegment,
GlNewDropdownDivider as GlDropdownDivider,
} from '@gitlab/ui'; } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
...@@ -33,13 +34,14 @@ const defaultStubs = { ...@@ -33,13 +34,14 @@ const defaultStubs = {
}, },
}; };
const createComponent = ({ function createComponent(options = {}) {
config = mockLabelToken, const {
value = { data: '' }, config = mockLabelToken,
active = false, value = { data: '' },
stubs = defaultStubs, active = false,
} = {}) => stubs = defaultStubs,
mount(LabelToken, { } = options;
return mount(LabelToken, {
propsData: { propsData: {
config, config,
value, value,
...@@ -51,6 +53,7 @@ const createComponent = ({ ...@@ -51,6 +53,7 @@ const createComponent = ({
}, },
stubs, stubs,
}); });
}
describe('LabelToken', () => { describe('LabelToken', () => {
let mock; let mock;
...@@ -204,6 +207,21 @@ describe('LabelToken', () => { ...@@ -204,6 +207,21 @@ describe('LabelToken', () => {
}); });
}); });
it('does not render divider when no defaultLabels', async () => {
wrapper = createComponent({
active: true,
config: { ...mockLabelToken, defaultLabels: [] },
stubs: { Portal: true },
});
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
await wrapper.vm.$nextTick();
expect(wrapper.contains(GlFilteredSearchSuggestion)).toBe(false);
expect(wrapper.contains(GlDropdownDivider)).toBe(false);
});
it('renders `DEFAULT_LABELS` as default suggestions', async () => { it('renders `DEFAULT_LABELS` as default suggestions', async () => {
wrapper = createComponent({ wrapper = createComponent({
active: true, active: true,
......
...@@ -3,6 +3,7 @@ import { ...@@ -3,6 +3,7 @@ import {
GlFilteredSearchToken, GlFilteredSearchToken,
GlFilteredSearchSuggestion, GlFilteredSearchSuggestion,
GlFilteredSearchTokenSegment, GlFilteredSearchTokenSegment,
GlNewDropdownDivider as GlDropdownDivider,
} from '@gitlab/ui'; } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
...@@ -31,13 +32,14 @@ const defaultStubs = { ...@@ -31,13 +32,14 @@ const defaultStubs = {
}, },
}; };
const createComponent = ({ function createComponent(options = {}) {
config = mockMilestoneToken, const {
value = { data: '' }, config = mockMilestoneToken,
active = false, value = { data: '' },
stubs = defaultStubs, active = false,
} = {}) => stubs = defaultStubs,
mount(MilestoneToken, { } = options;
return mount(MilestoneToken, {
propsData: { propsData: {
config, config,
value, value,
...@@ -49,6 +51,7 @@ const createComponent = ({ ...@@ -49,6 +51,7 @@ const createComponent = ({
}, },
stubs, stubs,
}); });
}
describe('MilestoneToken', () => { describe('MilestoneToken', () => {
let mock; let mock;
...@@ -176,6 +179,21 @@ describe('MilestoneToken', () => { ...@@ -176,6 +179,21 @@ describe('MilestoneToken', () => {
}); });
}); });
it('does not render divider when no defaultMilestones', async () => {
wrapper = createComponent({
active: true,
config: { ...mockMilestoneToken, defaultMilestones: [] },
stubs: { Portal: true },
});
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
await wrapper.vm.$nextTick();
expect(wrapper.contains(GlFilteredSearchSuggestion)).toBe(false);
expect(wrapper.contains(GlDropdownDivider)).toBe(false);
});
it('renders `DEFAULT_MILESTONES` as default suggestions', async () => { it('renders `DEFAULT_MILESTONES` as default suggestions', async () => {
wrapper = createComponent({ wrapper = createComponent({
active: true, active: 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