194 lines
9.5 KiB
Gherkin
194 lines
9.5 KiB
Gherkin
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 |
|