diff options
Diffstat (limited to 'spec/frontend/search')
21 files changed, 414 insertions, 849 deletions
diff --git a/spec/frontend/search/sidebar/components/all_scopes_start_filters_spec.js b/spec/frontend/search/sidebar/components/all_scopes_start_filters_spec.js new file mode 100644 index 00000000000..cd43214ed38 --- /dev/null +++ b/spec/frontend/search/sidebar/components/all_scopes_start_filters_spec.js @@ -0,0 +1,28 @@ +import { shallowMount } from '@vue/test-utils'; +import GroupFilter from '~/search/sidebar/components/group_filter.vue'; +import ProjectFilter from '~/search/sidebar/components/project_filter.vue'; +import AllScopesStartFilters from '~/search/sidebar/components/all_scopes_start_filters.vue'; + +describe('GlobalSearch AllScopesStartFilters', () => { + let wrapper; + + const findGroupFilter = () => wrapper.findComponent(GroupFilter); + const findProjectFilter = () => wrapper.findComponent(ProjectFilter); + + const createComponent = () => { + wrapper = shallowMount(AllScopesStartFilters); + }; + + describe('Renders correctly', () => { + beforeEach(() => { + createComponent(); + }); + it('renders ArchivedFilter', () => { + expect(findGroupFilter().exists()).toBe(true); + }); + + it('renders FiltersTemplate', () => { + expect(findProjectFilter().exists()).toBe(true); + }); + }); +}); diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js index c2d88493d71..3ff6bbf7666 100644 --- a/spec/frontend/search/sidebar/components/app_spec.js +++ b/spec/frontend/search/sidebar/components/app_spec.js @@ -18,10 +18,9 @@ import NotesFilters from '~/search/sidebar/components/notes_filters.vue'; import CommitsFilters from '~/search/sidebar/components/commits_filters.vue'; import MilestonesFilters from '~/search/sidebar/components/milestones_filters.vue'; import WikiBlobsFilters from '~/search/sidebar/components/wiki_blobs_filters.vue'; -import ScopeLegacyNavigation from '~/search/sidebar/components/scope_legacy_navigation.vue'; -import SmallScreenDrawerNavigation from '~/search/sidebar/components/small_screen_drawer_navigation.vue'; import ScopeSidebarNavigation from '~/search/sidebar/components/scope_sidebar_navigation.vue'; import DomElementListener from '~/vue_shared/components/dom_element_listener.vue'; +import AllScopesStartFilters from '~/search/sidebar/components/all_scopes_start_filters.vue'; jest.mock('~/super_sidebar/super_sidebar_collapsed_state_manager'); @@ -45,11 +44,6 @@ describe('GlobalSearchSidebar', () => { wrapper = shallowMount(GlobalSearchSidebar, { store, - provide: { - glFeatures: { - searchProjectWikisHideArchivedProjects: true, - }, - }, }); }; @@ -62,10 +56,9 @@ describe('GlobalSearchSidebar', () => { const findCommitsFilters = () => wrapper.findComponent(CommitsFilters); const findMilestonesFilters = () => wrapper.findComponent(MilestonesFilters); const findWikiBlobsFilters = () => wrapper.findComponent(WikiBlobsFilters); - const findScopeLegacyNavigation = () => wrapper.findComponent(ScopeLegacyNavigation); - const findSmallScreenDrawerNavigation = () => wrapper.findComponent(SmallScreenDrawerNavigation); const findScopeSidebarNavigation = () => wrapper.findComponent(ScopeSidebarNavigation); const findDomElementListener = () => wrapper.findComponent(DomElementListener); + const findAllScopesStartFilters = () => wrapper.findComponent(AllScopesStartFilters); describe('renders properly', () => { describe('always', () => { @@ -79,31 +72,50 @@ describe('GlobalSearchSidebar', () => { }); describe.each` - scope | filter | searchType | isShown - ${'issues'} | ${findIssuesFilters} | ${SEARCH_TYPE_BASIC} | ${true} - ${'merge_requests'} | ${findMergeRequestsFilters} | ${SEARCH_TYPE_BASIC} | ${true} - ${'projects'} | ${findProjectsFilters} | ${SEARCH_TYPE_BASIC} | ${true} - ${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_BASIC} | ${false} - ${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_ADVANCED} | ${true} - ${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_ZOEKT} | ${false} - ${'notes'} | ${findNotesFilters} | ${SEARCH_TYPE_BASIC} | ${true} - ${'notes'} | ${findNotesFilters} | ${SEARCH_TYPE_ADVANCED} | ${true} - ${'commits'} | ${findCommitsFilters} | ${SEARCH_TYPE_BASIC} | ${true} - ${'commits'} | ${findCommitsFilters} | ${SEARCH_TYPE_ADVANCED} | ${true} - ${'milestones'} | ${findMilestonesFilters} | ${SEARCH_TYPE_BASIC} | ${true} - ${'milestones'} | ${findMilestonesFilters} | ${SEARCH_TYPE_ADVANCED} | ${true} - ${'wiki_blobs'} | ${findWikiBlobsFilters} | ${SEARCH_TYPE_BASIC} | ${true} - ${'wiki_blobs'} | ${findWikiBlobsFilters} | ${SEARCH_TYPE_ADVANCED} | ${true} - `('with sidebar $scope scope:', ({ scope, filter, searchType, isShown }) => { + scope | filter + ${'issues'} | ${findIssuesFilters} + ${'issues'} | ${findAllScopesStartFilters} + ${'merge_requests'} | ${findMergeRequestsFilters} + ${'merge_requests'} | ${findAllScopesStartFilters} + ${'projects'} | ${findProjectsFilters} + ${'projects'} | ${findAllScopesStartFilters} + ${'blobs'} | ${findAllScopesStartFilters} + ${'notes'} | ${findNotesFilters} + ${'notes'} | ${findAllScopesStartFilters} + ${'commits'} | ${findCommitsFilters} + ${'commits'} | ${findAllScopesStartFilters} + ${'milestones'} | ${findMilestonesFilters} + ${'milestones'} | ${findAllScopesStartFilters} + ${'wiki_blobs'} | ${findWikiBlobsFilters} + ${'wiki_blobs'} | ${findAllScopesStartFilters} + `('with sidebar scope: $scope', ({ scope, filter }) => { + describe.each([SEARCH_TYPE_BASIC, SEARCH_TYPE_ADVANCED])( + 'with search_type %s', + (searchType) => { + beforeEach(() => { + getterSpies.currentScope = jest.fn(() => scope); + createComponent({ urlQuery: { scope }, searchType }); + }); + + it(`renders correctly ${filter.name.replace('find', '')}`, () => { + expect(filter().exists()).toBe(true); + }); + }, + ); + }); + + describe.each` + scope | filter | searchType | isShown + ${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_BASIC} | ${false} + ${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_ADVANCED} | ${true} + ${'blobs'} | ${findBlobsFilters} | ${SEARCH_TYPE_ZOEKT} | ${false} + `('sidebar blobs scope:', ({ scope, filter, searchType, isShown }) => { beforeEach(() => { getterSpies.currentScope = jest.fn(() => scope); createComponent({ urlQuery: { scope }, searchType }); }); - it(`renders correctly filter ${filter.name.replace( - 'find', - '', - )} when search_type ${searchType}`, () => { + it(`renders correctly filter BlobsFilters when search_type ${searchType}`, () => { expect(filter().exists()).toBe(isShown); }); }); @@ -129,46 +141,27 @@ describe('GlobalSearchSidebar', () => { }); }); - describe.each` - currentScope | sidebarNavShown | legacyNavShown - ${'issues'} | ${false} | ${true} - ${'test'} | ${false} | ${true} - ${'issues'} | ${true} | ${false} - ${'test'} | ${true} | ${false} - `( - 'renders navigation for scope $currentScope', - ({ currentScope, sidebarNavShown, legacyNavShown }) => { - beforeEach(() => { - getterSpies.currentScope = jest.fn(() => currentScope); - createComponent({ useSidebarNavigation: sidebarNavShown }); - }); - - it(`renders navigation correctly with legacyNavShown ${legacyNavShown}`, () => { - expect(findScopeLegacyNavigation().exists()).toBe(legacyNavShown); - expect(findSmallScreenDrawerNavigation().exists()).toBe(legacyNavShown); - }); - - it(`renders navigation correctly with sidebarNavShown ${sidebarNavShown}`, () => { - expect(findScopeSidebarNavigation().exists()).toBe(sidebarNavShown); - }); - }, - ); - }); + describe.each(['issues', 'test'])('for scope %p', (currentScope) => { + beforeEach(() => { + getterSpies.currentScope = jest.fn(() => currentScope); + createComponent(); + }); - describe('when useSidebarNavigation=true', () => { - beforeEach(() => { - createComponent({ useSidebarNavigation: true }); + it(`renders navigation correctly`, () => { + expect(findScopeSidebarNavigation().exists()).toBe(true); + }); }); + }); - it('toggles super sidebar when button is clicked', () => { - const elListener = findDomElementListener(); + it('toggles super sidebar when button is clicked', () => { + createComponent(); + const elListener = findDomElementListener(); - expect(toggleSuperSidebarCollapsed).not.toHaveBeenCalled(); + expect(toggleSuperSidebarCollapsed).not.toHaveBeenCalled(); - elListener.vm.$emit('click'); + elListener.vm.$emit('click'); - expect(toggleSuperSidebarCollapsed).toHaveBeenCalledTimes(1); - expect(elListener.props('selector')).toBe('#js-open-mobile-filters'); - }); + expect(toggleSuperSidebarCollapsed).toHaveBeenCalledTimes(1); + expect(elListener.props('selector')).toBe('#js-open-mobile-filters'); }); }); diff --git a/spec/frontend/search/sidebar/components/archived_filter_spec.js b/spec/frontend/search/sidebar/components/archived_filter_spec.js index 9ed677ca297..9e8ababa5da 100644 --- a/spec/frontend/search/sidebar/components/archived_filter_spec.js +++ b/spec/frontend/search/sidebar/components/archived_filter_spec.js @@ -33,7 +33,7 @@ describe('ArchivedFilter', () => { const findCheckboxFilter = () => wrapper.findComponent(GlFormCheckboxGroup); const findCheckboxFilterLabel = () => wrapper.findByTestId('label'); - const findH5 = () => wrapper.findComponent('h5'); + const findTitle = () => wrapper.findByTestId('archived-filter-title'); describe('old sidebar', () => { beforeEach(() => { @@ -45,8 +45,8 @@ describe('ArchivedFilter', () => { }); it('renders the divider', () => { - expect(findH5().exists()).toBe(true); - expect(findH5().text()).toBe(archivedFilterData.headerLabel); + expect(findTitle().exists()).toBe(true); + expect(findTitle().text()).toBe(archivedFilterData.headerLabel); }); it('wraps the label element with a tooltip', () => { @@ -66,8 +66,8 @@ describe('ArchivedFilter', () => { }); it("doesn't render the divider", () => { - expect(findH5().exists()).toBe(true); - expect(findH5().text()).toBe(archivedFilterData.headerLabel); + expect(findTitle().exists()).toBe(true); + expect(findTitle().text()).toBe(archivedFilterData.headerLabel); }); it('wraps the label element with a tooltip', () => { diff --git a/spec/frontend/search/sidebar/components/blobs_filters_spec.js b/spec/frontend/search/sidebar/components/blobs_filters_spec.js index 245ddb8f8bb..3f1feae8527 100644 --- a/spec/frontend/search/sidebar/components/blobs_filters_spec.js +++ b/spec/frontend/search/sidebar/components/blobs_filters_spec.js @@ -17,13 +17,11 @@ describe('GlobalSearch BlobsFilters', () => { currentScope: () => 'blobs', }; - const createComponent = ({ initialState = {} } = {}) => { + const createComponent = () => { const store = new Vuex.Store({ state: { urlQuery: MOCK_QUERY, - useSidebarNavigation: false, searchType: SEARCH_TYPE_ADVANCED, - ...initialState, }, getters: defaultGetters, }); @@ -35,10 +33,9 @@ describe('GlobalSearch BlobsFilters', () => { const findLanguageFilter = () => wrapper.findComponent(LanguageFilter); const findArchivedFilter = () => wrapper.findComponent(ArchivedFilter); - const findDividers = () => wrapper.findAll('hr'); beforeEach(() => { - createComponent({}); + createComponent(); }); it('renders LanguageFilter', () => { @@ -48,31 +45,4 @@ describe('GlobalSearch BlobsFilters', () => { it('renders ArchivedFilter', () => { expect(findArchivedFilter().exists()).toBe(true); }); - - it('renders divider correctly', () => { - expect(findDividers()).toHaveLength(1); - }); - - describe('Renders correctly in new nav', () => { - beforeEach(() => { - createComponent({ - initialState: { - searchType: SEARCH_TYPE_ADVANCED, - useSidebarNavigation: true, - }, - }); - }); - - it('renders correctly LanguageFilter', () => { - expect(findLanguageFilter().exists()).toBe(true); - }); - - it('renders correctly ArchivedFilter', () => { - expect(findArchivedFilter().exists()).toBe(true); - }); - - it("doesn't render dividers", () => { - expect(findDividers()).toHaveLength(0); - }); - }); }); diff --git a/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js b/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js index 6444ec10466..fedbd407b0b 100644 --- a/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js +++ b/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js @@ -22,23 +22,11 @@ describe('ConfidentialityFilter', () => { const findRadioFilter = () => wrapper.findComponent(RadioFilter); - describe('old sidebar', () => { - beforeEach(() => { - createComponent({ useSidebarNavigation: false }); - }); - - it('renders the component', () => { - expect(findRadioFilter().exists()).toBe(true); - }); + beforeEach(() => { + createComponent(); }); - describe('new sidebar', () => { - beforeEach(() => { - createComponent({ useSidebarNavigation: true }); - }); - - it('renders the component', () => { - expect(findRadioFilter().exists()).toBe(true); - }); + it('renders the component', () => { + expect(findRadioFilter().exists()).toBe(true); }); }); diff --git a/spec/frontend/search/sidebar/components/filters_template_spec.js b/spec/frontend/search/sidebar/components/filters_template_spec.js index f1a807c5ceb..18144e25ac3 100644 --- a/spec/frontend/search/sidebar/components/filters_template_spec.js +++ b/spec/frontend/search/sidebar/components/filters_template_spec.js @@ -52,7 +52,6 @@ describe('GlobalSearchSidebarLanguageFilter', () => { }; const findForm = () => wrapper.findComponent(GlForm); - const findDividers = () => wrapper.findAll('hr'); const findApplyButton = () => wrapper.findComponent(GlButton); const findResetButton = () => wrapper.findComponent(GlLink); const findSlotContent = () => wrapper.findByText('Filters Content'); @@ -66,10 +65,6 @@ describe('GlobalSearchSidebarLanguageFilter', () => { expect(findForm().exists()).toBe(true); }); - it('renders dividers', () => { - expect(findDividers()).toHaveLength(2); - }); - it('renders slot content', () => { expect(findSlotContent().exists()).toBe(true); }); diff --git a/spec/frontend/search/topbar/components/group_filter_spec.js b/spec/frontend/search/sidebar/components/group_filter_spec.js index fa8036a7f97..a90a8a38267 100644 --- a/spec/frontend/search/topbar/components/group_filter_spec.js +++ b/spec/frontend/search/sidebar/components/group_filter_spec.js @@ -1,13 +1,14 @@ import { shallowMount } from '@vue/test-utils'; +import { cloneDeep } from 'lodash'; import Vue from 'vue'; // eslint-disable-next-line no-restricted-imports import Vuex from 'vuex'; import { MOCK_GROUP, MOCK_QUERY, CURRENT_SCOPE } from 'jest/search/mock_data'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { GROUPS_LOCAL_STORAGE_KEY } from '~/search/store/constants'; -import GroupFilter from '~/search/topbar/components/group_filter.vue'; -import SearchableDropdown from '~/search/topbar/components/searchable_dropdown.vue'; -import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/topbar/constants'; +import GroupFilter from '~/search/sidebar/components/group_filter.vue'; +import SearchableDropdown from '~/search/sidebar/components/searchable_dropdown.vue'; +import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/sidebar/constants'; Vue.use(Vuex); @@ -27,6 +28,7 @@ describe('GroupFilter', () => { const defaultProps = { initialData: null, + searchHandler: jest.fn(), }; const createComponent = (initialState, props) => { @@ -68,19 +70,6 @@ describe('GroupFilter', () => { createComponent(); }); - describe('when @search is emitted', () => { - const search = 'test'; - - beforeEach(() => { - findSearchableDropdown().vm.$emit('search', search); - }); - - it('calls fetchGroups with the search paramter', () => { - expect(actionSpies.fetchGroups).toHaveBeenCalledTimes(1); - expect(actionSpies.fetchGroups).toHaveBeenCalledWith(expect.any(Object), search); - }); - }); - describe('when @change is emitted with Any', () => { beforeEach(() => { findSearchableDropdown().vm.$emit('change', ANY_OPTION); @@ -148,11 +137,12 @@ describe('GroupFilter', () => { describe('when initialData is set', () => { beforeEach(() => { - createComponent({}, { initialData: MOCK_GROUP }); + createComponent({ groupInitialJson: { ...MOCK_GROUP } }, {}); }); it('sets selectedGroup to ANY_OPTION', () => { - expect(wrapper.vm.selectedGroup).toBe(MOCK_GROUP); + // cloneDeep to fix Property or method `nodeType` is not defined bug + expect(cloneDeep(wrapper.vm.selectedGroup)).toStrictEqual(MOCK_GROUP); }); }); }); @@ -169,7 +159,13 @@ describe('GroupFilter', () => { initialData ? 'has' : 'does not have' } an initial group`, () => { beforeEach(() => { - createComponent({ query: { ...MOCK_QUERY, nav_source: navSource } }, { initialData }); + createComponent( + { + query: { ...MOCK_QUERY, nav_source: navSource }, + groupInitialJson: { ...initialData }, + }, + {}, + ); }); it(`${callMethod ? 'does' : 'does not'} call setFrequentGroup`, () => { diff --git a/spec/frontend/search/sidebar/components/issues_filters_spec.js b/spec/frontend/search/sidebar/components/issues_filters_spec.js index 860c5c147a6..ce9c6c2bb0c 100644 --- a/spec/frontend/search/sidebar/components/issues_filters_spec.js +++ b/spec/frontend/search/sidebar/components/issues_filters_spec.js @@ -19,11 +19,10 @@ describe('GlobalSearch IssuesFilters', () => { currentScope: () => 'issues', }; - const createComponent = ({ initialState = {}, searchIssueLabelAggregation = true } = {}) => { + const createComponent = ({ initialState = {} } = {}) => { const store = new Vuex.Store({ state: { urlQuery: MOCK_QUERY, - useSidebarNavigation: false, searchType: SEARCH_TYPE_ADVANCED, ...initialState, }, @@ -32,11 +31,6 @@ describe('GlobalSearch IssuesFilters', () => { wrapper = shallowMount(IssuesFilters, { store, - provide: { - glFeatures: { - searchIssueLabelAggregation, - }, - }, }); }; @@ -44,17 +38,10 @@ describe('GlobalSearch IssuesFilters', () => { const findConfidentialityFilter = () => wrapper.findComponent(ConfidentialityFilter); const findLabelFilter = () => wrapper.findComponent(LabelFilter); const findArchivedFilter = () => wrapper.findComponent(ArchivedFilter); - const findDividers = () => wrapper.findAll('hr'); - describe.each` - description | searchIssueLabelAggregation - ${'Renders correctly with Label Filter disabled'} | ${false} - ${'Renders correctly with Label Filter enabled'} | ${true} - `('$description', ({ searchIssueLabelAggregation }) => { + describe('Renders filters correctly with advanced search', () => { beforeEach(() => { - createComponent({ - searchIssueLabelAggregation, - }); + createComponent(); }); it('renders StatusFilter', () => { @@ -69,17 +56,8 @@ describe('GlobalSearch IssuesFilters', () => { expect(findArchivedFilter().exists()).toBe(true); }); - it(`renders correctly LabelFilter when searchIssueLabelAggregation is ${searchIssueLabelAggregation}`, () => { - expect(findLabelFilter().exists()).toBe(searchIssueLabelAggregation); - }); - - it('renders divider correctly', () => { - // two dividers can't be disabled - let dividersCount = 2; - if (searchIssueLabelAggregation) { - dividersCount += 1; - } - expect(findDividers()).toHaveLength(dividersCount); + it('renders correctly LabelFilter', () => { + expect(findLabelFilter().exists()).toBe(true); }); }); @@ -102,41 +80,6 @@ describe('GlobalSearch IssuesFilters', () => { it("doesn't render ArchivedFilter", () => { expect(findArchivedFilter().exists()).toBe(true); }); - - it('renders 1 divider', () => { - expect(findDividers()).toHaveLength(2); - }); - }); - - describe('Renders correctly in new nav', () => { - beforeEach(() => { - createComponent({ - initialState: { - searchType: SEARCH_TYPE_ADVANCED, - useSidebarNavigation: true, - }, - searchIssueLabelAggregation: true, - }); - }); - it('renders StatusFilter', () => { - expect(findStatusFilter().exists()).toBe(true); - }); - - it('renders ConfidentialityFilter', () => { - expect(findConfidentialityFilter().exists()).toBe(true); - }); - - it('renders LabelFilter', () => { - expect(findLabelFilter().exists()).toBe(true); - }); - - it('renders ArchivedFilter', () => { - expect(findArchivedFilter().exists()).toBe(true); - }); - - it("doesn't render dividers", () => { - expect(findDividers()).toHaveLength(0); - }); }); describe('Renders correctly with wrong scope', () => { @@ -159,9 +102,5 @@ describe('GlobalSearch IssuesFilters', () => { it("doesn't render ArchivedFilter", () => { expect(findArchivedFilter().exists()).toBe(false); }); - - it("doesn't render dividers", () => { - expect(findDividers()).toHaveLength(0); - }); }); }); diff --git a/spec/frontend/search/sidebar/components/label_filter_spec.js b/spec/frontend/search/sidebar/components/label_filter_spec.js index 9d2a0c5e739..7641036b9f6 100644 --- a/spec/frontend/search/sidebar/components/label_filter_spec.js +++ b/spec/frontend/search/sidebar/components/label_filter_spec.js @@ -85,11 +85,6 @@ describe('GlobalSearchSidebarLabelFilter', () => { wrapper = mountExtended(LabelFilter, { store, - provide: { - glFeatures: { - searchIssueLabelAggregation: true, - }, - }, }); }; diff --git a/spec/frontend/search/sidebar/components/merge_requests_filters_spec.js b/spec/frontend/search/sidebar/components/merge_requests_filters_spec.js index b02228a418f..8cd3cb45a20 100644 --- a/spec/frontend/search/sidebar/components/merge_requests_filters_spec.js +++ b/spec/frontend/search/sidebar/components/merge_requests_filters_spec.js @@ -21,7 +21,6 @@ describe('GlobalSearch MergeRequestsFilters', () => { const store = new Vuex.Store({ state: { urlQuery: MOCK_QUERY, - useSidebarNavigation: false, searchType: SEARCH_TYPE_ADVANCED, ...initialState, }, @@ -35,7 +34,6 @@ describe('GlobalSearch MergeRequestsFilters', () => { const findStatusFilter = () => wrapper.findComponent(StatusFilter); const findArchivedFilter = () => wrapper.findComponent(ArchivedFilter); - const findDividers = () => wrapper.findAll('hr'); describe('Renders correctly with Archived Filter', () => { beforeEach(() => { @@ -46,8 +44,8 @@ describe('GlobalSearch MergeRequestsFilters', () => { expect(findStatusFilter().exists()).toBe(true); }); - it('renders divider correctly', () => { - expect(findDividers()).toHaveLength(1); + it('renders ArchivedFilter', () => { + expect(findArchivedFilter().exists()).toBe(true); }); }); @@ -60,33 +58,9 @@ describe('GlobalSearch MergeRequestsFilters', () => { expect(findStatusFilter().exists()).toBe(true); }); - it('renders render ArchivedFilter', () => { - expect(findArchivedFilter().exists()).toBe(true); - }); - - it('renders 1 divider', () => { - expect(findDividers()).toHaveLength(1); - }); - }); - - describe('Renders correctly in new nav', () => { - beforeEach(() => { - createComponent({ - searchType: SEARCH_TYPE_ADVANCED, - useSidebarNavigation: true, - }); - }); - it('renders StatusFilter', () => { - expect(findStatusFilter().exists()).toBe(true); - }); - it('renders ArchivedFilter', () => { expect(findArchivedFilter().exists()).toBe(true); }); - - it("doesn't render divider", () => { - expect(findDividers()).toHaveLength(0); - }); }); describe('Renders correctly with wrong scope', () => { @@ -101,9 +75,5 @@ describe('GlobalSearch MergeRequestsFilters', () => { it("doesn't render ArchivedFilter", () => { expect(findArchivedFilter().exists()).toBe(false); }); - - it("doesn't render dividers", () => { - expect(findDividers()).toHaveLength(0); - }); }); }); diff --git a/spec/frontend/search/topbar/components/project_filter_spec.js b/spec/frontend/search/sidebar/components/project_filter_spec.js index e7808370098..817ec77380f 100644 --- a/spec/frontend/search/topbar/components/project_filter_spec.js +++ b/spec/frontend/search/sidebar/components/project_filter_spec.js @@ -1,13 +1,14 @@ import { shallowMount } from '@vue/test-utils'; +import { cloneDeep } from 'lodash'; import Vue from 'vue'; // eslint-disable-next-line no-restricted-imports import Vuex from 'vuex'; import { MOCK_PROJECT, MOCK_QUERY, CURRENT_SCOPE } from 'jest/search/mock_data'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { PROJECTS_LOCAL_STORAGE_KEY } from '~/search/store/constants'; -import ProjectFilter from '~/search/topbar/components/project_filter.vue'; -import SearchableDropdown from '~/search/topbar/components/searchable_dropdown.vue'; -import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/topbar/constants'; +import ProjectFilter from '~/search/sidebar/components/project_filter.vue'; +import SearchableDropdown from '~/search/sidebar/components/searchable_dropdown.vue'; +import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/sidebar/constants'; Vue.use(Vuex); @@ -27,12 +28,15 @@ describe('ProjectFilter', () => { const defaultProps = { initialData: null, + projectInitialJson: MOCK_PROJECT, + searchHandler: jest.fn(), }; const createComponent = (initialState, props) => { const store = new Vuex.Store({ state: { query: MOCK_QUERY, + projectInitialJson: MOCK_PROJECT, ...initialState, }, actions: actionSpies, @@ -68,18 +72,6 @@ describe('ProjectFilter', () => { createComponent(); }); - describe('when @search is emitted', () => { - const search = 'test'; - - beforeEach(() => { - findSearchableDropdown().vm.$emit('search', search); - }); - - it('calls fetchProjects with the search paramter', () => { - expect(actionSpies.fetchProjects).toHaveBeenCalledWith(expect.any(Object), search); - }); - }); - describe('when @change is emitted', () => { describe('with Any', () => { beforeEach(() => { @@ -139,17 +131,17 @@ describe('ProjectFilter', () => { describe('selectedProject', () => { describe('when initialData is null', () => { beforeEach(() => { - createComponent(); + createComponent({ projectInitialJson: ANY_OPTION }, {}); }); it('sets selectedProject to ANY_OPTION', () => { - expect(wrapper.vm.selectedProject).toBe(ANY_OPTION); + expect(cloneDeep(wrapper.vm.selectedProject)).toStrictEqual(ANY_OPTION); }); }); describe('when initialData is set', () => { beforeEach(() => { - createComponent({}, { initialData: MOCK_PROJECT }); + createComponent({ projectInitialJson: MOCK_PROJECT }, {}); }); it('sets selectedProject to the initialData', () => { @@ -170,7 +162,13 @@ describe('ProjectFilter', () => { initialData ? 'has' : 'does not have' } an initial project`, () => { beforeEach(() => { - createComponent({ query: { ...MOCK_QUERY, nav_source: navSource } }, { initialData }); + createComponent( + { + query: { ...MOCK_QUERY, nav_source: navSource }, + projectInitialJson: { ...initialData }, + }, + {}, + ); }); it(`${callMethod ? 'does' : 'does not'} call setFrequentProject`, () => { diff --git a/spec/frontend/search/sidebar/components/scope_legacy_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_legacy_navigation_spec.js deleted file mode 100644 index 63d8b34fcf0..00000000000 --- a/spec/frontend/search/sidebar/components/scope_legacy_navigation_spec.js +++ /dev/null @@ -1,145 +0,0 @@ -import { GlNav, GlNavItem, GlIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -// eslint-disable-next-line no-restricted-imports -import Vuex from 'vuex'; -import { MOCK_QUERY, MOCK_NAVIGATION } from 'jest/search/mock_data'; -import ScopeLegacyNavigation from '~/search/sidebar/components/scope_legacy_navigation.vue'; - -Vue.use(Vuex); - -const MOCK_NAVIGATION_ENTRIES = Object.entries(MOCK_NAVIGATION); - -describe('ScopeLegacyNavigation', () => { - let wrapper; - - const actionSpies = { - fetchSidebarCount: jest.fn(), - }; - - const getterSpies = { - currentScope: jest.fn(() => 'issues'), - }; - - const createComponent = (initialState) => { - const store = new Vuex.Store({ - state: { - urlQuery: MOCK_QUERY, - navigation: MOCK_NAVIGATION, - ...initialState, - }, - actions: actionSpies, - getters: getterSpies, - }); - - wrapper = shallowMount(ScopeLegacyNavigation, { - store, - }); - }; - - const findNavElement = () => wrapper.find('nav'); - const findGlNav = () => wrapper.findComponent(GlNav); - const findGlNavItems = () => wrapper.findAllComponents(GlNavItem); - const findGlNavItemActive = () => wrapper.find('[active=true]'); - const findGlNavItemActiveLabel = () => findGlNavItemActive().find('[data-testid="label"]'); - const findGlNavItemActiveCount = () => findGlNavItemActive().find('[data-testid="count"]'); - - describe('scope navigation', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders section', () => { - expect(findNavElement().exists()).toBe(true); - }); - - it('renders nav component', () => { - expect(findGlNav().exists()).toBe(true); - }); - - it('renders all nav item components', () => { - expect(findGlNavItems()).toHaveLength(MOCK_NAVIGATION_ENTRIES.length); - }); - - it('has all proper links', () => { - const linkAtPosition = 3; - const { link } = MOCK_NAVIGATION_ENTRIES[linkAtPosition][1]; - - expect(findGlNavItems().at(linkAtPosition).attributes('href')).toBe(link); - }); - }); - - describe('scope navigation sets proper state with url scope set', () => { - beforeEach(() => { - createComponent(); - }); - - it('has correct active item', () => { - expect(findGlNavItemActive().exists()).toBe(true); - expect(findGlNavItemActiveLabel().text()).toBe('Issues'); - }); - - it('has correct active item count', () => { - expect(findGlNavItemActiveCount().text()).toBe('2.4K'); - }); - - it('does not have plus sign after count text', () => { - expect(findGlNavItemActive().findComponent(GlIcon).exists()).toBe(false); - }); - - it('has count is highlighted correctly', () => { - expect(findGlNavItemActiveCount().classes('gl-text-gray-900')).toBe(true); - }); - }); - - describe('scope navigation sets proper state with NO url scope set', () => { - beforeEach(() => { - getterSpies.currentScope = jest.fn(() => 'projects'); - createComponent({ - urlQuery: {}, - navigation: { - ...MOCK_NAVIGATION, - projects: { - ...MOCK_NAVIGATION.projects, - active: true, - }, - issues: { - ...MOCK_NAVIGATION.issues, - active: false, - }, - }, - }); - }); - - it('has correct active item', () => { - expect(findGlNavItemActive().exists()).toBe(true); - expect(findGlNavItemActiveLabel().text()).toBe('Projects'); - }); - - it('has correct active item count', () => { - expect(findGlNavItemActiveCount().text()).toBe('10K'); - }); - - it('has correct active item count and over limit sign', () => { - expect(findGlNavItemActive().findComponent(GlIcon).exists()).toBe(true); - }); - }); - - describe.each` - searchTherm | hasBeenCalled - ${null} | ${0} - ${'test'} | ${1} - `('fetchSidebarCount', ({ searchTherm, hasBeenCalled }) => { - beforeEach(() => { - createComponent({ - urlQuery: { - search: searchTherm, - }, - }); - }); - - it('is only called when search term is set', () => { - expect(actionSpies.fetchSidebarCount).toHaveBeenCalledTimes(hasBeenCalled); - }); - }); -}); diff --git a/spec/frontend/search/sidebar/components/scope_sidebar_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_sidebar_navigation_spec.js index d85942b9634..44c243d15f7 100644 --- a/spec/frontend/search/sidebar/components/scope_sidebar_navigation_spec.js +++ b/spec/frontend/search/sidebar/components/scope_sidebar_navigation_spec.js @@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; // eslint-disable-next-line no-restricted-imports import Vuex from 'vuex'; +import sidebarEventHub from '~/super_sidebar/event_hub'; import ScopeSidebarNavigation from '~/search/sidebar/components/scope_sidebar_navigation.vue'; import NavItem from '~/super_sidebar/components/nav_item.vue'; import { MOCK_QUERY, MOCK_NAVIGATION, MOCK_NAVIGATION_ITEMS } from '../../mock_data'; @@ -49,6 +50,7 @@ describe('ScopeSidebarNavigation', () => { describe('scope navigation', () => { beforeEach(() => { + jest.spyOn(sidebarEventHub, '$emit'); createComponent({ urlQuery: { ...MOCK_QUERY, search: 'test' } }); }); @@ -71,6 +73,11 @@ describe('ScopeSidebarNavigation', () => { expect(findNavItems().at(linkAtPosition).findComponent('a').attributes('href')).toBe(link); }); + + it('always emits toggle-menu-header event', () => { + expect(sidebarEventHub.$emit).toHaveBeenCalledWith('toggle-menu-header', false); + expect(sidebarEventHub.$emit).toHaveBeenCalledTimes(1); + }); }); describe('scope navigation sets proper state with url scope set', () => { diff --git a/spec/frontend/search/sidebar/components/searchable_dropdown_spec.js b/spec/frontend/search/sidebar/components/searchable_dropdown_spec.js new file mode 100644 index 00000000000..c8f157e4fe4 --- /dev/null +++ b/spec/frontend/search/sidebar/components/searchable_dropdown_spec.js @@ -0,0 +1,117 @@ +import { GlCollapsibleListbox } from '@gitlab/ui'; +import { cloneDeep } from 'lodash'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +// eslint-disable-next-line no-restricted-imports +import Vuex from 'vuex'; +import waitForPromises from 'helpers/wait_for_promises'; +import { MOCK_GROUPS, MOCK_QUERY } from 'jest/search/mock_data'; +import SearchableDropdown from '~/search/sidebar/components/searchable_dropdown.vue'; +import { ANY_OPTION, GROUP_DATA } from '~/search/sidebar/constants'; +import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; + +Vue.use(Vuex); + +describe('Global Search Searchable Dropdown', () => { + let wrapper; + + const defaultProps = { + headerText: GROUP_DATA.headerText, + name: GROUP_DATA.name, + fullName: GROUP_DATA.fullName, + loading: false, + selectedItem: ANY_OPTION, + items: [], + frequentItems: [{ ...MOCK_GROUPS[0] }], + searchHandler: jest.fn(), + }; + + const createComponent = (initialState, props) => { + const store = new Vuex.Store({ + state: { + query: MOCK_QUERY, + ...initialState, + }, + }); + + wrapper = shallowMount(SearchableDropdown, { + store, + propsData: { + ...defaultProps, + ...props, + }, + }); + }; + + const findGlDropdown = () => wrapper.findComponent(GlCollapsibleListbox); + + describe('template', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders GlDropdown', () => { + expect(findGlDropdown().exists()).toBe(true); + }); + + const propItems = [ + { text: '', options: [{ value: ANY_OPTION.name, text: ANY_OPTION.name, ...ANY_OPTION }] }, + { + text: 'Frequently searched', + options: [{ value: MOCK_GROUPS[0].id, text: MOCK_GROUPS[0].full_name, ...MOCK_GROUPS[0] }], + }, + { + text: 'All available groups', + options: [{ value: MOCK_GROUPS[1].id, text: MOCK_GROUPS[1].full_name, ...MOCK_GROUPS[1] }], + }, + ]; + + beforeEach(() => { + createComponent({}, { items: MOCK_GROUPS }); + }); + + it('contains correct set of items', () => { + expect(findGlDropdown().props('items')).toStrictEqual(propItems); + }); + + it('renders searchable prop', () => { + expect(findGlDropdown().props('searchable')).toBe(true); + }); + + describe('events', () => { + it('emits select', () => { + findGlDropdown().vm.$emit('select', 1); + expect(cloneDeep(wrapper.emitted('change')[0][0])).toStrictEqual(MOCK_GROUPS[0]); + }); + + it('emits reset', () => { + findGlDropdown().vm.$emit('reset'); + expect(cloneDeep(wrapper.emitted('change')[0][0])).toStrictEqual(ANY_OPTION); + }); + + it('emits first-open', () => { + findGlDropdown().vm.$emit('shown'); + expect(wrapper.emitted('first-open')).toHaveLength(1); + findGlDropdown().vm.$emit('shown'); + expect(wrapper.emitted('first-open')).toHaveLength(1); + }); + }); + }); + + describe('when @search is emitted', () => { + const search = 'test'; + + beforeEach(async () => { + createComponent(); + findGlDropdown().vm.$emit('search', search); + + jest.advanceTimersByTime(DEFAULT_DEBOUNCE_AND_THROTTLE_MS); + await waitForPromises(); + }); + + it('calls fetchGroups with the search paramter', () => { + expect(defaultProps.searchHandler).toHaveBeenCalledTimes(1); + expect(defaultProps.searchHandler).toHaveBeenCalledWith(search); + }); + }); +}); diff --git a/spec/frontend/search/sidebar/components/small_screen_drawer_navigation_spec.js b/spec/frontend/search/sidebar/components/small_screen_drawer_navigation_spec.js deleted file mode 100644 index 5ab4afba7f0..00000000000 --- a/spec/frontend/search/sidebar/components/small_screen_drawer_navigation_spec.js +++ /dev/null @@ -1,68 +0,0 @@ -import { nextTick } from 'vue'; -import { GlDrawer } from '@gitlab/ui'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import DomElementListener from '~/vue_shared/components/dom_element_listener.vue'; -import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; -import SmallScreenDrawerNavigation from '~/search/sidebar/components/small_screen_drawer_navigation.vue'; - -describe('ScopeLegacyNavigation', () => { - let wrapper; - let closeSpy; - let toggleSpy; - - const createComponent = () => { - wrapper = shallowMountExtended(SmallScreenDrawerNavigation, { - slots: { - default: '<div data-testid="default-slot-content">test</div>', - }, - }); - }; - - const findGlDrawer = () => wrapper.findComponent(GlDrawer); - const findTitle = () => wrapper.findComponent('h2'); - const findSlot = () => wrapper.findByTestId('default-slot-content'); - const findDomElementListener = () => wrapper.findComponent(DomElementListener); - - describe('small screen navigation', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders drawer', () => { - expect(findGlDrawer().exists()).toBe(true); - expect(findGlDrawer().attributes('zindex')).toBe(DRAWER_Z_INDEX.toString()); - expect(findGlDrawer().attributes('headerheight')).toBe('0'); - }); - - it('renders title', () => { - expect(findTitle().exists()).toBe(true); - }); - - it('renders slots', () => { - expect(findSlot().exists()).toBe(true); - }); - }); - - describe('actions', () => { - beforeEach(() => { - closeSpy = jest.spyOn(SmallScreenDrawerNavigation.methods, 'closeSmallScreenFilters'); - toggleSpy = jest.spyOn(SmallScreenDrawerNavigation.methods, 'toggleSmallScreenFilters'); - createComponent(); - }); - - it('calls onClose', () => { - findGlDrawer().vm.$emit('close'); - expect(closeSpy).toHaveBeenCalled(); - }); - - it('calls toggleSmallScreenFilters', async () => { - expect(findGlDrawer().props('open')).toBe(false); - - findDomElementListener().vm.$emit('click'); - await nextTick(); - - expect(toggleSpy).toHaveBeenCalled(); - expect(findGlDrawer().props('open')).toBe(true); - }); - }); -}); diff --git a/spec/frontend/search/sidebar/components/status_filter_spec.js b/spec/frontend/search/sidebar/components/status_filter_spec.js index c230341c172..719932a79ef 100644 --- a/spec/frontend/search/sidebar/components/status_filter_spec.js +++ b/spec/frontend/search/sidebar/components/status_filter_spec.js @@ -22,23 +22,9 @@ describe('StatusFilter', () => { const findRadioFilter = () => wrapper.findComponent(RadioFilter); - describe('old sidebar', () => { - beforeEach(() => { - createComponent({ useSidebarNavigation: false }); - }); - - it('renders the component', () => { - expect(findRadioFilter().exists()).toBe(true); - }); - }); + it('renders the component', () => { + createComponent(); - describe('new sidebar', () => { - beforeEach(() => { - createComponent({ useSidebarNavigation: true }); - }); - - it('renders the component', () => { - expect(findRadioFilter().exists()).toBe(true); - }); + expect(findRadioFilter().exists()).toBe(true); }); }); diff --git a/spec/frontend/search/store/mutations_spec.js b/spec/frontend/search/store/mutations_spec.js index a517932b0eb..3462d4a326b 100644 --- a/spec/frontend/search/store/mutations_spec.js +++ b/spec/frontend/search/store/mutations_spec.js @@ -31,7 +31,7 @@ describe('Global Search Store Mutations', () => { mutations[types.RECEIVE_GROUPS_SUCCESS](state, MOCK_GROUPS); expect(state.fetchingGroups).toBe(false); - expect(state.groups).toBe(MOCK_GROUPS); + expect(state.groups).toStrictEqual(MOCK_GROUPS); }); }); @@ -57,7 +57,7 @@ describe('Global Search Store Mutations', () => { mutations[types.RECEIVE_PROJECTS_SUCCESS](state, MOCK_PROJECTS); expect(state.fetchingProjects).toBe(false); - expect(state.projects).toBe(MOCK_PROJECTS); + expect(state.projects).toStrictEqual(MOCK_PROJECTS); }); }); diff --git a/spec/frontend/search/topbar/components/app_spec.js b/spec/frontend/search/topbar/components/app_spec.js index 9704277c86b..d17bdc2a6e1 100644 --- a/spec/frontend/search/topbar/components/app_spec.js +++ b/spec/frontend/search/topbar/components/app_spec.js @@ -1,14 +1,14 @@ -import { GlSearchBoxByClick, GlButton } from '@gitlab/ui'; +import { GlSearchBoxByType, GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; // eslint-disable-next-line no-restricted-imports import Vuex from 'vuex'; import { MOCK_QUERY } from 'jest/search/mock_data'; import { stubComponent } from 'helpers/stub_component'; import GlobalSearchTopbar from '~/search/topbar/components/app.vue'; -import GroupFilter from '~/search/topbar/components/group_filter.vue'; -import ProjectFilter from '~/search/topbar/components/project_filter.vue'; import MarkdownDrawer from '~/vue_shared/components/markdown_drawer/markdown_drawer.vue'; +import SearchTypeIndicator from '~/search/topbar/components/search_type_indicator.vue'; +import { ENTER_KEY } from '~/lib/utils/keys'; import { SYNTAX_OPTIONS_ADVANCED_DOCUMENT, SYNTAX_OPTIONS_ZOEKT_DOCUMENT, @@ -41,42 +41,22 @@ describe('GlobalSearchTopbar', () => { }); }; - const findGlSearchBox = () => wrapper.findComponent(GlSearchBoxByClick); - const findGroupFilter = () => wrapper.findComponent(GroupFilter); - const findProjectFilter = () => wrapper.findComponent(ProjectFilter); + const findGlSearchBox = () => wrapper.findComponent(GlSearchBoxByType); const findSyntaxOptionButton = () => wrapper.findComponent(GlButton); const findSyntaxOptionDrawer = () => wrapper.findComponent(MarkdownDrawer); + const findSearchTypeIndicator = () => wrapper.findComponent(SearchTypeIndicator); describe('template', () => { beforeEach(() => { createComponent(); }); - describe('Search box', () => { - it('renders always', () => { - expect(findGlSearchBox().exists()).toBe(true); - }); + it('always renders Search box', () => { + expect(findGlSearchBox().exists()).toBe(true); }); - describe.each` - snippets | showFilters - ${null} | ${true} - ${{ query: { snippets: '' } }} | ${true} - ${{ query: { snippets: false } }} | ${true} - ${{ query: { snippets: true } }} | ${false} - ${{ query: { snippets: 'false' } }} | ${true} - ${{ query: { snippets: 'true' } }} | ${false} - `('topbar filters', ({ snippets, showFilters }) => { - beforeEach(() => { - createComponent(snippets); - }); - - it(`does${showFilters ? '' : ' not'} render when snippets is ${JSON.stringify( - snippets, - )}`, () => { - expect(findGroupFilter().exists()).toBe(showFilters); - expect(findProjectFilter().exists()).toBe(showFilters); - }); + it('always renders Search indicator', () => { + expect(findSearchTypeIndicator().exists()).toBe(true); }); describe.each` @@ -128,15 +108,15 @@ describe('GlobalSearchTopbar', () => { }); describe.each` - state | defaultBranchName | hasSyntaxOptions - ${{ query: { repository_ref: '' }, searchType: 'basic' }} | ${'master'} | ${false} - ${{ query: { repository_ref: 'v0.1' }, searchType: 'basic' }} | ${''} | ${false} - ${{ query: { repository_ref: 'master' }, searchType: 'basic' }} | ${'master'} | ${false} - ${{ query: { repository_ref: 'master' }, searchType: 'advanced' }} | ${''} | ${false} - ${{ query: { repository_ref: '' }, searchType: 'advanced' }} | ${'master'} | ${true} - ${{ query: { repository_ref: 'v0.1' }, searchType: 'advanced' }} | ${''} | ${false} - ${{ query: { repository_ref: 'master' }, searchType: 'advanced' }} | ${'master'} | ${true} - ${{ query: { repository_ref: 'master' }, searchType: 'zoekt' }} | ${'master'} | ${true} + state | hasSyntaxOptions + ${{ query: { repository_ref: '' }, searchType: 'basic', searchLevel: 'project', defaultBranchName: 'master' }} | ${false} + ${{ query: { repository_ref: 'v0.1' }, searchType: 'basic', searchLevel: 'project', defaultBranchName: '' }} | ${false} + ${{ query: { repository_ref: 'master' }, searchType: 'basic', searchLevel: 'project', defaultBranchName: 'master' }} | ${false} + ${{ query: { repository_ref: 'master' }, searchType: 'advanced', searchLevel: 'project', defaultBranchName: '' }} | ${false} + ${{ query: { repository_ref: '' }, searchType: 'advanced', searchLevel: 'project', defaultBranchName: 'master' }} | ${true} + ${{ query: { repository_ref: 'v0.1' }, searchType: 'advanced', searchLevel: 'project', defaultBranchName: '' }} | ${false} + ${{ query: { repository_ref: 'master' }, searchType: 'advanced', searchLevel: 'project', defaultBranchName: 'master' }} | ${true} + ${{ query: { repository_ref: 'master' }, searchType: 'zoekt', searchLevel: 'project', defaultBranchName: 'master' }} | ${true} `( `the syntax option based on component state`, ({ state, defaultBranchName, hasSyntaxOptions }) => { @@ -162,9 +142,10 @@ describe('GlobalSearchTopbar', () => { createComponent(); }); - it('clicking search button inside search box calls applyQuery', () => { - findGlSearchBox().vm.$emit('submit', { preventDefault: () => {} }); + it('clicking search button inside search box calls applyQuery', async () => { + await nextTick(); + findGlSearchBox().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY })); expect(actionSpies.applyQuery).toHaveBeenCalled(); }); }); diff --git a/spec/frontend/search/topbar/components/search_type_indicator_spec.js b/spec/frontend/search/topbar/components/search_type_indicator_spec.js new file mode 100644 index 00000000000..d69ca6dfb16 --- /dev/null +++ b/spec/frontend/search/topbar/components/search_type_indicator_spec.js @@ -0,0 +1,128 @@ +import Vue from 'vue'; +// eslint-disable-next-line no-restricted-imports +import Vuex from 'vuex'; +import { GlSprintf } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { MOCK_QUERY } from 'jest/search/mock_data'; +import SearchTypeIndicator from '~/search/topbar/components/search_type_indicator.vue'; + +Vue.use(Vuex); + +describe('SearchTypeIndicator', () => { + let wrapper; + + const actionSpies = { + applyQuery: jest.fn(), + setQuery: jest.fn(), + preloadStoredFrequentItems: jest.fn(), + }; + + const createComponent = (initialState = {}) => { + const store = new Vuex.Store({ + state: { + query: MOCK_QUERY, + ...initialState, + }, + actions: actionSpies, + }); + + wrapper = shallowMountExtended(SearchTypeIndicator, { + store, + stubs: { + GlSprintf, + }, + }); + }; + + const findIndicator = (id) => wrapper.findAllByTestId(id); + const findDocsLink = () => wrapper.findComponentByTestId('docs-link'); + const findSyntaxDocsLink = () => wrapper.findComponentByTestId('syntax-docs-link'); + + // searchType and search level params cobination in this test reflects + // all possible combinations + + describe.each` + searchType | searchLevel | repository | showSearchTypeIndicator + ${'advanced'} | ${'project'} | ${'master'} | ${'advanced-enabled'} + ${'advanced'} | ${'project'} | ${'v0.1'} | ${'advanced-disabled'} + ${'advanced'} | ${'group'} | ${'master'} | ${'advanced-enabled'} + ${'advanced'} | ${'global'} | ${'master'} | ${'advanced-enabled'} + ${'zoekt'} | ${'project'} | ${'master'} | ${'zoekt-enabled'} + ${'zoekt'} | ${'project'} | ${'v0.1'} | ${'zoekt-disabled'} + ${'zoekt'} | ${'group'} | ${'master'} | ${'zoekt-enabled'} + `( + 'search type indicator for $searchType $searchLevel', + ({ searchType, repository, showSearchTypeIndicator, searchLevel }) => { + beforeEach(() => { + createComponent({ + query: { repository_ref: repository }, + searchType, + searchLevel, + defaultBranchName: 'master', + }); + }); + it('renders correctly', () => { + expect(findIndicator(showSearchTypeIndicator).exists()).toBe(true); + }); + }, + ); + + describe.each` + searchType | repository | showSearchTypeIndicator + ${'basic'} | ${'master'} | ${true} + ${'basic'} | ${'v0.1'} | ${true} + `( + 'search type indicator for $searchType and $repository', + ({ searchType, repository, showSearchTypeIndicator }) => { + beforeEach(() => { + createComponent({ + query: { repository_ref: repository }, + searchType, + defaultBranchName: 'master', + }); + }); + it.each(['zoekt-enabled', 'zoekt-disabled', 'advanced-enabled', 'advanced-disabled'])( + 'renders correct indicator %s', + () => { + expect(findIndicator(searchType).exists()).toBe(showSearchTypeIndicator); + }, + ); + }, + ); + + describe.each` + searchType | docsLink + ${'advanced'} | ${'/help/user/search/advanced_search'} + ${'zoekt'} | ${'/help/user/search/exact_code_search'} + `('documentation link for $searchType', ({ searchType, docsLink }) => { + beforeEach(() => { + createComponent({ + query: { repository_ref: 'master' }, + searchType, + searchLevel: 'project', + defaultBranchName: 'master', + }); + }); + it('has correct link', () => { + expect(findDocsLink().attributes('href')).toBe(docsLink); + }); + }); + + describe.each` + searchType | syntaxdocsLink + ${'advanced'} | ${'/help/user/search/advanced_search#use-the-advanced-search-syntax'} + ${'zoekt'} | ${'/help/user/search/exact_code_search#syntax'} + `('Syntax documentation $searchType', ({ searchType, syntaxdocsLink }) => { + beforeEach(() => { + createComponent({ + query: { repository_ref: '000' }, + searchType, + searchLevel: 'project', + defaultBranchName: 'master', + }); + }); + it('has correct link', () => { + expect(findSyntaxDocsLink().attributes('href')).toBe(syntaxdocsLink); + }); + }); +}); diff --git a/spec/frontend/search/topbar/components/searchable_dropdown_item_spec.js b/spec/frontend/search/topbar/components/searchable_dropdown_item_spec.js deleted file mode 100644 index c911fe53d40..00000000000 --- a/spec/frontend/search/topbar/components/searchable_dropdown_item_spec.js +++ /dev/null @@ -1,93 +0,0 @@ -import { GlDropdownItem, GlAvatar } from '@gitlab/ui'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { MOCK_GROUPS } from 'jest/search/mock_data'; -import { truncateNamespace } from '~/lib/utils/text_utility'; -import SearchableDropdownItem from '~/search/topbar/components/searchable_dropdown_item.vue'; -import { GROUP_DATA } from '~/search/topbar/constants'; - -describe('Global Search Searchable Dropdown Item', () => { - let wrapper; - - const defaultProps = { - item: MOCK_GROUPS[0], - selectedItem: MOCK_GROUPS[0], - name: GROUP_DATA.name, - fullName: GROUP_DATA.fullName, - }; - - const createComponent = (props) => { - wrapper = shallowMountExtended(SearchableDropdownItem, { - propsData: { - ...defaultProps, - ...props, - }, - }); - }; - - const findGlDropdownItem = () => wrapper.findComponent(GlDropdownItem); - const findGlAvatar = () => wrapper.findComponent(GlAvatar); - const findDropdownTitle = () => wrapper.findByTestId('item-title'); - const findDropdownSubtitle = () => wrapper.findByTestId('item-namespace'); - - describe('template', () => { - describe('always', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders GlDropdownItem', () => { - expect(findGlDropdownItem().exists()).toBe(true); - }); - - it('renders GlAvatar', () => { - expect(findGlAvatar().exists()).toBe(true); - }); - - it('renders Dropdown Title correctly', () => { - const titleEl = findDropdownTitle(); - - expect(titleEl.exists()).toBe(true); - expect(titleEl.text()).toBe(MOCK_GROUPS[0][GROUP_DATA.name]); - }); - - it('renders Dropdown Subtitle correctly', () => { - const subtitleEl = findDropdownSubtitle(); - - expect(subtitleEl.exists()).toBe(true); - expect(subtitleEl.text()).toBe(truncateNamespace(MOCK_GROUPS[0][GROUP_DATA.fullName])); - }); - }); - - describe('when item === selectedItem', () => { - beforeEach(() => { - createComponent({ item: MOCK_GROUPS[0], selectedItem: MOCK_GROUPS[0] }); - }); - - it('marks the dropdown as checked', () => { - expect(findGlDropdownItem().attributes('ischecked')).toBe('true'); - }); - }); - - describe('when item !== selectedItem', () => { - beforeEach(() => { - createComponent({ item: MOCK_GROUPS[0], selectedItem: MOCK_GROUPS[1] }); - }); - - it('marks the dropdown as not checked', () => { - expect(findGlDropdownItem().attributes('ischecked')).toBeUndefined(); - }); - }); - }); - - describe('actions', () => { - beforeEach(() => { - createComponent(); - }); - - it('clicking the dropdown item $emits change with the item', () => { - findGlDropdownItem().vm.$emit('click'); - - expect(wrapper.emitted('change')[0]).toEqual([MOCK_GROUPS[0]]); - }); - }); -}); diff --git a/spec/frontend/search/topbar/components/searchable_dropdown_spec.js b/spec/frontend/search/topbar/components/searchable_dropdown_spec.js deleted file mode 100644 index 5acaa1c1900..00000000000 --- a/spec/frontend/search/topbar/components/searchable_dropdown_spec.js +++ /dev/null @@ -1,220 +0,0 @@ -import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui'; -import { shallowMount, mount } from '@vue/test-utils'; -import Vue, { nextTick } from 'vue'; -// eslint-disable-next-line no-restricted-imports -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 SearchableDropdown from '~/search/topbar/components/searchable_dropdown.vue'; -import { ANY_OPTION, GROUP_DATA } from '~/search/topbar/constants'; - -Vue.use(Vuex); - -describe('Global Search Searchable Dropdown', () => { - let wrapper; - - const defaultProps = { - headerText: GROUP_DATA.headerText, - name: GROUP_DATA.name, - fullName: GROUP_DATA.fullName, - loading: false, - selectedItem: ANY_OPTION, - items: [], - }; - - const createComponent = (initialState, props, mountFn = shallowMount) => { - const store = new Vuex.Store({ - state: { - query: MOCK_QUERY, - ...initialState, - }, - }); - - wrapper = extendedWrapper( - mountFn(SearchableDropdown, { - store, - propsData: { - ...defaultProps, - ...props, - }, - }), - ); - }; - - const findGlDropdown = () => wrapper.findComponent(GlDropdown); - const findGlDropdownSearch = () => findGlDropdown().findComponent(GlSearchBoxByType); - const findDropdownText = () => findGlDropdown().find('.dropdown-toggle-text'); - const findSearchableDropdownItems = () => wrapper.findAllByTestId('searchable-items'); - const findFrequentDropdownItems = () => wrapper.findAllByTestId('frequent-items'); - const findAnyDropdownItem = () => findGlDropdown().findComponent(GlDropdownItem); - const findFirstSearchableDropdownItem = () => findSearchableDropdownItems().at(0); - const findFirstFrequentDropdownItem = () => findFrequentDropdownItems().at(0); - const findLoader = () => wrapper.findComponent(GlSkeletonLoader); - - describe('template', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders GlDropdown', () => { - expect(findGlDropdown().exists()).toBe(true); - }); - - describe('findGlDropdownSearch', () => { - it('renders always', () => { - expect(findGlDropdownSearch().exists()).toBe(true); - }); - - it('has debounce prop', () => { - expect(findGlDropdownSearch().attributes('debounce')).toBe('500'); - }); - - describe('onSearch', () => { - const search = 'test search'; - - beforeEach(() => { - findGlDropdownSearch().vm.$emit('input', search); - }); - - it('$emits @search when input event is fired from GlSearchBoxByType', () => { - expect(wrapper.emitted('search')[0]).toEqual([search]); - }); - }); - }); - - describe('Searchable Dropdown Items', () => { - describe('when loading is false', () => { - beforeEach(() => { - createComponent({}, { items: MOCK_GROUPS }); - }); - - it('does not render loader', () => { - expect(findLoader().exists()).toBe(false); - }); - - it('renders the Any Dropdown', () => { - expect(findAnyDropdownItem().exists()).toBe(true); - }); - - it('renders searchable dropdown item for each item', () => { - expect(findSearchableDropdownItems()).toHaveLength(MOCK_GROUPS.length); - }); - }); - - describe('when loading is true', () => { - beforeEach(() => { - createComponent({}, { loading: true, items: MOCK_GROUPS }); - }); - - it('does render loader', () => { - expect(findLoader().exists()).toBe(true); - }); - - it('renders the Any Dropdown', () => { - expect(findAnyDropdownItem().exists()).toBe(true); - }); - - it('does not render searchable dropdown items', () => { - 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 }); - findGlDropdownSearch().vm.$emit('input', searchText); - }); - - it(`should${length ? '' : ' not'} render frequent dropdown items`, () => { - expect(findFrequentDropdownItems()).toHaveLength(length); - }); - }); - }); - - describe('Dropdown Text', () => { - describe('when selectedItem is any', () => { - beforeEach(() => { - createComponent({}, {}, mount); - }); - - it('sets dropdown text to Any', () => { - expect(findDropdownText().text()).toBe(ANY_OPTION.name); - }); - }); - - describe('selectedItem is set', () => { - beforeEach(() => { - createComponent({}, { selectedItem: MOCK_GROUP }, mount); - }); - - it('sets dropdown text to the selectedItem name', () => { - expect(findDropdownText().text()).toBe(MOCK_GROUP[GROUP_DATA.name]); - }); - }); - }); - }); - - describe('actions', () => { - beforeEach(() => { - createComponent({}, { items: MOCK_GROUPS, frequentItems: MOCK_GROUPS }); - }); - - it('clicking "Any" dropdown item $emits @change with ANY_OPTION', () => { - findAnyDropdownItem().vm.$emit('click'); - - expect(wrapper.emitted('change')[0]).toEqual([ANY_OPTION]); - }); - - it('on searchable item @change, the wrapper $emits change with the item', () => { - 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]]); - }); - - describe('opening the dropdown', () => { - beforeEach(() => { - findGlDropdown().vm.$emit('show'); - }); - - it('$emits @search and @first-open on the first open', () => { - expect(wrapper.emitted('search')[0]).toStrictEqual(['']); - expect(wrapper.emitted('first-open')[0]).toStrictEqual([]); - }); - - describe('when the dropdown has been opened', () => { - it('$emits @search with the searchText', async () => { - const searchText = 'foo'; - - findGlDropdownSearch().vm.$emit('input', searchText); - await nextTick(); - - expect(wrapper.emitted('search')[1]).toStrictEqual([searchText]); - expect(wrapper.emitted('first-open')).toHaveLength(1); - }); - - it('does not emit @first-open again', async () => { - expect(wrapper.emitted('first-open')).toHaveLength(1); - - findGlDropdownSearch().vm.$emit('input'); - await nextTick(); - - expect(wrapper.emitted('first-open')).toHaveLength(1); - }); - }); - }); - }); -}); |