diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-09 12:09:48 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-09 12:09:48 +0000 |
commit | 3c53fbc50bf8d084f1184836468850d2a83ef920 (patch) | |
tree | 85c451a4082e7b5e8dc3ecb6265edb1aef0b14f0 /spec/frontend/search | |
parent | e7462f7b49a60b2ee7be14682c23190f7f7c5ba7 (diff) | |
download | gitlab-ce-3c53fbc50bf8d084f1184836468850d2a83ef920.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/search')
-rw-r--r-- | spec/frontend/search/mock_data.js | 25 | ||||
-rw-r--r-- | spec/frontend/search/store/actions_spec.js | 44 | ||||
-rw-r--r-- | spec/frontend/search/store/mutations_spec.js | 36 | ||||
-rw-r--r-- | spec/frontend/search/topbar/components/app_spec.js | 14 | ||||
-rw-r--r-- | spec/frontend/search/topbar/components/scope_tabs_spec.js | 122 |
5 files changed, 231 insertions, 10 deletions
diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js index d076997b04a..5cd16a856e7 100644 --- a/spec/frontend/search/mock_data.js +++ b/spec/frontend/search/mock_data.js @@ -61,3 +61,28 @@ export const MOCK_SORT_OPTIONS = [ }, }, ]; + +export const MOCK_SEARCH_COUNTS_INPUT = { + scopeTabs: ['issues', 'snippet_titles', 'merge_requests'], + activeCount: '15', +}; + +export const MOCK_SEARCH_COUNT = { scope: 'issues', count: '15' }; + +export const MOCK_SEARCH_COUNTS_SUCCESS = [ + { scope: 'issues', count: '15' }, + { scope: 'snippet_titles', count: '15' }, + { scope: 'merge_requests', count: '15' }, +]; + +export const MOCK_SEARCH_COUNTS = [ + { scope: 'issues', count: '15' }, + { scope: 'snippet_titles', count: '5' }, + { scope: 'merge_requests', count: '1' }, +]; + +export const MOCK_SCOPE_TABS = [ + { scope: 'issues', title: 'Issues', count: '15' }, + { scope: 'snippet_titles', title: 'Titles and Descriptions', count: '5' }, + { scope: 'merge_requests', title: 'Merge requests', count: '1' }, +]; diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js index e4536a3e136..f751b857c36 100644 --- a/spec/frontend/search/store/actions_spec.js +++ b/spec/frontend/search/store/actions_spec.js @@ -7,7 +7,15 @@ import * as urlUtils from '~/lib/utils/url_utility'; import createState from '~/search/store/state'; import axios from '~/lib/utils/axios_utils'; import createFlash from '~/flash'; -import { MOCK_QUERY, MOCK_GROUPS, MOCK_PROJECT, MOCK_PROJECTS } from '../mock_data'; +import { + MOCK_QUERY, + MOCK_GROUPS, + MOCK_PROJECT, + MOCK_PROJECTS, + MOCK_SEARCH_COUNT, + MOCK_SEARCH_COUNTS_SUCCESS, + MOCK_SEARCH_COUNTS_INPUT, +} from '../mock_data'; jest.mock('~/flash'); jest.mock('~/lib/utils/url_utility', () => ({ @@ -37,19 +45,21 @@ describe('Global Search Store Actions', () => { }); describe.each` - action | axiosMock | type | expectedMutations | callback - ${actions.fetchGroups} | ${{ method: 'onGet', code: 200, res: MOCK_GROUPS }} | ${'success'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_SUCCESS, payload: MOCK_GROUPS }]} | ${noCallback} - ${actions.fetchGroups} | ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_ERROR }]} | ${flashCallback} - ${actions.fetchProjects} | ${{ method: 'onGet', code: 200, res: MOCK_PROJECTS }} | ${'success'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_SUCCESS, payload: MOCK_PROJECTS }]} | ${noCallback} - ${actions.fetchProjects} | ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_ERROR }]} | ${flashCallback} - `(`axios calls`, ({ action, axiosMock, type, expectedMutations, callback }) => { + action | axiosMock | payload | type | expectedMutations | callback + ${actions.fetchGroups} | ${{ method: 'onGet', code: 200, res: MOCK_GROUPS }} | ${null} | ${'success'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_SUCCESS, payload: MOCK_GROUPS }]} | ${noCallback} + ${actions.fetchGroups} | ${{ method: 'onGet', code: 500, res: null }} | ${null} | ${'error'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_ERROR }]} | ${flashCallback} + ${actions.fetchProjects} | ${{ method: 'onGet', code: 200, res: MOCK_PROJECTS }} | ${null} | ${'success'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_SUCCESS, payload: MOCK_PROJECTS }]} | ${noCallback} + ${actions.fetchProjects} | ${{ method: 'onGet', code: 500, res: null }} | ${null} | ${'error'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_ERROR }]} | ${flashCallback} + ${actions.fetchSearchCounts} | ${{ method: 'onGet', code: 200, res: MOCK_SEARCH_COUNT }} | ${MOCK_SEARCH_COUNTS_INPUT} | ${'success'} | ${[{ type: types.REQUEST_SEARCH_COUNTS, payload: MOCK_SEARCH_COUNTS_INPUT }, { type: types.RECEIVE_SEARCH_COUNTS_SUCCESS, payload: MOCK_SEARCH_COUNTS_SUCCESS }]} | ${noCallback} + ${actions.fetchSearchCounts} | ${{ method: 'onGet', code: 500, res: null }} | ${MOCK_SEARCH_COUNTS_INPUT} | ${'error'} | ${[{ type: types.REQUEST_SEARCH_COUNTS, payload: MOCK_SEARCH_COUNTS_INPUT }]} | ${flashCallback} + `(`axios calls`, ({ action, axiosMock, payload, type, expectedMutations, callback }) => { describe(action.name, () => { describe(`on ${type}`, () => { beforeEach(() => { - mock[axiosMock.method]().replyOnce(axiosMock.code, axiosMock.res); + mock[axiosMock.method]().reply(axiosMock.code, axiosMock.res); }); it(`should dispatch the correct mutations`, () => { - return testAction({ action, state, expectedMutations }).then(() => callback()); + return testAction({ action, payload, state, expectedMutations }).then(() => callback()); }); }); }); @@ -115,9 +125,25 @@ describe('Global Search Store Actions', () => { page: null, state: null, confidential: null, + nav_source: null, }); expect(urlUtils.visitUrl).toHaveBeenCalled(); }); }); }); + + it('calls setUrlParams with snippets, group_id, and project_id when snippets param is true', () => { + return testAction(actions.resetQuery, true, state, [], [], () => { + expect(urlUtils.setUrlParams).toHaveBeenCalledWith({ + ...state.query, + page: null, + state: null, + confidential: null, + nav_source: null, + group_id: null, + project_id: null, + snippets: true, + }); + }); + }); }); diff --git a/spec/frontend/search/store/mutations_spec.js b/spec/frontend/search/store/mutations_spec.js index 560ed66263b..e9a1107cb07 100644 --- a/spec/frontend/search/store/mutations_spec.js +++ b/spec/frontend/search/store/mutations_spec.js @@ -1,7 +1,13 @@ import mutations from '~/search/store/mutations'; import createState from '~/search/store/state'; import * as types from '~/search/store/mutation_types'; -import { MOCK_QUERY, MOCK_GROUPS, MOCK_PROJECTS } from '../mock_data'; +import { + MOCK_QUERY, + MOCK_GROUPS, + MOCK_PROJECTS, + MOCK_SEARCH_COUNTS, + MOCK_SCOPE_TABS, +} from '../mock_data'; describe('Global Search Store Mutations', () => { let state; @@ -71,4 +77,32 @@ describe('Global Search Store Mutations', () => { expect(state.query[payload.key]).toBe(payload.value); }); }); + + describe('REQUEST_SEARCH_COUNTS', () => { + it('sets the count to for the query.scope activeCount', () => { + const payload = { scopeTabs: ['issues'], activeCount: '22' }; + mutations[types.REQUEST_SEARCH_COUNTS](state, payload); + + expect(state.inflatedScopeTabs).toStrictEqual([ + { scope: 'issues', title: 'Issues', count: '22' }, + ]); + }); + + it('sets other scopes count to empty string', () => { + const payload = { scopeTabs: ['milestones'], activeCount: '22' }; + mutations[types.REQUEST_SEARCH_COUNTS](state, payload); + + expect(state.inflatedScopeTabs).toStrictEqual([ + { scope: 'milestones', title: 'Milestones', count: '' }, + ]); + }); + }); + + describe('RECEIVE_SEARCH_COUNTS_SUCCESS', () => { + it('sets the count from the input for all tabs', () => { + mutations[types.RECEIVE_SEARCH_COUNTS_SUCCESS](state, MOCK_SEARCH_COUNTS); + + expect(state.inflatedScopeTabs).toStrictEqual(MOCK_SCOPE_TABS); + }); + }); }); diff --git a/spec/frontend/search/topbar/components/app_spec.js b/spec/frontend/search/topbar/components/app_spec.js index faf3629b444..ff37c733fdb 100644 --- a/spec/frontend/search/topbar/components/app_spec.js +++ b/spec/frontend/search/topbar/components/app_spec.js @@ -5,6 +5,7 @@ import { MOCK_QUERY } from 'jest/search/mock_data'; 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 ScopeTabs from '~/search/topbar/components/scope_tabs.vue'; const localVue = createLocalVue(); localVue.use(Vuex); @@ -42,6 +43,7 @@ describe('GlobalSearchTopbar', () => { const findGroupFilter = () => wrapper.find(GroupFilter); const findProjectFilter = () => wrapper.find(ProjectFilter); const findSearchButton = () => wrapper.find(GlButton); + const findScopeTabs = () => wrapper.find(ScopeTabs); describe('template', () => { beforeEach(() => { @@ -52,6 +54,18 @@ describe('GlobalSearchTopbar', () => { expect(findTopbarForm().exists()).toBe(true); }); + describe('Scope Tabs', () => { + it('renders when search param is set', () => { + createComponent({ query: { search: 'test' } }); + expect(findScopeTabs().exists()).toBe(true); + }); + it('does not render search param is blank', () => { + createComponent({ query: {} }); + + expect(findScopeTabs().exists()).toBe(false); + }); + }); + describe('Search box', () => { it('renders always', () => { expect(findGlSearchBox().exists()).toBe(true); diff --git a/spec/frontend/search/topbar/components/scope_tabs_spec.js b/spec/frontend/search/topbar/components/scope_tabs_spec.js new file mode 100644 index 00000000000..5c06ce7a2ad --- /dev/null +++ b/spec/frontend/search/topbar/components/scope_tabs_spec.js @@ -0,0 +1,122 @@ +import Vuex from 'vuex'; +import { createLocalVue, mount } from '@vue/test-utils'; +import { GlTabs, GlTab, GlBadge } from '@gitlab/ui'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { MOCK_QUERY, MOCK_SCOPE_TABS } from 'jest/search/mock_data'; +import ScopeTabs from '~/search/topbar/components/scope_tabs.vue'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('ScopeTabs', () => { + let wrapper; + + const actionSpies = { + fetchSearchCounts: jest.fn(), + setQuery: jest.fn(), + resetQuery: jest.fn(), + }; + + const defaultProps = { + scopeTabs: ['issues', 'merge_requests', 'milestones'], + count: '20', + }; + + const createComponent = (props = {}, initialState = {}) => { + const store = new Vuex.Store({ + state: { + query: { + ...MOCK_QUERY, + search: 'test', + }, + ...initialState, + }, + actions: actionSpies, + }); + + wrapper = extendedWrapper( + mount(ScopeTabs, { + localVue, + store, + propsData: { + ...defaultProps, + ...props, + }, + }), + ); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findScopeTabs = () => wrapper.find(GlTabs); + const findTabs = () => wrapper.findAll(GlTab); + const findBadges = () => wrapper.findAll(GlBadge); + const findTabsTitle = () => + wrapper.findAll('[data-testid="tab-title"]').wrappers.map((w) => w.text()); + const findBadgesTitle = () => findBadges().wrappers.map((w) => w.text()); + const findBadgeByScope = (scope) => wrapper.findByTestId(`badge-${scope}`); + const findTabByScope = (scope) => wrapper.findByTestId(`tab-${scope}`); + + describe('template', () => { + beforeEach(() => { + createComponent({}, { inflatedScopeTabs: MOCK_SCOPE_TABS }); + }); + + it('always renders Scope Tabs', () => { + expect(findScopeTabs().exists()).toBe(true); + }); + + describe('findTabs', () => { + it('renders a tab for each scope', () => { + expect(findTabs()).toHaveLength(defaultProps.scopeTabs.length); + expect(findTabsTitle()).toStrictEqual([ + 'Issues', + 'Titles and Descriptions', + 'Merge requests', + ]); + }); + }); + + describe('findBadges', () => { + it('renders a badge for each scope', () => { + expect(findBadges()).toHaveLength(defaultProps.scopeTabs.length); + expect(findBadgesTitle()).toStrictEqual(['15', '5', '1']); + }); + + it('sets the variant to neutral for active tab only', () => { + expect(findBadgeByScope('issues').classes()).toContain('badge-neutral'); + expect(findBadgeByScope('snippet_titles').classes()).toContain('badge-muted'); + expect(findBadgeByScope('merge_requests').classes()).toContain('badge-muted'); + }); + }); + }); + + describe('methods', () => { + beforeEach(() => { + createComponent({}, { inflatedScopeTabs: MOCK_SCOPE_TABS }); + + findTabByScope('snippet_titles').vm.$emit('click'); + }); + + describe('handleTabChange', () => { + it('calls setQuery with scope, applies any search params from ALL_SCOPE_TABS, and sends nulls for page, state, confidential, and nav_source', () => { + expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), { + key: 'scope', + value: 'snippet_titles', + }); + }); + + it('calls resetQuery and sends true for snippet_titles tab', () => { + expect(actionSpies.resetQuery).toHaveBeenCalledWith(expect.any(Object), true); + }); + + it('calls resetQuery and does not send true for other tabs', () => { + findTabByScope('issues').vm.$emit('click'); + expect(actionSpies.resetQuery).toHaveBeenCalledWith(expect.any(Object), false); + }); + }); + }); +}); |