// 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 // `GET /cooking-session` behavior (including the optimizer) 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 so "this week" is deterministic. const TODAY = new Date("2026-08-17T09:00:00Z"); /** Bare `IngredientView` — only `key` drives the page's label lookup. */ function ingredient(key: string) { return { id: 1, key, icon: "VEGETABLE", category: "freshProduce", subcategory: "vegetables", reproducible: false, allergens: [], diets: [], }; } /** A plan with a pooled prep task in mise-en-place and a passive cook floated into a later phase. */ function planFixture() { return { startDate: "2026-08-17T00:00:00.000Z", finishDate: "2026-08-23T00:00:00.000Z", recipes: [ { recipeId: 1, name: "Soupe à l'oignon", portions: 4 }, { recipeId: 2, name: "Tarte à l'oignon", portions: 4 }, ], phases: [ { index: 0, kind: "mise-en-place", background: [], tasks: [ { id: "prep:chop:onion", kind: "merged-prep", technique: { id: 1, key: "chop" }, description: null, ingredients: [ { ingredient: ingredient("onion"), quantity: 5, unit: { id: 2, key: "piece", type: "COUNT", toBaseFactor: 1 }, }, ], utensils: [{ id: 1, key: "knife" }], sourceRecipes: [ { recipeId: 1, name: "Soupe à l'oignon", portions: 4 }, { recipeId: 2, name: "Tarte à l'oignon", portions: 4 }, ], originalSteps: [], }, ], }, { index: 1, kind: "cooking", background: [], tasks: [ { id: "step:0:1", kind: "step", technique: { id: 5, key: "simmer" }, description: "Faire mijoter le bouillon", ingredients: [], utensils: [], sourceRecipes: [{ recipeId: 1, name: "Soupe à l'oignon", portions: 4 }], originalSteps: [], }, ], }, { index: 2, kind: "cooking", background: [ { id: "bg:step:0:1", technique: { id: 5, key: "simmer" }, description: "Faire mijoter le bouillon", recipeId: 1, recipeName: "Soupe à l'oignon", }, ], tasks: [ { id: "step:1:3", kind: "step", technique: { id: 4, key: "bake" }, description: "Enfourner la tarte", ingredients: [], utensils: [], sourceRecipes: [{ recipeId: 2, name: "Tarte à l'oignon", portions: 4 }], originalSteps: [], }, ], }, ], }; } describe("Cooking session page", () => { beforeEach(() => { cy.viewport(1400, 900); cy.clock(TODAY, ["Date"]); cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile }); }); it("shows the empty message when nothing is planned that week", () => { cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: { startDate: "2026-08-17T00:00:00.000Z", finishDate: "2026-08-23T00:00:00.000Z", recipes: [], phases: [], }, }); cy.visit("/cuisiner"); cy.contains("h1", "Cuisiner cette semaine").should("be.visible"); cy.contains("Rien de planifié cette semaine à cuisiner").should("be.visible"); }); it("renders each phase, the pooled prep task, and the 'meanwhile' band", () => { cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: planFixture() }).as( "getPlan", ); cy.visit("/cuisiner?date=2026-08-17"); cy.wait("@getPlan").its("request.url").should("include", "date=2026-08-17"); // Mise en place: one pooled prep task, flagged shared, naming both recipes. cy.contains(".cooking-phase", "Mise en place").should("be.visible"); cy.get(".cooking-task--merged-prep") .should("contain.text", "Hacher") .and("contain.text", "Oignon") .and("contain.text", "Mutualisé"); cy.contains(".cooking-task--merged-prep", "Soupe à l'oignon").should("exist"); // A later phase shows the simmering soup as still running in the background. cy.contains(".cooking-phase__background", "Pendant ce temps") .should("contain.text", "Faire mijoter le bouillon") .and("contain.text", "Soupe à l'oignon"); // Recipe legend is present. cy.contains(".cooking-session__legend-item", "Tarte à l'oignon").should("be.visible"); }); it("shows an error state when the request fails", () => { cy.intercept("GET", /\/cooking-session\?/, { statusCode: 500, body: { code: 5000, message: "boom" }, }); cy.visit("/cuisiner"); cy.contains("Impossible de charger").should("be.visible"); }); });