Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-10-23 21:11:07 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-10-23 21:11:07 +0300
commitc5da163db1c10676b1a01a898b7b3a4506e65b89 (patch)
tree0110bf1518c8049cddab8d62e77d4f7dad2478a7 /spec/frontend
parent750a63ac4cc7acbecff3b8d22232cb1c15af34fb (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js134
-rw-r--r--spec/frontend/ci/pipelines_page/pipelines_spec.js28
-rw-r--r--spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js13
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js21
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js52
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js111
-rw-r--r--spec/frontend/work_items/components/work_item_labels_spec.js33
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();
+ });
});
});