1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-08 20:50:28 +00:00
Files
browser/libs/auth/src/angular/validators/compare-inputs.validator.ts
Patrick-Pimentel-Bitwarden 0b0397c3f0 fix(enums-eslint): Enum Rule for ESLint (#14650)
* fix(enums-eslint): Enum Rule for ESLint - Added enums in the warnings for eslint.

* fix(enums-eslint): Enum Rule for ESLint - Updated to error in both places for enums.

* fix(enums-eslint): Enum Rule for ESLint - Added new eslint plugin for warning on enums.

* fix(enums-eslint): Enum Rule for ESLint - Changed based on suggestion.

Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>

* refactor(browser-platform-utils): Remove Deprecation and Fix Code - Changed usages of firefox to private and moved the usages to the preferred public method and removed the deprecations.

* fix(enums-eslint): Enum Rule for ESLint - Updated to error and added disable rules for all other places.

* fix(enums-eslint): Enum Rule for ESLint - Undid other changes by accident
2025-05-13 10:07:38 -04:00

139 lines
4.8 KiB
TypeScript

import { AbstractControl, FormGroup, ValidationErrors, ValidatorFn } from "@angular/forms";
// FIXME: update to use a const object instead of a typescript enum
// eslint-disable-next-line @bitwarden/platform/no-enums
export enum ValidationGoal {
InputsShouldMatch,
InputsShouldNotMatch,
}
/**
* A cross-field validator that evaluates whether two form controls do or do
* not have the same input value (except for empty string values). This validator
* gets added to the entire FormGroup, not to an individual FormControl, like so:
*
* > ```
* > formGroup = new FormGroup({
* > password: new FormControl(),
* > confirmPassword: new FormControl(),
* > },
* > {
* > validators: compareInputs(...),
* > });
* > ```
*
* Notes:
* - Validation is controlled from either form control.
* - The error message is displayed under controlB by default, but can be set to controlA.
* - For more info on custom validators and cross-field validation:
* - https://v18.angular.dev/guide/forms/form-validation#defining-custom-validators
* - https://v18.angular.dev/guide/forms/form-validation#cross-field-validation
*
* @param validationGoal Whether you want to verify that the form controls do or do not have matching input values.
* @param controlNameA The name of the first form control to compare.
* @param controlNameB The name of the second form control to compare.
* @param errorMessage The error message to display if there is an error. This will probably
* be an i18n translated string.
* @param showErrorOn The control under which you want to display the error (default is controlB).
*
* @returns A validator function that can be used on a FormGroup.
*/
export function compareInputs(
validationGoal: ValidationGoal,
controlNameA: string,
controlNameB: string,
errorMessage: string,
showErrorOn: "controlA" | "controlB" = "controlB",
): ValidatorFn {
/**
* Documentation for the inner ValidatorFn that gets returned:
*
* @param formGroup The AbstractControl that we want to perform validation on. In this case we
* perform validation on the FormGroup, which is a subclass of AbstractControl.
* The reason we validate at the FormGroup level and not at the FormControl level
* is because we want to compare two child FormControls in a single validator, so
* we use the FormGroup as the common ancestor.
*
* @returns A ValidationErrors object if the validation fails, or null if the validation passes.
*/
return (formGroup: AbstractControl): ValidationErrors | null => {
if (!(formGroup instanceof FormGroup)) {
throw new Error("compareInputs only supports validation at the FormGroup level");
}
const controlA = formGroup.get(controlNameA);
const controlB = formGroup.get(controlNameB);
if (!controlA || !controlB) {
throw new Error(
"[compareInputs validator] one or both of the specified controls could not be found in the form group",
);
}
const controlThatShowsError = showErrorOn === "controlA" ? controlA : controlB;
// Don't compare empty strings
if (controlA.value === "" && controlB.value === "") {
return pass();
}
const controlValuesMatch = controlA.value === controlB.value;
if (validationGoal === ValidationGoal.InputsShouldMatch) {
if (controlValuesMatch) {
return pass();
} else {
return fail();
}
}
if (validationGoal === ValidationGoal.InputsShouldNotMatch) {
if (!controlValuesMatch) {
return pass();
} else {
return fail();
}
}
return null; // default return
function fail() {
controlThatShowsError.setErrors({
// Preserve any pre-existing errors
...(controlThatShowsError.errors || {}),
// Add new compareInputsError
compareInputsError: {
message: errorMessage,
},
});
return {
compareInputsError: {
message: errorMessage,
},
};
}
function pass(): null {
// Get the current errors object
const errorsObj = controlThatShowsError?.errors;
if (errorsObj != null) {
// Remove any compareInputsError if it exists, since that is the sole error we are targeting with this validator
if (errorsObj?.compareInputsError) {
delete errorsObj.compareInputsError;
}
// Check if the errorsObj is now empty
const isEmptyObj = Object.keys(errorsObj).length === 0;
// If the errorsObj is empty, set errors to null, otherwise set the errors to an object of pre-existing errors (other than compareInputsError)
controlThatShowsError.setErrors(isEmptyObj ? null : errorsObj);
}
// Return null for this validator
return null;
}
};
}