summaryrefslogtreecommitdiff
path: root/spec/frontend/admin/users/components
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-11-17 11:33:21 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-11-17 11:33:21 +0000
commit7021455bd1ed7b125c55eb1b33c5a01f2bc55ee0 (patch)
tree5bdc2229f5198d516781f8d24eace62fc7e589e9 /spec/frontend/admin/users/components
parent185b095e93520f96e9cfc31d9c3e69b498cdab7c (diff)
downloadgitlab-ce-15.6.0-rc42.tar.gz
Add latest changes from gitlab-org/gitlab@15-6-stable-eev15.6.0-rc42
Diffstat (limited to 'spec/frontend/admin/users/components')
-rw-r--r--spec/frontend/admin/users/components/actions/actions_spec.js57
-rw-r--r--spec/frontend/admin/users/components/actions/delete_with_contributions_spec.js107
-rw-r--r--spec/frontend/admin/users/components/associations/__snapshots__/associations_list_item_spec.js.snap3
-rw-r--r--spec/frontend/admin/users/components/associations/__snapshots__/associations_list_spec.js.snap34
-rw-r--r--spec/frontend/admin/users/components/associations/associations_list_item_spec.js25
-rw-r--r--spec/frontend/admin/users/components/associations/associations_list_spec.js78
-rw-r--r--spec/frontend/admin/users/components/modals/delete_user_modal_spec.js22
-rw-r--r--spec/frontend/admin/users/components/user_actions_spec.js7
8 files changed, 299 insertions, 34 deletions
diff --git a/spec/frontend/admin/users/components/actions/actions_spec.js b/spec/frontend/admin/users/components/actions/actions_spec.js
index 4967753b91c..8e9652332c1 100644
--- a/spec/frontend/admin/users/components/actions/actions_spec.js
+++ b/spec/frontend/admin/users/components/actions/actions_spec.js
@@ -1,13 +1,13 @@
import { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Actions from '~/admin/users/components/actions';
+import Delete from '~/admin/users/components/actions/delete.vue';
import eventHub, {
EVENT_OPEN_DELETE_USER_MODAL,
} from '~/admin/users/components/modals/delete_user_modal_event_hub';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
-import { OBSTACLE_TYPES } from '~/vue_shared/components/user_deletion_obstacles/constants';
-import { CONFIRMATION_ACTIONS, DELETE_ACTIONS } from '../../constants';
-import { paths } from '../../mock_data';
+import { CONFIRMATION_ACTIONS } from '../../constants';
+import { paths, userDeletionObstacles } from '../../mock_data';
describe('Action components', () => {
let wrapper;
@@ -41,40 +41,33 @@ describe('Action components', () => {
});
});
- describe('DELETE_ACTION_COMPONENTS', () => {
+ describe('DELETE', () => {
beforeEach(() => {
jest.spyOn(eventHub, '$emit').mockImplementation();
});
- const userDeletionObstacles = [
- { name: 'schedule1', type: OBSTACLE_TYPES.oncallSchedules },
- { name: 'policy1', type: OBSTACLE_TYPES.escalationPolicies },
- ];
-
- it.each(DELETE_ACTIONS)(
- 'renders a dropdown item that opens the delete user modal when clicked for "%s"',
- async (action) => {
- initComponent({
- component: Actions[capitalizeFirstCharacter(action)],
- props: {
- username: 'John Doe',
- paths,
- userDeletionObstacles,
- },
- });
+ it('renders a dropdown item that opens the delete user modal when Delete is clicked', async () => {
+ initComponent({
+ component: Delete,
+ props: {
+ username: 'John Doe',
+ userId: 1,
+ paths,
+ userDeletionObstacles,
+ },
+ });
- await findDropdownItem().vm.$emit('click');
+ await findDropdownItem().vm.$emit('click');
- expect(eventHub.$emit).toHaveBeenCalledWith(
- EVENT_OPEN_DELETE_USER_MODAL,
- expect.objectContaining({
- username: 'John Doe',
- blockPath: paths.block,
- deletePath: paths[action],
- userDeletionObstacles,
- }),
- );
- },
- );
+ expect(eventHub.$emit).toHaveBeenCalledWith(
+ EVENT_OPEN_DELETE_USER_MODAL,
+ expect.objectContaining({
+ username: 'John Doe',
+ blockPath: paths.block,
+ deletePath: paths.delete,
+ userDeletionObstacles,
+ }),
+ );
+ });
});
});
diff --git a/spec/frontend/admin/users/components/actions/delete_with_contributions_spec.js b/spec/frontend/admin/users/components/actions/delete_with_contributions_spec.js
new file mode 100644
index 00000000000..64a88aab2c2
--- /dev/null
+++ b/spec/frontend/admin/users/components/actions/delete_with_contributions_spec.js
@@ -0,0 +1,107 @@
+import { GlLoadingIcon } from '@gitlab/ui';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import DeleteWithContributions from '~/admin/users/components/actions/delete_with_contributions.vue';
+import eventHub, {
+ EVENT_OPEN_DELETE_USER_MODAL,
+} from '~/admin/users/components/modals/delete_user_modal_event_hub';
+import { associationsCount } from '~/api/user_api';
+import {
+ paths,
+ associationsCount as associationsCountData,
+ userDeletionObstacles,
+} from '../../mock_data';
+
+jest.mock('~/admin/users/components/modals/delete_user_modal_event_hub', () => ({
+ ...jest.requireActual('~/admin/users/components/modals/delete_user_modal_event_hub'),
+ __esModule: true,
+ default: {
+ $emit: jest.fn(),
+ },
+}));
+
+jest.mock('~/api/user_api', () => ({
+ associationsCount: jest.fn(),
+}));
+
+describe('DeleteWithContributions', () => {
+ let wrapper;
+
+ const defaultPropsData = {
+ username: 'John Doe',
+ userId: 1,
+ paths,
+ userDeletionObstacles,
+ };
+
+ const createComponent = () => {
+ wrapper = mountExtended(DeleteWithContributions, { propsData: defaultPropsData });
+ };
+
+ describe('when action is clicked', () => {
+ describe('when API request is loading', () => {
+ beforeEach(() => {
+ associationsCount.mockReturnValueOnce(new Promise(() => {}));
+
+ createComponent();
+ });
+
+ it('displays loading icon and disables button', async () => {
+ await wrapper.trigger('click');
+
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findByRole('menuitem').attributes()).toMatchObject({
+ disabled: 'disabled',
+ 'aria-busy': 'true',
+ });
+ });
+ });
+
+ describe('when API request is successful', () => {
+ beforeEach(() => {
+ associationsCount.mockResolvedValueOnce({
+ data: associationsCountData,
+ });
+
+ createComponent();
+ });
+
+ it('emits event with association counts', async () => {
+ await wrapper.trigger('click');
+ await waitForPromises();
+
+ expect(associationsCount).toHaveBeenCalledWith(defaultPropsData.userId);
+ expect(eventHub.$emit).toHaveBeenCalledWith(
+ EVENT_OPEN_DELETE_USER_MODAL,
+ expect.objectContaining({
+ associationsCount: associationsCountData,
+ username: defaultPropsData.username,
+ blockPath: paths.block,
+ deletePath: paths.deleteWithContributions,
+ userDeletionObstacles,
+ }),
+ );
+ });
+ });
+
+ describe('when API request is not successful', () => {
+ beforeEach(() => {
+ associationsCount.mockRejectedValueOnce();
+
+ createComponent();
+ });
+
+ it('emits event with error', async () => {
+ await wrapper.trigger('click');
+ await waitForPromises();
+
+ expect(eventHub.$emit).toHaveBeenCalledWith(
+ EVENT_OPEN_DELETE_USER_MODAL,
+ expect.objectContaining({
+ associationsCount: new Error(),
+ }),
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/admin/users/components/associations/__snapshots__/associations_list_item_spec.js.snap b/spec/frontend/admin/users/components/associations/__snapshots__/associations_list_item_spec.js.snap
new file mode 100644
index 00000000000..4237685e45c
--- /dev/null
+++ b/spec/frontend/admin/users/components/associations/__snapshots__/associations_list_item_spec.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AssociationsListItem renders interpolated message in a \`li\` element 1`] = `"<li><strong>5</strong> groups</li>"`;
diff --git a/spec/frontend/admin/users/components/associations/__snapshots__/associations_list_spec.js.snap b/spec/frontend/admin/users/components/associations/__snapshots__/associations_list_spec.js.snap
new file mode 100644
index 00000000000..dc98d367af7
--- /dev/null
+++ b/spec/frontend/admin/users/components/associations/__snapshots__/associations_list_spec.js.snap
@@ -0,0 +1,34 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AssociationsList when counts are 0 does not render items 1`] = `""`;
+
+exports[`AssociationsList when counts are plural renders plural counts 1`] = `
+"<ul class=\\"gl-mb-5\\">
+ <li><strong>2</strong> groups</li>
+ <li><strong>3</strong> projects</li>
+ <li><strong>4</strong> issues</li>
+ <li><strong>5</strong> merge requests</li>
+</ul>"
+`;
+
+exports[`AssociationsList when counts are singular renders singular counts 1`] = `
+"<ul class=\\"gl-mb-5\\">
+ <li><strong>1</strong> group</li>
+ <li><strong>1</strong> project</li>
+ <li><strong>1</strong> issue</li>
+ <li><strong>1</strong> merge request</li>
+</ul>"
+`;
+
+exports[`AssociationsList when there is an error displays an alert 1`] = `
+"<div class=\\"gl-mb-5 gl-alert gl-alert-not-dismissible gl-alert-danger\\"><svg data-testid=\\"error-icon\\" role=\\"img\\" aria-hidden=\\"true\\" class=\\"gl-icon s16 gl-alert-icon gl-alert-icon-no-title\\">
+ <use href=\\"#error\\"></use>
+ </svg>
+ <div role=\\"alert\\" aria-live=\\"assertive\\" class=\\"gl-alert-content\\">
+ <!---->
+ <div class=\\"gl-alert-body\\">An error occurred while fetching this user's contributions, and the request cannot return the number of issues, merge requests, groups, and projects linked to this user. If you proceed with deleting the user, all their contributions will still be deleted.</div>
+ <!---->
+ </div>
+ <!---->
+</div>"
+`;
diff --git a/spec/frontend/admin/users/components/associations/associations_list_item_spec.js b/spec/frontend/admin/users/components/associations/associations_list_item_spec.js
new file mode 100644
index 00000000000..5126df12c24
--- /dev/null
+++ b/spec/frontend/admin/users/components/associations/associations_list_item_spec.js
@@ -0,0 +1,25 @@
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import AssociationsListItem from '~/admin/users/components/associations/associations_list_item.vue';
+import { n__ } from '~/locale';
+
+describe('AssociationsListItem', () => {
+ let wrapper;
+ const count = 5;
+
+ const createComponent = () => {
+ wrapper = mountExtended(AssociationsListItem, {
+ propsData: {
+ message: n__('%{count} group', '%{count} groups', count),
+ count,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders interpolated message in a `li` element', () => {
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+});
diff --git a/spec/frontend/admin/users/components/associations/associations_list_spec.js b/spec/frontend/admin/users/components/associations/associations_list_spec.js
new file mode 100644
index 00000000000..d77a645111f
--- /dev/null
+++ b/spec/frontend/admin/users/components/associations/associations_list_spec.js
@@ -0,0 +1,78 @@
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import AssociationsList from '~/admin/users/components/associations/associations_list.vue';
+
+describe('AssociationsList', () => {
+ let wrapper;
+
+ const defaultPropsData = {
+ associationsCount: {
+ groups_count: 1,
+ projects_count: 1,
+ issues_count: 1,
+ merge_requests_count: 1,
+ },
+ };
+
+ const createComponent = ({ propsData = {} } = {}) => {
+ wrapper = mountExtended(AssociationsList, {
+ propsData: {
+ ...defaultPropsData,
+ ...propsData,
+ },
+ });
+ };
+
+ describe('when there is an error', () => {
+ it('displays an alert', () => {
+ createComponent({
+ propsData: {
+ associationsCount: new Error(),
+ },
+ });
+
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+ });
+
+ describe('when counts are singular', () => {
+ it('renders singular counts', () => {
+ createComponent();
+
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+ });
+
+ describe('when counts are plural', () => {
+ it('renders plural counts', () => {
+ createComponent({
+ propsData: {
+ associationsCount: {
+ groups_count: 2,
+ projects_count: 3,
+ issues_count: 4,
+ merge_requests_count: 5,
+ },
+ },
+ });
+
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+ });
+
+ describe('when counts are 0', () => {
+ it('does not render items', () => {
+ createComponent({
+ propsData: {
+ associationsCount: {
+ groups_count: 0,
+ projects_count: 0,
+ issues_count: 0,
+ merge_requests_count: 0,
+ },
+ },
+ });
+
+ expect(wrapper.html()).toMatchSnapshot();
+ });
+ });
+});
diff --git a/spec/frontend/admin/users/components/modals/delete_user_modal_spec.js b/spec/frontend/admin/users/components/modals/delete_user_modal_spec.js
index 70ed9eeb3e1..2e892e292d7 100644
--- a/spec/frontend/admin/users/components/modals/delete_user_modal_spec.js
+++ b/spec/frontend/admin/users/components/modals/delete_user_modal_spec.js
@@ -1,10 +1,12 @@
import { GlButton, GlFormInput, GlSprintf } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import eventHub, {
EVENT_OPEN_DELETE_USER_MODAL,
} from '~/admin/users/components/modals/delete_user_modal_event_hub';
import DeleteUserModal from '~/admin/users/components/modals/delete_user_modal.vue';
import UserDeletionObstaclesList from '~/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list.vue';
+import AssociationsList from '~/admin/users/components/associations/associations_list.vue';
import ModalStub from './stubs/modal_stub';
const TEST_DELETE_USER_URL = 'delete-url';
@@ -200,4 +202,24 @@ describe('Delete user modal', () => {
expect(obstacles.props('obstacles')).toEqual(userDeletionObstacles);
});
});
+
+ it('renders `AssociationsList` component and passes `associationsCount` prop', async () => {
+ const associationsCount = {
+ groups_count: 5,
+ projects_count: 0,
+ issues_count: 5,
+ merge_requests_count: 5,
+ };
+
+ createComponent();
+ emitOpenModalEvent({
+ ...mockModalData,
+ associationsCount,
+ });
+ await nextTick();
+
+ expect(wrapper.findComponent(AssociationsList).props('associationsCount')).toEqual(
+ associationsCount,
+ );
+ });
});
diff --git a/spec/frontend/admin/users/components/user_actions_spec.js b/spec/frontend/admin/users/components/user_actions_spec.js
index ffc05e744c8..1b080b05c95 100644
--- a/spec/frontend/admin/users/components/user_actions_spec.js
+++ b/spec/frontend/admin/users/components/user_actions_spec.js
@@ -121,8 +121,11 @@ describe('AdminUserActions component', () => {
it.each(DELETE_ACTIONS)('renders a delete action component item for "%s"', (action) => {
const component = wrapper.findComponent(Actions[capitalizeFirstCharacter(action)]);
- expect(component.props('username')).toBe(user.name);
- expect(component.props('paths')).toEqual(userPaths);
+ expect(component.props()).toMatchObject({
+ username: user.name,
+ userId: user.id,
+ paths: userPaths,
+ });
expect(component.text()).toBe(I18N_USER_ACTIONS[action]);
});
});