From 7021455bd1ed7b125c55eb1b33c5a01f2bc55ee0 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 17 Nov 2022 11:33:21 +0000 Subject: Add latest changes from gitlab-org/gitlab@15-6-stable-ee --- .../admin/users/components/actions/actions_spec.js | 57 +++++------ .../actions/delete_with_contributions_spec.js | 107 +++++++++++++++++++++ .../associations_list_item_spec.js.snap | 3 + .../__snapshots__/associations_list_spec.js.snap | 34 +++++++ .../associations/associations_list_item_spec.js | 25 +++++ .../associations/associations_list_spec.js | 78 +++++++++++++++ .../components/modals/delete_user_modal_spec.js | 22 +++++ .../admin/users/components/user_actions_spec.js | 7 +- 8 files changed, 299 insertions(+), 34 deletions(-) create mode 100644 spec/frontend/admin/users/components/actions/delete_with_contributions_spec.js create mode 100644 spec/frontend/admin/users/components/associations/__snapshots__/associations_list_item_spec.js.snap create mode 100644 spec/frontend/admin/users/components/associations/__snapshots__/associations_list_spec.js.snap create mode 100644 spec/frontend/admin/users/components/associations/associations_list_item_spec.js create mode 100644 spec/frontend/admin/users/components/associations/associations_list_spec.js (limited to 'spec/frontend/admin/users/components') 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`] = `"
  • 5 groups
  • "`; 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`] = ` +"" +`; + +exports[`AssociationsList when counts are singular renders singular counts 1`] = ` +"" +`; + +exports[`AssociationsList when there is an error displays an alert 1`] = ` +"
    + + +
    + +
    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.
    + +
    + +
    " +`; 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]); }); }); -- cgit v1.2.1