diff options
Diffstat (limited to 'spec/frontend/vue_shared/components/members')
24 files changed, 0 insertions, 2280 deletions
diff --git a/spec/frontend/vue_shared/components/members/action_buttons/access_request_action_buttons_spec.js b/spec/frontend/vue_shared/components/members/action_buttons/access_request_action_buttons_spec.js deleted file mode 100644 index 58cb8ef61d1..00000000000 --- a/spec/frontend/vue_shared/components/members/action_buttons/access_request_action_buttons_spec.js +++ /dev/null @@ -1,108 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import AccessRequestActionButtons from '~/vue_shared/components/members/action_buttons/access_request_action_buttons.vue'; -import RemoveMemberButton from '~/vue_shared/components/members/action_buttons/remove_member_button.vue'; -import ApproveAccessRequestButton from '~/vue_shared/components/members/action_buttons/approve_access_request_button.vue'; -import { accessRequest as member } from '../mock_data'; - -describe('AccessRequestActionButtons', () => { - let wrapper; - - const createComponent = (propsData = {}) => { - wrapper = shallowMount(AccessRequestActionButtons, { - propsData: { - member, - isCurrentUser: true, - ...propsData, - }, - }); - }; - - const findRemoveMemberButton = () => wrapper.find(RemoveMemberButton); - const findApproveButton = () => wrapper.find(ApproveAccessRequestButton); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('when user has `canRemove` permissions', () => { - beforeEach(() => { - createComponent({ - permissions: { - canRemove: true, - }, - }); - }); - - it('renders remove member button', () => { - expect(findRemoveMemberButton().exists()).toBe(true); - }); - - it('sets props correctly', () => { - expect(findRemoveMemberButton().props()).toMatchObject({ - memberId: member.id, - title: 'Deny access', - isAccessRequest: true, - icon: 'close', - }); - }); - - describe('when member is the current user', () => { - it('sets `message` prop correctly', () => { - expect(findRemoveMemberButton().props('message')).toBe( - `Are you sure you want to withdraw your access request for "${member.source.name}"`, - ); - }); - }); - - describe('when member is not the current user', () => { - it('sets `message` prop correctly', () => { - createComponent({ - isCurrentUser: false, - permissions: { - canRemove: true, - }, - }); - - expect(findRemoveMemberButton().props('message')).toBe( - `Are you sure you want to deny ${member.user.name}'s request to join "${member.source.name}"`, - ); - }); - }); - }); - - describe('when user does not have `canRemove` permissions', () => { - it('does not render remove member button', () => { - createComponent({ - permissions: { - canRemove: false, - }, - }); - - expect(findRemoveMemberButton().exists()).toBe(false); - }); - }); - - describe('when user has `canUpdate` permissions', () => { - it('renders the approve button', () => { - createComponent({ - permissions: { - canUpdate: true, - }, - }); - - expect(findApproveButton().exists()).toBe(true); - }); - }); - - describe('when user does not have `canUpdate` permissions', () => { - it('does not render the approve button', () => { - createComponent({ - permissions: { - canUpdate: false, - }, - }); - - expect(findApproveButton().exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/action_buttons/approve_access_request_button_spec.js b/spec/frontend/vue_shared/components/members/action_buttons/approve_access_request_button_spec.js deleted file mode 100644 index 93edaaa400d..00000000000 --- a/spec/frontend/vue_shared/components/members/action_buttons/approve_access_request_button_spec.js +++ /dev/null @@ -1,74 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { GlButton, GlForm } from '@gitlab/ui'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import ApproveAccessRequestButton from '~/vue_shared/components/members/action_buttons/approve_access_request_button.vue'; - -jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('ApproveAccessRequestButton', () => { - let wrapper; - - const createStore = (state = {}) => { - return new Vuex.Store({ - state: { - memberPath: '/groups/foo-bar/-/group_members/:id', - ...state, - }, - }); - }; - - const createComponent = (propsData = {}, state) => { - wrapper = shallowMount(ApproveAccessRequestButton, { - localVue, - store: createStore(state), - propsData: { - memberId: 1, - ...propsData, - }, - directives: { - GlTooltip: createMockDirective(), - }, - }); - }; - - const findForm = () => wrapper.find(GlForm); - const findButton = () => findForm().find(GlButton); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('displays a tooltip', () => { - const button = findButton(); - - expect(getBinding(button.element, 'gl-tooltip')).not.toBeUndefined(); - expect(button.attributes('title')).toBe('Grant access'); - }); - - it('sets `aria-label` attribute', () => { - expect(findButton().attributes('aria-label')).toBe('Grant access'); - }); - - it('submits the form when button is clicked', () => { - expect(findButton().attributes('type')).toBe('submit'); - }); - - it('displays form with correct action and inputs', () => { - const form = findForm(); - - expect(form.attributes('action')).toBe( - '/groups/foo-bar/-/group_members/1/approve_access_request', - ); - expect(form.find('input[name="authenticity_token"]').attributes('value')).toBe( - 'mock-csrf-token', - ); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/action_buttons/invite_action_buttons_spec.js b/spec/frontend/vue_shared/components/members/action_buttons/invite_action_buttons_spec.js deleted file mode 100644 index 1374cdc6aef..00000000000 --- a/spec/frontend/vue_shared/components/members/action_buttons/invite_action_buttons_spec.js +++ /dev/null @@ -1,85 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import InviteActionButtons from '~/vue_shared/components/members/action_buttons/invite_action_buttons.vue'; -import RemoveMemberButton from '~/vue_shared/components/members/action_buttons/remove_member_button.vue'; -import ResendInviteButton from '~/vue_shared/components/members/action_buttons/resend_invite_button.vue'; -import { invite as member } from '../mock_data'; - -describe('InviteActionButtons', () => { - let wrapper; - - const createComponent = (propsData = {}) => { - wrapper = shallowMount(InviteActionButtons, { - propsData: { - member, - ...propsData, - }, - }); - }; - - const findRemoveMemberButton = () => wrapper.find(RemoveMemberButton); - const findResendInviteButton = () => wrapper.find(ResendInviteButton); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('when user has `canRemove` permissions', () => { - beforeEach(() => { - createComponent({ - permissions: { - canRemove: true, - }, - }); - }); - - it('renders remove member button', () => { - expect(findRemoveMemberButton().exists()).toBe(true); - }); - - it('sets props correctly', () => { - expect(findRemoveMemberButton().props()).toEqual({ - memberId: member.id, - message: `Are you sure you want to revoke the invitation for ${member.invite.email} to join "${member.source.name}"`, - title: 'Revoke invite', - isAccessRequest: false, - icon: 'remove', - }); - }); - }); - - describe('when user does not have `canRemove` permissions', () => { - it('does not render remove member button', () => { - createComponent({ - permissions: { - canRemove: false, - }, - }); - - expect(findRemoveMemberButton().exists()).toBe(false); - }); - }); - - describe('when user has `canResend` permissions', () => { - it('renders resend invite button', () => { - createComponent({ - permissions: { - canResend: true, - }, - }); - - expect(findResendInviteButton().exists()).toBe(true); - }); - }); - - describe('when user does not have `canResend` permissions', () => { - it('does not render resend invite button', () => { - createComponent({ - permissions: { - canResend: false, - }, - }); - - expect(findResendInviteButton().exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/action_buttons/leave_button_spec.js b/spec/frontend/vue_shared/components/members/action_buttons/leave_button_spec.js deleted file mode 100644 index 00896b23b95..00000000000 --- a/spec/frontend/vue_shared/components/members/action_buttons/leave_button_spec.js +++ /dev/null @@ -1,59 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlButton } from '@gitlab/ui'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import LeaveButton from '~/vue_shared/components/members/action_buttons/leave_button.vue'; -import LeaveModal from '~/vue_shared/components/members/modals/leave_modal.vue'; -import { LEAVE_MODAL_ID } from '~/vue_shared/components/members/constants'; -import { member } from '../mock_data'; - -describe('LeaveButton', () => { - let wrapper; - - const createComponent = (propsData = {}) => { - wrapper = shallowMount(LeaveButton, { - propsData: { - member, - ...propsData, - }, - directives: { - GlTooltip: createMockDirective(), - GlModal: createMockDirective(), - }, - }); - }; - - const findButton = () => wrapper.find(GlButton); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('displays a tooltip', () => { - const button = findButton(); - - expect(getBinding(button.element, 'gl-tooltip')).not.toBeUndefined(); - expect(button.attributes('title')).toBe('Leave'); - }); - - it('sets `aria-label` attribute', () => { - expect(findButton().attributes('aria-label')).toBe('Leave'); - }); - - it('renders leave modal', () => { - const leaveModal = wrapper.find(LeaveModal); - - expect(leaveModal.exists()).toBe(true); - expect(leaveModal.props('member')).toEqual(member); - }); - - it('triggers leave modal', () => { - const binding = getBinding(findButton().element, 'gl-modal'); - - expect(binding).not.toBeUndefined(); - expect(binding.value).toBe(LEAVE_MODAL_ID); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/action_buttons/remove_group_link_button_spec.js b/spec/frontend/vue_shared/components/members/action_buttons/remove_group_link_button_spec.js deleted file mode 100644 index 84fe1c51773..00000000000 --- a/spec/frontend/vue_shared/components/members/action_buttons/remove_group_link_button_spec.js +++ /dev/null @@ -1,64 +0,0 @@ -import { mount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { GlButton } from '@gitlab/ui'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import RemoveGroupLinkButton from '~/vue_shared/components/members/action_buttons/remove_group_link_button.vue'; -import { group } from '../mock_data'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('RemoveGroupLinkButton', () => { - let wrapper; - - const actions = { - showRemoveGroupLinkModal: jest.fn(), - }; - - const createStore = () => { - return new Vuex.Store({ - actions, - }); - }; - - const createComponent = () => { - wrapper = mount(RemoveGroupLinkButton, { - localVue, - store: createStore(), - propsData: { - groupLink: group, - }, - directives: { - GlTooltip: createMockDirective(), - }, - }); - }; - - const findButton = () => wrapper.find(GlButton); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - it('displays a tooltip', () => { - const button = findButton(); - - expect(getBinding(button.element, 'gl-tooltip')).not.toBeUndefined(); - expect(button.attributes('title')).toBe('Remove group'); - }); - - it('sets `aria-label` attribute', () => { - expect(findButton().attributes('aria-label')).toBe('Remove group'); - }); - - it('calls Vuex action to open remove group link modal when clicked', () => { - findButton().trigger('click'); - - expect(actions.showRemoveGroupLinkModal).toHaveBeenCalledWith(expect.any(Object), group); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/action_buttons/remove_member_button_spec.js b/spec/frontend/vue_shared/components/members/action_buttons/remove_member_button_spec.js deleted file mode 100644 index 7aa30494234..00000000000 --- a/spec/frontend/vue_shared/components/members/action_buttons/remove_member_button_spec.js +++ /dev/null @@ -1,66 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import RemoveMemberButton from '~/vue_shared/components/members/action_buttons/remove_member_button.vue'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('RemoveMemberButton', () => { - let wrapper; - - const createStore = (state = {}) => { - return new Vuex.Store({ - state: { - memberPath: '/groups/foo-bar/-/group_members/:id', - ...state, - }, - }); - }; - - const createComponent = (propsData = {}, state) => { - wrapper = shallowMount(RemoveMemberButton, { - localVue, - store: createStore(state), - propsData: { - memberId: 1, - message: 'Are you sure you want to remove John Smith?', - title: 'Remove member', - isAccessRequest: true, - ...propsData, - }, - directives: { - GlTooltip: createMockDirective(), - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - it('sets attributes on button', () => { - createComponent(); - - expect(wrapper.attributes()).toMatchObject({ - 'data-member-path': '/groups/foo-bar/-/group_members/1', - 'data-message': 'Are you sure you want to remove John Smith?', - 'data-is-access-request': 'true', - 'aria-label': 'Remove member', - title: 'Remove member', - icon: 'remove', - }); - }); - - it('displays `title` prop as a tooltip', () => { - createComponent(); - - expect(getBinding(wrapper.element, 'gl-tooltip')).not.toBeUndefined(); - }); - - it('has CSS class used by `remove_member_modal.vue`', () => { - createComponent(); - - expect(wrapper.classes()).toContain('js-remove-member-button'); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/action_buttons/resend_invite_button_spec.js b/spec/frontend/vue_shared/components/members/action_buttons/resend_invite_button_spec.js deleted file mode 100644 index 859fdd01043..00000000000 --- a/spec/frontend/vue_shared/components/members/action_buttons/resend_invite_button_spec.js +++ /dev/null @@ -1,66 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { GlButton } from '@gitlab/ui'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import ResendInviteButton from '~/vue_shared/components/members/action_buttons/resend_invite_button.vue'; - -jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('ResendInviteButton', () => { - let wrapper; - - const createStore = (state = {}) => { - return new Vuex.Store({ - state: { - memberPath: '/groups/foo-bar/-/group_members/:id', - ...state, - }, - }); - }; - - const createComponent = (propsData = {}, state) => { - wrapper = shallowMount(ResendInviteButton, { - localVue, - store: createStore(state), - propsData: { - memberId: 1, - ...propsData, - }, - directives: { - GlTooltip: createMockDirective(), - }, - }); - }; - - const findForm = () => wrapper.find('form'); - const findButton = () => findForm().find(GlButton); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('displays a tooltip', () => { - expect(getBinding(findButton().element, 'gl-tooltip')).not.toBeUndefined(); - expect(findButton().attributes('title')).toBe('Resend invite'); - }); - - it('submits the form when button is clicked', () => { - expect(findButton().attributes('type')).toBe('submit'); - }); - - it('displays form with correct action and inputs', () => { - expect(findForm().attributes('action')).toBe('/groups/foo-bar/-/group_members/1/resend_invite'); - expect( - findForm() - .find('input[name="authenticity_token"]') - .attributes('value'), - ).toBe('mock-csrf-token'); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/action_buttons/user_action_buttons_spec.js b/spec/frontend/vue_shared/components/members/action_buttons/user_action_buttons_spec.js deleted file mode 100644 index f766ad5b0d1..00000000000 --- a/spec/frontend/vue_shared/components/members/action_buttons/user_action_buttons_spec.js +++ /dev/null @@ -1,89 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import UserActionButtons from '~/vue_shared/components/members/action_buttons/user_action_buttons.vue'; -import RemoveMemberButton from '~/vue_shared/components/members/action_buttons/remove_member_button.vue'; -import LeaveButton from '~/vue_shared/components/members/action_buttons/leave_button.vue'; -import { member, orphanedMember } from '../mock_data'; - -describe('UserActionButtons', () => { - let wrapper; - - const createComponent = (propsData = {}) => { - wrapper = shallowMount(UserActionButtons, { - propsData: { - member, - isCurrentUser: false, - ...propsData, - }, - }); - }; - - const findRemoveMemberButton = () => wrapper.find(RemoveMemberButton); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('when user has `canRemove` permissions', () => { - beforeEach(() => { - createComponent({ - permissions: { - canRemove: true, - }, - }); - }); - - it('renders remove member button', () => { - expect(findRemoveMemberButton().exists()).toBe(true); - }); - - it('sets props correctly', () => { - expect(findRemoveMemberButton().props()).toEqual({ - memberId: member.id, - message: `Are you sure you want to remove ${member.user.name} from "${member.source.name}"`, - title: 'Remove member', - isAccessRequest: false, - icon: 'remove', - }); - }); - - describe('when member is orphaned', () => { - it('sets `message` prop correctly', () => { - createComponent({ - member: orphanedMember, - permissions: { - canRemove: true, - }, - }); - - expect(findRemoveMemberButton().props('message')).toBe( - `Are you sure you want to remove this orphaned member from "${orphanedMember.source.name}"`, - ); - }); - }); - - describe('when member is the current user', () => { - it('renders leave button', () => { - createComponent({ - isCurrentUser: true, - permissions: { - canRemove: true, - }, - }); - - expect(wrapper.find(LeaveButton).exists()).toBe(true); - }); - }); - }); - - describe('when user does not have `canRemove` permissions', () => { - it('does not render remove member button', () => { - createComponent({ - permissions: { - canRemove: false, - }, - }); - - expect(findRemoveMemberButton().exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/avatars/group_avatar_spec.js b/spec/frontend/vue_shared/components/members/avatars/group_avatar_spec.js deleted file mode 100644 index d6f5773295c..00000000000 --- a/spec/frontend/vue_shared/components/members/avatars/group_avatar_spec.js +++ /dev/null @@ -1,46 +0,0 @@ -import { mount, createWrapper } from '@vue/test-utils'; -import { getByText as getByTextHelper } from '@testing-library/dom'; -import { GlAvatarLink } from '@gitlab/ui'; -import { group as member } from '../mock_data'; -import GroupAvatar from '~/vue_shared/components/members/avatars/group_avatar.vue'; - -describe('MemberList', () => { - let wrapper; - - const group = member.sharedWithGroup; - - const createComponent = (propsData = {}) => { - wrapper = mount(GroupAvatar, { - propsData: { - member, - ...propsData, - }, - }); - }; - - const getByText = (text, options) => - createWrapper(getByTextHelper(wrapper.element, text, options)); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders link to group', () => { - const link = wrapper.find(GlAvatarLink); - - expect(link.exists()).toBe(true); - expect(link.attributes('href')).toBe(group.webUrl); - }); - - it("renders group's full name", () => { - expect(getByText(group.fullName).exists()).toBe(true); - }); - - it("renders group's avatar", () => { - expect(wrapper.find('img').attributes('src')).toBe(group.avatarUrl); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/avatars/invite_avatar_spec.js b/spec/frontend/vue_shared/components/members/avatars/invite_avatar_spec.js deleted file mode 100644 index 7948da7eb40..00000000000 --- a/spec/frontend/vue_shared/components/members/avatars/invite_avatar_spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import { mount, createWrapper } from '@vue/test-utils'; -import { getByText as getByTextHelper } from '@testing-library/dom'; -import { invite as member } from '../mock_data'; -import InviteAvatar from '~/vue_shared/components/members/avatars/invite_avatar.vue'; - -describe('MemberList', () => { - let wrapper; - - const { invite } = member; - - const createComponent = (propsData = {}) => { - wrapper = mount(InviteAvatar, { - propsData: { - member, - ...propsData, - }, - }); - }; - - const getByText = (text, options) => - createWrapper(getByTextHelper(wrapper.element, text, options)); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders email as name', () => { - expect(getByText(invite.email).exists()).toBe(true); - }); - - it('renders avatar', () => { - expect(wrapper.find('img').attributes('src')).toBe(invite.avatarUrl); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/avatars/user_avatar_spec.js b/spec/frontend/vue_shared/components/members/avatars/user_avatar_spec.js deleted file mode 100644 index 93d8e640968..00000000000 --- a/spec/frontend/vue_shared/components/members/avatars/user_avatar_spec.js +++ /dev/null @@ -1,115 +0,0 @@ -import { mount, createWrapper } from '@vue/test-utils'; -import { within } from '@testing-library/dom'; -import { GlAvatarLink, GlBadge } from '@gitlab/ui'; -import { member as memberMock, orphanedMember } from '../mock_data'; -import UserAvatar from '~/vue_shared/components/members/avatars/user_avatar.vue'; - -describe('UserAvatar', () => { - let wrapper; - - const { user } = memberMock; - - const createComponent = (propsData = {}) => { - wrapper = mount(UserAvatar, { - propsData: { - member: memberMock, - isCurrentUser: false, - ...propsData, - }, - }); - }; - - const getByText = (text, options) => - createWrapper(within(wrapper.element).findByText(text, options)); - - const findStatusEmoji = emoji => wrapper.find(`gl-emoji[data-name="${emoji}"]`); - - afterEach(() => { - wrapper.destroy(); - }); - - it("renders link to user's profile", () => { - createComponent(); - - const link = wrapper.find(GlAvatarLink); - - expect(link.exists()).toBe(true); - expect(link.attributes()).toMatchObject({ - href: user.webUrl, - 'data-user-id': `${user.id}`, - 'data-username': user.username, - }); - }); - - it("renders user's name", () => { - createComponent(); - - expect(getByText(user.name).exists()).toBe(true); - }); - - it("renders user's username", () => { - createComponent(); - - expect(getByText(`@${user.username}`).exists()).toBe(true); - }); - - it("renders user's avatar", () => { - createComponent(); - - expect(wrapper.find('img').attributes('src')).toBe(user.avatarUrl); - }); - - describe('when user property does not exist', () => { - it('displays an orphaned user', () => { - createComponent({ member: orphanedMember }); - - expect(getByText('Orphaned member').exists()).toBe(true); - }); - }); - - describe('badges', () => { - it.each` - member | badgeText - ${{ ...memberMock, user: { ...memberMock.user, blocked: true } }} | ${'Blocked'} - ${{ ...memberMock, user: { ...memberMock.user, twoFactorEnabled: true } }} | ${'2FA'} - `('renders the "$badgeText" badge', ({ member, badgeText }) => { - createComponent({ member }); - - expect(wrapper.find(GlBadge).text()).toBe(badgeText); - }); - - it('renders the "It\'s you" badge when member is current user', () => { - createComponent({ isCurrentUser: true }); - - expect(getByText("It's you").exists()).toBe(true); - }); - }); - - describe('user status', () => { - const emoji = 'island'; - - describe('when set', () => { - it('displays the status emoji', () => { - createComponent({ - member: { - ...memberMock, - user: { - ...memberMock.user, - status: { emoji, messageHtml: 'On vacation' }, - }, - }, - }); - - expect(findStatusEmoji(emoji).exists()).toBe(true); - }); - }); - - describe('when not set', () => { - it('does not display status emoji', () => { - createComponent(); - - expect(findStatusEmoji(emoji).exists()).toBe(false); - }); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/mock_data.js b/spec/frontend/vue_shared/components/members/mock_data.js deleted file mode 100644 index 5674929716d..00000000000 --- a/spec/frontend/vue_shared/components/members/mock_data.js +++ /dev/null @@ -1,71 +0,0 @@ -export const member = { - requestedAt: null, - canUpdate: false, - canRemove: false, - canOverride: false, - isOverridden: false, - accessLevel: { integerValue: 50, stringValue: 'Owner' }, - source: { - id: 178, - name: 'Foo Bar', - webUrl: 'https://gitlab.com/groups/foo-bar', - }, - user: { - id: 123, - name: 'Administrator', - username: 'root', - webUrl: 'https://gitlab.com/root', - avatarUrl: 'https://www.gravatar.com/avatar/4816142ef496f956a277bedf1a40607b?s=80&d=identicon', - blocked: false, - twoFactorEnabled: false, - }, - id: 238, - createdAt: '2020-07-17T16:22:46.923Z', - expiresAt: null, - usingLicense: false, - groupSso: false, - groupManagedAccount: false, - validRoles: { - Guest: 10, - Reporter: 20, - Developer: 30, - Maintainer: 40, - Owner: 50, - 'Minimal Access': 5, - }, -}; - -export const group = { - accessLevel: { integerValue: 10, stringValue: 'Guest' }, - sharedWithGroup: { - id: 24, - name: 'Commit451', - avatarUrl: '/uploads/-/system/user/avatar/1/avatar.png?width=40', - fullPath: 'parent-group/commit451', - fullName: 'Parent group / Commit451', - webUrl: 'https://gitlab.com/groups/parent-group/commit451', - }, - id: 3, - createdAt: '2020-08-06T15:31:07.662Z', - expiresAt: null, - validRoles: { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 }, -}; - -const { user, ...memberNoUser } = member; -export const invite = { - ...memberNoUser, - invite: { - email: 'jewel@hudsonwalter.biz', - avatarUrl: 'https://www.gravatar.com/avatar/cbab7510da7eec2f60f638261b05436d?s=80&d=identicon', - canResend: true, - }, -}; - -export const orphanedMember = memberNoUser; - -export const accessRequest = { - ...member, - requestedAt: '2020-07-17T16:22:46.923Z', -}; - -export const members = [member]; diff --git a/spec/frontend/vue_shared/components/members/modals/leave_modal_spec.js b/spec/frontend/vue_shared/components/members/modals/leave_modal_spec.js deleted file mode 100644 index 63de355a3c8..00000000000 --- a/spec/frontend/vue_shared/components/members/modals/leave_modal_spec.js +++ /dev/null @@ -1,91 +0,0 @@ -import { mount, createLocalVue, createWrapper } from '@vue/test-utils'; -import { GlModal, GlForm } from '@gitlab/ui'; -import { nextTick } from 'vue'; -import { within } from '@testing-library/dom'; -import Vuex from 'vuex'; -import LeaveModal from '~/vue_shared/components/members/modals/leave_modal.vue'; -import { LEAVE_MODAL_ID } from '~/vue_shared/components/members/constants'; -import { member } from '../mock_data'; - -jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('LeaveModal', () => { - let wrapper; - - const createStore = (state = {}) => { - return new Vuex.Store({ - state: { - memberPath: '/groups/foo-bar/-/group_members/:id', - ...state, - }, - }); - }; - - const createComponent = (propsData = {}, state) => { - wrapper = mount(LeaveModal, { - localVue, - store: createStore(state), - propsData: { - member, - ...propsData, - }, - attrs: { - static: true, - visible: true, - }, - }); - }; - - const findModal = () => wrapper.find(GlModal); - - const findForm = () => findModal().find(GlForm); - - const getByText = (text, options) => - createWrapper(within(findModal().element).getByText(text, options)); - - beforeEach(async () => { - createComponent(); - await nextTick(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('sets modal ID', () => { - expect(findModal().props('modalId')).toBe(LEAVE_MODAL_ID); - }); - - it('displays modal title', () => { - expect(getByText(`Leave "${member.source.name}"`).exists()).toBe(true); - }); - - it('displays modal body', () => { - expect(getByText(`Are you sure you want to leave "${member.source.name}"?`).exists()).toBe( - true, - ); - }); - - it('displays form with correct action and inputs', () => { - const form = findForm(); - - expect(form.attributes('action')).toBe('/groups/foo-bar/-/group_members/leave'); - expect(form.find('input[name="_method"]').attributes('value')).toBe('delete'); - expect(form.find('input[name="authenticity_token"]').attributes('value')).toBe( - 'mock-csrf-token', - ); - }); - - it('submits the form when "Leave" button is clicked', () => { - const submitSpy = jest.spyOn(findForm().element, 'submit'); - - getByText('Leave').trigger('click'); - - expect(submitSpy).toHaveBeenCalled(); - - submitSpy.mockRestore(); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/modals/remove_group_link_modal_spec.js b/spec/frontend/vue_shared/components/members/modals/remove_group_link_modal_spec.js deleted file mode 100644 index 84da051792d..00000000000 --- a/spec/frontend/vue_shared/components/members/modals/remove_group_link_modal_spec.js +++ /dev/null @@ -1,106 +0,0 @@ -import { mount, createLocalVue, createWrapper } from '@vue/test-utils'; -import { GlModal, GlForm } from '@gitlab/ui'; -import { nextTick } from 'vue'; -import { within } from '@testing-library/dom'; -import Vuex from 'vuex'; -import RemoveGroupLinkModal from '~/vue_shared/components/members/modals/remove_group_link_modal.vue'; -import { REMOVE_GROUP_LINK_MODAL_ID } from '~/vue_shared/components/members/constants'; -import { group } from '../mock_data'; - -jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('RemoveGroupLinkModal', () => { - let wrapper; - - const actions = { - hideRemoveGroupLinkModal: jest.fn(), - }; - - const createStore = (state = {}) => { - return new Vuex.Store({ - state: { - memberPath: '/groups/foo-bar/-/group_links/:id', - groupLinkToRemove: group, - removeGroupLinkModalVisible: true, - ...state, - }, - actions, - }); - }; - - const createComponent = state => { - wrapper = mount(RemoveGroupLinkModal, { - localVue, - store: createStore(state), - attrs: { - static: true, - }, - }); - }; - - const findModal = () => wrapper.find(GlModal); - const findForm = () => findModal().find(GlForm); - const getByText = (text, options) => - createWrapper(within(findModal().element).getByText(text, options)); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - describe('when modal is open', () => { - beforeEach(async () => { - createComponent(); - await nextTick(); - }); - - it('sets modal ID', () => { - expect(findModal().props('modalId')).toBe(REMOVE_GROUP_LINK_MODAL_ID); - }); - - it('displays modal title', () => { - expect(getByText(`Remove "${group.sharedWithGroup.fullName}"`).exists()).toBe(true); - }); - - it('displays modal body', () => { - expect( - getByText(`Are you sure you want to remove "${group.sharedWithGroup.fullName}"?`).exists(), - ).toBe(true); - }); - - it('displays form with correct action and inputs', () => { - const form = findForm(); - - expect(form.attributes('action')).toBe(`/groups/foo-bar/-/group_links/${group.id}`); - expect(form.find('input[name="_method"]').attributes('value')).toBe('delete'); - expect(form.find('input[name="authenticity_token"]').attributes('value')).toBe( - 'mock-csrf-token', - ); - }); - - it('submits the form when "Remove group" button is clicked', () => { - const submitSpy = jest.spyOn(findForm().element, 'submit'); - - getByText('Remove group').trigger('click'); - - expect(submitSpy).toHaveBeenCalled(); - - submitSpy.mockRestore(); - }); - - it('calls `hideRemoveGroupLinkModal` action when modal is closed', () => { - getByText('Cancel').trigger('click'); - - expect(actions.hideRemoveGroupLinkModal).toHaveBeenCalled(); - }); - }); - - it('modal does not show when `removeGroupLinkModalVisible` is `false`', () => { - createComponent({ removeGroupLinkModalVisible: false }); - - expect(findModal().vm.$attrs.visible).toBe(false); - }); -}); 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(); - }); -}); diff --git a/spec/frontend/vue_shared/components/members/utils_spec.js b/spec/frontend/vue_shared/components/members/utils_spec.js deleted file mode 100644 index 3f2b2097133..00000000000 --- a/spec/frontend/vue_shared/components/members/utils_spec.js +++ /dev/null @@ -1,122 +0,0 @@ -import { - generateBadges, - isGroup, - isDirectMember, - isCurrentUser, - canRemove, - canResend, - canUpdate, - canOverride, -} from '~/vue_shared/components/members/utils'; -import { member as memberMock, group, invite } from './mock_data'; - -const DIRECT_MEMBER_ID = 178; -const INHERITED_MEMBER_ID = 179; -const IS_CURRENT_USER_ID = 123; -const IS_NOT_CURRENT_USER_ID = 124; - -describe('Members Utils', () => { - describe('generateBadges', () => { - it('has correct properties for each badge', () => { - const badges = generateBadges(memberMock, true); - - badges.forEach(badge => { - expect(badge).toEqual( - expect.objectContaining({ - show: expect.any(Boolean), - text: expect.any(String), - variant: expect.stringMatching(/muted|neutral|info|success|danger|warning/), - }), - ); - }); - }); - - it.each` - member | expected - ${memberMock} | ${{ show: true, text: "It's you", variant: 'success' }} - ${{ ...memberMock, user: { ...memberMock.user, blocked: true } }} | ${{ show: true, text: 'Blocked', variant: 'danger' }} - ${{ ...memberMock, user: { ...memberMock.user, twoFactorEnabled: true } }} | ${{ show: true, text: '2FA', variant: 'info' }} - `('returns expected output for "$expected.text" badge', ({ member, expected }) => { - expect(generateBadges(member, true)).toContainEqual(expect.objectContaining(expected)); - }); - }); - - describe('isGroup', () => { - test.each` - member | expected - ${group} | ${true} - ${memberMock} | ${false} - `('returns $expected', ({ member, expected }) => { - expect(isGroup(member)).toBe(expected); - }); - }); - - describe('isDirectMember', () => { - test.each` - sourceId | expected - ${DIRECT_MEMBER_ID} | ${true} - ${INHERITED_MEMBER_ID} | ${false} - `('returns $expected', ({ sourceId, expected }) => { - expect(isDirectMember(memberMock, sourceId)).toBe(expected); - }); - }); - - describe('isCurrentUser', () => { - test.each` - currentUserId | expected - ${IS_CURRENT_USER_ID} | ${true} - ${IS_NOT_CURRENT_USER_ID} | ${false} - `('returns $expected', ({ currentUserId, expected }) => { - expect(isCurrentUser(memberMock, currentUserId)).toBe(expected); - }); - }); - - describe('canRemove', () => { - const memberCanRemove = { - ...memberMock, - canRemove: true, - }; - - test.each` - member | sourceId | expected - ${memberCanRemove} | ${DIRECT_MEMBER_ID} | ${true} - ${memberCanRemove} | ${INHERITED_MEMBER_ID} | ${false} - ${memberMock} | ${INHERITED_MEMBER_ID} | ${false} - `('returns $expected', ({ member, sourceId, expected }) => { - expect(canRemove(member, sourceId)).toBe(expected); - }); - }); - - describe('canResend', () => { - test.each` - member | expected - ${invite} | ${true} - ${{ ...invite, invite: { ...invite.invite, canResend: false } }} | ${false} - `('returns $expected', ({ member, sourceId, expected }) => { - expect(canResend(member, sourceId)).toBe(expected); - }); - }); - - describe('canUpdate', () => { - const memberCanUpdate = { - ...memberMock, - canUpdate: true, - }; - - test.each` - member | currentUserId | sourceId | expected - ${memberCanUpdate} | ${IS_NOT_CURRENT_USER_ID} | ${DIRECT_MEMBER_ID} | ${true} - ${memberCanUpdate} | ${IS_CURRENT_USER_ID} | ${DIRECT_MEMBER_ID} | ${false} - ${memberCanUpdate} | ${IS_CURRENT_USER_ID} | ${INHERITED_MEMBER_ID} | ${false} - ${memberMock} | ${IS_NOT_CURRENT_USER_ID} | ${DIRECT_MEMBER_ID} | ${false} - `('returns $expected', ({ member, currentUserId, sourceId, expected }) => { - expect(canUpdate(member, currentUserId, sourceId)).toBe(expected); - }); - }); - - describe('canOverride', () => { - it('returns `false`', () => { - expect(canOverride(memberMock)).toBe(false); - }); - }); -}); |