batchCooking/apps/web/cypress/component/CheckboxOption.cy.tsx
Nicolas 70a77ddf0f feat(web): infra Cypress Component Testing, premier test sur CheckboxOption
Étape 1 du refactor (voir PR) : met en place le vrai mode Component
Testing de Cypress, séparé de l'e2e — monte un composant React isolé
(pas de routeur, pas de backend), pour tester les composants
génériques (components/ui/) indépendamment de tout parcours
utilisateur.

- cypress.config.ts : nouveau bloc `component` (devServer Vite, réutilise
  vite.config.ts de l'appli — même plugin React, même Sass). Le hook
  GPU-disable est factorisé (`disableGpu`) puisque e2e et component ont
  chacun leur propre `setupNodeEvents`, pas de config partagée par défaut.
- cypress/support/component.ts + component-index.html : fichiers de
  support standards Cypress CT — importe le vrai global.scss de l'appli
  (les composants génériques sont stylés via lui, pas de CSS scopé à eux).
- cypress/component/CheckboxOption.cy.tsx : 4 scénarios sur
  components/ui/Checkbox.tsx (rendu du label, reflet du prop `checked`
  sur l'input natif + la classe `is-selected`, callback `onChange` avec
  la valeur inversée, comportement contrôlé via un wrapper avec état).

Dépendance ajoutée, épinglée : @cypress/vite-dev-server@5.2.1 (dernière
version sans peer dependency cypress >=14 — 6.0.3+ l'exige explicitement,
on est sur cypress@13.17.0).

Testé en local jusqu'au mur GPU/Electron habituel (config + devServer
Vite chargent sans erreur) — l'exécution réelle du montage reste à
vérifier via la CI.
2026-08-19 13:18:02 +02:00

68 lines
2.2 KiB
TypeScript

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(
<CheckboxOption checked={false} onChange={() => {}}>
Végétarien
</CheckboxOption>,
);
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(
<CheckboxOption checked={false} onChange={() => {}}>
Végétarien
</CheckboxOption>,
);
cy.get("input[type=checkbox]").should("not.be.checked");
cy.get("label").should("not.have.class", "is-selected");
cy.mount(
<CheckboxOption checked={true} onChange={() => {}}>
Végétarien
</CheckboxOption>,
);
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(
<CheckboxOption checked={false} onChange={onChange}>
Végétarien
</CheckboxOption>,
);
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 (
<CheckboxOption checked={checked} onChange={setChecked}>
Végétarien
</CheckboxOption>
);
}
cy.mount(<Wrapper />);
cy.get("input[type=checkbox]").should("not.be.checked").click();
cy.get("input[type=checkbox]").should("be.checked");
});
});