feat(SPA): schema list and the sidebar Content group
This commit is contained in:
1 parent
73a75d5cb9
commit
3c67cd2314
34 files changed
+2181
-18
No files matched your search
@@ -0,0 +1,193 @@
|
||||
Feature: Schema list and content navigation in the back-office
|
||||
As an owner or admin
|
||||
I want to see the custom and built-in content types on a Schema screen
|
||||
And as any verified user I want a Content menu with one link per custom type
|
||||
So that I can reach the content types I manage and write entries for
|
||||
|
||||
Background:
|
||||
Given the SPA calls the API with relative URLs under /v1 and the "jwt" HttpOnly cookie
|
||||
And GET /v1/content-type returns a JSON array of {id, key, name, icon, fields, created_at, updated_at}
|
||||
And GET /v1/builtin-type returns a JSON array of {key, name, fields}, for owners and admins only
|
||||
And API errors are plain text
|
||||
And the session store exposes the role of the signed-in user, and only owner and admin exist today
|
||||
And a session whose role is neither owner nor admin (absent or unknown) is a test double for the forbidden states
|
||||
And the Schema screen is the route /schema with meta auth "verified", title "Schema"
|
||||
And only owners and admins can use Schema; the SPA decides from the session role, the API still enforces it
|
||||
And the typed API client exposes listContentTypes() and listBuiltinTypes()
|
||||
And a content types store loads them once per session and exposes loading, error and items
|
||||
And this slice does not add the Vitest runner, it already exists (task test:web)
|
||||
|
||||
Scenario Outline: The Schema entry is shown to owners and admins only
|
||||
Given a verified user with role "<role>" is signed in
|
||||
When the back-office shell renders
|
||||
Then the sidebar <schema_entry> a "Schema" entry linking to /schema, with the "shapes" icon, placed after "Users"
|
||||
And no existing nav item changed
|
||||
|
||||
Examples:
|
||||
| role | schema_entry |
|
||||
| owner | shows |
|
||||
| admin | shows |
|
||||
| unknown | does not show |
|
||||
|
||||
Scenario: The Content group lists one link per custom type
|
||||
Given a verified user is signed in
|
||||
And GET /v1/content-type returns the types "events" (Events), "recipes" (Recipes) and "press-mentions" (Press mentions)
|
||||
When the back-office shell renders
|
||||
Then the sidebar shows a "Content" group under the main items
|
||||
And the group contains three links, in the API order, to /content/events, /content/recipes and /content/press-mentions
|
||||
And each row shows the type name
|
||||
And the row of the type matching the current route /content/:key is marked as the current page
|
||||
|
||||
Scenario Outline: Each type shows its icon, or the default icon
|
||||
Given a custom type "recipes" with icon <icon>
|
||||
When the sidebar renders
|
||||
Then the row for "recipes" shows <shown>
|
||||
|
||||
Examples:
|
||||
| icon | shown |
|
||||
| "chef-hat" | the icon i-lucide-chef-hat (in bundle) |
|
||||
| null | the default icon "files" |
|
||||
| "not-bundled" | the default icon "files" |
|
||||
|
||||
Scenario: A long type name is truncated in one line and keeps its full name available
|
||||
Given a custom type named "Frequently asked questions and answers"
|
||||
When the sidebar renders on desktop
|
||||
Then the label is cut with an ellipsis on one line and does not widen the sidebar
|
||||
And the full name is available in a tooltip and as the link's accessible name
|
||||
|
||||
Scenario: With more than 5 types the desktop group shows 5 and the selected one
|
||||
Given 14 custom types and the current route is /content/<selected>
|
||||
When the sidebar renders on desktop
|
||||
Then the group shows the first 5 types, plus the selected type if it is not among them
|
||||
And a "Show all 14" action follows
|
||||
When the user activates "Show all 14"
|
||||
Then all 14 types are listed
|
||||
|
||||
Scenario: With 5 types or fewer there is no "Show all"
|
||||
Given 5 custom types
|
||||
When the sidebar renders
|
||||
Then all 5 are listed and no "Show all" action appears
|
||||
|
||||
Scenario: On mobile the group is one collapsed row in the drawer
|
||||
Given 14 custom types and a 390 px wide viewport
|
||||
When the user opens the drawer
|
||||
Then the Content group shows one collapsed row "All content types" with the count 14
|
||||
When the user expands it
|
||||
Then the 14 types are listed with rows 44 px high
|
||||
|
||||
Scenario: No custom type hides the group
|
||||
Given GET /v1/content-type returns []
|
||||
When the shell renders
|
||||
Then there is no Content group and no empty placeholder in the sidebar
|
||||
|
||||
Scenario Outline: A failing content type request does not break the shell
|
||||
Given GET /v1/content-type <failure>
|
||||
When the shell renders
|
||||
Then the Content group is not shown
|
||||
And the rest of the sidebar and the page render normally
|
||||
|
||||
Examples:
|
||||
| failure |
|
||||
| answers 500 |
|
||||
| fails (network) |
|
||||
|
||||
Scenario: The Content group is loaded once for the whole session
|
||||
Given the user navigates between several back-office screens
|
||||
Then GET /v1/content-type is called once and the list is shared by the sidebar and the Schema list
|
||||
|
||||
Scenario: Populated list
|
||||
Given an owner or admin is signed in
|
||||
And GET /v1/content-type returns three types
|
||||
And GET /v1/builtin-type returns articles, authors, sites, media and users
|
||||
When they open /schema
|
||||
Then the page shows the title "Schema" and the text "Content types and the fields they are made of."
|
||||
And a "New content type" action is enabled
|
||||
And a table has the columns "Name", "Key", "Fields", "Last updated"
|
||||
And each custom row shows name, key, the number of fields and the updated date as an absolute date, using the board's sample data
|
||||
And after the custom rows come the five built-in rows, each with a lock icon and a "Built-in" badge
|
||||
And a built-in row shows its field count from the length of fields and an em dash as Last updated
|
||||
And the text "Built-in types are managed by RenewCMS. You can read their fields, but not change them." is shown
|
||||
And the summary "3 content types · 5 built-in" is shown
|
||||
And below 640 px the table is a list of cards with the same content
|
||||
|
||||
Scenario: Loading
|
||||
Given GET /v1/content-type has not answered
|
||||
When an owner opens /schema
|
||||
Then no skeleton is shown during the first 150 ms
|
||||
And after 150 ms skeleton rows are shown
|
||||
And "New content type" is disabled
|
||||
And the 403 page and the empty state are not shown
|
||||
|
||||
Scenario: Empty, no custom type yet
|
||||
Given GET /v1/content-type returns []
|
||||
When an owner opens /schema
|
||||
Then the empty state "No content types yet" is shown with the text "Create a content type to manage your own kind of content, like events or recipes. The built-in types stay as they are."
|
||||
And a "New content type" action is shown and enabled
|
||||
And the five built-in types are still listed below
|
||||
|
||||
Scenario Outline: Error
|
||||
Given GET /v1/content-type <failure>
|
||||
When an owner opens /schema
|
||||
Then an alert shows "Couldn't load content types"
|
||||
And it shows "GET /v1/content-type failed. Check your connection and try again."
|
||||
And a "Try again" action is shown
|
||||
And no table is shown
|
||||
|
||||
Examples:
|
||||
| failure |
|
||||
| answers 500 |
|
||||
| fails (network) |
|
||||
|
||||
Scenario: Retry recovers
|
||||
Given the list is in the Error state
|
||||
When the user activates "Try again"
|
||||
Then the skeleton rows show after 150 ms and GET /v1/content-type is called again
|
||||
And when it answers 200 the Populated state replaces the alert
|
||||
|
||||
Scenario: Forbidden for a user who is not owner or admin
|
||||
Given a verified user whose role is neither owner nor admin is signed in
|
||||
When they open /schema directly
|
||||
Then the URL stays /schema and nothing redirects
|
||||
And the page shows "You don't have access to Schema"
|
||||
And it shows "Only owners and admins can change content types. You can still add and edit entries from the Content menu."
|
||||
And a "Go to Home" action navigates to /home
|
||||
And GET /v1/builtin-type is never called
|
||||
|
||||
Scenario: The API answers 403 for the list
|
||||
Given the session says owner or admin but GET /v1/builtin-type answers 403
|
||||
When they open /schema
|
||||
Then the same "You don't have access to Schema" page is shown
|
||||
|
||||
Scenario: Built-in types fail to load while custom types load
|
||||
Given GET /v1/content-type answers 200 and GET /v1/builtin-type answers 500
|
||||
When an owner opens /schema
|
||||
Then the Error state is shown
|
||||
|
||||
Scenario: An unverified or signed-out user keeps the existing redirects
|
||||
Given the user is not verified or not signed in
|
||||
When they open /schema
|
||||
Then they are redirected to /register/pending or /login as for any other "verified" route
|
||||
|
||||
Scenario: New content type
|
||||
Given the list is Populated or Empty
|
||||
When an owner activates "New content type"
|
||||
Then the router navigates to /schema/new (the builder is a later slice)
|
||||
|
||||
Scenario: Rows are not links in this slice
|
||||
Given the list is Populated
|
||||
Then custom and built-in rows do not navigate (the editor and the read-only view are later slices)
|
||||
|
||||
Scenario Outline: The typed client maps responses
|
||||
When the client calls <call>
|
||||
And the API answers <answer>
|
||||
Then it <result>
|
||||
|
||||
Examples:
|
||||
| call | answer | result |
|
||||
| listContentTypes | 200 with icon null | returns the types, icon typed string or null |
|
||||
| listContentTypes | 200 [] | returns an empty array |
|
||||
| listContentTypes | 500 plain text | rejects with an error carrying status 500 |
|
||||
| listContentTypes | network failure | rejects with a network error |
|
||||
| listBuiltinTypes | 200 | returns {key, name, fields} items |
|
||||
| listBuiltinTypes | 403 plain text | rejects with an error carrying status 403 |
|
||||
| listBuiltinTypes | 500 plain text | rejects with an error carrying status 500 |
|
||||
Reference in new issue
Block a user