diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-23 21:11:07 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-23 21:11:07 +0300 |
commit | c5da163db1c10676b1a01a898b7b3a4506e65b89 (patch) | |
tree | 0110bf1518c8049cddab8d62e77d4f7dad2478a7 /spec/frontend | |
parent | 750a63ac4cc7acbecff3b8d22232cb1c15af34fb (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
7 files changed, 281 insertions, 111 deletions
diff --git a/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js b/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js index 4057759b9b9..d38226fedb2 100644 --- a/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js +++ b/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js @@ -1,12 +1,6 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import { - GlDropdown, - GlDropdownItem, - GlInfiniteScroll, - GlLoadingIcon, - GlSearchBoxByType, -} from '@gitlab/ui'; +import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; @@ -76,17 +70,15 @@ describe('Pipeline editor branch switcher', () => { totalBranches: mockTotalBranches, }, apolloProvider: mockApollo, + stubs: { GlCollapsibleListbox }, }); return waitForPromises(); }; - const findDropdown = () => wrapper.findComponent(GlDropdown); - const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); - const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType); - const findInfiniteScroll = () => wrapper.findComponent(GlInfiniteScroll); - const defaultBranchInDropdown = () => findDropdownItems().at(0); + const findGlCollapsibleListbox = () => wrapper.findComponent(GlCollapsibleListbox); + const findGlListboxItems = () => wrapper.findAllComponents(GlListboxItem); + const defaultBranchInDropdown = () => findGlListboxItems().at(0); const setAvailableBranchesMock = (availableBranches) => { mockAvailableBranchQuery.mockResolvedValue(availableBranches); @@ -112,11 +104,7 @@ describe('Pipeline editor branch switcher', () => { }); it('disables the dropdown', () => { - expect(findDropdown().props('disabled')).toBe(true); - }); - - it('shows loading icon', () => { - expect(findLoadingIcon().exists()).toBe(true); + expect(findGlCollapsibleListbox().props('disabled')).toBe(true); }); }); @@ -126,29 +114,25 @@ describe('Pipeline editor branch switcher', () => { await createComponent(); }); - it('does not render the loading icon', () => { - expect(findLoadingIcon().exists()).toBe(false); - }); - it('renders search box', () => { - expect(findSearchBox().exists()).toBe(true); + expect(findGlCollapsibleListbox().props().searchable).toBe(true); }); it('renders list of branches', () => { - expect(findDropdown().exists()).toBe(true); - expect(findDropdownItems()).toHaveLength(mockTotalBranchResults); + expect(findGlCollapsibleListbox().exists()).toBe(true); + expect(findGlListboxItems()).toHaveLength(mockTotalBranchResults); }); it('renders current branch with a check mark', () => { expect(defaultBranchInDropdown().text()).toBe(mockDefaultBranch); - expect(defaultBranchInDropdown().props('isChecked')).toBe(true); + expect(defaultBranchInDropdown().props('isSelected')).toBe(true); }); it('does not render check mark for other branches', () => { - const nonDefaultBranch = findDropdownItems().at(1); + const nonDefaultBranch = findGlListboxItems().at(1); expect(nonDefaultBranch.text()).not.toBe(mockDefaultBranch); - expect(nonDefaultBranch.props('isChecked')).toBe(false); + expect(nonDefaultBranch.props('isSelected')).toBe(false); }); }); @@ -159,7 +143,7 @@ describe('Pipeline editor branch switcher', () => { }); it('does not render dropdown', () => { - expect(findDropdown().props('disabled')).toBe(true); + expect(findGlCollapsibleListbox().props('disabled')).toBe(true); }); it('shows an error message', () => { @@ -175,8 +159,8 @@ describe('Pipeline editor branch switcher', () => { }); it('updates session history when selecting a different branch', async () => { - const branch = findDropdownItems().at(1); - branch.vm.$emit('click'); + const branch = findGlListboxItems().at(1); + findGlCollapsibleListbox().vm.$emit('select', branch.text()); await waitForPromises(); expect(window.history.pushState).toHaveBeenCalled(); @@ -184,7 +168,7 @@ describe('Pipeline editor branch switcher', () => { }); it('does not update session history when selecting current branch', async () => { - const branch = findDropdownItems().at(0); + const branch = findGlListboxItems().at(0); branch.vm.$emit('click'); await waitForPromises(); @@ -192,21 +176,21 @@ describe('Pipeline editor branch switcher', () => { expect(window.history.pushState).not.toHaveBeenCalled(); }); - it('emits the refetchContent event when selecting a different branch', async () => { - const branch = findDropdownItems().at(1); + it('emits the `refetchContent` event when selecting a different branch', async () => { + const branch = findGlListboxItems().at(1); expect(branch.text()).not.toBe(mockDefaultBranch); expect(wrapper.emitted('refetchContent')).toBeUndefined(); - branch.vm.$emit('click'); + findGlCollapsibleListbox().vm.$emit('select', branch.text()); await waitForPromises(); expect(wrapper.emitted('refetchContent')).toBeDefined(); expect(wrapper.emitted('refetchContent')).toHaveLength(1); }); - it('does not emit the refetchContent event when selecting the current branch', async () => { - const branch = findDropdownItems().at(0); + it('does not emit the `refetchContent` event when selecting the current branch', async () => { + const branch = findGlListboxItems().at(0); expect(branch.text()).toBe(mockDefaultBranch); expect(wrapper.emitted('refetchContent')).toBeUndefined(); @@ -223,11 +207,11 @@ describe('Pipeline editor branch switcher', () => { await waitForPromises(); }); - it('emits `select-branch` event and does not switch branch', async () => { + it('emits `select-branch` event and does not switch branch', () => { expect(wrapper.emitted('select-branch')).toBeUndefined(); - const branch = findDropdownItems().at(1); - await branch.vm.$emit('click'); + const branch = findGlListboxItems().at(1); + findGlCollapsibleListbox().vm.$emit('select', branch.text()); expect(wrapper.emitted('select-branch')).toEqual([[branch.text()]]); expect(wrapper.emitted('refetchContent')).toBeUndefined(); @@ -248,7 +232,7 @@ describe('Pipeline editor branch switcher', () => { it('shows error message on fetch error', async () => { mockAvailableBranchQuery.mockResolvedValue(new Error()); - findSearchBox().vm.$emit('input', 'te'); + findGlCollapsibleListbox().vm.$emit('search', 'te'); await waitForPromises(); testErrorHandling(); @@ -260,7 +244,8 @@ describe('Pipeline editor branch switcher', () => { }); it('calls query with correct variables', async () => { - findSearchBox().vm.$emit('input', 'te'); + findGlCollapsibleListbox().vm.$emit('search', 'te'); + await waitForPromises(); expect(mockAvailableBranchQuery).toHaveBeenCalledWith({ @@ -272,35 +257,35 @@ describe('Pipeline editor branch switcher', () => { }); it('fetches new list of branches', async () => { - expect(findDropdownItems()).toHaveLength(mockTotalBranchResults); + expect(findGlListboxItems()).toHaveLength(mockTotalBranchResults); - findSearchBox().vm.$emit('input', 'te'); + findGlCollapsibleListbox().vm.$emit('search', 'te'); await waitForPromises(); - expect(findDropdownItems()).toHaveLength(mockTotalSearchResults); + expect(findGlListboxItems()).toHaveLength(mockTotalSearchResults); }); it('does not hide dropdown when search result is empty', async () => { mockAvailableBranchQuery.mockResolvedValue(mockEmptySearchBranches); - findSearchBox().vm.$emit('input', 'aaaaa'); + findGlCollapsibleListbox().vm.$emit('search', 'aaaa'); await waitForPromises(); - expect(findDropdown().exists()).toBe(true); - expect(findDropdownItems()).toHaveLength(0); + expect(findGlCollapsibleListbox().exists()).toBe(true); + expect(findGlListboxItems()).toHaveLength(0); }); }); describe('without a search term', () => { beforeEach(async () => { mockAvailableBranchQuery.mockResolvedValue(mockSearchBranches); - findSearchBox().vm.$emit('input', 'te'); + findGlCollapsibleListbox().vm.$emit('search', 'te'); await waitForPromises(); mockAvailableBranchQuery.mockResolvedValue(generateMockProjectBranches()); }); it('calls query with correct variables', async () => { - findSearchBox().vm.$emit('input', ''); + findGlCollapsibleListbox().vm.$emit('search', ''); await waitForPromises(); expect(mockAvailableBranchQuery).toHaveBeenCalledWith({ @@ -312,71 +297,34 @@ describe('Pipeline editor branch switcher', () => { }); it('fetches new list of branches', async () => { - expect(findDropdownItems()).toHaveLength(mockTotalSearchResults); + expect(findGlListboxItems()).toHaveLength(mockTotalSearchResults); - findSearchBox().vm.$emit('input', ''); + findGlCollapsibleListbox().vm.$emit('search', ''); await waitForPromises(); - expect(findDropdownItems()).toHaveLength(mockTotalBranchResults); + expect(findGlListboxItems()).toHaveLength(mockTotalBranchResults); }); }); }); describe('when scrolling to the bottom of the list', () => { beforeEach(async () => { - setAvailableBranchesMock(generateMockProjectBranches()); - await createComponent(); + createComponent(); + await waitForPromises(); }); afterEach(() => { mockAvailableBranchQuery.mockClear(); }); - describe('when search term is empty', () => { - it('fetches more branches', async () => { - expect(mockAvailableBranchQuery).toHaveBeenCalledTimes(1); - - setAvailableBranchesMock(generateMockProjectBranches('new-')); - findInfiniteScroll().vm.$emit('bottomReached'); - await waitForPromises(); - - expect(mockAvailableBranchQuery).toHaveBeenCalledTimes(2); - }); - - it('calls the query with the correct variables', async () => { - setAvailableBranchesMock(generateMockProjectBranches('new-')); - findInfiniteScroll().vm.$emit('bottomReached'); - await waitForPromises(); - - expect(mockAvailableBranchQuery).toHaveBeenCalledWith({ - limit: mockBranchPaginationLimit, - offset: mockBranchPaginationLimit, // offset changed - projectFullPath: mockProjectFullPath, - searchPattern: '*', - }); - }); - - it('shows error message on fetch error', async () => { - mockAvailableBranchQuery.mockResolvedValue(new Error()); - - findInfiniteScroll().vm.$emit('bottomReached'); - await waitForPromises(); - - testErrorHandling(); - }); - }); - describe('when search term exists', () => { it('does not fetch more branches', async () => { - findSearchBox().vm.$emit('input', 'te'); + findGlCollapsibleListbox().vm.$emit('search', 'new'); await waitForPromises(); expect(mockAvailableBranchQuery).toHaveBeenCalledTimes(2); mockAvailableBranchQuery.mockClear(); - findInfiniteScroll().vm.$emit('bottomReached'); - await waitForPromises(); - expect(mockAvailableBranchQuery).not.toHaveBeenCalled(); }); }); diff --git a/spec/frontend/ci/pipelines_page/pipelines_spec.js b/spec/frontend/ci/pipelines_page/pipelines_spec.js index 4fa9d52eae4..97192058ff6 100644 --- a/spec/frontend/ci/pipelines_page/pipelines_spec.js +++ b/spec/frontend/ci/pipelines_page/pipelines_spec.js @@ -29,7 +29,12 @@ import NavigationControls from '~/ci/pipelines_page/components/nav_controls.vue' import PipelinesComponent from '~/ci/pipelines_page/pipelines.vue'; import PipelinesCiTemplates from '~/ci/pipelines_page/components/empty_state/pipelines_ci_templates.vue'; import PipelinesTableComponent from '~/ci/common/pipelines_table.vue'; -import { PIPELINE_IID_KEY, RAW_TEXT_WARNING, TRACKING_CATEGORIES } from '~/ci/constants'; +import { + PIPELINE_ID_KEY, + PIPELINE_IID_KEY, + RAW_TEXT_WARNING, + TRACKING_CATEGORIES, +} from '~/ci/constants'; import Store from '~/ci/pipeline_details/stores/pipelines_store'; import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; @@ -374,6 +379,8 @@ describe('Pipelines', () => { beforeEach(() => { gon.current_user_id = 1; + + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); }); it('should change the text to Show Pipeline IID', async () => { @@ -386,6 +393,25 @@ describe('Pipelines', () => { expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockFilteredPipeline.iid}`); }); + it('tracks the iid usage of the ID/IID dropdown', async () => { + findPipelineKeyCollapsibleBox().vm.$emit('select', PIPELINE_IID_KEY); + + await waitForPromises(); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'pipelines_display_options', { + label: TRACKING_CATEGORIES.listbox, + property: 'iid', + }); + }); + + it('does not track the id usage of the ID/IID dropdown', async () => { + findPipelineKeyCollapsibleBox().vm.$emit('select', PIPELINE_ID_KEY); + + await waitForPromises(); + + expect(trackingSpy).not.toHaveBeenCalled(); + }); + it('calls mutation to save idType preference', () => { mutationMock = jest.fn().mockResolvedValue(setIdTypePreferenceMutationResponse); createComponent(); diff --git a/spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js b/spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js index 7572122a5f3..ffc19d66cac 100644 --- a/spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js +++ b/spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js @@ -1,4 +1,5 @@ import { GlFilteredSearch, GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { nextTick } from 'vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { assertProps } from 'helpers/assert_props'; import RunnerFilteredSearchBar from '~/ci/runner/components/runner_filtered_search_bar.vue'; @@ -35,8 +36,8 @@ describe('RunnerList', () => { const mockOtherSort = CONTACTED_DESC; const mockFilters = [ - { type: PARAM_KEY_STATUS, value: { data: STATUS_ONLINE, operator: '=' } }, - { type: FILTERED_SEARCH_TERM, value: { data: '' } }, + { id: 1, type: PARAM_KEY_STATUS, value: { data: STATUS_ONLINE, operator: '=' } }, + { id: 2, type: FILTERED_SEARCH_TERM, value: { data: '' } }, ]; const expectToHaveLastEmittedInput = (value) => { @@ -148,10 +149,12 @@ describe('RunnerList', () => { ).toEqual('Last contact'); }); - it('when the user sets a filter, the "search" preserves the other filters', () => { + it('when the user sets a filter, the "search" preserves the other filters', async () => { findGlFilteredSearch().vm.$emit('input', mockFilters); findGlFilteredSearch().vm.$emit('submit'); + await nextTick(); + expectToHaveLastEmittedInput({ runnerType: INSTANCE_TYPE, membership: DEFAULT_MEMBERSHIP, @@ -162,10 +165,12 @@ describe('RunnerList', () => { }); }); - it('when the user sets a filter, the "search" is emitted with filters', () => { + it('when the user sets a filter, the "search" is emitted with filters', async () => { findGlFilteredSearch().vm.$emit('input', mockFilters); findGlFilteredSearch().vm.$emit('submit'); + await nextTick(); + expectToHaveLastEmittedInput({ runnerType: null, membership: DEFAULT_MEMBERSHIP, diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js index 00a412d9de8..bb612a13209 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js @@ -297,27 +297,31 @@ describe('FilteredSearchBarRoot', () => { await nextTick(); }); - it('calls `uniqueTokens` on `filterValue` prop to remove duplicates', () => { - wrapper.vm.handleFilterSubmit(); + it('calls `uniqueTokens` on `filterValue` prop to remove duplicates', async () => { + findGlFilteredSearch().vm.$emit('submit'); + await nextTick(); expect(uniqueTokens).toHaveBeenCalledWith(wrapper.vm.filterValue); }); - it('calls `recentSearchesStore.addRecentSearch` with serialized value of provided `filters` param', () => { + it('calls `recentSearchesStore.addRecentSearch` with serialized value of provided `filters` param', async () => { jest.spyOn(wrapper.vm.recentSearchesStore, 'addRecentSearch'); - wrapper.vm.handleFilterSubmit(); + findGlFilteredSearch().vm.$emit('submit'); + await nextTick(); return wrapper.vm.recentSearchesPromise.then(() => { expect(wrapper.vm.recentSearchesStore.addRecentSearch).toHaveBeenCalledWith(mockFilters); }); }); - it('calls `recentSearchesService.save` with array of searches', () => { + it('calls `recentSearchesService.save` with array of searches', async () => { jest.spyOn(wrapper.vm.recentSearchesService, 'save'); wrapper.vm.handleFilterSubmit(); + await nextTick(); + return wrapper.vm.recentSearchesPromise.then(() => { expect(wrapper.vm.recentSearchesService.save).toHaveBeenCalledWith([mockFilters]); }); @@ -336,15 +340,16 @@ describe('FilteredSearchBarRoot', () => { it('calls `blurSearchInput` method to remove focus from filter input field', () => { jest.spyOn(wrapper.vm, 'blurSearchInput'); - wrapper.findComponent(GlFilteredSearch).vm.$emit('submit', mockFilters); + findGlFilteredSearch().vm.$emit('submit', mockFilters); expect(wrapper.vm.blurSearchInput).toHaveBeenCalled(); }); - it('emits component event `onFilter` with provided filters param', () => { + it('emits component event `onFilter` with provided filters param', async () => { jest.spyOn(wrapper.vm, 'removeQuotesEnclosure'); - wrapper.vm.handleFilterSubmit(); + findGlFilteredSearch().vm.$emit('submit'); + await nextTick(); expect(wrapper.emitted('onFilter')[0]).toEqual([mockFilters]); expect(wrapper.vm.removeQuotesEnclosure).toHaveBeenCalledWith(mockFilters); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js index 72e3475df75..88618de6979 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js @@ -88,6 +88,7 @@ function createComponent({ slots = defaultSlots, scopedSlots = defaultScopedSlots, mountFn = mount, + groupMultiSelectTokens = false, } = {}) { return mountFn(BaseToken, { propsData: { @@ -95,6 +96,9 @@ function createComponent({ ...props, }, provide: { + glFeatures: { + groupMultiSelectTokens, + }, portalName: 'fake target', alignSuggestions: jest.fn(), suggestionsListClass: () => 'custom-class', @@ -148,6 +152,24 @@ describe('BaseToken', () => { `"${mockRegularLabel.title}"`, ); }); + + it('uses last item in list when value is an array', () => { + const mockGetActiveTokenValue = jest.fn(); + + wrapper = createComponent({ + props: { + value: { data: mockLabels.map((l) => l.title) }, + suggestions: mockLabels, + getActiveTokenValue: mockGetActiveTokenValue, + }, + groupMultiSelectTokens: true, + }); + + const lastTitle = mockLabels[mockLabels.length - 1].title; + + expect(mockGetActiveTokenValue).toHaveBeenCalledTimes(1); + expect(mockGetActiveTokenValue).toHaveBeenCalledWith(mockLabels, lastTitle); + }); }); }); @@ -385,6 +407,28 @@ describe('BaseToken', () => { expect(setTokenValueToRecentlyUsed).not.toHaveBeenCalled(); }); + + it('emits token-selected event when groupMultiSelectTokens: true', () => { + wrapper = createComponent({ + props: { suggestions: mockLabels }, + groupMultiSelectTokens: true, + }); + + findGlFilteredSearchToken().vm.$emit('select', mockTokenValue.title); + + expect(wrapper.emitted('token-selected')).toEqual([[mockTokenValue.title]]); + }); + + it('does not emit token-selected event when groupMultiSelectTokens: true', () => { + wrapper = createComponent({ + props: { suggestions: mockLabels }, + groupMultiSelectTokens: false, + }); + + findGlFilteredSearchToken().vm.$emit('select', mockTokenValue.title); + + expect(wrapper.emitted('token-selected')).toBeUndefined(); + }); }); }); @@ -476,6 +520,14 @@ describe('BaseToken', () => { expect(wrapper.emitted('fetch-suggestions')[2]).toEqual(['foo']); }); }); + + it('does not emit `fetch-suggestions` when value is array', () => { + expect(wrapper.emitted('fetch-suggestions')).toEqual([[''], ['']]); + + findGlFilteredSearchToken().vm.$emit('input', { data: ['first item'] }); + + expect(wrapper.emitted('fetch-suggestions')).toEqual([[''], ['']]); + }); }); }); }); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js index 0229d00eb91..4462d1bfaf5 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js @@ -3,6 +3,7 @@ import { GlFilteredSearchSuggestion, GlDropdownDivider, GlAvatar, + GlIcon, } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; @@ -53,6 +54,7 @@ function createComponent(options = {}) { stubs = defaultStubs, data = {}, listeners = {}, + groupMultiSelectTokens = false, } = options; return mount(UserToken, { apolloProvider: mockApollo, @@ -63,6 +65,9 @@ function createComponent(options = {}) { cursorPosition: 'start', }, provide: { + glFeatures: { + groupMultiSelectTokens, + }, portalName: 'fake target', alignSuggestions: function fakeAlignSuggestions() {}, suggestionsListClass: () => 'custom-class', @@ -82,6 +87,8 @@ describe('UserToken', () => { let wrapper; const findBaseToken = () => wrapper.findComponent(BaseToken); + const findSuggestions = () => wrapper.findAllComponents(GlFilteredSearchSuggestion); + const findIconAtSuggestion = (index) => findSuggestions().at(index).findComponent(GlIcon); beforeEach(() => { mock = new MockAdapter(axios); @@ -303,6 +310,110 @@ describe('UserToken', () => { expect(mockInput).toHaveBeenLastCalledWith([{ data: 'mockData', operator: '=' }]); }); + describe('multiSelect', () => { + it('renders check icons in suggestions when multiSelect is true', async () => { + wrapper = createComponent({ + value: { data: [mockUsers[0].username, mockUsers[1].username], operator: '=' }, + data: { + users: mockUsers, + }, + config: { ...mockAuthorToken, multiSelect: true, initialUsers: mockUsers }, + active: true, + stubs: { Portal: true }, + groupMultiSelectTokens: true, + }); + + await activateSuggestionsList(); + + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); + + expect(findIconAtSuggestion(1).exists()).toBe(false); + expect(findIconAtSuggestion(2).props('name')).toBe('check'); + expect(findIconAtSuggestion(3).props('name')).toBe('check'); + + // test for left padding on unchecked items (so alignment is correct) + expect(findIconAtSuggestion(4).exists()).toBe(false); + expect(suggestions.at(4).find('.gl-pl-6').exists()).toBe(true); + }); + + it('renders multiple users when multiSelect is true', async () => { + wrapper = createComponent({ + value: { data: [mockUsers[0].username, mockUsers[1].username], operator: '=' }, + data: { + users: mockUsers, + }, + config: { ...mockAuthorToken, multiSelect: true, initialUsers: mockUsers }, + groupMultiSelectTokens: true, + }); + + await nextTick(); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); + + expect(tokenSegments).toHaveLength(3); // Author, =, "Administrator" + + const tokenValue = tokenSegments.at(2); + + const [user1, user2] = mockUsers; + + expect(tokenValue.findAllComponents(GlAvatar).at(1).props('src')).toBe( + mockUsers[1].avatar_url, + ); + expect(tokenValue.text()).toBe(`${user1.name},${user2.name}`); + }); + + it('adds new user to multi-select-values', () => { + wrapper = createComponent({ + value: { data: [mockUsers[0].username], operator: '=' }, + data: { + users: mockUsers, + }, + config: { ...mockAuthorToken, multiSelect: true, initialUsers: mockUsers }, + active: true, + groupMultiSelectTokens: true, + }); + + findBaseToken().vm.$emit('token-selected', mockUsers[1].username); + + expect(findBaseToken().props().multiSelectValues).toEqual([ + mockUsers[0].username, + mockUsers[1].username, + ]); + }); + + it('removes existing user from array', () => { + const initialUsers = [mockUsers[0].username, mockUsers[1].username]; + wrapper = createComponent({ + value: { data: initialUsers, operator: '=' }, + data: { + users: mockUsers, + }, + config: { ...mockAuthorToken, multiSelect: true, initialUsers: mockUsers }, + active: true, + groupMultiSelectTokens: true, + }); + + findBaseToken().vm.$emit('token-selected', mockUsers[0].username); + + expect(findBaseToken().props().multiSelectValues).toEqual([mockUsers[1].username]); + }); + + it('clears input field after token selected', () => { + wrapper = createComponent({ + value: { data: [mockUsers[0].username, mockUsers[1].username], operator: '=' }, + data: { + users: mockUsers, + }, + config: { ...mockAuthorToken, multiSelect: true, initialUsers: mockUsers }, + active: true, + groupMultiSelectTokens: true, + }); + + findBaseToken().vm.$emit('token-selected', 'test'); + + expect(wrapper.emitted('input')).toEqual([[{ operator: '=', data: '' }]]); + }); + }); + describe('when loading', () => { beforeEach(() => { wrapper = createComponent({ diff --git a/spec/frontend/work_items/components/work_item_labels_spec.js b/spec/frontend/work_items/components/work_item_labels_spec.js index 28aa7ffa1be..d7bebac6dbd 100644 --- a/spec/frontend/work_items/components/work_item_labels_spec.js +++ b/spec/frontend/work_items/components/work_item_labels_spec.js @@ -5,7 +5,8 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; -import labelSearchQuery from '~/sidebar/components/labels/labels_select_widget/graphql/project_labels.query.graphql'; +import groupLabelsQuery from '~/sidebar/components/labels/labels_select_widget/graphql/group_labels.query.graphql'; +import projectLabelsQuery from '~/sidebar/components/labels/labels_select_widget/graphql/project_labels.query.graphql'; import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; import groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql'; import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql'; @@ -37,7 +38,8 @@ describe('WorkItemLabels component', () => { const groupWorkItemQuerySuccess = jest .fn() .mockResolvedValue(groupWorkItemByIidResponseFactory({ labels: null })); - const successSearchQueryHandler = jest.fn().mockResolvedValue(projectLabelsResponse); + const projectLabelsQueryHandler = jest.fn().mockResolvedValue(projectLabelsResponse); + const groupLabelsQueryHandler = jest.fn().mockResolvedValue(projectLabelsResponse); const successUpdateWorkItemMutationHandler = jest .fn() .mockResolvedValue(updateWorkItemMutationResponse); @@ -47,7 +49,7 @@ describe('WorkItemLabels component', () => { canUpdate = true, isGroup = false, workItemQueryHandler = workItemQuerySuccess, - searchQueryHandler = successSearchQueryHandler, + searchQueryHandler = projectLabelsQueryHandler, updateWorkItemMutationHandler = successUpdateWorkItemMutationHandler, workItemIid = '1', } = {}) => { @@ -55,7 +57,8 @@ describe('WorkItemLabels component', () => { apolloProvider: createMockApollo([ [workItemByIidQuery, workItemQueryHandler], [groupWorkItemByIidQuery, groupWorkItemQuerySuccess], - [labelSearchQuery, searchQueryHandler], + [projectLabelsQuery, searchQueryHandler], + [groupLabelsQuery, groupLabelsQueryHandler], [updateWorkItemMutation, updateWorkItemMutationHandler], ]), provide: { @@ -179,7 +182,7 @@ describe('WorkItemLabels component', () => { findTokenSelector().vm.$emit('text-input', searchKey); await waitForPromises(); - expect(successSearchQueryHandler).toHaveBeenCalledWith( + expect(projectLabelsQueryHandler).toHaveBeenCalledWith( expect.objectContaining({ searchTerm: searchKey }), ); }); @@ -273,6 +276,16 @@ describe('WorkItemLabels component', () => { expect(workItemQuerySuccess).not.toHaveBeenCalled(); }); + + it('calls the project labels query on search', async () => { + createComponent(); + + findTokenSelector().vm.$emit('focus'); + findTokenSelector().vm.$emit('text-input', 'hello'); + await waitForPromises(); + + expect(projectLabelsQueryHandler).toHaveBeenCalled(); + }); }); describe('when group context', () => { @@ -296,5 +309,15 @@ describe('WorkItemLabels component', () => { expect(groupWorkItemQuerySuccess).not.toHaveBeenCalled(); }); + + it('calls the group labels query on search', async () => { + createComponent({ isGroup: true }); + + findTokenSelector().vm.$emit('focus'); + findTokenSelector().vm.$emit('text-input', 'hello'); + await waitForPromises(); + + expect(groupLabelsQueryHandler).toHaveBeenCalled(); + }); }); }); |