Tests: couverture Cypress pour la grille de planning (step 4/4)

- home-planning.cy.ts → planning-page.cy.ts : nav sidebar reprise
  telle quelle, nouveaux cas pour la grille (case vide, recettes
  placées dans les bonnes cases, colonne du jour courant, navigation
  semaine précédente/suivante, popover calendrier)
- cy.clock fige "aujourd'hui" (2026-08-17, un lundi) pour des
  assertions de date déterministes ; cy.viewport élargi (desktop-only,
  cf. décision produit) pour que les 7 colonnes tiennent sans scroll
  horizontal lors des assertions de visibilité
This commit is contained in:
Nicolas 2026-08-17 14:19:39 +02:00
parent 07c3851957
commit 9d4de338e3
2 changed files with 163 additions and 104 deletions

View file

@ -1,104 +0,0 @@
// Mocks the API via cy.intercept — see auth.cy.ts for the rationale (no
// live backend in this CI job; apps/api's own Mocha/Cucumber suites cover
// real API behavior against a real database).
const authenticatedProfile = {
id: 1,
firstName: "Alice",
lastName: "Martin",
email: "alice@example.com",
tokenVersion: 0,
houseId: 1,
dietId: null,
};
describe("Sidebar navigation", () => {
beforeEach(() => {
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
cy.intercept("GET", "**/planning/current", { 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");
});
it("shows the signed-in user's name and lets them log out from the account menu", () => {
cy.intercept("POST", "**/auth/logout", { statusCode: 204 }).as("logout");
cy.contains("button", "Bonjour Alice").should("be.visible").click();
cy.contains("button", "Se déconnecter").click();
cy.wait("@logout");
cy.url().should("include", "/login");
});
});
describe("Home planning view", () => {
it("shows an empty state when the household has no current planning", () => {
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
cy.intercept("GET", "**/planning/current", { statusCode: 200, body: null });
cy.visit("/");
cy.contains("h1", "Planning de la semaine").should("be.visible");
cy.contains("Aucun planning pour cette semaine.").should("be.visible");
cy.get("table").should("not.exist");
});
it("renders the current planning's meals when there is one", () => {
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
cy.intercept("GET", "**/planning/current", {
statusCode: 200,
body: {
id: 1,
startDate: "2026-08-10T00:00:00.000Z",
finishDate: "2026-08-16T00:00:00.000Z",
items: [
{ id: 1, weekDay: "lundi", meal: "Dîner", recipe: { id: 1, name: "Ratatouille" } },
{
id: 2,
weekDay: "mardi",
meal: "Déjeuner",
recipe: { id: 2, name: "Curry de lentilles" },
},
],
},
});
cy.visit("/");
cy.contains("Aucun planning pour cette semaine.").should("not.exist");
cy.get("table.planning-table tbody tr").should("have.length", 2);
cy.contains("td", "Ratatouille").should("be.visible");
cy.contains("td", "Curry de lentilles").should("be.visible");
});
it("shows an error state when the planning request fails", () => {
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
cy.intercept("GET", "**/planning/current", {
statusCode: 500,
body: { code: 5000, message: "boom" },
});
cy.visit("/");
cy.contains("Impossible de charger le planning, réessayez plus tard").should("be.visible");
});
});

View file

@ -0,0 +1,163 @@
// Mocks the API via cy.intercept — see auth.cy.ts for the rationale (no
// live backend in this CI job; apps/api's own Mocha/Cucumber suites cover
// real API behavior against a real database).
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");
});
it("shows the signed-in user's name and lets them log out from the account menu", () => {
cy.intercept("POST", "**/auth/logout", { statusCode: 204 }).as("logout");
cy.contains("button", "Bonjour Alice").should("be.visible").click();
cy.contains("button", "Se déconnecter").click();
cy.wait("@logout");
cy.url().should("include", "/login");
});
});
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", recipe: { id: 1, name: "Ratatouille" } },
{
id: 2,
weekDay: "mercredi",
meal: "dejeuner",
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");
});
});