diff --git a/apps/web/cypress/e2e/cooking-session-page.cy.ts b/apps/web/cypress/e2e/cooking-session-page.cy.ts index 0c2580f..aeff9c7 100644 --- a/apps/web/cypress/e2e/cooking-session-page.cy.ts +++ b/apps/web/cypress/e2e/cooking-session-page.cy.ts @@ -30,14 +30,14 @@ function ingredient(key: string) { }; } -/** A plan with a pooled prep task in mise-en-place and a passive cook floated into a later phase. */ +/** A 3-phase plan: a pooled prep task in mise-en-place, then two cooking phases, the last with a passive cook floated into its background. */ 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 }, + { recipeId: 2, name: "Tarte à l'oignon", portions: 6 }, ], phases: [ { @@ -60,7 +60,7 @@ function planFixture() { utensils: [{ id: 1, key: "knife" }], sourceRecipes: [ { recipeId: 1, name: "Soupe à l'oignon", portions: 4 }, - { recipeId: 2, name: "Tarte à l'oignon", portions: 4 }, + { recipeId: 2, name: "Tarte à l'oignon", portions: 6 }, ], originalSteps: [], }, @@ -103,7 +103,7 @@ function planFixture() { description: "Enfourner la tarte", ingredients: [], utensils: [], - sourceRecipes: [{ recipeId: 2, name: "Tarte à l'oignon", portions: 4 }], + sourceRecipes: [{ recipeId: 2, name: "Tarte à l'oignon", portions: 6 }], originalSteps: [], }, ], @@ -136,7 +136,7 @@ describe("Cooking session page", () => { cy.contains("Rien de planifié cette semaine à cuisiner").should("be.visible"); }); - it("renders each phase, the pooled prep task, and the 'meanwhile' band", () => { + it("opens on the Plan tab: a stepper of every phase, only the first phase's tasks shown", () => { cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: planFixture() }).as( "getPlan", ); @@ -144,21 +144,86 @@ describe("Cooking session page", () => { 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"); + // One stepper node per phase; the first is the current one. + cy.get(".cooking-stepper__node").should("have.length", 3); + cy.get(".cooking-stepper__node").first().should("have.class", "cooking-stepper__node--current"); + cy.contains(".cooking-plan__position", "Étape 1 / 3").should("be.visible"); + cy.contains(".cooking-plan__count", "0 / 1 tâches").should("be.visible"); + + // Mise en place: the pooled prep task, flagged shared, naming both recipes. 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. + // The next phase's step is not rendered yet. + cy.contains("Faire mijoter le bouillon").should("not.exist"); + + // Prev is disabled on the first phase. + cy.contains(".cooking-plan__nav-btn", "Étape précédente").should("be.disabled"); + }); + + it("walks the phases with the next/prev buttons and by clicking a stepper node", () => { + cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: planFixture() }); + cy.visit("/cuisiner?date=2026-08-17"); + + // Forward to phase 2. + cy.contains(".cooking-plan__nav-btn", "Étape suivante").click(); + cy.contains(".cooking-plan__position", "Étape 2 / 3").should("be.visible"); + cy.contains(".cooking-phase__tasks", "Faire mijoter le bouillon").should("be.visible"); + + // Forward to phase 3 — its background band shows the soup still simmering. + cy.contains(".cooking-plan__nav-btn", "Étape suivante").click(); + cy.contains(".cooking-plan__position", "Étape 3 / 3").should("be.visible"); cy.contains(".cooking-phase__background", "Pendant ce temps") .should("contain.text", "Faire mijoter le bouillon") .and("contain.text", "Soupe à l'oignon"); + cy.contains(".cooking-phase__tasks", "Enfourner la tarte").should("be.visible"); + cy.contains(".cooking-plan__nav-btn", "Étape suivante").should("be.disabled"); - // Recipe legend is present. - cy.contains(".cooking-session__legend-item", "Tarte à l'oignon").should("be.visible"); + // Back one step. + cy.contains(".cooking-plan__nav-btn", "Étape précédente").click(); + cy.contains(".cooking-plan__position", "Étape 2 / 3").should("be.visible"); + + // Jump straight back to the first phase from the stepper. + cy.get(".cooking-stepper__node").first().click(); + cy.contains(".cooking-plan__position", "Étape 1 / 3").should("be.visible"); + cy.get(".cooking-task--merged-prep").should("be.visible"); + }); + + it("ticks a task, marks its phase done in the stepper, and keeps the tick after a reload", () => { + cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: planFixture() }); + cy.visit("/cuisiner?date=2026-08-17"); + + cy.get(".cooking-task").first().find('input[type="checkbox"]').check({ force: true }); + cy.get(".cooking-task").first().should("have.class", "cooking-task--done"); + // The whole phase is now done — its stepper node reflects that. + cy.get(".cooking-stepper__node").first().should("have.class", "cooking-stepper__node--done"); + cy.contains(".cooking-plan__count", "1 / 1 tâches").should("be.visible"); + + cy.reload(); + + cy.get(".cooking-task").first().should("have.class", "cooking-task--done"); + cy.get(".cooking-stepper__node").first().should("have.class", "cooking-stepper__node--done"); + }); + + it("lists the week's recipes on the Recettes tab, each linking to its own page", () => { + cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: planFixture() }); + cy.visit("/cuisiner?date=2026-08-17"); + + cy.contains(".cooking-session-page__tab", "Recettes").click(); + + cy.get(".cooking-recipes__item").should("have.length", 2); + cy.contains(".cooking-recipes__item", "Soupe à l'oignon") + .find("a") + .should("have.attr", "href", "/recettes/1"); + cy.contains(".cooking-recipes__item", "Tarte à l'oignon") + .should("contain.text", "6 portions") + .find("a") + .should("have.attr", "href", "/recettes/2"); + + // The stepper isn't part of this tab. + cy.get(".cooking-stepper").should("not.exist"); }); it("shows an error state when the request fails", () => { diff --git a/apps/web/cypress/e2e/cooking-session.feature b/apps/web/cypress/e2e/cooking-session.feature index e7f3b67..754c51e 100644 --- a/apps/web/cypress/e2e/cooking-session.feature +++ b/apps/web/cypress/e2e/cooking-session.feature @@ -22,3 +22,34 @@ Feature: Start cooking an optimized plan And I should see "Mise en place" And the pooled prep task should mention "Hacher" and "Oignon" And the pooled prep task should be flagged as shared + + Scenario: The stepper walks the phases one at a time + Given the planning for this week has recipes "Soupe à l'oignon" and "Tarte à l'oignon" + And the cooking plan for "2026-08-17" has a mise en place then a cooking step + When I visit "/cuisiner?date=2026-08-17" + Then the current cooking step should be "Étape 1 / 2" + And I should not see "Faire mijoter le bouillon" + When I go to the next cooking step + Then the current cooking step should be "Étape 2 / 2" + And I should see "Faire mijoter le bouillon" + When I go to the previous cooking step + Then the current cooking step should be "Étape 1 / 2" + + Scenario: A ticked task stays ticked while moving through the stepper + Given the planning for this week has recipes "Soupe à l'oignon" and "Tarte à l'oignon" + And the cooking plan for "2026-08-17" has a mise en place then a cooking step + When I visit "/cuisiner?date=2026-08-17" + And I tick the cooking task "Hacher" + Then the cooking task "Hacher" should be done + When I go to the next cooking step + And I go to the previous cooking step + Then the cooking task "Hacher" should be done + + Scenario: The Recettes tab lists the week's planned recipes + Given the planning for this week has recipes "Soupe à l'oignon" and "Tarte à l'oignon" + And the cooking plan for "2026-08-17" has a mise en place then a cooking step + When I visit "/cuisiner?date=2026-08-17" + And I open the "Recettes" cooking tab + Then I should see "Soupe à l'oignon" + And the recipe "Soupe à l'oignon" should link to "/recettes/1" + And the recipe "Tarte à l'oignon" should link to "/recettes/2" diff --git a/apps/web/cypress/e2e/cooking-session.ts b/apps/web/cypress/e2e/cooking-session.ts index c249fab..9c36ae2 100644 --- a/apps/web/cypress/e2e/cooking-session.ts +++ b/apps/web/cypress/e2e/cooking-session.ts @@ -1,4 +1,4 @@ -import { Given, Then } from "@badeball/cypress-cucumber-preprocessor"; +import { Given, Then, When } from "@badeball/cypress-cucumber-preprocessor"; /** Bare `IngredientView` — only `key` drives the page's `catalog.ingredients.*` lookup. */ function ingredient(key: string) { @@ -99,3 +99,94 @@ Then( Then("the pooled prep task should be flagged as shared", () => { cy.get(".cooking-task--merged-prep").contains("Mutualisé").should("be.visible"); }); + +Given("the cooking plan for {string} has a mise en place then a cooking step", (date: string) => { + // Two phases so the stepper actually has somewhere to step to: the + // pooled `chop onion` prep, then a `simmer` cooking step. + cy.intercept("GET", `**/cooking-session?date=${date}`, { + statusCode: 200, + body: { + startDate: `${date}T00: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: 6 }, + ], + 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: [], + sourceRecipes: [ + { recipeId: 1, name: "Soupe à l'oignon", portions: 4 }, + { recipeId: 2, name: "Tarte à l'oignon", portions: 6 }, + ], + 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: [], + }, + ], + }, + ], + }, + }); +}); + +When("I open the {string} cooking tab", (label: string) => { + cy.contains(".cooking-session-page__tab", label).click(); +}); + +// The task card is a `