diff options
Diffstat (limited to 'spec/frontend/profile')
6 files changed, 402 insertions, 123 deletions
diff --git a/spec/frontend/profile/account/components/update_username_spec.js b/spec/frontend/profile/account/components/update_username_spec.js index be39a7f4d80..45e5e0f885f 100644 --- a/spec/frontend/profile/account/components/update_username_spec.js +++ b/spec/frontend/profile/account/components/update_username_spec.js @@ -1,173 +1,135 @@ -import Vue from 'vue'; +import { shallowMount } from '@vue/test-utils'; +import { GlModal } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; import { TEST_HOST } from 'helpers/test_constants'; -import mountComponent from 'helpers/vue_mount_component_helper'; import axios from '~/lib/utils/axios_utils'; -import updateUsername from '~/profile/account/components/update_username.vue'; +import UpdateUsername from '~/profile/account/components/update_username.vue'; describe('UpdateUsername component', () => { const rootUrl = TEST_HOST; const actionUrl = `${TEST_HOST}/update/username`; - const username = 'hasnoname'; - const newUsername = 'new_username'; - let Component; - let vm; + const defaultProps = { + actionUrl, + rootUrl, + initialUsername: 'hasnoname', + }; + let wrapper; let axiosMock; + const createComponent = (props = {}) => { + wrapper = shallowMount(UpdateUsername, { + propsData: { + ...defaultProps, + ...props, + }, + stubs: { + GlModal, + }, + }); + }; + beforeEach(() => { axiosMock = new MockAdapter(axios); - Component = Vue.extend(updateUsername); - vm = mountComponent(Component, { - actionUrl, - rootUrl, - initialUsername: username, - }); + createComponent(); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); axiosMock.restore(); }); const findElements = () => { - const modalSelector = `#${vm.$options.modalId}`; + const modal = wrapper.find(GlModal); return { - input: vm.$el.querySelector(`#${vm.$options.inputId}`), - openModalBtn: vm.$el.querySelector(`[data-target="${modalSelector}"]`), - modal: vm.$el.querySelector(modalSelector), - modalBody: vm.$el.querySelector(`${modalSelector} .modal-body`), - modalHeader: vm.$el.querySelector(`${modalSelector} .modal-title`), - confirmModalBtn: vm.$el.querySelector(`${modalSelector} .btn-warning`), + modal, + input: wrapper.find(`#${wrapper.vm.$options.inputId}`), + openModalBtn: wrapper.find('[data-testid="username-change-confirmation-modal"]'), + modalBody: modal.find('.modal-body'), + modalHeader: modal.find('.modal-title'), + confirmModalBtn: wrapper.find('.btn-warning'), }; }; - it('has a disabled button if the username was not changed', done => { - const { input, openModalBtn } = findElements(); - input.dispatchEvent(new Event('input')); - - Vue.nextTick() - .then(() => { - expect(vm.username).toBe(username); - expect(vm.newUsername).toBe(username); - expect(openModalBtn).toBeDisabled(); - }) - .then(done) - .catch(done.fail); + it('has a disabled button if the username was not changed', async () => { + const { openModalBtn } = findElements(); + + await wrapper.vm.$nextTick(); + + expect(openModalBtn.props('disabled')).toBe(true); }); - it('has an enabled button which if the username was changed', done => { + it('has an enabled button which if the username was changed', async () => { const { input, openModalBtn } = findElements(); - input.value = newUsername; - input.dispatchEvent(new Event('input')); - - Vue.nextTick() - .then(() => { - expect(vm.username).toBe(username); - expect(vm.newUsername).toBe(newUsername); - expect(openModalBtn).not.toBeDisabled(); - }) - .then(done) - .catch(done.fail); - }); - it('confirmation modal contains proper header and body', done => { - const { modalBody, modalHeader } = findElements(); + input.element.value = 'newUsername'; + input.trigger('input'); - vm.newUsername = newUsername; + await wrapper.vm.$nextTick(); - Vue.nextTick() - .then(() => { - expect(modalHeader.textContent).toContain('Change username?'); - expect(modalBody.textContent).toContain( - `You are going to change the username ${username} to ${newUsername}`, - ); - }) - .then(done) - .catch(done.fail); + expect(openModalBtn.props('disabled')).toBe(false); }); - it('confirmation modal should escape usernames properly', done => { - const { modalBody } = findElements(); + describe('changing username', () => { + const newUsername = 'new_username'; - vm.username = '<i>Italic</i>'; - vm.newUsername = vm.username; + beforeEach(async () => { + createComponent(); + wrapper.setData({ newUsername }); - Vue.nextTick() - .then(() => { - expect(modalBody.innerHTML).toContain('<i>Italic</i>'); - expect(modalBody.innerHTML).not.toContain(vm.username); - }) - .then(done) - .catch(done.fail); - }); + await wrapper.vm.$nextTick(); + }); - it('executes API call on confirmation button click', done => { - const { confirmModalBtn } = findElements(); + it('confirmation modal contains proper header and body', async () => { + const { modal } = findElements(); - axiosMock.onPut(actionUrl).replyOnce(() => [200, { message: 'Username changed' }]); - jest.spyOn(axios, 'put'); + expect(modal.attributes('title')).toBe('Change username?'); + expect(modal.text()).toContain( + `You are going to change the username ${defaultProps.initialUsername} to ${newUsername}`, + ); + }); - vm.newUsername = newUsername; + it('executes API call on confirmation button click', async () => { + axiosMock.onPut(actionUrl).replyOnce(() => [200, { message: 'Username changed' }]); + jest.spyOn(axios, 'put'); - Vue.nextTick() - .then(() => { - confirmModalBtn.click(); + await wrapper.vm.onConfirm(); + await wrapper.vm.$nextTick(); - expect(axios.put).toHaveBeenCalledWith(actionUrl, { user: { username: newUsername } }); - }) - .then(done) - .catch(done.fail); - }); + expect(axios.put).toHaveBeenCalledWith(actionUrl, { user: { username: newUsername } }); + }); - it('sets the username after a successful update', done => { - const { input, openModalBtn } = findElements(); + it('sets the username after a successful update', async () => { + const { input, openModalBtn } = findElements(); - axiosMock.onPut(actionUrl).replyOnce(() => { - expect(input).toBeDisabled(); - expect(openModalBtn).toBeDisabled(); + axiosMock.onPut(actionUrl).replyOnce(() => { + expect(input.attributes('disabled')).toBe('disabled'); + expect(openModalBtn.props('disabled')).toBe(true); - return [200, { message: 'Username changed' }]; + return [200, { message: 'Username changed' }]; + }); + + await wrapper.vm.onConfirm(); + await wrapper.vm.$nextTick(); + + expect(input.attributes('disabled')).toBe(undefined); + expect(openModalBtn.props('disabled')).toBe(true); }); - vm.newUsername = newUsername; - - vm.onConfirm() - .then(() => { - expect(vm.username).toBe(newUsername); - expect(vm.newUsername).toBe(newUsername); - expect(input).not.toBeDisabled(); - expect(input.value).toBe(newUsername); - expect(openModalBtn).toBeDisabled(); - }) - .then(done) - .catch(done.fail); - }); + it('does not set the username after a erroneous update', async () => { + const { input, openModalBtn } = findElements(); - it('does not set the username after a erroneous update', done => { - const { input, openModalBtn } = findElements(); + axiosMock.onPut(actionUrl).replyOnce(() => { + expect(input.attributes('disabled')).toBe('disabled'); + expect(openModalBtn.props('disabled')).toBe(true); - axiosMock.onPut(actionUrl).replyOnce(() => { - expect(input).toBeDisabled(); - expect(openModalBtn).toBeDisabled(); + return [400, { message: 'Invalid username' }]; + }); - return [400, { message: 'Invalid username' }]; + await expect(wrapper.vm.onConfirm()).rejects.toThrow(); + expect(input.attributes('disabled')).toBe(undefined); + expect(openModalBtn.props('disabled')).toBe(false); }); - - const invalidUsername = 'anything.git'; - vm.newUsername = invalidUsername; - - vm.onConfirm() - .then(() => done.fail('Expected onConfirm to throw!')) - .catch(() => { - expect(vm.username).toBe(username); - expect(vm.newUsername).toBe(invalidUsername); - expect(input).not.toBeDisabled(); - expect(input.value).toBe(invalidUsername); - expect(openModalBtn).not.toBeDisabled(); - }) - .then(done) - .catch(done.fail); }); }); diff --git a/spec/frontend/profile/preferences/components/__snapshots__/integration_view_spec.js.snap b/spec/frontend/profile/preferences/components/__snapshots__/integration_view_spec.js.snap new file mode 100644 index 00000000000..2fd1fd6a04e --- /dev/null +++ b/spec/frontend/profile/preferences/components/__snapshots__/integration_view_spec.js.snap @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`IntegrationView component should render IntegrationView properly 1`] = ` +<div + name="sourcegraph" +> + <label + class="label-bold" + > + + Foo + + </label> + + <gl-link-stub + class="has-tooltip" + href="http://foo.com/help" + title="More information" + > + <gl-icon-stub + class="vertical-align-middle" + name="question-o" + size="16" + /> + </gl-link-stub> + + <div + class="form-group form-check" + data-testid="profile-preferences-integration-form-group" + > + <input + data-testid="profile-preferences-integration-hidden-field" + name="user[foo_enabled]" + type="hidden" + value="0" + /> + + <input + class="form-check-input" + data-testid="profile-preferences-integration-checkbox" + id="user_foo_enabled" + name="user[foo_enabled]" + type="checkbox" + value="1" + /> + + <label + class="form-check-label" + for="user_foo_enabled" + > + + Enable foo + + </label> + + <gl-form-text-stub + tag="div" + textvariant="muted" + > + <integration-help-text-stub + message="Click %{linkStart}Foo%{linkEnd}!" + messageurl="http://foo.com" + /> + </gl-form-text-stub> + </div> +</div> +`; diff --git a/spec/frontend/profile/preferences/components/__snapshots__/profile_preferences_spec.js.snap b/spec/frontend/profile/preferences/components/__snapshots__/profile_preferences_spec.js.snap new file mode 100644 index 00000000000..4df92cf86a5 --- /dev/null +++ b/spec/frontend/profile/preferences/components/__snapshots__/profile_preferences_spec.js.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ProfilePreferences component should render ProfilePreferences properly 1`] = ` +<div + class="row gl-mt-3 js-preferences-form" +> + <div + class="col-sm-12" + > + <hr + data-testid="profile-preferences-integrations-rule" + /> + </div> + + <div + class="col-lg-4 profile-settings-sidebar" + > + <h4 + class="gl-mt-0" + data-testid="profile-preferences-integrations-heading" + > + + Integrations + + </h4> + + <p> + + Customize integrations with third party services. + + </p> + </div> + + <div + class="col-lg-8" + > + <integration-view-stub + config="[object Object]" + helplink="http://foo.com/help" + message="Click %{linkStart}Foo%{linkEnd}!" + messageurl="http://foo.com" + /> + <integration-view-stub + config="[object Object]" + helplink="http://bar.com/help" + message="Click %{linkStart}Bar%{linkEnd}!" + messageurl="http://bar.com" + /> + </div> +</div> +`; diff --git a/spec/frontend/profile/preferences/components/integration_view_spec.js b/spec/frontend/profile/preferences/components/integration_view_spec.js new file mode 100644 index 00000000000..5d55a089119 --- /dev/null +++ b/spec/frontend/profile/preferences/components/integration_view_spec.js @@ -0,0 +1,124 @@ +import { shallowMount } from '@vue/test-utils'; + +import { GlFormText } from '@gitlab/ui'; +import IntegrationView from '~/profile/preferences/components/integration_view.vue'; +import IntegrationHelpText from '~/vue_shared/components/integrations_help_text.vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { integrationViews, userFields } from '../mock_data'; + +const viewProps = convertObjectPropsToCamelCase(integrationViews[0]); + +describe('IntegrationView component', () => { + let wrapper; + const defaultProps = { + config: { + title: 'Foo', + label: 'Enable foo', + formName: 'foo_enabled', + }, + ...viewProps, + }; + + function createComponent(options = {}) { + const { props = {}, provide = {} } = options; + return shallowMount(IntegrationView, { + provide: { + userFields, + ...provide, + }, + propsData: { + ...defaultProps, + ...props, + }, + }); + } + + function findCheckbox() { + return wrapper.find('[data-testid="profile-preferences-integration-checkbox"]'); + } + function findFormGroup() { + return wrapper.find('[data-testid="profile-preferences-integration-form-group"]'); + } + function findHiddenField() { + return wrapper.find('[data-testid="profile-preferences-integration-hidden-field"]'); + } + function findFormGroupLabel() { + return wrapper.find('[data-testid="profile-preferences-integration-form-group"] label'); + } + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('should render the title correctly', () => { + wrapper = createComponent(); + + expect(wrapper.find('label.label-bold').text()).toBe('Foo'); + }); + + it('should render the form correctly', () => { + wrapper = createComponent(); + + expect(findFormGroup().exists()).toBe(true); + expect(findHiddenField().exists()).toBe(true); + expect(findCheckbox().exists()).toBe(true); + expect(findCheckbox().attributes('id')).toBe('user_foo_enabled'); + expect(findCheckbox().attributes('name')).toBe('user[foo_enabled]'); + }); + + it('should have the checkbox value to be set to 1', () => { + wrapper = createComponent(); + + expect(findCheckbox().attributes('value')).toBe('1'); + }); + + it('should have the hidden value to be set to 0', () => { + wrapper = createComponent(); + + expect(findHiddenField().attributes('value')).toBe('0'); + }); + + it('should set the checkbox value to be true', () => { + wrapper = createComponent(); + + expect(findCheckbox().element.checked).toBe(true); + }); + + it('should set the checkbox value to be false when false is provided', () => { + wrapper = createComponent({ + provide: { + userFields: { + foo_enabled: false, + }, + }, + }); + + expect(findCheckbox().element.checked).toBe(false); + }); + + it('should set the checkbox value to be false when not provided', () => { + wrapper = createComponent({ provide: { userFields: {} } }); + + expect(findCheckbox().element.checked).toBe(false); + }); + + it('should render the help text', () => { + wrapper = createComponent(); + + expect(wrapper.find(GlFormText).exists()).toBe(true); + expect(wrapper.find(IntegrationHelpText).exists()).toBe(true); + }); + + it('should render the label correctly', () => { + wrapper = createComponent(); + + expect(findFormGroupLabel().text()).toBe('Enable foo'); + }); + + it('should render IntegrationView properly', () => { + wrapper = createComponent(); + + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/spec/frontend/profile/preferences/components/profile_preferences_spec.js b/spec/frontend/profile/preferences/components/profile_preferences_spec.js new file mode 100644 index 00000000000..fcc27d8faaf --- /dev/null +++ b/spec/frontend/profile/preferences/components/profile_preferences_spec.js @@ -0,0 +1,57 @@ +import { shallowMount } from '@vue/test-utils'; + +import ProfilePreferences from '~/profile/preferences/components/profile_preferences.vue'; +import IntegrationView from '~/profile/preferences/components/integration_view.vue'; +import { integrationViews, userFields } from '../mock_data'; + +describe('ProfilePreferences component', () => { + let wrapper; + const defaultProvide = { + integrationViews: [], + userFields, + }; + + function createComponent(options = {}) { + const { props = {}, provide = {} } = options; + return shallowMount(ProfilePreferences, { + provide: { + ...defaultProvide, + ...provide, + }, + propsData: props, + }); + } + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('should not render Integrations section', () => { + wrapper = createComponent(); + const views = wrapper.findAll(IntegrationView); + const divider = wrapper.find('[data-testid="profile-preferences-integrations-rule"]'); + const heading = wrapper.find('[data-testid="profile-preferences-integrations-heading"]'); + + expect(divider.exists()).toBe(false); + expect(heading.exists()).toBe(false); + expect(views).toHaveLength(0); + }); + + it('should render Integration section', () => { + wrapper = createComponent({ provide: { integrationViews } }); + const divider = wrapper.find('[data-testid="profile-preferences-integrations-rule"]'); + const heading = wrapper.find('[data-testid="profile-preferences-integrations-heading"]'); + const views = wrapper.findAll(IntegrationView); + + expect(divider.exists()).toBe(true); + expect(heading.exists()).toBe(true); + expect(views).toHaveLength(integrationViews.length); + }); + + it('should render ProfilePreferences properly', () => { + wrapper = createComponent({ provide: { integrationViews } }); + + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/spec/frontend/profile/preferences/mock_data.js b/spec/frontend/profile/preferences/mock_data.js new file mode 100644 index 00000000000..d07d5f565dc --- /dev/null +++ b/spec/frontend/profile/preferences/mock_data.js @@ -0,0 +1,18 @@ +export const integrationViews = [ + { + name: 'sourcegraph', + help_link: 'http://foo.com/help', + message: 'Click %{linkStart}Foo%{linkEnd}!', + message_url: 'http://foo.com', + }, + { + name: 'gitpod', + help_link: 'http://bar.com/help', + message: 'Click %{linkStart}Bar%{linkEnd}!', + message_url: 'http://bar.com', + }, +]; + +export const userFields = { + foo_enabled: true, +}; |