From 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 18 Feb 2021 10:34:06 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-9-stable-ee --- .../search/highlight_blob_search_result_spec.js | 3 +- spec/frontend/search/index_spec.js | 3 + spec/frontend/search/mock_data.js | 22 ++- .../frontend/search/sidebar/components/app_spec.js | 4 +- .../components/confidentiality_filter_spec.js | 2 +- .../search/sidebar/components/radio_filter_spec.js | 6 +- .../sidebar/components/status_filter_spec.js | 4 +- spec/frontend/search/sort/components/app_spec.js | 168 +++++++++++++++++++++ spec/frontend/search/store/actions_spec.js | 6 +- spec/frontend/search/store/mutations_spec.js | 2 +- spec/frontend/search/topbar/components/app_spec.js | 113 ++++++++++++++ .../search/topbar/components/group_filter_spec.js | 2 +- .../topbar/components/project_filter_spec.js | 4 +- .../topbar/components/searchable_dropdown_spec.js | 4 +- 14 files changed, 322 insertions(+), 21 deletions(-) create mode 100644 spec/frontend/search/sort/components/app_spec.js create mode 100644 spec/frontend/search/topbar/components/app_spec.js (limited to 'spec/frontend/search') diff --git a/spec/frontend/search/highlight_blob_search_result_spec.js b/spec/frontend/search/highlight_blob_search_result_spec.js index 112e6f5124f..c1b0c7d794b 100644 --- a/spec/frontend/search/highlight_blob_search_result_spec.js +++ b/spec/frontend/search/highlight_blob_search_result_spec.js @@ -1,6 +1,7 @@ import setHighlightClass from '~/search/highlight_blob_search_result'; const fixture = 'search/blob_search_result.html'; +const searchKeyword = 'Send'; // spec/frontend/fixtures/search.rb#79 describe('search/highlight_blob_search_result', () => { preloadFixtures(fixture); @@ -8,7 +9,7 @@ describe('search/highlight_blob_search_result', () => { beforeEach(() => loadFixtures(fixture)); it('highlights lines with search term occurrence', () => { - setHighlightClass(); + setHighlightClass(searchKeyword); expect(document.querySelectorAll('.blob-result .hll').length).toBe(4); }); diff --git a/spec/frontend/search/index_spec.js b/spec/frontend/search/index_spec.js index 023cd341345..1992a7f4437 100644 --- a/spec/frontend/search/index_spec.js +++ b/spec/frontend/search/index_spec.js @@ -1,9 +1,11 @@ +import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result'; import { initSearchApp } from '~/search'; import createStore from '~/search/store'; jest.mock('~/search/store'); jest.mock('~/search/topbar'); jest.mock('~/search/sidebar'); +jest.mock('ee_else_ce/search/highlight_blob_search_result'); describe('initSearchApp', () => { let defaultLocation; @@ -42,6 +44,7 @@ describe('initSearchApp', () => { it(`decodes ${search} to ${decodedSearch}`, () => { expect(createStore).toHaveBeenCalledWith({ query: { search: decodedSearch } }); + expect(setHighlightClass).toHaveBeenCalledWith(decodedSearch); }); }); }); diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js index ee509eaad8d..d076997b04a 100644 --- a/spec/frontend/search/mock_data.js +++ b/spec/frontend/search/mock_data.js @@ -26,7 +26,7 @@ export const MOCK_GROUPS = [ export const MOCK_PROJECT = { name: 'test project', - namespace_id: MOCK_GROUP.id, + namespace: MOCK_GROUP, nameWithNamespace: 'test group test project', id: 'test_1', }; @@ -34,14 +34,30 @@ export const MOCK_PROJECT = { export const MOCK_PROJECTS = [ { name: 'test project', - namespace_id: MOCK_GROUP.id, + namespace: MOCK_GROUP, name_with_namespace: 'test group test project', id: 'test_1', }, { name: 'test project 2', - namespace_id: MOCK_GROUP.id, + namespace: MOCK_GROUP, name_with_namespace: 'test group test project 2', id: 'test_2', }, ]; + +export const MOCK_SORT_OPTIONS = [ + { + title: 'Most relevant', + sortable: false, + sortParam: 'relevant', + }, + { + title: 'Created date', + sortable: true, + sortParam: { + asc: 'created_asc', + desc: 'created_desc', + }, + }, +]; diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js index 94a39b90d02..b93527c1fe9 100644 --- a/spec/frontend/search/sidebar/components/app_spec.js +++ b/spec/frontend/search/sidebar/components/app_spec.js @@ -1,6 +1,6 @@ -import Vuex from 'vuex'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; import { GlButton, GlLink } from '@gitlab/ui'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +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'; diff --git a/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js b/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js index 42fcc859308..3713e1d414f 100644 --- a/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js +++ b/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js @@ -1,5 +1,5 @@ -import Vuex from 'vuex'; import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; import { MOCK_QUERY } from 'jest/search/mock_data'; import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter.vue'; import RadioFilter from '~/search/sidebar/components/radio_filter.vue'; diff --git a/spec/frontend/search/sidebar/components/radio_filter_spec.js b/spec/frontend/search/sidebar/components/radio_filter_spec.js index 9918af54cfe..4c81312e479 100644 --- a/spec/frontend/search/sidebar/components/radio_filter_spec.js +++ b/spec/frontend/search/sidebar/components/radio_filter_spec.js @@ -1,10 +1,10 @@ -import Vuex from 'vuex'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; import { GlFormRadioGroup, GlFormRadio } from '@gitlab/ui'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; import { MOCK_QUERY } from 'jest/search/mock_data'; import RadioFilter from '~/search/sidebar/components/radio_filter.vue'; -import { stateFilterData } from '~/search/sidebar/constants/state_filter_data'; import { confidentialFilterData } from '~/search/sidebar/constants/confidential_filter_data'; +import { stateFilterData } from '~/search/sidebar/constants/state_filter_data'; const localVue = createLocalVue(); localVue.use(Vuex); diff --git a/spec/frontend/search/sidebar/components/status_filter_spec.js b/spec/frontend/search/sidebar/components/status_filter_spec.js index 21fc663397e..08ce57b206b 100644 --- a/spec/frontend/search/sidebar/components/status_filter_spec.js +++ b/spec/frontend/search/sidebar/components/status_filter_spec.js @@ -1,8 +1,8 @@ -import Vuex from 'vuex'; import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; import { MOCK_QUERY } from 'jest/search/mock_data'; -import StatusFilter from '~/search/sidebar/components/status_filter.vue'; import RadioFilter from '~/search/sidebar/components/radio_filter.vue'; +import StatusFilter from '~/search/sidebar/components/status_filter.vue'; const localVue = createLocalVue(); localVue.use(Vuex); diff --git a/spec/frontend/search/sort/components/app_spec.js b/spec/frontend/search/sort/components/app_spec.js new file mode 100644 index 00000000000..5806d6b51d2 --- /dev/null +++ b/spec/frontend/search/sort/components/app_spec.js @@ -0,0 +1,168 @@ +import { GlButtonGroup, GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { MOCK_QUERY, MOCK_SORT_OPTIONS } from 'jest/search/mock_data'; +import GlobalSearchSort from '~/search/sort/components/app.vue'; +import { SORT_DIRECTION_UI } from '~/search/sort/constants'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('GlobalSearchSort', () => { + let wrapper; + + const actionSpies = { + setQuery: jest.fn(), + applyQuery: jest.fn(), + }; + + const defaultProps = { + searchSortOptions: MOCK_SORT_OPTIONS, + }; + + const createComponent = (initialState, props) => { + const store = new Vuex.Store({ + state: { + query: MOCK_QUERY, + ...initialState, + }, + actions: actionSpies, + }); + + wrapper = shallowMount(GlobalSearchSort, { + localVue, + store, + propsData: { + ...defaultProps, + ...props, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findSortButtonGroup = () => wrapper.find(GlButtonGroup); + const findSortDropdown = () => wrapper.find(GlDropdown); + const findSortDirectionButton = () => wrapper.find(GlButton); + const findDropdownItems = () => findSortDropdown().findAll(GlDropdownItem); + const findDropdownItemsText = () => findDropdownItems().wrappers.map((w) => w.text()); + + describe('template', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders Sort Button Group', () => { + expect(findSortButtonGroup().exists()).toBe(true); + }); + + it('renders Sort Dropdown', () => { + expect(findSortDropdown().exists()).toBe(true); + }); + + it('renders Sort Direction Button', () => { + expect(findSortDirectionButton().exists()).toBe(true); + }); + }); + + describe('Sort Dropdown Items', () => { + describe('renders', () => { + beforeEach(() => { + createComponent(); + }); + + it('an instance for each namespace', () => { + expect(findDropdownItemsText()).toStrictEqual( + MOCK_SORT_OPTIONS.map((option) => option.title), + ); + }); + }); + + describe.each` + sortQuery | value + ${null} | ${MOCK_SORT_OPTIONS[0].title} + ${'asdf'} | ${MOCK_SORT_OPTIONS[0].title} + ${MOCK_SORT_OPTIONS[0].sortParam} | ${MOCK_SORT_OPTIONS[0].title} + ${MOCK_SORT_OPTIONS[1].sortParam.desc} | ${MOCK_SORT_OPTIONS[1].title} + ${MOCK_SORT_OPTIONS[1].sortParam.asc} | ${MOCK_SORT_OPTIONS[1].title} + `('selected', ({ sortQuery, value }) => { + describe(`when sort option is ${sortQuery}`, () => { + beforeEach(() => { + createComponent({ query: { sort: sortQuery } }); + }); + + it('is set correctly', () => { + expect(findSortDropdown().attributes('text')).toBe(value); + }); + }); + }); + }); + + describe.each` + description | sortQuery | sortUi | disabled + ${'non-sortable'} | ${MOCK_SORT_OPTIONS[0].sortParam} | ${SORT_DIRECTION_UI.disabled} | ${'true'} + ${'descending sortable'} | ${MOCK_SORT_OPTIONS[1].sortParam.desc} | ${SORT_DIRECTION_UI.desc} | ${undefined} + ${'ascending sortable'} | ${MOCK_SORT_OPTIONS[1].sortParam.asc} | ${SORT_DIRECTION_UI.asc} | ${undefined} + `('Sort Direction Button', ({ description, sortQuery, sortUi, disabled }) => { + describe(`when sort option is ${description}`, () => { + beforeEach(() => { + createComponent({ query: { sort: sortQuery } }); + }); + + it('sets the UI correctly', () => { + expect(findSortDirectionButton().attributes('disabled')).toBe(disabled); + expect(findSortDirectionButton().attributes('title')).toBe(sortUi.tooltip); + expect(findSortDirectionButton().attributes('icon')).toBe(sortUi.icon); + }); + }); + }); + + describe('actions', () => { + describe.each` + description | index | value + ${'non-sortable'} | ${0} | ${MOCK_SORT_OPTIONS[0].sortParam} + ${'sortable'} | ${1} | ${MOCK_SORT_OPTIONS[1].sortParam.desc} + `('handleSortChange', ({ description, index, value }) => { + describe(`when clicking a ${description} option`, () => { + beforeEach(() => { + createComponent(); + findDropdownItems().at(index).vm.$emit('click'); + }); + + it('calls setQuery and applyQuery correctly', () => { + expect(actionSpies.setQuery).toHaveBeenCalledTimes(1); + expect(actionSpies.applyQuery).toHaveBeenCalledTimes(1); + expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), { + key: 'sort', + value, + }); + }); + }); + }); + + describe.each` + description | sortQuery | value + ${'descending'} | ${MOCK_SORT_OPTIONS[1].sortParam.desc} | ${MOCK_SORT_OPTIONS[1].sortParam.asc} + ${'ascending'} | ${MOCK_SORT_OPTIONS[1].sortParam.asc} | ${MOCK_SORT_OPTIONS[1].sortParam.desc} + `('handleSortDirectionChange', ({ description, sortQuery, value }) => { + describe(`when toggling a ${description} option`, () => { + beforeEach(() => { + createComponent({ query: { sort: sortQuery } }); + findSortDirectionButton().vm.$emit('click'); + }); + + it('calls setQuery and applyQuery correctly', () => { + expect(actionSpies.setQuery).toHaveBeenCalledTimes(1); + expect(actionSpies.applyQuery).toHaveBeenCalledTimes(1); + expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), { + key: 'sort', + value, + }); + }); + }); + }); + }); +}); diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js index e4536a3e136..ab622c53387 100644 --- a/spec/frontend/search/store/actions_spec.js +++ b/spec/frontend/search/store/actions_spec.js @@ -1,12 +1,12 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; import Api from '~/api'; +import createFlash from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import * as urlUtils from '~/lib/utils/url_utility'; import * as actions from '~/search/store/actions'; import * as types from '~/search/store/mutation_types'; -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'; jest.mock('~/flash'); diff --git a/spec/frontend/search/store/mutations_spec.js b/spec/frontend/search/store/mutations_spec.js index 560ed66263b..df94ba40ff2 100644 --- a/spec/frontend/search/store/mutations_spec.js +++ b/spec/frontend/search/store/mutations_spec.js @@ -1,6 +1,6 @@ +import * as types from '~/search/store/mutation_types'; 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'; describe('Global Search Store Mutations', () => { diff --git a/spec/frontend/search/topbar/components/app_spec.js b/spec/frontend/search/topbar/components/app_spec.js new file mode 100644 index 00000000000..fb953f2ed1b --- /dev/null +++ b/spec/frontend/search/topbar/components/app_spec.js @@ -0,0 +1,113 @@ +import { GlForm, GlSearchBoxByType, GlButton } from '@gitlab/ui'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +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'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('GlobalSearchTopbar', () => { + let wrapper; + + const actionSpies = { + applyQuery: jest.fn(), + setQuery: jest.fn(), + }; + + const createComponent = (initialState) => { + const store = new Vuex.Store({ + state: { + query: MOCK_QUERY, + ...initialState, + }, + actions: actionSpies, + }); + + wrapper = shallowMount(GlobalSearchTopbar, { + localVue, + store, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findTopbarForm = () => wrapper.find(GlForm); + const findGlSearchBox = () => wrapper.find(GlSearchBoxByType); + const findGroupFilter = () => wrapper.find(GroupFilter); + const findProjectFilter = () => wrapper.find(ProjectFilter); + const findSearchButton = () => wrapper.find(GlButton); + + describe('template', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders Topbar Form always', () => { + expect(findTopbarForm().exists()).toBe(true); + }); + + describe('Search box', () => { + it('renders always', () => { + expect(findGlSearchBox().exists()).toBe(true); + }); + + describe('onSearch', () => { + const testSearch = 'test search'; + + beforeEach(() => { + findGlSearchBox().vm.$emit('input', testSearch); + }); + + it('calls setQuery when input event is fired from GlSearchBoxByType', () => { + expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), { + key: 'search', + value: testSearch, + }); + }); + }); + }); + + 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('renders SearchButton always', () => { + expect(findSearchButton().exists()).toBe(true); + }); + }); + + describe('actions', () => { + beforeEach(() => { + createComponent(); + }); + + it('clicking SearchButton calls applyQuery', () => { + findTopbarForm().vm.$emit('submit', { preventDefault: () => {} }); + + expect(actionSpies.applyQuery).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/search/topbar/components/group_filter_spec.js b/spec/frontend/search/topbar/components/group_filter_spec.js index 017808d576e..15b46f9c058 100644 --- a/spec/frontend/search/topbar/components/group_filter_spec.js +++ b/spec/frontend/search/topbar/components/group_filter_spec.js @@ -1,5 +1,5 @@ -import Vuex from 'vuex'; import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; import { MOCK_GROUP, MOCK_QUERY } from 'jest/search/mock_data'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import GroupFilter from '~/search/topbar/components/group_filter.vue'; diff --git a/spec/frontend/search/topbar/components/project_filter_spec.js b/spec/frontend/search/topbar/components/project_filter_spec.js index c1fc61d7e89..3bd0769b34a 100644 --- a/spec/frontend/search/topbar/components/project_filter_spec.js +++ b/spec/frontend/search/topbar/components/project_filter_spec.js @@ -1,5 +1,5 @@ -import Vuex from 'vuex'; import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; import { MOCK_PROJECT, MOCK_QUERY } from 'jest/search/mock_data'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import ProjectFilter from '~/search/topbar/components/project_filter.vue'; @@ -99,7 +99,7 @@ describe('ProjectFilter', () => { it('calls setUrlParams with project id, group id, then calls visitUrl', () => { expect(setUrlParams).toHaveBeenCalledWith({ - [GROUP_DATA.queryParam]: MOCK_PROJECT.namespace_id, + [GROUP_DATA.queryParam]: MOCK_PROJECT.namespace.id, [PROJECT_DATA.queryParam]: MOCK_PROJECT.id, }); expect(visitUrl).toHaveBeenCalled(); diff --git a/spec/frontend/search/topbar/components/searchable_dropdown_spec.js b/spec/frontend/search/topbar/components/searchable_dropdown_spec.js index 86e29571d0f..5de948592d4 100644 --- a/spec/frontend/search/topbar/components/searchable_dropdown_spec.js +++ b/spec/frontend/search/topbar/components/searchable_dropdown_spec.js @@ -1,6 +1,6 @@ -import Vuex from 'vuex'; -import { createLocalVue, shallowMount, mount } from '@vue/test-utils'; import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui'; +import { createLocalVue, shallowMount, mount } from '@vue/test-utils'; +import Vuex from 'vuex'; 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'; -- cgit v1.2.1