Commit 47be8411 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo Committed by Jose Ivan Vargas

Added fetchData methods to each token

Adds a fetchData method to each token
and binds to the input event, allowing
the data to be refetched when input
changes

Apply search query to api requests
parent 0f5b14d7
...@@ -34,6 +34,7 @@ export default { ...@@ -34,6 +34,7 @@ export default {
unique: true, unique: true,
symbol: '%', symbol: '%',
isLoading: this.milestonesLoading, isLoading: this.milestonesLoading,
fetchData: this.fetchMilestones,
}, },
{ {
icon: 'labels', icon: 'labels',
...@@ -44,6 +45,7 @@ export default { ...@@ -44,6 +45,7 @@ export default {
unique: false, unique: false,
symbol: '~', symbol: '~',
isLoading: this.labelsLoading, isLoading: this.labelsLoading,
fetchData: this.fetchLabels,
}, },
]; ];
}, },
......
...@@ -9,11 +9,11 @@ export const setMilestonesEndpoint = ({ commit }, milestonesEndpoint) => ...@@ -9,11 +9,11 @@ export const setMilestonesEndpoint = ({ commit }, milestonesEndpoint) =>
export const setLabelsEndpoint = ({ commit }, labelsEndpoint) => export const setLabelsEndpoint = ({ commit }, labelsEndpoint) =>
commit(types.SET_LABELS_ENDPOINT, labelsEndpoint); commit(types.SET_LABELS_ENDPOINT, labelsEndpoint);
export const fetchMilestones = ({ commit, state }) => { export const fetchMilestones = ({ commit, state }, search_title = '') => {
commit(types.REQUEST_MILESTONES); commit(types.REQUEST_MILESTONES);
return axios return axios
.get(state.milestonesEndpoint) .get(state.milestonesEndpoint, { params: { search_title } })
.then(({ data }) => { .then(({ data }) => {
commit(types.RECEIVE_MILESTONES_SUCCESS, data); commit(types.RECEIVE_MILESTONES_SUCCESS, data);
}) })
...@@ -24,11 +24,11 @@ export const fetchMilestones = ({ commit, state }) => { ...@@ -24,11 +24,11 @@ export const fetchMilestones = ({ commit, state }) => {
}); });
}; };
export const fetchLabels = ({ commit, state }) => { export const fetchLabels = ({ commit, state }, search = '') => {
commit(types.REQUEST_LABELS); commit(types.REQUEST_LABELS);
return axios return axios
.get(state.labelsEndpoint) .get(state.labelsEndpoint, { params: { search } })
.then(({ data }) => { .then(({ data }) => {
commit(types.RECEIVE_LABELS_SUCCESS, data); commit(types.RECEIVE_LABELS_SUCCESS, data);
}) })
......
...@@ -65,6 +65,7 @@ export default { ...@@ -65,6 +65,7 @@ export default {
symbol: '%', symbol: '%',
isLoading: this.milestonesLoading, isLoading: this.milestonesLoading,
operators: [{ value: '=', description: 'is', default: 'true' }], operators: [{ value: '=', description: 'is', default: 'true' }],
fetchData: this.fetchMilestones,
}, },
{ {
icon: 'labels', icon: 'labels',
...@@ -76,6 +77,7 @@ export default { ...@@ -76,6 +77,7 @@ export default {
symbol: '~', symbol: '~',
isLoading: this.labelsLoading, isLoading: this.labelsLoading,
operators: [{ value: '=', description: 'is', default: 'true' }], operators: [{ value: '=', description: 'is', default: 'true' }],
fetchData: this.fetchLabels,
}, },
{ {
icon: 'pencil', icon: 'pencil',
...@@ -86,6 +88,7 @@ export default { ...@@ -86,6 +88,7 @@ export default {
unique: true, unique: true,
isLoading: this.authorsLoading, isLoading: this.authorsLoading,
operators: [{ value: '=', description: 'is', default: 'true' }], operators: [{ value: '=', description: 'is', default: 'true' }],
fetchData: this.fetchAuthors,
}, },
{ {
icon: 'user', icon: 'user',
...@@ -96,15 +99,23 @@ export default { ...@@ -96,15 +99,23 @@ export default {
unique: false, unique: false,
isLoading: this.assigneesLoading, isLoading: this.assigneesLoading,
operators: [{ value: '=', description: 'is', default: 'true' }], operators: [{ value: '=', description: 'is', default: 'true' }],
fetchData: this.fetchAssignees,
}, },
]; ];
}, },
}, },
mounted() { mounted() {
this.initializeTokens(); this.initializeTokens();
}, },
methods: { methods: {
...mapActions('filters', ['setFilters', 'fetchTokenData']), ...mapActions('filters', [
'setFilters',
'fetchMilestones',
'fetchLabels',
'fetchAuthors',
'fetchAssignees',
]),
initializeTokens() { initializeTokens() {
const { const {
selectedMilestone: milestone = null, selectedMilestone: milestone = null,
......
...@@ -15,21 +15,12 @@ export const setPaths = ({ commit }, { groupPath = '', milestonesPath = '', labe ...@@ -15,21 +15,12 @@ export const setPaths = ({ commit }, { groupPath = '', milestonesPath = '', labe
commit(types.SET_LABELS_PATH, appendExtension(ls)); commit(types.SET_LABELS_PATH, appendExtension(ls));
}; };
export const fetchTokenData = ({ dispatch }) => { export const fetchMilestones = ({ commit, state }, search_title = '') => {
return Promise.all([
dispatch('fetchLabels'),
dispatch('fetchMilestones'),
dispatch('fetchAuthors'),
dispatch('fetchAssignees'),
]);
};
export const fetchMilestones = ({ commit, state }) => {
commit(types.REQUEST_MILESTONES); commit(types.REQUEST_MILESTONES);
const { milestonesPath } = state; const { milestonesPath } = state;
return axios return axios
.get(milestonesPath) .get(milestonesPath, { params: { search_title } })
.then(({ data }) => commit(types.RECEIVE_MILESTONES_SUCCESS, data)) .then(({ data }) => commit(types.RECEIVE_MILESTONES_SUCCESS, data))
.catch(({ response }) => { .catch(({ response }) => {
const { status } = response; const { status } = response;
...@@ -38,11 +29,11 @@ export const fetchMilestones = ({ commit, state }) => { ...@@ -38,11 +29,11 @@ export const fetchMilestones = ({ commit, state }) => {
}); });
}; };
export const fetchLabels = ({ commit, state }) => { export const fetchLabels = ({ commit, state }, search = '') => {
commit(types.REQUEST_LABELS); commit(types.REQUEST_LABELS);
return axios return axios
.get(state.labelsPath) .get(state.labelsPath, { params: { search } })
.then(({ data }) => commit(types.RECEIVE_LABELS_SUCCESS, data)) .then(({ data }) => commit(types.RECEIVE_LABELS_SUCCESS, data))
.catch(({ response }) => { .catch(({ response }) => {
const { status } = response; const { status } = response;
...@@ -90,7 +81,5 @@ export const setFilters = ({ dispatch }, nextFilters) => ...@@ -90,7 +81,5 @@ export const setFilters = ({ dispatch }, nextFilters) =>
export const initialize = ({ dispatch, commit }, initialFilters) => { export const initialize = ({ dispatch, commit }, initialFilters) => {
commit(types.INITIALIZE, initialFilters); commit(types.INITIALIZE, initialFilters);
return dispatch('setPaths', initialFilters) return dispatch('setPaths', initialFilters).then(() => dispatch('setFilters', initialFilters));
.then(() => dispatch('setFilters', initialFilters))
.then(() => dispatch('fetchTokenData'));
}; };
...@@ -6,6 +6,8 @@ import { ...@@ -6,6 +6,8 @@ import {
GlLoadingIcon, GlLoadingIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { debounce } from 'lodash';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
export default { export default {
components: { components: {
...@@ -29,38 +31,14 @@ export default { ...@@ -29,38 +31,14 @@ export default {
labels() { labels() {
return this.config.labels; return this.config.labels;
}, },
filteredLabels() { },
const labelsList = this.labels.map(label => ({ created() {
...label, this.searchLabels(this.value);
value: this.getEscapedText(label.title),
}));
return this.value?.data
? labelsList.filter(
label => label.title.toLowerCase().indexOf(this.value.data?.toLowerCase()) !== -1,
)
: labelsList;
},
}, },
methods: { methods: {
getEscapedText(text) { searchLabels: debounce(function debouncedSearch({ data = '' }) {
let escapedText = text; this.config.fetchData(data);
const hasSpace = text.indexOf(' ') !== -1; }, DEBOUNCE_DELAY),
const hasDoubleQuote = text.indexOf('"') !== -1;
// Encapsulate value with quotes if it has spaces
// Known side effect: values's with both single and double quotes
// won't escape properly
if (hasSpace) {
if (hasDoubleQuote) {
escapedText = `'${text}'`;
} else {
// Encapsulate singleQuotes or if it hasSpace
escapedText = `"${text}"`;
}
}
return escapedText;
},
}, },
defaultSuggestions: [ defaultSuggestions: [
// eslint-disable-next-line @gitlab/require-i18n-strings // eslint-disable-next-line @gitlab/require-i18n-strings
...@@ -72,7 +50,12 @@ export default { ...@@ -72,7 +50,12 @@ export default {
</script> </script>
<template> <template>
<gl-filtered-search-token :config="config" v-bind="{ ...$props, ...$attrs }" v-on="$listeners"> <gl-filtered-search-token
:config="config"
v-bind="{ ...$props, ...$attrs }"
v-on="$listeners"
@input="searchLabels"
>
<template #view="{ inputValue }"> <template #view="{ inputValue }">
<template v-if="config.symbol">{{ config.symbol }}</template <template v-if="config.symbol">{{ config.symbol }}</template
>{{ inputValue }} >{{ inputValue }}
...@@ -86,12 +69,12 @@ export default { ...@@ -86,12 +69,12 @@ export default {
:value="suggestion.value" :value="suggestion.value"
>{{ suggestion.text }}</gl-filtered-search-suggestion >{{ suggestion.text }}</gl-filtered-search-suggestion
> >
<gl-dropdown-divider v-if="config.isLoading || filteredLabels.length" /> <gl-dropdown-divider v-if="config.isLoading || labels.length" />
<gl-filtered-search-suggestion <gl-filtered-search-suggestion
v-for="label in filteredLabels" v-for="label in labels"
ref="labelItem" ref="labelItem"
:key="label.id" :key="label.id"
:value="label.value" :value="label.title"
> >
<div class="d-flex"> <div class="d-flex">
<span <span
......
...@@ -6,6 +6,8 @@ import { ...@@ -6,6 +6,8 @@ import {
GlLoadingIcon, GlLoadingIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { debounce } from 'lodash';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
export default { export default {
components: { components: {
...@@ -29,14 +31,9 @@ export default { ...@@ -29,14 +31,9 @@ export default {
milestones() { milestones() {
return this.config.milestones; return this.config.milestones;
}, },
filteredMilestones() { },
return this.value?.data created() {
? this.milestones.filter( this.searchMilestones(this.value);
milestone =>
milestone.title.toLowerCase().indexOf(this.value.data?.toLowerCase()) !== -1,
)
: this.milestones;
},
}, },
methods: { methods: {
getEscapedText(text) { getEscapedText(text) {
...@@ -58,6 +55,9 @@ export default { ...@@ -58,6 +55,9 @@ export default {
return escapedText; return escapedText;
}, },
searchMilestones: debounce(function debouncedSearch({ data = '' }) {
this.config.fetchData(data);
}, DEBOUNCE_DELAY),
}, },
defaultSuggestions: [ defaultSuggestions: [
// eslint-disable-next-line @gitlab/require-i18n-strings // eslint-disable-next-line @gitlab/require-i18n-strings
...@@ -73,7 +73,12 @@ export default { ...@@ -73,7 +73,12 @@ export default {
</script> </script>
<template> <template>
<gl-filtered-search-token :config="config" v-bind="{ ...$props, ...$attrs }" v-on="$listeners"> <gl-filtered-search-token
:config="config"
v-bind="{ ...$props, ...$attrs }"
v-on="$listeners"
@input="searchMilestones"
>
<template #view="{ inputValue }"> <template #view="{ inputValue }">
<template v-if="config.symbol">{{ config.symbol }}</template <template v-if="config.symbol">{{ config.symbol }}</template
>{{ inputValue }} >{{ inputValue }}
...@@ -85,11 +90,11 @@ export default { ...@@ -85,11 +90,11 @@ export default {
:value="suggestion.value" :value="suggestion.value"
>{{ suggestion.text }}</gl-filtered-search-suggestion >{{ suggestion.text }}</gl-filtered-search-suggestion
> >
<gl-dropdown-divider v-if="config.isLoading || filteredMilestones.length" /> <gl-dropdown-divider v-if="config.isLoading || milestones.length" />
<gl-loading-icon v-if="config.isLoading" /> <gl-loading-icon v-if="config.isLoading" />
<template v-else> <template v-else>
<gl-filtered-search-suggestion <gl-filtered-search-suggestion
v-for="milestone in filteredMilestones" v-for="milestone in milestones"
ref="milestoneItem" ref="milestoneItem"
:key="milestone.id" :key="milestone.id"
:value="getEscapedText(milestone.title)" :value="getEscapedText(milestone.title)"
......
...@@ -5,6 +5,8 @@ import { ...@@ -5,6 +5,8 @@ import {
GlFilteredSearchSuggestion, GlFilteredSearchSuggestion,
GlLoadingIcon, GlLoadingIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { debounce } from 'lodash';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
export default { export default {
components: { components: {
...@@ -25,19 +27,29 @@ export default { ...@@ -25,19 +27,29 @@ export default {
}, },
}, },
computed: { computed: {
users() {
return this.config.users;
},
selectedUser() { selectedUser() {
return this.value?.data return this.value?.data
? this.config.users.find(({ username }) => username === this.value.data) ? this.config.users.find(({ username }) => username === this.value.data)
: {}; : {};
}, },
}, },
created() {
this.searchUsers(this.value);
},
methods: {
searchUsers: debounce(function debouncedSearch({ data = '' }) {
this.config.fetchData(data);
}, DEBOUNCE_DELAY),
},
}; };
</script> </script>
<template> <template>
<gl-filtered-search-token :config="config" v-bind="{ ...$props, ...$attrs }" v-on="$listeners"> <gl-filtered-search-token
:config="config"
v-bind="{ ...$props, ...$attrs }"
v-on="$listeners"
@input="searchUsers"
>
<template #view="{ inputValue }"> <template #view="{ inputValue }">
<div v-if="selectedUser" data-testid="selected-user"> <div v-if="selectedUser" data-testid="selected-user">
<gl-avatar :size="16" :src="selectedUser.avatar_url" /> <gl-avatar :size="16" :src="selectedUser.avatar_url" />
...@@ -48,7 +60,7 @@ export default { ...@@ -48,7 +60,7 @@ export default {
<gl-loading-icon v-if="config.isLoading" /> <gl-loading-icon v-if="config.isLoading" />
<template v-else> <template v-else>
<gl-filtered-search-suggestion <gl-filtered-search-suggestion
v-for="user in users" v-for="user in config.users"
:key="user.username" :key="user.username"
:value="user.username" :value="user.username"
data-testid="user-item" data-testid="user-item"
......
...@@ -39,7 +39,7 @@ describe('Filters actions', () => { ...@@ -39,7 +39,7 @@ describe('Filters actions', () => {
selectedMilestone: 'NEXT', selectedMilestone: 'NEXT',
}; };
it('dispatches setPaths, setFilters and fetchTokenData', () => { it('dispatches setPaths, setFilters', () => {
return actions return actions
.initialize( .initialize(
{ {
...@@ -50,10 +50,9 @@ describe('Filters actions', () => { ...@@ -50,10 +50,9 @@ describe('Filters actions', () => {
initialData, initialData,
) )
.then(() => { .then(() => {
expect(mockDispatch).toHaveBeenCalledTimes(3); expect(mockDispatch).toHaveBeenCalledTimes(2);
expect(mockDispatch).toHaveBeenCalledWith('setPaths', initialData); expect(mockDispatch).toHaveBeenCalledWith('setPaths', initialData);
expect(mockDispatch).toHaveBeenCalledWith('setFilters', initialData); expect(mockDispatch).toHaveBeenCalledWith('setFilters', initialData);
expect(mockDispatch).toHaveBeenCalledWith('fetchTokenData');
}); });
}); });
...@@ -128,23 +127,6 @@ describe('Filters actions', () => { ...@@ -128,23 +127,6 @@ describe('Filters actions', () => {
}); });
}); });
describe('fetchTokenData', () => {
it('dispatches requests for token data', () => {
return testAction(
actions.fetchTokenData,
{ milestonesPath, labelsPath },
state,
[],
[
{ type: 'fetchLabels' },
{ type: 'fetchMilestones' },
{ type: 'fetchAuthors' },
{ type: 'fetchAssignees' },
],
);
});
});
describe('fetchAuthors', () => { describe('fetchAuthors', () => {
describe('success', () => { describe('success', () => {
beforeEach(() => { beforeEach(() => {
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui'; import { GlFilteredSearchSuggestion, GlLoadingIcon, GlFilteredSearchToken } from '@gitlab/ui';
import LabelToken from 'ee/analytics/shared/components/tokens/label_token.vue'; import LabelToken from 'ee/analytics/shared/components/tokens/label_token.vue';
import { mockLabels } from './mock_data'; import { mockLabels } from './mock_data';
...@@ -14,6 +14,7 @@ describe('LabelToken', () => { ...@@ -14,6 +14,7 @@ describe('LabelToken', () => {
unique: false, unique: false,
symbol: '~', symbol: '~',
isLoading: false, isLoading: false,
fetchData: jest.fn(),
}; };
const stubs = { const stubs = {
GlFilteredSearchToken: { GlFilteredSearchToken: {
...@@ -21,7 +22,7 @@ describe('LabelToken', () => { ...@@ -21,7 +22,7 @@ describe('LabelToken', () => {
}, },
}; };
const createComponent = (props = {}, options) => { const createComponent = (props = {}, options = { stubs }) => {
wrapper = shallowMount(LabelToken, { wrapper = shallowMount(LabelToken, {
propsData: { propsData: {
config: defaultConfig, config: defaultConfig,
...@@ -34,41 +35,69 @@ describe('LabelToken', () => { ...@@ -34,41 +35,69 @@ describe('LabelToken', () => {
const findFilteredSearchSuggestion = index => const findFilteredSearchSuggestion = index =>
wrapper.findAll(GlFilteredSearchSuggestion).at(index); wrapper.findAll(GlFilteredSearchSuggestion).at(index);
const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
const findAllLabelSuggestions = () => wrapper.findAll({ ref: 'labelItem' }); const findAllLabelSuggestions = () => wrapper.findAll({ ref: 'labelItem' });
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
it('renders a loading icon', () => { it('renders a loading icon', () => {
createComponent({ config: { isLoading: true }, value: {} }, { stubs }); createComponent({ config: { ...defaultConfig, isLoading: true }, value: {} }, { stubs });
expect(findLoadingIcon().exists()).toBe(true); expect(findLoadingIcon().exists()).toBe(true);
}); });
describe('suggestions', () => { describe('suggestions', () => {
it('renders a suggestion for each item', () => {
createComponent();
const res = findAllLabelSuggestions();
expect(res).toHaveLength(mockLabels.length);
mockLabels.forEach((m, index) => {
expect(res.at(index).html()).toContain(m.title);
});
});
describe('default suggestions', () => { describe('default suggestions', () => {
it.each` it.each`
text | dropdownIndex text | dropdownIndex
${'None'} | ${0} ${'None'} | ${0}
${'Any'} | ${1} ${'Any'} | ${1}
`('renders the "$text" suggestion', ({ text, dropdownIndex }) => { `('renders the "$text" suggestion', ({ text, dropdownIndex }) => {
createComponent(null, { stubs }); createComponent(null);
expect(findFilteredSearchSuggestion(dropdownIndex).text()).toEqual(text); expect(findFilteredSearchSuggestion(dropdownIndex).text()).toEqual(text);
}); });
}); });
});
describe('search', () => {
describe('when no search term is given', () => { describe('when no search term is given', () => {
it('renders two label suggestions', () => { it('calls `fetchData` with an empty search term', () => {
createComponent(null, { stubs }); createComponent({
value: defaultValue,
});
expect(defaultConfig.fetchData).toHaveBeenCalledWith('');
});
});
describe('when the search term "Peaches castle" is given', () => {
const data = "Peach's castle";
it('calls `fetchData` with the search term', () => {
createComponent({ value: { data } });
expect(findAllLabelSuggestions()).toHaveLength(2); expect(defaultConfig.fetchData).toHaveBeenCalledWith(data);
}); });
}); });
describe('when the search term "Alero" is given', () => { describe('when the input changes', () => {
it('renders one label suggestion that matches the search term', () => { const data = 'Moo moo farm';
createComponent({ value: { data: 'Alero' } }, { stubs }); it('calls `fetchData` with the updated search term', () => {
createComponent({ value: defaultValue }, { stubs: { GlFilteredSearchToken } });
expect(defaultConfig.fetchData).not.toHaveBeenCalledWith(data);
expect(findAllLabelSuggestions()).toHaveLength(1); findFilteredSearchToken().vm.$emit('input', { data });
expect(defaultConfig.fetchData).toHaveBeenCalledWith(data);
}); });
}); });
}); });
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui'; import { GlFilteredSearchSuggestion, GlLoadingIcon, GlFilteredSearchToken } from '@gitlab/ui';
import MilestoneToken from 'ee/analytics/shared/components/tokens/milestone_token.vue'; import MilestoneToken from 'ee/analytics/shared/components/tokens/milestone_token.vue';
import { mockMilestones } from './mock_data'; import { mockMilestones } from './mock_data';
...@@ -18,6 +18,7 @@ describe('MilestoneToken', () => { ...@@ -18,6 +18,7 @@ describe('MilestoneToken', () => {
const findFilteredSearchSuggestion = index => const findFilteredSearchSuggestion = index =>
wrapper.findAll(GlFilteredSearchSuggestion).at(index); wrapper.findAll(GlFilteredSearchSuggestion).at(index);
const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
const findAllMilestoneSuggestions = () => wrapper.findAll({ ref: 'milestoneItem' }); const findAllMilestoneSuggestions = () => wrapper.findAll({ ref: 'milestoneItem' });
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
...@@ -31,6 +32,7 @@ describe('MilestoneToken', () => { ...@@ -31,6 +32,7 @@ describe('MilestoneToken', () => {
unique: true, unique: true,
symbol: '%', symbol: '%',
isLoading: false, isLoading: false,
fetchData: jest.fn(),
}; };
stubs = { stubs = {
GlFilteredSearchToken: { GlFilteredSearchToken: {
...@@ -72,21 +74,46 @@ describe('MilestoneToken', () => { ...@@ -72,21 +74,46 @@ describe('MilestoneToken', () => {
); );
}); });
it('renders a suggestion for each item', () => {
createComponent({ config, value }, { stubs });
const res = findAllMilestoneSuggestions();
expect(res).toHaveLength(mockMilestones.length);
mockMilestones.forEach((m, index) => {
expect(res.at(index).html()).toContain(m.title);
});
});
});
describe('search', () => {
describe('when no search term is given', () => { describe('when no search term is given', () => {
it('renders two milestone suggestions', () => { it('calls `fetchData` with an empty search term', () => {
createComponent({ config, value }, { stubs }); createComponent({ config, value }, { stubs });
expect(findAllMilestoneSuggestions()).toHaveLength(2); expect(config.fetchData).toHaveBeenCalledWith('');
}); });
}); });
describe('when the search term "v4" is given', () => { describe('when the search term "v4" is given', () => {
it('renders one milestone suggestion that matches the search term', () => { const query = 'v4';
value.data = 'v4'; it('calls `fetchData` with the search term', () => {
value.data = query;
createComponent({ config, value }, { stubs }); createComponent({ config, value }, { stubs });
expect(findAllMilestoneSuggestions()).toHaveLength(1); expect(config.fetchData).toHaveBeenCalledWith(query);
});
});
describe('when the input changes', () => {
const data = 'v4';
it('calls `fetchData` with the updated search term', () => {
createComponent({ config, value }, { stubs: { GlFilteredSearchToken } });
expect(config.fetchData).not.toHaveBeenCalledWith(data);
findFilteredSearchToken().vm.$emit('input', { data });
expect(config.fetchData).toHaveBeenCalledWith(data);
}); });
}); });
}); });
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui'; import { GlFilteredSearchSuggestion, GlLoadingIcon, GlFilteredSearchToken } from '@gitlab/ui';
import UserToken from 'ee/analytics/shared/components/tokens/user_token.vue'; import UserToken from 'ee/analytics/shared/components/tokens/user_token.vue';
import { mockUsers } from './mock_data'; import { mockUsers } from './mock_data';
...@@ -8,6 +8,7 @@ describe('UserToken', () => { ...@@ -8,6 +8,7 @@ describe('UserToken', () => {
let value; let value;
let config; let config;
let stubs; let stubs;
const defaultValue = { data: '' };
const createComponent = (props = {}, options) => { const createComponent = (props = {}, options) => {
wrapper = shallowMount(UserToken, { wrapper = shallowMount(UserToken, {
...@@ -18,14 +19,16 @@ describe('UserToken', () => { ...@@ -18,14 +19,16 @@ describe('UserToken', () => {
const findFilteredSearchSuggestion = index => const findFilteredSearchSuggestion = index =>
wrapper.findAll(GlFilteredSearchSuggestion).at(index); wrapper.findAll(GlFilteredSearchSuggestion).at(index);
const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken);
const findAllUserSuggestions = () => wrapper.findAll('[data-testid="user-item"]'); const findAllUserSuggestions = () => wrapper.findAll('[data-testid="user-item"]');
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
beforeEach(() => { beforeEach(() => {
value = { data: '' }; value = defaultValue;
config = { config = {
users: mockUsers, users: mockUsers,
isLoading: false, isLoading: false,
fetchData: jest.fn(),
}; };
stubs = { stubs = {
GlFilteredSearchToken: { GlFilteredSearchToken: {
...@@ -73,4 +76,37 @@ describe('UserToken', () => { ...@@ -73,4 +76,37 @@ describe('UserToken', () => {
expect(findAllUserSuggestions()).toHaveLength(3); expect(findAllUserSuggestions()).toHaveLength(3);
}); });
}); });
describe('search', () => {
describe('when no search term is given', () => {
it('calls `fetchData` with an empty search term', () => {
createComponent({
config,
value,
});
expect(config.fetchData).toHaveBeenCalledWith('');
});
});
describe('when the search term "Diddy Kong" is given', () => {
const data = 'Diddy Kong';
it('calls `fetchData` with the search term', () => {
createComponent({ config, value: { data } });
expect(config.fetchData).toHaveBeenCalledWith(data);
});
});
describe('when the input changes', () => {
const data = 'Donkey Kong';
it('calls `fetchData` with the updated search term', () => {
createComponent({ config, value: defaultValue }, { stubs: { GlFilteredSearchToken } });
expect(config.fetchData).not.toHaveBeenCalledWith(data);
findFilteredSearchToken().vm.$emit('input', { data });
expect(config.fetchData).toHaveBeenCalledWith(data);
});
});
});
}); });
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