All files / app/identity identity-forms.ts

100% Statements 13/13
100% Branches 2/2
100% Functions 3/3
100% Lines 13/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67                          1x 227x 227x   227x                                       1x   73x 73x                 1x 154x                 154x 40x 40x     154x    
import type { Signal } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import type { AbstractControl, ValidationErrors } from '@angular/forms';
 
import { PASSWORDS } from '@app/shared/constants';
import { controlErrorsSignal } from '@app/shared/control-errors-signal.util';
import { controlInvalidSignal } from '@app/shared/control-invalid-signal.util';
 
import { passwordFirebaseValidator, passwordStrengthValidator } from './validators/passwords';
 
/**
 * Pair control with Angular Signals for handling validation in the template.
 */
const getControlStructure = <T extends AbstractControl>(control: T): ControlStruct<T> => {
  const $invalid = controlInvalidSignal(control);
  const $errors = controlErrorsSignal(control);
 
  return { $errors, $invalid, control };
};
 
export { PASSWORDS };
 
/**
 * For each Identity Control generate two Signals for error handling.
 */
export interface ControlStruct<T extends AbstractControl = FormControl> {
  /** Returns errors for the control, but only when the control is dirty. */
  readonly $errors: Signal<ValidationErrors | undefined>;
  /** Flag for aria-invalid, but only when the control is modified, invalid, and interacted with. */
  readonly $invalid: Signal<boolean>;
  /** Identity control. */
  readonly control: T;
}
 
/**
 * Emails are required and must be a valid email address.
 */
export const createEmailControl = (): ControlStruct<FormControl> => {
  // eslint-disable-next-line unicorn/no-null -- DOM forms use null
  const control = new FormControl<string | null>(null, [ Validators.required, Validators.email ]);
  return getControlStructure(control);
};
 
/**
 * Passwords are required and have length requirements. Complexity is required for new password fields.
 * @param isNewPassword - Adds extra validators to control when being used to create a new password.
 *                      Note: this should only be used on the first password field, not the confirm
 *                      field.
 */
export const createPasswordControl = (isNewPassword: boolean = false): ControlStruct<FormControl> => {
  const control = new FormControl<string | null>(
    null, // eslint-disable-line unicorn/no-null -- DOM forms use null
    [
      Validators.required,
      Validators.minLength(PASSWORDS.minLength),
      Validators.maxLength(PASSWORDS.maxLength),
    ],
  );
 
  if (isNewPassword) {
    control.addValidators(passwordStrengthValidator);
    control.addAsyncValidators(passwordFirebaseValidator());
  }
 
  return getControlStructure(control);
};