Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									afbe8fe679
								
							
						
					
					
						commit
						dbdfb3e36d
					
				|  | @ -1,83 +0,0 @@ | |||
| <script> | ||||
| import { s__, __, sprintf } from '~/locale'; | ||||
| import { parseUserDeletionObstacles } from '~/vue_shared/components/user_deletion_obstacles/utils'; | ||||
| import ActionButtonGroup from './action_button_group.vue'; | ||||
| import LeaveButton from './leave_button.vue'; | ||||
| import RemoveMemberButton from './remove_member_button.vue'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'UserActionButtons', | ||||
|   i18n: { | ||||
|     title: __('Remove member'), | ||||
|   }, | ||||
|   components: { | ||||
|     ActionButtonGroup, | ||||
|     RemoveMemberButton, | ||||
|     LeaveButton, | ||||
|     LdapOverrideButton: () => | ||||
|       import('ee_component/members/components/ldap/ldap_override_button.vue'), | ||||
|   }, | ||||
|   props: { | ||||
|     member: { | ||||
|       type: Object, | ||||
|       required: true, | ||||
|     }, | ||||
|     isCurrentUser: { | ||||
|       type: Boolean, | ||||
|       required: true, | ||||
|     }, | ||||
|     permissions: { | ||||
|       type: Object, | ||||
|       required: true, | ||||
|     }, | ||||
|   }, | ||||
|   computed: { | ||||
|     message() { | ||||
|       const { user, source } = this.member; | ||||
| 
 | ||||
|       if (user) { | ||||
|         return sprintf( | ||||
|           s__('Members|Are you sure you want to remove %{usersName} from "%{source}"?'), | ||||
|           { | ||||
|             usersName: user.name, | ||||
|             source: source.fullName, | ||||
|           }, | ||||
|           false, | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       return sprintf( | ||||
|         s__('Members|Are you sure you want to remove this orphaned member from "%{source}"?'), | ||||
|         { | ||||
|           source: source.fullName, | ||||
|         }, | ||||
|       ); | ||||
|     }, | ||||
|     userDeletionObstaclesUserData() { | ||||
|       return { | ||||
|         name: this.member.user?.name, | ||||
|         obstacles: parseUserDeletionObstacles(this.member.user), | ||||
|       }; | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <action-button-group> | ||||
|     <div v-if="permissions.canRemove" class="gl-px-1"> | ||||
|       <leave-button v-if="isCurrentUser" :member="member" /> | ||||
|       <remove-member-button | ||||
|         v-else | ||||
|         :member-id="member.id" | ||||
|         :member-type="member.type" | ||||
|         :user-deletion-obstacles="userDeletionObstaclesUserData" | ||||
|         :message="message" | ||||
|         :title="$options.i18n.title" | ||||
|       /> | ||||
|     </div> | ||||
|     <div v-else-if="permissions.canOverride && !member.isOverridden" class="gl-px-1"> | ||||
|       <ldap-override-button :member="member" /> | ||||
|     </div> | ||||
|   </action-button-group> | ||||
| </template> | ||||
|  | @ -0,0 +1,14 @@ | |||
| import { __, s__ } from '~/locale'; | ||||
| 
 | ||||
| export const I18N = { | ||||
|   actions: __('More actions'), | ||||
|   editPermissions: s__('Members|Edit permissions'), | ||||
|   leaveGroup: __('Leave group'), | ||||
|   removeMember: __('Remove member'), | ||||
|   confirmNormalUserRemoval: s__( | ||||
|     'Members|Are you sure you want to remove %{userName} from "%{group}"?', | ||||
|   ), | ||||
|   confirmOrphanedUserRemoval: s__( | ||||
|     'Members|Are you sure you want to remove this orphaned member from "%{group}"?', | ||||
|   ), | ||||
| }; | ||||
|  | @ -1,20 +1,17 @@ | |||
| <script> | ||||
| import { GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui'; | ||||
| import { __ } from '~/locale'; | ||||
| import { GlDropdownItem, GlModalDirective } from '@gitlab/ui'; | ||||
| import { LEAVE_MODAL_ID } from '../../constants'; | ||||
| import LeaveModal from '../modals/leave_modal.vue'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'LeaveButton', | ||||
|   title: __('Leave'), | ||||
|   name: 'LeaveGroupDropdownItem', | ||||
|   modalId: LEAVE_MODAL_ID, | ||||
|   components: { | ||||
|     GlButton, | ||||
|     GlDropdownItem, | ||||
|     LeaveModal, | ||||
|   }, | ||||
|   directives: { | ||||
|     GlModal: GlModalDirective, | ||||
|     GlTooltip: GlTooltipDirective, | ||||
|   }, | ||||
|   props: { | ||||
|     member: { | ||||
|  | @ -26,14 +23,10 @@ export default { | |||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div> | ||||
|     <gl-button | ||||
|       v-gl-tooltip.hover | ||||
|       v-gl-modal="$options.modalId" | ||||
|       :title="$options.title" | ||||
|       :aria-label="$options.title" | ||||
|       icon="leave" | ||||
|     /> | ||||
|   <gl-dropdown-item v-gl-modal="$options.modalId"> | ||||
|     <span class="gl-text-red-500"> | ||||
|       <slot></slot> | ||||
|     </span> | ||||
|     <leave-modal :member="member" /> | ||||
|   </div> | ||||
|   </gl-dropdown-item> | ||||
| </template> | ||||
|  | @ -0,0 +1,75 @@ | |||
| <script> | ||||
| import { GlDropdownItem } from '@gitlab/ui'; | ||||
| import { mapActions, mapState } from 'vuex'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'RemoveMemberDropdownItem', | ||||
|   components: { GlDropdownItem }, | ||||
|   inject: ['namespace'], | ||||
|   props: { | ||||
|     memberId: { | ||||
|       type: Number, | ||||
|       required: true, | ||||
|     }, | ||||
|     memberType: { | ||||
|       type: String, | ||||
|       required: false, | ||||
|       default: null, | ||||
|     }, | ||||
|     modalMessage: { | ||||
|       type: String, | ||||
|       required: true, | ||||
|     }, | ||||
|     isAccessRequest: { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: false, | ||||
|     }, | ||||
|     isInvite: { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: false, | ||||
|     }, | ||||
|     userDeletionObstacles: { | ||||
|       type: Object, | ||||
|       required: false, | ||||
|       default: () => ({}), | ||||
|     }, | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapState({ | ||||
|       memberPath(state) { | ||||
|         return state[this.namespace].memberPath; | ||||
|       }, | ||||
|     }), | ||||
|     modalData() { | ||||
|       return { | ||||
|         isAccessRequest: this.isAccessRequest, | ||||
|         isInvite: this.isInvite, | ||||
|         memberPath: this.memberPath.replace(':id', this.memberId), | ||||
|         memberType: this.memberType, | ||||
|         message: this.modalMessage, | ||||
|         userDeletionObstacles: this.userDeletionObstacles, | ||||
|       }; | ||||
|     }, | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions({ | ||||
|       showRemoveMemberModal(dispatch, payload) { | ||||
|         return dispatch(`${this.namespace}/showRemoveMemberModal`, payload); | ||||
|       }, | ||||
|     }), | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <gl-dropdown-item | ||||
|     data-qa-selector="delete_member_dropdown_item" | ||||
|     @click="showRemoveMemberModal(modalData)" | ||||
|   > | ||||
|     <span class="gl-text-red-500"> | ||||
|       <slot></slot> | ||||
|     </span> | ||||
|   </gl-dropdown-item> | ||||
| </template> | ||||
|  | @ -0,0 +1,96 @@ | |||
| <script> | ||||
| import { GlDropdown, GlTooltipDirective } from '@gitlab/ui'; | ||||
| import { sprintf } from '~/locale'; | ||||
| import { parseUserDeletionObstacles } from '~/vue_shared/components/user_deletion_obstacles/utils'; | ||||
| import { I18N } from './constants'; | ||||
| import LeaveGroupDropdownItem from './leave_group_dropdown_item.vue'; | ||||
| import RemoveMemberDropdownItem from './remove_member_dropdown_item.vue'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'UserActionDropdown', | ||||
|   i18n: I18N, | ||||
|   components: { | ||||
|     GlDropdown, | ||||
|     LdapOverrideDropdownItem: () => | ||||
|       import('ee_component/members/components/ldap/ldap_override_dropdown_item.vue'), | ||||
|     LeaveGroupDropdownItem, | ||||
|     RemoveMemberDropdownItem, | ||||
|   }, | ||||
|   directives: { | ||||
|     GlTooltip: GlTooltipDirective, | ||||
|   }, | ||||
|   props: { | ||||
|     member: { | ||||
|       type: Object, | ||||
|       required: true, | ||||
|     }, | ||||
|     isCurrentUser: { | ||||
|       type: Boolean, | ||||
|       required: true, | ||||
|     }, | ||||
|     permissions: { | ||||
|       type: Object, | ||||
|       required: true, | ||||
|     }, | ||||
|   }, | ||||
|   computed: { | ||||
|     modalMessage() { | ||||
|       const { user, source } = this.member; | ||||
| 
 | ||||
|       if (user) { | ||||
|         return sprintf( | ||||
|           this.$options.i18n.confirmNormalUserRemoval, | ||||
|           { userName: user.name, group: source.fullName }, | ||||
|           false, | ||||
|         ); | ||||
|       } | ||||
| 
 | ||||
|       return sprintf(this.$options.i18n.confirmOrphanedUserRemoval, { group: source.fullName }); | ||||
|     }, | ||||
|     userDeletionObstaclesUserData() { | ||||
|       return { | ||||
|         name: this.member.user?.name, | ||||
|         obstacles: parseUserDeletionObstacles(this.member.user), | ||||
|       }; | ||||
|     }, | ||||
|     showDropdown() { | ||||
|       return this.permissions.canRemove || this.showLdapOverride; | ||||
|     }, | ||||
|     showLdapOverride() { | ||||
|       return this.permissions.canOverride && !this.member.isOverridden; | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <gl-dropdown | ||||
|     v-if="showDropdown" | ||||
|     v-gl-tooltip="$options.i18n.actions" | ||||
|     :text="$options.i18n.actions" | ||||
|     text-sr-only | ||||
|     icon="ellipsis_v" | ||||
|     category="tertiary" | ||||
|     no-caret | ||||
|     right | ||||
|     data-testid="user-action-dropdown" | ||||
|     data-qa-selector="user_action_dropdown" | ||||
|   > | ||||
|     <template v-if="permissions.canRemove"> | ||||
|       <leave-group-dropdown-item v-if="isCurrentUser" :member="member">{{ | ||||
|         $options.i18n.leaveGroup | ||||
|       }}</leave-group-dropdown-item> | ||||
|       <remove-member-dropdown-item | ||||
|         v-else | ||||
|         :member-id="member.id" | ||||
|         :member-type="member.type" | ||||
|         :user-deletion-obstacles="userDeletionObstaclesUserData" | ||||
|         :modal-message="modalMessage" | ||||
|         >{{ $options.i18n.removeMember }}</remove-member-dropdown-item | ||||
|       > | ||||
|     </template> | ||||
|     <ldap-override-dropdown-item v-else-if="showLdapOverride" :member="member">{{ | ||||
|       $options.i18n.editPermissions | ||||
|     }}</ldap-override-dropdown-item> | ||||
|   </gl-dropdown> | ||||
| </template> | ||||
|  | @ -3,12 +3,12 @@ import { MEMBER_TYPES, EE_ACTION_BUTTONS } from 'ee_else_ce/members/constants'; | |||
| import AccessRequestActionButtons from '../action_buttons/access_request_action_buttons.vue'; | ||||
| import GroupActionButtons from '../action_buttons/group_action_buttons.vue'; | ||||
| import InviteActionButtons from '../action_buttons/invite_action_buttons.vue'; | ||||
| import UserActionButtons from '../action_buttons/user_action_buttons.vue'; | ||||
| import UserActionDropdown from '../action_dropdowns/user_action_dropdown.vue'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'MemberActionButtons', | ||||
|   components: { | ||||
|     UserActionButtons, | ||||
|     UserActionDropdown, | ||||
|     GroupActionButtons, | ||||
|     InviteActionButtons, | ||||
|     AccessRequestActionButtons, | ||||
|  | @ -36,7 +36,7 @@ export default { | |||
|   computed: { | ||||
|     actionButtonComponent() { | ||||
|       const dictionary = { | ||||
|         [MEMBER_TYPES.user]: 'user-action-buttons', | ||||
|         [MEMBER_TYPES.user]: 'user-action-dropdown', | ||||
|         [MEMBER_TYPES.group]: 'group-action-buttons', | ||||
|         [MEMBER_TYPES.invite]: 'invite-action-buttons', | ||||
|         [MEMBER_TYPES.accessRequest]: 'access-request-action-buttons', | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ module ContainerRegistry | |||
|     include Gitlab::Utils::StrongMemoize | ||||
| 
 | ||||
|     attr_accessor :uri | ||||
|     attr_reader :options, :base_uri | ||||
| 
 | ||||
|     REGISTRY_VERSION_HEADER = 'gitlab-container-registry-version' | ||||
|     REGISTRY_FEATURES_HEADER = 'gitlab-container-registry-features' | ||||
|  |  | |||
|  | @ -25824,10 +25824,10 @@ msgstr "" | |||
| msgid "Members|Are you sure you want to remove \"%{groupName}\"?" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Members|Are you sure you want to remove %{usersName} from \"%{source}\"?" | ||||
| msgid "Members|Are you sure you want to remove %{userName} from \"%{group}\"?" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Members|Are you sure you want to remove this orphaned member from \"%{source}\"?" | ||||
| msgid "Members|Are you sure you want to remove this orphaned member from \"%{group}\"?" | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "Members|Are you sure you want to revoke the invitation for %{inviteEmail} to join \"%{source}\"" | ||||
|  |  | |||
|  | @ -22,8 +22,12 @@ module QA | |||
|           element :access_level_link | ||||
|         end | ||||
| 
 | ||||
|         view 'app/assets/javascripts/members/components/action_buttons/remove_member_button.vue' do | ||||
|           element :delete_member_button | ||||
|         view 'app/assets/javascripts/members/components/action_dropdowns/user_action_dropdown.vue' do | ||||
|           element :user_action_dropdown | ||||
|         end | ||||
| 
 | ||||
|         view 'app/assets/javascripts/members/components/action_dropdowns/remove_member_dropdown_item.vue' do | ||||
|           element :delete_member_dropdown_item | ||||
|         end | ||||
| 
 | ||||
|         view 'app/assets/javascripts/members/components/members_tabs.vue' do | ||||
|  | @ -41,7 +45,8 @@ module QA | |||
| 
 | ||||
|         def remove_member(username) | ||||
|           within_element(:member_row, text: username) do | ||||
|             click_element :delete_member_button | ||||
|             click_element :user_action_dropdown | ||||
|             click_element :delete_member_dropdown_item | ||||
|           end | ||||
| 
 | ||||
|           within_element(:remove_member_modal) do | ||||
|  |  | |||
|  | @ -269,9 +269,12 @@ RSpec.describe 'Admin Groups', feature_category: :subgroups do | |||
|         expect(page).to have_content('Developer') | ||||
|       end | ||||
| 
 | ||||
|       find_member_row(current_user).click_button(title: 'Leave') | ||||
|       show_actions_for_username(current_user) | ||||
|       click_button _('Leave group') | ||||
| 
 | ||||
|       accept_gl_confirm(button_text: 'Leave') | ||||
|       within_modal do | ||||
|         click_button _('Leave') | ||||
|       end | ||||
| 
 | ||||
|       wait_for_all_requests | ||||
| 
 | ||||
|  |  | |||
|  | @ -151,12 +151,11 @@ RSpec.describe "Admin::Projects", feature_category: :projects do | |||
| 
 | ||||
|       expect(find_member_row(current_user)).to have_content('Developer') | ||||
| 
 | ||||
|       page.within find_member_row(current_user) do | ||||
|         click_button 'Leave' | ||||
|       end | ||||
|       show_actions_for_username(current_user) | ||||
|       click_button _('Leave group') | ||||
| 
 | ||||
|       within_modal do | ||||
|         click_button('Leave') | ||||
|         click_button _('Leave') | ||||
|       end | ||||
| 
 | ||||
|       expect(page).to have_current_path(dashboard_projects_path, ignore_query: true, url: false) | ||||
|  |  | |||
|  | @ -50,12 +50,13 @@ RSpec.describe 'Groups > Members > Manage members', feature_category: :subgroups | |||
| 
 | ||||
|     # Open modal | ||||
|     page.within(second_row) do | ||||
|       click_button 'Remove member' | ||||
|       show_actions | ||||
|       click_button _('Remove member') | ||||
|     end | ||||
| 
 | ||||
|     within_modal do | ||||
|       expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests' | ||||
|       click_button('Remove member') | ||||
|       click_button _('Remove member') | ||||
|     end | ||||
| 
 | ||||
|     wait_for_requests | ||||
|  |  | |||
|  | @ -139,17 +139,15 @@ RSpec.describe 'Projects > Members > Manage members', :js, feature_category: :on | |||
| 
 | ||||
|       it 'can only remove non-Owner members' do | ||||
|         page.within find_member_row(project_owner) do | ||||
|           expect(page).not_to have_button('Remove member') | ||||
|           expect(page).not_to have_selector user_action_dropdown | ||||
|         end | ||||
| 
 | ||||
|         # Open modal | ||||
|         page.within find_member_row(project_developer) do | ||||
|           click_button 'Remove member' | ||||
|         end | ||||
|         show_actions_for_username(project_developer) | ||||
|         click_button _('Remove member') | ||||
| 
 | ||||
|         within_modal do | ||||
|           expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests' | ||||
|           click_button('Remove member') | ||||
|           click_button _('Remove member') | ||||
|         end | ||||
| 
 | ||||
|         wait_for_requests | ||||
|  | @ -163,18 +161,12 @@ RSpec.describe 'Projects > Members > Manage members', :js, feature_category: :on | |||
|       let(:current_user) { group_owner } | ||||
| 
 | ||||
|       it 'can remove any direct member' do | ||||
|         page.within find_member_row(project_owner) do | ||||
|           expect(page).to have_button('Remove member') | ||||
|         end | ||||
| 
 | ||||
|         # Open modal | ||||
|         page.within find_member_row(project_owner) do | ||||
|           click_button 'Remove member' | ||||
|         end | ||||
|         show_actions_for_username(project_owner) | ||||
|         click_button _('Remove member') | ||||
| 
 | ||||
|         within_modal do | ||||
|           expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests' | ||||
|           click_button('Remove member') | ||||
|           click_button _('Remove member') | ||||
|         end | ||||
| 
 | ||||
|         wait_for_requests | ||||
|  |  | |||
|  | @ -22,13 +22,12 @@ RSpec.describe 'Projects > Settings > User manages project members', feature_cat | |||
|   it 'cancels a team member', :js do | ||||
|     visit(project_project_members_path(project)) | ||||
| 
 | ||||
|     page.within find_member_row(user_dmitriy) do | ||||
|       click_button 'Remove member' | ||||
|     end | ||||
|     show_actions_for_username(user_dmitriy) | ||||
|     click_button _('Remove member') | ||||
| 
 | ||||
|     within_modal do | ||||
|       expect(page).to have_unchecked_field 'Also unassign this user from related issues and merge requests' | ||||
|       click_button('Remove member') | ||||
|       click_button _('Remove member') | ||||
|     end | ||||
| 
 | ||||
|     visit(project_project_members_path(project)) | ||||
|  |  | |||
|  | @ -1,59 +0,0 @@ | |||
| import { GlButton } from '@gitlab/ui'; | ||||
| import { shallowMount } from '@vue/test-utils'; | ||||
| import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; | ||||
| import LeaveButton from '~/members/components/action_buttons/leave_button.vue'; | ||||
| import LeaveModal from '~/members/components/modals/leave_modal.vue'; | ||||
| import { LEAVE_MODAL_ID } from '~/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.findComponent(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.findComponent(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); | ||||
|   }); | ||||
| }); | ||||
|  | @ -0,0 +1,53 @@ | |||
| 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 } from '../../mock_data'; | ||||
| 
 | ||||
| describe('LeaveGroupDropdownItem', () => { | ||||
|   let wrapper; | ||||
|   const text = 'dummy'; | ||||
| 
 | ||||
|   const createComponent = (propsData = {}) => { | ||||
|     wrapper = shallowMount(LeaveGroupDropdownItem, { | ||||
|       propsData: { | ||||
|         member, | ||||
|         ...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('member')).toEqual(member); | ||||
|   }); | ||||
| 
 | ||||
|   it('binds to the LeaveModal component', () => { | ||||
|     const binding = getBinding(findDropdownItem().element, 'gl-modal'); | ||||
| 
 | ||||
|     expect(binding.value).toBe(LEAVE_MODAL_ID); | ||||
|   }); | ||||
| }); | ||||
|  | @ -0,0 +1,74 @@ | |||
| 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 } 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, | ||||
|         memberType: 'GroupMember', | ||||
|         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); | ||||
|   }); | ||||
| }); | ||||
|  | @ -1,25 +1,31 @@ | |||
| import { shallowMount } from '@vue/test-utils'; | ||||
| import LeaveButton from '~/members/components/action_buttons/leave_button.vue'; | ||||
| import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue'; | ||||
| import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue'; | ||||
| 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 { parseUserDeletionObstacles } from '~/vue_shared/components/user_deletion_obstacles/utils'; | ||||
| import { member, orphanedMember } from '../../mock_data'; | ||||
| 
 | ||||
| describe('UserActionButtons', () => { | ||||
| describe('UserActionDropdown', () => { | ||||
|   let wrapper; | ||||
| 
 | ||||
|   const createComponent = (propsData = {}) => { | ||||
|     wrapper = shallowMount(UserActionButtons, { | ||||
|     wrapper = shallowMount(UserActionDropdown, { | ||||
|       propsData: { | ||||
|         member, | ||||
|         isCurrentUser: false, | ||||
|         isInvitedUser: false, | ||||
|         ...propsData, | ||||
|       }, | ||||
|       directives: { | ||||
|         GlTooltip: createMockDirective(), | ||||
|       }, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   const findRemoveMemberButton = () => wrapper.findComponent(RemoveMemberButton); | ||||
|   const findRemoveMemberDropdownItem = () => wrapper.findComponent(RemoveMemberDropdownItem); | ||||
| 
 | ||||
|   afterEach(() => { | ||||
|     wrapper.destroy(); | ||||
|  | @ -34,16 +40,30 @@ describe('UserActionButtons', () => { | |||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     it('renders remove member button', () => { | ||||
|       expect(findRemoveMemberButton().exists()).toBe(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(findRemoveMemberButton().props()).toEqual({ | ||||
|       expect(findRemoveMemberDropdownItem().props()).toEqual({ | ||||
|         memberId: member.id, | ||||
|         memberType: 'GroupMember', | ||||
|         message: `Are you sure you want to remove ${member.user.name} from "${member.source.fullName}"?`, | ||||
|         title: UserActionButtons.i18n.title, | ||||
|         modalMessage: sprintf( | ||||
|           I18N.confirmNormalUserRemoval, | ||||
|           { | ||||
|             userName: member.user.name, | ||||
|             group: member.source.fullName, | ||||
|           }, | ||||
|           false, | ||||
|         ), | ||||
|         isAccessRequest: false, | ||||
|         isInvite: false, | ||||
|         userDeletionObstacles: { | ||||
|  | @ -62,14 +82,14 @@ describe('UserActionButtons', () => { | |||
|           }, | ||||
|         }); | ||||
| 
 | ||||
|         expect(findRemoveMemberButton().props('message')).toBe( | ||||
|           `Are you sure you want to remove this orphaned member from "${orphanedMember.source.fullName}"?`, | ||||
|         expect(findRemoveMemberDropdownItem().props('modalMessage')).toBe( | ||||
|           sprintf(I18N.confirmOrphanedUserRemoval, { group: orphanedMember.source.fullName }), | ||||
|         ); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     describe('when member is the current user', () => { | ||||
|       it('renders leave button', () => { | ||||
|       it('renders leave dropdown with correct text', () => { | ||||
|         createComponent({ | ||||
|           isCurrentUser: true, | ||||
|           permissions: { | ||||
|  | @ -77,20 +97,22 @@ describe('UserActionButtons', () => { | |||
|           }, | ||||
|         }); | ||||
| 
 | ||||
|         expect(wrapper.findComponent(LeaveButton).exists()).toBe(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 button', () => { | ||||
|     it('does not render remove member dropdown', () => { | ||||
|       createComponent({ | ||||
|         permissions: { | ||||
|           canRemove: false, | ||||
|         }, | ||||
|       }); | ||||
| 
 | ||||
|       expect(findRemoveMemberButton().exists()).toBe(false); | ||||
|       expect(findRemoveMemberDropdownItem().exists()).toBe(false); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|  | @ -108,7 +130,7 @@ describe('UserActionButtons', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('sets member type correctly', () => { | ||||
|       expect(findRemoveMemberButton().props().memberType).toBe('GroupMember'); | ||||
|       expect(findRemoveMemberDropdownItem().props().memberType).toBe('GroupMember'); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|  | @ -126,7 +148,7 @@ describe('UserActionButtons', () => { | |||
|     }); | ||||
| 
 | ||||
|     it('sets member type correctly', () => { | ||||
|       expect(findRemoveMemberButton().props().memberType).toBe('ProjectMember'); | ||||
|       expect(findRemoveMemberDropdownItem().props().memberType).toBe('ProjectMember'); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  | @ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils'; | |||
| import AccessRequestActionButtons from '~/members/components/action_buttons/access_request_action_buttons.vue'; | ||||
| import GroupActionButtons from '~/members/components/action_buttons/group_action_buttons.vue'; | ||||
| import InviteActionButtons from '~/members/components/action_buttons/invite_action_buttons.vue'; | ||||
| import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue'; | ||||
| import UserActionDropdown from '~/members/components/action_dropdowns/user_action_dropdown.vue'; | ||||
| import MemberActionButtons from '~/members/components/table/member_action_buttons.vue'; | ||||
| import { MEMBER_TYPES } from '~/members/constants'; | ||||
| import { member as memberMock, group, invite, accessRequest } from '../../mock_data'; | ||||
|  | @ -29,7 +29,7 @@ describe('MemberActionButtons', () => { | |||
| 
 | ||||
|   it.each` | ||||
|     memberType                    | member           | expectedComponent             | expectedComponentName | ||||
|     ${MEMBER_TYPES.user}          | ${memberMock}    | ${UserActionButtons}          | ${'UserActionButtons'} | ||||
|     ${MEMBER_TYPES.user}          | ${memberMock}    | ${UserActionDropdown}         | ${'UserActionDropdown'} | ||||
|     ${MEMBER_TYPES.group}         | ${group}         | ${GroupActionButtons}         | ${'GroupActionButtons'} | ||||
|     ${MEMBER_TYPES.invite}        | ${invite}        | ${InviteActionButtons}        | ${'InviteActionButtons'} | ||||
|     ${MEMBER_TYPES.accessRequest} | ${accessRequest} | ${AccessRequestActionButtons} | ${'AccessRequestActionButtons'} | ||||
|  |  | |||
|  | @ -56,6 +56,22 @@ module Spec | |||
|               click_button 'Search' | ||||
|             end | ||||
|           end | ||||
| 
 | ||||
|           def user_action_dropdown | ||||
|             '[data-testid="user-action-dropdown"]' | ||||
|           end | ||||
| 
 | ||||
|           def show_actions | ||||
|             within user_action_dropdown do | ||||
|               find('button').click | ||||
|             end | ||||
|           end | ||||
| 
 | ||||
|           def show_actions_for_username(user) | ||||
|             within find_username_row(user) do | ||||
|               show_actions | ||||
|             end | ||||
|           end | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue