diff --git a/app/javascript/__tests__/password_confirmation.test.js b/app/javascript/__tests__/password_confirmation.test.js new file mode 100644 index 0000000000..13b001506f --- /dev/null +++ b/app/javascript/__tests__/password_confirmation.test.js @@ -0,0 +1,62 @@ +/* eslint-env jest */ +import { checkPasswordsAndDisplayPopup } from '../src/password_confirmation' + +require('jest') + +let submitButton, passwordField, confirmationField + +beforeEach(() => { + document.body.innerHTML = ` +
+
+
+ +
+
+
+ +
+ +
+ ` + submitButton = document.getElementsByClassName('submit-password')[0] + passwordField = document.getElementsByClassName('password-new')[0] + confirmationField = document.getElementsByClassName('password-confirmation')[0] + + submitButton.disabled = true + submitButton.classList.add('disabled') + submitButton.setAttribute('aria-disabled', true) +}) + +describe('ensure the password field matches the confirmation field on the client side', () => { + function isDisabled (el) { + return el.disabled && el.classList.contains('disabled') && el.hasAttribute('aria-disabled') + } + + test('password fields match', () => { + passwordField.value = '123456' + confirmationField.value = '123456' + checkPasswordsAndDisplayPopup(submitButton, passwordField, confirmationField) + expect(isDisabled(submitButton)).toBe(false) + expect(document.getElementsByClassName('swal2-container').length).toBe(0) + }) + + test("password fields don't match", () => { + passwordField.value = '123456' + confirmationField.value = 'bad' + checkPasswordsAndDisplayPopup(submitButton, passwordField, confirmationField, true) + expect(document.getElementsByClassName('swal2-container').length).toBe(1) + }) + + test("password fields don't match and pop-up suppressed", () => { + passwordField.value = '123456' + confirmationField.value = 'bad' + checkPasswordsAndDisplayPopup(submitButton, passwordField, confirmationField) + expect(document.getElementsByClassName('swal2-container').length).toBe(0) + }) + + test('password fields match but are empty', () => { + expect(isDisabled(submitButton)).toBe(true) + expect(document.getElementsByClassName('swal2-container').length).toBe(0) + }) +}) diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 5a4c69b240..0ed0e4b696 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -31,6 +31,7 @@ require('src/dashboard') require('src/sidebar') require('src/readMore') require('src/tooltip') +require('src/password_confirmation') require('src/reports') // Uncomment to copy all static images under ../images to the output folder and diff --git a/app/javascript/src/password_confirmation.js b/app/javascript/src/password_confirmation.js new file mode 100644 index 0000000000..3a66f671a6 --- /dev/null +++ b/app/javascript/src/password_confirmation.js @@ -0,0 +1,61 @@ +/* eslint-env jquery */ + +import Swal from 'sweetalert2' + +import { disableBtn, enableBtn } from './casa_case' + +const SUBMIT_BUTTON_CLASS = 'submit-password' +const PASSWORD_FIELD_CLASS = 'password-new' +const CONFIRMATION_FIELD_CLASS = 'password-confirmation' + +function disableButtonWhenEmptyString (str, button) { + str.length === 0 ? disableBtn(button) : enableBtn(button) +} + +// Checks if the password is equivalent to confirmation and has at least 1 character. If not, +// it will disable the button. +// @param {HTMLElement} button - submit button for the form field +// @param {HTMLElement} password - text input form field +// @param {HTMLElement} confirmation - text input form field +// @param {boolean} enablePopup - display popup when field is not in focus +function checkPasswordsAndDisplayPopup (button, password, confirmation, enablePopup = false) { + const passwordText = password.value + const confirmationText = confirmation.value + + if (passwordText === confirmationText) { + disableButtonWhenEmptyString(passwordText, button) + } else { + if (enablePopup) { + Swal.fire({ + icon: 'error', + title: 'Password Error', + text: 'The password and the confirmation password do not match' + }) + } + disableBtn(button) + } +} + +// Expects the class name constants above are applied to the correct fields. See +// `app/views/users/edit.html.erb` for usage +$('document').ready(() => { + if ($(`.${SUBMIT_BUTTON_CLASS}`).length > 0) { + const button = $(`.${SUBMIT_BUTTON_CLASS}`)[0] + const password = $(`.${PASSWORD_FIELD_CLASS}`)[0] + const confirmation = $(`.${CONFIRMATION_FIELD_CLASS}`)[0] + + disableBtn(button) + + $(`.${PASSWORD_FIELD_CLASS}`).on('blur', () => { + checkPasswordsAndDisplayPopup(button, password, confirmation) + }) + + $(`.${CONFIRMATION_FIELD_CLASS}`).on('blur', () => { + checkPasswordsAndDisplayPopup(button, password, confirmation, true) + }) + } +}) + +export { + checkPasswordsAndDisplayPopup +} diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb index 845b6c09a5..435bb8918b 100644 --- a/app/views/devise/passwords/edit.html.erb +++ b/app/views/devise/passwords/edit.html.erb @@ -21,7 +21,7 @@ class: "form-control", required: true %> <%= f.password_field :password, autofocus: true, autocomplete: "new-password", - class: "form-control", + class: "form-control password-new", required: true, minlength: 6 %> @@ -30,13 +30,13 @@ class: "form-control", required: true %> <%= f.label :password_confirmation, "Confirm new password" %>
<%= f.password_field :password_confirmation, autocomplete: "new-password", - class: "form-control", + class: "form-control password-confirmation", required: true, minlength: 6 %>
- <%= f.submit "Change my password", class: "btn btn-primary" %> + <%= f.submit "Change my password", class: "btn btn-primary submit-password" %>
<% end %> diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index c5e4877fd3..83fa32a251 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -49,14 +49,14 @@
<%= f.label :password, "New Password" %>
- <%= f.password_field :password, autocomplete: "off", class: "form-control", minlength: 6 %> + <%= f.password_field :password, autocomplete: "off", class: "form-control password-new", minlength: 6 %>
<%= f.label :password_confirmation, "New Password Confirmation" %>
- <%= f.password_field :password_confirmation, class: "form-control", minlength: 6 %> + <%= f.password_field :password_confirmation, class: "form-control password-confirmation", minlength: 6 %>
- <%= f.submit "Update Password", class: "btn btn-danger" %> + <%= f.submit "Update Password", class: "btn btn-danger submit-password" %>
<% end %> diff --git a/cypress/integration/volunteer/edit_profile.spec.js b/cypress/integration/volunteer/edit_profile.spec.js index 8edd233f5e..9bbafa48cb 100644 --- a/cypress/integration/volunteer/edit_profile.spec.js +++ b/cypress/integration/volunteer/edit_profile.spec.js @@ -9,7 +9,9 @@ context("Logging into cypress as a volunteer", () => { cy.contains("Change Password").click(); cy.contains("Current Password").should("exist"); cy.contains("New Password").should("exist"); + cy.get(".password-new").type("123456") cy.contains("New Password Confirmation").should("exist"); + cy.get(".password-confirmation").type("123456") cy.contains("Update Profile").click(); cy.contains("Profile was successfully updated.").should("exist"); });