diff --git a/apps/web/cypress/e2e/recipes.cy.ts b/apps/web/cypress/e2e/recipes.cy.ts index c6eebf6..34d2ca4 100644 --- a/apps/web/cypress/e2e/recipes.cy.ts +++ b/apps/web/cypress/e2e/recipes.cy.ts @@ -5,6 +5,8 @@ // The favorite-toggle and delete-recipe journeys moved to recipes.feature — // this file now only covers catalog browsing/display. +import { PHONE_VIEWPORT } from "../support/viewports"; + const authenticatedProfile = { id: 1, firstName: "Alice", @@ -460,3 +462,38 @@ describe("Recipe catalog", () => { ); }); }); + +// Ticket #90 — `.recipe-table-wrap` had no horizontal-scroll guard at all +// (unlike the planning grid's own wrapper), so a narrow viewport just let +// the browser compress every column instead of scrolling. Below the +// tablet breakpoint (1024px) the master-detail split already collapses to +// one column (recipes.scss's own `bp.tablet-down` rule) and gives the +// table the full page width — still comfortably under `.recipe-table`'s +// own 36rem floor on a phone, which is what this covers. +describe("Recipe catalog (phone)", () => { + beforeEach(() => { + cy.setViewport("phone"); + interceptAuth(); + }); + + it("keeps the table on its own horizontal scroll, at full width, instead of squeezing its columns", () => { + cy.intercept("GET", /\/recipes\?/, { statusCode: 200, body: [ratatouille, omelette] }); + + cy.visit("/recettes"); + + cy.contains(".recipe-table__name", "Ratatouille").should("be.visible"); + cy.contains(".recipe-table__name", "Omelette").should("be.visible"); + + cy.get(".recipe-table-wrap").should(($el) => { + const s = getComputedStyle($el[0]); + expect(s.overflowX).to.be.oneOf(["auto", "scroll"]); + expect($el[0].scrollWidth).to.be.greaterThan($el[0].clientWidth); + }); + + // ...and that overflow stays *inside* the wrapper — same global rule + // every other page is held to. + cy.document() + .its("documentElement.scrollWidth") + .should("be.lte", PHONE_VIEWPORT.width + 1); + }); +}); diff --git a/apps/web/cypress/e2e/shopping-list-page.cy.ts b/apps/web/cypress/e2e/shopping-list-page.cy.ts new file mode 100644 index 0000000..79ed527 --- /dev/null +++ b/apps/web/cypress/e2e/shopping-list-page.cy.ts @@ -0,0 +1,67 @@ +// Plain layout-mode spec (not Gherkin) for ShoppingListPage's own +// phone-viewport pass (issue #90) — the actual grouped-list rendering, +// aggregation and week-navigation journey is already covered end-to-end by +// shopping-list.feature; this only exercises the responsive change added +// here. Mocks the API via cy.intercept — this job doesn't run a live +// backend (see .github/workflows/ci.yml). + +import { PHONE_VIEWPORT } from "../support/viewports"; + +const authenticatedProfile = { + id: 1, + firstName: "Alice", + lastName: "Martin", + email: "alice@example.com", + tokenVersion: 0, + houseId: 1, + dietId: null, +}; + +/** Same minimal-fixture convention as shopping-list.ts's own `buildShoppingListItem` step-def helper — only the fields this page actually reads. */ +function shoppingListItem(id: number, ingredientKey: string, icon: string, category: string) { + return { + ingredient: { + id, + key: ingredientKey, + icon, + category, + subcategory: category, + reproducible: false, + allergens: [], + diets: [], + }, + quantity: 2, + unit: { id, key: "piece", type: "COUNT", toBaseFactor: 1 }, + }; +} + +describe("Shopping list page (phone)", () => { + beforeEach(() => { + cy.setViewport("phone"); + cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile }); + }); + + it("gives each row a taller, more legible padding than on desktop, with no horizontal body scroll", () => { + cy.intercept("GET", /\/shopping-list\?/, { + statusCode: 200, + body: { + startDate: "2026-08-17", + finishDate: "2026-08-23", + items: [shoppingListItem(1, "egg", "EGG", "dairyAndCheese")], + }, + }).as("getList"); + + cy.visit("/liste-de-courses"); + cy.wait("@getList"); + + cy.get(".shopping-list__item").should(($el) => { + // --space-md (16px) on phone vs. the desktop default --space-sm + // (8px) — see shopping-list-page.scss's phone block. + expect(getComputedStyle($el[0]).paddingTop).to.equal("16px"); + }); + + cy.document() + .its("documentElement.scrollWidth") + .should("be.lte", PHONE_VIEWPORT.width + 1); + }); +}); diff --git a/apps/web/src/features/recipes/recipes.scss b/apps/web/src/features/recipes/recipes.scss index a87c0a8..6cb8f8c 100644 --- a/apps/web/src/features/recipes/recipes.scss +++ b/apps/web/src/features/recipes/recipes.scss @@ -260,6 +260,17 @@ height: 100%; min-height: 0; overflow-y: auto; + // Issue #90: unlike the planning grid's own wrapper (planning-page.scss), + // this one had no horizontal guard at all — on a narrow viewport the + // table's `width: 100%` below just let the browser compress every column + // (photo/name/allergens/diets) to fit instead of scrolling. Same fix, + // scaled down: 4 narrower columns don't need anywhere near the planning + // grid's 62rem, but they do need *some* floor so the badge columns don't + // get squeezed to the point of wrapping every single pill onto its own + // line. Shared by both the full catalog (`/recettes`) and + // `RecipePickerDialog`'s own instance of this same table/wrapper — fixes + // the identical compression there too, not just the full-page case. + overflow-x: auto; background: var(--color-surface); border-radius: var(--radius-md); box-shadow: var(--shadow-sm); @@ -267,6 +278,7 @@ .recipe-table { width: 100%; + min-width: 36rem; border-collapse: collapse; text-align: left; diff --git a/apps/web/src/pages/shopping-list/shopping-list-page.scss b/apps/web/src/pages/shopping-list/shopping-list-page.scss index 5b391f9..2ce9ba7 100644 --- a/apps/web/src/pages/shopping-list/shopping-list-page.scss +++ b/apps/web/src/pages/shopping-list/shopping-list-page.scss @@ -5,6 +5,8 @@ // `__status`), a simple grouped list rather than a grid below it. // ============================================================================= +@use "../../styles/breakpoints" as bp; + .shopping-list-page { height: 100%; display: flex; @@ -100,3 +102,25 @@ font-variant-numeric: tabular-nums; } } + +// --- Phone (issue #90) ------------------------------------------------------ +// This list has no checkbox/delete button at all — it's deliberately +// read-only (see ShoppingListPage.tsx's own doc comment) — so, unlike the +// planning grid or the cooking-session task cards, there's no *tap* target +// to enlarge here; the chantier's original ticket assumed some existed +// (written before this page's actual code was read). The structure also +// already doesn't overflow: `&-name` is `flex: 1; min-width: 0` and wraps +// rather than pushing `&-quantity` out. What phone-viewport reading +// genuinely rewards here — held in one hand, glanced at repeatedly while +// pushing a cart — is a slightly roomier row and a clearer icon, not a +// structural change. +@include bp.phone { + .shopping-list__item { + padding: var(--space-md); + } + + .shopping-list__item-icon svg { + width: 1.5rem; + height: 1.5rem; + } +}