diff --git a/webapp/components/Password/Change.spec.js b/webapp/components/Password/Change.spec.js deleted file mode 100644 index 88f25554e..000000000 --- a/webapp/components/Password/Change.spec.js +++ /dev/null @@ -1,166 +0,0 @@ -import { mount } from '@vue/test-utils' -import ChangePassword from './Change.vue' - -const localVue = global.localVue - -describe('ChangePassword.vue', () => { - let mocks - - beforeEach(() => { - mocks = { - $toast: { - error: jest.fn(), - success: jest.fn(), - }, - $t: jest.fn(), - $store: { - commit: jest.fn(), - }, - $apollo: { - mutate: jest - .fn() - .mockRejectedValue({ message: 'Ouch!' }) - .mockResolvedValueOnce({ data: { changePassword: 'NEWTOKEN' } }), - }, - } - }) - - describe('mount', () => { - let wrapper - const Wrapper = () => { - return mount(ChangePassword, { mocks, localVue }) - } - - beforeEach(() => { - wrapper = Wrapper() - }) - - it('renders three input fields', () => { - expect(wrapper.findAll('input')).toHaveLength(3) - }) - - describe('validations', () => { - beforeEach(() => { - // $t must return strings so async-validator produces proper error messages - // (jest.fn() returns undefined which causes Error(undefined) → DsInputError type warning) - mocks.$t = jest.fn((key) => key) - wrapper = Wrapper() - }) - - describe('new password and confirmation', () => { - describe('mismatch', () => { - beforeEach(async () => { - await wrapper.find('input#oldPassword').setValue('oldsecret') - await wrapper.find('input#password').setValue('superdupersecret') - await wrapper.find('input#passwordConfirmation').setValue('different') - }) - - it('does not submit the form', async () => { - mocks.$apollo.mutate.mockReset() - await wrapper.find('form').trigger('submit') - await wrapper.vm.$nextTick() - expect(mocks.$apollo.mutate).not.toHaveBeenCalled() - }) - - it('displays a validation error', async () => { - await wrapper.find('form').trigger('submit') - await wrapper.vm.$nextTick() - expect(wrapper.vm.formErrors).toHaveProperty('passwordConfirmation') - }) - }) - - describe('match', () => { - beforeEach(async () => { - await wrapper.find('input#oldPassword').setValue('oldsecret') - await wrapper.find('input#password').setValue('superdupersecret') - await wrapper.find('input#passwordConfirmation').setValue('superdupersecret') - }) - - it('passes validation and submits', async () => { - mocks.$apollo.mutate.mockReset() - mocks.$apollo.mutate.mockResolvedValue({ data: { changePassword: 'TOKEN' } }) - await wrapper.find('form').trigger('submit') - expect(mocks.$apollo.mutate).toHaveBeenCalled() - }) - - describe('while mutation is pending', () => { - it('sets loading while mutation is pending', async () => { - mocks.$apollo.mutate.mockReset() - let resolvePromise - mocks.$apollo.mutate.mockReturnValue( - new Promise((resolve) => { - resolvePromise = resolve - }), - ) - - const promise = wrapper.vm.handleSubmit() - expect(wrapper.vm.loading).toBe(true) - - resolvePromise({ data: { changePassword: 'TOKEN' } }) - await promise - expect(wrapper.vm.loading).toBe(false) - }) - }) - }) - }) - }) - - describe('given valid input', () => { - beforeEach(() => { - wrapper.find('input#oldPassword').setValue('supersecret') - wrapper.find('input#password').setValue('superdupersecret') - wrapper.find('input#passwordConfirmation').setValue('superdupersecret') - }) - - describe('submit form', () => { - beforeEach(async () => { - await wrapper.find('form').trigger('submit') - }) - - it('calls changePassword mutation', () => { - expect(mocks.$apollo.mutate).toHaveBeenCalled() - }) - - it('passes form data as variables', () => { - expect(mocks.$apollo.mutate.mock.calls[0][0]).toEqual( - expect.objectContaining({ - variables: { - oldPassword: 'supersecret', - password: 'superdupersecret', - passwordConfirmation: 'superdupersecret', - }, - }), - ) - }) - - describe('mutation resolves', () => { - beforeEach(() => { - wrapper.find('form').trigger('submit') - }) - - it('calls auth/SET_TOKEN with response', () => { - expect(mocks.$store.commit).toHaveBeenCalledWith('auth/SET_TOKEN', 'NEWTOKEN') - }) - - it('displays success message', () => { - expect(mocks.$t).toHaveBeenCalledWith('settings.security.change-password.success') - expect(mocks.$toast.success).toHaveBeenCalled() - }) - }) - - describe('mutation rejects', () => { - beforeEach(async () => { - await wrapper.find('input#oldPassword').setValue('supersecret') - await wrapper.find('input#password').setValue('supersecret') - await wrapper.find('input#passwordConfirmation').setValue('supersecret') - await wrapper.find('form').trigger('submit') - }) - - it('displays error message', async () => { - expect(mocks.$toast.error).toHaveBeenCalledWith('Ouch!') - }) - }) - }) - }) - }) -}) diff --git a/webapp/components/Password/PasswordForm.spec.js b/webapp/components/Password/PasswordForm.spec.js new file mode 100644 index 000000000..8d58ac170 --- /dev/null +++ b/webapp/components/Password/PasswordForm.spec.js @@ -0,0 +1,183 @@ +import { mount } from '@vue/test-utils' +import PasswordForm from './PasswordForm.vue' + +const localVue = global.localVue + +describe('PasswordForm.vue', () => { + let mocks + + beforeEach(() => { + mocks = { + $t: jest.fn((key) => key), + } + }) + + const Wrapper = (props = {}) => { + return mount(PasswordForm, { mocks, localVue, propsData: props }) + } + + describe('without requireOldPassword', () => { + let wrapper + + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders two input fields (new password, confirm)', () => { + expect(wrapper.findAll('input')).toHaveLength(2) + }) + + it('does not render old password field', () => { + expect(wrapper.find('input#oldPassword').exists()).toBe(false) + }) + + it('renders new password field', () => { + expect(wrapper.find('input#password').exists()).toBe(true) + }) + + it('renders password confirmation field', () => { + expect(wrapper.find('input#passwordConfirmation').exists()).toBe(true) + }) + + it('renders submit button', () => { + expect(wrapper.find('button[type="submit"]').exists()).toBe(true) + }) + + it('renders password strength indicator', () => { + expect(wrapper.findComponent({ name: 'PasswordMeter' }).exists()).toBe(true) + }) + + describe('submit with empty fields', () => { + it('does not emit submit', async () => { + await wrapper.find('form').trigger('submit') + await wrapper.vm.$nextTick() + expect(wrapper.emitted('submit')).toBeFalsy() + }) + }) + + describe('submit with mismatched passwords', () => { + beforeEach(async () => { + await wrapper.find('input#password').setValue('supersecret') + await wrapper.find('input#passwordConfirmation').setValue('different') + }) + + it('does not emit submit', async () => { + await wrapper.find('form').trigger('submit') + await wrapper.vm.$nextTick() + expect(wrapper.emitted('submit')).toBeFalsy() + }) + }) + + describe('submit with valid input', () => { + beforeEach(async () => { + await wrapper.find('input#password').setValue('supersecret') + await wrapper.find('input#passwordConfirmation').setValue('supersecret') + }) + + it('emits submit with form data', async () => { + await wrapper.find('form').trigger('submit') + await wrapper.vm.$nextTick() + expect(wrapper.emitted('submit')).toBeTruthy() + expect(wrapper.emitted('submit')[0][0]).toEqual({ + password: 'supersecret', + passwordConfirmation: 'supersecret', + }) + }) + + it('sets loading state on submit', async () => { + await wrapper.find('form').trigger('submit') + await wrapper.vm.$nextTick() + expect(wrapper.vm.loading).toBe(true) + }) + }) + + describe('done()', () => { + it('resets loading and form fields', async () => { + await wrapper.find('input#password').setValue('supersecret') + await wrapper.find('input#passwordConfirmation').setValue('supersecret') + await wrapper.find('form').trigger('submit') + await wrapper.vm.$nextTick() + + wrapper.vm.done() + expect(wrapper.vm.loading).toBe(false) + expect(wrapper.vm.formData.password).toBe('') + expect(wrapper.vm.formData.passwordConfirmation).toBe('') + }) + }) + + describe('fail()', () => { + it('resets loading but keeps form data', async () => { + await wrapper.find('input#password').setValue('supersecret') + await wrapper.find('input#passwordConfirmation').setValue('supersecret') + await wrapper.find('form').trigger('submit') + await wrapper.vm.$nextTick() + + wrapper.vm.fail() + expect(wrapper.vm.loading).toBe(false) + expect(wrapper.vm.formData.password).toBe('supersecret') + }) + }) + }) + + describe('with requireOldPassword', () => { + let wrapper + + beforeEach(() => { + wrapper = Wrapper({ requireOldPassword: true }) + }) + + it('renders three input fields', () => { + expect(wrapper.findAll('input')).toHaveLength(3) + }) + + it('renders old password field', () => { + expect(wrapper.find('input#oldPassword').exists()).toBe(true) + }) + + describe('submit without old password', () => { + beforeEach(async () => { + await wrapper.find('input#password').setValue('supersecret') + await wrapper.find('input#passwordConfirmation').setValue('supersecret') + }) + + it('does not emit submit', async () => { + await wrapper.find('form').trigger('submit') + await wrapper.vm.$nextTick() + expect(wrapper.emitted('submit')).toBeFalsy() + }) + }) + + describe('submit with all fields valid', () => { + beforeEach(async () => { + await wrapper.find('input#oldPassword').setValue('oldsecret') + await wrapper.find('input#password').setValue('supersecret') + await wrapper.find('input#passwordConfirmation').setValue('supersecret') + }) + + it('emits submit with all form data including oldPassword', async () => { + await wrapper.find('form').trigger('submit') + await wrapper.vm.$nextTick() + expect(wrapper.emitted('submit')[0][0]).toEqual({ + oldPassword: 'oldsecret', + password: 'supersecret', + passwordConfirmation: 'supersecret', + }) + }) + }) + + describe('done()', () => { + it('also resets oldPassword field', async () => { + await wrapper.find('input#oldPassword').setValue('oldsecret') + await wrapper.find('input#password').setValue('supersecret') + await wrapper.find('input#passwordConfirmation').setValue('supersecret') + await wrapper.find('form').trigger('submit') + await wrapper.vm.$nextTick() + + wrapper.vm.done() + expect(wrapper.vm.formData.oldPassword).toBe('') + expect(wrapper.vm.formData.password).toBe('') + expect(wrapper.vm.formData.passwordConfirmation).toBe('') + }) + }) + }) +}) diff --git a/webapp/components/Password/Change.vue b/webapp/components/Password/PasswordForm.vue similarity index 63% rename from webapp/components/Password/Change.vue rename to webapp/components/Password/PasswordForm.vue index eb4f4c5e7..9eb482bec 100644 --- a/webapp/components/Password/Change.vue +++ b/webapp/components/Password/PasswordForm.vue @@ -1,6 +1,7 @@