148 lines
7.9 KiB
Gherkin
148 lines
7.9 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 five navigation destinations
|
|
Given the current route is /article
|
|
When DashboardShell mounts
|
|
Then the sidebar navigation lists exactly five destinations in this order: "Home" linking to
|
|
/home, "Articles" linking to /article, "Users" linking to /users, "Integration" linking
|
|
to /integration, and "Settings" linking to /settings
|
|
And the "Articles" destination has aria-current="page"
|
|
And none of "Home", "Users", "Integration" and "Settings" 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
|