Skip to content
代码片段 群组 项目
未验证 提交 e83a8a2b 编辑于 作者: Justin Ho Tuan Duong's avatar Justin Ho Tuan Duong 提交者: GitLab
浏览文件

Merge branch '509542-move-organization-role-change-dropdown-o-drawer' into 'master'

No related branches found
No related tags found
无相关合并请求
...@@ -9,6 +9,15 @@ export const ORGANIZATION_ROOT_ROUTE_NAME = 'root'; ...@@ -9,6 +9,15 @@ export const ORGANIZATION_ROOT_ROUTE_NAME = 'root';
export const ACCESS_LEVEL_DEFAULT = 'default'; export const ACCESS_LEVEL_DEFAULT = 'default';
export const ACCESS_LEVEL_OWNER = 'owner'; export const ACCESS_LEVEL_OWNER = 'owner';
// Matches `app/graphql/types/organizations/organization_user_access_level_enum.rb
export const ACCESS_LEVEL_DEFAULT_STRING = 'DEFAULT';
export const ACCESS_LEVEL_OWNER_STRING = 'OWNER';
export const ACCESS_LEVEL_LABEL = {
[ACCESS_LEVEL_DEFAULT_STRING]: __('User'),
[ACCESS_LEVEL_OWNER_STRING]: __('Owner'),
};
export const FORM_FIELD_NAME = 'name'; export const FORM_FIELD_NAME = 'name';
export const FORM_FIELD_ID = 'id'; export const FORM_FIELD_ID = 'id';
export const FORM_FIELD_PATH = 'path'; export const FORM_FIELD_PATH = 'path';
......
<script>
import { GlCollapsibleListbox, GlDrawer, GlTooltipDirective } from '@gitlab/ui';
import UserAvatar from '~/vue_shared/components/users_table/user_avatar.vue';
import {
ACCESS_LEVEL_DEFAULT_STRING,
ACCESS_LEVEL_LABEL,
ACCESS_LEVEL_OWNER_STRING,
} from '~/organizations/shared/constants';
import { getContentWrapperHeight } from '~/lib/utils/dom_utils';
import { DRAWER_Z_INDEX } from '~/lib/utils/constants';
import { s__ } from '~/locale';
import organizationUserUpdateMutation from '~/organizations/users/graphql/mutations/organization_user_update.mutation.graphql';
import { createAlert } from '~/alert';
export default {
name: 'UserDetailsDrawer',
components: {
GlCollapsibleListbox,
GlDrawer,
UserAvatar,
},
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['paths'],
i18n: {
title: s__('Organization|Organization user details'),
roleListboxLabel: s__('Organization|Organization role'),
disabledRoleListboxTooltipText: s__('Organization|Organizations must have at least one owner.'),
errorMessage: s__(
'Organization|An error occurred updating the organization role. Please try again.',
),
successMessage: s__('Organization|Organization role was updated successfully.'),
},
roleListboxItems: [
{
text: ACCESS_LEVEL_LABEL[ACCESS_LEVEL_DEFAULT_STRING],
value: ACCESS_LEVEL_DEFAULT_STRING,
},
{
text: ACCESS_LEVEL_LABEL[ACCESS_LEVEL_OWNER_STRING],
value: ACCESS_LEVEL_OWNER_STRING,
},
],
props: {
user: {
type: Object,
required: false,
default: null,
},
},
data() {
return {
initialAccessLevel: this.user?.accessLevel.stringValue,
selectedAccessLevel: this.user?.accessLevel.stringValue,
loading: false,
};
},
computed: {
drawerHeaderHeight() {
return getContentWrapperHeight();
},
roleListboxDisabled() {
return this.user?.isLastOwner;
},
roleListboxTooltip() {
return this.roleListboxDisabled ? this.$options.i18n.disabledRoleListboxTooltipText : null;
},
},
watch: {
user(value) {
this.initialAccessLevel = value?.accessLevel.stringValue;
this.selectedAccessLevel = value?.accessLevel.stringValue;
},
},
methods: {
setLoading(value) {
this.loading = value;
this.$emit('loading', value);
},
onUpdateSuccess() {
this.initialAccessLevel = this.selectedAccessLevel;
this.$toast.show(this.$options.i18n.successMessage);
this.$emit('role-change');
},
async onRoleSelect() {
this.setLoading(true);
try {
const {
data: {
organizationUserUpdate: { errors },
},
} = await this.$apollo.mutate({
mutation: organizationUserUpdateMutation,
variables: {
input: {
id: this.user.gid,
accessLevel: this.selectedAccessLevel,
},
},
});
if (errors.length) {
createAlert({ message: errors[0] });
return;
}
this.onUpdateSuccess();
} catch (error) {
createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
} finally {
this.setLoading(false);
}
},
close() {
this.$emit('close');
},
},
DRAWER_Z_INDEX,
};
</script>
<template>
<gl-drawer
v-if="user"
open
header-sticky
:header-height="drawerHeaderHeight"
:z-index="$options.DRAWER_Z_INDEX"
@close="close"
>
<template #title>
<h4 class="gl-m-0">{{ $options.i18n.title }}</h4>
</template>
<template #default>
<div>
<user-avatar :user="user" :admin-user-path="paths.adminUser" />
</div>
<div>
<h5>{{ $options.i18n.roleListboxLabel }}</h5>
<div
v-gl-tooltip="{ disabled: !roleListboxTooltip, title: roleListboxTooltip }"
class="gl-rounded-base focus:gl-focus"
:tabindex="roleListboxDisabled && 0"
>
<gl-collapsible-listbox
v-model="selectedAccessLevel"
block
toggle-class="gl-form-input-xl"
:disabled="roleListboxDisabled"
:items="$options.roleListboxItems"
:loading="loading"
@select="onRoleSelect"
/>
</div>
</div>
</template>
</gl-drawer>
</template>
<script> <script>
import { import { GlButton, GlLoadingIcon, GlKeysetPagination, GlTooltipDirective } from '@gitlab/ui';
GlLoadingIcon,
GlKeysetPagination,
GlCollapsibleListbox,
GlTooltipDirective,
} from '@gitlab/ui';
import { createAlert } from '~/alert';
import UsersTable from '~/vue_shared/components/users_table/users_table.vue'; import UsersTable from '~/vue_shared/components/users_table/users_table.vue';
import UserDetailsDrawer from '~/organizations/users/components/user_details_drawer.vue';
import { import {
FIELD_NAME, FIELD_NAME,
FIELD_ORGANIZATION_ROLE, FIELD_ORGANIZATION_ROLE,
FIELD_CREATED_AT, FIELD_CREATED_AT,
FIELD_LAST_ACTIVITY_ON, FIELD_LAST_ACTIVITY_ON,
} from '~/vue_shared/components/users_table/constants'; } from '~/vue_shared/components/users_table/constants';
import { ACCESS_LEVEL_DEFAULT, ACCESS_LEVEL_OWNER } from '~/organizations/shared/constants'; import {
ACCESS_LEVEL_DEFAULT,
ACCESS_LEVEL_OWNER,
ACCESS_LEVEL_LABEL,
} from '~/organizations/shared/constants';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import organizationUserUpdateMutation from '../graphql/mutations/organization_user_update.mutation.graphql';
export default { export default {
name: 'UsersView', name: 'UsersView',
...@@ -30,10 +28,11 @@ export default { ...@@ -30,10 +28,11 @@ export default {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
components: { components: {
GlButton,
GlLoadingIcon, GlLoadingIcon,
GlKeysetPagination, GlKeysetPagination,
GlCollapsibleListbox,
UsersTable, UsersTable,
UserDetailsDrawer,
}, },
inject: ['paths'], inject: ['paths'],
roleListboxItems: [ roleListboxItems: [
...@@ -70,52 +69,22 @@ export default { ...@@ -70,52 +69,22 @@ export default {
}, },
data() { data() {
return { return {
roleListboxLoadingStates: [], userDetailsDrawerActiveUser: null,
userDetailsDrawerLoading: false,
}; };
}, },
methods: { methods: {
async onRoleSelect(accessLevel, user) { setUserDetailsDrawerActiveUser(user) {
this.roleListboxLoadingStates.push(user.gid); this.userDetailsDrawerActiveUser = user;
try {
const {
data: {
organizationUserUpdate: { errors },
},
} = await this.$apollo.mutate({
mutation: organizationUserUpdateMutation,
variables: {
input: {
id: user.gid,
accessLevel,
},
},
});
if (errors.length) {
createAlert({ message: errors[0] });
return;
}
this.$toast.show(this.$options.i18n.successMessage);
this.$emit('role-change');
} catch (error) {
createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
} finally {
this.roleListboxLoadingStates.splice(this.roleListboxLoadingStates.indexOf(user.gid), 1);
}
}, },
roleListboxItemText(accessLevel) { setUserDetailsDrawerLoading(loading) {
return this.$options.roleListboxItems.find((item) => item.value === accessLevel).text; this.userDetailsDrawerLoading = loading;
}, },
isRoleListboxDisabled(user) { onRoleChange() {
return user.isLastOwner; this.$emit('role-change');
}, },
roleListboxTooltip(user) { userAccessLevelLabel(user) {
return this.isRoleListboxDisabled(user) return ACCESS_LEVEL_LABEL[user.accessLevel.stringValue];
? this.$options.i18n.disabledRoleListboxTooltipText
: null;
}, },
}, },
}; };
...@@ -132,26 +101,25 @@ export default { ...@@ -132,26 +101,25 @@ export default {
:column-widths="$options.usersTable.columnWidths" :column-widths="$options.usersTable.columnWidths"
> >
<template #organization-role="{ user }"> <template #organization-role="{ user }">
<div <gl-button
v-gl-tooltip="{ disabled: !roleListboxTooltip(user), title: roleListboxTooltip(user) }" class="gl-block"
class="gl-rounded-base focus:gl-focus" variant="link"
:tabindex="isRoleListboxDisabled(user) && 0" :disabled="userDetailsDrawerLoading"
@click="setUserDetailsDrawerActiveUser(user)"
> >
<gl-collapsible-listbox {{ userAccessLevelLabel(user) }}
:disabled="isRoleListboxDisabled(user)" </gl-button>
:selected="user.accessLevel.stringValue"
block
toggle-class="gl-form-input-xl"
:items="$options.roleListboxItems"
:loading="roleListboxLoadingStates.includes(user.gid)"
@select="onRoleSelect($event, user)"
/>
</div>
</template> </template>
</users-table> </users-table>
<div class="gl-flex gl-justify-center"> <div class="gl-flex gl-justify-center">
<gl-keyset-pagination v-bind="pageInfo" @prev="$emit('prev')" @next="$emit('next')" /> <gl-keyset-pagination v-bind="pageInfo" @prev="$emit('prev')" @next="$emit('next')" />
</div> </div>
</template> </template>
<user-details-drawer
:user="userDetailsDrawerActiveUser"
@loading="setUserDetailsDrawerLoading"
@close="setUserDetailsDrawerActiveUser(null)"
@role-change="onRoleChange"
/>
</div> </div>
</template> </template>
...@@ -38998,6 +38998,9 @@ msgstr "" ...@@ -38998,6 +38998,9 @@ msgstr ""
msgid "Organization|Organization successfully created." msgid "Organization|Organization successfully created."
msgstr "" msgstr ""
   
msgid "Organization|Organization user details"
msgstr ""
msgid "Organization|Organization visibility level" msgid "Organization|Organization visibility level"
msgstr "" msgstr ""
   
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlCollapsibleListbox, GlDrawer } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import organizationUserUpdateResponseWithErrors from 'test_fixtures/graphql/organizations/organization_user_update.mutation.graphql_with_errors.json';
import organizationUserUpdateResponse from 'test_fixtures/graphql/organizations/organization_user_update.mutation.graphql.json';
import organizationUserUpdateMutation from '~/organizations/users/graphql/mutations/organization_user_update.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { pageInfoMultiplePages } from 'jest/organizations/mock_data';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import UserDetailsDrawer from '~/organizations/users/components/user_details_drawer.vue';
import UserAvatar from '~/vue_shared/components/users_table/user_avatar.vue';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
import {
ACCESS_LEVEL_DEFAULT_STRING,
ACCESS_LEVEL_OWNER_STRING,
} from '~/organizations/shared/constants';
import { MOCK_PATHS, MOCK_USERS_FORMATTED } from '../mock_data';
Vue.use(VueApollo);
jest.mock('~/alert');
describe('UserDetailsDrawer', () => {
let wrapper;
let mockApollo;
const mockUser = MOCK_USERS_FORMATTED[0];
const successfulResponseHandler = jest.fn().mockResolvedValue(organizationUserUpdateResponse);
const mockToastShow = jest.fn();
const findGlDrawer = () => wrapper.findComponent(GlDrawer);
const findGlCollapsibleListbox = () => wrapper.findComponent(GlCollapsibleListbox);
const findUserAvatar = () => wrapper.findComponent(UserAvatar);
const selectRole = (value) => findGlCollapsibleListbox().vm.$emit('select', value);
const createComponent = ({ props = {}, handler = successfulResponseHandler } = {}) => {
mockApollo = createMockApollo([[organizationUserUpdateMutation, handler]]);
wrapper = shallowMount(UserDetailsDrawer, {
propsData: {
user: mockUser,
pageInfo: pageInfoMultiplePages,
...props,
},
provide: {
paths: MOCK_PATHS,
},
apolloProvider: mockApollo,
mocks: {
$toast: {
show: mockToastShow,
},
},
directives: {
GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
afterEach(() => {
mockApollo = null;
});
describe('when there is no active user', () => {
it('does not render drawer', () => {
createComponent({ props: { user: null } });
expect(findGlDrawer().exists()).toBe(false);
});
});
describe('when there is an active user', () => {
beforeEach(() => {
createComponent();
});
it('renders open drawer', () => {
expect(findGlDrawer().exists()).toBe(true);
expect(findGlDrawer().props('open')).toBe(true);
});
it('renders drawer title', () => {
expect(findGlDrawer().text()).toContain('Organization user details');
});
it('renders user avatar', () => {
expect(findUserAvatar().props()).toMatchObject({
user: mockUser,
adminUserPath: MOCK_PATHS.adminUser,
});
});
it('renders role listbox label', () => {
expect(findGlDrawer().text()).toContain('Organization role');
});
it('renders role listbox correct props', () => {
expect(findGlCollapsibleListbox().props()).toMatchObject({
items: UserDetailsDrawer.roleListboxItems,
selected: mockUser.accessLevel.stringValue,
disabled: false,
});
});
it('does not render disabled listbox tooltip', () => {
const tooltipContainer = findGlCollapsibleListbox().element.parentNode;
const tooltip = getBinding(tooltipContainer, 'gl-tooltip');
expect(tooltip.value.disabled).toBe(true);
expect(tooltipContainer.getAttribute('tabindex')).toBe(null);
});
describe('when user is last owner of organization', () => {
beforeEach(() => {
createComponent({
props: {
loading: false,
user: { ...mockUser, isLastOwner: true },
},
});
});
it('renders listbox as disabled', () => {
expect(findGlCollapsibleListbox().props('disabled')).toBe(true);
});
it('renders tooltip and makes element focusable', () => {
const tooltipContainer = findGlCollapsibleListbox().element.parentNode;
const tooltip = getBinding(tooltipContainer, 'gl-tooltip');
expect(tooltip.value).toEqual({
title: 'Organizations must have at least one owner.',
disabled: false,
});
expect(tooltipContainer.getAttribute('tabindex')).toBe('0');
});
});
describe('when selecting new role', () => {
beforeEach(() => {
createComponent();
selectRole(ACCESS_LEVEL_DEFAULT_STRING);
});
it('calls GraphQL mutation with correct variables', () => {
expect(successfulResponseHandler).toHaveBeenCalledWith({
input: {
id: mockUser.gid,
accessLevel: ACCESS_LEVEL_DEFAULT_STRING,
},
});
});
it('sets listbox to loading', () => {
expect(findGlCollapsibleListbox().props('loading')).toBe(true);
});
it('emits loading start event', () => {
expect(wrapper.emitted('loading')[0]).toEqual([true]);
});
describe('when role update is successful', () => {
beforeEach(async () => {
await waitForPromises();
});
it('shows toast when GraphQL mutation is successful', () => {
expect(mockToastShow).toHaveBeenCalledWith('Organization role was updated successfully.');
});
it('emits role-change event', () => {
expect(wrapper.emitted('role-change')).toHaveLength(1);
});
it('emits loading end event', () => {
expect(wrapper.emitted('loading')[1]).toEqual([false]);
});
});
describe('when role update has a validation error', () => {
beforeEach(async () => {
const errorResponseHandler = jest
.fn()
.mockResolvedValue(organizationUserUpdateResponseWithErrors);
createComponent({
handler: errorResponseHandler,
props: { user: { ...mockUser, accessLevel: ACCESS_LEVEL_OWNER_STRING } },
});
selectRole(ACCESS_LEVEL_DEFAULT_STRING);
await waitForPromises();
});
it('creates an alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'You cannot change the access of the last owner from the organization',
});
});
});
describe('when role update has a network error', () => {
const error = new Error();
beforeEach(async () => {
const errorResponseHandler = jest.fn().mockRejectedValue(error);
createComponent({
handler: errorResponseHandler,
props: { user: { ...mockUser, accessLevel: ACCESS_LEVEL_OWNER_STRING } },
});
selectRole(ACCESS_LEVEL_DEFAULT_STRING);
await waitForPromises();
});
it('creates an alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'An error occurred updating the organization role. Please try again.',
error,
captureError: true,
});
});
});
});
describe('when drawer is closed', () => {
it('emits close event', () => {
findGlDrawer().vm.$emit('close');
expect(wrapper.emitted('close')).toHaveLength(1);
});
});
});
});
import VueApollo from 'vue-apollo'; import { GlLoadingIcon, GlKeysetPagination, GlButton } from '@gitlab/ui';
import Vue, { nextTick } from 'vue'; import { shallowMount, mount } from '@vue/test-utils';
import { GlLoadingIcon, GlKeysetPagination, GlCollapsibleListbox } from '@gitlab/ui'; import UserDetailsDrawer from '~/organizations/users/components/user_details_drawer.vue';
import organizationUserUpdateResponse from 'test_fixtures/graphql/organizations/organization_user_update.mutation.graphql.json';
import organizationUserUpdateResponseWithErrors from 'test_fixtures/graphql/organizations/organization_user_update.mutation.graphql_with_errors.json';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import UsersView from '~/organizations/users/components/users_view.vue'; import UsersView from '~/organizations/users/components/users_view.vue';
import UsersTable from '~/vue_shared/components/users_table/users_table.vue'; import UsersTable from '~/vue_shared/components/users_table/users_table.vue';
import organizationUserUpdateMutation from '~/organizations/users/graphql/mutations/organization_user_update.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { createAlert } from '~/alert';
import waitForPromises from 'helpers/wait_for_promises';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { pageInfoMultiplePages } from 'jest/organizations/mock_data'; import { pageInfoMultiplePages } from 'jest/organizations/mock_data';
import { ACCESS_LEVEL_LABEL } from '~/organizations/shared/constants';
import { MOCK_PATHS, MOCK_USERS_FORMATTED } from '../mock_data'; import { MOCK_PATHS, MOCK_USERS_FORMATTED } from '../mock_data';
Vue.use(VueApollo);
jest.mock('~/alert');
describe('UsersView', () => { describe('UsersView', () => {
let wrapper; let wrapper;
let mockApollo;
const successfulResponseHandler = jest.fn().mockResolvedValue(organizationUserUpdateResponse);
const mockToastShow = jest.fn();
const createComponent = ({ propsData = {}, handler = successfulResponseHandler } = {}) => { const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => {
mockApollo = createMockApollo([[organizationUserUpdateMutation, handler]]); wrapper = mountFn(UsersView, {
wrapper = mountExtended(UsersView, {
propsData: { propsData: {
loading: false, loading: false,
users: MOCK_USERS_FORMATTED, users: MOCK_USERS_FORMATTED,
pageInfo: pageInfoMultiplePages, pageInfo: pageInfoMultiplePages,
...propsData, ...props,
}, },
provide: { provide: {
paths: MOCK_PATHS, paths: MOCK_PATHS,
}, },
apolloProvider: mockApollo,
mocks: {
$toast: {
show: mockToastShow,
},
},
directives: {
GlTooltip: createMockDirective('gl-tooltip'),
},
}); });
}; };
const findGlLoading = () => wrapper.findComponent(GlLoadingIcon); const findGlLoading = () => wrapper.findComponent(GlLoadingIcon);
const findUsersTable = () => wrapper.findComponent(UsersTable); const findGlButton = () => wrapper.findComponent(GlButton);
const findGlKeysetPagination = () => wrapper.findComponent(GlKeysetPagination); const findGlKeysetPagination = () => wrapper.findComponent(GlKeysetPagination);
const findListbox = () => wrapper.findComponent(GlCollapsibleListbox); const findUserDetailsDrawer = () => wrapper.findComponent(UserDetailsDrawer);
const listboxSelectOwner = () => findListbox().vm.$emit('select', 'OWNER'); const findUsersTable = () => wrapper.findComponent(UsersTable);
afterEach(() => {
mockApollo = null;
});
describe.each` describe.each`
description | loading | usersData description | loading | usersData
...@@ -67,7 +37,7 @@ describe('UsersView', () => { ...@@ -67,7 +37,7 @@ describe('UsersView', () => {
${'when not loading and has no users'} | ${false} | ${[]} ${'when not loading and has no users'} | ${false} | ${[]}
`('$description', ({ loading, usersData }) => { `('$description', ({ loading, usersData }) => {
beforeEach(() => { beforeEach(() => {
createComponent({ propsData: { loading, users: usersData } }); createComponent({ props: { loading, users: usersData } });
}); });
it(`does ${loading ? '' : 'not '}render loading icon`, () => { it(`does ${loading ? '' : 'not '}render loading icon`, () => {
...@@ -102,142 +72,49 @@ describe('UsersView', () => { ...@@ -102,142 +72,49 @@ describe('UsersView', () => {
}); });
describe('Organization role', () => { describe('Organization role', () => {
it('renders listbox with role options', () => { const mockUser = MOCK_USERS_FORMATTED[0];
createComponent();
expect(wrapper.findComponent(GlCollapsibleListbox).props()).toMatchObject({ beforeEach(() => {
items: [ createComponent({ mountFn: mount });
{
text: 'User',
value: 'DEFAULT',
},
{
text: 'Owner',
value: 'OWNER',
},
],
selected: MOCK_USERS_FORMATTED[0].accessLevel.stringValue,
disabled: false,
});
}); });
it('does not render tooltip', () => { it("render an organization role button with the user's role", () => {
createComponent(); const userAccessLevel = mockUser.accessLevel.stringValue;
const tooltipContainer = findListbox().element.parentNode;
const tooltip = getBinding(tooltipContainer, 'gl-tooltip');
expect(tooltip.value.disabled).toBe(true); expect(findGlButton().text()).toBe(ACCESS_LEVEL_LABEL[userAccessLevel]);
expect(tooltipContainer.getAttribute('tabindex')).toBe(null);
}); });
describe('when user is last owner of organization', () => { describe('when the organization role button is clicked', () => {
const [firstUser] = MOCK_USERS_FORMATTED; beforeEach(async () => {
await findGlButton().trigger('click');
beforeEach(() => {
createComponent({
propsData: {
loading: false,
users: [{ ...firstUser, isLastOwner: true }],
},
});
}); });
it('renders listbox as disabled', () => { it("sets the user details drawer's active user to selected user", () => {
expect(findListbox().props('disabled')).toBe(true); expect(findUserDetailsDrawer().props('user')).toBe(mockUser);
}); });
it('renders tooltip and makes element focusable', () => { describe('when the user details drawer is closed', () => {
const tooltipContainer = findListbox().element.parentNode; it("reset the user details drawer's active user to null", async () => {
const tooltip = getBinding(tooltipContainer, 'gl-tooltip'); await findUserDetailsDrawer().vm.$emit('close');
expect(tooltip.value).toEqual({ expect(findGlButton().props('user')).toBeUndefined();
title: 'Organizations must have at least one owner.',
disabled: false,
}); });
expect(tooltipContainer.getAttribute('tabindex')).toBe('0');
}); });
}); });
describe('when role is changed', () => { describe('when the user details drawer is loading', () => {
afterEach(async () => { it('disable the organization role button', async () => {
// clean up any unresolved GraphQL mutations await findUserDetailsDrawer().vm.$emit('loading', true);
await waitForPromises();
});
it('calls GraphQL mutation with correct variables', () => { expect(findGlButton().props('disabled')).toBe(true);
createComponent();
listboxSelectOwner();
expect(successfulResponseHandler).toHaveBeenCalledWith({
input: {
id: MOCK_USERS_FORMATTED[0].gid,
accessLevel: 'OWNER',
},
});
}); });
});
it('shows dropdown as loading while waiting for GraphQL mutation', async () => { describe('when the user role has been changed', () => {
createComponent(); it('emits role-change event', async () => {
listboxSelectOwner(); await findUserDetailsDrawer().vm.$emit('role-change');
await nextTick();
expect(findListbox().props('loading')).toBe(true);
});
it('shows toast when GraphQL mutation is successful', async () => {
createComponent();
listboxSelectOwner();
await waitForPromises();
expect(mockToastShow).toHaveBeenCalledWith('Organization role was updated successfully.');
});
it('emits role-change event when GraphQL mutation is successful', async () => {
createComponent();
listboxSelectOwner();
await waitForPromises();
expect(wrapper.emitted('role-change')).toEqual([[]]);
});
it('calls createAlert when GraphQL mutation has validation error', async () => {
const errorResponseHandler = jest
.fn()
.mockResolvedValue(organizationUserUpdateResponseWithErrors);
createComponent({
handler: errorResponseHandler,
});
listboxSelectOwner();
await waitForPromises();
expect(createAlert).toHaveBeenCalledWith({
message: 'You cannot change the access of the last owner from the organization',
});
});
it('calls createAlert when GraphQL mutation has network error', async () => {
const error = new Error();
const errorResponseHandler = jest.fn().mockRejectedValue(error);
createComponent({
handler: errorResponseHandler,
});
listboxSelectOwner();
await waitForPromises();
expect(createAlert).toHaveBeenCalledWith({ expect(wrapper.emitted('role-change')).toHaveLength(1);
message: 'An error occurred updating the organization role. Please try again.',
error,
captureError: true,
});
}); });
}); });
}); });
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册