Some checks failed
Nouveau composant `components/ui/PasswordInput` : un `<input>` de mot de passe avec un bouton œil (`lucide-react` Eye/EyeOff) qui bascule le `type` entre "password" et "text". Visibilité en état local, toujours masquée au montage, jamais persistée. Bouton `type="button"` (ne soumet pas le formulaire) + `aria-label` traduit (`common.passwordInput.*`) + `aria-pressed`. Branché dans `LoginPage` et `SignupPage` à la place du `<input type="password">` nu. La règle `.auth-card button` de `auth-form.scss` est resserrée sur `button[type="submit"]` pour que le bouton œil n'hérite pas du traitement plein-largeur du bouton de connexion. Tests : `cypress/component/PasswordInput.cy.tsx` (7 cas, composant isolé) + `cypress/e2e/auth-form.cy.ts` (mode layout, l'œil sur /login et /signup). Closes #134 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
50 lines
2 KiB
TypeScript
50 lines
2 KiB
TypeScript
// Mocks the API via cy.intercept — this job runs no live backend (see
|
|
// .github/workflows/ci.yml). The signup/login *journeys* live in
|
|
// auth.feature; this plain layout spec covers only the show/hide password
|
|
// toggle (PasswordInput) wired into both auth pages — "a section of an
|
|
// existing page" in specs/dev-conventions.md's test taxonomy, so a plain
|
|
// `.cy.ts`, not a Gherkin scenario.
|
|
|
|
describe("Auth forms — password show/hide toggle", () => {
|
|
beforeEach(() => {
|
|
// Both pages sit behind RedirectIfAuthenticated; a 401 on the session
|
|
// check keeps the visitor logged out so the form actually renders.
|
|
cy.intercept("GET", "**/auth/me", { statusCode: 401 });
|
|
});
|
|
|
|
for (const { label, path } of [
|
|
{ label: "login", path: "/login" },
|
|
{ label: "signup", path: "/signup" },
|
|
]) {
|
|
it(`reveals then re-masks the password field on ${label}`, () => {
|
|
cy.visit(path);
|
|
|
|
// Starts masked, with the toggle offering to reveal it.
|
|
cy.get("#password").should("have.attr", "type", "password");
|
|
cy.get('[aria-label="Afficher le mot de passe"]')
|
|
.should("be.visible")
|
|
.and("have.attr", "aria-pressed", "false");
|
|
|
|
// Reveal.
|
|
cy.get('[aria-label="Afficher le mot de passe"]').click();
|
|
cy.get("#password").should("have.attr", "type", "text");
|
|
cy.get('[aria-label="Masquer le mot de passe"]').should("have.attr", "aria-pressed", "true");
|
|
|
|
// Re-mask.
|
|
cy.get('[aria-label="Masquer le mot de passe"]').click();
|
|
cy.get("#password").should("have.attr", "type", "password");
|
|
});
|
|
}
|
|
|
|
it("does not submit the login form when the eye is clicked", () => {
|
|
cy.intercept("POST", "**/auth/login", cy.spy().as("loginRequest"));
|
|
cy.visit("/login");
|
|
|
|
cy.get("#email").type("alice@example.com");
|
|
cy.get("#password").type("correct-horse-battery-staple");
|
|
cy.get('[aria-label="Afficher le mot de passe"]').click();
|
|
|
|
cy.get("#password").should("have.attr", "type", "text");
|
|
cy.get("@loginRequest").should("not.have.been.called");
|
|
});
|
|
});
|