diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-14 21:08:31 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-14 21:08:31 +0300 |
commit | 06ac12d53c3f0b7cee2755a1254bf1af05d55044 (patch) | |
tree | 95d0be0bd751a22d6135f496c425c44d774fbe54 /spec/frontend/admin | |
parent | b689f371350fbf1b71f266764ee018befc9b91f7 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/admin')
4 files changed, 396 insertions, 0 deletions
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 new file mode 100644 index 00000000000..5e367891337 --- /dev/null +++ b/spec/frontend/admin/users/components/modals/__snapshots__/delete_user_modal_spec.js.snap @@ -0,0 +1,80 @@ +// 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> + + <oncall-schedules-list-stub + schedules="schedule1,schedule2" + username="username" + /> + + <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> +`; 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 new file mode 100644 index 00000000000..fee74764645 --- /dev/null +++ b/spec/frontend/admin/users/components/modals/delete_user_modal_spec.js @@ -0,0 +1,167 @@ +import { GlButton, GlFormInput } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import DeleteUserModal from '~/admin/users/components/modals/delete_user_modal.vue'; +import OncallSchedulesList from '~/vue_shared/components/oncall_schedules_list.vue'; +import ModalStub from './stubs/modal_stub'; + +const TEST_DELETE_USER_URL = 'delete-url'; +const TEST_BLOCK_USER_URL = 'block-url'; +const TEST_CSRF = 'csrf'; + +describe('User Operation confirmation modal', () => { + let wrapper; + let formSubmitSpy; + + const findButton = (variant, category) => + wrapper + .findAll(GlButton) + .filter((w) => w.attributes('variant') === variant && w.attributes('category') === category) + .at(0); + const findForm = () => wrapper.find('form'); + const findUsernameInput = () => wrapper.findComponent(GlFormInput); + const findPrimaryButton = () => findButton('danger', 'primary'); + const findSecondaryButton = () => findButton('danger', 'secondary'); + const findAuthenticityToken = () => new FormData(findForm().element).get('authenticity_token'); + const getUsername = () => findUsernameInput().attributes('value'); + const getMethodParam = () => new FormData(findForm().element).get('_method'); + const getFormAction = () => findForm().attributes('action'); + const findOnCallSchedulesList = () => wrapper.findComponent(OncallSchedulesList); + + const setUsername = (username) => { + findUsernameInput().vm.$emit('input', username); + }; + + const username = 'username'; + const badUsername = 'bad_username'; + const oncallSchedules = '["schedule1", "schedule2"]'; + + const createComponent = (props = {}) => { + wrapper = shallowMount(DeleteUserModal, { + propsData: { + username, + title: 'title', + content: 'content', + action: 'action', + secondaryAction: 'secondaryAction', + deleteUserUrl: TEST_DELETE_USER_URL, + blockUserUrl: TEST_BLOCK_USER_URL, + csrfToken: TEST_CSRF, + oncallSchedules, + ...props, + }, + stubs: { + GlModal: ModalStub, + }, + }); + }; + + beforeEach(() => { + formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('renders modal with form included', () => { + createComponent(); + expect(wrapper.element).toMatchSnapshot(); + }); + + describe('on created', () => { + beforeEach(() => { + createComponent(); + }); + + it('has disabled buttons', () => { + expect(findPrimaryButton().attributes('disabled')).toBeTruthy(); + expect(findSecondaryButton().attributes('disabled')).toBeTruthy(); + }); + }); + + describe('with incorrect username', () => { + beforeEach(() => { + createComponent(); + setUsername(badUsername); + + return wrapper.vm.$nextTick(); + }); + + it('shows incorrect username', () => { + expect(getUsername()).toEqual(badUsername); + }); + + it('has disabled buttons', () => { + expect(findPrimaryButton().attributes('disabled')).toBeTruthy(); + expect(findSecondaryButton().attributes('disabled')).toBeTruthy(); + }); + }); + + describe('with correct username', () => { + beforeEach(() => { + createComponent(); + setUsername(username); + + return wrapper.vm.$nextTick(); + }); + + it('shows correct username', () => { + expect(getUsername()).toEqual(username); + }); + + it('has enabled buttons', () => { + expect(findPrimaryButton().attributes('disabled')).toBeFalsy(); + expect(findSecondaryButton().attributes('disabled')).toBeFalsy(); + }); + + describe('when primary action is submitted', () => { + beforeEach(() => { + findPrimaryButton().vm.$emit('click'); + + return wrapper.vm.$nextTick(); + }); + + it('clears the input', () => { + expect(getUsername()).toEqual(''); + }); + + it('has correct form attributes and calls submit', () => { + expect(getFormAction()).toBe(TEST_DELETE_USER_URL); + expect(getMethodParam()).toBe('delete'); + expect(findAuthenticityToken()).toBe(TEST_CSRF); + expect(formSubmitSpy).toHaveBeenCalled(); + }); + }); + + describe('when secondary action is submitted', () => { + beforeEach(() => { + findSecondaryButton().vm.$emit('click'); + + return wrapper.vm.$nextTick(); + }); + + it('has correct form attributes and calls submit', () => { + expect(getFormAction()).toBe(TEST_BLOCK_USER_URL); + expect(getMethodParam()).toBe('put'); + expect(findAuthenticityToken()).toBe(TEST_CSRF); + expect(formSubmitSpy).toHaveBeenCalled(); + }); + }); + }); + + describe('Related oncall-schedules list', () => { + it('does NOT render the list when user has no related schedules', () => { + createComponent({ oncallSchedules: '[]' }); + expect(findOnCallSchedulesList().exists()).toBe(false); + }); + + it('renders the list when user has related schedules', () => { + createComponent(); + + const schedules = findOnCallSchedulesList(); + expect(schedules.exists()).toBe(true); + expect(schedules.props('schedules')).toEqual(JSON.parse(oncallSchedules)); + }); + }); +}); diff --git a/spec/frontend/admin/users/components/modals/stubs/modal_stub.js b/spec/frontend/admin/users/components/modals/stubs/modal_stub.js new file mode 100644 index 00000000000..4dc55e909a0 --- /dev/null +++ b/spec/frontend/admin/users/components/modals/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/admin/users/components/modals/user_modal_manager_spec.js b/spec/frontend/admin/users/components/modals/user_modal_manager_spec.js new file mode 100644 index 00000000000..65ce242662b --- /dev/null +++ b/spec/frontend/admin/users/components/modals/user_modal_manager_spec.js @@ -0,0 +1,126 @@ +import { mount } from '@vue/test-utils'; +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', () => { + createComponent(); + wrapper.vm.show({ + glModalAction: 'action1', + extraProp: 'extraPropValue', + }); + + return wrapper.vm.$nextTick().then(() => { + 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 wrapper.vm.$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 wrapper.vm.$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 wrapper.vm.$nextTick(); + + expect(findModal().exists()).toBe(false); + }); + }); +}); |