Files
jellytau/src/lib/utils/pinForm.test.ts
T
dtourolle 27a995f877 feat(profiles): manage PINs from settings
Settings had only an ask-on-start toggle and a button that bounced to the
picker, so there was nowhere to actually turn a PIN on or off -- the one
part of this feature that genuinely is configuration rather than a
front-door action.

Scoped to the active profile on purpose. Letting a signed-in session set
a PIN on someone else's profile is an escalation path with no real use:
a PIN-less profile could be locked by whoever is standing at the device,
and its owner pushed down the password route to get back into their own
account. Other profiles are listed read-only so a parent can see which
are protected; adding and removing them stays on the picker, which
settings now deep-links into with ?manage=1 rather than growing a second
copy of the tile list.

Form validation is extracted to pinForm.ts and unit-tested -- shape only,
so the form can say what is wrong before submitting. Rust validates again
and remains the only thing that ever compares a PIN.
2026-08-30 19:42:35 +02:00

95 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from "vitest";
import { validatePinForm, pinActionLabel, type PinFormState } from "./pinForm";
function form(overrides: Partial<PinFormState> = {}): PinFormState {
return {
intent: "set",
hasPin: false,
currentPin: "",
newPin: "",
confirmPin: "",
...overrides,
};
}
describe("validatePinForm", () => {
it("accepts a well-formed new PIN", () => {
expect(validatePinForm(form({ newPin: "1234", confirmPin: "1234" }))).toBeNull();
});
it("requires the current PIN whenever one is set", () => {
expect(
validatePinForm(form({ intent: "change", hasPin: true, newPin: "5678", confirmPin: "5678" })),
).toBe("Enter your current PIN.");
});
it("does not ask for a current PIN when none is set", () => {
expect(validatePinForm(form({ hasPin: false, newPin: "1234", confirmPin: "1234" }))).toBeNull();
});
it("rejects non-digits", () => {
expect(validatePinForm(form({ newPin: "12a4", confirmPin: "12a4" }))).toBe(
"A PIN can only contain digits.",
);
});
it("enforces the length range at both ends", () => {
expect(validatePinForm(form({ newPin: "123", confirmPin: "123" }))).toBe(
"A PIN must be 48 digits.",
);
expect(validatePinForm(form({ newPin: "123456789", confirmPin: "123456789" }))).toBe(
"A PIN must be 48 digits.",
);
});
it("catches a mistyped confirmation", () => {
expect(validatePinForm(form({ newPin: "1234", confirmPin: "1235" }))).toBe(
"The two PINs do not match.",
);
});
it("asks for confirmation before comparing", () => {
expect(validatePinForm(form({ newPin: "1234", confirmPin: "" }))).toBe("Confirm your new PIN.");
});
it("rejects a change that changes nothing", () => {
expect(
validatePinForm(
form({
intent: "change",
hasPin: true,
currentPin: "1234",
newPin: "1234",
confirmPin: "1234",
}),
),
).toBe("The new PIN is the same as the current one.");
});
it("needs only the current PIN to turn one off", () => {
expect(validatePinForm(form({ intent: "clear", hasPin: true, currentPin: "1234" }))).toBeNull();
});
it("will not turn off a PIN without the current one", () => {
expect(validatePinForm(form({ intent: "clear", hasPin: true, currentPin: "" }))).toBe(
"Enter your current PIN.",
);
});
it("reports one problem at a time, most blocking first", () => {
// Both the current PIN is missing *and* the new one is too short; the
// current-PIN prompt is the one that comes back.
expect(
validatePinForm(form({ intent: "change", hasPin: true, newPin: "1", confirmPin: "2" })),
).toBe("Enter your current PIN.");
});
});
describe("pinActionLabel", () => {
it("names each intent", () => {
expect(pinActionLabel("set")).toBe("Set PIN");
expect(pinActionLabel("change")).toBe("Change PIN");
expect(pinActionLabel("clear")).toBe("Turn off PIN");
});
});