diff options
Diffstat (limited to 'spec/frontend/members/components/action_dropdowns')
3 files changed, 351 insertions, 0 deletions
diff --git a/spec/frontend/members/components/action_dropdowns/leave_group_dropdown_item_spec.js b/spec/frontend/members/components/action_dropdowns/leave_group_dropdown_item_spec.js new file mode 100644 index 00000000000..90f5b217007 --- /dev/null +++ b/spec/frontend/members/components/action_dropdowns/leave_group_dropdown_item_spec.js @@ -0,0 +1,54 @@ +import { GlDropdownItem } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import LeaveGroupDropdownItem from '~/members/components/action_dropdowns/leave_group_dropdown_item.vue'; +import LeaveModal from '~/members/components/modals/leave_modal.vue'; +import { LEAVE_MODAL_ID } from '~/members/constants'; +import { member, permissions } from '../../mock_data'; + +describe('LeaveGroupDropdownItem', () => { + let wrapper; + const text = 'dummy'; + + const createComponent = (propsData = {}) => { + wrapper = shallowMount(LeaveGroupDropdownItem, { + propsData: { + member, + permissions, + ...propsData, + }, + directives: { + GlModal: createMockDirective(), + }, + slots: { + default: text, + }, + }); + }; + + const findDropdownItem = () => wrapper.findComponent(GlDropdownItem); + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders a slot with red text', () => { + expect(findDropdownItem().html()).toContain(`<span class="gl-text-red-500">${text}</span>`); + }); + + it('contains LeaveModal component', () => { + const leaveModal = wrapper.findComponent(LeaveModal); + + expect(leaveModal.props()).toEqual({ member, permissions }); + }); + + it('binds to the LeaveModal component', () => { + const binding = getBinding(findDropdownItem().element, 'gl-modal'); + + expect(binding.value).toBe(LEAVE_MODAL_ID); + }); +}); diff --git a/spec/frontend/members/components/action_dropdowns/remove_member_dropdown_item_spec.js b/spec/frontend/members/components/action_dropdowns/remove_member_dropdown_item_spec.js new file mode 100644 index 00000000000..e1c498249d7 --- /dev/null +++ b/spec/frontend/members/components/action_dropdowns/remove_member_dropdown_item_spec.js @@ -0,0 +1,77 @@ +import { GlDropdownItem } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import Vuex from 'vuex'; +import { modalData } from 'jest/members/mock_data'; +import RemoveMemberDropdownItem from '~/members/components/action_dropdowns/remove_member_dropdown_item.vue'; +import { MEMBER_TYPES, MEMBER_MODEL_TYPE_GROUP_MEMBER } from '~/members/constants'; + +Vue.use(Vuex); + +describe('RemoveMemberDropdownItem', () => { + let wrapper; + const text = 'dummy'; + + const actions = { + showRemoveMemberModal: jest.fn(), + }; + + const createStore = (state = {}) => { + return new Vuex.Store({ + modules: { + [MEMBER_TYPES.user]: { + namespaced: true, + state: { + memberPath: '/groups/foo-bar/-/group_members/:id', + ...state, + }, + actions, + }, + }, + }); + }; + + const createComponent = (propsData = {}, state) => { + wrapper = shallowMount(RemoveMemberDropdownItem, { + store: createStore(state), + provide: { + namespace: MEMBER_TYPES.user, + }, + propsData: { + memberId: 1, + memberModelType: MEMBER_MODEL_TYPE_GROUP_MEMBER, + modalMessage: 'Are you sure you want to remove John Smith?', + isAccessRequest: true, + isInvite: true, + userDeletionObstacles: { name: 'user', obstacles: [] }, + ...propsData, + }, + slots: { + default: text, + }, + }); + }; + + const findDropdownItem = () => wrapper.findComponent(GlDropdownItem); + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders a slot with red text', () => { + expect(findDropdownItem().html()).toContain(`<span class="gl-text-red-500">${text}</span>`); + }); + + it('calls Vuex action to show `remove member` modal when clicked', () => { + findDropdownItem().vm.$emit('click'); + + expect(actions.showRemoveMemberModal).toHaveBeenCalledWith(expect.any(Object), { + ...modalData, + preventRemoval: false, + }); + }); +}); diff --git a/spec/frontend/members/components/action_dropdowns/user_action_dropdown_spec.js b/spec/frontend/members/components/action_dropdowns/user_action_dropdown_spec.js new file mode 100644 index 00000000000..5a2de1cac80 --- /dev/null +++ b/spec/frontend/members/components/action_dropdowns/user_action_dropdown_spec.js @@ -0,0 +1,220 @@ +import { shallowMount } from '@vue/test-utils'; +import { sprintf } from '~/locale'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import LeaveGroupDropdownItem from '~/members/components/action_dropdowns/leave_group_dropdown_item.vue'; +import RemoveMemberDropdownItem from '~/members/components/action_dropdowns/remove_member_dropdown_item.vue'; +import UserActionDropdown from '~/members/components/action_dropdowns/user_action_dropdown.vue'; +import { I18N } from '~/members/components/action_dropdowns/constants'; +import { + MEMBER_MODEL_TYPE_GROUP_MEMBER, + MEMBER_MODEL_TYPE_PROJECT_MEMBER, +} from '~/members/constants'; +import { parseUserDeletionObstacles } from '~/vue_shared/components/user_deletion_obstacles/utils'; +import { member, orphanedMember } from '../../mock_data'; + +describe('UserActionDropdown', () => { + let wrapper; + + const createComponent = (propsData = {}) => { + wrapper = shallowMount(UserActionDropdown, { + propsData: { + member, + isCurrentUser: false, + isInvitedUser: false, + ...propsData, + }, + directives: { + GlTooltip: createMockDirective(), + }, + }); + }; + + const findRemoveMemberDropdownItem = () => wrapper.findComponent(RemoveMemberDropdownItem); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when user has `canRemove` permissions', () => { + beforeEach(() => { + createComponent({ + permissions: { + canRemove: true, + }, + }); + }); + + it('renders remove member dropdown with correct text', () => { + const removeMemberDropdownItem = findRemoveMemberDropdownItem(); + expect(removeMemberDropdownItem.exists()).toBe(true); + expect(removeMemberDropdownItem.html()).toContain(I18N.removeMember); + }); + + it('displays a tooltip', () => { + const tooltip = getBinding(wrapper.element, 'gl-tooltip'); + expect(tooltip).not.toBeUndefined(); + expect(tooltip.value).toBe(I18N.actions); + }); + + it('sets props correctly', () => { + expect(findRemoveMemberDropdownItem().props()).toEqual({ + memberId: member.id, + memberModelType: MEMBER_MODEL_TYPE_GROUP_MEMBER, + modalMessage: sprintf( + I18N.confirmNormalUserRemoval, + { + userName: member.user.name, + group: member.source.fullName, + }, + false, + ), + isAccessRequest: false, + isInvite: false, + userDeletionObstacles: { + name: member.user.name, + obstacles: parseUserDeletionObstacles(member.user), + }, + preventRemoval: false, + }); + }); + + describe('when member is orphaned', () => { + it('sets `message` prop correctly', () => { + createComponent({ + member: orphanedMember, + permissions: { + canRemove: true, + }, + }); + + expect(findRemoveMemberDropdownItem().props('modalMessage')).toBe( + sprintf(I18N.confirmOrphanedUserRemoval, { group: orphanedMember.source.fullName }), + ); + }); + }); + + describe('when member is the current user', () => { + it('renders leave dropdown with correct text', () => { + createComponent({ + isCurrentUser: true, + permissions: { + canRemove: true, + }, + }); + + const leaveGroupDropdownItem = wrapper.findComponent(LeaveGroupDropdownItem); + expect(leaveGroupDropdownItem.exists()).toBe(true); + expect(leaveGroupDropdownItem.html()).toContain(I18N.leaveGroup); + }); + }); + }); + + describe('when user does not have `canRemove` permissions', () => { + it('does not render remove member dropdown', () => { + createComponent({ + permissions: { + canRemove: false, + }, + }); + + expect(findRemoveMemberDropdownItem().exists()).toBe(false); + }); + }); + + describe('when user can remove but it is blocked by last owner', () => { + const permissions = { + canRemove: false, + canRemoveBlockedByLastOwner: true, + }; + + it('renders remove member dropdown', () => { + createComponent({ + permissions, + }); + + expect(findRemoveMemberDropdownItem().exists()).toBe(true); + }); + + describe('when member model type is `GroupMember`', () => { + it('passes correct message to the modal', () => { + createComponent({ + permissions, + }); + + expect(findRemoveMemberDropdownItem().props('modalMessage')).toBe( + I18N.lastGroupOwnerCannotBeRemoved, + ); + }); + }); + + describe('when member model type is `ProjectMember`', () => { + it('passes correct message to the modal', () => { + createComponent({ + member: { + ...member, + type: MEMBER_MODEL_TYPE_PROJECT_MEMBER, + }, + permissions, + }); + + expect(findRemoveMemberDropdownItem().props('modalMessage')).toBe( + I18N.personalProjectOwnerCannotBeRemoved, + ); + }); + }); + + describe('when member is the current user', () => { + it('renders leave dropdown with correct props', () => { + createComponent({ + isCurrentUser: true, + permissions, + }); + + expect(wrapper.findComponent(LeaveGroupDropdownItem).props()).toEqual({ + member, + permissions, + }); + }); + }); + }); + + describe('when group member', () => { + beforeEach(() => { + createComponent({ + member: { + ...member, + type: MEMBER_MODEL_TYPE_GROUP_MEMBER, + }, + permissions: { + canRemove: true, + }, + }); + }); + + it('sets member type correctly', () => { + expect(findRemoveMemberDropdownItem().props().memberModelType).toBe( + MEMBER_MODEL_TYPE_GROUP_MEMBER, + ); + }); + }); + + describe('when project member', () => { + beforeEach(() => { + createComponent({ + member: { + ...member, + type: MEMBER_MODEL_TYPE_PROJECT_MEMBER, + }, + permissions: { + canRemove: true, + }, + }); + }); + + it('sets member type correctly', () => { + expect(findRemoveMemberDropdownItem().props().memberModelType).toBe( + MEMBER_MODEL_TYPE_PROJECT_MEMBER, + ); + }); + }); +}); |