From 308146dc398fd4c13453048105498018459e0985 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 10 Oct 2019 00:06:44 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .../__snapshots__/delete_user_modal_spec.js.snap | 63 +++++++++ .../user_operation_confirmation_modal_spec.js.snap | 33 +++++ .../users/components/delete_user_modal_spec.js | 85 ++++++++++++ .../admin/users/components/stubs/modal_stub.js | 23 ++++ .../users/components/user_modal_manager_spec.js | 148 +++++++++++++++++++++ .../user_operation_confirmation_modal_spec.js | 47 +++++++ 6 files changed, 399 insertions(+) create mode 100644 spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap create mode 100644 spec/frontend/pages/admin/users/components/__snapshots__/user_operation_confirmation_modal_spec.js.snap create mode 100644 spec/frontend/pages/admin/users/components/delete_user_modal_spec.js create mode 100644 spec/frontend/pages/admin/users/components/stubs/modal_stub.js create mode 100644 spec/frontend/pages/admin/users/components/user_modal_manager_spec.js create mode 100644 spec/frontend/pages/admin/users/components/user_operation_confirmation_modal_spec.js (limited to 'spec/frontend/pages') diff --git a/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap new file mode 100644 index 00000000000..78a736a9060 --- /dev/null +++ b/spec/frontend/pages/admin/users/components/__snapshots__/delete_user_modal_spec.js.snap @@ -0,0 +1,63 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`User Operation confirmation modal renders modal with form included 1`] = ` +
+

+ content +

+ +

+ To confirm, type + + username + +

+ +
+ + + + + + + + + Cancel + + + + + secondaryAction + + + + + action + +
+`; diff --git a/spec/frontend/pages/admin/users/components/__snapshots__/user_operation_confirmation_modal_spec.js.snap b/spec/frontend/pages/admin/users/components/__snapshots__/user_operation_confirmation_modal_spec.js.snap new file mode 100644 index 00000000000..4a3989f5192 --- /dev/null +++ b/spec/frontend/pages/admin/users/components/__snapshots__/user_operation_confirmation_modal_spec.js.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`User Operation confirmation modal renders modal with form included 1`] = ` + +
+ + content + + + + + +
+
+`; diff --git a/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js b/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js new file mode 100644 index 00000000000..57802a41bb5 --- /dev/null +++ b/spec/frontend/pages/admin/users/components/delete_user_modal_spec.js @@ -0,0 +1,85 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlButton, GlFormInput } from '@gitlab/ui'; +import DeleteUserModal from '~/pages/admin/users/components/delete_user_modal.vue'; +import ModalStub from './stubs/modal_stub'; + +describe('User Operation confirmation modal', () => { + let wrapper; + + const findButton = variant => + wrapper + .findAll(GlButton) + .filter(w => w.attributes('variant') === variant) + .at(0); + + const createComponent = (props = {}) => { + wrapper = shallowMount(DeleteUserModal, { + propsData: { + title: 'title', + content: 'content', + action: 'action', + secondaryAction: 'secondaryAction', + deleteUserUrl: 'delete-url', + blockUserUrl: 'block-url', + username: 'username', + csrfToken: 'csrf', + ...props, + }, + stubs: { + GlModal: ModalStub, + }, + sync: false, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('renders modal with form included', () => { + createComponent(); + expect(wrapper.element).toMatchSnapshot(); + }); + + it.each` + variant | prop | action + ${'danger'} | ${'deleteUserUrl'} | ${'delete'} + ${'warning'} | ${'blockUserUrl'} | ${'block'} + `('closing modal with $variant button triggers $action', ({ variant, prop }) => { + createComponent(); + const form = wrapper.find('form'); + jest.spyOn(form.element, 'submit').mockReturnValue(); + const modalButton = findButton(variant); + modalButton.vm.$emit('click'); + return wrapper.vm.$nextTick().then(() => { + expect(form.element.submit).toHaveBeenCalled(); + expect(form.element.action).toContain(wrapper.props(prop)); + expect(new FormData(form.element).get('authenticity_token')).toEqual( + wrapper.props('csrfToken'), + ); + }); + }); + + it('disables buttons by default', () => { + createComponent(); + const blockButton = findButton('warning'); + const deleteButton = findButton('danger'); + expect(blockButton.attributes().disabled).toBeTruthy(); + expect(deleteButton.attributes().disabled).toBeTruthy(); + }); + + it('enables button when username is typed', () => { + createComponent({ + username: 'some-username', + }); + wrapper.find(GlFormInput).vm.$emit('input', 'some-username'); + const blockButton = findButton('warning'); + const deleteButton = findButton('danger'); + + return wrapper.vm.$nextTick().then(() => { + expect(blockButton.attributes().disabled).toBeFalsy(); + expect(deleteButton.attributes().disabled).toBeFalsy(); + }); + }); +}); diff --git a/spec/frontend/pages/admin/users/components/stubs/modal_stub.js b/spec/frontend/pages/admin/users/components/stubs/modal_stub.js new file mode 100644 index 00000000000..4dc55e909a0 --- /dev/null +++ b/spec/frontend/pages/admin/users/components/stubs/modal_stub.js @@ -0,0 +1,23 @@ +const ModalStub = { + inheritAttrs: false, + name: 'glmodal-stub', + data() { + return { + showWasCalled: false, + }; + }, + methods: { + show() { + this.showWasCalled = true; + }, + hide() {}, + }, + render(h) { + const children = [this.$slots.default, this.$slots['modal-footer']] + .filter(Boolean) + .reduce((acc, nodes) => acc.concat(nodes), []); + return h('div', children); + }, +}; + +export default ModalStub; diff --git a/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js b/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js new file mode 100644 index 00000000000..7653fffc502 --- /dev/null +++ b/spec/frontend/pages/admin/users/components/user_modal_manager_spec.js @@ -0,0 +1,148 @@ +import { shallowMount } from '@vue/test-utils'; +import UserModalManager from '~/pages/admin/users/components/user_modal_manager.vue'; +import ModalStub from './stubs/modal_stub'; + +describe('Users admin page Modal Manager', () => { + const modalConfiguration = { + action1: { + title: 'action1', + content: 'Action Modal 1', + }, + action2: { + title: 'action2', + content: 'Action Modal 2', + }, + }; + + const actionModals = { + action1: ModalStub, + action2: ModalStub, + }; + + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMount(UserModalManager, { + propsData: { + actionModals, + modalConfiguration, + csrfToken: 'dummyCSRF', + ...props, + }, + stubs: { + dummyComponent1: true, + dummyComponent2: true, + }, + sync: false, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('render behavior', () => { + it('does not renders modal when initialized', () => { + createComponent(); + expect(wrapper.find({ ref: 'modal' }).exists()).toBeFalsy(); + }); + + it('throws if non-existing action is requested', () => { + createComponent(); + expect(() => wrapper.vm.show({ glModalAction: 'non-existing' })).toThrow(); + }); + + 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', () => { + createComponent(); + wrapper.vm.show({ + glModalAction: 'action1', + extraProp: 'extraPropValue', + }); + + return wrapper.vm.$nextTick().then(() => { + const modal = wrapper.find({ ref: 'modal' }); + expect(modal.exists()).toBeTruthy(); + expect(modal.vm.$attrs.csrfToken).toEqual('dummyCSRF'); + expect(modal.vm.$attrs.extraProp).toEqual('extraPropValue'); + expect(modal.vm.showWasCalled).toBeTruthy(); + }); + }); + }); + + describe('global listener', () => { + beforeEach(() => { + jest.spyOn(document, 'addEventListener'); + jest.spyOn(document, 'removeEventListener'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it('registers global listener on mount', () => { + createComponent(); + expect(document.addEventListener).toHaveBeenCalledWith('click', expect.any(Function)); + }); + + it('removes global listener on destroy', () => { + createComponent(); + wrapper.destroy(); + expect(document.removeEventListener).toHaveBeenCalledWith('click', expect.any(Function)); + }); + }); + + describe('click handling', () => { + let node; + + beforeEach(() => { + node = document.createElement('div'); + document.body.appendChild(node); + }); + + afterEach(() => { + node.remove(); + node = null; + }); + + it('ignores wrong clicks', () => { + createComponent(); + const event = new window.MouseEvent('click', { + bubbles: true, + cancellable: true, + }); + jest.spyOn(event, 'preventDefault'); + node.dispatchEvent(event); + expect(event.preventDefault).not.toHaveBeenCalled(); + }); + + it('captures click with glModalAction', () => { + createComponent(); + node.dataset.glModalAction = 'action1'; + const event = new window.MouseEvent('click', { + bubbles: true, + cancellable: true, + }); + jest.spyOn(event, 'preventDefault'); + node.dispatchEvent(event); + + expect(event.preventDefault).toHaveBeenCalled(); + return wrapper.vm.$nextTick().then(() => { + const modal = wrapper.find({ ref: 'modal' }); + expect(modal.exists()).toBeTruthy(); + expect(modal.vm.showWasCalled).toBeTruthy(); + }); + }); + }); +}); diff --git a/spec/frontend/pages/admin/users/components/user_operation_confirmation_modal_spec.js b/spec/frontend/pages/admin/users/components/user_operation_confirmation_modal_spec.js new file mode 100644 index 00000000000..0ecdae2618c --- /dev/null +++ b/spec/frontend/pages/admin/users/components/user_operation_confirmation_modal_spec.js @@ -0,0 +1,47 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlModal } from '@gitlab/ui'; +import UserOperationConfirmationModal from '~/pages/admin/users/components/user_operation_confirmation_modal.vue'; + +describe('User Operation confirmation modal', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMount(UserOperationConfirmationModal, { + propsData: { + title: 'title', + content: 'content', + action: 'action', + url: '/url', + username: 'username', + csrfToken: 'csrf', + method: 'method', + ...props, + }, + sync: false, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('renders modal with form included', () => { + createComponent(); + expect(wrapper.element).toMatchSnapshot(); + }); + + it('closing modal with ok button triggers form submit', () => { + createComponent(); + const form = wrapper.find('form'); + jest.spyOn(form.element, 'submit').mockReturnValue(); + wrapper.find(GlModal).vm.$emit('ok'); + return wrapper.vm.$nextTick().then(() => { + expect(form.element.submit).toHaveBeenCalled(); + expect(form.element.action).toContain(wrapper.props('url')); + expect(new FormData(form.element).get('authenticity_token')).toEqual( + wrapper.props('csrfToken'), + ); + }); + }); +}); -- cgit v1.2.1