import { useState } from "react"; import { CheckboxOption } from "../../src/components/ui/Checkbox"; // First real component test — mounts CheckboxOption in isolation (no // router, no backend), unlike everything under cypress/e2e/ which always // visits a full routed page. See cypress.config.ts's `component` block. describe("CheckboxOption", () => { it("renders its label content", () => { cy.mount( {}}> Végétarien , ); cy.contains("label", "Végétarien").should("be.visible"); }); it("reflects the checked prop on the native input, and the is-selected class", () => { cy.mount( {}}> Végétarien , ); cy.get("input[type=checkbox]").should("not.be.checked"); cy.get("label").should("not.have.class", "is-selected"); cy.mount( {}}> Végétarien , ); cy.get("input[type=checkbox]").should("be.checked"); cy.get("label").should("have.class", "is-selected"); }); it("calls onChange with the toggled value when clicked", () => { const onChange = cy.stub().as("onChange"); cy.mount( Végétarien , ); cy.get("input[type=checkbox]").click(); cy.get("@onChange").should("have.been.calledOnceWith", true); }); it("is a controlled component — stays checked only while the parent says so", () => { // A tiny stateful wrapper, since CheckboxOption itself takes no // internal state — this is what actually exercises the checked/onChange // contract the way a real caller (AllergySelect, the theme picker…) // would. function Wrapper() { const [checked, setChecked] = useState(false); return ( Végétarien ); } cy.mount(); cy.get("input[type=checkbox]").should("not.be.checked").click(); cy.get("input[type=checkbox]").should("be.checked"); }); it("calls onChange with false when clicking while already checked", () => { const onChange = cy.stub().as("onChange"); cy.mount( Végétarien , ); cy.get("input[type=checkbox]").click(); cy.get("@onChange").should("have.been.calledOnceWith", false); }); it("merges the caller's className with the container layout, alongside is-selected", () => { cy.mount( {}} className="allergy-select__option"> Végétarien , ); cy.get("label") .should("have.class", "allergy-select__option") .and("not.have.class", "is-selected"); cy.mount( {}} className="allergy-select__option"> Végétarien , ); cy.get("label").should("have.class", "allergy-select__option").and("have.class", "is-selected"); }); it("has no className at all when the caller doesn't pass one", () => { cy.mount( {}}> Végétarien , ); // `[className, checked && "is-selected"].filter(Boolean).join(" ")` with // both falsy collapses to "" — worth pinning down since a stray // "false"/"null" string in `class` would be a real (if harmless-looking) // regression. cy.get("label").should("have.attr", "class", ""); }); it("toggles when the click lands on the label text, not just the input itself", () => { // The label wraps the input (native HTML forwards the click), which is // what actually makes the whole row clickable — not just a tiny // checkbox hitbox. This is real browser behavior, not something the // component's own code implements, but it's exactly the contract the // "selectable card" look (global.scss) relies on, so it's worth pinning // down here rather than trusting it silently. const onChange = cy.stub().as("onChange"); cy.mount( Végétarien , ); cy.contains("label", "Végétarien").click(); cy.get("@onChange").should("have.been.calledOnceWith", true); }); it("marks the check-mark decoration as aria-hidden, so screen readers only announce the checkbox itself", () => { cy.mount( {}}> Végétarien , ); cy.get("span.check-mark").should("have.attr", "aria-hidden", "true"); }); });