Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/admin')
-rw-r--r--spec/frontend/admin/statistics_panel/store/actions_spec.js37
-rw-r--r--spec/frontend/admin/topics/components/remove_avatar_spec.js11
-rw-r--r--spec/frontend/admin/users/components/actions/actions_spec.js42
-rw-r--r--spec/frontend/admin/users/components/modals/__snapshots__/delete_user_modal_spec.js.snap174
-rw-r--r--spec/frontend/admin/users/components/modals/delete_user_modal_spec.js104
-rw-r--r--spec/frontend/admin/users/components/modals/user_modal_manager_spec.js126
6 files changed, 125 insertions, 369 deletions
diff --git a/spec/frontend/admin/statistics_panel/store/actions_spec.js b/spec/frontend/admin/statistics_panel/store/actions_spec.js
index c7481b664b3..e7cdb5feb6a 100644
--- a/spec/frontend/admin/statistics_panel/store/actions_spec.js
+++ b/spec/frontend/admin/statistics_panel/store/actions_spec.js
@@ -22,8 +22,8 @@ describe('Admin statistics panel actions', () => {
mock.onGet(/api\/(.*)\/application\/statistics/).replyOnce(200, mockStatistics);
});
- it('dispatches success with received data', (done) =>
- testAction(
+ it('dispatches success with received data', () => {
+ return testAction(
actions.fetchStatistics,
null,
state,
@@ -37,8 +37,8 @@ describe('Admin statistics panel actions', () => {
),
},
],
- done,
- ));
+ );
+ });
});
describe('error', () => {
@@ -46,8 +46,8 @@ describe('Admin statistics panel actions', () => {
mock.onGet(/api\/(.*)\/application\/statistics/).replyOnce(500);
});
- it('dispatches error', (done) =>
- testAction(
+ it('dispatches error', () => {
+ return testAction(
actions.fetchStatistics,
null,
state,
@@ -61,26 +61,26 @@ describe('Admin statistics panel actions', () => {
payload: new Error('Request failed with status code 500'),
},
],
- done,
- ));
+ );
+ });
});
});
describe('requestStatistic', () => {
- it('should commit the request mutation', (done) =>
- testAction(
+ it('should commit the request mutation', () => {
+ return testAction(
actions.requestStatistics,
null,
state,
[{ type: types.REQUEST_STATISTICS }],
[],
- done,
- ));
+ );
+ });
});
describe('receiveStatisticsSuccess', () => {
- it('should commit received data', (done) =>
- testAction(
+ it('should commit received data', () => {
+ return testAction(
actions.receiveStatisticsSuccess,
mockStatistics,
state,
@@ -91,13 +91,13 @@ describe('Admin statistics panel actions', () => {
},
],
[],
- done,
- ));
+ );
+ });
});
describe('receiveStatisticsError', () => {
- it('should commit error', (done) => {
- testAction(
+ it('should commit error', () => {
+ return testAction(
actions.receiveStatisticsError,
500,
state,
@@ -108,7 +108,6 @@ describe('Admin statistics panel actions', () => {
},
],
[],
- done,
);
});
});
diff --git a/spec/frontend/admin/topics/components/remove_avatar_spec.js b/spec/frontend/admin/topics/components/remove_avatar_spec.js
index d4656f0a199..97d257c682c 100644
--- a/spec/frontend/admin/topics/components/remove_avatar_spec.js
+++ b/spec/frontend/admin/topics/components/remove_avatar_spec.js
@@ -1,10 +1,11 @@
-import { GlButton, GlModal } from '@gitlab/ui';
+import { GlButton, GlModal, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import RemoveAvatar from '~/admin/topics/components/remove_avatar.vue';
const modalID = 'fake-id';
const path = 'topic/path/1';
+const name = 'Topic 1';
jest.mock('lodash/uniqueId', () => () => 'fake-id');
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
@@ -16,10 +17,14 @@ describe('RemoveAvatar', () => {
wrapper = shallowMount(RemoveAvatar, {
provide: {
path,
+ name,
},
directives: {
GlModal: createMockDirective(),
},
+ stubs: {
+ GlSprintf,
+ },
});
};
@@ -55,8 +60,8 @@ describe('RemoveAvatar', () => {
const modal = findModal();
expect(modal.exists()).toBe(true);
- expect(modal.props('title')).toBe('Confirm remove avatar');
- expect(modal.text()).toBe('Avatar will be removed. Are you sure?');
+ expect(modal.props('title')).toBe('Remove topic avatar');
+ expect(modal.text()).toBe(`Topic avatar for ${name} will be removed. This cannot be undone.`);
});
it('contains the correct modal ID', () => {
diff --git a/spec/frontend/admin/users/components/actions/actions_spec.js b/spec/frontend/admin/users/components/actions/actions_spec.js
index fa485e73999..b758c15a91a 100644
--- a/spec/frontend/admin/users/components/actions/actions_spec.js
+++ b/spec/frontend/admin/users/components/actions/actions_spec.js
@@ -1,9 +1,9 @@
import { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import { kebabCase } from 'lodash';
import Actions from '~/admin/users/components/actions';
-import SharedDeleteAction from '~/admin/users/components/actions/shared/shared_delete_action.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';
@@ -14,12 +14,11 @@ describe('Action components', () => {
const findDropdownItem = () => wrapper.find(GlDropdownItem);
- const initComponent = ({ component, props, stubs = {} } = {}) => {
+ const initComponent = ({ component, props } = {}) => {
wrapper = shallowMount(component, {
propsData: {
...props,
},
- stubs,
});
};
@@ -29,7 +28,7 @@ describe('Action components', () => {
});
describe('CONFIRMATION_ACTIONS', () => {
- it.each(CONFIRMATION_ACTIONS)('renders a dropdown item for "%s"', async (action) => {
+ it.each(CONFIRMATION_ACTIONS)('renders a dropdown item for "%s"', (action) => {
initComponent({
component: Actions[capitalizeFirstCharacter(action)],
props: {
@@ -38,20 +37,23 @@ describe('Action components', () => {
},
});
- await nextTick();
expect(findDropdownItem().exists()).toBe(true);
});
});
describe('DELETE_ACTION_COMPONENTS', () => {
+ beforeEach(() => {
+ jest.spyOn(eventHub, '$emit').mockImplementation();
+ });
+
const userDeletionObstacles = [
{ name: 'schedule1', type: OBSTACLE_TYPES.oncallSchedules },
{ name: 'policy1', type: OBSTACLE_TYPES.escalationPolicies },
];
- it.each(DELETE_ACTIONS.map((action) => [action, paths[action]]))(
- 'renders a dropdown item for "%s"',
- async (action, expectedPath) => {
+ 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: {
@@ -59,21 +61,19 @@ describe('Action components', () => {
paths,
userDeletionObstacles,
},
- stubs: { SharedDeleteAction },
});
- await nextTick();
- const sharedAction = wrapper.find(SharedDeleteAction);
+ await findDropdownItem().vm.$emit('click');
- expect(sharedAction.attributes('data-block-user-url')).toBe(paths.block);
- expect(sharedAction.attributes('data-delete-user-url')).toBe(expectedPath);
- expect(sharedAction.attributes('data-gl-modal-action')).toBe(kebabCase(action));
- expect(sharedAction.attributes('data-username')).toBe('John Doe');
- expect(sharedAction.attributes('data-user-deletion-obstacles')).toBe(
- JSON.stringify(userDeletionObstacles),
+ expect(eventHub.$emit).toHaveBeenCalledWith(
+ EVENT_OPEN_DELETE_USER_MODAL,
+ expect.objectContaining({
+ username: 'John Doe',
+ blockPath: paths.block,
+ deletePath: paths[action],
+ userDeletionObstacles,
+ }),
);
-
- expect(findDropdownItem().exists()).toBe(true);
},
);
});
diff --git a/spec/frontend/admin/users/components/modals/__snapshots__/delete_user_modal_spec.js.snap b/spec/frontend/admin/users/components/modals/__snapshots__/delete_user_modal_spec.js.snap
index 7a17ef2cc6c..265569ac0e3 100644
--- a/spec/frontend/admin/users/components/modals/__snapshots__/delete_user_modal_spec.js.snap
+++ b/spec/frontend/admin/users/components/modals/__snapshots__/delete_user_modal_spec.js.snap
@@ -1,160 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`User Operation confirmation modal renders modal with form included 1`] = `
-<div>
- <p>
- <gl-sprintf-stub
- message="content"
- />
- </p>
-
- <user-deletion-obstacles-list-stub
- obstacles="schedule1,policy1"
- username="username"
+exports[`Delete user modal renders modal with form included 1`] = `
+<form
+ action=""
+ method="post"
+>
+ <input
+ name="_method"
+ type="hidden"
+ value="delete"
/>
- <p>
- <gl-sprintf-stub
- message="To confirm, type %{username}"
- />
- </p>
-
- <form
- action="delete-url"
- method="post"
- >
- <input
- name="_method"
- type="hidden"
- value="delete"
- />
-
- <input
- name="authenticity_token"
- type="hidden"
- value="csrf"
- />
-
- <gl-form-input-stub
- autocomplete="off"
- autofocus=""
- name="username"
- type="text"
- value=""
- />
- </form>
- <gl-button-stub
- buttontextclasses=""
- category="primary"
- icon=""
- size="medium"
- variant="default"
- >
- Cancel
- </gl-button-stub>
-
- <gl-button-stub
- buttontextclasses=""
- category="secondary"
- disabled="true"
- icon=""
- size="medium"
- variant="danger"
- >
-
- secondaryAction
-
- </gl-button-stub>
-
- <gl-button-stub
- buttontextclasses=""
- category="primary"
- disabled="true"
- icon=""
- size="medium"
- variant="danger"
- >
- action
- </gl-button-stub>
-</div>
-`;
-
-exports[`User Operation confirmation modal when user's name has leading and trailing whitespace displays user's name without whitespace 1`] = `
-<div>
- <p>
- content
- </p>
-
- <user-deletion-obstacles-list-stub
- obstacles="schedule1,policy1"
- username="John Smith"
+ <input
+ name="authenticity_token"
+ type="hidden"
+ value="csrf"
/>
- <p>
- To confirm, type
- <code
- class="gl-white-space-pre-wrap"
- >
- John Smith
- </code>
- </p>
-
- <form
- action="delete-url"
- method="post"
- >
- <input
- name="_method"
- type="hidden"
- value="delete"
- />
-
- <input
- name="authenticity_token"
- type="hidden"
- value="csrf"
- />
-
- <gl-form-input-stub
- autocomplete="off"
- autofocus=""
- name="username"
- type="text"
- value=""
- />
- </form>
- <gl-button-stub
- buttontextclasses=""
- category="primary"
- icon=""
- size="medium"
- variant="default"
- >
- Cancel
- </gl-button-stub>
-
- <gl-button-stub
- buttontextclasses=""
- category="secondary"
- disabled="true"
- icon=""
- size="medium"
- variant="danger"
- >
-
- secondaryAction
-
- </gl-button-stub>
-
- <gl-button-stub
- buttontextclasses=""
- category="primary"
- disabled="true"
- icon=""
- size="medium"
- variant="danger"
- >
- action
- </gl-button-stub>
-</div>
+ <gl-form-input-stub
+ autocomplete="off"
+ autofocus=""
+ name="username"
+ type="text"
+ value=""
+ />
+</form>
`;
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 f875cd24ee1..09a345ac826 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,6 +1,8 @@
import { GlButton, GlFormInput, GlSprintf } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-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 ModalStub from './stubs/modal_stub';
@@ -9,7 +11,7 @@ const TEST_DELETE_USER_URL = 'delete-url';
const TEST_BLOCK_USER_URL = 'block-url';
const TEST_CSRF = 'csrf';
-describe('User Operation confirmation modal', () => {
+describe('Delete user modal', () => {
let wrapper;
let formSubmitSpy;
@@ -27,28 +29,36 @@ describe('User Operation confirmation modal', () => {
const getMethodParam = () => new FormData(findForm().element).get('_method');
const getFormAction = () => findForm().attributes('action');
const findUserDeletionObstaclesList = () => wrapper.findComponent(UserDeletionObstaclesList);
+ const findMessageUsername = () => wrapper.findByTestId('message-username');
+ const findConfirmUsername = () => wrapper.findByTestId('confirm-username');
+ const emitOpenModalEvent = (modalData) => {
+ return eventHub.$emit(EVENT_OPEN_DELETE_USER_MODAL, modalData);
+ };
const setUsername = (username) => {
- findUsernameInput().vm.$emit('input', username);
+ return findUsernameInput().vm.$emit('input', username);
};
const username = 'username';
const badUsername = 'bad_username';
- const userDeletionObstacles = '["schedule1", "policy1"]';
+ const userDeletionObstacles = ['schedule1', 'policy1'];
+
+ const mockModalData = {
+ username,
+ blockPath: TEST_BLOCK_USER_URL,
+ deletePath: TEST_DELETE_USER_URL,
+ userDeletionObstacles,
+ i18n: {
+ title: 'Modal for %{username}',
+ primaryButtonLabel: 'Delete user',
+ messageBody: 'Delete %{username} or rather %{strongStart}block user%{strongEnd}?',
+ },
+ };
- const createComponent = (props = {}, stubs = {}) => {
- wrapper = shallowMount(DeleteUserModal, {
+ const createComponent = (stubs = {}) => {
+ wrapper = shallowMountExtended(DeleteUserModal, {
propsData: {
- username,
- title: 'title',
- content: 'content',
- action: 'action',
- secondaryAction: 'secondaryAction',
- deleteUserUrl: TEST_DELETE_USER_URL,
- blockUserUrl: TEST_BLOCK_USER_URL,
csrfToken: TEST_CSRF,
- userDeletionObstacles,
- ...props,
},
stubs: {
GlModal: ModalStub,
@@ -68,7 +78,7 @@ describe('User Operation confirmation modal', () => {
it('renders modal with form included', () => {
createComponent();
- expect(wrapper.element).toMatchSnapshot();
+ expect(findForm().element).toMatchSnapshot();
});
describe('on created', () => {
@@ -83,11 +93,11 @@ describe('User Operation confirmation modal', () => {
});
describe('with incorrect username', () => {
- beforeEach(async () => {
+ beforeEach(() => {
createComponent();
- setUsername(badUsername);
+ emitOpenModalEvent(mockModalData);
- await nextTick();
+ return setUsername(badUsername);
});
it('shows incorrect username', () => {
@@ -101,11 +111,11 @@ describe('User Operation confirmation modal', () => {
});
describe('with correct username', () => {
- beforeEach(async () => {
+ beforeEach(() => {
createComponent();
- setUsername(username);
+ emitOpenModalEvent(mockModalData);
- await nextTick();
+ return setUsername(username);
});
it('shows correct username', () => {
@@ -117,11 +127,9 @@ describe('User Operation confirmation modal', () => {
expect(findSecondaryButton().attributes('disabled')).toBeFalsy();
});
- describe('when primary action is submitted', () => {
- beforeEach(async () => {
- findPrimaryButton().vm.$emit('click');
-
- await nextTick();
+ describe('when primary action is clicked', () => {
+ beforeEach(() => {
+ return findPrimaryButton().vm.$emit('click');
});
it('clears the input', () => {
@@ -136,11 +144,9 @@ describe('User Operation confirmation modal', () => {
});
});
- describe('when secondary action is submitted', () => {
- beforeEach(async () => {
- findSecondaryButton().vm.$emit('click');
-
- await nextTick();
+ describe('when secondary action is clicked', () => {
+ beforeEach(() => {
+ return findSecondaryButton().vm.$emit('click');
});
it('has correct form attributes and calls submit', () => {
@@ -154,22 +160,23 @@ describe('User Operation confirmation modal', () => {
describe("when user's name has leading and trailing whitespace", () => {
beforeEach(() => {
- createComponent(
- {
- username: ' John Smith ',
- },
- { GlSprintf },
- );
+ createComponent({ GlSprintf });
+ return emitOpenModalEvent({ ...mockModalData, username: ' John Smith ' });
});
it("displays user's name without whitespace", () => {
- expect(wrapper.element).toMatchSnapshot();
+ expect(findMessageUsername().text()).toBe('John Smith');
+ expect(findConfirmUsername().text()).toBe('John Smith');
});
- it("shows enabled buttons when user's name is entered without whitespace", async () => {
- setUsername('John Smith');
+ it('passes user name without whitespace to the obstacles', () => {
+ expect(findUserDeletionObstaclesList().props()).toMatchObject({
+ userName: 'John Smith',
+ });
+ });
- await nextTick();
+ it("shows enabled buttons when user's name is entered without whitespace", async () => {
+ await setUsername('John Smith');
expect(findPrimaryButton().attributes('disabled')).toBeUndefined();
expect(findSecondaryButton().attributes('disabled')).toBeUndefined();
@@ -177,17 +184,20 @@ describe('User Operation confirmation modal', () => {
});
describe('Related user-deletion-obstacles list', () => {
- it('does NOT render the list when user has no related obstacles', () => {
- createComponent({ userDeletionObstacles: '[]' });
+ it('does NOT render the list when user has no related obstacles', async () => {
+ createComponent();
+ await emitOpenModalEvent({ ...mockModalData, userDeletionObstacles: [] });
+
expect(findUserDeletionObstaclesList().exists()).toBe(false);
});
- it('renders the list when user has related obstalces', () => {
+ it('renders the list when user has related obstalces', async () => {
createComponent();
+ await emitOpenModalEvent(mockModalData);
const obstacles = findUserDeletionObstaclesList();
expect(obstacles.exists()).toBe(true);
- expect(obstacles.props('obstacles')).toEqual(JSON.parse(userDeletionObstacles));
+ expect(obstacles.props('obstacles')).toEqual(userDeletionObstacles);
});
});
});
diff --git a/spec/frontend/admin/users/components/modals/user_modal_manager_spec.js b/spec/frontend/admin/users/components/modals/user_modal_manager_spec.js
deleted file mode 100644
index 4786357faa1..00000000000
--- a/spec/frontend/admin/users/components/modals/user_modal_manager_spec.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import { mount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import UserModalManager from '~/admin/users/components/modals/user_modal_manager.vue';
-import ModalStub from './stubs/modal_stub';
-
-describe('Users admin page Modal Manager', () => {
- let wrapper;
-
- const modalConfiguration = {
- action1: {
- title: 'action1',
- content: 'Action Modal 1',
- },
- action2: {
- title: 'action2',
- content: 'Action Modal 2',
- },
- };
-
- const findModal = () => wrapper.find({ ref: 'modal' });
-
- const createComponent = (props = {}) => {
- wrapper = mount(UserModalManager, {
- propsData: {
- selector: '.js-delete-user-modal-button',
- modalConfiguration,
- csrfToken: 'dummyCSRF',
- ...props,
- },
- stubs: {
- DeleteUserModal: ModalStub,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('render behavior', () => {
- it('does not renders modal when initialized', () => {
- createComponent();
- expect(findModal().exists()).toBeFalsy();
- });
-
- it('throws if action has no proper configuration', () => {
- createComponent({
- modalConfiguration: {},
- });
- expect(() => wrapper.vm.show({ glModalAction: 'action1' })).toThrow();
- });
-
- it('renders modal with expected props when valid configuration is passed', async () => {
- createComponent();
- wrapper.vm.show({
- glModalAction: 'action1',
- extraProp: 'extraPropValue',
- });
-
- await nextTick();
- const modal = findModal();
- expect(modal.exists()).toBeTruthy();
- expect(modal.vm.$attrs.csrfToken).toEqual('dummyCSRF');
- expect(modal.vm.$attrs.extraProp).toEqual('extraPropValue');
- expect(modal.vm.showWasCalled).toBeTruthy();
- });
- });
-
- describe('click handling', () => {
- let button;
- let button2;
-
- const createButtons = () => {
- button = document.createElement('button');
- button2 = document.createElement('button');
- button.setAttribute('class', 'js-delete-user-modal-button');
- button.setAttribute('data-username', 'foo');
- button.setAttribute('data-gl-modal-action', 'action1');
- button.setAttribute('data-block-user-url', '/block');
- button.setAttribute('data-delete-user-url', '/delete');
- document.body.appendChild(button);
- document.body.appendChild(button2);
- };
- const removeButtons = () => {
- button.remove();
- button = null;
- button2.remove();
- button2 = null;
- };
-
- beforeEach(() => {
- createButtons();
- createComponent();
- });
-
- afterEach(() => {
- removeButtons();
- });
-
- it('renders the modal when the button is clicked', async () => {
- button.click();
-
- await nextTick();
-
- expect(findModal().exists()).toBe(true);
- });
-
- it('does not render the modal when a misconfigured button is clicked', async () => {
- button.removeAttribute('data-gl-modal-action');
- button.click();
-
- await nextTick();
-
- expect(findModal().exists()).toBe(false);
- });
-
- it('does not render the modal when a button without the selector class is clicked', async () => {
- button2.click();
-
- await nextTick();
-
- expect(findModal().exists()).toBe(false);
- });
- });
-});