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>2021-10-20 11:43:02 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 11:43:02 +0300
commitd9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch)
tree2341ef426af70ad1e289c38036737e04b0aa5007 /spec/frontend/projects
parentd6e514dd13db8947884cd58fe2a9c2a063400a9b (diff)
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'spec/frontend/projects')
-rw-r--r--spec/frontend/projects/new/components/app_spec.js44
-rw-r--r--spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js75
-rw-r--r--spec/frontend/projects/new/components/new_project_url_select_spec.js235
-rw-r--r--spec/frontend/projects/projects_filterable_list_spec.js2
-rw-r--r--spec/frontend/projects/settings/components/new_access_dropdown_spec.js345
5 files changed, 701 insertions, 0 deletions
diff --git a/spec/frontend/projects/new/components/app_spec.js b/spec/frontend/projects/new/components/app_spec.js
new file mode 100644
index 00000000000..f6edbab3cca
--- /dev/null
+++ b/spec/frontend/projects/new/components/app_spec.js
@@ -0,0 +1,44 @@
+import { shallowMount } from '@vue/test-utils';
+import App from '~/projects/new/components/app.vue';
+import NewNamespacePage from '~/vue_shared/new_namespace/new_namespace_page.vue';
+
+describe('Experimental new project creation app', () => {
+ let wrapper;
+
+ const createComponent = (propsData) => {
+ wrapper = shallowMount(App, { propsData });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('passes custom new project guideline text to underlying component', () => {
+ const DEMO_GUIDELINES = 'Demo guidelines';
+ const guidelineSelector = '#new-project-guideline';
+ createComponent({
+ newProjectGuidelines: DEMO_GUIDELINES,
+ });
+
+ expect(wrapper.find(guidelineSelector).text()).toBe(DEMO_GUIDELINES);
+ });
+
+ it.each`
+ isCiCdAvailable | outcome
+ ${false} | ${'do not show CI/CD panel'}
+ ${true} | ${'show CI/CD panel'}
+ `('$outcome when isCiCdAvailable is $isCiCdAvailable', ({ isCiCdAvailable }) => {
+ createComponent({
+ isCiCdAvailable,
+ });
+
+ expect(
+ Boolean(
+ wrapper
+ .findComponent(NewNamespacePage)
+ .props()
+ .panels.find((p) => p.name === 'cicd_for_external_repo'),
+ ),
+ ).toBe(isCiCdAvailable);
+ });
+});
diff --git a/spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js b/spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js
new file mode 100644
index 00000000000..31ddbc80ae4
--- /dev/null
+++ b/spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js
@@ -0,0 +1,75 @@
+import { GlPopover, GlFormInputGroup } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import NewProjectPushTipPopover from '~/projects/new/components/new_project_push_tip_popover.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+
+describe('New project push tip popover', () => {
+ let wrapper;
+ const targetId = 'target';
+ const pushToCreateProjectCommand = 'command';
+ const workingWithProjectsHelpPath = 'path';
+
+ const findPopover = () => wrapper.findComponent(GlPopover);
+ const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
+ const findFormInput = () => wrapper.findComponent(GlFormInputGroup);
+ const findHelpLink = () => wrapper.find('a');
+ const findTarget = () => document.getElementById(targetId);
+
+ const buildWrapper = () => {
+ wrapper = shallowMount(NewProjectPushTipPopover, {
+ propsData: {
+ target: findTarget(),
+ },
+ stubs: {
+ GlFormInputGroup,
+ },
+ provide: {
+ pushToCreateProjectCommand,
+ workingWithProjectsHelpPath,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ setFixtures(`<a id="${targetId}"></a>`);
+ buildWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders popover that targets the specified target', () => {
+ expect(findPopover().props()).toMatchObject({
+ target: findTarget(),
+ triggers: 'click blur',
+ placement: 'top',
+ title: 'Push to create a project',
+ });
+ });
+
+ it('renders a readonly form input with the push to create command', () => {
+ expect(findFormInput().props()).toMatchObject({
+ value: pushToCreateProjectCommand,
+ selectOnClick: true,
+ });
+ expect(findFormInput().attributes()).toMatchObject({
+ 'aria-label': 'Push project from command line',
+ readonly: 'readonly',
+ });
+ });
+
+ it('allows copying the push command using the clipboard button', () => {
+ expect(findClipboardButton().props()).toMatchObject({
+ text: pushToCreateProjectCommand,
+ tooltipPlacement: 'right',
+ title: 'Copy command',
+ });
+ });
+
+ it('displays a link to open the push command help page reference', () => {
+ expect(findHelpLink().attributes().href).toBe(
+ `${workingWithProjectsHelpPath}#push-to-create-a-new-project`,
+ );
+ });
+});
diff --git a/spec/frontend/projects/new/components/new_project_url_select_spec.js b/spec/frontend/projects/new/components/new_project_url_select_spec.js
new file mode 100644
index 00000000000..aa16b71172b
--- /dev/null
+++ b/spec/frontend/projects/new/components/new_project_url_select_spec.js
@@ -0,0 +1,235 @@
+import {
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownSectionHeader,
+ GlSearchBoxByType,
+} from '@gitlab/ui';
+import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import eventHub from '~/projects/new/event_hub';
+import NewProjectUrlSelect from '~/projects/new/components/new_project_url_select.vue';
+import searchQuery from '~/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql';
+
+describe('NewProjectUrlSelect component', () => {
+ let wrapper;
+
+ const data = {
+ currentUser: {
+ groups: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Group/26',
+ fullPath: 'flightjs',
+ },
+ {
+ id: 'gid://gitlab/Group/28',
+ fullPath: 'h5bp',
+ },
+ {
+ id: 'gid://gitlab/Group/30',
+ fullPath: 'h5bp/subgroup',
+ },
+ ],
+ },
+ namespace: {
+ id: 'gid://gitlab/Namespace/1',
+ fullPath: 'root',
+ },
+ },
+ };
+
+ const localVue = createLocalVue();
+ localVue.use(VueApollo);
+
+ const defaultProvide = {
+ namespaceFullPath: 'h5bp',
+ namespaceId: '28',
+ rootUrl: 'https://gitlab.com/',
+ trackLabel: 'blank_project',
+ userNamespaceFullPath: 'root',
+ userNamespaceId: '1',
+ };
+
+ const mountComponent = ({
+ search = '',
+ queryResponse = data,
+ provide = defaultProvide,
+ mountFn = shallowMount,
+ } = {}) => {
+ const requestHandlers = [[searchQuery, jest.fn().mockResolvedValue({ data: queryResponse })]];
+ const apolloProvider = createMockApollo(requestHandlers);
+
+ return mountFn(NewProjectUrlSelect, {
+ localVue,
+ apolloProvider,
+ provide,
+ data() {
+ return {
+ search,
+ };
+ },
+ });
+ };
+
+ const findButtonLabel = () => wrapper.findComponent(GlButton);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findInput = () => wrapper.findComponent(GlSearchBoxByType);
+ const findHiddenInput = () => wrapper.find('input');
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the root url as a label', () => {
+ wrapper = mountComponent();
+
+ expect(findButtonLabel().text()).toBe(defaultProvide.rootUrl);
+ expect(findButtonLabel().props('label')).toBe(true);
+ });
+
+ describe('when namespaceId is provided', () => {
+ beforeEach(() => {
+ wrapper = mountComponent();
+ });
+
+ it('renders a dropdown with the given namespace full path as the text', () => {
+ expect(findDropdown().props('text')).toBe(defaultProvide.namespaceFullPath);
+ });
+
+ it('renders a dropdown with the given namespace id in the hidden input', () => {
+ expect(findHiddenInput().attributes('value')).toBe(defaultProvide.namespaceId);
+ });
+ });
+
+ describe('when namespaceId is not provided', () => {
+ const provide = {
+ ...defaultProvide,
+ namespaceFullPath: undefined,
+ namespaceId: undefined,
+ };
+
+ beforeEach(() => {
+ wrapper = mountComponent({ provide });
+ });
+
+ it("renders a dropdown with the user's namespace full path as the text", () => {
+ expect(findDropdown().props('text')).toBe(defaultProvide.userNamespaceFullPath);
+ });
+
+ it("renders a dropdown with the user's namespace id in the hidden input", () => {
+ expect(findHiddenInput().attributes('value')).toBe(defaultProvide.userNamespaceId);
+ });
+ });
+
+ it('focuses on the input when the dropdown is opened', async () => {
+ wrapper = mountComponent({ mountFn: mount });
+
+ jest.runOnlyPendingTimers();
+ await wrapper.vm.$nextTick();
+
+ const spy = jest.spyOn(findInput().vm, 'focusInput');
+
+ findDropdown().vm.$emit('shown');
+
+ expect(spy).toHaveBeenCalledTimes(1);
+ });
+
+ it('renders expected dropdown items', async () => {
+ wrapper = mountComponent({ mountFn: mount });
+
+ jest.runOnlyPendingTimers();
+ await wrapper.vm.$nextTick();
+
+ const listItems = wrapper.findAll('li');
+
+ expect(listItems).toHaveLength(6);
+ expect(listItems.at(0).findComponent(GlDropdownSectionHeader).text()).toBe('Groups');
+ expect(listItems.at(1).text()).toBe(data.currentUser.groups.nodes[0].fullPath);
+ expect(listItems.at(2).text()).toBe(data.currentUser.groups.nodes[1].fullPath);
+ expect(listItems.at(3).text()).toBe(data.currentUser.groups.nodes[2].fullPath);
+ expect(listItems.at(4).findComponent(GlDropdownSectionHeader).text()).toBe('Users');
+ expect(listItems.at(5).text()).toBe(data.currentUser.namespace.fullPath);
+ });
+
+ describe('when selecting from a group template', () => {
+ const groupId = getIdFromGraphQLId(data.currentUser.groups.nodes[1].id);
+
+ beforeEach(async () => {
+ wrapper = mountComponent({ mountFn: mount });
+
+ jest.runOnlyPendingTimers();
+ await wrapper.vm.$nextTick();
+
+ eventHub.$emit('select-template', groupId);
+ });
+
+ it('filters the dropdown items to the selected group and children', async () => {
+ const listItems = wrapper.findAll('li');
+
+ expect(listItems).toHaveLength(3);
+ expect(listItems.at(0).findComponent(GlDropdownSectionHeader).text()).toBe('Groups');
+ expect(listItems.at(1).text()).toBe(data.currentUser.groups.nodes[1].fullPath);
+ expect(listItems.at(2).text()).toBe(data.currentUser.groups.nodes[2].fullPath);
+ });
+
+ it('sets the selection to the group', async () => {
+ expect(findDropdown().props('text')).toBe(data.currentUser.groups.nodes[1].fullPath);
+ });
+ });
+
+ it('renders `No matches found` when there are no matching dropdown items', async () => {
+ const queryResponse = {
+ currentUser: {
+ groups: {
+ nodes: [],
+ },
+ namespace: {
+ id: 'gid://gitlab/Namespace/1',
+ fullPath: 'root',
+ },
+ },
+ };
+
+ wrapper = mountComponent({ search: 'no matches', queryResponse, mountFn: mount });
+
+ jest.runOnlyPendingTimers();
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.find('li').text()).toBe('No matches found');
+ });
+
+ it('updates hidden input with selected namespace', async () => {
+ wrapper = mountComponent();
+
+ jest.runOnlyPendingTimers();
+ await wrapper.vm.$nextTick();
+
+ wrapper.findComponent(GlDropdownItem).vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+
+ expect(findHiddenInput().attributes()).toMatchObject({
+ name: 'project[namespace_id]',
+ value: getIdFromGraphQLId(data.currentUser.groups.nodes[0].id).toString(),
+ });
+ });
+
+ it('tracks clicking on the dropdown', () => {
+ wrapper = mountComponent();
+
+ const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+
+ findDropdown().vm.$emit('show');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'activate_form_input', {
+ label: defaultProvide.trackLabel,
+ property: 'project_path',
+ });
+
+ unmockTracking();
+ });
+});
diff --git a/spec/frontend/projects/projects_filterable_list_spec.js b/spec/frontend/projects/projects_filterable_list_spec.js
index 377d347623a..d4dbf85b5ca 100644
--- a/spec/frontend/projects/projects_filterable_list_spec.js
+++ b/spec/frontend/projects/projects_filterable_list_spec.js
@@ -1,3 +1,4 @@
+// eslint-disable-next-line import/no-deprecated
import { getJSONFixture, setHTMLFixture } from 'helpers/fixtures';
import ProjectsFilterableList from '~/projects/projects_filterable_list';
@@ -14,6 +15,7 @@ describe('ProjectsFilterableList', () => {
</div>
<div class="js-projects-list-holder"></div>
`);
+ // eslint-disable-next-line import/no-deprecated
getJSONFixture('static/projects.json');
form = document.querySelector('form#project-filter-form');
filter = document.querySelector('.js-projects-list-filter');
diff --git a/spec/frontend/projects/settings/components/new_access_dropdown_spec.js b/spec/frontend/projects/settings/components/new_access_dropdown_spec.js
new file mode 100644
index 00000000000..a42891423cd
--- /dev/null
+++ b/spec/frontend/projects/settings/components/new_access_dropdown_spec.js
@@ -0,0 +1,345 @@
+import {
+ GlSprintf,
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownSectionHeader,
+ GlSearchBoxByType,
+} from '@gitlab/ui';
+import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { getUsers, getGroups, getDeployKeys } from '~/projects/settings/api/access_dropdown_api';
+import AccessDropdown, { i18n } from '~/projects/settings/components/access_dropdown.vue';
+import { ACCESS_LEVELS, LEVEL_TYPES } from '~/projects/settings/constants';
+
+jest.mock('~/projects/settings/api/access_dropdown_api', () => ({
+ getGroups: jest.fn().mockResolvedValue({
+ data: [
+ { id: 4, name: 'group4' },
+ { id: 5, name: 'group5' },
+ { id: 6, name: 'group6' },
+ ],
+ }),
+ getUsers: jest.fn().mockResolvedValue({
+ data: [
+ { id: 7, name: 'user7' },
+ { id: 8, name: 'user8' },
+ { id: 9, name: 'user9' },
+ ],
+ }),
+ getDeployKeys: jest.fn().mockResolvedValue({
+ data: [
+ { id: 10, title: 'key10', fingerprint: 'abcdefghijklmnop', owner: { name: 'user1' } },
+ { id: 11, title: 'key11', fingerprint: 'abcdefghijklmnop', owner: { name: 'user2' } },
+ { id: 12, title: 'key12', fingerprint: 'abcdefghijklmnop', owner: { name: 'user3' } },
+ ],
+ }),
+}));
+
+describe('Access Level Dropdown', () => {
+ let wrapper;
+ const mockAccessLevelsData = [
+ {
+ id: 1,
+ text: 'role1',
+ },
+ {
+ id: 2,
+ text: 'role2',
+ },
+ {
+ id: 3,
+ text: 'role3',
+ },
+ ];
+
+ const createComponent = ({
+ accessLevelsData = mockAccessLevelsData,
+ accessLevel = ACCESS_LEVELS.PUSH,
+ hasLicense,
+ label,
+ disabled,
+ preselectedItems,
+ } = {}) => {
+ wrapper = shallowMountExtended(AccessDropdown, {
+ propsData: {
+ accessLevelsData,
+ accessLevel,
+ hasLicense,
+ label,
+ disabled,
+ preselectedItems,
+ },
+ stubs: {
+ GlSprintf,
+ GlDropdown,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownToggleLabel = () => findDropdown().props('text');
+ const findAllDropdownItems = () => findDropdown().findAllComponents(GlDropdownItem);
+ const findAllDropdownHeaders = () => findDropdown().findAllComponents(GlDropdownSectionHeader);
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+
+ const findDropdownItemWithText = (items, text) =>
+ items.filter((item) => item.text().includes(text)).at(0);
+
+ describe('data request', () => {
+ it('should make an api call for users, groups && deployKeys when user has a license', () => {
+ createComponent();
+ expect(getUsers).toHaveBeenCalled();
+ expect(getGroups).toHaveBeenCalled();
+ expect(getDeployKeys).toHaveBeenCalled();
+ });
+
+ it('should make an api call for deployKeys but not for users or groups when user does not have a license', () => {
+ createComponent({ hasLicense: false });
+ expect(getUsers).not.toHaveBeenCalled();
+ expect(getGroups).not.toHaveBeenCalled();
+ expect(getDeployKeys).toHaveBeenCalled();
+ });
+
+ it('should make api calls when search query is updated', async () => {
+ createComponent();
+ const query = 'root';
+
+ findSearchBox().vm.$emit('input', query);
+ await nextTick();
+ expect(getUsers).toHaveBeenCalledWith(query);
+ expect(getGroups).toHaveBeenCalled();
+ expect(getDeployKeys).toHaveBeenCalledWith(query);
+ });
+ });
+
+ describe('layout', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('renders headers for each section ', () => {
+ expect(findAllDropdownHeaders()).toHaveLength(4);
+ });
+
+ it('renders dropdown item for each access level type', () => {
+ expect(findAllDropdownItems()).toHaveLength(12);
+ });
+ });
+
+ describe('toggleLabel', () => {
+ let dropdownItems = [];
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+ dropdownItems = findAllDropdownItems();
+ });
+
+ const findItemByNameAndClick = async (name) => {
+ findDropdownItemWithText(dropdownItems, name).trigger('click');
+ await nextTick();
+ };
+
+ it('when no items selected and custom label provided, displays it and has default CSS class', () => {
+ wrapper.destroy();
+ const customLabel = 'Set the access level';
+ createComponent({ label: customLabel });
+ expect(findDropdownToggleLabel()).toBe(customLabel);
+ expect(findDropdown().props('toggleClass')).toBe('gl-text-gray-500!');
+ });
+
+ it('when no items selected, displays a default fallback label and has default CSS class ', () => {
+ expect(findDropdownToggleLabel()).toBe(i18n.selectUsers);
+ expect(findDropdown().props('toggleClass')).toBe('gl-text-gray-500!');
+ });
+
+ it('displays a number of selected items for each group level', async () => {
+ dropdownItems.wrappers.forEach((item) => {
+ item.trigger('click');
+ });
+ await nextTick();
+ expect(findDropdownToggleLabel()).toBe('3 roles, 3 users, 3 deploy keys, 3 groups');
+ });
+
+ it('with only role selected displays the role name and has no class applied', async () => {
+ await findItemByNameAndClick('role1');
+ expect(findDropdownToggleLabel()).toBe('role1');
+ expect(findDropdown().props('toggleClass')).toBe('');
+ });
+
+ it('with only groups selected displays the number of selected groups', async () => {
+ await findItemByNameAndClick('group4');
+ await findItemByNameAndClick('group5');
+ await findItemByNameAndClick('group6');
+ expect(findDropdownToggleLabel()).toBe('3 groups');
+ expect(findDropdown().props('toggleClass')).toBe('');
+ });
+
+ it('with only users selected displays the number of selected users', async () => {
+ await findItemByNameAndClick('user7');
+ await findItemByNameAndClick('user8');
+ expect(findDropdownToggleLabel()).toBe('2 users');
+ expect(findDropdown().props('toggleClass')).toBe('');
+ });
+
+ it('with users and groups selected displays the number of selected users & groups', async () => {
+ await findItemByNameAndClick('group4');
+ await findItemByNameAndClick('group6');
+ await findItemByNameAndClick('user7');
+ await findItemByNameAndClick('user9');
+ expect(findDropdownToggleLabel()).toBe('2 users, 2 groups');
+ expect(findDropdown().props('toggleClass')).toBe('');
+ });
+
+ it('with users and deploy keys selected displays the number of selected users & keys', async () => {
+ await findItemByNameAndClick('user8');
+ await findItemByNameAndClick('key10');
+ await findItemByNameAndClick('key11');
+ expect(findDropdownToggleLabel()).toBe('1 user, 2 deploy keys');
+ expect(findDropdown().props('toggleClass')).toBe('');
+ });
+ });
+
+ describe('selecting an item', () => {
+ it('selects the item on click and deselects on the next click ', async () => {
+ createComponent();
+ await waitForPromises();
+
+ const item = findAllDropdownItems().at(1);
+ item.trigger('click');
+ await nextTick();
+ expect(item.props('isChecked')).toBe(true);
+ item.trigger('click');
+ await nextTick();
+ expect(item.props('isChecked')).toBe(false);
+ });
+
+ it('emits a formatted update on selection ', async () => {
+ // ids: the items appear in that order in the dropdown
+ // 1 2 3 - roles
+ // 4 5 6 - groups
+ // 7 8 9 - users
+ // 10 11 12 - deploy_keys
+ // we set 2 from each group as preselected. Then for the sake of the test deselect one, leave one as-is
+ // and select a new one from the group.
+ // Preselected items should have `id` along with `user_id/group_id/access_level/deplo_key_id`.
+ // Items to be removed from previous selection will have `_deploy` flag set to true
+ // Newly selected items will have only `user_id/group_id/access_level/deploy_key_id` (depending on their type);
+ const preselectedItems = [
+ { id: 112, type: 'role', access_level: 2 },
+ { id: 113, type: 'role', access_level: 3 },
+ { id: 115, type: 'group', group_id: 5 },
+ { id: 116, type: 'group', group_id: 6 },
+ { id: 118, type: 'user', user_id: 8, name: 'user8' },
+ { id: 119, type: 'user', user_id: 9, name: 'user9' },
+ { id: 121, type: 'deploy_key', deploy_key_id: 11 },
+ { id: 122, type: 'deploy_key', deploy_key_id: 12 },
+ ];
+
+ createComponent({ preselectedItems });
+ await waitForPromises();
+ const spy = jest.spyOn(wrapper.vm, '$emit');
+ const dropdownItems = findAllDropdownItems();
+ // select new item from each group
+ findDropdownItemWithText(dropdownItems, 'role1').trigger('click');
+ findDropdownItemWithText(dropdownItems, 'group4').trigger('click');
+ findDropdownItemWithText(dropdownItems, 'user7').trigger('click');
+ findDropdownItemWithText(dropdownItems, 'key10').trigger('click');
+ // deselect one item from each group
+ findDropdownItemWithText(dropdownItems, 'role2').trigger('click');
+ findDropdownItemWithText(dropdownItems, 'group5').trigger('click');
+ findDropdownItemWithText(dropdownItems, 'user8').trigger('click');
+ findDropdownItemWithText(dropdownItems, 'key11').trigger('click');
+
+ expect(spy).toHaveBeenLastCalledWith('select', [
+ { access_level: 1 },
+ { id: 112, access_level: 2, _destroy: true },
+ { id: 113, access_level: 3 },
+ { group_id: 4 },
+ { id: 115, group_id: 5, _destroy: true },
+ { id: 116, group_id: 6 },
+ { user_id: 7 },
+ { id: 118, user_id: 8, _destroy: true },
+ { id: 119, user_id: 9 },
+ { deploy_key_id: 10 },
+ { id: 121, deploy_key_id: 11, _destroy: true },
+ { id: 122, deploy_key_id: 12 },
+ ]);
+ });
+ });
+
+ describe('Handling preselected items', () => {
+ const preselectedItems = [
+ { id: 112, type: 'role', access_level: 2 },
+ { id: 115, type: 'group', group_id: 5 },
+ { id: 118, type: 'user', user_id: 8, name: 'user2' },
+ { id: 121, type: 'deploy_key', deploy_key_id: 11 },
+ ];
+
+ const findSelected = (type) =>
+ wrapper.findAllByTestId(`${type}-dropdown-item`).filter((w) => w.props('isChecked'));
+
+ beforeEach(async () => {
+ createComponent({ preselectedItems });
+ await waitForPromises();
+ });
+
+ it('should set selected roles as intersection between the server response and preselected', () => {
+ const selectedRoles = findSelected(LEVEL_TYPES.ROLE);
+ expect(selectedRoles).toHaveLength(1);
+ expect(selectedRoles.at(0).text()).toBe('role2');
+ });
+
+ it('should set selected groups as intersection between the server response and preselected', () => {
+ const selectedGroups = findSelected(LEVEL_TYPES.GROUP);
+ expect(selectedGroups).toHaveLength(1);
+ expect(selectedGroups.at(0).text()).toBe('group5');
+ });
+
+ it('should set selected users to all preselected mapping `user_id` to `id`', () => {
+ const selectedUsers = findSelected(LEVEL_TYPES.USER);
+ expect(selectedUsers).toHaveLength(1);
+ expect(selectedUsers.at(0).text()).toBe('user2');
+ });
+
+ it('should set selected deploy keys as intersection between the server response and preselected mapping some keys', () => {
+ const selectedDeployKeys = findSelected(LEVEL_TYPES.DEPLOY_KEY);
+ expect(selectedDeployKeys).toHaveLength(1);
+ expect(selectedDeployKeys.at(0).text()).toContain('key11 (abcdefghijklmn...)');
+ });
+ });
+
+ describe('on dropdown open', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should set the search input focus', () => {
+ wrapper.vm.$refs.search.focusInput = jest.fn();
+ findDropdown().vm.$emit('shown');
+
+ expect(wrapper.vm.$refs.search.focusInput).toHaveBeenCalled();
+ });
+ });
+
+ describe('on dropdown close', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('should emit `hidden` event with dropdown selection', () => {
+ jest.spyOn(wrapper.vm, '$emit');
+
+ findAllDropdownItems().at(1).trigger('click');
+
+ findDropdown().vm.$emit('hidden');
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('hidden', [{ access_level: 2 }]);
+ });
+ });
+});