diff --git a/apps/web/cypress/e2e/app-settings-page.cy.ts b/apps/web/cypress/e2e/app-settings-page.cy.ts index 9309d95..14e6d7a 100644 --- a/apps/web/cypress/e2e/app-settings-page.cy.ts +++ b/apps/web/cypress/e2e/app-settings-page.cy.ts @@ -1,16 +1,14 @@ // Mocks the API via cy.intercept — this job doesn't run a live backend (see -// .github/workflows/ci.yml); apps/api's own Mocha suite covers real API -// behavior against a real database. +// .github/workflows/ci.yml). // -// Cypress always runs as a plain web browser — `Capacitor.isNativePlatform()` -// is never true here, so only this page's web-side content (the build -// version + the "stay on web" preference) is reachable from this spec. The -// native-only half (App.getInfo()/checkNow's actual version-comparison -// network call) has no seam to fake native from a web run — same -// untested-on-purpose posture already accepted for `useAppUpdateCheck`'s -// background check and `UpdateBanner` (neither has e2e coverage either); -// the underlying comparison logic itself is unit-tested in -// version-compare.cy.tsx, unchanged by this ticket. +// Issue #107 — "Paramètres d'application" is packaged-app only now. Its +// content (installed version + update check) is inert on the web, and the +// one web-relevant control it carried (the "stay on web" redirect toggle) +// moved to `/telecharger` (see download-page.cy.ts). Cypress always runs as +// a plain web browser (`Capacitor.isNativePlatform()` is never true here), +// so the page itself has no seam to reach — same untested-on-purpose +// posture already accepted for `useAppUpdateCheck` / `UpdateBanner`. This +// spec now only covers the web-side gating. const authenticatedProfile = { id: 1, @@ -22,47 +20,21 @@ const authenticatedProfile = { dietId: null, }; -const STAY_ON_WEB_KEY = "batchcooking:stayOnWeb"; - -describe("App settings (/parametres/application)", () => { +describe("App settings — hidden on the web (#107)", () => { beforeEach(() => { cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile }); + cy.intercept("GET", /\/planning\?/, { statusCode: 200, body: null }); }); - it("shows the web build version, and the update-check section only on native", () => { - cy.visit("/parametres/application"); - - cy.contains("h1", "Paramètres d'application").should("be.visible"); - // The exact build number isn't asserted (would couple this test to - // package.json's version, bumped independently of this page) — just - // that a "Version X" line rendered at all, proving `useInstalledVersion` - // resolved something rather than being stuck empty. - cy.contains(".app-settings__version", "Version").should("be.visible"); - - cy.contains("button", "Vérifier les mises à jour").should("not.exist"); - cy.contains( - "La vérification des mises à jour n'est disponible que dans l'application mobile", - ).should("be.visible"); + it("is absent from the Paramètres menu", () => { + cy.visit("/"); + cy.contains("button", "Paramètres").click(); + cy.contains("a", "Paramètres d'application").should("not.exist"); }); - it("defaults 'stay on web' to unchecked, and persists a toggle across a reload", () => { + it("redirects /parametres/application to /parametres/compte instead of an orphaned page", () => { cy.visit("/parametres/application"); - - cy.contains("label", "Toujours rester sur le site web") - .find('input[type="checkbox"]') - .should("not.be.checked"); - - cy.contains("label", "Toujours rester sur le site web").click(); - cy.contains("label", "Toujours rester sur le site web") - .find('input[type="checkbox"]') - .should("be.checked"); - cy.window().then((win) => { - expect(win.localStorage.getItem(STAY_ON_WEB_KEY)).to.equal("true"); - }); - - cy.reload(); - cy.contains("label", "Toujours rester sur le site web") - .find('input[type="checkbox"]') - .should("be.checked"); + cy.url().should("include", "/parametres/compte"); + cy.contains("h1", "Paramètres d'application").should("not.exist"); }); }); diff --git a/apps/web/cypress/e2e/download-page.cy.ts b/apps/web/cypress/e2e/download-page.cy.ts index 1e09f17..7fc3aa8 100644 --- a/apps/web/cypress/e2e/download-page.cy.ts +++ b/apps/web/cypress/e2e/download-page.cy.ts @@ -23,4 +23,29 @@ describe("DownloadPage — the mobile-app-redirect fallback", () => { cy.visit("/telecharger"); cy.url().should("include", "/telecharger"); }); + + // Issue #107 — the "toujours rester sur le site web" preference moved + // here from "Paramètres d'application" (packaged-app only now). Written to + // `localStorage` on the public web origin, which is where + // `attemptOpenNativeApp` reads it. + it("defaults 'stay on web' to unchecked, and persists a toggle across a reload", () => { + cy.visit("/telecharger"); + + cy.contains("label", "Toujours rester sur le site web") + .find('input[type="checkbox"]') + .should("not.be.checked"); + + cy.contains("label", "Toujours rester sur le site web").click(); + cy.contains("label", "Toujours rester sur le site web") + .find('input[type="checkbox"]') + .should("be.checked"); + cy.window().then((win) => { + expect(win.localStorage.getItem("batchcooking:stayOnWeb")).to.equal("true"); + }); + + cy.reload(); + cy.contains("label", "Toujours rester sur le site web") + .find('input[type="checkbox"]') + .should("be.checked"); + }); }); diff --git a/apps/web/cypress/e2e/sidebar.cy.ts b/apps/web/cypress/e2e/sidebar.cy.ts index d1b94e1..f1543ed 100644 --- a/apps/web/cypress/e2e/sidebar.cy.ts +++ b/apps/web/cypress/e2e/sidebar.cy.ts @@ -25,7 +25,7 @@ describe("Sidebar — settings menu and account menu", () => { cy.get(".app-sidebar__nav a").should("not.contain", "Foyer"); }); - it("reveals the four settings pages behind the Paramètres toggle", () => { + it("reveals the settings pages behind the Paramètres toggle", () => { cy.visit("/"); cy.contains("a", "Compte").should("not.exist"); @@ -43,11 +43,9 @@ describe("Sidebar — settings menu and account menu", () => { "href", "/parametres/preferences-utilisateur", ); - cy.contains("a", "Paramètres d'application").should( - "have.attr", - "href", - "/parametres/application", - ); + // "Paramètres d'application" is packaged-app only (issue #107) — Cypress + // runs as a plain web browser, so it's filtered out here. + cy.contains("a", "Paramètres d'application").should("not.exist"); }); it("opens the account menu from the greeting and links to Mon compte", () => { diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 9bacd80..f221d9d 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,3 +1,4 @@ +import { Capacitor } from "@capacitor/core"; import { Navigate, Outlet, Route, Routes } from "react-router-dom"; import { AdminAuthProvider } from "./features/admin/AdminAuthContext"; import { RequireAdmin } from "./features/admin/RequireAdmin"; @@ -108,7 +109,21 @@ export function App() { } /> } /> } /> - } /> + {/* Native app only (issue #107): on the web its sections are inert + (no "installed version", the update check is a no-op) or have + moved to `/telecharger` (the stay-on-web toggle), and the nav + entry is hidden — so a stray bookmark redirects rather than + landing on an orphaned page (same pattern as `/foyer` below). */} + + ) : ( + + ) + } + /> } /> } /> diff --git a/apps/web/src/layouts/AppLayout.tsx b/apps/web/src/layouts/AppLayout.tsx index 7ad72db..69dca5b 100644 --- a/apps/web/src/layouts/AppLayout.tsx +++ b/apps/web/src/layouts/AppLayout.tsx @@ -1,3 +1,5 @@ +import { Capacitor } from "@capacitor/core"; +import type { LucideIcon } from "lucide-react"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom"; @@ -41,19 +43,44 @@ const NAV_ITEMS = [ }, ] as const; +/** One entry in the "Paramètres" nav — see {@link SETTINGS_ITEMS}. */ +interface SettingsNavItem { + to: string; + /** Maps to `layout.settings.nav.` in `locales/fr/translation.json`. */ + key: string; + Icon: LucideIcon; + /** + * Hidden entirely outside the packaged app (issue #107): its page's + * options — the version/update check, and the "stay on the website" + * redirect toggle — are either inert on the web or have moved to + * `/telecharger`. The route itself redirects away on the web too (see + * `App.tsx`). + */ + nativeOnly?: boolean; +} + /** - * One entry in the "Paramètres" nav — revealed by {@link SettingsMenu} on - * desktop, listed inside {@link MobileMoreSheet} on mobile. `key` maps to - * `layout.settings.nav.`. + * The "Paramètres" nav entries — revealed by {@link SettingsMenu} on + * desktop, listed inside {@link MobileMoreSheet} on mobile. Filtered to + * {@link settingsNavItems} for actual rendering. */ -const SETTINGS_ITEMS = [ +const SETTINGS_ITEMS: SettingsNavItem[] = [ { to: "/parametres/compte", key: "account", Icon: AccountIcon }, { to: "/parametres/preferences", key: "preferences", Icon: DietPreferencesIcon }, { to: "/parametres/foyer", key: "household", Icon: HouseholdIcon }, { to: "/parametres/preferences-utilisateur", key: "userPreferences", Icon: UserPreferencesIcon }, - { to: "/parametres/application", key: "application", Icon: AppSettingsIcon }, + { to: "/parametres/application", key: "application", Icon: AppSettingsIcon, nativeOnly: true }, { to: "/parametres/credits", key: "credits", Icon: CreditsIcon }, -] as const; +]; + +/** + * What's actually rendered — {@link SETTINGS_ITEMS} minus the `nativeOnly` + * ones on the web. `Capacitor.isNativePlatform()` is fixed for the life of + * the page, so this is settled once at module load rather than per render. + */ +const settingsNavItems: SettingsNavItem[] = SETTINGS_ITEMS.filter( + (item) => Capacitor.isNativePlatform() || !item.nativeOnly, +); /** * Shell for every authenticated page: on desktop/tablet, a sidebar (brand, @@ -220,7 +247,7 @@ function SettingsMenu() { {isOpen && (