mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 01:33:33 +00:00
refactor(set-change-password): [Auth/PM-17649] Move CompareInputs Validator (#14173)
This PR - Moves the `compareInputs` validator to `libs/auth` (with some minor updates to the validator) - Adds unit tests for `compareInputs` - Removes the deprecated input validators from `InputsFieldMatch` along with the `inputs-field-match.validator.ts` file
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
import { FormControl, FormGroup, ValidationErrors } from "@angular/forms";
|
||||
|
||||
import { compareInputs, ValidationGoal } from "./compare-inputs.validator";
|
||||
|
||||
describe("compareInputs", () => {
|
||||
let validationErrorsObj: ValidationErrors;
|
||||
|
||||
beforeEach(() => {
|
||||
// Use a fresh object for each test so that a mutation in one test doesn't affect another test
|
||||
validationErrorsObj = {
|
||||
compareInputsError: {
|
||||
message: "Custom error message",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it("should throw an error if compareInputs is not being applied to a FormGroup", () => {
|
||||
// Arrange
|
||||
const notAFormGroup = new FormControl("form-control");
|
||||
|
||||
// Act
|
||||
const validatorFn = compareInputs(
|
||||
ValidationGoal.InputsShouldMatch,
|
||||
"ctrlA",
|
||||
"ctrlB",
|
||||
"Custom error message",
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(() => validatorFn(notAFormGroup)).toThrow(
|
||||
"compareInputs only supports validation at the FormGroup level",
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw an error if either control is not found", () => {
|
||||
// Arrange
|
||||
const formGroupMissingControl = new FormGroup({
|
||||
ctrlA: new FormControl("content"),
|
||||
});
|
||||
|
||||
// Act
|
||||
const validatorFn = compareInputs(
|
||||
ValidationGoal.InputsShouldMatch,
|
||||
"ctrlA",
|
||||
"ctrlB", // ctrlB is missing above
|
||||
"Custom error message",
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(() => validatorFn(formGroupMissingControl)).toThrow(
|
||||
"[compareInputs validator] one or both of the specified controls could not be found in the form group",
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw an error if the name of one of the form controls is incorrect or mispelled", () => {
|
||||
// Arrange
|
||||
const formGroupMissingControl = new FormGroup({
|
||||
ctrlA: new FormControl("content"),
|
||||
ctrlB: new FormControl("content"),
|
||||
});
|
||||
|
||||
// Act
|
||||
const validatorFn = compareInputs(
|
||||
ValidationGoal.InputsShouldMatch,
|
||||
"ctrlA",
|
||||
"ctrlC", // ctrlC is incorrect (mimics a developer misspelling a form control name)
|
||||
"Custom error message",
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(() => validatorFn(formGroupMissingControl)).toThrow(
|
||||
"[compareInputs validator] one or both of the specified controls could not be found in the form group",
|
||||
);
|
||||
});
|
||||
|
||||
it("should return null if both controls have empty string values", () => {
|
||||
// Arrange
|
||||
const formGroup = new FormGroup({
|
||||
ctrlA: new FormControl(""),
|
||||
ctrlB: new FormControl(""),
|
||||
});
|
||||
|
||||
// Act
|
||||
const validatorFn = compareInputs(
|
||||
ValidationGoal.InputsShouldMatch,
|
||||
"ctrlA",
|
||||
"ctrlB",
|
||||
"Custom error message",
|
||||
);
|
||||
|
||||
const result = validatorFn(formGroup);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should call setErrors() on ctrlB if validation fails", () => {
|
||||
// Arrange
|
||||
const formGroup = new FormGroup({
|
||||
ctrlA: new FormControl("apple"),
|
||||
ctrlB: new FormControl("banana"),
|
||||
});
|
||||
|
||||
const ctrlBSetErrorsSpy = jest.spyOn(formGroup.controls.ctrlB, "setErrors");
|
||||
|
||||
// Act
|
||||
const validatorFn = compareInputs(
|
||||
ValidationGoal.InputsShouldMatch,
|
||||
"ctrlA",
|
||||
"ctrlB",
|
||||
"Custom error message",
|
||||
);
|
||||
|
||||
validatorFn(formGroup);
|
||||
|
||||
// Assert
|
||||
expect(ctrlBSetErrorsSpy).toHaveBeenCalledWith(validationErrorsObj);
|
||||
});
|
||||
|
||||
it("should call setErrors() on ctrlA if validation fails and 'showErrorOn' is set to 'controlA'", () => {
|
||||
// Arrange
|
||||
const formGroup = new FormGroup({
|
||||
ctrlA: new FormControl("apple"),
|
||||
ctrlB: new FormControl("banana"),
|
||||
});
|
||||
|
||||
const ctrlASetErrorsSpy = jest.spyOn(formGroup.controls.ctrlA, "setErrors");
|
||||
const ctrlBSetErrorsSpy = jest.spyOn(formGroup.controls.ctrlB, "setErrors");
|
||||
|
||||
// Act
|
||||
const validatorFn = compareInputs(
|
||||
ValidationGoal.InputsShouldMatch,
|
||||
"ctrlA",
|
||||
"ctrlB",
|
||||
"Custom error message",
|
||||
"controlA",
|
||||
);
|
||||
|
||||
validatorFn(formGroup);
|
||||
|
||||
// Assert
|
||||
expect(ctrlASetErrorsSpy).toHaveBeenCalledWith(validationErrorsObj);
|
||||
expect(ctrlBSetErrorsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not call setErrors() on ctrlB if validation passes and there is not a pre-existing error on ctrlB", () => {
|
||||
// Arrange
|
||||
const formGroup = new FormGroup({
|
||||
ctrlA: new FormControl("apple"),
|
||||
ctrlB: new FormControl("apple"),
|
||||
});
|
||||
|
||||
const ctrlBSetErrorsSpy = jest.spyOn(formGroup.controls.ctrlB, "setErrors");
|
||||
|
||||
// Act
|
||||
const validatorFn = compareInputs(
|
||||
ValidationGoal.InputsShouldMatch,
|
||||
"ctrlA",
|
||||
"ctrlB",
|
||||
"Custom error message",
|
||||
);
|
||||
|
||||
validatorFn(formGroup);
|
||||
|
||||
// Assert
|
||||
expect(ctrlBSetErrorsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call setErrors(null) on ctrlB if validation passes and there is a pre-existing error on ctrlB", () => {
|
||||
// Arrange
|
||||
const formGroup = new FormGroup({
|
||||
ctrlA: new FormControl("apple"),
|
||||
ctrlB: new FormControl("apple"),
|
||||
});
|
||||
|
||||
const ctrlBSetErrorsSpy = jest.spyOn(formGroup.controls.ctrlB, "setErrors");
|
||||
|
||||
formGroup.controls.ctrlB.setErrors(validationErrorsObj); // the pre-existing error
|
||||
|
||||
// Act
|
||||
const validatorFn = compareInputs(
|
||||
ValidationGoal.InputsShouldMatch,
|
||||
"ctrlA",
|
||||
"ctrlB",
|
||||
"Custom error message",
|
||||
);
|
||||
|
||||
validatorFn(formGroup);
|
||||
|
||||
// Assert
|
||||
expect(ctrlBSetErrorsSpy).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
const cases = [
|
||||
{
|
||||
expected: null,
|
||||
goal: ValidationGoal.InputsShouldMatch,
|
||||
matchStatus: "match",
|
||||
values: { ctrlA: "apple", ctrlB: "apple" },
|
||||
},
|
||||
{
|
||||
expected: "a ValidationErrors object",
|
||||
goal: ValidationGoal.InputsShouldMatch,
|
||||
matchStatus: "do not match",
|
||||
values: { ctrlA: "apple", ctrlB: "banana" },
|
||||
},
|
||||
{
|
||||
expected: null,
|
||||
goal: ValidationGoal.InputsShouldNotMatch,
|
||||
matchStatus: "do not match",
|
||||
values: { ctrlA: "apple", ctrlB: "banana" },
|
||||
},
|
||||
{
|
||||
expected: "a ValidationErrors object",
|
||||
goal: ValidationGoal.InputsShouldNotMatch,
|
||||
matchStatus: "match",
|
||||
values: { ctrlA: "apple", ctrlB: "apple" },
|
||||
},
|
||||
];
|
||||
|
||||
cases.forEach(({ goal, expected, matchStatus, values }) => {
|
||||
const goalString =
|
||||
goal === ValidationGoal.InputsShouldMatch ? "InputsShouldMatch" : "InputsShouldNotMatch";
|
||||
|
||||
it(`should return ${expected} if the goal is ${goalString} and the inputs ${matchStatus}`, () => {
|
||||
// Arrange
|
||||
const formGroup = new FormGroup({
|
||||
ctrlA: new FormControl(values.ctrlA),
|
||||
ctrlB: new FormControl(values.ctrlB),
|
||||
});
|
||||
|
||||
// Act
|
||||
const validatorFn = compareInputs(goal, "ctrlA", "ctrlB", "Custom error message");
|
||||
|
||||
const result = validatorFn(formGroup);
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(expected === null ? null : validationErrorsObj);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user