diff options
Diffstat (limited to 'spec/frontend/search/sidebar')
16 files changed, 599 insertions, 455 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/sidebar/components/group_filter_spec.js b/spec/frontend/search/sidebar/components/group_filter_spec.js new file mode 100644 index 00000000000..a90a8a38267 --- /dev/null +++ b/spec/frontend/search/sidebar/components/group_filter_spec.js @@ -0,0 +1,183 @@ +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/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); + +jest.mock('~/lib/utils/url_utility', () => ({ + visitUrl: jest.fn(), + setUrlParams: jest.fn(), +})); + +describe('GroupFilter', () => { + let wrapper; + + const actionSpies = { + fetchGroups: jest.fn(), + setFrequentGroup: jest.fn(), + loadFrequentGroups: jest.fn(), + }; + + const defaultProps = { + initialData: null, + searchHandler: jest.fn(), + }; + + const createComponent = (initialState, props) => { + const store = new Vuex.Store({ + state: { + query: MOCK_QUERY, + ...initialState, + }, + actions: actionSpies, + getters: { + frequentGroups: () => [], + currentScope: () => CURRENT_SCOPE, + }, + }); + + wrapper = shallowMount(GroupFilter, { + store, + propsData: { + ...defaultProps, + ...props, + }, + }); + }; + + const findSearchableDropdown = () => wrapper.findComponent(SearchableDropdown); + + describe('template', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders SearchableDropdown always', () => { + expect(findSearchableDropdown().exists()).toBe(true); + }); + }); + + describe('events', () => { + beforeEach(() => { + createComponent(); + }); + + describe('when @change is emitted with Any', () => { + beforeEach(() => { + findSearchableDropdown().vm.$emit('change', ANY_OPTION); + }); + + it('calls setUrlParams with group null, project id null, nav_source null, and then calls visitUrl', () => { + expect(setUrlParams).toHaveBeenCalledWith({ + [GROUP_DATA.queryParam]: null, + [PROJECT_DATA.queryParam]: null, + nav_source: null, + scope: CURRENT_SCOPE, + }); + + expect(visitUrl).toHaveBeenCalled(); + }); + + it('does not call setFrequentGroup', () => { + expect(actionSpies.setFrequentGroup).not.toHaveBeenCalled(); + }); + }); + + describe('when @change is emitted with a group', () => { + beforeEach(() => { + findSearchableDropdown().vm.$emit('change', MOCK_GROUP); + }); + + it('calls setUrlParams with group id, project id null, nav_source null, and then calls visitUrl', () => { + expect(setUrlParams).toHaveBeenCalledWith({ + [GROUP_DATA.queryParam]: MOCK_GROUP.id, + [PROJECT_DATA.queryParam]: null, + nav_source: null, + scope: CURRENT_SCOPE, + }); + + expect(visitUrl).toHaveBeenCalled(); + }); + + it(`calls setFrequentGroup with the group and ${GROUPS_LOCAL_STORAGE_KEY}`, () => { + expect(actionSpies.setFrequentGroup).toHaveBeenCalledWith(expect.any(Object), MOCK_GROUP); + }); + }); + + describe('when @first-open is emitted', () => { + beforeEach(() => { + findSearchableDropdown().vm.$emit('first-open'); + }); + + it('calls loadFrequentGroups', () => { + expect(actionSpies.loadFrequentGroups).toHaveBeenCalledTimes(1); + }); + }); + }); + + describe('computed', () => { + describe('selectedGroup', () => { + describe('when initialData is null', () => { + beforeEach(() => { + createComponent(); + }); + + it('sets selectedGroup to ANY_OPTION', () => { + expect(wrapper.vm.selectedGroup).toBe(ANY_OPTION); + }); + }); + + describe('when initialData is set', () => { + beforeEach(() => { + createComponent({ groupInitialJson: { ...MOCK_GROUP } }, {}); + }); + + it('sets selectedGroup to ANY_OPTION', () => { + // cloneDeep to fix Property or method `nodeType` is not defined bug + expect(cloneDeep(wrapper.vm.selectedGroup)).toStrictEqual(MOCK_GROUP); + }); + }); + }); + }); + + describe.each` + navSource | initialData | callMethod + ${null} | ${null} | ${false} + ${null} | ${MOCK_GROUP} | ${false} + ${'navbar'} | ${null} | ${false} + ${'navbar'} | ${MOCK_GROUP} | ${true} + `('onCreate', ({ navSource, initialData, callMethod }) => { + describe(`when nav_source is ${navSource} and ${ + initialData ? 'has' : 'does not have' + } an initial group`, () => { + beforeEach(() => { + createComponent( + { + query: { ...MOCK_QUERY, nav_source: navSource }, + groupInitialJson: { ...initialData }, + }, + {}, + ); + }); + + it(`${callMethod ? 'does' : 'does not'} call setFrequentGroup`, () => { + if (callMethod) { + expect(actionSpies.setFrequentGroup).toHaveBeenCalledWith( + expect.any(Object), + initialData, + ); + } else { + expect(actionSpies.setFrequentGroup).not.toHaveBeenCalled(); + } + }); + }); + }); +}); 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/sidebar/components/project_filter_spec.js b/spec/frontend/search/sidebar/components/project_filter_spec.js new file mode 100644 index 00000000000..817ec77380f --- /dev/null +++ b/spec/frontend/search/sidebar/components/project_filter_spec.js @@ -0,0 +1,186 @@ +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/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); + +jest.mock('~/lib/utils/url_utility', () => ({ + visitUrl: jest.fn(), + setUrlParams: jest.fn(), +})); + +describe('ProjectFilter', () => { + let wrapper; + + const actionSpies = { + fetchProjects: jest.fn(), + setFrequentProject: jest.fn(), + loadFrequentProjects: jest.fn(), + }; + + 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, + getters: { + frequentProjects: () => [], + currentScope: () => CURRENT_SCOPE, + }, + }); + + wrapper = shallowMount(ProjectFilter, { + store, + propsData: { + ...defaultProps, + ...props, + }, + }); + }; + + const findSearchableDropdown = () => wrapper.findComponent(SearchableDropdown); + + describe('template', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders SearchableDropdown always', () => { + expect(findSearchableDropdown().exists()).toBe(true); + }); + }); + + describe('events', () => { + beforeEach(() => { + createComponent(); + }); + + describe('when @change is emitted', () => { + describe('with Any', () => { + beforeEach(() => { + findSearchableDropdown().vm.$emit('change', ANY_OPTION); + }); + + it('calls setUrlParams with null, no group id, nav_source null, then calls visitUrl', () => { + expect(setUrlParams).toHaveBeenCalledWith({ + [PROJECT_DATA.queryParam]: null, + nav_source: null, + scope: CURRENT_SCOPE, + }); + expect(visitUrl).toHaveBeenCalled(); + }); + + it('does not call setFrequentProject', () => { + expect(actionSpies.setFrequentProject).not.toHaveBeenCalled(); + }); + }); + + describe('with a Project', () => { + beforeEach(() => { + findSearchableDropdown().vm.$emit('change', MOCK_PROJECT); + }); + + it('calls setUrlParams with project id, group id, nav_source null, then calls visitUrl', () => { + expect(setUrlParams).toHaveBeenCalledWith({ + [GROUP_DATA.queryParam]: MOCK_PROJECT.namespace.id, + [PROJECT_DATA.queryParam]: MOCK_PROJECT.id, + nav_source: null, + scope: CURRENT_SCOPE, + }); + expect(visitUrl).toHaveBeenCalled(); + }); + + it(`calls setFrequentProject with the group and ${PROJECTS_LOCAL_STORAGE_KEY}`, () => { + expect(actionSpies.setFrequentProject).toHaveBeenCalledWith( + expect.any(Object), + MOCK_PROJECT, + ); + }); + }); + }); + + describe('when @first-open is emitted', () => { + beforeEach(() => { + findSearchableDropdown().vm.$emit('first-open'); + }); + + it('calls loadFrequentProjects', () => { + expect(actionSpies.loadFrequentProjects).toHaveBeenCalledTimes(1); + }); + }); + }); + + describe('computed', () => { + describe('selectedProject', () => { + describe('when initialData is null', () => { + beforeEach(() => { + createComponent({ projectInitialJson: ANY_OPTION }, {}); + }); + + it('sets selectedProject to ANY_OPTION', () => { + expect(cloneDeep(wrapper.vm.selectedProject)).toStrictEqual(ANY_OPTION); + }); + }); + + describe('when initialData is set', () => { + beforeEach(() => { + createComponent({ projectInitialJson: MOCK_PROJECT }, {}); + }); + + it('sets selectedProject to the initialData', () => { + expect(wrapper.vm.selectedProject).toBe(MOCK_PROJECT); + }); + }); + }); + }); + + describe.each` + navSource | initialData | callMethod + ${null} | ${null} | ${false} + ${null} | ${MOCK_PROJECT} | ${false} + ${'navbar'} | ${null} | ${false} + ${'navbar'} | ${MOCK_PROJECT} | ${true} + `('onCreate', ({ navSource, initialData, callMethod }) => { + describe(`when nav_source is ${navSource} and ${ + initialData ? 'has' : 'does not have' + } an initial project`, () => { + beforeEach(() => { + createComponent( + { + query: { ...MOCK_QUERY, nav_source: navSource }, + projectInitialJson: { ...initialData }, + }, + {}, + ); + }); + + it(`${callMethod ? 'does' : 'does not'} call setFrequentProject`, () => { + if (callMethod) { + expect(actionSpies.setFrequentProject).toHaveBeenCalledWith( + expect.any(Object), + initialData, + ); + } else { + expect(actionSpies.setFrequentProject).not.toHaveBeenCalled(); + } + }); + }); + }); +}); 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); }); }); |