[UI mobile] Liste de courses et catalogue de recettes : renforcement mobile #99
4 changed files with 140 additions and 0 deletions
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
67
apps/web/cypress/e2e/shopping-list-page.cy.ts
Normal file
67
apps/web/cypress/e2e/shopping-list-page.cy.ts
Normal file
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue