- Bouton "Commencer a cuisiner" dans l'en-tete du planning, actif seulement quand la semaine affichee contient >= 1 recette ; navigue vers /cuisiner?date=<semaine>. - Page CookingSessionPage (/cuisiner) : consomme GET /cooking-session, rend les phases (mise en place / cuisson / dressage), les taches mutualisees avec badge "Mutualise" + ingredients/ustensiles resolus, et la bande "Pendant ce temps" pour les cuissons de fond. Semaine lue depuis ?date=, WeekNavigator en fallback ; recalcul a chaque visite (comme la liste de courses). - Logique pure extraite dans cooking-session.ts (composition des libelles, formatage des quantites). - i18n : bloc cookingSession.* + planning.startCooking. - apiClient.getCookingPlanForWeek. - Tests Cypress : cooking-session-page.cy.ts (layout, 3 cas), cooking-session .feature (parcours planning -> /cuisiner), assertions bouton dans planning-page.cy.ts ; step generique "button should be disabled" mutualise. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
174 lines
5.2 KiB
TypeScript
174 lines
5.2 KiB
TypeScript
// 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");
|
|
});
|
|
});
|