feat: PrimeVue replaced by Nuxt UI using token files from web/src/theme, with AuthShell, DashboardShell, AppBoot and the route guards from routes.md
This commit is contained in:
1 parent
24f95a8fd7
commit
eed661bb24
38 files changed
+2365
-108
No files matched your search
@@ -0,0 +1,146 @@
|
||||
Feature: Nuxt UI shell foundation and router guards
|
||||
|
||||
As the maintainer of RenewCMS
|
||||
I want the SPA to run on Nuxt UI instead of PrimeVue, with AuthShell and DashboardShell layouts,
|
||||
an AppBoot skeleton, and a router guard that resolves and caches the session once per app load
|
||||
So that every later screen slice can build on a themed, guarded shell instead of an unguarded router
|
||||
with no layout
|
||||
|
||||
Background:
|
||||
Given web/src/main.ts wires the app through the @nuxt/ui/vite plugin, and no longer imports
|
||||
primevue, @primeuix/themes, primeicons or tailwindcss-primeui
|
||||
And web/src/theme/ui.config.ts and web/src/assets/main.css hold the token files copied from
|
||||
docs/design/source/tokens/ (ui.config.ts, renewcms.css), and public/favicon.svg is the file
|
||||
copied from docs/design/source/brand/favicon.svg
|
||||
And the router declares route meta.auth as one of "public", "session", "verified" or "any", and
|
||||
meta.title for every verified route
|
||||
And a global router.beforeEach guard runs on every navigation, and on the first navigation of the
|
||||
app it resolves the session by calling GET /v1/auth/me once and caches the result (session or
|
||||
"no session") in a session Pinia store; later navigations in the same app load reuse that
|
||||
cached result instead of calling GET /v1/auth/me again
|
||||
And /home, /article and /integration currently render only a trivial placeholder view inside
|
||||
DashboardShell; later screen slices (Home, Articles, Integration) replace that placeholder
|
||||
content with the real screens
|
||||
|
||||
Scenario: A public route never triggers a session check
|
||||
Given the target route is /login, whose meta.auth is "public"
|
||||
When the router navigates to it as the first navigation of the app
|
||||
Then GET /v1/auth/me is not called
|
||||
And AuthShell renders immediately, with no AppBoot skeleton shown
|
||||
|
||||
Scenario: The first navigation to a non-public route resolves the session and shows AppBoot meanwhile
|
||||
Given the target route is /home, whose meta.auth is "verified"
|
||||
And GET /v1/auth/me has not resolved yet
|
||||
When the router navigates to /home as the first navigation of the app
|
||||
Then DashboardShell renders with the AppBoot skeleton in place of the page content
|
||||
When GET /v1/auth/me then resolves 200 with is_verified true
|
||||
Then the AppBoot skeleton is replaced by the routed page's content
|
||||
And the navigation is not redirected
|
||||
|
||||
Scenario: A second navigation reuses the cached session instead of calling GET /v1/auth/me again
|
||||
Given GET /v1/auth/me has already resolved 200 with is_verified true during the first navigation
|
||||
When the router navigates from /home to /article, whose meta.auth is also "verified"
|
||||
Then GET /v1/auth/me is not called a second time
|
||||
And no AppBoot skeleton is shown for this navigation
|
||||
|
||||
Scenario: A verified-only route admits a verified session
|
||||
Given the target route is /article, whose meta.auth is "verified"
|
||||
And GET /v1/auth/me resolves 200 with is_verified true
|
||||
When the router navigates to /article
|
||||
Then the navigation completes to /article
|
||||
And there is no redirect
|
||||
|
||||
Scenario: A verified-only route redirects an unverified session to the pending page
|
||||
Given the target route is /article, whose meta.auth is "verified"
|
||||
And GET /v1/auth/me resolves 200 with is_verified false
|
||||
When the router navigates to /article
|
||||
Then the router redirects to /register/pending
|
||||
And the navigation to /article does not complete
|
||||
|
||||
Scenario: A session-only route admits an unverified session
|
||||
Given the target route is /register/pending, whose meta.auth is "session"
|
||||
And GET /v1/auth/me resolves 200 with is_verified false
|
||||
When the router navigates to /register/pending
|
||||
Then the navigation completes to /register/pending
|
||||
And there is no redirect
|
||||
|
||||
Scenario: No session redirects to login with the original path and an expired reason
|
||||
Given the target route is /article/12/edit, whose meta.auth is "verified"
|
||||
And GET /v1/auth/me resolves 401
|
||||
When the router navigates to /article/12/edit
|
||||
Then the router redirects to "/login?redirect=/article/12/edit&reason=expired"
|
||||
And the navigation to /article/12/edit does not complete
|
||||
|
||||
Scenario Outline: The root path resolves to /home, /login or /register/pending depending on the session
|
||||
Given GET /v1/auth/me resolves <response>
|
||||
When the router navigates to /
|
||||
Then the router redirects to <destination>
|
||||
|
||||
Examples:
|
||||
| response | destination |
|
||||
| 200 with is_verified true | /home |
|
||||
| 401 | /login |
|
||||
| 200 with is_verified false | /register/pending |
|
||||
|
||||
Scenario: A session check that fails with a server error renders the Generic error view instead of redirecting
|
||||
Given the target route is /, whose meta.auth is "any"
|
||||
And GET /v1/auth/me fails with a 500 response
|
||||
When the router navigates to /
|
||||
Then the router does not redirect anywhere
|
||||
And the Generic error view renders in place, with breadcrumb "Home", title "Something went wrong",
|
||||
text "This page couldn't load. Your saved work is safe. Try again, and if it keeps happening,
|
||||
restart the RenewCMS server.", a primary action "Try again" and a secondary action "Go to Home"
|
||||
When "Try again" is activated
|
||||
Then GET /v1/auth/me is called again to re-run the session check
|
||||
|
||||
Scenario: A session check that fails with a network error on a non-root verified route also renders the Generic error view
|
||||
Given the target route is /article, whose meta.auth is "verified"
|
||||
And GET /v1/auth/me fails with a network error
|
||||
When the router navigates to /article
|
||||
Then the router does not redirect to /login or /register/pending
|
||||
And DashboardShell renders the Generic error view in place of the AppBoot skeleton and the page
|
||||
content, with title "Something went wrong" and a primary action "Try again"
|
||||
When "Try again" is activated
|
||||
Then GET /v1/auth/me is called again to re-run the session check
|
||||
|
||||
Scenario Outline: Each verified placeholder route renders inside DashboardShell with its own heading, breadcrumb and active nav item
|
||||
Given GET /v1/auth/me resolves 200 with is_verified true
|
||||
When the router navigates to <path>
|
||||
Then the route renders inside DashboardShell
|
||||
And the page heading is <title>
|
||||
And the breadcrumb is <title>
|
||||
And the nav item for <title> has aria-current="page"
|
||||
|
||||
Examples:
|
||||
| path | title |
|
||||
| /home | "Home" |
|
||||
| /article | "Articles" |
|
||||
| /integration | "Integration" |
|
||||
|
||||
Scenario: AuthShell renders its title as the page heading and sets the document title
|
||||
Given a screen renders AuthShell with title "Log in" and no description
|
||||
When AuthShell mounts
|
||||
Then AuthShell renders an h1 containing "Log in"
|
||||
And document.title is "Log in · RenewCMS"
|
||||
|
||||
Scenario: AuthShell renders its notice and footer slots
|
||||
Given a screen renders AuthShell with a "notice" slot containing "Your session expired." and a
|
||||
"footer" slot containing a "Forgot password?" link
|
||||
When AuthShell mounts
|
||||
Then the alert above the title contains "Your session expired."
|
||||
And the footer area outside the card contains a "Forgot password?" link
|
||||
|
||||
Scenario: DashboardShell renders the three navigation destinations
|
||||
Given the current route is /article
|
||||
When DashboardShell mounts
|
||||
Then the sidebar navigation lists exactly three destinations: "Home" linking to /home, "Articles"
|
||||
linking to /article, and "Integration" linking to /integration
|
||||
And the "Articles" destination has aria-current="page"
|
||||
And neither "Home" nor "Integration" has aria-current set
|
||||
|
||||
Scenario: DashboardShell shows the offline banner when the browser reports offline
|
||||
Given navigator.onLine is false
|
||||
When DashboardShell mounts
|
||||
Then an offline banner is shown
|
||||
When navigator.onLine becomes true
|
||||
Then the offline banner is hidden
|
||||
@@ -0,0 +1,73 @@
|
||||
# QA: Nuxt UI shell foundation and router guards
|
||||
|
||||
## Automated (Vitest + Vue Test Utils, `task test:web`)
|
||||
|
||||
Mock `useApi` / the `/v1/auth/me` call per case; no real backend, no httptest server.
|
||||
|
||||
1. **Public route skips session check** — mount the router at /login; assert the mocked
|
||||
GET /v1/auth/me function is never invoked, and AuthShell is rendered with no AppBoot skeleton.
|
||||
2. **AppBoot shown while GET /v1/auth/me is pending** — mount the router at /home with an
|
||||
unresolved GET /v1/auth/me promise; assert the AppBoot skeleton (USkeleton) is present and the
|
||||
route's own content is absent; resolve the promise with 200 is_verified true; assert the skeleton
|
||||
is gone and the route's content is present.
|
||||
3. **Session cached across navigations** — resolve GET /v1/auth/me once (200 is_verified true),
|
||||
navigate /home → /article; assert the mock was called exactly once.
|
||||
4. **Verified route + verified session** — mock 200 is_verified true, navigate to /article; assert
|
||||
the router's current route is /article (no redirect).
|
||||
5. **Verified route + unverified session** — mock 200 is_verified false, navigate to /article; assert
|
||||
the router redirects to /register/pending.
|
||||
6. **Session route + unverified session** — mock 200 is_verified false, navigate to
|
||||
/register/pending; assert no redirect.
|
||||
7. **No session** — mock a 401, navigate to /article/12/edit; assert redirect to
|
||||
/login?redirect=/article/12/edit&reason=expired.
|
||||
8. **Root path outcomes** — one test per Examples row: 200 is_verified true → /home, 401 → /login,
|
||||
200 is_verified false → /register/pending.
|
||||
9. **Boot failure at root renders the Generic error view** — mock GET /v1/auth/me rejecting with a
|
||||
500, navigate to /; assert no redirect happens, and the Generic error view renders with title
|
||||
"Something went wrong" and text "This page couldn't load. Your saved work is safe. Try again, and
|
||||
if it keeps happening, restart the RenewCMS server."; click "Try again"; assert
|
||||
GET /v1/auth/me is called a second time.
|
||||
10. **Boot failure at a non-root verified route renders the Generic error view** — mock
|
||||
GET /v1/auth/me rejecting with a network error, navigate to /article; assert no redirect to
|
||||
/login or /register/pending happens, DashboardShell shows the Generic error view instead of
|
||||
AppBoot or page content; click "Try again"; assert GET /v1/auth/me is called a second time.
|
||||
11. **Placeholder verified routes wire up shell, heading, breadcrumb and active nav** — one test per
|
||||
Examples row (/home → "Home", /article → "Articles", /integration → "Integration"): mock 200
|
||||
is_verified true, navigate to the path; assert the route renders inside DashboardShell, the page
|
||||
heading and breadcrumb both equal the expected title, and the corresponding nav item has
|
||||
aria-current="page" while the other two do not.
|
||||
12. **AuthShell renders title as h1 and sets document.title** — mount AuthShell with
|
||||
title="Log in"; assert an h1 with text "Log in" and document.title === "Log in · RenewCMS".
|
||||
13. **AuthShell notice/footer slots** — mount AuthShell with notice and footer slot content; assert
|
||||
both render outside/above the card content as specified.
|
||||
14. **DashboardShell nav + aria-current** — mount DashboardShell with current route /article; assert
|
||||
3 links (Home→/home, Articles→/article, Integration→/integration), and only the Articles link
|
||||
has aria-current="page".
|
||||
15. **DashboardShell offline banner** — mock navigator.onLine false, mount DashboardShell; assert
|
||||
the offline banner is visible; set navigator.onLine true and dispatch the 'online' event; assert
|
||||
it hides.
|
||||
|
||||
## Visual (manual verification — desktop and mobile, light and dark)
|
||||
|
||||
Reference: `docs/design/source/RenewCMS Screens Back-office.dc.html`, groups "Boot" and "Global",
|
||||
plus the components.md AuthShell/DashboardShell sections.
|
||||
|
||||
- [ ] AuthShell at desktop (sm+): 400px UPageCard variant="outline", centered, top offset 12vh,
|
||||
light and dark.
|
||||
- [ ] AuthShell below sm: card loses border, 20px padding fills screen, fields/buttons use xl size,
|
||||
light and dark.
|
||||
- [ ] AuthShell footer: UColorModeSelect size="sm" plus footer links render outside the card.
|
||||
- [ ] DashboardShell desktop: sidebar with RenewCMS logo header, 3 nav items, UserMenu footer;
|
||||
collapsing the sidebar leaves a 56px icon rail with tooltips on hover/focus.
|
||||
- [ ] DashboardShell mobile (below lg): UDashboardSidebarToggle appears, sidebar becomes a
|
||||
slideover, closes after navigating.
|
||||
- [ ] DashboardShell active nav item uses the sand-fill/ink-text bend, not Nuxt UI's default
|
||||
primary-tinted background.
|
||||
- [ ] AppBoot skeleton visual match to the boot board, light and dark.
|
||||
- [ ] Generic error view visual match to the Global board's "Generic error" state, desktop and
|
||||
mobile, light and dark (breadcrumb "Home", title "Something went wrong", primary "Try again",
|
||||
secondary "Go to Home").
|
||||
- [ ] Keyboard: first Tab stop on a DashboardShell route is the "Skip to content" link; sidebar is
|
||||
a `nav` labeled "Main"; collapse button exposes `aria-expanded`; AuthShell moves focus to its
|
||||
h1 on route change; the Generic error view's title gets focus on render.
|
||||
- [ ] Favicon renders as the copied favicon.svg mark in the browser tab, dark-aware.
|
||||
Reference in new issue
Block a user