diff options
Diffstat (limited to 'spec/frontend/vue_shared/components/members/table')
9 files changed, 0 insertions, 1080 deletions
diff --git a/spec/frontend/vue_shared/components/members/table/created_at_spec.js b/spec/frontend/vue_shared/components/members/table/created_at_spec.js deleted file mode 100644 index cf3821baf44..00000000000 --- a/spec/frontend/vue_shared/components/members/table/created_at_spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import { mount, createWrapper } from '@vue/test-utils'; -import { within } from '@testing-library/dom'; -import { useFakeDate } from 'helpers/fake_date'; -import CreatedAt from '~/vue_shared/components/members/table/created_at.vue'; -import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; - -describe('CreatedAt', () => { - // March 15th, 2020 - useFakeDate(2020, 2, 15); - - const date = '2020-03-01T00:00:00.000'; - const dateTimeAgo = '2 weeks ago'; - - let wrapper; - - const createComponent = propsData => { - wrapper = mount(CreatedAt, { - propsData: { - date, - ...propsData, - }, - }); - }; - - const getByText = (text, options) => - createWrapper(within(wrapper.element).getByText(text, options)); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('created at text', () => { - beforeEach(() => { - createComponent(); - }); - - it('displays created at text', () => { - expect(getByText(dateTimeAgo).exists()).toBe(true); - }); - - it('uses `TimeAgoTooltip` component to display tooltip', () => { - expect(wrapper.find(TimeAgoTooltip).exists()).toBe(true); - }); - }); - - describe('when `createdBy` prop is provided', () => { - it('displays a link to the user that created the member', () => { - createComponent({ - createdBy: { - name: 'Administrator', - webUrl: 'https://gitlab.com/root', - }, - }); - - const link = getByText('Administrator'); - - expect(link.exists()).toBe(true); - expect(link.attributes('href')).toBe('https://gitlab.com/root'); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/table/expiration_datepicker_spec.js b/spec/frontend/vue_shared/components/members/table/expiration_datepicker_spec.js deleted file mode 100644 index a1afdbc2b49..00000000000 --- a/spec/frontend/vue_shared/components/members/table/expiration_datepicker_spec.js +++ /dev/null @@ -1,166 +0,0 @@ -import { mount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { nextTick } from 'vue'; -import { GlDatepicker } from '@gitlab/ui'; -import { useFakeDate } from 'helpers/fake_date'; -import waitForPromises from 'helpers/wait_for_promises'; -import ExpirationDatepicker from '~/vue_shared/components/members/table/expiration_datepicker.vue'; -import { member } from '../mock_data'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('ExpirationDatepicker', () => { - // March 15th, 2020 3:00 - useFakeDate(2020, 2, 15, 3); - - let wrapper; - let actions; - let resolveUpdateMemberExpiration; - const $toast = { - show: jest.fn(), - }; - - const createStore = () => { - actions = { - updateMemberExpiration: jest.fn( - () => - new Promise(resolve => { - resolveUpdateMemberExpiration = resolve; - }), - ), - }; - - return new Vuex.Store({ actions }); - }; - - const createComponent = (propsData = {}) => { - wrapper = mount(ExpirationDatepicker, { - propsData: { - member, - permissions: { canUpdate: true }, - ...propsData, - }, - localVue, - store: createStore(), - mocks: { - $toast, - }, - }); - }; - - const findInput = () => wrapper.find('input'); - const findDatepicker = () => wrapper.find(GlDatepicker); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('datepicker input', () => { - it('sets `member.expiresAt` as initial date', async () => { - createComponent({ member: { ...member, expiresAt: '2020-03-17T00:00:00Z' } }); - - await nextTick(); - - expect(findInput().element.value).toBe('2020-03-17'); - }); - }); - - describe('props', () => { - beforeEach(() => { - createComponent(); - }); - - it('sets `minDate` prop as tomorrow', () => { - expect( - findDatepicker() - .props('minDate') - .toISOString(), - ).toBe(new Date('2020-3-16').toISOString()); - }); - - it('sets `target` prop as `null` so datepicker opens on focus', () => { - expect(findDatepicker().props('target')).toBe(null); - }); - - it("sets `container` prop as `null` so table styles don't affect the datepicker styles", () => { - expect(findDatepicker().props('container')).toBe(null); - }); - - it('shows clear button', () => { - expect(findDatepicker().props('showClearButton')).toBe(true); - }); - }); - - describe('when datepicker is changed', () => { - beforeEach(async () => { - createComponent(); - - findDatepicker().vm.$emit('input', new Date('2020-03-17')); - }); - - it('calls `updateMemberExpiration` Vuex action', () => { - expect(actions.updateMemberExpiration).toHaveBeenCalledWith(expect.any(Object), { - memberId: member.id, - expiresAt: new Date('2020-03-17'), - }); - }); - - it('displays toast when successful', async () => { - resolveUpdateMemberExpiration(); - await waitForPromises(); - - expect($toast.show).toHaveBeenCalledWith('Expiration date updated successfully.'); - }); - - it('disables dropdown while waiting for `updateMemberExpiration` to resolve', async () => { - expect(findDatepicker().props('disabled')).toBe(true); - - resolveUpdateMemberExpiration(); - await waitForPromises(); - - expect(findDatepicker().props('disabled')).toBe(false); - }); - }); - - describe('when datepicker is cleared', () => { - beforeEach(async () => { - createComponent(); - - findInput().setValue('2020-03-17'); - await nextTick(); - wrapper.find('[data-testid="clear-button"]').trigger('click'); - }); - - it('calls `updateMemberExpiration` Vuex action', () => { - expect(actions.updateMemberExpiration).toHaveBeenCalledWith(expect.any(Object), { - memberId: member.id, - expiresAt: null, - }); - }); - - it('displays toast when successful', async () => { - resolveUpdateMemberExpiration(); - await waitForPromises(); - - expect($toast.show).toHaveBeenCalledWith('Expiration date removed successfully.'); - }); - - it('disables datepicker while waiting for `updateMemberExpiration` to resolve', async () => { - expect(findDatepicker().props('disabled')).toBe(true); - - resolveUpdateMemberExpiration(); - await waitForPromises(); - - expect(findDatepicker().props('disabled')).toBe(false); - }); - }); - - describe('when user does not have `canUpdate` permissions', () => { - it('disables datepicker', () => { - createComponent({ permissions: { canUpdate: false } }); - - expect(findDatepicker().props('disabled')).toBe(true); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/table/expires_at_spec.js b/spec/frontend/vue_shared/components/members/table/expires_at_spec.js deleted file mode 100644 index 95ae251b0fd..00000000000 --- a/spec/frontend/vue_shared/components/members/table/expires_at_spec.js +++ /dev/null @@ -1,86 +0,0 @@ -import { mount, createWrapper } from '@vue/test-utils'; -import { within } from '@testing-library/dom'; -import { useFakeDate } from 'helpers/fake_date'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import ExpiresAt from '~/vue_shared/components/members/table/expires_at.vue'; - -describe('ExpiresAt', () => { - // March 15th, 2020 - useFakeDate(2020, 2, 15); - - let wrapper; - - const createComponent = propsData => { - wrapper = mount(ExpiresAt, { - propsData, - directives: { - GlTooltip: createMockDirective(), - }, - }); - }; - - const getByText = (text, options) => - createWrapper(within(wrapper.element).getByText(text, options)); - - const getTooltipDirective = elementWrapper => getBinding(elementWrapper.element, 'gl-tooltip'); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('when no expiration date is set', () => { - it('displays "No expiration set"', () => { - createComponent({ date: null }); - - expect(getByText('No expiration set').exists()).toBe(true); - }); - }); - - describe('when expiration date is in the past', () => { - let expiredText; - - beforeEach(() => { - createComponent({ date: '2019-03-15T00:00:00.000' }); - - expiredText = getByText('Expired'); - }); - - it('displays "Expired"', () => { - expect(expiredText.exists()).toBe(true); - expect(expiredText.classes()).toContain('gl-text-red-500'); - }); - - it('displays tooltip with formatted date', () => { - const tooltipDirective = getTooltipDirective(expiredText); - - expect(tooltipDirective).not.toBeUndefined(); - expect(expiredText.attributes('title')).toBe('Mar 15, 2019 12:00am GMT+0000'); - }); - }); - - describe('when expiration date is in the future', () => { - it.each` - date | expected | warningColor - ${'2020-03-23T00:00:00.000'} | ${'in 8 days'} | ${false} - ${'2020-03-20T00:00:00.000'} | ${'in 5 days'} | ${true} - ${'2020-03-16T00:00:00.000'} | ${'in 1 day'} | ${true} - ${'2020-03-15T05:00:00.000'} | ${'in about 5 hours'} | ${true} - ${'2020-03-15T01:00:00.000'} | ${'in about 1 hour'} | ${true} - ${'2020-03-15T00:30:00.000'} | ${'in 30 minutes'} | ${true} - ${'2020-03-15T00:01:15.000'} | ${'in 1 minute'} | ${true} - ${'2020-03-15T00:00:15.000'} | ${'in less than a minute'} | ${true} - `('displays "$expected"', ({ date, expected, warningColor }) => { - createComponent({ date }); - - const expiredText = getByText(expected); - - expect(expiredText.exists()).toBe(true); - - if (warningColor) { - expect(expiredText.classes()).toContain('gl-text-orange-500'); - } else { - expect(expiredText.classes()).not.toContain('gl-text-orange-500'); - } - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/table/member_action_buttons_spec.js b/spec/frontend/vue_shared/components/members/table/member_action_buttons_spec.js deleted file mode 100644 index e55d9b6be2a..00000000000 --- a/spec/frontend/vue_shared/components/members/table/member_action_buttons_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { MEMBER_TYPES } from '~/vue_shared/components/members/constants'; -import { member as memberMock, group, invite, accessRequest } from '../mock_data'; -import MemberActionButtons from '~/vue_shared/components/members/table/member_action_buttons.vue'; -import UserActionButtons from '~/vue_shared/components/members/action_buttons/user_action_buttons.vue'; -import GroupActionButtons from '~/vue_shared/components/members/action_buttons/group_action_buttons.vue'; -import InviteActionButtons from '~/vue_shared/components/members/action_buttons/invite_action_buttons.vue'; -import AccessRequestActionButtons from '~/vue_shared/components/members/action_buttons/access_request_action_buttons.vue'; - -describe('MemberActionButtons', () => { - let wrapper; - - const createComponent = (propsData = {}) => { - wrapper = shallowMount(MemberActionButtons, { - propsData: { - isCurrentUser: false, - permissions: { - canRemove: true, - }, - ...propsData, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - test.each` - memberType | member | expectedComponent | expectedComponentName - ${MEMBER_TYPES.user} | ${memberMock} | ${UserActionButtons} | ${'UserActionButtons'} - ${MEMBER_TYPES.group} | ${group} | ${GroupActionButtons} | ${'GroupActionButtons'} - ${MEMBER_TYPES.invite} | ${invite} | ${InviteActionButtons} | ${'InviteActionButtons'} - ${MEMBER_TYPES.accessRequest} | ${accessRequest} | ${AccessRequestActionButtons} | ${'AccessRequestActionButtons'} - `( - 'renders $expectedComponentName when `memberType` is $memberType', - ({ memberType, member, expectedComponent }) => { - createComponent({ memberType, member }); - - expect(wrapper.find(expectedComponent).exists()).toBe(true); - }, - ); -}); diff --git a/spec/frontend/vue_shared/components/members/table/member_avatar_spec.js b/spec/frontend/vue_shared/components/members/table/member_avatar_spec.js deleted file mode 100644 index a171dd830c1..00000000000 --- a/spec/frontend/vue_shared/components/members/table/member_avatar_spec.js +++ /dev/null @@ -1,39 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { MEMBER_TYPES } from '~/vue_shared/components/members/constants'; -import { member as memberMock, group, invite, accessRequest } from '../mock_data'; -import MemberAvatar from '~/vue_shared/components/members/table/member_avatar.vue'; -import UserAvatar from '~/vue_shared/components/members/avatars/user_avatar.vue'; -import GroupAvatar from '~/vue_shared/components/members/avatars/group_avatar.vue'; -import InviteAvatar from '~/vue_shared/components/members/avatars/invite_avatar.vue'; - -describe('MemberList', () => { - let wrapper; - - const createComponent = propsData => { - wrapper = shallowMount(MemberAvatar, { - propsData: { - isCurrentUser: false, - ...propsData, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - test.each` - memberType | member | expectedComponent | expectedComponentName - ${MEMBER_TYPES.user} | ${memberMock} | ${UserAvatar} | ${'UserAvatar'} - ${MEMBER_TYPES.group} | ${group} | ${GroupAvatar} | ${'GroupAvatar'} - ${MEMBER_TYPES.invite} | ${invite} | ${InviteAvatar} | ${'InviteAvatar'} - ${MEMBER_TYPES.accessRequest} | ${accessRequest} | ${UserAvatar} | ${'UserAvatar'} - `( - 'renders $expectedComponentName when `memberType` is $memberType', - ({ memberType, member, expectedComponent }) => { - createComponent({ memberType, member }); - - expect(wrapper.find(expectedComponent).exists()).toBe(true); - }, - ); -}); diff --git a/spec/frontend/vue_shared/components/members/table/member_source_spec.js b/spec/frontend/vue_shared/components/members/table/member_source_spec.js deleted file mode 100644 index 8b914d76674..00000000000 --- a/spec/frontend/vue_shared/components/members/table/member_source_spec.js +++ /dev/null @@ -1,71 +0,0 @@ -import { mount, createWrapper } from '@vue/test-utils'; -import { getByText as getByTextHelper } from '@testing-library/dom'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import MemberSource from '~/vue_shared/components/members/table/member_source.vue'; - -describe('MemberSource', () => { - let wrapper; - - const createComponent = propsData => { - wrapper = mount(MemberSource, { - propsData: { - memberSource: { - id: 102, - name: 'Foo bar', - webUrl: 'https://gitlab.com/groups/foo-bar', - }, - ...propsData, - }, - directives: { - GlTooltip: createMockDirective(), - }, - }); - }; - - const getByText = (text, options) => - createWrapper(getByTextHelper(wrapper.element, text, options)); - - const getTooltipDirective = elementWrapper => getBinding(elementWrapper.element, 'gl-tooltip'); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('direct member', () => { - it('displays "Direct member"', () => { - createComponent({ - isDirectMember: true, - }); - - expect(getByText('Direct member').exists()).toBe(true); - }); - }); - - describe('inherited member', () => { - let sourceGroupLink; - - beforeEach(() => { - createComponent({ - isDirectMember: false, - }); - - sourceGroupLink = getByText('Foo bar'); - }); - - it('displays a link to source group', () => { - createComponent({ - isDirectMember: false, - }); - - expect(sourceGroupLink.exists()).toBe(true); - expect(sourceGroupLink.attributes('href')).toBe('https://gitlab.com/groups/foo-bar'); - }); - - it('displays tooltip with "Inherited"', () => { - const tooltipDirective = getTooltipDirective(sourceGroupLink); - - expect(tooltipDirective).not.toBeUndefined(); - expect(sourceGroupLink.attributes('title')).toBe('Inherited'); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/table/member_table_cell_spec.js b/spec/frontend/vue_shared/components/members/table/member_table_cell_spec.js deleted file mode 100644 index ba693975a88..00000000000 --- a/spec/frontend/vue_shared/components/members/table/member_table_cell_spec.js +++ /dev/null @@ -1,251 +0,0 @@ -import { mount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { MEMBER_TYPES } from '~/vue_shared/components/members/constants'; -import { member as memberMock, group, invite, accessRequest } from '../mock_data'; -import MembersTableCell from '~/vue_shared/components/members/table/members_table_cell.vue'; - -describe('MemberList', () => { - const WrappedComponent = { - props: { - memberType: { - type: String, - required: true, - }, - isDirectMember: { - type: Boolean, - required: true, - }, - isCurrentUser: { - type: Boolean, - required: true, - }, - permissions: { - type: Object, - required: true, - }, - }, - render(createElement) { - return createElement('div', this.memberType); - }, - }; - - const localVue = createLocalVue(); - localVue.use(Vuex); - localVue.component('wrapped-component', WrappedComponent); - - const createStore = (state = {}) => { - return new Vuex.Store({ - state: { - sourceId: 1, - currentUserId: 1, - ...state, - }, - }); - }; - - let wrapper; - - const createComponent = (propsData, state = {}) => { - wrapper = mount(MembersTableCell, { - localVue, - propsData, - store: createStore(state), - scopedSlots: { - default: ` - <wrapped-component - :member-type="props.memberType" - :is-direct-member="props.isDirectMember" - :is-current-user="props.isCurrentUser" - :permissions="props.permissions" - /> - `, - }, - }); - }; - - const findWrappedComponent = () => wrapper.find(WrappedComponent); - - const memberCurrentUser = { - ...memberMock, - user: { - ...memberMock.user, - id: 1, - }, - }; - - const createComponentWithDirectMember = (member = {}) => { - createComponent({ - member: { - ...memberMock, - source: { - ...memberMock.source, - id: 1, - }, - ...member, - }, - }); - }; - const createComponentWithInheritedMember = (member = {}) => { - createComponent({ - member: { ...memberMock, ...member }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - test.each` - member | expectedMemberType - ${memberMock} | ${MEMBER_TYPES.user} - ${group} | ${MEMBER_TYPES.group} - ${invite} | ${MEMBER_TYPES.invite} - ${accessRequest} | ${MEMBER_TYPES.accessRequest} - `( - 'sets scoped slot prop `memberType` to $expectedMemberType', - ({ member, expectedMemberType }) => { - createComponent({ member }); - - expect(findWrappedComponent().props('memberType')).toBe(expectedMemberType); - }, - ); - - describe('isDirectMember', () => { - it('returns `true` when member source has same ID as `sourceId`', () => { - createComponentWithDirectMember(); - - expect(findWrappedComponent().props('isDirectMember')).toBe(true); - }); - - it('returns `false` when member is inherited', () => { - createComponentWithInheritedMember(); - - expect(findWrappedComponent().props('isDirectMember')).toBe(false); - }); - - it('returns `true` for linked groups', () => { - createComponent({ - member: group, - }); - - expect(findWrappedComponent().props('isDirectMember')).toBe(true); - }); - }); - - describe('isCurrentUser', () => { - it('returns `true` when `member.user` has the same ID as `currentUserId`', () => { - createComponent({ - member: memberCurrentUser, - }); - - expect(findWrappedComponent().props('isCurrentUser')).toBe(true); - }); - - it('returns `false` when `member.user` does not have the same ID as `currentUserId`', () => { - createComponent({ - member: memberMock, - }); - - expect(findWrappedComponent().props('isCurrentUser')).toBe(false); - }); - }); - - describe('permissions', () => { - describe('canRemove', () => { - describe('for a direct member', () => { - it('returns `true` when `canRemove` is `true`', () => { - createComponentWithDirectMember({ - canRemove: true, - }); - - expect(findWrappedComponent().props('permissions').canRemove).toBe(true); - }); - - it('returns `false` when `canRemove` is `false`', () => { - createComponentWithDirectMember({ - canRemove: false, - }); - - expect(findWrappedComponent().props('permissions').canRemove).toBe(false); - }); - }); - - describe('for an inherited member', () => { - it('returns `false`', () => { - createComponentWithInheritedMember(); - - expect(findWrappedComponent().props('permissions').canRemove).toBe(false); - }); - }); - }); - - describe('canResend', () => { - describe('when member type is `invite`', () => { - it('returns `true` when `canResend` is `true`', () => { - createComponent({ - member: invite, - }); - - expect(findWrappedComponent().props('permissions').canResend).toBe(true); - }); - - it('returns `false` when `canResend` is `false`', () => { - createComponent({ - member: { - ...invite, - invite: { - ...invite, - canResend: false, - }, - }, - }); - - expect(findWrappedComponent().props('permissions').canResend).toBe(false); - }); - }); - - describe('when member type is not `invite`', () => { - it('returns `false`', () => { - createComponent({ member: memberMock }); - - expect(findWrappedComponent().props('permissions').canResend).toBe(false); - }); - }); - }); - - describe('canUpdate', () => { - describe('for a direct member', () => { - it('returns `true` when `canUpdate` is `true`', () => { - createComponentWithDirectMember({ - canUpdate: true, - }); - - expect(findWrappedComponent().props('permissions').canUpdate).toBe(true); - }); - - it('returns `false` when `canUpdate` is `false`', () => { - createComponentWithDirectMember({ - canUpdate: false, - }); - - expect(findWrappedComponent().props('permissions').canUpdate).toBe(false); - }); - - it('returns `false` for current user', () => { - createComponentWithDirectMember(memberCurrentUser); - - expect(findWrappedComponent().props('permissions').canUpdate).toBe(false); - }); - }); - - describe('for an inherited member', () => { - it('returns `false`', () => { - createComponentWithInheritedMember(); - - expect(findWrappedComponent().props('permissions').canUpdate).toBe(false); - }); - }); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/table/members_table_spec.js b/spec/frontend/vue_shared/components/members/table/members_table_spec.js deleted file mode 100644 index e593e88438c..00000000000 --- a/spec/frontend/vue_shared/components/members/table/members_table_spec.js +++ /dev/null @@ -1,212 +0,0 @@ -import { mount, createLocalVue, createWrapper } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { - getByText as getByTextHelper, - getByTestId as getByTestIdHelper, - within, -} from '@testing-library/dom'; -import { GlBadge, GlTable } from '@gitlab/ui'; -import MembersTable from '~/vue_shared/components/members/table/members_table.vue'; -import MemberAvatar from '~/vue_shared/components/members/table/member_avatar.vue'; -import MemberSource from '~/vue_shared/components/members/table/member_source.vue'; -import ExpiresAt from '~/vue_shared/components/members/table/expires_at.vue'; -import CreatedAt from '~/vue_shared/components/members/table/created_at.vue'; -import RoleDropdown from '~/vue_shared/components/members/table/role_dropdown.vue'; -import ExpirationDatepicker from '~/vue_shared/components/members/table/expiration_datepicker.vue'; -import MemberActionButtons from '~/vue_shared/components/members/table/member_action_buttons.vue'; -import * as initUserPopovers from '~/user_popovers'; -import { member as memberMock, invite, accessRequest } from '../mock_data'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('MemberList', () => { - let wrapper; - - const createStore = (state = {}) => { - return new Vuex.Store({ - state: { - members: [], - tableFields: [], - tableAttrs: { - table: { 'data-qa-selector': 'members_list' }, - tr: { 'data-qa-selector': 'member_row' }, - }, - sourceId: 1, - currentUserId: 1, - ...state, - }, - }); - }; - - const createComponent = state => { - wrapper = mount(MembersTable, { - localVue, - store: createStore(state), - stubs: [ - 'member-avatar', - 'member-source', - 'expires-at', - 'created-at', - 'member-action-buttons', - 'role-dropdown', - 'remove-group-link-modal', - 'expiration-datepicker', - ], - }); - }; - - const getByText = (text, options) => - createWrapper(getByTextHelper(wrapper.element, text, options)); - - const getByTestId = (id, options) => - createWrapper(getByTestIdHelper(wrapper.element, id, options)); - - const findTable = () => wrapper.find(GlTable); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - describe('fields', () => { - const directMember = { - ...memberMock, - source: { ...memberMock.source, id: 1 }, - }; - - const memberCanUpdate = { - ...directMember, - canUpdate: true, - }; - - it.each` - field | label | member | expectedComponent - ${'account'} | ${'Account'} | ${memberMock} | ${MemberAvatar} - ${'source'} | ${'Source'} | ${memberMock} | ${MemberSource} - ${'granted'} | ${'Access granted'} | ${memberMock} | ${CreatedAt} - ${'invited'} | ${'Invited'} | ${invite} | ${CreatedAt} - ${'requested'} | ${'Requested'} | ${accessRequest} | ${CreatedAt} - ${'expires'} | ${'Access expires'} | ${memberMock} | ${ExpiresAt} - ${'maxRole'} | ${'Max role'} | ${memberCanUpdate} | ${RoleDropdown} - ${'expiration'} | ${'Expiration'} | ${memberMock} | ${ExpirationDatepicker} - `('renders the $label field', ({ field, label, member, expectedComponent }) => { - createComponent({ - members: [member], - tableFields: [field], - }); - - expect(getByText(label, { selector: '[role="columnheader"]' }).exists()).toBe(true); - - if (expectedComponent) { - expect( - wrapper - .find(`[data-label="${label}"][role="cell"]`) - .find(expectedComponent) - .exists(), - ).toBe(true); - } - }); - - describe('"Actions" field', () => { - it('renders "Actions" field for screen readers', () => { - createComponent({ members: [memberCanUpdate], tableFields: ['actions'] }); - - const actionField = getByTestId('col-actions'); - - expect(actionField.exists()).toBe(true); - expect(actionField.classes('gl-sr-only')).toBe(true); - expect( - wrapper - .find(`[data-label="Actions"][role="cell"]`) - .find(MemberActionButtons) - .exists(), - ).toBe(true); - }); - - describe('when user is not logged in', () => { - it('does not render the "Actions" field', () => { - createComponent({ currentUserId: null, tableFields: ['actions'] }); - - expect(within(wrapper.element).queryByTestId('col-actions')).toBe(null); - }); - }); - - const memberCanRemove = { - ...directMember, - canRemove: true, - }; - - describe.each` - permission | members - ${'canUpdate'} | ${[memberCanUpdate]} - ${'canRemove'} | ${[memberCanRemove]} - ${'canResend'} | ${[invite]} - `('when one of the members has $permission permissions', ({ members }) => { - it('renders the "Actions" field', () => { - createComponent({ members, tableFields: ['actions'] }); - - expect(getByTestId('col-actions').exists()).toBe(true); - }); - }); - - describe.each` - permission | members - ${'canUpdate'} | ${[memberMock]} - ${'canRemove'} | ${[memberMock]} - ${'canResend'} | ${[{ ...invite, invite: { ...invite.invite, canResend: false } }]} - `('when none of the members have $permission permissions', ({ members }) => { - it('does not render the "Actions" field', () => { - createComponent({ members, tableFields: ['actions'] }); - - expect(within(wrapper.element).queryByTestId('col-actions')).toBe(null); - }); - }); - }); - }); - - describe('when `members` is an empty array', () => { - it('displays a "No members found" message', () => { - createComponent(); - - expect(getByText('No members found').exists()).toBe(true); - }); - }); - - describe('when member can not be updated', () => { - it('renders badge in "Max role" field', () => { - createComponent({ members: [memberMock], tableFields: ['maxRole'] }); - - expect( - wrapper - .find(`[data-label="Max role"][role="cell"]`) - .find(GlBadge) - .text(), - ).toBe(memberMock.accessLevel.stringValue); - }); - }); - - it('initializes user popovers when mounted', () => { - const initUserPopoversMock = jest.spyOn(initUserPopovers, 'default'); - - createComponent(); - - expect(initUserPopoversMock).toHaveBeenCalled(); - }); - - it('adds QA selector to table', () => { - createComponent(); - - expect(findTable().attributes('data-qa-selector')).toBe('members_list'); - }); - - it('adds QA selector to table row', () => { - createComponent(); - - expect( - findTable() - .find('tbody tr') - .attributes('data-qa-selector'), - ).toBe('member_row'); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/table/role_dropdown_spec.js b/spec/frontend/vue_shared/components/members/table/role_dropdown_spec.js deleted file mode 100644 index 55ec7000693..00000000000 --- a/spec/frontend/vue_shared/components/members/table/role_dropdown_spec.js +++ /dev/null @@ -1,151 +0,0 @@ -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, - permissions: {}, - ...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().props('disabled')).toBe(true); - - await waitForPromises(); - - expect(findDropdown().props('disabled')).toBe(false); - }); - }); - }); - - 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(); - }); -}); |