Commit cb98fe72 authored by Zack Cuddy's avatar Zack Cuddy Committed by Andrew Fontaine

Global Search - Recent Groups/Projects

parent 59abe43f
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants';
export const frequentGroups = (state) => {
return state.frequentItems[GROUPS_LOCAL_STORAGE_KEY];
};
export const frequentProjects = (state) => {
return state.frequentItems[PROJECTS_LOCAL_STORAGE_KEY];
};
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import * as actions from './actions'; import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations'; import mutations from './mutations';
import createState from './state'; import createState from './state';
...@@ -8,6 +9,7 @@ Vue.use(Vuex); ...@@ -8,6 +9,7 @@ Vue.use(Vuex);
export const getStoreConfig = ({ query }) => ({ export const getStoreConfig = ({ query }) => ({
actions, actions,
getters,
mutations, mutations,
state: createState({ query }), state: createState({ query }),
}); });
......
...@@ -24,7 +24,15 @@ export const setFrequentItemToLS = (key, data, itemData) => { ...@@ -24,7 +24,15 @@ export const setFrequentItemToLS = (key, data, itemData) => {
return; return;
} }
const keyList = ['id', 'avatar_url', 'name', 'full_name', 'name_with_namespace', 'frequency']; const keyList = [
'id',
'avatar_url',
'name',
'full_name',
'name_with_namespace',
'frequency',
'lastUsed',
];
try { try {
const frequentItems = data[key].map((obj) => extractKeys(obj, keyList)); const frequentItems = data[key].map((obj) => extractKeys(obj, keyList));
...@@ -35,17 +43,25 @@ export const setFrequentItemToLS = (key, data, itemData) => { ...@@ -35,17 +43,25 @@ export const setFrequentItemToLS = (key, data, itemData) => {
// Up the frequency (Max 5) // Up the frequency (Max 5)
const currentFrequency = frequentItems[existingItemIndex].frequency; const currentFrequency = frequentItems[existingItemIndex].frequency;
frequentItems[existingItemIndex].frequency = Math.min(currentFrequency + 1, MAX_FREQUENCY); frequentItems[existingItemIndex].frequency = Math.min(currentFrequency + 1, MAX_FREQUENCY);
frequentItems[existingItemIndex].lastUsed = new Date().getTime();
} else { } else {
// Only store a max of 5 items // Only store a max of 5 items
if (frequentItems.length >= MAX_FREQUENT_ITEMS) { if (frequentItems.length >= MAX_FREQUENT_ITEMS) {
frequentItems.pop(); frequentItems.pop();
} }
frequentItems.push({ ...item, frequency: 1 }); frequentItems.push({ ...item, frequency: 1, lastUsed: new Date().getTime() });
} }
// Sort by frequency // Sort by frequency and lastUsed
frequentItems.sort((a, b) => b.frequency - a.frequency); frequentItems.sort((a, b) => {
if (a.frequency > b.frequency) {
return -1;
} else if (a.frequency < b.frequency) {
return 1;
}
return b.lastUsed - a.lastUsed;
});
// Note we do not need to commit a mutation here as immediately after this we refresh the page to // Note we do not need to commit a mutation here as immediately after this we refresh the page to
// update the search results. // update the search results.
......
<script> <script>
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions, mapGetters } from 'vuex';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '../constants'; import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '../constants';
import SearchableDropdown from './searchable_dropdown.vue'; import SearchableDropdown from './searchable_dropdown.vue';
...@@ -19,6 +19,7 @@ export default { ...@@ -19,6 +19,7 @@ export default {
}, },
computed: { computed: {
...mapState(['groups', 'fetchingGroups']), ...mapState(['groups', 'fetchingGroups']),
...mapGetters(['frequentGroups']),
selectedGroup() { selectedGroup() {
return isEmpty(this.initialData) ? ANY_OPTION : this.initialData; return isEmpty(this.initialData) ? ANY_OPTION : this.initialData;
}, },
...@@ -49,6 +50,7 @@ export default { ...@@ -49,6 +50,7 @@ export default {
:loading="fetchingGroups" :loading="fetchingGroups"
:selected-item="selectedGroup" :selected-item="selectedGroup"
:items="groups" :items="groups"
:frequent-items="frequentGroups"
@first-open="loadFrequentGroups" @first-open="loadFrequentGroups"
@search="fetchGroups" @search="fetchGroups"
@change="handleGroupChange" @change="handleGroupChange"
......
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions, mapGetters } from 'vuex';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '../constants'; import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '../constants';
import SearchableDropdown from './searchable_dropdown.vue'; import SearchableDropdown from './searchable_dropdown.vue';
...@@ -18,6 +18,7 @@ export default { ...@@ -18,6 +18,7 @@ export default {
}, },
computed: { computed: {
...mapState(['projects', 'fetchingProjects']), ...mapState(['projects', 'fetchingProjects']),
...mapGetters(['frequentProjects']),
selectedProject() { selectedProject() {
return this.initialData ? this.initialData : ANY_OPTION; return this.initialData ? this.initialData : ANY_OPTION;
}, },
...@@ -52,6 +53,7 @@ export default { ...@@ -52,6 +53,7 @@ export default {
:loading="fetchingProjects" :loading="fetchingProjects"
:selected-item="selectedProject" :selected-item="selectedProject"
:items="projects" :items="projects"
:frequent-items="frequentProjects"
@first-open="loadFrequentProjects" @first-open="loadFrequentProjects"
@search="fetchProjects" @search="fetchProjects"
@change="handleProjectChange" @change="handleProjectChange"
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import { import {
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlDropdownSectionHeader,
GlSearchBoxByType, GlSearchBoxByType,
GlLoadingIcon, GlLoadingIcon,
GlIcon, GlIcon,
...@@ -16,11 +17,13 @@ import SearchableDropdownItem from './searchable_dropdown_item.vue'; ...@@ -16,11 +17,13 @@ import SearchableDropdownItem from './searchable_dropdown_item.vue';
export default { export default {
i18n: { i18n: {
clearLabel: __('Clear'), clearLabel: __('Clear'),
frequentlySearched: __('Frequently searched'),
}, },
name: 'SearchableDropdown', name: 'SearchableDropdown',
components: { components: {
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlDropdownSectionHeader,
GlSearchBoxByType, GlSearchBoxByType,
GlLoadingIcon, GlLoadingIcon,
GlIcon, GlIcon,
...@@ -61,6 +64,11 @@ export default { ...@@ -61,6 +64,11 @@ export default {
required: false, required: false,
default: () => [], default: () => [],
}, },
frequentItems: {
type: Array,
required: false,
default: () => [],
},
}, },
data() { data() {
return { return {
...@@ -68,6 +76,11 @@ export default { ...@@ -68,6 +76,11 @@ export default {
hasBeenOpened: false, hasBeenOpened: false,
}; };
}, },
computed: {
showFrequentItems() {
return !this.searchText && this.frequentItems.length > 0;
},
},
methods: { methods: {
isSelected(selected) { isSelected(selected) {
return selected.id === this.selectedItem.id; return selected.id === this.selectedItem.id;
...@@ -139,6 +152,25 @@ export default { ...@@ -139,6 +152,25 @@ export default {
<span data-testid="item-title">{{ $options.ANY_OPTION.name }}</span> <span data-testid="item-title">{{ $options.ANY_OPTION.name }}</span>
</gl-dropdown-item> </gl-dropdown-item>
</div> </div>
<div
v-if="showFrequentItems"
class="gl-border-b-solid gl-border-b-gray-100 gl-border-b-1 gl-pb-2 gl-mb-2"
>
<gl-dropdown-section-header>{{
$options.i18n.frequentlySearched
}}</gl-dropdown-section-header>
<searchable-dropdown-item
v-for="item in frequentItems"
:key="item.id"
:item="item"
:selected-item="selectedItem"
:search-text="searchText"
:name="name"
:full-name="fullName"
data-testid="frequent-items"
@change="updateDropdown"
/>
</div>
<div v-if="!loading"> <div v-if="!loading">
<searchable-dropdown-item <searchable-dropdown-item
v-for="item in items" v-for="item in items"
...@@ -148,6 +180,7 @@ export default { ...@@ -148,6 +180,7 @@ export default {
:search-text="searchText" :search-text="searchText"
:name="name" :name="name"
:full-name="fullName" :full-name="fullName"
data-testid="searchable-items"
@change="updateDropdown" @change="updateDropdown"
/> />
</div> </div>
......
...@@ -14359,6 +14359,9 @@ msgstr "" ...@@ -14359,6 +14359,9 @@ msgstr ""
msgid "Frequency" msgid "Frequency"
msgstr "" msgstr ""
msgid "Frequently searched"
msgstr ""
msgid "Friday" msgid "Friday"
msgstr "" msgstr ""
......
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from '~/search/store/constants';
import * as getters from '~/search/store/getters';
import createState from '~/search/store/state';
import { MOCK_QUERY, MOCK_GROUPS, MOCK_PROJECTS } from '../mock_data';
describe('Global Search Store Getters', () => {
let state;
beforeEach(() => {
state = createState({ query: MOCK_QUERY });
});
describe('frequentGroups', () => {
beforeEach(() => {
state.frequentItems[GROUPS_LOCAL_STORAGE_KEY] = MOCK_GROUPS;
});
it('returns the correct data', () => {
expect(getters.frequentGroups(state)).toStrictEqual(MOCK_GROUPS);
});
});
describe('frequentProjects', () => {
beforeEach(() => {
state.frequentItems[PROJECTS_LOCAL_STORAGE_KEY] = MOCK_PROJECTS;
});
it('returns the correct data', () => {
expect(getters.frequentProjects(state)).toStrictEqual(MOCK_PROJECTS);
});
});
});
...@@ -9,6 +9,9 @@ import { ...@@ -9,6 +9,9 @@ import {
STALE_STORED_DATA, STALE_STORED_DATA,
} from '../mock_data'; } from '../mock_data';
const PREV_TIME = new Date().getTime() - 1;
const CURRENT_TIME = new Date().getTime();
useLocalStorageSpy(); useLocalStorageSpy();
jest.mock('~/lib/utils/accessor', () => ({ jest.mock('~/lib/utils/accessor', () => ({
isLocalStorageAccessSafe: jest.fn().mockReturnValue(true), isLocalStorageAccessSafe: jest.fn().mockReturnValue(true),
...@@ -52,28 +55,32 @@ describe('Global Search Store Utils', () => { ...@@ -52,28 +55,32 @@ describe('Global Search Store Utils', () => {
describe('with existing data', () => { describe('with existing data', () => {
describe(`when frequency is less than ${MAX_FREQUENCY}`, () => { describe(`when frequency is less than ${MAX_FREQUENCY}`, () => {
beforeEach(() => { beforeEach(() => {
frequentItems[MOCK_LS_KEY] = [{ ...MOCK_GROUPS[0], frequency: 1 }]; frequentItems[MOCK_LS_KEY] = [{ ...MOCK_GROUPS[0], frequency: 1, lastUsed: PREV_TIME }];
setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]); setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]);
}); });
it('adds 1 to the frequency and calls localStorage.setItem', () => { it('adds 1 to the frequency, tracks lastUsed, and calls localStorage.setItem', () => {
expect(localStorage.setItem).toHaveBeenCalledWith( expect(localStorage.setItem).toHaveBeenCalledWith(
MOCK_LS_KEY, MOCK_LS_KEY,
JSON.stringify([{ ...MOCK_GROUPS[0], frequency: 2 }]), JSON.stringify([{ ...MOCK_GROUPS[0], frequency: 2, lastUsed: CURRENT_TIME }]),
); );
}); });
}); });
describe(`when frequency is equal to ${MAX_FREQUENCY}`, () => { describe(`when frequency is equal to ${MAX_FREQUENCY}`, () => {
beforeEach(() => { beforeEach(() => {
frequentItems[MOCK_LS_KEY] = [{ ...MOCK_GROUPS[0], frequency: MAX_FREQUENCY }]; frequentItems[MOCK_LS_KEY] = [
{ ...MOCK_GROUPS[0], frequency: MAX_FREQUENCY, lastUsed: PREV_TIME },
];
setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]); setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]);
}); });
it(`does not further increase frequency past ${MAX_FREQUENCY} and calls localStorage.setItem`, () => { it(`does not further increase frequency past ${MAX_FREQUENCY}, tracks lastUsed, and calls localStorage.setItem`, () => {
expect(localStorage.setItem).toHaveBeenCalledWith( expect(localStorage.setItem).toHaveBeenCalledWith(
MOCK_LS_KEY, MOCK_LS_KEY,
JSON.stringify([{ ...MOCK_GROUPS[0], frequency: MAX_FREQUENCY }]), JSON.stringify([
{ ...MOCK_GROUPS[0], frequency: MAX_FREQUENCY, lastUsed: CURRENT_TIME },
]),
); );
}); });
}); });
...@@ -85,10 +92,10 @@ describe('Global Search Store Utils', () => { ...@@ -85,10 +92,10 @@ describe('Global Search Store Utils', () => {
setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]); setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[0]);
}); });
it('adds a new entry with frequency 1 and calls localStorage.setItem', () => { it('adds a new entry with frequency 1, tracks lastUsed, and calls localStorage.setItem', () => {
expect(localStorage.setItem).toHaveBeenCalledWith( expect(localStorage.setItem).toHaveBeenCalledWith(
MOCK_LS_KEY, MOCK_LS_KEY,
JSON.stringify([{ ...MOCK_GROUPS[0], frequency: 1 }]), JSON.stringify([{ ...MOCK_GROUPS[0], frequency: 1, lastUsed: CURRENT_TIME }]),
); );
}); });
}); });
...@@ -96,18 +103,20 @@ describe('Global Search Store Utils', () => { ...@@ -96,18 +103,20 @@ describe('Global Search Store Utils', () => {
describe('with multiple entries', () => { describe('with multiple entries', () => {
beforeEach(() => { beforeEach(() => {
frequentItems[MOCK_LS_KEY] = [ frequentItems[MOCK_LS_KEY] = [
{ ...MOCK_GROUPS[0], frequency: 1 }, { id: 1, frequency: 2, lastUsed: PREV_TIME },
{ ...MOCK_GROUPS[1], frequency: 1 }, { id: 2, frequency: 1, lastUsed: PREV_TIME },
{ id: 3, frequency: 1, lastUsed: PREV_TIME },
]; ];
setFrequentItemToLS(MOCK_LS_KEY, frequentItems, MOCK_GROUPS[1]); setFrequentItemToLS(MOCK_LS_KEY, frequentItems, { id: 3 });
}); });
it('sorts the array by most frequent', () => { it('sorts the array by most frequent and lastUsed', () => {
expect(localStorage.setItem).toHaveBeenCalledWith( expect(localStorage.setItem).toHaveBeenCalledWith(
MOCK_LS_KEY, MOCK_LS_KEY,
JSON.stringify([ JSON.stringify([
{ ...MOCK_GROUPS[1], frequency: 2 }, { id: 3, frequency: 2, lastUsed: CURRENT_TIME },
{ ...MOCK_GROUPS[0], frequency: 1 }, { id: 1, frequency: 2, lastUsed: PREV_TIME },
{ id: 2, frequency: 1, lastUsed: PREV_TIME },
]), ]),
); );
}); });
...@@ -116,24 +125,24 @@ describe('Global Search Store Utils', () => { ...@@ -116,24 +125,24 @@ describe('Global Search Store Utils', () => {
describe('with max entries', () => { describe('with max entries', () => {
beforeEach(() => { beforeEach(() => {
frequentItems[MOCK_LS_KEY] = [ frequentItems[MOCK_LS_KEY] = [
{ id: 1, frequency: 5 }, { id: 1, frequency: 5, lastUsed: PREV_TIME },
{ id: 2, frequency: 4 }, { id: 2, frequency: 4, lastUsed: PREV_TIME },
{ id: 3, frequency: 3 }, { id: 3, frequency: 3, lastUsed: PREV_TIME },
{ id: 4, frequency: 2 }, { id: 4, frequency: 2, lastUsed: PREV_TIME },
{ id: 5, frequency: 1 }, { id: 5, frequency: 1, lastUsed: PREV_TIME },
]; ];
setFrequentItemToLS(MOCK_LS_KEY, frequentItems, { id: 6 }); setFrequentItemToLS(MOCK_LS_KEY, frequentItems, { id: 6 });
}); });
it('removes the least frequent', () => { it('removes the last item in the array', () => {
expect(localStorage.setItem).toHaveBeenCalledWith( expect(localStorage.setItem).toHaveBeenCalledWith(
MOCK_LS_KEY, MOCK_LS_KEY,
JSON.stringify([ JSON.stringify([
{ id: 1, frequency: 5 }, { id: 1, frequency: 5, lastUsed: PREV_TIME },
{ id: 2, frequency: 4 }, { id: 2, frequency: 4, lastUsed: PREV_TIME },
{ id: 3, frequency: 3 }, { id: 3, frequency: 3, lastUsed: PREV_TIME },
{ id: 4, frequency: 2 }, { id: 4, frequency: 2, lastUsed: PREV_TIME },
{ id: 6, frequency: 1 }, { id: 6, frequency: 1, lastUsed: CURRENT_TIME },
]), ]),
); );
}); });
...@@ -160,7 +169,7 @@ describe('Global Search Store Utils', () => { ...@@ -160,7 +169,7 @@ describe('Global Search Store Utils', () => {
it('parses out extra data for LS', () => { it('parses out extra data for LS', () => {
expect(localStorage.setItem).toHaveBeenCalledWith( expect(localStorage.setItem).toHaveBeenCalledWith(
MOCK_LS_KEY, MOCK_LS_KEY,
JSON.stringify([{ ...MOCK_GROUPS[0], frequency: 1 }]), JSON.stringify([{ ...MOCK_GROUPS[0], frequency: 1, lastUsed: CURRENT_TIME }]),
); );
}); });
}); });
......
...@@ -35,6 +35,9 @@ describe('GroupFilter', () => { ...@@ -35,6 +35,9 @@ describe('GroupFilter', () => {
...initialState, ...initialState,
}, },
actions: actionSpies, actions: actionSpies,
getters: {
frequentGroups: () => [],
},
}); });
wrapper = shallowMount(GroupFilter, { wrapper = shallowMount(GroupFilter, {
......
...@@ -35,6 +35,9 @@ describe('ProjectFilter', () => { ...@@ -35,6 +35,9 @@ describe('ProjectFilter', () => {
...initialState, ...initialState,
}, },
actions: actionSpies, actions: actionSpies,
getters: {
frequentProjects: () => [],
},
}); });
wrapper = shallowMount(ProjectFilter, { wrapper = shallowMount(ProjectFilter, {
......
...@@ -2,9 +2,9 @@ import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlSkeletonLoader } from ...@@ -2,9 +2,9 @@ import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlSkeletonLoader } from
import { shallowMount, mount } from '@vue/test-utils'; import { shallowMount, mount } from '@vue/test-utils';
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { MOCK_GROUPS, MOCK_GROUP, MOCK_QUERY } from 'jest/search/mock_data'; import { MOCK_GROUPS, MOCK_GROUP, MOCK_QUERY } from 'jest/search/mock_data';
import SearchableDropdown from '~/search/topbar/components/searchable_dropdown.vue'; import SearchableDropdown from '~/search/topbar/components/searchable_dropdown.vue';
import SearchableDropdownItem from '~/search/topbar/components/searchable_dropdown_item.vue';
import { ANY_OPTION, GROUP_DATA } from '~/search/topbar/constants'; import { ANY_OPTION, GROUP_DATA } from '~/search/topbar/constants';
Vue.use(Vuex); Vue.use(Vuex);
...@@ -29,13 +29,15 @@ describe('Global Search Searchable Dropdown', () => { ...@@ -29,13 +29,15 @@ describe('Global Search Searchable Dropdown', () => {
}, },
}); });
wrapper = mountFn(SearchableDropdown, { wrapper = extendedWrapper(
store, mountFn(SearchableDropdown, {
propsData: { store,
...defaultProps, propsData: {
...props, ...defaultProps,
}, ...props,
}); },
}),
);
}; };
afterEach(() => { afterEach(() => {
...@@ -45,10 +47,11 @@ describe('Global Search Searchable Dropdown', () => { ...@@ -45,10 +47,11 @@ describe('Global Search Searchable Dropdown', () => {
const findGlDropdown = () => wrapper.findComponent(GlDropdown); const findGlDropdown = () => wrapper.findComponent(GlDropdown);
const findGlDropdownSearch = () => findGlDropdown().findComponent(GlSearchBoxByType); const findGlDropdownSearch = () => findGlDropdown().findComponent(GlSearchBoxByType);
const findDropdownText = () => findGlDropdown().find('.dropdown-toggle-text'); const findDropdownText = () => findGlDropdown().find('.dropdown-toggle-text');
const findSearchableDropdownItems = () => const findSearchableDropdownItems = () => wrapper.findAllByTestId('searchable-items');
findGlDropdown().findAllComponents(SearchableDropdownItem); const findFrequentDropdownItems = () => wrapper.findAllByTestId('frequent-items');
const findAnyDropdownItem = () => findGlDropdown().findComponent(GlDropdownItem); const findAnyDropdownItem = () => findGlDropdown().findComponent(GlDropdownItem);
const findFirstGroupDropdownItem = () => findSearchableDropdownItems().at(0); const findFirstSearchableDropdownItem = () => findSearchableDropdownItems().at(0);
const findFirstFrequentDropdownItem = () => findFrequentDropdownItems().at(0);
const findLoader = () => wrapper.findComponent(GlSkeletonLoader); const findLoader = () => wrapper.findComponent(GlSkeletonLoader);
describe('template', () => { describe('template', () => {
...@@ -82,7 +85,7 @@ describe('Global Search Searchable Dropdown', () => { ...@@ -82,7 +85,7 @@ describe('Global Search Searchable Dropdown', () => {
}); });
}); });
describe('findDropdownItems', () => { describe('Searchable Dropdown Items', () => {
describe('when loading is false', () => { describe('when loading is false', () => {
beforeEach(() => { beforeEach(() => {
createComponent({}, { items: MOCK_GROUPS }); createComponent({}, { items: MOCK_GROUPS });
...@@ -96,7 +99,7 @@ describe('Global Search Searchable Dropdown', () => { ...@@ -96,7 +99,7 @@ describe('Global Search Searchable Dropdown', () => {
expect(findAnyDropdownItem().exists()).toBe(true); expect(findAnyDropdownItem().exists()).toBe(true);
}); });
it('renders SearchableDropdownItem for each item', () => { it('renders searchable dropdown item for each item', () => {
expect(findSearchableDropdownItems()).toHaveLength(MOCK_GROUPS.length); expect(findSearchableDropdownItems()).toHaveLength(MOCK_GROUPS.length);
}); });
}); });
...@@ -114,12 +117,31 @@ describe('Global Search Searchable Dropdown', () => { ...@@ -114,12 +117,31 @@ describe('Global Search Searchable Dropdown', () => {
expect(findAnyDropdownItem().exists()).toBe(true); expect(findAnyDropdownItem().exists()).toBe(true);
}); });
it('does not render SearchableDropdownItem', () => { it('does not render searchable dropdown items', () => {
expect(findSearchableDropdownItems()).toHaveLength(0); expect(findSearchableDropdownItems()).toHaveLength(0);
}); });
}); });
}); });
describe.each`
searchText | frequentItems | length
${''} | ${[]} | ${0}
${''} | ${MOCK_GROUPS} | ${MOCK_GROUPS.length}
${'test'} | ${[]} | ${0}
${'test'} | ${MOCK_GROUPS} | ${0}
`('Frequent Dropdown Items', ({ searchText, frequentItems, length }) => {
describe(`when search is ${searchText} and frequentItems length is ${frequentItems.length}`, () => {
beforeEach(() => {
createComponent({}, { frequentItems });
wrapper.setData({ searchText });
});
it(`should${length ? '' : ' not'} render frequent dropdown items`, () => {
expect(findFrequentDropdownItems()).toHaveLength(length);
});
});
});
describe('Dropdown Text', () => { describe('Dropdown Text', () => {
describe('when selectedItem is any', () => { describe('when selectedItem is any', () => {
beforeEach(() => { beforeEach(() => {
...@@ -145,7 +167,7 @@ describe('Global Search Searchable Dropdown', () => { ...@@ -145,7 +167,7 @@ describe('Global Search Searchable Dropdown', () => {
describe('actions', () => { describe('actions', () => {
beforeEach(() => { beforeEach(() => {
createComponent({}, { items: MOCK_GROUPS }); createComponent({}, { items: MOCK_GROUPS, frequentItems: MOCK_GROUPS });
}); });
it('clicking "Any" dropdown item $emits @change with ANY_OPTION', () => { it('clicking "Any" dropdown item $emits @change with ANY_OPTION', () => {
...@@ -154,8 +176,14 @@ describe('Global Search Searchable Dropdown', () => { ...@@ -154,8 +176,14 @@ describe('Global Search Searchable Dropdown', () => {
expect(wrapper.emitted('change')[0]).toEqual([ANY_OPTION]); expect(wrapper.emitted('change')[0]).toEqual([ANY_OPTION]);
}); });
it('on SearchableDropdownItem @change, the wrapper $emits change with the item', () => { it('on searchable item @change, the wrapper $emits change with the item', () => {
findFirstGroupDropdownItem().vm.$emit('change', MOCK_GROUPS[0]); findFirstSearchableDropdownItem().vm.$emit('change', MOCK_GROUPS[0]);
expect(wrapper.emitted('change')[0]).toEqual([MOCK_GROUPS[0]]);
});
it('on frequent item @change, the wrapper $emits change with the item', () => {
findFirstFrequentDropdownItem().vm.$emit('change', MOCK_GROUPS[0]);
expect(wrapper.emitted('change')[0]).toEqual([MOCK_GROUPS[0]]); expect(wrapper.emitted('change')[0]).toEqual([MOCK_GROUPS[0]]);
}); });
......
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