diff options
Diffstat (limited to 'spec/frontend/admin')
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); - }); - }); -}); |