// 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"); }); });