From 4c7534af369f02572da13a124ec789a061b604f4 Mon Sep 17 00:00:00 2001 From: Geoffrey Kwan Date: Fri, 13 Oct 2023 18:47:37 -0400 Subject: [PATCH] feat(Password): Move password requirements to a menu that opens on focus #1049 --- .../new-password-and-confirm.component.html | 59 +++++++++++-------- .../new-password-and-confirm.component.scss | 9 +++ ...new-password-and-confirm.component.spec.ts | 15 ++++- .../new-password-and-confirm.component.ts | 15 +++++ .../new-password-and-confirm.harness.ts | 35 ++++++----- src/app/password/password.module.ts | 2 + 6 files changed, 92 insertions(+), 43 deletions(-) diff --git a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.html b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.html index 4ad95f19f22..4751882b9e3 100644 --- a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.html +++ b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.html @@ -1,12 +1,19 @@
- + {{ passwordLabel }} -
- -
-
Password Strength:
- -
Very Weak
-
Weak
-
Good
-
Strong
-
Very Strong
-
-
-
-
Your password needs to:
- + +
+ +
+
Password Strength:
+ +
Very Weak
+
Weak
+
Good
+
Strong
+
Very Strong
+
+
+
+
Your password needs to:
+ +
+
+

diff --git a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.scss b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.scss index a0e125c460d..59befd640ec 100644 --- a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.scss +++ b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.scss @@ -1,3 +1,12 @@ +.password-requirements-menu { + width: 300px; + margin: 20px; +} + +::ng-deep .mat-mdc-menu-panel { + max-width: none !important; +} + .password-strength-label { margin-right: 8px; } diff --git a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.spec.ts b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.spec.ts index 9f00385697f..658444bb3fc 100644 --- a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.spec.ts +++ b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.spec.ts @@ -10,10 +10,13 @@ import { NewPasswordAndConfirmHarness } from './new-password-and-confirm.harness import { PasswordModule } from '../password.module'; import { PasswordErrors } from '../../domain/password/password-errors'; import { PasswordRequirementComponent } from '../password-requirement/password-requirement.component'; +import { MatMenuModule } from '@angular/material/menu'; +import { HarnessLoader } from '@angular/cdk/testing'; let component: NewPasswordAndConfirmComponent; let fixture: ComponentFixture; let newPasswordAndConfirmHarness: NewPasswordAndConfirmHarness; +let rootLoader: HarnessLoader; describe('NewPasswordAndConfirmComponent', () => { beforeEach(async () => { @@ -24,6 +27,7 @@ describe('NewPasswordAndConfirmComponent', () => { MatFormFieldModule, MatIconModule, MatInputModule, + MatMenuModule, PasswordModule, ReactiveFormsModule ] @@ -36,6 +40,7 @@ describe('NewPasswordAndConfirmComponent', () => { fixture, NewPasswordAndConfirmHarness ); + rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture); }); newPasswordValidation(); confirmPasswordValidation(); @@ -107,9 +112,13 @@ function passwordErrorCases(): void { } async function checkPasswordRequirements(passwordErrors: PasswordErrors): Promise { - expect(await newPasswordAndConfirmHarness.isMissingLetter()).toBe(passwordErrors.missingLetter); - expect(await newPasswordAndConfirmHarness.isMissingNumber()).toBe(passwordErrors.missingNumber); - expect(await newPasswordAndConfirmHarness.isTooShort()).toBe(passwordErrors.tooShort); + expect(await newPasswordAndConfirmHarness.isMissingLetter(rootLoader)).toBe( + passwordErrors.missingLetter + ); + expect(await newPasswordAndConfirmHarness.isMissingNumber(rootLoader)).toBe( + passwordErrors.missingNumber + ); + expect(await newPasswordAndConfirmHarness.isTooShort(rootLoader)).toBe(passwordErrors.tooShort); } function confirmPasswordValidation() { diff --git a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts index a3ecb9adbb4..7cba65d6aa4 100644 --- a/src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts +++ b/src/app/password/new-password-and-confirm/new-password-and-confirm.component.ts @@ -7,6 +7,7 @@ import { ValidatorFn, Validators } from '@angular/forms'; +import { MatMenuTrigger } from '@angular/material/menu'; @Component({ selector: 'new-password-and-confirm', @@ -83,4 +84,18 @@ export class NewPasswordAndConfirmComponent implements OnInit { protected passwordStrengthChange(value: number): void { this.passwordStrength = value ? value : 0; } + + onNewPasswordFocus(menuTrigger: MatMenuTrigger): void { + // This setTimeout is required because sometimes when the user clicks on the input, it will + // trigger a blur and then a focus which can lead to the menu not opening. This makes sure that + // if a blur and a focus occur right after each other, the openMenu() will be called after the + // blur is complete. + setTimeout(() => { + menuTrigger.openMenu(); + }); + } + + onNewPasswordBlur(menuTrigger: MatMenuTrigger): void { + menuTrigger.closeMenu(); + } } diff --git a/src/app/password/new-password-and-confirm/new-password-and-confirm.harness.ts b/src/app/password/new-password-and-confirm/new-password-and-confirm.harness.ts index 7083f424513..8806b91ad0b 100644 --- a/src/app/password/new-password-and-confirm/new-password-and-confirm.harness.ts +++ b/src/app/password/new-password-and-confirm/new-password-and-confirm.harness.ts @@ -1,4 +1,4 @@ -import { ComponentHarness } from '@angular/cdk/testing'; +import { ComponentHarness, HarnessLoader } from '@angular/cdk/testing'; import { MatInputHarness } from '@angular/material/input/testing'; import { MatErrorHarness } from '@angular/material/form-field/testing'; import { PasswordRequirementHarness } from '../password-requirement/password-requirement.harness'; @@ -6,39 +6,42 @@ import { PasswordRequirementHarness } from '../password-requirement/password-req export class NewPasswordAndConfirmHarness extends ComponentHarness { static hostSelector = 'new-password-and-confirm'; - protected getNewPasswordInput = this.locatorFor( + getNewPasswordInput = this.locatorFor( MatInputHarness.with({ selector: 'input[name="newPassword"]' }) ); - protected getConfirmNewPasswordInput = this.locatorFor( + getConfirmNewPasswordInput = this.locatorFor( MatInputHarness.with({ selector: 'input[name="confirmNewPassword"]' }) ); - protected getNewPasswordRequiredError = this.locatorForOptional( + getNewPasswordRequiredError = this.locatorForOptional( MatErrorHarness.with({ selector: '.new-password-required-error' }) ); - protected getConfirmNewPasswordRequiredError = this.locatorForOptional( + getConfirmNewPasswordRequiredError = this.locatorForOptional( MatErrorHarness.with({ selector: '.confirm-new-password-required-error' }) ); - protected getConfirmNewPasswordDoesNotMatchError = this.locatorForOptional( + getConfirmNewPasswordDoesNotMatchError = this.locatorForOptional( MatErrorHarness.with({ selector: '.confirm-new-password-does-not-match-error' }) ); - protected getPasswordRequirements = this.locatorForAll(PasswordRequirementHarness); + getPasswordRequirements = this.locatorForAll(PasswordRequirementHarness); - async isMissingLetter(): Promise { - return this.isMissingRequirement('include a letter'); + async isMissingLetter(rootLoader: HarnessLoader): Promise { + return this.isMissingRequirement(rootLoader, 'include a letter'); } - async isMissingNumber(): Promise { - return this.isMissingRequirement('include a number'); + async isMissingNumber(rootLoader: HarnessLoader): Promise { + return this.isMissingRequirement(rootLoader, 'include a number'); } - async isTooShort(): Promise { - return this.isMissingRequirement('be at least 8 characters long'); + async isTooShort(rootLoader: HarnessLoader): Promise { + return this.isMissingRequirement(rootLoader, 'be at least 8 characters long'); } - private async isMissingRequirement(requirement: string): Promise { - const passwordRequirement = await this.locatorFor( + private async isMissingRequirement( + rootLoader: HarnessLoader, + requirement: string + ): Promise { + const passwordRequirement = await rootLoader.getHarness( PasswordRequirementHarness.with({ text: requirement }) - )(); + ); return await passwordRequirement.isFail(); } diff --git a/src/app/password/password.module.ts b/src/app/password/password.module.ts index 605414c6a09..9f3da55444f 100644 --- a/src/app/password/password.module.ts +++ b/src/app/password/password.module.ts @@ -8,6 +8,7 @@ import { NewPasswordAndConfirmComponent } from './new-password-and-confirm/new-p import { MatIconModule } from '@angular/material/icon'; import { PasswordStrengthMeterModule } from 'angular-password-strength-meter'; import { PasswordRequirementComponent } from './password-requirement/password-requirement.component'; +import { MatMenuModule } from '@angular/material/menu'; @NgModule({ imports: [ @@ -17,6 +18,7 @@ import { PasswordRequirementComponent } from './password-requirement/password-re MatFormFieldModule, MatIconModule, MatInputModule, + MatMenuModule, PasswordStrengthMeterModule.forRoot(), ReactiveFormsModule ],