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 }, }); }); // A "selon le goût" line — `quantity`/`unit` both null (issue #128). Its own // step rather than a `buildShoppingListItem` data-table column so that // helper's `quantity`/`unitKey` contract stays required for every other row. Given( "the shopping list for {string} has a « selon le goût » line for {string}", (date: string, ingredientKey: string) => { cy.intercept("GET", `**/shopping-list?date=${date}`, { statusCode: 200, body: { startDate: date, finishDate: date, items: [ { ingredient: { id: 1, key: ingredientKey, icon: "JAR", category: "condimentsAndSpices", subcategory: "spices", reproducible: false, allergens: [], diets: [], }, quantity: null, unit: null, }, ], }, }); }, ); // 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); }, ); // Issue #109 — a line can be ticked off as "in the cart" (local, per-week, // `localStorage` only — see `shopping-list-progress.ts`). When("I tick the shopping list item {string}", (name: string) => { cy.contains(".shopping-list__item", name).find("label.shopping-list__item-check").click(); }); Then("the shopping list item {string} should be ticked", (name: string) => { cy.contains(".shopping-list__item", name) .should("have.class", "shopping-list__item--checked") .find('input[type="checkbox"]') .should("be.checked"); }); Then("the shopping list item {string} should not be ticked", (name: string) => { cy.contains(".shopping-list__item", name) .should("not.have.class", "shopping-list__item--checked") .find('input[type="checkbox"]') .should("not.be.checked"); }); When("I reload the page", () => { cy.reload(); });