diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 08:43:02 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 08:43:02 +0000 |
commit | d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch) | |
tree | 2341ef426af70ad1e289c38036737e04b0aa5007 /spec/frontend/projects | |
parent | d6e514dd13db8947884cd58fe2a9c2a063400a9b (diff) | |
download | gitlab-ce-d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb.tar.gz |
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'spec/frontend/projects')
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 }]); + }); + }); +}); |