batchCooking/apps/web/cypress/e2e/planning-page.cy.ts
Nicolas c29648e293 fix(ci): corrige lint/tests cassés par la migration portions
- Dialog.tsx : remplace le div role="dialog" par un <dialog> natif
  (showModal) — corrige lint/a11y/useSemanticElements, récupère
  gratuitement le piège de focus et l'Échap natifs. Le clic extérieur
  est rebranché en imperative addEventListener pour éviter
  lint/a11y/useKeyWithClickEvents sur un élément non interactif.
- RecipePickerDialog.tsx : retire l'autoFocus (lint/a11y/noAutofocus),
  ordre des imports/formatage corrigés par `biome check --write`.
- apps/api/test/planning.test.ts, apps/api/test/recipe.test.ts :
  les fixtures qui créent un `PlanningItem` directement via Prisma
  n'avaient pas le nouveau champ `portions` requis.
- apps/web/cypress/e2e/planning-page.cy.ts : ajoute `portions` aux
  items mockés pour rester fidèle au contrat `PlanningItemView`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 14:56:50 +02:00

163 lines
6.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Mocks the API via cy.intercept — this job doesn't run a live backend (see
// .github/workflows/ci.yml); apps/api's own Mocha suite covers real API
// behavior against a real database.
//
// The logout journey (also reachable from here, via the account menu) moved
// to auth.feature — same behavior, no need to cover it twice.
const authenticatedProfile = {
id: 1,
firstName: "Alice",
lastName: "Martin",
email: "alice@example.com",
tokenVersion: 0,
houseId: 1,
dietId: null,
};
// 2026-08-17 is a Monday — frozen via `cy.clock` so "today"/"this week"
// assertions are deterministic instead of depending on the day the suite
// happens to run.
const TODAY = new Date("2026-08-17T09:00:00Z");
function freezeToday() {
cy.clock(TODAY, ["Date"]);
}
describe("Sidebar navigation", () => {
beforeEach(() => {
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
cy.intercept("GET", /\/planning\?/, { statusCode: 200, body: null });
cy.visit("/");
});
// The Foyer/Compte/Préférences links — behind the sidebar's "Paramètres"
// toggle, not the main nav tested here — are covered by sidebar.cy.ts.
it("highlights the current section and navigates between stub pages", () => {
cy.contains("nav a", "Planning").should("have.class", "active");
cy.contains("nav a", "Recettes").click();
cy.url().should("include", "/recettes");
cy.contains("h1", "Recettes").should("be.visible");
cy.contains("nav a", "Recettes").should("have.class", "active");
cy.contains("nav a", "Planning").should("not.have.class", "active");
cy.contains("nav a", "Liste de courses").click();
cy.url().should("include", "/liste-de-courses");
cy.contains("h1", "Liste de courses").should("be.visible");
cy.contains("nav a", "Planning").click();
cy.url().should("eq", `${Cypress.config().baseUrl}/`);
cy.contains("h1", "Planning de la semaine").should("be.visible");
});
});
describe("Planning grid", () => {
beforeEach(() => {
// Desktop-only design (see the plan/PR description) — wider than
// Cypress's default 1000×660 so all 7 day columns fit without the grid's
// horizontal scroll hiding the later ones from visibility assertions.
cy.viewport(1600, 900);
freezeToday();
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
});
it("shows an empty grid (every slot just offering '+') when the household has no planning yet", () => {
cy.intercept("GET", /\/planning\?/, { statusCode: 200, body: null }).as("getPlanning");
cy.visit("/");
cy.wait("@getPlanning").its("request.url").should("include", "date=2026-08-17");
cy.contains("h1", "Planning de la semaine").should("be.visible");
// 5 meal rows × 7 days = 35 empty slots, each just a "+".
cy.get(".add-recipe-btn").should("have.length", 35);
cy.get(".recipe-chip").should("not.exist");
});
it("renders each recipe in its (day, meal) cell, and highlights today's column", () => {
cy.intercept("GET", /\/planning\?/, {
statusCode: 200,
body: {
id: 1,
startDate: "2026-08-17T00:00:00.000Z",
finishDate: "2026-08-23T00:00:00.000Z",
items: [
{
id: 1,
weekDay: "mardi",
meal: "diner",
portions: 4,
recipe: { id: 1, name: "Ratatouille" },
},
{
id: 2,
weekDay: "mercredi",
meal: "dejeuner",
portions: 2,
recipe: { id: 2, name: "Curry de lentilles" },
},
],
},
});
cy.visit("/");
cy.contains("th", "Lundi").should("be.visible");
cy.contains("th", "Dimanche").should("be.visible");
cy.contains(".recipe-chip", "Ratatouille").should("be.visible");
cy.contains(".recipe-chip", "Curry de lentilles").should("be.visible");
// Today (17 août, Lundi) is marked — its column header carries `.today`.
cy.contains("th.today .day-date", "17").should("be.visible");
});
it("shows a loading state, then an error state when the request fails", () => {
cy.intercept("GET", /\/planning\?/, {
statusCode: 500,
body: { code: 5000, message: "boom" },
});
cy.visit("/");
cy.contains("Impossible de charger le planning, réessayez plus tard").should("be.visible");
});
// Assertions below check the rendered week label/badge, not the intercepted
// request count — React StrictMode (see main.tsx) double-invokes effects in
// dev, so the `GET /planning` mount effect can fire twice per navigation;
// counting exact `cy.wait` calls against that would be flaky, but the
// rendered result is the same either way.
it("navigates to the next/previous week, re-fetching each time", () => {
cy.intercept("GET", /\/planning\?/, { statusCode: 200, body: null }).as("getPlanning");
cy.visit("/");
cy.wait("@getPlanning").its("request.url").should("include", "date=2026-08-17");
cy.contains("Semaine du 17 au 23 août 2026").should("be.visible");
cy.contains("Cette semaine").should("be.visible");
cy.get(".week-nav__arrow").last().click();
cy.contains("Semaine du 24 au 30 août 2026").should("be.visible");
cy.contains("Cette semaine").should("not.exist");
cy.get(".week-nav__arrow").first().click();
cy.contains("Semaine du 17 au 23 août 2026").should("be.visible");
cy.get(".week-nav__arrow").first().click();
cy.contains("Semaine du 10 au 16 août 2026").should("be.visible");
});
it("jumps to an arbitrary week by picking a day in the calendar popover", () => {
cy.intercept("GET", /\/planning\?/, { statusCode: 200, body: null }).as("getPlanning");
cy.visit("/");
cy.wait("@getPlanning");
cy.contains("button", "Semaine du").click();
cy.get(".calendar-popover").should("be.visible");
// Picking the 25th (still August, unambiguous in the visible grid)
// should jump to the week of the 24th30th.
cy.get(".calendar-grid__day").contains(/^25$/).click();
cy.contains("Semaine du 24 au 30 août 2026").should("be.visible");
cy.get(".calendar-popover").should("not.exist");
});
});