import { mount, createWrapper, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; import { nextTick } from 'vue'; import { within } from '@testing-library/dom'; import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import waitForPromises from 'helpers/wait_for_promises'; import RoleDropdown from '~/vue_shared/components/members/table/role_dropdown.vue'; import { member } from '../mock_data'; const localVue = createLocalVue(); localVue.use(Vuex); describe('RoleDropdown', () => { let wrapper; let actions; const $toast = { show: jest.fn(), }; const createStore = () => { actions = { updateMemberRole: jest.fn(() => Promise.resolve()), }; return new Vuex.Store({ actions }); }; const createComponent = (propsData = {}) => { wrapper = mount(RoleDropdown, { propsData: { member, ...propsData, }, localVue, store: createStore(), mocks: { $toast, }, }); }; const getDropdownMenu = () => within(wrapper.element).getByRole('menu'); const getByTextInDropdownMenu = (text, options = {}) => createWrapper(within(getDropdownMenu()).getByText(text, options)); const getDropdownItemByText = text => createWrapper( within(getDropdownMenu()) .getByText(text, { selector: '[role="menuitem"] p' }) .closest('[role="menuitem"]'), ); const getCheckedDropdownItem = () => wrapper .findAll(GlDropdownItem) .wrappers.find(dropdownItemWrapper => dropdownItemWrapper.props('isChecked')); const findDropdownToggle = () => wrapper.find('button[aria-haspopup="true"]'); const findDropdown = () => wrapper.find(GlDropdown); afterEach(() => { wrapper.destroy(); }); describe('when dropdown is open', () => { beforeEach(done => { createComponent(); findDropdownToggle().trigger('click'); wrapper.vm.$root.$on('bv::dropdown::shown', () => { done(); }); }); it('renders all valid roles', () => { Object.keys(member.validRoles).forEach(role => { expect(getDropdownItemByText(role).exists()).toBe(true); }); }); it('renders dropdown header', () => { expect(getByTextInDropdownMenu('Change permissions').exists()).toBe(true); }); it('sets dropdown toggle and checks selected role', () => { expect(findDropdownToggle().text()).toBe('Owner'); expect(getCheckedDropdownItem().text()).toBe('Owner'); }); describe('when dropdown item is selected', () => { it('does nothing if the item selected was already selected', () => { getDropdownItemByText('Owner').trigger('click'); expect(actions.updateMemberRole).not.toHaveBeenCalled(); }); it('calls `updateMemberRole` Vuex action', () => { getDropdownItemByText('Developer').trigger('click'); expect(actions.updateMemberRole).toHaveBeenCalledWith(expect.any(Object), { memberId: member.id, accessLevel: { integerValue: 30, stringValue: 'Developer' }, }); }); it('displays toast when successful', async () => { getDropdownItemByText('Developer').trigger('click'); await waitForPromises(); expect($toast.show).toHaveBeenCalledWith('Role updated successfully.'); }); it('disables dropdown while waiting for `updateMemberRole` to resolve', async () => { getDropdownItemByText('Developer').trigger('click'); await nextTick(); expect(findDropdown().attributes('disabled')).toBe('disabled'); await waitForPromises(); expect(findDropdown().attributes('disabled')).toBeUndefined(); }); }); }); it("sets initial dropdown toggle value to member's role", () => { createComponent(); expect(findDropdownToggle().text()).toBe('Owner'); }); it('sets the dropdown alignment to right on mobile', async () => { jest.spyOn(bp, 'isDesktop').mockReturnValue(false); createComponent(); await nextTick(); expect(findDropdown().attributes('right')).toBe('true'); }); it('sets the dropdown alignment to left on desktop', async () => { jest.spyOn(bp, 'isDesktop').mockReturnValue(true); createComponent(); await nextTick(); expect(findDropdown().attributes('right')).toBeUndefined(); }); });