GET /shopping-list?date= (shopping-list.service.ts/.routes.ts) somme les ingrédients de chaque recette planifiée sur la semaine, mis à l'échelle par les portions de chaque créneau (PlanningItem.portions / Recipe.portions), regroupés par paire (ingredientId, unitId) — jamais null contrairement à GET /planning, une semaine vide redescend en items: []. Côté web, ShoppingListPage rend cette liste groupée par rayon (même IngredientCategory que IngredientPicker), triée alphabétiquement en français à l'intérieur d'un rayon (shopping-list.ts, logique pure extraite du composant). WeekNavigator (flèches + calendrier) est extrait de PlanningPage vers features/planning/ pour être partagé entre les deux pages ; ses libellés migrent de planning.* vers common.weekNav.*/ common.calendar.*/common.days.*, plus génériques pour une page qui n'est plus seulement le planning. ComingSoonPage retiré (plus aucun appelant, Liste de courses avait le dernier stub restant). Tests : Mocha (agrégation, mise à l'échelle par portions, unités non fusionnées) + Cucumber (shopping-list.feature : liste vide, groupement/tri, navigation de semaine) + mise à jour de layout.cy.ts/planning-page.cy.ts pour le nouveau rendu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
72 lines
2.7 KiB
TypeScript
72 lines
2.7 KiB
TypeScript
import { type DataTable, Given, Then, When } from "@badeball/cypress-cucumber-preprocessor";
|
|
|
|
/**
|
|
* One data-table row → a fake `ShoppingListItemView` — same minimal-fixture
|
|
* convention as `recipe-form.ts`'s ingredient fixtures (only the fields
|
|
* `ShoppingListPage` actually reads at runtime: the ingredient's `key`/
|
|
* `icon`/`category` for `IngredientTypeIcon`/`CategoryIcon`/translation, the
|
|
* unit's `key`; `id` only needs to be unique per row for the React list
|
|
* key). `index` seeds both ids so two rows never collide.
|
|
*/
|
|
function buildShoppingListItem(
|
|
row: { ingredientKey: string; icon: string; category: string; quantity: string; unitKey: string },
|
|
index: number,
|
|
) {
|
|
return {
|
|
ingredient: {
|
|
id: index,
|
|
key: row.ingredientKey,
|
|
icon: row.icon,
|
|
category: row.category,
|
|
subcategory: row.category,
|
|
reproducible: false,
|
|
allergens: [],
|
|
diets: [],
|
|
},
|
|
quantity: Number(row.quantity),
|
|
unit: { id: index, key: row.unitKey, type: "MASS", toBaseFactor: 1 },
|
|
};
|
|
}
|
|
|
|
Given("the shopping list for {string} is empty", (date: string) => {
|
|
cy.intercept("GET", `**/shopping-list?date=${date}`, {
|
|
statusCode: 200,
|
|
body: { startDate: date, finishDate: date, items: [] },
|
|
});
|
|
});
|
|
|
|
Given("the shopping list for {string} contains:", (date: string, dataTable: DataTable) => {
|
|
const items = dataTable.hashes().map((row, i) => buildShoppingListItem(row, i + 1));
|
|
cy.intercept("GET", `**/shopping-list?date=${date}`, {
|
|
statusCode: 200,
|
|
body: { startDate: date, finishDate: date, items },
|
|
});
|
|
});
|
|
|
|
// Same class as PlanningPage's own week navigator (`WeekNavigator`, now
|
|
// shared between the two pages) — `planning-page.cy.ts` already exercises
|
|
// the prev/next arrows directly by class, same approach here.
|
|
When("I click the next week arrow", () => {
|
|
cy.get(".week-nav__arrow").last().click();
|
|
});
|
|
|
|
Then(
|
|
"the shopping list group {string} should appear before {string}",
|
|
(first: string, second: string) => {
|
|
cy.get(".shopping-list__group-title").then(($titles) => {
|
|
const texts = [...$titles].map((el) => el.textContent?.trim() ?? "");
|
|
const firstIndex = texts.findIndex((text) => text.includes(first));
|
|
const secondIndex = texts.findIndex((text) => text.includes(second));
|
|
expect(firstIndex, `"${first}" should be a rendered group`).to.be.greaterThan(-1);
|
|
expect(secondIndex, `"${second}" should be a rendered group`).to.be.greaterThan(-1);
|
|
expect(firstIndex).to.be.lessThan(secondIndex);
|
|
});
|
|
},
|
|
);
|
|
|
|
Then(
|
|
"the shopping list should show {string} at quantity {string}",
|
|
(name: string, quantity: string) => {
|
|
cy.contains(".shopping-list__item", name).should("contain.text", quantity);
|
|
},
|
|
);
|