diff --git a/apps/web/cypress/component/Dialog.cy.tsx b/apps/web/cypress/component/Dialog.cy.tsx new file mode 100644 index 0000000..b5f2703 --- /dev/null +++ b/apps/web/cypress/component/Dialog.cy.tsx @@ -0,0 +1,47 @@ +import { Dialog } from "../../src/components/ui/Dialog"; +import { PHONE_VIEWPORT } from "../support/viewports"; + +// First component test for Dialog — until now it was only exercised +// indirectly through RecipePickerDialog's own e2e coverage. Added here +// specifically to prove `cy.setViewport()` (mobile/tablet UI chantier, +// ticket #83) works correctly in component-test mode too, not just e2e — +// and along the way, covers the phone full-screen treatment (ticket #91) +// that had no test of its own yet. + +describe("Dialog — phone full-screen treatment", () => { + it("is a centered, rounded panel with room around it at a desktop viewport", () => { + // Component testing's own default viewport (unlike e2e's) is already + // phone-sized — an explicit wide viewport here, not just "whatever the + // default is", is what actually makes this the desktop case. + const desktopWidth = 1280; + cy.viewport(desktopWidth, 800); + cy.mount( + {}}> + Contenu + , + ); + + cy.get(".dialog-panel").should(($el) => { + const style = getComputedStyle($el[0]); + expect(style.borderRadius).to.not.equal("0px"); + expect($el[0].getBoundingClientRect().width).to.be.lessThan(desktopWidth); + }); + }); + + it("goes edge-to-edge, with no rounding, at a phone viewport", () => { + cy.setViewport("phone"); + cy.mount( + {}}> + Contenu + , + ); + + cy.get(".dialog-panel").should(($el) => { + const style = getComputedStyle($el[0]); + expect(style.borderRadius).to.equal("0px"); + const rect = $el[0].getBoundingClientRect(); + expect(rect.width).to.be.closeTo(PHONE_VIEWPORT.width, 1); + expect(rect.height).to.be.closeTo(PHONE_VIEWPORT.height, 1); + }); + }); +}); diff --git a/apps/web/cypress/e2e/layout.cy.ts b/apps/web/cypress/e2e/layout.cy.ts index 65f6589..c00089f 100644 --- a/apps/web/cypress/e2e/layout.cy.ts +++ b/apps/web/cypress/e2e/layout.cy.ts @@ -11,6 +11,8 @@ // width (some stuck to the left edge with a lopsided gap, others correctly // full-bleed). +import { PHONE_VIEWPORT } from "../support/viewports"; + const authenticatedProfile = { id: 1, firstName: "Alice", @@ -185,7 +187,7 @@ describe("Responsive breakpoint — the sidebar becomes a bottom tab bar under 6 }); it("renders the bottom nav instead of the sidebar, with every route tab legible and a working Plus tab", () => { - cy.viewport(375, 812); + cy.setViewport("phone"); cy.visit("/"); // The desktop sidebar isn't just hidden — it's not in the DOM at all. @@ -193,10 +195,10 @@ describe("Responsive breakpoint — the sidebar becomes a bottom tab bar under 6 cy.get(".app-bottom-nav").should(($nav) => { const rect = $nav[0].getBoundingClientRect(); - expect(rect.width).to.be.closeTo(375, 1); + expect(rect.width).to.be.closeTo(PHONE_VIEWPORT.width, 1); // A short bar flush against the bottom edge, not a tall rail. expect(rect.height).to.be.lessThan(120); - expect(rect.bottom).to.be.closeTo(812, 1); + expect(rect.bottom).to.be.closeTo(PHONE_VIEWPORT.height, 1); expect(getComputedStyle($nav[0]).position).to.equal("fixed"); }); @@ -238,8 +240,8 @@ describe("Responsive breakpoint — the sidebar becomes a bottom tab bar under 6 cy.get(".bottom-sheet").should("not.exist"); }); - it("keeps the desktop sidebar (not the bottom nav) above the phone breakpoint", () => { - cy.viewport(1000, 700); + it("keeps the desktop sidebar (not the bottom nav) at a tablet viewport", () => { + cy.setViewport("tablet"); cy.visit("/"); cy.get(".app-sidebar").should("exist"); @@ -248,8 +250,6 @@ describe("Responsive breakpoint — the sidebar becomes a bottom tab bar under 6 }); describe("Responsive breakpoint — page content under 640px (issue #39)", () => { - const PHONE: [number, number] = [375, 812]; - beforeEach(() => { interceptAuth(); }); @@ -266,7 +266,7 @@ describe("Responsive breakpoint — page content under 640px (issue #39)", () => expect(s.paddingLeft).to.equal("32px"); }); - cy.viewport(...PHONE); + cy.setViewport("phone"); cy.visit("/"); cy.get(".app-content").should(($el) => { const s = getComputedStyle($el[0]); @@ -298,7 +298,7 @@ describe("Responsive breakpoint — page content under 640px (issue #39)", () => }, }); - cy.viewport(...PHONE); + cy.setViewport("phone"); cy.visit("/"); // The grid wrapper is the scroll container, and its content is genuinely @@ -313,7 +313,7 @@ describe("Responsive breakpoint — page content under 640px (issue #39)", () => // body). cy.document() .its("documentElement.scrollWidth") - .should("be.lte", 375 + 1); + .should("be.lte", PHONE_VIEWPORT.width + 1); // The per-recipe remove "✕" is hover-gated on desktop; on touch it must // be permanently visible or a planned recipe can't be removed at all. diff --git a/apps/web/cypress/support/component.ts b/apps/web/cypress/support/component.ts index 38f5f07..14335b1 100644 --- a/apps/web/cypress/support/component.ts +++ b/apps/web/cypress/support/component.ts @@ -3,6 +3,8 @@ import { mount } from "cypress/react18"; // components (Checkbox, Radio, Dialog…) are styled through it, not their // own scoped CSS, so mounting one without it would test unstyled markup. import "../../src/styles/global.scss"; +// Registers `cy.setViewport("phone" | "tablet")` — see viewports.ts. +import "./viewports"; declare global { namespace Cypress { diff --git a/apps/web/cypress/support/e2e.ts b/apps/web/cypress/support/e2e.ts index 7ef04fe..97b10d7 100644 --- a/apps/web/cypress/support/e2e.ts +++ b/apps/web/cypress/support/e2e.ts @@ -1,3 +1,3 @@ // Cypress support file — global config and custom commands will go here // as features (and their e2e specs) get added. -export {}; +import "./viewports"; diff --git a/apps/web/cypress/support/viewports.ts b/apps/web/cypress/support/viewports.ts new file mode 100644 index 0000000..f2ce9ce --- /dev/null +++ b/apps/web/cypress/support/viewports.ts @@ -0,0 +1,30 @@ +// Named viewport presets for phone/tablet coverage — the mobile/tablet UI +// chantier's testing convention (ticket #83), shared by both the e2e and +// component support files (see e2e.ts/component.ts, each of which imports +// this once for its side effect of registering `cy.setViewport`). +// +// Sized from real, representative Android devices rather than Cypress's own +// built-in presets (`cy.viewport("iphone-x")` etc.) — this app is +// Android-only (see the root README), so an Apple-named preset would be a +// odd source of truth to borrow pixel values from. Comfortably inside +// `_breakpoints.scss`'s `$phone`/`$tablet` bands (not flush against either +// edge) so a test genuinely exercises "a phone" / "a tablet", not the exact +// boundary pixel. +export const PHONE_VIEWPORT = { width: 390, height: 844 } as const; +export const TABLET_VIEWPORT = { width: 900, height: 1200 } as const; + +export type ViewportPreset = "phone" | "tablet"; + +declare global { + namespace Cypress { + interface Chainable { + /** `cy.viewport(width, height)` for one of the mobile/tablet UI chantier's two named presets — see PHONE_VIEWPORT/TABLET_VIEWPORT above for the exact values. */ + setViewport(preset: ViewportPreset): Chainable; + } + } +} + +Cypress.Commands.add("setViewport", (preset: ViewportPreset) => { + const { width, height } = preset === "phone" ? PHONE_VIEWPORT : TABLET_VIEWPORT; + cy.viewport(width, height); +});