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