import { useState } from "react"; import "../../src/i18n/i18n"; import { PasswordInput } from "../../src/components/ui/PasswordInput"; // Mounts PasswordInput in isolation (no form, no router, no backend) — the // same "generic component test" posture as CheckboxOption.cy.tsx. Needs // `../../src/i18n/i18n` imported for its side effect: unlike Checkbox/Radio, // this component calls `useTranslation()` for the eye button's aria-label, // and with no in the tree it falls back to that default // instance (see TechStepCorrectionPopover.cy.tsx for the same note). const SHOW_LABEL = "Afficher le mot de passe"; const HIDE_LABEL = "Masquer le mot de passe"; /** * Controlled wrapper — PasswordInput holds no text state of its own, so a * real caller (LoginPage, SignupPage) always owns the value. `onSpy` lets a * test assert on what each change reports. */ function Wrapper({ onSpy }: { onSpy?: (value: string) => void }) { const [value, setValue] = useState(""); return ( { setValue(next); onSpy?.(next); }} autoComplete="current-password" /> ); } describe("PasswordInput", () => { it("renders a masked password field by default", () => { cy.mount(); cy.get("input#password").should("have.attr", "type", "password"); }); it("forwards id and autoComplete to the native input", () => { cy.mount(); cy.get("input#password").should("have.attr", "autocomplete", "current-password"); }); it("reveals the text when the eye is clicked, then re-masks it on a second click", () => { cy.mount(); cy.get("input#password").type("hunter2").should("have.attr", "type", "password"); cy.get("button.password-input__toggle").click(); cy.get("input#password").should("have.attr", "type", "text").and("have.value", "hunter2"); cy.get("button.password-input__toggle").click(); cy.get("input#password").should("have.attr", "type", "password").and("have.value", "hunter2"); }); it("labels the toggle with the action the next click performs, and exposes state via aria-pressed", () => { cy.mount(); cy.get("button.password-input__toggle") .should("have.attr", "aria-label", SHOW_LABEL) .and("have.attr", "aria-pressed", "false"); cy.get("button.password-input__toggle").click(); cy.get("button.password-input__toggle") .should("have.attr", "aria-label", HIDE_LABEL) .and("have.attr", "aria-pressed", "true"); }); it("is a controlled input — it shows the parent's value and reports every change", () => { const onSpy = cy.stub().as("onSpy"); cy.mount(); cy.get("input#password").type("abc"); cy.get("input#password").should("have.value", "abc"); cy.get("@onSpy").should("have.been.calledWith", "abc"); }); it("does not submit a surrounding form when the eye is clicked — the toggle is a plain button", () => { const onSubmit = cy.stub().as("onSubmit"); cy.mount(
{ e.preventDefault(); onSubmit(); }} > , ); cy.get("button.password-input__toggle").click(); cy.get("@onSubmit").should("not.have.been.called"); }); it("starts hidden again on a fresh mount — visibility is never persisted", () => { cy.mount(); cy.get("button.password-input__toggle").click(); cy.get("input#password").should("have.attr", "type", "text"); // A brand-new mount, as if the visitor navigated back to the page later. cy.mount(); cy.get("input#password").should("have.attr", "type", "password"); }); });