feat: built-in type read-only view,s chema list, states, spec files

This commit is contained in:
Florian Sylvain committed 2026-10-19 22:31:13 +02:00
1 parent 3c67cd2314
commit f514146fed
24 files changed
+1220 -88

No files matched your search

+171
View File
@@ -0,0 +1,171 @@
Feature: Read-only view of a built-in content type
As an owner or admin
I want to open a built-in type from the Schema list and read its fields
So that I can see the rules RenewCMS applies without being able to change them
Background:
Given the SPA calls the API with relative URLs under /v1 and the "jwt" HttpOnly cookie
And GET /v1/builtin-type returns [{key, name, fields}] for owners and admins only
And each field is {key, label, type, required, unique, default, min, max, options}
And API errors are plain text
And the route /schema/:key has meta auth "verified" and title "Schema"
And the contentTypes store already exposes builtinItems, builtinStatus and load({builtin: true})
And the view takes its data from builtinItems, matched on the route key, and never calls GET /v1/builtin-type/{key}
And only owners and admins can use Schema; the SPA decides from the session role, the API still enforces it
And a session whose role is neither owner nor admin is a test double for the forbidden states
And the built-in types are articles ("Articles"), authors, sites, media and users with 5, 5, 8, 6 and 6 fields in the sample data
Scenario: A built-in row of the Schema list is a link
Given the Schema list is Populated
When the user looks at the five built-in rows
Then each row is a single link to /schema/<its key> whose accessible name is the type name
And the row keeps its lock icon, name, "Built-in" badge, key, field count and a chevron-right at the end
And on desktop its "Last updated" cell reads "Managed by RenewCMS"
And below 640 px the card of a built-in type shows "<key> · <n> fields" with no "Updated" text and a chevron-right
And custom rows are not links and show no chevron
Scenario: Opening a built-in type from the list
Given the Schema list is Populated
When the user activates the "Articles" row
Then the router navigates to /schema/articles
And GET /v1/builtin-type is not called a second time
Scenario: Read-only view of a built-in type (design state 6, desktop)
Given an owner or admin is signed in
And GET /v1/builtin-type returns the type "articles" named "Articles" with the fields
| label | key | type | required | unique | min | max | default |
| Title | title | text | true | false | | 200 | |
| Slug | slug | text | true | true | | | |
| Body | body | richtext | false | false | | 10000 | |
| Online | online | boolean | false | false | | | false |
| Published on | published_on | date | false | false | | | |
When they open /schema/articles
Then the breadcrumb is "Schema" (a link to /schema) then "Articles"
And the h1 "Articles" is shown with a lock icon and a "Built-in" badge
And an info alert titled "Managed by RenewCMS" says "Articles is a built-in type. You can read its fields here, but you can't edit it, delete it or add fields."
And a table has the columns "Label", "Key", "Type", "Rules"
And the rows are, in this order
| Label | Key | Type | Rules |
| Title | title | Text | Required · up to 200 characters |
| Slug | slug | Text | Required · unique |
| Body | body | Rich text | Up to 10,000 characters |
| Online | online | Yes or no | Off by default |
| Published on | published_on | Date | – |
And each row starts with the icon of its type
And the note "Entries of built-in types are managed from their own screens, like Articles and Authors." is shown under the table
And there is no "New content type", edit, delete, add field, save or discard action
And the keys are shown in the mono style
And GET /v1/content-type and GET /v1/builtin-type are each called once
Scenario: Read-only view of a built-in type on mobile
Given the same data and a 390 px wide viewport
When they open /schema/articles
Then the topbar shows an arrow-left and the title "Articles"
And the fields are cards in order, each with the icon box of its type, the label, and the key in mono followed by " · " and the rules
And the Type column is not shown
And the "Built-in" badge, the alert and the note are shown
Scenario Outline: Field types show their label and icon
Given a built-in field of type "<type>"
Then the Type cell reads "<label>" with the icon <icon>
Examples:
| type | label | icon |
| text | Text | type |
| richtext | Rich text | pilcrow |
| number | Number | hash |
| boolean | Yes or no | toggle-left |
| date | Date | calendar |
| select | Choice | list |
Scenario Outline: The Rules cell is derived from the field
Given a field of type "<type>" with <settings>
Then the Rules cell reads "<rules>"
Examples:
| type | settings | rules |
| text | required, max 200 | Required · up to 200 characters |
| text | required, unique | Required · unique |
| richtext | max 10000 | Up to 10,000 characters |
| boolean | default false | Off by default |
| boolean | default true | On by default |
| date | nothing | – |
| text | min 3, max 80 (proposed copy) | 3 to 80 characters |
| text | min 3 (proposed copy) | At least 3 characters |
| number | min 1, max 5000 (proposed copy) | Between 1 and 5000 |
| number | min 1 (proposed copy) | At least 1 |
| number | max 5000 (proposed copy) | At most 5000 |
| select | 3 options (proposed copy) | 3 choices |
| text | default "Draft" (proposed copy) | Default: Draft |
Scenario: Back to the list
Given the read-only view is shown
When the user activates the "Schema" breadcrumb, or the arrow-left on mobile
Then the router navigates to /schema
Scenario: Deep link and reload load what the view needs
Given the user opens /schema/articles as the first page of the session
Then the session is resolved, then GET /v1/builtin-type is called once
And the view shows the read-only view when it answers 200
Scenario: Loading
Given GET /v1/builtin-type has not answered
When an owner opens /schema/articles
Then nothing is shown in the content area during the first 150 ms
And after 150 ms skeleton rows are shown
And the not-found page and the 403 page are not shown
Scenario Outline: Unknown key
Given GET /v1/builtin-type returns the five built-in types
When an owner opens <path>
Then the page "Content type not found" is shown with "It may have been deleted, or the address may be mistyped."
And the URL stays <path> and nothing redirects
And no field table is shown
Examples:
| path | why |
| /schema/unknown | a mistyped key |
| /schema/events | a custom type; its builder is a later slice |
| /schema/new | the builder is a later slice |
Scenario Outline: Error
Given GET /v1/builtin-type <failure>
When an owner opens /schema/articles
Then an alert shows "Couldn't load content types"
And it shows "GET /v1/builtin-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 view is in the Error state
When the user activates "Try again"
Then GET /v1/builtin-type is called again
And when it answers 200 the read-only view 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/articles
Then the URL stays /schema/articles 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
Given the session says owner or admin but GET /v1/builtin-type answers 403
When they open /schema/articles
Then the same "You don't have access to Schema" page 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/articles
Then they are redirected to /register/pending or /login as for any other "verified" route
Scenario: The list and the view share one load
Given the user opens /schema, then /schema/articles, then goes back
Then GET /v1/builtin-type and GET /v1/content-type are each called once in the session
+42
View File
@@ -0,0 +1,42 @@
# QA: Read-only view of a built-in type (SPA slice 2)
Runner: Vitest + Vue Test Utils (`task test:web`). No Go test: there is no backend change.
Same pattern as features/schema_list_and_content_nav.qa.md: mock `/v1` through the existing api composable, mount with a test router (routes /schema, /schema/:key) and a resolved session whose role varies. The forbidden cases use a session with an unknown role.
## Automated
Fixtures: the five built-in types with the board's sample data; "articles" has the 5 fields of the board (Title, Slug, Body, Online, Published on), the others only need a `fields` array of the right length (5, 5, 8, 6, 6). Three custom types as in slice 1.
SchemaTable / list (extend SchemaView.spec and the SchemaTable spec):
1. Built-in rows are single links (`a` with href /schema/<key>), accessible name = type name, chevron present, "Managed by RenewCMS" in the desktop Last updated cell, no "Updated" text on the mobile card (use `components/__tests__/viewport.ts`).
2. Custom rows are not links and have no chevron. Replaces the slice-1 test "rows are not links".
3. Activating the "Articles" row navigates to /schema/articles; no second GET /v1/builtin-type.
Read-only view (new SchemaBuiltinView.spec), one test per state:
4. Read-only: owner and admin. Assert the breadcrumb, h1 with lock and "Built-in", alert title and text, columns, the five rows (label, key, type label, rules) in order, the note, and the absence of any new/edit/delete/add/save/discard button. Both /v1 calls made once.
5. Mobile: arrow-left + title, cards with key and rules, no Type column.
6. Type labels and icons (it.each over the 6 types).
7. Rules derivation (it.each over the Examples table of the feature, incl. 10,000 separator, the en dash for empty, and capitalisation of the first part). Cases tagged "proposed copy" are separate it.each rows so a design change touches them only.
8. Back: the breadcrumb and the mobile arrow push /schema.
9. Deep link: a store in its initial state loads the builtin list once on mount.
10. Loading: unresolved promise, fake timers. Nothing at 149 ms, skeleton at 150 ms, no not-found or 403 page. A fast response never shows the skeleton.
11. Unknown key: it.each over /schema/unknown, /schema/events, /schema/new. Assert the "Content type not found" page copy, URL unchanged, no table.
12. Error: 500 and network failure. Assert the alert copy ("GET /v1/builtin-type failed. ..."), "Try again", no table. Retry then returns 200 and the view replaces the alert (second call made).
13. Forbidden: unknown role gives the 403 page copy, "Go to Home" navigates to /home, URL stays /schema/articles, builtin-type never called. Plus a case where builtin-type answers 403 for an owner.
14. Shared load: navigating /schema -> /schema/articles -> back calls each list endpoint once.
Router and guard:
15. `/schema/:key` is registered with auth "verified" and title "Schema". Unverified and signed-out users get the existing redirects (extend routerGuard.spec). /schema (exact) is unchanged.
## Visual (task visual:check -- schema)
Run `task visual:check -- schema`; every capture within 10 % at desktop and mobile, light and dark. Nothing is clock-sensitive.
- Replace `skip: 'pending'` of state 6 in `web/visual/states.ts` with a real setup: open /schema/articles. Mock GET /v1/session, GET /v1/content-type (the 3 custom types), GET /v1/builtin-type with the five types. "articles" must carry the board's 5 fields: Title (text, required, max 200), Slug (text, required, unique), Body (richtext, max 10000), Online (boolean, default false), Published on (date, none). Field counts 5, 5, 8, 6, 6. Keys in the mock follow the board's sample text ("articles" etc.), since the view resolves the key from data. Use the sidebar mock of states 1-5 and mock every other /v1 call the screen makes.
- States 1 to 5 must still pass after the list-row change (chevron on built-in rows, "Managed by RenewCMS" in Last updated). If the board and the slice-1 rendering differ on custom rows' chevron, report it, do not fix it silently.
- Loading, Error, Unknown key and Forbidden-on-detail have no board frame: Vitest only, not in the visual check.
## Manual
- Keyboard: Tab order list rows (each row one stop), Enter on a focused built-in row opens it; visible focus ring; the breadcrumb and back arrow are reachable.
- Hover on a built-in row, and focus/hover motion.
- Real backend: log in as owner and as admin, open each of the five built-in rows, compare the rendered Rules with the actual fields returned by GET /v1/builtin-type (the board's rules are illustrative), and confirm a plain user gets the 403 page on /schema/article.
+4 -3
View File
@@ -105,7 +105,7 @@ Feature: Schema list and content navigation in the back-office
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 a built-in row shows its field count from the length of fields and "Managed by RenewCMS" as Last updated on desktop (no Updated text on mobile)
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
@@ -173,9 +173,10 @@ Feature: Schema list and content navigation in the back-office
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
Scenario: Custom 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)
Then custom rows do not navigate (the editor is a later slice)
And built-in rows are links, see schema_builtin_view.feature
Scenario Outline: The typed client maps responses
When the client calls <call>
+1 -1
View File
@@ -35,7 +35,7 @@ Client and store:
Run `task visual:check -- schema`, every capture within 10 % at desktop and mobile, light and dark. States to set up in `web/visual/states.ts`, reusing the board's sample data (names, keys, field counts, dates, counts "3 content types · 5 built-in" taken from SchemaFrame.dc.html):
- 1 Populated, 2 Loading, 3 Empty, 4 Error, 5 Forbidden (403).
- State 6 (built-in read-only view) is out of scope: report it as not set up in this slice, and flag it to the user if the check demands a setup for every state.
- State 6 (built-in read-only view) is covered by schema_builtin_view.qa.md.
- None of these is clock-sensitive; if the Last updated cell turns out to be relative on the board, mark it `skip: 'clock-sensitive'`.
- Mock every `/v1` call the screen makes, including the sidebar's GET /v1/content-type.
- The sidebar group and drawer frames belong to the later `content` group.