From 9dc93a4519d9d5d7be48ff274127136236a3adb3 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 20 Apr 2021 23:50:22 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-11-stable-ee --- .../components/signup_checkbox_spec.js | 66 ++++ .../components/signup_form_spec.js | 331 +++++++++++++++++++++ .../admin/signup_restrictions/mock_data.js | 41 +++ spec/frontend/admin/signup_restrictions/utils.js | 19 ++ .../admin/signup_restrictions/utils_spec.js | 22 ++ .../admin/users/components/user_date_spec.js | 2 +- .../admin/users/components/users_table_spec.js | 2 +- spec/frontend/admin/users/new_spec.js | 76 +++++ 8 files changed, 557 insertions(+), 2 deletions(-) create mode 100644 spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js create mode 100644 spec/frontend/admin/signup_restrictions/components/signup_form_spec.js create mode 100644 spec/frontend/admin/signup_restrictions/mock_data.js create mode 100644 spec/frontend/admin/signup_restrictions/utils.js create mode 100644 spec/frontend/admin/signup_restrictions/utils_spec.js create mode 100644 spec/frontend/admin/users/new_spec.js (limited to 'spec/frontend/admin') diff --git a/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js b/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js new file mode 100644 index 00000000000..ae9b6f57ee0 --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js @@ -0,0 +1,66 @@ +import { GlFormCheckbox } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import SignupCheckbox from '~/pages/admin/application_settings/general/components/signup_checkbox.vue'; + +describe('Signup Form', () => { + let wrapper; + + const props = { + name: 'name', + helpText: 'some help text', + label: 'a label', + value: true, + dataQaSelector: 'qa_selector', + }; + + const mountComponent = () => { + wrapper = shallowMount(SignupCheckbox, { + propsData: props, + stubs: { + GlFormCheckbox, + }, + }); + }; + + const findByTestId = (id) => wrapper.find(`[data-testid="${id}"]`); + const findHiddenInput = () => findByTestId('input'); + const findCheckbox = () => wrapper.find(GlFormCheckbox); + const findCheckboxLabel = () => findByTestId('label'); + const findHelpText = () => findByTestId('helpText'); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('Signup Checkbox', () => { + beforeEach(() => { + mountComponent(); + }); + + describe('hidden input element', () => { + it('gets passed correct values from props', () => { + expect(findHiddenInput().attributes('name')).toBe(props.name); + + expect(findHiddenInput().attributes('value')).toBe('1'); + }); + }); + + describe('checkbox', () => { + it('gets passed correct checked value', () => { + expect(findCheckbox().attributes('checked')).toBe('true'); + }); + + it('gets passed correct label', () => { + expect(findCheckboxLabel().text()).toBe(props.label); + }); + + it('gets passed correct help text', () => { + expect(findHelpText().text()).toBe(props.helpText); + }); + + it('gets passed data qa selector', () => { + expect(findCheckbox().attributes('data-qa-selector')).toBe(props.dataQaSelector); + }); + }); + }); +}); diff --git a/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js b/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js new file mode 100644 index 00000000000..18339164d5a --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js @@ -0,0 +1,331 @@ +import { GlButton, GlModal } from '@gitlab/ui'; +import { within, fireEvent } from '@testing-library/dom'; +import { shallowMount, mount } from '@vue/test-utils'; +import { stubComponent } from 'helpers/stub_component'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import SignupForm from '~/pages/admin/application_settings/general/components/signup_form.vue'; +import { mockData } from '../mock_data'; + +jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); + +describe('Signup Form', () => { + let wrapper; + let formSubmitSpy; + + const mountComponent = ({ injectedProps = {}, mountFn = shallowMount, stubs = {} } = {}) => { + wrapper = extendedWrapper( + mountFn(SignupForm, { + provide: { + ...mockData, + ...injectedProps, + }, + stubs, + }), + ); + }; + + const queryByLabelText = (text) => within(wrapper.element).queryByLabelText(text); + + const findForm = () => wrapper.findByTestId('form'); + const findInputCsrf = () => findForm().find('[name="authenticity_token"]'); + const findFormSubmitButton = () => findForm().find(GlButton); + + const findDenyListRawRadio = () => queryByLabelText('Enter denylist manually'); + const findDenyListFileRadio = () => queryByLabelText('Upload denylist file'); + + const findDenyListRawInputGroup = () => wrapper.findByTestId('domain-denylist-raw-input-group'); + const findDenyListFileInputGroup = () => wrapper.findByTestId('domain-denylist-file-input-group'); + + const findRequireAdminApprovalCheckbox = () => + wrapper.findByTestId('require-admin-approval-checkbox'); + const findUserCapInput = () => wrapper.findByTestId('user-cap-input'); + const findModal = () => wrapper.find(GlModal); + + afterEach(() => { + wrapper.destroy(); + + formSubmitSpy = null; + }); + + describe('form data', () => { + beforeEach(() => { + mountComponent(); + }); + + it.each` + prop | propValue | elementSelector | formElementPassedDataType | formElementKey | expected + ${'signupEnabled'} | ${mockData.signupEnabled} | ${'[name="application_setting[signup_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.signupEnabled} + ${'requireAdminApprovalAfterUserSignup'} | ${mockData.requireAdminApprovalAfterUserSignup} | ${'[name="application_setting[require_admin_approval_after_user_signup]"]'} | ${'prop'} | ${'value'} | ${mockData.requireAdminApprovalAfterUserSignup} + ${'sendUserConfirmationEmail'} | ${mockData.sendUserConfirmationEmail} | ${'[name="application_setting[send_user_confirmation_email]"]'} | ${'prop'} | ${'value'} | ${mockData.sendUserConfirmationEmail} + ${'newUserSignupsCap'} | ${mockData.newUserSignupsCap} | ${'[name="application_setting[new_user_signups_cap]"]'} | ${'attribute'} | ${'value'} | ${mockData.newUserSignupsCap} + ${'minimumPasswordLength'} | ${mockData.minimumPasswordLength} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'value'} | ${mockData.minimumPasswordLength} + ${'minimumPasswordLengthMin'} | ${mockData.minimumPasswordLengthMin} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'min'} | ${mockData.minimumPasswordLengthMin} + ${'minimumPasswordLengthMax'} | ${mockData.minimumPasswordLengthMax} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'max'} | ${mockData.minimumPasswordLengthMax} + ${'domainAllowlistRaw'} | ${mockData.domainAllowlistRaw} | ${'[name="application_setting[domain_allowlist_raw]"]'} | ${'value'} | ${'value'} | ${mockData.domainAllowlistRaw} + ${'domainDenylistEnabled'} | ${mockData.domainDenylistEnabled} | ${'[name="application_setting[domain_denylist_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.domainDenylistEnabled} + ${'denylistTypeRawSelected'} | ${mockData.denylistTypeRawSelected} | ${'[name="denylist_type"]'} | ${'attribute'} | ${'checked'} | ${'raw'} + ${'domainDenylistRaw'} | ${mockData.domainDenylistRaw} | ${'[name="application_setting[domain_denylist_raw]"]'} | ${'value'} | ${'value'} | ${mockData.domainDenylistRaw} + ${'emailRestrictionsEnabled'} | ${mockData.emailRestrictionsEnabled} | ${'[name="application_setting[email_restrictions_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.emailRestrictionsEnabled} + ${'emailRestrictions'} | ${mockData.emailRestrictions} | ${'[name="application_setting[email_restrictions]"]'} | ${'value'} | ${'value'} | ${mockData.emailRestrictions} + ${'afterSignUpText'} | ${mockData.afterSignUpText} | ${'[name="application_setting[after_sign_up_text]"]'} | ${'value'} | ${'value'} | ${mockData.afterSignUpText} + `( + 'form element $elementSelector gets $expected value for $formElementKey $formElementPassedDataType when prop $prop is set to $propValue', + ({ elementSelector, expected, formElementKey, formElementPassedDataType }) => { + const formElement = wrapper.find(elementSelector); + + switch (formElementPassedDataType) { + case 'attribute': + expect(formElement.attributes(formElementKey)).toBe(expected); + break; + case 'prop': + expect(formElement.props(formElementKey)).toBe(expected); + break; + case 'value': + expect(formElement.element.value).toBe(expected); + break; + default: + expect(formElement.props(formElementKey)).toBe(expected); + break; + } + }, + ); + it('gets passed the path for action attribute', () => { + expect(findForm().attributes('action')).toBe(mockData.settingsPath); + }); + + it('gets passed the csrf token as a hidden input value', () => { + expect(findInputCsrf().attributes('type')).toBe('hidden'); + + expect(findInputCsrf().attributes('value')).toBe('mock-csrf-token'); + }); + }); + + describe('domain deny list', () => { + describe('when it is set to raw from props', () => { + beforeEach(() => { + mountComponent({ mountFn: mount }); + }); + + it('has raw list selected', () => { + expect(findDenyListRawRadio().checked).toBe(true); + }); + + it('has file not selected', () => { + expect(findDenyListFileRadio().checked).toBe(false); + }); + + it('raw list input is displayed', () => { + expect(findDenyListRawInputGroup().exists()).toBe(true); + }); + + it('file input is not displayed', () => { + expect(findDenyListFileInputGroup().exists()).toBe(false); + }); + + describe('when user clicks on file radio', () => { + beforeEach(() => { + fireEvent.click(findDenyListFileRadio()); + }); + + it('has raw list not selected', () => { + expect(findDenyListRawRadio().checked).toBe(false); + }); + + it('has file selected', () => { + expect(findDenyListFileRadio().checked).toBe(true); + }); + + it('raw list input is not displayed', () => { + expect(findDenyListRawInputGroup().exists()).toBe(false); + }); + + it('file input is displayed', () => { + expect(findDenyListFileInputGroup().exists()).toBe(true); + }); + }); + }); + + describe('when it is set to file from injected props', () => { + beforeEach(() => { + mountComponent({ mountFn: mount, injectedProps: { denylistTypeRawSelected: false } }); + }); + + it('has raw list not selected', () => { + expect(findDenyListRawRadio().checked).toBe(false); + }); + + it('has file selected', () => { + expect(findDenyListFileRadio().checked).toBe(true); + }); + + it('raw list input is not displayed', () => { + expect(findDenyListRawInputGroup().exists()).toBe(false); + }); + + it('file input is displayed', () => { + expect(findDenyListFileInputGroup().exists()).toBe(true); + }); + + describe('when user clicks on raw list radio', () => { + beforeEach(() => { + fireEvent.click(findDenyListRawRadio()); + }); + + it('has raw list selected', () => { + expect(findDenyListRawRadio().checked).toBe(true); + }); + + it('has file not selected', () => { + expect(findDenyListFileRadio().checked).toBe(false); + }); + + it('raw list input is displayed', () => { + expect(findDenyListRawInputGroup().exists()).toBe(true); + }); + + it('file input is not displayed', () => { + expect(findDenyListFileInputGroup().exists()).toBe(false); + }); + }); + }); + }); + + describe('form submit button confirmation modal for side-effect of adding possibly unwanted new users', () => { + it.each` + requireAdminApprovalAction | userCapAction | buttonEffect + ${'unchanged from true'} | ${'unchanged'} | ${'submits form'} + ${'unchanged from false'} | ${'unchanged'} | ${'submits form'} + ${'toggled off'} | ${'unchanged'} | ${'shows confirmation modal'} + ${'toggled on'} | ${'unchanged'} | ${'submits form'} + ${'unchanged from false'} | ${'increased'} | ${'shows confirmation modal'} + ${'unchanged from true'} | ${'increased'} | ${'shows confirmation modal'} + ${'toggled off'} | ${'increased'} | ${'shows confirmation modal'} + ${'toggled on'} | ${'increased'} | ${'shows confirmation modal'} + ${'toggled on'} | ${'decreased'} | ${'submits form'} + ${'unchanged from false'} | ${'changed from limited to unlimited'} | ${'shows confirmation modal'} + ${'unchanged from false'} | ${'changed from unlimited to limited'} | ${'submits form'} + ${'unchanged from false'} | ${'unchanged from unlimited'} | ${'submits form'} + `( + '$buttonEffect if require admin approval for new sign-ups is $requireAdminApprovalAction and the user cap is $userCapAction', + async ({ requireAdminApprovalAction, userCapAction, buttonEffect }) => { + let isModalDisplayed; + + switch (buttonEffect) { + case 'shows confirmation modal': + isModalDisplayed = true; + break; + case 'submits form': + isModalDisplayed = false; + break; + default: + isModalDisplayed = false; + break; + } + + const isFormSubmittedWhenClickingFormSubmitButton = !isModalDisplayed; + + const injectedProps = {}; + + const USER_CAP_DEFAULT = 5; + + switch (userCapAction) { + case 'changed from unlimited to limited': + injectedProps.newUserSignupsCap = ''; + break; + case 'unchanged from unlimited': + injectedProps.newUserSignupsCap = ''; + break; + default: + injectedProps.newUserSignupsCap = USER_CAP_DEFAULT; + break; + } + + switch (requireAdminApprovalAction) { + case 'unchanged from true': + injectedProps.requireAdminApprovalAfterUserSignup = true; + break; + case 'unchanged from false': + injectedProps.requireAdminApprovalAfterUserSignup = false; + break; + case 'toggled off': + injectedProps.requireAdminApprovalAfterUserSignup = true; + break; + case 'toggled on': + injectedProps.requireAdminApprovalAfterUserSignup = false; + break; + default: + injectedProps.requireAdminApprovalAfterUserSignup = false; + break; + } + + formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation(); + + await mountComponent({ + injectedProps, + stubs: { GlButton, GlModal: stubComponent(GlModal) }, + }); + + findModal().vm.show = jest.fn(); + + if ( + requireAdminApprovalAction === 'toggled off' || + requireAdminApprovalAction === 'toggled on' + ) { + await findRequireAdminApprovalCheckbox().vm.$emit('input', false); + } + + switch (userCapAction) { + case 'increased': + await findUserCapInput().vm.$emit('input', USER_CAP_DEFAULT + 1); + break; + case 'decreased': + await findUserCapInput().vm.$emit('input', USER_CAP_DEFAULT - 1); + break; + case 'changed from limited to unlimited': + await findUserCapInput().vm.$emit('input', ''); + break; + case 'changed from unlimited to limited': + await findUserCapInput().vm.$emit('input', USER_CAP_DEFAULT); + break; + default: + break; + } + + await findFormSubmitButton().trigger('click'); + + if (isFormSubmittedWhenClickingFormSubmitButton) { + expect(formSubmitSpy).toHaveBeenCalled(); + expect(findModal().vm.show).not.toHaveBeenCalled(); + } else { + expect(formSubmitSpy).not.toHaveBeenCalled(); + expect(findModal().vm.show).toHaveBeenCalled(); + } + }, + ); + + describe('modal actions', () => { + beforeEach(async () => { + const INITIAL_USER_CAP = 5; + + await mountComponent({ + injectedProps: { + newUserSignupsCap: INITIAL_USER_CAP, + }, + stubs: { GlButton, GlModal: stubComponent(GlModal) }, + }); + + await findUserCapInput().vm.$emit('input', INITIAL_USER_CAP + 1); + + await findFormSubmitButton().trigger('click'); + }); + + it('submits the form after clicking approve users button', async () => { + formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation(); + + await findModal().vm.$emit('primary'); + + expect(formSubmitSpy).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/frontend/admin/signup_restrictions/mock_data.js b/spec/frontend/admin/signup_restrictions/mock_data.js new file mode 100644 index 00000000000..624a5614c9c --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/mock_data.js @@ -0,0 +1,41 @@ +export const rawMockData = { + host: 'path/to/host', + settingsPath: 'path/to/settings', + signupEnabled: 'true', + requireAdminApprovalAfterUserSignup: 'true', + sendUserConfirmationEmail: 'true', + minimumPasswordLength: '8', + minimumPasswordLengthMin: '3', + minimumPasswordLengthMax: '10', + minimumPasswordLengthHelpLink: 'help/link', + domainAllowlistRaw: 'domain1.com, domain2.com', + newUserSignupsCap: '8', + domainDenylistEnabled: 'true', + denylistTypeRawSelected: 'true', + domainDenylistRaw: 'domain2.com, domain3.com', + emailRestrictionsEnabled: 'true', + supportedSyntaxLinkUrl: '/supported/syntax/link', + emailRestrictions: 'user1@domain.com, user2@domain.com', + afterSignUpText: 'Congratulations on your successful sign-up!', +}; + +export const mockData = { + host: 'path/to/host', + settingsPath: 'path/to/settings', + signupEnabled: true, + requireAdminApprovalAfterUserSignup: true, + sendUserConfirmationEmail: true, + minimumPasswordLength: '8', + minimumPasswordLengthMin: '3', + minimumPasswordLengthMax: '10', + minimumPasswordLengthHelpLink: 'help/link', + domainAllowlistRaw: 'domain1.com, domain2.com', + newUserSignupsCap: '8', + domainDenylistEnabled: true, + denylistTypeRawSelected: true, + domainDenylistRaw: 'domain2.com, domain3.com', + emailRestrictionsEnabled: true, + supportedSyntaxLinkUrl: '/supported/syntax/link', + emailRestrictions: 'user1@domain.com, user2@domain.com', + afterSignUpText: 'Congratulations on your successful sign-up!', +}; diff --git a/spec/frontend/admin/signup_restrictions/utils.js b/spec/frontend/admin/signup_restrictions/utils.js new file mode 100644 index 00000000000..30a95467e09 --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/utils.js @@ -0,0 +1,19 @@ +export const setDataAttributes = (data, element) => { + Object.keys(data).forEach((key) => { + const value = data[key]; + + // attribute should be: + // - valueless if value is 'true' + // - absent if value is 'false' + switch (value) { + case false: + break; + case true: + element.dataset[`${key}`] = ''; + break; + default: + element.dataset[`${key}`] = value; + break; + } + }); +}; diff --git a/spec/frontend/admin/signup_restrictions/utils_spec.js b/spec/frontend/admin/signup_restrictions/utils_spec.js new file mode 100644 index 00000000000..fd5c4c3317b --- /dev/null +++ b/spec/frontend/admin/signup_restrictions/utils_spec.js @@ -0,0 +1,22 @@ +import { getParsedDataset } from '~/pages/admin/application_settings/utils'; +import { rawMockData, mockData } from './mock_data'; + +describe('utils', () => { + describe('getParsedDataset', () => { + it('returns correct results', () => { + expect( + getParsedDataset({ + dataset: rawMockData, + booleanAttributes: [ + 'signupEnabled', + 'requireAdminApprovalAfterUserSignup', + 'sendUserConfirmationEmail', + 'domainDenylistEnabled', + 'denylistTypeRawSelected', + 'emailRestrictionsEnabled', + ], + }), + ).toEqual(mockData); + }); + }); +}); diff --git a/spec/frontend/admin/users/components/user_date_spec.js b/spec/frontend/admin/users/components/user_date_spec.js index 6428b10059b..1a2f2938db5 100644 --- a/spec/frontend/admin/users/components/user_date_spec.js +++ b/spec/frontend/admin/users/components/user_date_spec.js @@ -1,6 +1,6 @@ import { shallowMount } from '@vue/test-utils'; -import UserDate from '~/admin/users/components/user_date.vue'; +import UserDate from '~/vue_shared/components/user_date.vue'; import { users } from '../mock_data'; const mockDate = users[0].createdAt; diff --git a/spec/frontend/admin/users/components/users_table_spec.js b/spec/frontend/admin/users/components/users_table_spec.js index f1fcc20fb65..424b0deebd3 100644 --- a/spec/frontend/admin/users/components/users_table_spec.js +++ b/spec/frontend/admin/users/components/users_table_spec.js @@ -3,8 +3,8 @@ import { mount } from '@vue/test-utils'; import AdminUserActions from '~/admin/users/components/user_actions.vue'; import AdminUserAvatar from '~/admin/users/components/user_avatar.vue'; -import AdminUserDate from '~/admin/users/components/user_date.vue'; import AdminUsersTable from '~/admin/users/components/users_table.vue'; +import AdminUserDate from '~/vue_shared/components/user_date.vue'; import { users, paths } from '../mock_data'; diff --git a/spec/frontend/admin/users/new_spec.js b/spec/frontend/admin/users/new_spec.js new file mode 100644 index 00000000000..692c583dca8 --- /dev/null +++ b/spec/frontend/admin/users/new_spec.js @@ -0,0 +1,76 @@ +import { + setupInternalUserRegexHandler, + ID_USER_EMAIL, + ID_USER_EXTERNAL, + ID_WARNING, +} from '~/admin/users/new'; + +describe('admin/users/new', () => { + const FIXTURE = 'admin/users/new_with_internal_user_regex.html'; + + let elExternal; + let elUserEmail; + let elWarningMessage; + + beforeEach(() => { + loadFixtures(FIXTURE); + setupInternalUserRegexHandler(); + + elExternal = document.getElementById(ID_USER_EXTERNAL); + elUserEmail = document.getElementById(ID_USER_EMAIL); + elWarningMessage = document.getElementById(ID_WARNING); + + elExternal.checked = true; + }); + + const changeEmail = (val) => { + elUserEmail.value = val; + elUserEmail.dispatchEvent(new Event('input')); + }; + + const hasHiddenWarning = () => elWarningMessage.classList.contains('hidden'); + + describe('Behaviour of userExternal checkbox', () => { + it('hides warning by default', () => { + expect(hasHiddenWarning()).toBe(true); + }); + + describe('when matches email as internal', () => { + beforeEach(() => { + changeEmail('test@'); + }); + + it('has external unchecked', () => { + expect(elExternal.checked).toBe(false); + }); + + it('shows warning', () => { + expect(hasHiddenWarning()).toBe(false); + }); + + describe('when external is checked again', () => { + beforeEach(() => { + elExternal.dispatchEvent(new Event('change')); + }); + + it('hides warning', () => { + expect(hasHiddenWarning()).toBe(true); + }); + }); + }); + + describe('when matches emails as external', () => { + beforeEach(() => { + changeEmail('test.ext@'); + }); + + it('has external checked', () => { + expect(elExternal.checked).toBe(true); + }); + + it('hides warning', () => { + expect(hasHiddenWarning()).toBe(true); + }); + }); + }); +}); -- cgit v1.2.3