Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,27 @@
:title="$tr('changePasswordHeader')"
:submitText="$tr('saveChangesAction')"
:cancelText="$tr('cancelAction')"
@submit="submitPassword"
@submit="submit"
@cancel="dialog = false"
>
<!-- inline style here avoids scrollbar on validations -->
<VForm
ref="form"
style="height: 196px"
>
<PasswordField
<form ref="form">
<KTextbox
v-model="password"
:additionalRules="passwordValidationRules"
type="password"
:label="$tr('newPasswordLabel')"
:invalid="errors.password"
:invalidText="$tr('passwordValidationMessage')"
:showInvalidText="true"
/>
<PasswordField
<KTextbox
v-model="confirmation"
type="password"
:label="$tr('confirmNewPasswordLabel')"
:additionalRules="passwordConfirmRules"
:invalid="errors.confirmation"
:invalidText="$tr('formInvalidText')"
:showInvalidText="true"
/>
</VForm>
</form>
</KModal>

</template>
Expand All @@ -32,23 +34,33 @@
<script>

import { mapActions } from 'vuex';
import PasswordField from 'shared/views/form/PasswordField';
import { generateFormMixin } from 'shared/mixins';

const formMixin = generateFormMixin({
password: {
required: true,
// eslint-disable-next-line no-unused-vars
validator: (value, _vm) => {
return value.length >= 8;
},
},
confirmation: {
required: true,
validator: (value, vm) => {
return value === vm.password;
},
},
});

export default {
name: 'ChangePasswordForm',
components: { PasswordField },
mixins: [formMixin],
props: {
value: {
type: Boolean,
default: false,
},
},
data() {
return {
password: '',
confirmation: '',
};
},
computed: {
dialog: {
get() {
Expand All @@ -58,36 +70,30 @@
this.$emit('input', value);
},
},
passwordValidationRules() {
return [value => (value.length >= 8 ? true : this.$tr('passwordValidationMessage'))];
},
passwordConfirmRules() {
return [value => (this.password === value ? true : this.$tr('formInvalidText'))];
},
},
watch: {
value(value) {
// Reset values on open
if (value) {
this.password = '';
this.confirmation = '';
this.reset();
}
},
},
methods: {
...mapActions(['showSnackbar']),
...mapActions('settings', ['updateUserPassword']),
submitPassword() {
if (this.$refs.form.validate()) {
return this.updateUserPassword(this.password)
.then(() => {
this.dialog = false;
this.showSnackbar({ text: this.$tr('paswordChangeSuccess') });
})
.catch(() => {
this.showSnackbar({ text: this.$tr('passwordChangeFailed') });
});
}

// This is called from formMixin
// eslint-disable-next-line kolibri/vue-no-unused-methods, vue/no-unused-properties
onSubmit() {
return this.updateUserPassword(this.password)
.then(() => {
this.dialog = false;
this.showSnackbar({ text: this.$tr('paswordChangeSuccess') });
})
.catch(() => {
this.showSnackbar({ text: this.$tr('passwordChangeFailed') });
});
},
},
$trs: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,81 @@ function makeWrapper() {
}

describe('changePasswordForm', () => {
let wrapper;

beforeEach(() => {
wrapper = makeWrapper();
it('should render the form', () => {
const wrapper = makeWrapper();
expect(wrapper.html()).toContain('Change password');
expect(wrapper.html()).toContain('New password');
expect(wrapper.html()).toContain('Confirm new password');
expect(wrapper.html()).toContain('Save changes');
expect(wrapper.html()).toContain('Cancel');
});

it('validation should fail if passwords do not match', async () => {
await wrapper.setData({ password: 'test' });
expect(typeof wrapper.vm.passwordConfirmRules[0]('data')).toBe('string');
describe('if a password is too short', () => {
let updateUserPassword;
let wrapper;

beforeAll(async () => {
wrapper = makeWrapper();
updateUserPassword = jest.spyOn(wrapper.vm, 'updateUserPassword');
const passwordInputs = wrapper.findAll('input[type="password"]');
passwordInputs.at(0).setValue('pw');
wrapper.findComponent({ name: 'KModal' }).vm.$emit('submit');
await wrapper.vm.$nextTick();
});

it('should show a correct error message', () => {
expect(wrapper.html()).toContain('Password should be at least 8 characters long');
});

it('should not call updateUserPassword', () => {
expect(updateUserPassword).not.toHaveBeenCalled();
});
});

it('failed validation should not call updateUserPassword', async () => {
const updateUserPassword = jest.spyOn(wrapper.vm, 'updateUserPassword');
updateUserPassword.mockImplementation(() => Promise.resolve());
await wrapper.vm.submitPassword();
expect(updateUserPassword).not.toHaveBeenCalled();
describe(`if passwords don't match`, () => {
let updateUserPassword;
let wrapper;

beforeAll(async () => {
wrapper = makeWrapper();
updateUserPassword = jest.spyOn(wrapper.vm, 'updateUserPassword');
const passwordInputs = wrapper.findAll('input[type="password"]');
passwordInputs.at(0).setValue('password1');
passwordInputs.at(1).setValue('password2');
wrapper.findComponent({ name: 'KModal' }).vm.$emit('submit');
await wrapper.vm.$nextTick();
});

it('should show a correct error message', () => {
expect(wrapper.html()).toContain(`Passwords don't match`);
});

it('should not call updateUserPassword', () => {
expect(updateUserPassword).not.toHaveBeenCalled();
});
});

it('clicking submit should call updateUserPassword', async () => {
const updateUserPassword = jest.spyOn(wrapper.vm, 'updateUserPassword');
updateUserPassword.mockImplementation(() => Promise.resolve());
await wrapper.setData({ password: 'tester123', confirmation: 'tester123' });
await wrapper.vm.submitPassword();
expect(updateUserPassword).toHaveBeenCalled();
describe('if passwords match and are valid', () => {
let updateUserPassword;
let wrapper;

beforeAll(async () => {
wrapper = makeWrapper();
updateUserPassword = jest.spyOn(wrapper.vm, 'updateUserPassword');
const passwordInputs = wrapper.findAll('input[type="password"]');
passwordInputs.at(0).setValue('password123');
passwordInputs.at(1).setValue('password123');
wrapper.findComponent({ name: 'KModal' }).vm.$emit('submit');
await wrapper.vm.$nextTick();
});

it('should not show any error messages', () => {
expect(wrapper.html()).not.toContain('Password should be at least 8 characters long');
expect(wrapper.html()).not.toContain(`Passwords don't match`);
});

it('should call updateUserPassword', () => {
expect(updateUserPassword).toHaveBeenCalled();
});
});
});