Files
RenewCMS/features/nuxt_ui_shell_foundation.feature
T

147 lines
7.8 KiB
Gherkin

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