diff options
Diffstat (limited to 'spec/frontend/search')
-rw-r--r-- | spec/frontend/search/mock_data.js | 84 | ||||
-rw-r--r-- | spec/frontend/search/sidebar/components/app_spec.js | 120 | ||||
-rw-r--r-- | spec/frontend/search/sidebar/components/filters_spec.js | 132 | ||||
-rw-r--r-- | spec/frontend/search/sidebar/components/scope_navigation_spec.js | 80 | ||||
-rw-r--r-- | spec/frontend/search/store/actions_spec.js | 35 | ||||
-rw-r--r-- | spec/frontend/search/store/mutations_spec.js | 22 |
6 files changed, 388 insertions, 85 deletions
diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js index 0542e96c77c..fa5ccfeb478 100644 --- a/spec/frontend/search/mock_data.js +++ b/spec/frontend/search/mock_data.js @@ -107,3 +107,87 @@ export const PROMISE_ALL_EXPECTED_MUTATIONS = { payload: { key: PROJECTS_LOCAL_STORAGE_KEY, data: [MOCK_FRESH_DATA_RES, MOCK_FRESH_DATA_RES] }, }, }; + +export const MOCK_NAVIGATION = { + projects: { + label: 'Projects', + scope: 'projects', + link: '/search?scope=projects&search=et', + count_link: '/search/count?scope=projects&search=et', + }, + blobs: { + label: 'Code', + scope: 'blobs', + link: '/search?scope=blobs&search=et', + count_link: '/search/count?scope=blobs&search=et', + }, + issues: { + label: 'Issues', + scope: 'issues', + link: '/search?scope=issues&search=et', + active: true, + count: '2,430', + }, + merge_requests: { + label: 'Merge requests', + scope: 'merge_requests', + link: '/search?scope=merge_requests&search=et', + count_link: '/search/count?scope=merge_requests&search=et', + }, + wiki_blobs: { + label: 'Wiki', + scope: 'wiki_blobs', + link: '/search?scope=wiki_blobs&search=et', + count_link: '/search/count?scope=wiki_blobs&search=et', + }, + commits: { + label: 'Commits', + scope: 'commits', + link: '/search?scope=commits&search=et', + count_link: '/search/count?scope=commits&search=et', + }, + notes: { + label: 'Comments', + scope: 'notes', + link: '/search?scope=notes&search=et', + count_link: '/search/count?scope=notes&search=et', + }, + milestones: { + label: 'Milestones', + scope: 'milestones', + link: '/search?scope=milestones&search=et', + count_link: '/search/count?scope=milestones&search=et', + }, + users: { + label: 'Users', + scope: 'users', + link: '/search?scope=users&search=et', + count_link: '/search/count?scope=users&search=et', + }, +}; + +export const MOCK_NAVIGATION_DATA = { + projects: { + label: 'Projects', + scope: 'projects', + link: '/search?scope=projects&search=et', + count_link: '/search/count?scope=projects&search=et', + }, +}; + +export const MOCK_ENDPOINT_RESPONSE = { count: '13' }; + +export const MOCK_DATA_FOR_NAVIGATION_ACTION_MUTATION = { + projects: { + count: '13', + label: 'Projects', + scope: 'projects', + link: '/search?scope=projects&search=et', + count_link: '/search/count?scope=projects&search=et', + }, +}; + +export const MOCK_NAVIGATION_ACTION_MUTATION = { + type: types.RECEIVE_NAVIGATION_COUNT, + payload: { key: 'projects', count: '13' }, +}; diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js index 89959feec39..e87217950cd 100644 --- a/spec/frontend/search/sidebar/components/app_spec.js +++ b/spec/frontend/search/sidebar/components/app_spec.js @@ -1,11 +1,10 @@ -import { GlButton, GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import Vuex from 'vuex'; import { MOCK_QUERY } from 'jest/search/mock_data'; import GlobalSearchSidebar from '~/search/sidebar/components/app.vue'; -import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter.vue'; -import StatusFilter from '~/search/sidebar/components/status_filter.vue'; +import ResultsFilters from '~/search/sidebar/components/results_filters.vue'; +import ScopeNavigation from '~/search/sidebar/components/scope_navigation.vue'; Vue.use(Vuex); @@ -17,7 +16,7 @@ describe('GlobalSearchSidebar', () => { resetQuery: jest.fn(), }; - const createComponent = (initialState) => { + const createComponent = (initialState, featureFlags) => { const store = new Vuex.Store({ state: { urlQuery: MOCK_QUERY, @@ -28,6 +27,11 @@ describe('GlobalSearchSidebar', () => { wrapper = shallowMount(GlobalSearchSidebar, { store, + provide: { + glFeatures: { + ...featureFlags, + }, + }, }); }; @@ -35,118 +39,68 @@ describe('GlobalSearchSidebar', () => { wrapper.destroy(); }); - const findSidebarForm = () => wrapper.find('form'); - const findStatusFilter = () => wrapper.findComponent(StatusFilter); - const findConfidentialityFilter = () => wrapper.findComponent(ConfidentialityFilter); - const findApplyButton = () => wrapper.findComponent(GlButton); - const findResetLinkButton = () => wrapper.findComponent(GlLink); + const findSidebarSection = () => wrapper.find('section'); + const findFilters = () => wrapper.findComponent(ResultsFilters); + const findSidebarNavigation = () => wrapper.findComponent(ScopeNavigation); - describe('template', () => { + describe('renders properly', () => { describe('scope=projects', () => { beforeEach(() => { createComponent({ urlQuery: { ...MOCK_QUERY, scope: 'projects' } }); }); - it("doesn't render StatusFilter", () => { - expect(findStatusFilter().exists()).toBe(false); - }); - - it("doesn't render ConfidentialityFilter", () => { - expect(findConfidentialityFilter().exists()).toBe(false); + it('shows section', () => { + expect(findSidebarSection().exists()).toBe(true); }); - it("doesn't render ApplyButton", () => { - expect(findApplyButton().exists()).toBe(false); + it("doesn't shows filters", () => { + expect(findFilters().exists()).toBe(false); }); }); - describe('scope=issues', () => { + describe('scope=merge_requests', () => { beforeEach(() => { - createComponent({ urlQuery: MOCK_QUERY }); - }); - it('renders StatusFilter', () => { - expect(findStatusFilter().exists()).toBe(true); + createComponent({ urlQuery: { ...MOCK_QUERY, scope: 'merge_requests' } }); }); - it('renders ConfidentialityFilter', () => { - expect(findConfidentialityFilter().exists()).toBe(true); + it('shows section', () => { + expect(findSidebarSection().exists()).toBe(true); }); - it('renders ApplyButton', () => { - expect(findApplyButton().exists()).toBe(true); + it('shows filters', () => { + expect(findFilters().exists()).toBe(true); }); }); - }); - describe('ApplyButton', () => { - describe('when sidebarDirty is false', () => { + describe('scope=issues', () => { beforeEach(() => { - createComponent({ sidebarDirty: false }); - }); - - it('disables the button', () => { - expect(findApplyButton().attributes('disabled')).toBe('true'); + createComponent({ urlQuery: MOCK_QUERY }); }); - }); - - describe('when sidebarDirty is true', () => { - beforeEach(() => { - createComponent({ sidebarDirty: true }); + it('shows section', () => { + expect(findSidebarSection().exists()).toBe(true); }); - it('enables the button', () => { - expect(findApplyButton().attributes('disabled')).toBe(undefined); + it('shows filters', () => { + expect(findFilters().exists()).toBe(true); }); }); }); - describe('ResetLinkButton', () => { - describe('with no filter selected', () => { - beforeEach(() => { - createComponent({ urlQuery: {} }); - }); - - it('does not render', () => { - expect(findResetLinkButton().exists()).toBe(false); - }); - }); - - describe('with filter selected', () => { - beforeEach(() => { - createComponent({ urlQuery: MOCK_QUERY }); - }); - - it('does render', () => { - expect(findResetLinkButton().exists()).toBe(true); - }); + describe('when search_page_vertical_nav is enabled', () => { + beforeEach(() => { + createComponent({}, { searchPageVerticalNav: true }); }); - - describe('with filter selected and user updated query back to default', () => { - beforeEach(() => { - createComponent({ urlQuery: MOCK_QUERY, query: {} }); - }); - - it('does render', () => { - expect(findResetLinkButton().exists()).toBe(true); - }); + it('shows the vertical navigation', () => { + expect(findSidebarNavigation().exists()).toBe(true); }); }); - describe('actions', () => { + describe('when search_page_vertical_nav is disabled', () => { beforeEach(() => { - createComponent({}); + createComponent({}, { searchPageVerticalNav: false }); }); - - it('clicking ApplyButton calls applyQuery', () => { - findSidebarForm().trigger('submit'); - - expect(actionSpies.applyQuery).toHaveBeenCalled(); - }); - - it('clicking ResetLinkButton calls resetQuery', () => { - findResetLinkButton().vm.$emit('click'); - - expect(actionSpies.resetQuery).toHaveBeenCalled(); + it('hides the vertical navigation', () => { + expect(findSidebarNavigation().exists()).toBe(false); }); }); }); diff --git a/spec/frontend/search/sidebar/components/filters_spec.js b/spec/frontend/search/sidebar/components/filters_spec.js new file mode 100644 index 00000000000..4f217709297 --- /dev/null +++ b/spec/frontend/search/sidebar/components/filters_spec.js @@ -0,0 +1,132 @@ +import { GlButton, GlLink } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import Vuex from 'vuex'; +import { MOCK_QUERY } from 'jest/search/mock_data'; +import ResultsFilters from '~/search/sidebar/components/results_filters.vue'; +import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter.vue'; +import StatusFilter from '~/search/sidebar/components/status_filter.vue'; + +Vue.use(Vuex); + +describe('GlobalSearchSidebarFilters', () => { + let wrapper; + + const actionSpies = { + applyQuery: jest.fn(), + resetQuery: jest.fn(), + }; + + const createComponent = (initialState) => { + const store = new Vuex.Store({ + state: { + urlQuery: MOCK_QUERY, + ...initialState, + }, + actions: actionSpies, + }); + + wrapper = shallowMount(ResultsFilters, { + store, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findSidebarForm = () => wrapper.find('form'); + const findStatusFilter = () => wrapper.findComponent(StatusFilter); + const findConfidentialityFilter = () => wrapper.findComponent(ConfidentialityFilter); + const findApplyButton = () => wrapper.findComponent(GlButton); + const findResetLinkButton = () => wrapper.findComponent(GlLink); + + describe('Renders correctly', () => { + beforeEach(() => { + createComponent({ urlQuery: MOCK_QUERY }); + }); + it('renders StatusFilter', () => { + expect(findStatusFilter().exists()).toBe(true); + }); + + it('renders ConfidentialityFilter', () => { + expect(findConfidentialityFilter().exists()).toBe(true); + }); + + it('renders ApplyButton', () => { + expect(findApplyButton().exists()).toBe(true); + }); + }); + + describe('ApplyButton', () => { + describe('when sidebarDirty is false', () => { + beforeEach(() => { + createComponent({ sidebarDirty: false }); + }); + + it('disables the button', () => { + expect(findApplyButton().attributes('disabled')).toBe('true'); + }); + }); + + describe('when sidebarDirty is true', () => { + beforeEach(() => { + createComponent({ sidebarDirty: true }); + }); + + it('enables the button', () => { + expect(findApplyButton().attributes('disabled')).toBe(undefined); + }); + }); + }); + + describe('ResetLinkButton', () => { + describe('with no filter selected', () => { + beforeEach(() => { + createComponent({ urlQuery: {} }); + }); + + it('does not render', () => { + expect(findResetLinkButton().exists()).toBe(false); + }); + }); + + describe('with filter selected', () => { + beforeEach(() => { + createComponent({ urlQuery: MOCK_QUERY }); + }); + + it('does render', () => { + expect(findResetLinkButton().exists()).toBe(true); + }); + }); + + describe('with filter selected and user updated query back to default', () => { + beforeEach(() => { + createComponent({ urlQuery: MOCK_QUERY, query: {} }); + }); + + it('does render', () => { + expect(findResetLinkButton().exists()).toBe(true); + }); + }); + }); + + describe('actions', () => { + beforeEach(() => { + createComponent({}); + }); + + it('clicking ApplyButton calls applyQuery', () => { + findSidebarForm().trigger('submit'); + + expect(actionSpies.applyQuery).toHaveBeenCalled(); + }); + + it('clicking ResetLinkButton calls resetQuery', () => { + findResetLinkButton().vm.$emit('click'); + + expect(actionSpies.resetQuery).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/search/sidebar/components/scope_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_navigation_spec.js new file mode 100644 index 00000000000..6262a52e01a --- /dev/null +++ b/spec/frontend/search/sidebar/components/scope_navigation_spec.js @@ -0,0 +1,80 @@ +import { GlNav, GlNavItem } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import Vuex from 'vuex'; +import { MOCK_QUERY, MOCK_NAVIGATION } from 'jest/search/mock_data'; +import ScopeNavigation from '~/search/sidebar/components/scope_navigation.vue'; + +Vue.use(Vuex); + +describe('ScopeNavigation', () => { + let wrapper; + + const actionSpies = { + fetchSidebarCount: jest.fn(), + }; + + const createComponent = (initialState) => { + const store = new Vuex.Store({ + state: { + urlQuery: MOCK_QUERY, + navigation: MOCK_NAVIGATION, + ...initialState, + }, + actions: actionSpies, + }); + + wrapper = shallowMount(ScopeNavigation, { + store, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findNavElement = () => wrapper.find('nav'); + const findGlNav = () => wrapper.findComponent(GlNav); + const findGlNavItems = () => wrapper.findAllComponents(GlNavItem); + const findGlNavItemActive = () => findGlNavItems().wrappers.filter((w) => w.attributes('active')); + const findGlNavItemActiveCount = () => findGlNavItemActive().at(0).findAll('span').at(1); + + 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(9); + }); + + it('nav items have proper links', () => { + const linkAtPosition = 3; + const { link } = MOCK_NAVIGATION[Object.keys(MOCK_NAVIGATION)[linkAtPosition]]; + + expect(findGlNavItems().at(linkAtPosition).attributes('href')).toBe(link); + }); + }); + + describe('scope navigation sets proper state', () => { + beforeEach(() => { + createComponent(); + }); + + it('sets proper class to active item', () => { + expect(findGlNavItemActive()).toHaveLength(1); + }); + + it('active item', () => { + expect(findGlNavItemActiveCount().text()).toBe('2.4K'); + }); + }); +}); diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js index c442ffa521d..3d19b27ff86 100644 --- a/spec/frontend/search/store/actions_spec.js +++ b/spec/frontend/search/store/actions_spec.js @@ -2,6 +2,7 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; import Api from '~/api'; import { createAlert } from '~/flash'; +import * as logger from '~/lib/logger'; import axios from '~/lib/utils/axios_utils'; import * as urlUtils from '~/lib/utils/url_utility'; import * as actions from '~/search/store/actions'; @@ -23,6 +24,9 @@ import { MOCK_FRESH_DATA_RES, PRELOAD_EXPECTED_MUTATIONS, PROMISE_ALL_EXPECTED_MUTATIONS, + MOCK_NAVIGATION_DATA, + MOCK_NAVIGATION_ACTION_MUTATION, + MOCK_ENDPOINT_RESPONSE, } from '../mock_data'; jest.mock('~/flash'); @@ -31,6 +35,9 @@ jest.mock('~/lib/utils/url_utility', () => ({ joinPaths: jest.fn().mockReturnValue(''), visitUrl: jest.fn(), })); +jest.mock('~/lib/logger', () => ({ + logError: jest.fn(), +})); describe('Global Search Store Actions', () => { let mock; @@ -260,4 +267,32 @@ describe('Global Search Store Actions', () => { ); }); }); + + describe.each` + action | axiosMock | type | scope | expectedMutations | errorLogs + ${actions.fetchSidebarCount} | ${{ method: 'onGet', code: 200 }} | ${'success'} | ${'issues'} | ${[MOCK_NAVIGATION_ACTION_MUTATION]} | ${0} + ${actions.fetchSidebarCount} | ${{ method: null, code: 0 }} | ${'success'} | ${'projects'} | ${[]} | ${0} + ${actions.fetchSidebarCount} | ${{ method: 'onGet', code: 500 }} | ${'error'} | ${'issues'} | ${[]} | ${1} + `('fetchSidebarCount', ({ action, axiosMock, type, expectedMutations, scope, errorLogs }) => { + describe(`on ${type}`, () => { + beforeEach(() => { + state.navigation = MOCK_NAVIGATION_DATA; + state.urlQuery = { + scope, + }; + + if (axiosMock.method) { + mock[axiosMock.method]().reply(axiosMock.code, MOCK_ENDPOINT_RESPONSE); + } + }); + + it(`should ${expectedMutations.length === 0 ? 'NOT ' : ''}dispatch ${ + expectedMutations.length === 0 ? '' : 'the correct ' + }mutations for ${scope}`, () => { + return testAction({ action, state, expectedMutations }).then(() => { + expect(logger.logError).toHaveBeenCalledTimes(errorLogs); + }); + }); + }); + }); }); diff --git a/spec/frontend/search/store/mutations_spec.js b/spec/frontend/search/store/mutations_spec.js index 25f9b692955..a79ec8f70b0 100644 --- a/spec/frontend/search/store/mutations_spec.js +++ b/spec/frontend/search/store/mutations_spec.js @@ -1,13 +1,20 @@ import * as types from '~/search/store/mutation_types'; import mutations from '~/search/store/mutations'; import createState from '~/search/store/state'; -import { MOCK_QUERY, MOCK_GROUPS, MOCK_PROJECTS } from '../mock_data'; +import { + MOCK_QUERY, + MOCK_GROUPS, + MOCK_PROJECTS, + MOCK_NAVIGATION_DATA, + MOCK_NAVIGATION_ACTION_MUTATION, + MOCK_DATA_FOR_NAVIGATION_ACTION_MUTATION, +} from '../mock_data'; describe('Global Search Store Mutations', () => { let state; beforeEach(() => { - state = createState({ query: MOCK_QUERY }); + state = createState({ query: MOCK_QUERY, navigation: MOCK_NAVIGATION_DATA }); }); describe('REQUEST_GROUPS', () => { @@ -90,4 +97,15 @@ describe('Global Search Store Mutations', () => { expect(state.frequentItems[payload.key]).toStrictEqual(payload.data); }); }); + + describe('RECEIVE_NAVIGATION_COUNT', () => { + it('sets frequentItems[key] to data', () => { + const { payload } = MOCK_NAVIGATION_ACTION_MUTATION; + mutations[types.RECEIVE_NAVIGATION_COUNT](state, payload); + + expect(state.navigation[payload.key]).toStrictEqual( + MOCK_DATA_FOR_NAVIGATION_ACTION_MUTATION[payload.key], + ); + }); + }); }); |