[UI mobile] Session de cuisine : passe mobile #98
2 changed files with 143 additions and 0 deletions
|
|
@ -3,6 +3,8 @@
|
||||||
// `GET /cooking-session` behavior (including the optimizer) against a real
|
// `GET /cooking-session` behavior (including the optimizer) against a real
|
||||||
// database.
|
// database.
|
||||||
|
|
||||||
|
import { PHONE_VIEWPORT } from "../support/viewports";
|
||||||
|
|
||||||
const authenticatedProfile = {
|
const authenticatedProfile = {
|
||||||
id: 1,
|
id: 1,
|
||||||
firstName: "Alice",
|
firstName: "Alice",
|
||||||
|
|
@ -421,3 +423,72 @@ describe("Cooking session page", () => {
|
||||||
cy.contains("Impossible de charger").should("be.visible");
|
cy.contains("Impossible de charger").should("be.visible");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Ticket #89 — this page's first dedicated responsive pass (it previously
|
||||||
|
// had no `@media` rule at all, see cooking-session-page.scss's own header
|
||||||
|
// comment). No layout is restructured on phone (the flex-column shell and
|
||||||
|
// the two horizontally-scrolling rows already degrade safely) — this covers
|
||||||
|
// the two things that actually needed fixing: comfortable touch targets on
|
||||||
|
// the screen's own controls, and a real flex overflow bug in the Recettes
|
||||||
|
// tab (a long recipe name had no upper bound and could crowd its portions
|
||||||
|
// badge/chevron out of the row).
|
||||||
|
describe("Cooking session page (phone)", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.setViewport("phone");
|
||||||
|
cy.clock(TODAY, ["Date"]);
|
||||||
|
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gives the task cards and stepper nodes a taller, more tappable padding than on desktop", () => {
|
||||||
|
cy.intercept("GET", /\/cooking-session\?/, { statusCode: 200, body: planFixture() });
|
||||||
|
cy.visit("/cuisiner?date=2026-08-17");
|
||||||
|
|
||||||
|
cy.get(".cooking-task")
|
||||||
|
.first()
|
||||||
|
.should(($el) => {
|
||||||
|
// --space-md (16px) on phone vs. the shared checkbox-label rule's
|
||||||
|
// own --space-xs (4px) app-wide default — see cooking-session-page
|
||||||
|
// .scss's phone block.
|
||||||
|
expect(getComputedStyle($el[0]).paddingTop).to.equal("16px");
|
||||||
|
});
|
||||||
|
cy.get(".cooking-stepper__node")
|
||||||
|
.first()
|
||||||
|
.should(($el) => {
|
||||||
|
expect(getComputedStyle($el[0]).paddingTop).to.equal("16px");
|
||||||
|
});
|
||||||
|
|
||||||
|
// The document itself never scrolls sideways, same global rule every
|
||||||
|
// other page is held to.
|
||||||
|
cy.document()
|
||||||
|
.its("documentElement.scrollWidth")
|
||||||
|
.should("be.lte", PHONE_VIEWPORT.width + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("truncates a long recipe name in the Recettes tab instead of pushing its portions badge out of view", () => {
|
||||||
|
const longName =
|
||||||
|
"Un très long nom de recette qui ne devrait surtout pas pousser le badge de portions hors de l'écran";
|
||||||
|
cy.intercept("GET", /\/cooking-session\?/, {
|
||||||
|
statusCode: 200,
|
||||||
|
body: {
|
||||||
|
startDate: "2026-08-17T00:00:00.000Z",
|
||||||
|
finishDate: "2026-08-23T00:00:00.000Z",
|
||||||
|
recipes: [{ recipeId: 1, name: longName, portions: 4 }],
|
||||||
|
phases: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
cy.visit("/cuisiner?date=2026-08-17");
|
||||||
|
cy.contains(".cooking-session-page__tab", "Recettes").click();
|
||||||
|
|
||||||
|
cy.get(".cooking-recipes__name").should(($el) => {
|
||||||
|
const s = getComputedStyle($el[0]);
|
||||||
|
expect(s.textOverflow).to.equal("ellipsis");
|
||||||
|
expect(s.whiteSpace).to.equal("nowrap");
|
||||||
|
// The name's own box is clipped narrower than its full text — proof
|
||||||
|
// the truncation is actually engaged, not just declared.
|
||||||
|
expect($el[0].scrollWidth).to.be.greaterThan($el[0].clientWidth);
|
||||||
|
});
|
||||||
|
// ...and the badge survives, still on screen, not clipped away by the
|
||||||
|
// card's own `overflow: hidden`.
|
||||||
|
cy.contains(".cooking-recipes__portions", "4 portions").should("be.visible");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
// on the theme.
|
// on the theme.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
@use "../../styles/breakpoints" as bp;
|
||||||
|
|
||||||
.cooking-session-page {
|
.cooking-session-page {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -134,12 +136,24 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `flex: 1; min-width: 0` + truncation — without it, a long recipe name
|
||||||
|
// has no upper bound in this flex row and can crowd the portions badge
|
||||||
|
// and chevron off to the side (or past the card's own `overflow: hidden`
|
||||||
|
// on `&__item`, silently clipping them) instead of the name itself
|
||||||
|
// truncating. Same fix as `.recipe-chip__name` (planning-page.scss) for
|
||||||
|
// the identical shape of bug.
|
||||||
&__name {
|
&__name {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__portions {
|
&__portions {
|
||||||
|
flex-shrink: 0;
|
||||||
padding: 0.1rem var(--space-sm);
|
padding: 0.1rem var(--space-sm);
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
background: var(--color-surface-alt);
|
background: var(--color-surface-alt);
|
||||||
|
|
@ -486,3 +500,61 @@
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Phone (issue #89, first responsive pass) -------------------------------
|
||||||
|
// Nothing here reorganizes the layout — the flex-column shell and the two
|
||||||
|
// horizontally-scrolling rows (`&__tabs`, `.cooking-stepper`, both the same
|
||||||
|
// generic `overflow-x: auto` pattern used throughout the app) already
|
||||||
|
// degrade safely on a narrow screen, and every text block wraps within its
|
||||||
|
// own flex/block container (no fixed widths). What was actually missing:
|
||||||
|
// comfortable touch targets on this screen's own controls — the task cards
|
||||||
|
// especially, since ticking one off is THE primary, repeated, one-handed
|
||||||
|
// action of the whole "Plan" tab. Same audit `planning-page.scss` got in
|
||||||
|
// #88, applied here to this page's own elements.
|
||||||
|
@include bp.phone {
|
||||||
|
// The task card is a shared "selectable card" checkbox label
|
||||||
|
// (global.scss's `label:has(> input[type="checkbox"])`, padding
|
||||||
|
// `--space-xs --space-sm` app-wide — fine with a mouse, too tight to tap
|
||||||
|
// reliably mid-cooking). `--space-md` all around clears Android's 48dp
|
||||||
|
// minimum touch target (this app is Android-only, see the root README)
|
||||||
|
// even in the worst case — a one-line headline with no utensils and no
|
||||||
|
// "Mutualisé" badge (~25px of text): 25 + 2×16 = 57px. Bumped here, not
|
||||||
|
// in the global rule: this page's task cards are the one place site-wide
|
||||||
|
// where quick, repeated tapping actually matters. Scoped under
|
||||||
|
// `.cooking-phase__tasks` (not a bare `.cooking-task` selector) for the
|
||||||
|
// same specificity reason as `&__tasks .cooking-task--done` above: a
|
||||||
|
// plain single class loses to `label:has(> input[type="checkbox"])`'s
|
||||||
|
// own specificity (a `:has()` argument counts its inner element +
|
||||||
|
// attribute), so it silently never applied without this.
|
||||||
|
.cooking-phase__tasks .cooking-task {
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same reasoning for the stepper's own nodes: the enlarged 1.75rem
|
||||||
|
// (28px) marker below is this node's tallest element, so `--space-md`
|
||||||
|
// padding puts its total height at 28 + 2×16 = 60px, comfortably past
|
||||||
|
// 48dp rather than landing exactly on it.
|
||||||
|
.cooking-stepper__node {
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cooking-stepper__marker {
|
||||||
|
width: 1.75rem;
|
||||||
|
height: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab bar, Recettes summary row, Prev/Next — same treatment, so every
|
||||||
|
// tappable control on this page ends up consistently comfortable rather
|
||||||
|
// than fixing only the one most obviously broken.
|
||||||
|
.cooking-session-page__tab {
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cooking-recipes__row {
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cooking-plan__nav-btn {
|
||||||
|
padding: var(--space-md) var(--space-lg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue