Files
RenewCMS/features/schema_list_and_content_nav.feature
T

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 |