Commit 20197497 authored by Enrique Alcántara's avatar Enrique Alcántara

Merge branch '297396_05_1-gldropdown-topbar-keyboard-nav' into 'master'

Global Search - Header Search Keyboard Control

See merge request gitlab-org/gitlab!74244
parents 74797190 b0014825
...@@ -3,6 +3,8 @@ import { GlSearchBoxByType, GlOutsideDirective as Outside } from '@gitlab/ui'; ...@@ -3,6 +3,8 @@ import { GlSearchBoxByType, GlOutsideDirective as Outside } from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex'; import { mapState, mapActions, mapGetters } from 'vuex';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale'; import { __ } from '~/locale';
import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue';
import { FIRST_DROPDOWN_INDEX, SEARCH_BOX_INDEX } from '../constants';
import HeaderSearchAutocompleteItems from './header_search_autocomplete_items.vue'; import HeaderSearchAutocompleteItems from './header_search_autocomplete_items.vue';
import HeaderSearchDefaultItems from './header_search_default_items.vue'; import HeaderSearchDefaultItems from './header_search_default_items.vue';
import HeaderSearchScopedItems from './header_search_scoped_items.vue'; import HeaderSearchScopedItems from './header_search_scoped_items.vue';
...@@ -18,15 +20,17 @@ export default { ...@@ -18,15 +20,17 @@ export default {
HeaderSearchDefaultItems, HeaderSearchDefaultItems,
HeaderSearchScopedItems, HeaderSearchScopedItems,
HeaderSearchAutocompleteItems, HeaderSearchAutocompleteItems,
DropdownKeyboardNavigation,
}, },
data() { data() {
return { return {
showDropdown: false, showDropdown: false,
currentFocusIndex: SEARCH_BOX_INDEX,
}; };
}, },
computed: { computed: {
...mapState(['search']), ...mapState(['search']),
...mapGetters(['searchQuery']), ...mapGetters(['searchQuery', 'searchOptions']),
searchText: { searchText: {
get() { get() {
return this.search; return this.search;
...@@ -35,12 +39,25 @@ export default { ...@@ -35,12 +39,25 @@ export default {
this.setSearch(value); this.setSearch(value);
}, },
}, },
currentFocusedOption() {
return this.searchOptions[this.currentFocusIndex];
},
isLoggedIn() {
return gon?.current_username;
},
showSearchDropdown() { showSearchDropdown() {
return this.showDropdown && gon?.current_username; return this.showDropdown && this.isLoggedIn;
}, },
showDefaultItems() { showDefaultItems() {
return !this.searchText; return !this.searchText;
}, },
defaultIndex() {
if (this.showDefaultItems) {
return SEARCH_BOX_INDEX;
}
return FIRST_DROPDOWN_INDEX;
},
}, },
methods: { methods: {
...mapActions(['setSearch', 'fetchAutocompleteOptions']), ...mapActions(['setSearch', 'fetchAutocompleteOptions']),
...@@ -51,7 +68,7 @@ export default { ...@@ -51,7 +68,7 @@ export default {
this.showDropdown = false; this.showDropdown = false;
}, },
submitSearch() { submitSearch() {
return visitUrl(this.searchQuery); return visitUrl(this.currentFocusedOption?.url || this.searchQuery);
}, },
getAutocompleteOptions(searchTerm) { getAutocompleteOptions(searchTerm) {
if (!searchTerm) { if (!searchTerm) {
...@@ -61,6 +78,7 @@ export default { ...@@ -61,6 +78,7 @@ export default {
this.fetchAutocompleteOptions(); this.fetchAutocompleteOptions();
}, },
}, },
SEARCH_BOX_INDEX,
}; };
</script> </script>
...@@ -68,14 +86,14 @@ export default { ...@@ -68,14 +86,14 @@ export default {
<section v-outside="closeDropdown" class="header-search gl-relative"> <section v-outside="closeDropdown" class="header-search gl-relative">
<gl-search-box-by-type <gl-search-box-by-type
v-model="searchText" v-model="searchText"
class="gl-z-index-1"
:debounce="500" :debounce="500"
autocomplete="off" autocomplete="off"
:placeholder="$options.i18n.searchPlaceholder" :placeholder="$options.i18n.searchPlaceholder"
@focus="openDropdown" @focus="openDropdown"
@click="openDropdown" @click="openDropdown"
@input="getAutocompleteOptions" @input="getAutocompleteOptions"
@keydown.enter="submitSearch" @keydown.enter.stop.prevent="submitSearch"
@keydown.esc="closeDropdown"
/> />
<div <div
v-if="showSearchDropdown" v-if="showSearchDropdown"
...@@ -83,10 +101,20 @@ export default { ...@@ -83,10 +101,20 @@ export default {
class="header-search-dropdown-menu gl-absolute gl-w-full gl-bg-white gl-border-1 gl-rounded-base gl-border-solid gl-border-gray-200 gl-shadow-x0-y2-b4-s0" class="header-search-dropdown-menu gl-absolute gl-w-full gl-bg-white gl-border-1 gl-rounded-base gl-border-solid gl-border-gray-200 gl-shadow-x0-y2-b4-s0"
> >
<div class="header-search-dropdown-content gl-overflow-y-auto gl-py-2"> <div class="header-search-dropdown-content gl-overflow-y-auto gl-py-2">
<header-search-default-items v-if="showDefaultItems" /> <dropdown-keyboard-navigation
v-model="currentFocusIndex"
:max="searchOptions.length - 1"
:min="$options.SEARCH_BOX_INDEX"
:default-index="defaultIndex"
@tab="closeDropdown"
/>
<header-search-default-items
v-if="showDefaultItems"
:current-focused-option="currentFocusedOption"
/>
<template v-else> <template v-else>
<header-search-scoped-items /> <header-search-scoped-items :current-focused-option="currentFocusedOption" />
<header-search-autocomplete-items /> <header-search-autocomplete-items :current-focused-option="currentFocusedOption" />
</template> </template>
</div> </div>
</div> </div>
......
...@@ -23,10 +23,26 @@ export default { ...@@ -23,10 +23,26 @@ export default {
directives: { directives: {
SafeHtml, SafeHtml,
}, },
props: {
currentFocusedOption: {
type: Object,
required: false,
default: () => null,
},
},
computed: { computed: {
...mapState(['search', 'loading']), ...mapState(['search', 'loading']),
...mapGetters(['autocompleteGroupedSearchOptions']), ...mapGetters(['autocompleteGroupedSearchOptions']),
}, },
watch: {
currentFocusedOption() {
const focusedElement = this.$refs[this.currentFocusedOption?.html_id]?.[0]?.$el;
if (focusedElement) {
focusedElement.scrollIntoView(false);
}
},
},
methods: { methods: {
highlightedName(val) { highlightedName(val) {
return highlight(val, this.search); return highlight(val, this.search);
...@@ -38,6 +54,9 @@ export default { ...@@ -38,6 +54,9 @@ export default {
return SMALL_AVATAR_PX; return SMALL_AVATAR_PX;
}, },
isOptionFocused(data) {
return this.currentFocusedOption?.html_id === data.html_id;
},
}, },
}; };
</script> </script>
...@@ -49,9 +68,10 @@ export default { ...@@ -49,9 +68,10 @@ export default {
<gl-dropdown-divider /> <gl-dropdown-divider />
<gl-dropdown-section-header>{{ option.category }}</gl-dropdown-section-header> <gl-dropdown-section-header>{{ option.category }}</gl-dropdown-section-header>
<gl-dropdown-item <gl-dropdown-item
v-for="(data, index) in option.data" v-for="data in option.data"
:id="`autocomplete-${option.category}-${index}`" :ref="data.html_id"
:key="index" :key="data.html_id"
:class="{ 'gl-bg-gray-50': isOptionFocused(data) }"
tabindex="-1" tabindex="-1"
:href="data.url" :href="data.url"
> >
......
...@@ -12,6 +12,13 @@ export default { ...@@ -12,6 +12,13 @@ export default {
GlDropdownSectionHeader, GlDropdownSectionHeader,
GlDropdownItem, GlDropdownItem,
}, },
props: {
currentFocusedOption: {
type: Object,
required: false,
default: () => null,
},
},
computed: { computed: {
...mapState(['searchContext']), ...mapState(['searchContext']),
...mapGetters(['defaultSearchOptions']), ...mapGetters(['defaultSearchOptions']),
...@@ -23,6 +30,11 @@ export default { ...@@ -23,6 +30,11 @@ export default {
); );
}, },
}, },
methods: {
isOptionFocused(option) {
return this.currentFocusedOption?.html_id === option.html_id;
},
},
}; };
</script> </script>
...@@ -30,9 +42,10 @@ export default { ...@@ -30,9 +42,10 @@ export default {
<div> <div>
<gl-dropdown-section-header>{{ sectionHeader }}</gl-dropdown-section-header> <gl-dropdown-section-header>{{ sectionHeader }}</gl-dropdown-section-header>
<gl-dropdown-item <gl-dropdown-item
v-for="(option, index) in defaultSearchOptions" v-for="option in defaultSearchOptions"
:id="`default-${index}`" :ref="option.html_id"
:key="index" :key="option.html_id"
:class="{ 'gl-bg-gray-50': isOptionFocused(option) }"
tabindex="-1" tabindex="-1"
:href="option.url" :href="option.url"
> >
......
...@@ -7,19 +7,32 @@ export default { ...@@ -7,19 +7,32 @@ export default {
components: { components: {
GlDropdownItem, GlDropdownItem,
}, },
props: {
currentFocusedOption: {
type: Object,
required: false,
default: () => null,
},
},
computed: { computed: {
...mapState(['search']), ...mapState(['search']),
...mapGetters(['scopedSearchOptions']), ...mapGetters(['scopedSearchOptions']),
}, },
methods: {
isOptionFocused(option) {
return this.currentFocusedOption?.html_id === option.html_id;
},
},
}; };
</script> </script>
<template> <template>
<div> <div>
<gl-dropdown-item <gl-dropdown-item
v-for="(option, index) in scopedSearchOptions" v-for="option in scopedSearchOptions"
:id="`scoped-${index}`" :ref="option.html_id"
:key="index" :key="option.html_id"
:class="{ 'gl-bg-gray-50': isOptionFocused(option) }"
tabindex="-1" tabindex="-1"
:href="option.url" :href="option.url"
> >
......
...@@ -23,3 +23,7 @@ export const PROJECTS_CATEGORY = 'Projects'; ...@@ -23,3 +23,7 @@ export const PROJECTS_CATEGORY = 'Projects';
export const LARGE_AVATAR_PX = 32; export const LARGE_AVATAR_PX = 32;
export const SMALL_AVATAR_PX = 16; export const SMALL_AVATAR_PX = 16;
export const FIRST_DROPDOWN_INDEX = 0;
export const SEARCH_BOX_INDEX = -1;
...@@ -54,22 +54,27 @@ export const defaultSearchOptions = (state, getters) => { ...@@ -54,22 +54,27 @@ export const defaultSearchOptions = (state, getters) => {
return [ return [
{ {
html_id: 'default-issues-assigned',
title: MSG_ISSUES_ASSIGNED_TO_ME, title: MSG_ISSUES_ASSIGNED_TO_ME,
url: `${getters.scopedIssuesPath}/?assignee_username=${userName}`, url: `${getters.scopedIssuesPath}/?assignee_username=${userName}`,
}, },
{ {
html_id: 'default-issues-created',
title: MSG_ISSUES_IVE_CREATED, title: MSG_ISSUES_IVE_CREATED,
url: `${getters.scopedIssuesPath}/?author_username=${userName}`, url: `${getters.scopedIssuesPath}/?author_username=${userName}`,
}, },
{ {
html_id: 'default-mrs-assigned',
title: MSG_MR_ASSIGNED_TO_ME, title: MSG_MR_ASSIGNED_TO_ME,
url: `${getters.scopedMRPath}/?assignee_username=${userName}`, url: `${getters.scopedMRPath}/?assignee_username=${userName}`,
}, },
{ {
html_id: 'default-mrs-reviewer',
title: MSG_MR_IM_REVIEWER, title: MSG_MR_IM_REVIEWER,
url: `${getters.scopedMRPath}/?reviewer_username=${userName}`, url: `${getters.scopedMRPath}/?reviewer_username=${userName}`,
}, },
{ {
html_id: 'default-mrs-created',
title: MSG_MR_IVE_CREATED, title: MSG_MR_IVE_CREATED,
url: `${getters.scopedMRPath}/?author_username=${userName}`, url: `${getters.scopedMRPath}/?author_username=${userName}`,
}, },
...@@ -122,6 +127,7 @@ export const scopedSearchOptions = (state, getters) => { ...@@ -122,6 +127,7 @@ export const scopedSearchOptions = (state, getters) => {
if (state.searchContext.project) { if (state.searchContext.project) {
options.push({ options.push({
html_id: 'scoped-in-project',
scope: state.searchContext.project.name, scope: state.searchContext.project.name,
description: MSG_IN_PROJECT, description: MSG_IN_PROJECT,
url: getters.projectUrl, url: getters.projectUrl,
...@@ -130,6 +136,7 @@ export const scopedSearchOptions = (state, getters) => { ...@@ -130,6 +136,7 @@ export const scopedSearchOptions = (state, getters) => {
if (state.searchContext.group) { if (state.searchContext.group) {
options.push({ options.push({
html_id: 'scoped-in-group',
scope: state.searchContext.group.name, scope: state.searchContext.group.name,
description: MSG_IN_GROUP, description: MSG_IN_GROUP,
url: getters.groupUrl, url: getters.groupUrl,
...@@ -137,6 +144,7 @@ export const scopedSearchOptions = (state, getters) => { ...@@ -137,6 +144,7 @@ export const scopedSearchOptions = (state, getters) => {
} }
options.push({ options.push({
html_id: 'scoped-in-all',
description: MSG_IN_ALL_GITLAB, description: MSG_IN_ALL_GITLAB,
url: getters.allUrl, url: getters.allUrl,
}); });
...@@ -165,3 +173,18 @@ export const autocompleteGroupedSearchOptions = (state) => { ...@@ -165,3 +173,18 @@ export const autocompleteGroupedSearchOptions = (state) => {
return results; return results;
}; };
export const searchOptions = (state, getters) => {
if (!state.search) {
return getters.defaultSearchOptions;
}
const sortedAutocompleteOptions = Object.values(getters.autocompleteGroupedSearchOptions).reduce(
(options, group) => {
return [...options, ...group.data];
},
[],
);
return getters.scopedSearchOptions.concat(sortedAutocompleteOptions);
};
...@@ -7,7 +7,9 @@ export default { ...@@ -7,7 +7,9 @@ export default {
}, },
[types.RECEIVE_AUTOCOMPLETE_SUCCESS](state, data) { [types.RECEIVE_AUTOCOMPLETE_SUCCESS](state, data) {
state.loading = false; state.loading = false;
state.autocompleteOptions = data; state.autocompleteOptions = data.map((d, i) => {
return { html_id: `autocomplete-${d.category}-${i}`, ...d };
});
}, },
[types.RECEIVE_AUTOCOMPLETE_ERROR](state) { [types.RECEIVE_AUTOCOMPLETE_ERROR](state) {
state.loading = false; state.loading = false;
......
...@@ -6,9 +6,15 @@ import HeaderSearchApp from '~/header_search/components/app.vue'; ...@@ -6,9 +6,15 @@ import HeaderSearchApp from '~/header_search/components/app.vue';
import HeaderSearchAutocompleteItems from '~/header_search/components/header_search_autocomplete_items.vue'; import HeaderSearchAutocompleteItems from '~/header_search/components/header_search_autocomplete_items.vue';
import HeaderSearchDefaultItems from '~/header_search/components/header_search_default_items.vue'; import HeaderSearchDefaultItems from '~/header_search/components/header_search_default_items.vue';
import HeaderSearchScopedItems from '~/header_search/components/header_search_scoped_items.vue'; import HeaderSearchScopedItems from '~/header_search/components/header_search_scoped_items.vue';
import { ENTER_KEY, ESC_KEY } from '~/lib/utils/keys'; import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue';
import { ENTER_KEY } from '~/lib/utils/keys';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import { MOCK_SEARCH, MOCK_SEARCH_QUERY, MOCK_USERNAME } from '../mock_data'; import {
MOCK_SEARCH,
MOCK_SEARCH_QUERY,
MOCK_USERNAME,
MOCK_DEFAULT_SEARCH_OPTIONS,
} from '../mock_data';
Vue.use(Vuex); Vue.use(Vuex);
...@@ -24,7 +30,7 @@ describe('HeaderSearchApp', () => { ...@@ -24,7 +30,7 @@ describe('HeaderSearchApp', () => {
fetchAutocompleteOptions: jest.fn(), fetchAutocompleteOptions: jest.fn(),
}; };
const createComponent = (initialState) => { const createComponent = (initialState, mockGetters) => {
const store = new Vuex.Store({ const store = new Vuex.Store({
state: { state: {
...initialState, ...initialState,
...@@ -32,6 +38,8 @@ describe('HeaderSearchApp', () => { ...@@ -32,6 +38,8 @@ describe('HeaderSearchApp', () => {
actions: actionSpies, actions: actionSpies,
getters: { getters: {
searchQuery: () => MOCK_SEARCH_QUERY, searchQuery: () => MOCK_SEARCH_QUERY,
searchOptions: () => MOCK_DEFAULT_SEARCH_OPTIONS,
...mockGetters,
}, },
}); });
...@@ -50,6 +58,7 @@ describe('HeaderSearchApp', () => { ...@@ -50,6 +58,7 @@ describe('HeaderSearchApp', () => {
const findHeaderSearchScopedItems = () => wrapper.findComponent(HeaderSearchScopedItems); const findHeaderSearchScopedItems = () => wrapper.findComponent(HeaderSearchScopedItems);
const findHeaderSearchAutocompleteItems = () => const findHeaderSearchAutocompleteItems = () =>
wrapper.findComponent(HeaderSearchAutocompleteItems); wrapper.findComponent(HeaderSearchAutocompleteItems);
const findDropdownKeyboardNavigation = () => wrapper.findComponent(DropdownKeyboardNavigation);
describe('template', () => { describe('template', () => {
it('always renders Header Search Input', () => { it('always renders Header Search Input', () => {
...@@ -66,8 +75,8 @@ describe('HeaderSearchApp', () => { ...@@ -66,8 +75,8 @@ describe('HeaderSearchApp', () => {
`('Header Search Dropdown', ({ showDropdown, username, showSearchDropdown }) => { `('Header Search Dropdown', ({ showDropdown, username, showSearchDropdown }) => {
describe(`when showDropdown is ${showDropdown} and current_username is ${username}`, () => { describe(`when showDropdown is ${showDropdown} and current_username is ${username}`, () => {
beforeEach(() => { beforeEach(() => {
createComponent();
window.gon.current_username = username; window.gon.current_username = username;
createComponent();
wrapper.setData({ showDropdown }); wrapper.setData({ showDropdown });
}); });
...@@ -78,31 +87,42 @@ describe('HeaderSearchApp', () => { ...@@ -78,31 +87,42 @@ describe('HeaderSearchApp', () => {
}); });
describe.each` describe.each`
search | showDefault | showScoped | showAutocomplete search | showDefault | showScoped | showAutocomplete | showDropdownNavigation
${null} | ${true} | ${false} | ${false} ${null} | ${true} | ${false} | ${false} | ${true}
${''} | ${true} | ${false} | ${false} ${''} | ${true} | ${false} | ${false} | ${true}
${MOCK_SEARCH} | ${false} | ${true} | ${true} ${MOCK_SEARCH} | ${false} | ${true} | ${true} | ${true}
`('Header Search Dropdown Items', ({ search, showDefault, showScoped, showAutocomplete }) => { `(
describe(`when search is ${search}`, () => { 'Header Search Dropdown Items',
beforeEach(() => { ({ search, showDefault, showScoped, showAutocomplete, showDropdownNavigation }) => {
createComponent({ search }); describe(`when search is ${search}`, () => {
window.gon.current_username = MOCK_USERNAME; beforeEach(() => {
wrapper.setData({ showDropdown: true }); window.gon.current_username = MOCK_USERNAME;
}); createComponent({ search });
findHeaderSearchInput().vm.$emit('click');
it(`should${showDefault ? '' : ' not'} render the Default Dropdown Items`, () => { });
expect(findHeaderSearchDefaultItems().exists()).toBe(showDefault);
}); it(`should${showDefault ? '' : ' not'} render the Default Dropdown Items`, () => {
expect(findHeaderSearchDefaultItems().exists()).toBe(showDefault);
it(`should${showScoped ? '' : ' not'} render the Scoped Dropdown Items`, () => { });
expect(findHeaderSearchScopedItems().exists()).toBe(showScoped);
it(`should${showScoped ? '' : ' not'} render the Scoped Dropdown Items`, () => {
expect(findHeaderSearchScopedItems().exists()).toBe(showScoped);
});
it(`should${
showAutocomplete ? '' : ' not'
} render the Autocomplete Dropdown Items`, () => {
expect(findHeaderSearchAutocompleteItems().exists()).toBe(showAutocomplete);
});
it(`should${
showDropdownNavigation ? '' : ' not'
} render the Dropdown Navigation Component`, () => {
expect(findDropdownKeyboardNavigation().exists()).toBe(showDropdownNavigation);
});
}); });
},
it(`should${showAutocomplete ? '' : ' not'} render the Autocomplete Dropdown Items`, () => { );
expect(findHeaderSearchAutocompleteItems().exists()).toBe(showAutocomplete);
});
});
});
}); });
describe('events', () => { describe('events', () => {
...@@ -132,21 +152,6 @@ describe('HeaderSearchApp', () => { ...@@ -132,21 +152,6 @@ describe('HeaderSearchApp', () => {
}); });
}); });
describe('when dropdown is opened', () => {
beforeEach(() => {
wrapper.setData({ showDropdown: true });
});
it('onKey-Escape closes dropdown', async () => {
expect(findHeaderSearchDropdown().exists()).toBe(true);
findHeaderSearchInput().vm.$emit('keydown', new KeyboardEvent({ key: ESC_KEY }));
await wrapper.vm.$nextTick();
expect(findHeaderSearchDropdown().exists()).toBe(false);
});
});
describe('onInput', () => { describe('onInput', () => {
beforeEach(() => { beforeEach(() => {
findHeaderSearchInput().vm.$emit('input', MOCK_SEARCH); findHeaderSearchInput().vm.$emit('input', MOCK_SEARCH);
...@@ -160,8 +165,49 @@ describe('HeaderSearchApp', () => { ...@@ -160,8 +165,49 @@ describe('HeaderSearchApp', () => {
expect(actionSpies.fetchAutocompleteOptions).toHaveBeenCalled(); expect(actionSpies.fetchAutocompleteOptions).toHaveBeenCalled();
}); });
}); });
});
describe('Dropdown Keyboard Navigation', () => {
beforeEach(() => {
findHeaderSearchInput().vm.$emit('click');
});
it('submits a search onKey-Enter', async () => { it('closes dropdown when @tab is emitted', async () => {
expect(findHeaderSearchDropdown().exists()).toBe(true);
findDropdownKeyboardNavigation().vm.$emit('tab');
await wrapper.vm.$nextTick();
expect(findHeaderSearchDropdown().exists()).toBe(false);
});
});
});
describe('computed', () => {
describe('currentFocusedOption', () => {
const MOCK_INDEX = 1;
beforeEach(() => {
createComponent();
window.gon.current_username = MOCK_USERNAME;
findHeaderSearchInput().vm.$emit('click');
});
it(`when currentFocusIndex changes to ${MOCK_INDEX} updates the data to searchOptions[${MOCK_INDEX}]`, async () => {
findDropdownKeyboardNavigation().vm.$emit('change', MOCK_INDEX);
await wrapper.vm.$nextTick();
expect(wrapper.vm.currentFocusedOption).toBe(MOCK_DEFAULT_SEARCH_OPTIONS[MOCK_INDEX]);
});
});
});
describe('Submitting a search', () => {
describe('with no currentFocusedOption', () => {
beforeEach(() => {
createComponent();
});
it('onKey-enter submits a search', async () => {
findHeaderSearchInput().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY })); findHeaderSearchInput().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
...@@ -169,5 +215,22 @@ describe('HeaderSearchApp', () => { ...@@ -169,5 +215,22 @@ describe('HeaderSearchApp', () => {
expect(visitUrl).toHaveBeenCalledWith(MOCK_SEARCH_QUERY); expect(visitUrl).toHaveBeenCalledWith(MOCK_SEARCH_QUERY);
}); });
}); });
describe('with currentFocusedOption', () => {
const MOCK_INDEX = 1;
beforeEach(() => {
createComponent();
window.gon.current_username = MOCK_USERNAME;
findHeaderSearchInput().vm.$emit('click');
});
it('onKey-enter clicks the selected dropdown item rather than submitting a search', async () => {
findDropdownKeyboardNavigation().vm.$emit('change', MOCK_INDEX);
await wrapper.vm.$nextTick();
findHeaderSearchInput().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
expect(visitUrl).toHaveBeenCalledWith(MOCK_DEFAULT_SEARCH_OPTIONS[MOCK_INDEX].url);
});
});
}); });
}); });
...@@ -9,14 +9,14 @@ import { ...@@ -9,14 +9,14 @@ import {
PROJECTS_CATEGORY, PROJECTS_CATEGORY,
SMALL_AVATAR_PX, SMALL_AVATAR_PX,
} from '~/header_search/constants'; } from '~/header_search/constants';
import { MOCK_GROUPED_AUTOCOMPLETE_OPTIONS, MOCK_AUTOCOMPLETE_OPTIONS } from '../mock_data'; import { MOCK_GROUPED_AUTOCOMPLETE_OPTIONS, MOCK_SORTED_AUTOCOMPLETE_OPTIONS } from '../mock_data';
Vue.use(Vuex); Vue.use(Vuex);
describe('HeaderSearchAutocompleteItems', () => { describe('HeaderSearchAutocompleteItems', () => {
let wrapper; let wrapper;
const createComponent = (initialState, mockGetters) => { const createComponent = (initialState, mockGetters, props) => {
const store = new Vuex.Store({ const store = new Vuex.Store({
state: { state: {
loading: false, loading: false,
...@@ -30,6 +30,9 @@ describe('HeaderSearchAutocompleteItems', () => { ...@@ -30,6 +30,9 @@ describe('HeaderSearchAutocompleteItems', () => {
wrapper = shallowMount(HeaderSearchAutocompleteItems, { wrapper = shallowMount(HeaderSearchAutocompleteItems, {
store, store,
propsData: {
...props,
},
}); });
}; };
...@@ -38,6 +41,7 @@ describe('HeaderSearchAutocompleteItems', () => { ...@@ -38,6 +41,7 @@ describe('HeaderSearchAutocompleteItems', () => {
}); });
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findFirstDropdownItem = () => findDropdownItems().at(0);
const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => w.text()); const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => w.text());
const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href')); const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href'));
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
...@@ -69,16 +73,16 @@ describe('HeaderSearchAutocompleteItems', () => { ...@@ -69,16 +73,16 @@ describe('HeaderSearchAutocompleteItems', () => {
describe('Dropdown items', () => { describe('Dropdown items', () => {
it('renders item for each option in autocomplete option', () => { it('renders item for each option in autocomplete option', () => {
expect(findDropdownItems()).toHaveLength(MOCK_AUTOCOMPLETE_OPTIONS.length); expect(findDropdownItems()).toHaveLength(MOCK_SORTED_AUTOCOMPLETE_OPTIONS.length);
}); });
it('renders titles correctly', () => { it('renders titles correctly', () => {
const expectedTitles = MOCK_AUTOCOMPLETE_OPTIONS.map((o) => o.label); const expectedTitles = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.label);
expect(findDropdownItemTitles()).toStrictEqual(expectedTitles); expect(findDropdownItemTitles()).toStrictEqual(expectedTitles);
}); });
it('renders links correctly', () => { it('renders links correctly', () => {
const expectedLinks = MOCK_AUTOCOMPLETE_OPTIONS.map((o) => o.url); const expectedLinks = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.url);
expect(findDropdownItemLinks()).toStrictEqual(expectedLinks); expect(findDropdownItemLinks()).toStrictEqual(expectedLinks);
}); });
}); });
...@@ -104,5 +108,42 @@ describe('HeaderSearchAutocompleteItems', () => { ...@@ -104,5 +108,42 @@ describe('HeaderSearchAutocompleteItems', () => {
}); });
}); });
}); });
describe.each`
currentFocusedOption | isFocused
${null} | ${false}
${{ html_id: 'not-a-match' }} | ${false}
${MOCK_SORTED_AUTOCOMPLETE_OPTIONS[0]} | ${true}
`('isOptionFocused', ({ currentFocusedOption, isFocused }) => {
describe(`when currentFocusedOption.html_id is ${currentFocusedOption?.html_id}`, () => {
beforeEach(() => {
createComponent({}, {}, { currentFocusedOption });
});
it(`should${isFocused ? '' : ' not'} have gl-bg-gray-50 applied`, () => {
expect(findFirstDropdownItem().classes('gl-bg-gray-50')).toBe(isFocused);
});
});
});
});
describe('watchers', () => {
describe('currentFocusedOption', () => {
beforeEach(() => {
createComponent();
});
it('when focused changes to existing element calls scroll into view on the newly focused element', async () => {
const focusedElement = findFirstDropdownItem().element;
const scrollSpy = jest.spyOn(focusedElement, 'scrollIntoView');
wrapper.setProps({ currentFocusedOption: MOCK_SORTED_AUTOCOMPLETE_OPTIONS[0] });
await wrapper.vm.$nextTick();
expect(scrollSpy).toHaveBeenCalledWith(false);
scrollSpy.mockRestore();
});
});
}); });
}); });
...@@ -10,7 +10,7 @@ Vue.use(Vuex); ...@@ -10,7 +10,7 @@ Vue.use(Vuex);
describe('HeaderSearchDefaultItems', () => { describe('HeaderSearchDefaultItems', () => {
let wrapper; let wrapper;
const createComponent = (initialState) => { const createComponent = (initialState, props) => {
const store = new Vuex.Store({ const store = new Vuex.Store({
state: { state: {
searchContext: MOCK_SEARCH_CONTEXT, searchContext: MOCK_SEARCH_CONTEXT,
...@@ -23,6 +23,9 @@ describe('HeaderSearchDefaultItems', () => { ...@@ -23,6 +23,9 @@ describe('HeaderSearchDefaultItems', () => {
wrapper = shallowMount(HeaderSearchDefaultItems, { wrapper = shallowMount(HeaderSearchDefaultItems, {
store, store,
propsData: {
...props,
},
}); });
}; };
...@@ -32,6 +35,7 @@ describe('HeaderSearchDefaultItems', () => { ...@@ -32,6 +35,7 @@ describe('HeaderSearchDefaultItems', () => {
const findDropdownHeader = () => wrapper.findComponent(GlDropdownSectionHeader); const findDropdownHeader = () => wrapper.findComponent(GlDropdownSectionHeader);
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findFirstDropdownItem = () => findDropdownItems().at(0);
const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => w.text()); const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => w.text());
const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href')); const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href'));
...@@ -77,5 +81,22 @@ describe('HeaderSearchDefaultItems', () => { ...@@ -77,5 +81,22 @@ describe('HeaderSearchDefaultItems', () => {
}); });
}); });
}); });
describe.each`
currentFocusedOption | isFocused
${null} | ${false}
${{ html_id: 'not-a-match' }} | ${false}
${MOCK_DEFAULT_SEARCH_OPTIONS[0]} | ${true}
`('isOptionFocused', ({ currentFocusedOption, isFocused }) => {
describe(`when currentFocusedOption.html_id is ${currentFocusedOption?.html_id}`, () => {
beforeEach(() => {
createComponent({}, { currentFocusedOption });
});
it(`should${isFocused ? '' : ' not'} have gl-bg-gray-50 applied`, () => {
expect(findFirstDropdownItem().classes('gl-bg-gray-50')).toBe(isFocused);
});
});
});
}); });
}); });
...@@ -11,7 +11,7 @@ Vue.use(Vuex); ...@@ -11,7 +11,7 @@ Vue.use(Vuex);
describe('HeaderSearchScopedItems', () => { describe('HeaderSearchScopedItems', () => {
let wrapper; let wrapper;
const createComponent = (initialState) => { const createComponent = (initialState, props) => {
const store = new Vuex.Store({ const store = new Vuex.Store({
state: { state: {
search: MOCK_SEARCH, search: MOCK_SEARCH,
...@@ -24,6 +24,9 @@ describe('HeaderSearchScopedItems', () => { ...@@ -24,6 +24,9 @@ describe('HeaderSearchScopedItems', () => {
wrapper = shallowMount(HeaderSearchScopedItems, { wrapper = shallowMount(HeaderSearchScopedItems, {
store, store,
propsData: {
...props,
},
}); });
}; };
...@@ -32,6 +35,7 @@ describe('HeaderSearchScopedItems', () => { ...@@ -32,6 +35,7 @@ describe('HeaderSearchScopedItems', () => {
}); });
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findFirstDropdownItem = () => findDropdownItems().at(0);
const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => trimText(w.text())); const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => trimText(w.text()));
const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href')); const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href'));
...@@ -57,5 +61,22 @@ describe('HeaderSearchScopedItems', () => { ...@@ -57,5 +61,22 @@ describe('HeaderSearchScopedItems', () => {
expect(findDropdownItemLinks()).toStrictEqual(expectedLinks); expect(findDropdownItemLinks()).toStrictEqual(expectedLinks);
}); });
}); });
describe.each`
currentFocusedOption | isFocused
${null} | ${false}
${{ html_id: 'not-a-match' }} | ${false}
${MOCK_SCOPED_SEARCH_OPTIONS[0]} | ${true}
`('isOptionFocused', ({ currentFocusedOption, isFocused }) => {
describe(`when currentFocusedOption.html_id is ${currentFocusedOption?.html_id}`, () => {
beforeEach(() => {
createComponent({}, { currentFocusedOption });
});
it(`should${isFocused ? '' : ' not'} have gl-bg-gray-50 applied`, () => {
expect(findFirstDropdownItem().classes('gl-bg-gray-50')).toBe(isFocused);
});
});
});
}); });
}); });
...@@ -46,22 +46,27 @@ export const MOCK_SEARCH_CONTEXT = { ...@@ -46,22 +46,27 @@ export const MOCK_SEARCH_CONTEXT = {
export const MOCK_DEFAULT_SEARCH_OPTIONS = [ export const MOCK_DEFAULT_SEARCH_OPTIONS = [
{ {
html_id: 'default-issues-assigned',
title: MSG_ISSUES_ASSIGNED_TO_ME, title: MSG_ISSUES_ASSIGNED_TO_ME,
url: `${MOCK_ISSUE_PATH}/?assignee_username=${MOCK_USERNAME}`, url: `${MOCK_ISSUE_PATH}/?assignee_username=${MOCK_USERNAME}`,
}, },
{ {
html_id: 'default-issues-created',
title: MSG_ISSUES_IVE_CREATED, title: MSG_ISSUES_IVE_CREATED,
url: `${MOCK_ISSUE_PATH}/?author_username=${MOCK_USERNAME}`, url: `${MOCK_ISSUE_PATH}/?author_username=${MOCK_USERNAME}`,
}, },
{ {
html_id: 'default-mrs-assigned',
title: MSG_MR_ASSIGNED_TO_ME, title: MSG_MR_ASSIGNED_TO_ME,
url: `${MOCK_MR_PATH}/?assignee_username=${MOCK_USERNAME}`, url: `${MOCK_MR_PATH}/?assignee_username=${MOCK_USERNAME}`,
}, },
{ {
html_id: 'default-mrs-reviewer',
title: MSG_MR_IM_REVIEWER, title: MSG_MR_IM_REVIEWER,
url: `${MOCK_MR_PATH}/?reviewer_username=${MOCK_USERNAME}`, url: `${MOCK_MR_PATH}/?reviewer_username=${MOCK_USERNAME}`,
}, },
{ {
html_id: 'default-mrs-created',
title: MSG_MR_IVE_CREATED, title: MSG_MR_IVE_CREATED,
url: `${MOCK_MR_PATH}/?author_username=${MOCK_USERNAME}`, url: `${MOCK_MR_PATH}/?author_username=${MOCK_USERNAME}`,
}, },
...@@ -69,42 +74,75 @@ export const MOCK_DEFAULT_SEARCH_OPTIONS = [ ...@@ -69,42 +74,75 @@ export const MOCK_DEFAULT_SEARCH_OPTIONS = [
export const MOCK_SCOPED_SEARCH_OPTIONS = [ export const MOCK_SCOPED_SEARCH_OPTIONS = [
{ {
html_id: 'scoped-in-project',
scope: MOCK_PROJECT.name, scope: MOCK_PROJECT.name,
description: MSG_IN_PROJECT, description: MSG_IN_PROJECT,
url: MOCK_PROJECT.path, url: MOCK_PROJECT.path,
}, },
{ {
html_id: 'scoped-in-group',
scope: MOCK_GROUP.name, scope: MOCK_GROUP.name,
description: MSG_IN_GROUP, description: MSG_IN_GROUP,
url: MOCK_GROUP.path, url: MOCK_GROUP.path,
}, },
{ {
html_id: 'scoped-in-all',
description: MSG_IN_ALL_GITLAB, description: MSG_IN_ALL_GITLAB,
url: MOCK_ALL_PATH, url: MOCK_ALL_PATH,
}, },
]; ];
export const MOCK_AUTOCOMPLETE_OPTIONS = [ export const MOCK_AUTOCOMPLETE_OPTIONS_RES = [
{ {
category: 'Projects', category: 'Projects',
id: 1, id: 1,
label: 'MockProject1', label: 'MockProject1',
url: 'project/1', url: 'project/1',
}, },
{
category: 'Groups',
id: 1,
label: 'MockGroup1',
url: 'group/1',
},
{ {
category: 'Projects', category: 'Projects',
id: 2, id: 2,
label: 'MockProject2', label: 'MockProject2',
url: 'project/2', url: 'project/2',
}, },
{
category: 'Help',
label: 'GitLab Help',
url: 'help/gitlab',
},
];
export const MOCK_AUTOCOMPLETE_OPTIONS = [
{
category: 'Projects',
html_id: 'autocomplete-Projects-0',
id: 1,
label: 'MockProject1',
url: 'project/1',
},
{ {
category: 'Groups', category: 'Groups',
html_id: 'autocomplete-Groups-1',
id: 1, id: 1,
label: 'MockGroup1', label: 'MockGroup1',
url: 'group/1', url: 'group/1',
}, },
{
category: 'Projects',
html_id: 'autocomplete-Projects-2',
id: 2,
label: 'MockProject2',
url: 'project/2',
},
{ {
category: 'Help', category: 'Help',
html_id: 'autocomplete-Help-3',
label: 'GitLab Help', label: 'GitLab Help',
url: 'help/gitlab', url: 'help/gitlab',
}, },
...@@ -116,12 +154,16 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [ ...@@ -116,12 +154,16 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
data: [ data: [
{ {
category: 'Projects', category: 'Projects',
html_id: 'autocomplete-Projects-0',
id: 1, id: 1,
label: 'MockProject1', label: 'MockProject1',
url: 'project/1', url: 'project/1',
}, },
{ {
category: 'Projects', category: 'Projects',
html_id: 'autocomplete-Projects-2',
id: 2, id: 2,
label: 'MockProject2', label: 'MockProject2',
url: 'project/2', url: 'project/2',
...@@ -133,6 +175,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [ ...@@ -133,6 +175,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
data: [ data: [
{ {
category: 'Groups', category: 'Groups',
html_id: 'autocomplete-Groups-1',
id: 1, id: 1,
label: 'MockGroup1', label: 'MockGroup1',
url: 'group/1', url: 'group/1',
...@@ -144,9 +188,41 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [ ...@@ -144,9 +188,41 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
data: [ data: [
{ {
category: 'Help', category: 'Help',
html_id: 'autocomplete-Help-3',
label: 'GitLab Help', label: 'GitLab Help',
url: 'help/gitlab', url: 'help/gitlab',
}, },
], ],
}, },
]; ];
export const MOCK_SORTED_AUTOCOMPLETE_OPTIONS = [
{
category: 'Projects',
html_id: 'autocomplete-Projects-0',
id: 1,
label: 'MockProject1',
url: 'project/1',
},
{
category: 'Projects',
html_id: 'autocomplete-Projects-2',
id: 2,
label: 'MockProject2',
url: 'project/2',
},
{
category: 'Groups',
html_id: 'autocomplete-Groups-1',
id: 1,
label: 'MockGroup1',
url: 'group/1',
},
{
category: 'Help',
html_id: 'autocomplete-Help-3',
label: 'GitLab Help',
url: 'help/gitlab',
},
];
...@@ -5,7 +5,7 @@ import * as actions from '~/header_search/store/actions'; ...@@ -5,7 +5,7 @@ import * as actions from '~/header_search/store/actions';
import * as types from '~/header_search/store/mutation_types'; import * as types from '~/header_search/store/mutation_types';
import createState from '~/header_search/store/state'; import createState from '~/header_search/store/state';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { MOCK_SEARCH, MOCK_AUTOCOMPLETE_OPTIONS } from '../mock_data'; import { MOCK_SEARCH, MOCK_AUTOCOMPLETE_OPTIONS_RES } from '../mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
...@@ -29,9 +29,9 @@ describe('Header Search Store Actions', () => { ...@@ -29,9 +29,9 @@ describe('Header Search Store Actions', () => {
}); });
describe.each` describe.each`
axiosMock | type | expectedMutations | flashCallCount axiosMock | type | expectedMutations | flashCallCount
${{ method: 'onGet', code: 200, res: MOCK_AUTOCOMPLETE_OPTIONS }} | ${'success'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_SUCCESS, payload: MOCK_AUTOCOMPLETE_OPTIONS }]} | ${0} ${{ method: 'onGet', code: 200, res: MOCK_AUTOCOMPLETE_OPTIONS_RES }} | ${'success'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_SUCCESS, payload: MOCK_AUTOCOMPLETE_OPTIONS_RES }]} | ${0}
${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_ERROR }]} | ${1} ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_ERROR }]} | ${1}
`('fetchAutocompleteOptions', ({ axiosMock, type, expectedMutations, flashCallCount }) => { `('fetchAutocompleteOptions', ({ axiosMock, type, expectedMutations, flashCallCount }) => {
describe(`on ${type}`, () => { describe(`on ${type}`, () => {
beforeEach(() => { beforeEach(() => {
......
...@@ -15,6 +15,7 @@ import { ...@@ -15,6 +15,7 @@ import {
MOCK_SEARCH, MOCK_SEARCH,
MOCK_AUTOCOMPLETE_OPTIONS, MOCK_AUTOCOMPLETE_OPTIONS,
MOCK_GROUPED_AUTOCOMPLETE_OPTIONS, MOCK_GROUPED_AUTOCOMPLETE_OPTIONS,
MOCK_SORTED_AUTOCOMPLETE_OPTIONS,
} from '../mock_data'; } from '../mock_data';
describe('Header Search Store Getters', () => { describe('Header Search Store Getters', () => {
...@@ -248,4 +249,44 @@ describe('Header Search Store Getters', () => { ...@@ -248,4 +249,44 @@ describe('Header Search Store Getters', () => {
); );
}); });
}); });
describe.each`
search | defaultSearchOptions | scopedSearchOptions | autocompleteGroupedSearchOptions | expectedArray
${null} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS} | ${MOCK_DEFAULT_SEARCH_OPTIONS}
${MOCK_SEARCH} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${[]} | ${MOCK_SCOPED_SEARCH_OPTIONS}
${MOCK_SEARCH} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${[]} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS} | ${MOCK_SORTED_AUTOCOMPLETE_OPTIONS}
${MOCK_SEARCH} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS} | ${MOCK_SCOPED_SEARCH_OPTIONS.concat(MOCK_SORTED_AUTOCOMPLETE_OPTIONS)}
`(
'searchOptions',
({
search,
defaultSearchOptions,
scopedSearchOptions,
autocompleteGroupedSearchOptions,
expectedArray,
}) => {
describe(`when search is ${search} and the defaultSearchOptions${
defaultSearchOptions.length ? '' : ' do not'
} exist, scopedSearchOptions${
scopedSearchOptions.length ? '' : ' do not'
} exist, and autocompleteGroupedSearchOptions${
autocompleteGroupedSearchOptions.length ? '' : ' do not'
} exist`, () => {
const mockGetters = {
defaultSearchOptions,
scopedSearchOptions,
autocompleteGroupedSearchOptions,
};
beforeEach(() => {
createState();
state.search = search;
});
it(`should return the correct combined array`, () => {
expect(getters.searchOptions(state, mockGetters)).toStrictEqual(expectedArray);
});
});
},
);
}); });
import * as types from '~/header_search/store/mutation_types'; import * as types from '~/header_search/store/mutation_types';
import mutations from '~/header_search/store/mutations'; import mutations from '~/header_search/store/mutations';
import createState from '~/header_search/store/state'; import createState from '~/header_search/store/state';
import { MOCK_SEARCH, MOCK_AUTOCOMPLETE_OPTIONS } from '../mock_data'; import {
MOCK_SEARCH,
MOCK_AUTOCOMPLETE_OPTIONS_RES,
MOCK_AUTOCOMPLETE_OPTIONS,
} from '../mock_data';
describe('Header Search Store Mutations', () => { describe('Header Search Store Mutations', () => {
let state; let state;
...@@ -20,8 +24,8 @@ describe('Header Search Store Mutations', () => { ...@@ -20,8 +24,8 @@ describe('Header Search Store Mutations', () => {
}); });
describe('RECEIVE_AUTOCOMPLETE_SUCCESS', () => { describe('RECEIVE_AUTOCOMPLETE_SUCCESS', () => {
it('sets loading to false and sets autocompleteOptions array', () => { it('sets loading to false and then formats and sets the autocompleteOptions array', () => {
mutations[types.RECEIVE_AUTOCOMPLETE_SUCCESS](state, MOCK_AUTOCOMPLETE_OPTIONS); mutations[types.RECEIVE_AUTOCOMPLETE_SUCCESS](state, MOCK_AUTOCOMPLETE_OPTIONS_RES);
expect(state.loading).toBe(false); expect(state.loading).toBe(false);
expect(state.autocompleteOptions).toStrictEqual(MOCK_AUTOCOMPLETE_OPTIONS); expect(state.autocompleteOptions).toStrictEqual(MOCK_AUTOCOMPLETE_OPTIONS);
......
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