All checks were successful
La liste de courses était volontairement en lecture seule. Elle gagne un
état « acheté » par article, utile en magasin (au doigt sur mobile).
- `shopping-list-progress.ts` (nouveau) : `ShoppingListProgressStore`,
calqué sur `CookingProgressStore` — `localStorage` uniquement, par
semaine (clé `batchcooking:shoppingListChecked:<weekStart>`), aucun état
serveur. Une coche n'est jamais autoritaire (la liste est recalculée
depuis le planning à chaque visite) et se perd sans dommage.
- `ShoppingListPage.tsx` : chaque ligne devient un `CheckboxOption` ;
`<ShoppingListItems key={weekKey}>` remonte à chaque changement de
semaine (checklist réinitialisée). Clé d'article = `ingredientId-unitId`
(déjà la `key` React de la ligne).
- `shopping-list-page.scss` : override scopé du look « carte sélectionnable »
global — un article coché est barré et estompé, pas surligné en primary.
Cible tactile ≥ 44 px sous le breakpoint phone.
- `specs/frontend-architecture.md` : la note « délibérément en lecture
seule » est remplacée.
Tests : `shopping-list.feature` — nouveau scénario « cocher un article »
(coché → barré, persiste au reload, n'affecte pas les autres).
`shopping-list-page.cy.ts` — la cible tactile de la ligne (le label
`CheckboxOption`) fait 16 px de padding + ≥ 44 px de haut sur téléphone.
Local : `pnpm lint` + `pnpm --filter web build` OK, `shopping-list.feature`
4/4, `shopping-list-page.cy.ts` 1/1.
Closes #109
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
69 lines
2.3 KiB
TypeScript
69 lines
2.3 KiB
TypeScript
// 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 tap target 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");
|
|
|
|
// The interactive row is the `CheckboxOption` label (issue #109), not
|
|
// the `<li>` wrapper. --space-md (16px) padding on phone vs. the desktop
|
|
// default --space-sm (8px), plus a ≥44px min-height tap target.
|
|
cy.get(".shopping-list__item-check").should(($el) => {
|
|
expect(getComputedStyle($el[0]).paddingTop).to.equal("16px");
|
|
expect($el[0].getBoundingClientRect().height).to.be.gte(44);
|
|
});
|
|
|
|
cy.document()
|
|
.its("documentElement.scrollWidth")
|
|
.should("be.lte", PHONE_VIEWPORT.width + 1);
|
|
});
|
|
});
|