feat: schema builder's create flow
This commit is contained in:
1 parent
f514146fed
commit
083cc9e23e
71 files changed
+7223
-61
No files matched your search
@@ -0,0 +1,516 @@
|
||||
Feature: Create a custom content type in the Schema builder
|
||||
As an owner or admin
|
||||
I want to define a new content type (name, key and ordered fields) on /schema/new
|
||||
So that users can write entries of that type
|
||||
|
||||
Background:
|
||||
Given the SPA calls the API with relative URLs under /v1 and the "jwt" HttpOnly cookie
|
||||
And API errors are plain text
|
||||
And POST /v1/content-type takes {key, name, fields[{key, label, type, required, unique, default, min, max, options}]}
|
||||
And the builder never sends an "icon" key
|
||||
And POST answers 201 with the content type, 400 invalid_content_type_key, 409 content_type_key_taken, 422 invalid_field, 403 or 500
|
||||
And the route /schema/new is declared before /schema/:key, has meta auth "verified" and title "Schema", and renders the builder
|
||||
And a route meta flag, not a path pattern, tells the shell that /schema/new needs the custom list only
|
||||
And only owners and admins can use Schema; the SPA decides from the session role, the API still enforces it
|
||||
And the six field types are text "Text", richtext "Rich text", number "Number", boolean "Yes or no", date "Date" and select "Choice"
|
||||
And the limits are a type name of up to 100 characters, a type key of 2 to 40, 1 to 50 fields, a field key of up to 40 characters and 1 to 50 choices
|
||||
And the layout is: Name and Key with the status chip, Discard and Save on top; the FieldList on the left; the FieldInspector on the right; the ApiPreview at the bottom
|
||||
And below 48rem the FieldList is the page, the FieldInspector is a 92% bottom sheet closed with "Done", the ApiPreview is a bottom sheet, and Discard and Save sit in a bottom bar
|
||||
And every string goes through t() and has a French entry in i18n/messages.ts
|
||||
And a copy tagged "(proposed copy)" is not in the design and is the coder's single point of change
|
||||
|
||||
Scenario: Reaching the builder
|
||||
Given the Schema list is Populated or Empty
|
||||
When the user activates "New content type" (header button, empty-state button or the mobile floating button)
|
||||
Then the router navigates to /schema/new
|
||||
And the builder is shown, not the "Content type not found" page
|
||||
And /schema/events and /schema/unknown still show "Content type not found" for a key that is not built-in
|
||||
|
||||
Scenario: Roles and sessions
|
||||
Given <who> opens /schema/new
|
||||
Then <result>
|
||||
|
||||
Examples:
|
||||
| who | result |
|
||||
| a verified owner or admin | the builder |
|
||||
| a verified user who is neither | "You don't have access to Schema" with "Go to Home", URL unchanged |
|
||||
| an unverified user | the redirect to /register/pending |
|
||||
| a signed-out visitor | the redirect to /login |
|
||||
|
||||
Scenario: The builder needs no built-in list
|
||||
Given an owner opens /schema/new as the first page of the session
|
||||
Then GET /v1/content-type is called once for the sidebar
|
||||
And GET /v1/builtin-type is never called
|
||||
And the builder is shown as soon as the session is ready, with no skeleton
|
||||
|
||||
Scenario: New type, desktop (design state 1)
|
||||
Given an owner opens /schema/new on a 1440 px viewport
|
||||
Then the breadcrumb is "Schema" (a link to /schema) then "New content type"
|
||||
And "Name" is an empty text input with the placeholder "For example, Events", marked required, with maxlength 100
|
||||
And "Key" is a read-only mono input with the placeholder "events" and the help "Used in URLs and the API. Lowercase letters, digits and hyphens. It can't change after you save."
|
||||
And the list header reads "Fields" and "1 of 50"
|
||||
And one row is shown with the label "Title" and the key "title" in mono, selected, with the badge "New"
|
||||
And "Add field" is shown under the list
|
||||
And the inspector title is "Field: Title" with the badge "New"
|
||||
And "Label" holds "Title"
|
||||
And "Key" holds "title" with the Auto badge and the help "The name used in the API. Starts with a letter. Lowercase letters, digits and underscores."
|
||||
And the type tiles show Text selected
|
||||
And "Required" is off with "An entry can't be saved with this field empty."
|
||||
And "Unique values" is off
|
||||
And "Default value" is empty with "Used when an entry is created and this field is left out."
|
||||
And "Shortest" and "Longest" are empty with "Counted in characters. Entries show a counter."
|
||||
And "Save" and "Discard" are disabled
|
||||
And no status chip is shown
|
||||
And no "entries use this content type" bar is shown
|
||||
And the API preview bar is collapsed
|
||||
|
||||
Scenario: New type, mobile (design state 1)
|
||||
Given the same page on a 390 px viewport
|
||||
Then the topbar shows an arrow-left and the title "New content type"
|
||||
And Name and Key are stacked above the list, then the rows, then the "API preview" row with a chevron
|
||||
And no inspector is shown until a row is activated
|
||||
And the bottom bar holds Discard and Save
|
||||
|
||||
Scenario Outline: The key follows the name until it is edited
|
||||
Given an empty new type
|
||||
When the user types the name "<name>"
|
||||
Then the Key holds "<key>" with the Auto badge, read-only
|
||||
|
||||
Examples:
|
||||
| name | key |
|
||||
| Events | events |
|
||||
| Press mentions | press-mentions |
|
||||
| Café & Bar | cafe-bar |
|
||||
| Job openings! | job-openings |
|
||||
| 2026 Awards | 2026-awards |
|
||||
| !!! | (empty, mode empty) |
|
||||
| a name of 60 letters | the first 40 characters, without a trailing hyphen |
|
||||
|
||||
Scenario: Editing and resetting the key
|
||||
Given the name "Events" and the Auto key "events"
|
||||
When the user activates the pencil
|
||||
Then the Key becomes editable, the Auto badge goes and "Reset to match the name" is shown
|
||||
When the user types the key "agenda" then edits the name to "Events and talks"
|
||||
Then the Key stays "agenda"
|
||||
When the user activates "Reset to match the name"
|
||||
Then the Key is "events-and-talks" with the Auto badge
|
||||
|
||||
Scenario Outline: Type-level rules (design states 6 and 14)
|
||||
Given a new type with a valid field
|
||||
When the user sets <input> to <value> and activates Save
|
||||
Then <message> is shown under <input> with an alert icon
|
||||
And POST /v1/content-type is not called
|
||||
|
||||
Examples:
|
||||
| input | value | message |
|
||||
| Name | (empty or spaces) | Give the content type a name. |
|
||||
| Key | e | Use 2 to 40 lowercase letters, digits or hyphens. |
|
||||
| Key | Events | Use 2 to 40 lowercase letters, digits or hyphens. |
|
||||
| Key | my_type | Use 2 to 40 lowercase letters, digits or hyphens. |
|
||||
| Key | -events | Use 2 to 40 lowercase letters, digits or hyphens. |
|
||||
| Key | events--2 | Use 2 to 40 lowercase letters, digits or hyphens. |
|
||||
| Key | new | The key "new" is reserved. Choose a different key. (proposed copy) |
|
||||
|
||||
Scenario Outline: Accepted type keys
|
||||
Given a new type with a valid field and the name "X"
|
||||
When the user types the key "<key>" and activates Save
|
||||
Then POST /v1/content-type is called with that key
|
||||
|
||||
Examples:
|
||||
| key |
|
||||
| ab |
|
||||
| press-mentions |
|
||||
| 2026-awards |
|
||||
| 40 lowercase letters |
|
||||
|
||||
Scenario: Type-level errors together (design state 6)
|
||||
Given a new type with no name, the key "e" and no field
|
||||
When the user activates Save
|
||||
Then "Give the content type a name.", "Use 2 to 40 lowercase letters, digits or hyphens." and "A content type needs at least one field." are shown
|
||||
And the empty list shows "No fields yet" with the message in the error colour
|
||||
And the chip reads "3 problems to fix"
|
||||
And Save is disabled until all three are fixed, and Discard stays enabled
|
||||
And the inspector shows "Field settings" and "Add a field, then choose its type and rules here."
|
||||
|
||||
Scenario: When errors are shown
|
||||
Given a new type whose name is empty
|
||||
Then no error is shown while nothing was edited
|
||||
When the user edits an input, an error for that input shows as they type
|
||||
And when the user activates Save, every error of the form shows at once
|
||||
And errors are tied to their input with aria-describedby and aria-invalid
|
||||
And a row with an error shows an alert icon, not only the red border
|
||||
|
||||
Scenario Outline: The status chip
|
||||
Given <situation>
|
||||
Then the chip reads "<chip>"
|
||||
|
||||
Examples:
|
||||
| situation | chip |
|
||||
| a pristine new type | (no chip) |
|
||||
| an edit with no error | Unsaved changes |
|
||||
| three inputs in error | 3 problems to fix |
|
||||
| one input in error | 1 problem to fix |
|
||||
| the POST in flight | Saving… |
|
||||
| the POST failed with 500 | Save failed |
|
||||
| navigator.onLine is false | Waiting for connection |
|
||||
|
||||
Scenario Outline: Counting problems
|
||||
Given <errors>
|
||||
Then the chip counts <count>
|
||||
|
||||
Examples:
|
||||
| errors | count |
|
||||
| no name, a one-letter key, no field | 3 |
|
||||
| a field with no label, a field with min above max, a choice list with a blank and a duplicate | 3 |
|
||||
| one choice list with a blank row and a duplicate row | 1 |
|
||||
| one field with a bad key | 1 |
|
||||
|
||||
Scenario: Field list rows
|
||||
Given a type with the fields Title, Starts on, Online
|
||||
Then each row shows a grip button labelled "Move <label>", the icon box of its type, the label and the key in mono
|
||||
And an unselected row shows its badges "New", "Required", "Unique" and an alert icon when in error
|
||||
And the selected row is on the sand background and shows Move up, Move down and Remove
|
||||
And a field without a label reads "Untitled field" and a field without a key reads "–"
|
||||
|
||||
Scenario: Adding a field
|
||||
Given the list has 1 field
|
||||
When the user activates "Add field"
|
||||
Then a row is appended, selected, with a Text type and no label, and the label input is focused
|
||||
And the header reads "2 of 50"
|
||||
And at 50 fields "Add field" is disabled and the header reads "50 of 50"
|
||||
|
||||
Scenario Outline: Field key from the label
|
||||
Given a new field is selected
|
||||
When the user types the label "<label>"
|
||||
Then its Key holds "<key>" with the Auto badge
|
||||
|
||||
Examples:
|
||||
| label | key |
|
||||
| Title | title |
|
||||
| Starts on | starts_on |
|
||||
| Éléments | elements |
|
||||
| Ends on (date) | ends_on_date |
|
||||
| 2nd room | nd_room |
|
||||
| Title (when "title" exists)| title_2 |
|
||||
| a label of 60 letters | the first 40 characters |
|
||||
|
||||
Scenario Outline: Field key rules (design state 7)
|
||||
Given a new field is selected with the Key in manual mode
|
||||
When the user types the key "<key>"
|
||||
Then <outcome>
|
||||
|
||||
Examples:
|
||||
| key | outcome |
|
||||
| Title | "Start with a letter. Use lowercase letters, digits and underscores." under Key |
|
||||
| 1st | the same message |
|
||||
| a-b | the same message |
|
||||
| a b | the same message |
|
||||
| (empty) | the same message |
|
||||
| starts_on | no error |
|
||||
| a | no error |
|
||||
| a_40_characters | no error, and a 41st character cannot be typed |
|
||||
|
||||
Scenario: Duplicate field keys
|
||||
Given two fields whose keys are both "title" in manual mode
|
||||
Then "Another field already uses this key." (proposed copy) is shown under the Key of the second field
|
||||
And the chip counts one problem for that field
|
||||
|
||||
Scenario: A single field key error (design state 7)
|
||||
Given a new field with the key "start-date" and everything else valid
|
||||
Then the Key shows the error with the alert icon and the chip reads "1 problem to fix"
|
||||
And Save is disabled
|
||||
|
||||
Scenario Outline: The inspector follows the type
|
||||
Given a new field of type "<type>"
|
||||
Then the limits are labelled <limits>
|
||||
And "Unique values" is <unique>
|
||||
And the default control is <default>
|
||||
|
||||
Examples:
|
||||
| type | limits | unique | default |
|
||||
| text | "Shortest" and "Longest" with the suffix "characters" | available | a text input, placeholder "No default" |
|
||||
| richtext | "Shortest" and "Longest" with the suffix "characters" | disabled with "Not available for rich text." | a textarea, placeholder "No default" |
|
||||
| number | "Lowest value" and "Highest value" | available | a number input, placeholder "No default" |
|
||||
| boolean | none; "Yes or no fields have no limits." | disabled with "Not available for yes or no fields." | the switch "On by default" with help "New entries start with this switch off when the field is left out." |
|
||||
| date | "Earliest date" and "Latest date" | available | a date input, placeholder "No default" |
|
||||
| select | none; "Limits come from the choices below." | available | a select of the choices, placeholder "None" |
|
||||
|
||||
Scenario: Type tiles
|
||||
Given the inspector of a new field
|
||||
Then six radio tiles are shown, each with its icon, name and description
|
||||
| tile | icon | description |
|
||||
| Text | type | Short text, like a title or a name |
|
||||
| Rich text | pilcrow | Long text with formatting, written in Markdown|
|
||||
| Number | hash | Whole or decimal numbers |
|
||||
| Yes or no | toggle-left | An on or off switch |
|
||||
| Date | calendar | A calendar day, without a time |
|
||||
| Choice | list | One option from a list you define |
|
||||
And the selected tile is on the sand background
|
||||
And the help under "Longest" reads "Counted in characters. Entries show a counter." for Text and Rich text, and "Entries outside the range are refused." for Number and Date
|
||||
|
||||
Scenario: Changing the type of a new field
|
||||
Given a Number field with min 1, max 5000, default 20 and unique on
|
||||
When the user picks the tile "Yes or no"
|
||||
Then the label, the key and Required are kept
|
||||
And min, max, default, options and unique are reset
|
||||
When the user picks "Choice"
|
||||
Then the choice list editor is shown with no choice
|
||||
|
||||
Scenario Outline: Field rules
|
||||
Given a new field of type "<type>" with <settings>
|
||||
Then <message> is shown under <where>
|
||||
|
||||
Examples:
|
||||
| type | settings | message | where |
|
||||
| text | an empty label or spaces | Give the field a label. | Label |
|
||||
| number | min 6000, max 5000 | Lowest value can't be above the highest value. | the limits |
|
||||
| text | min 10, max 3 | Shortest can't be longer than longest. (proposed copy) | the limits |
|
||||
| date | min 2027-12-31, max 2026-01-01 | Earliest date can't be after the latest date. (proposed copy) | the limits |
|
||||
| text | min 2.5 | Use a whole number, 0 or more. (proposed copy) | the limit |
|
||||
| text | min -1 | Use a whole number, 0 or more. (proposed copy) | the limit |
|
||||
| number | min 1, max 5000, default 0 | The default value is outside the limits. (proposed copy) | Default value |
|
||||
| text | max 3, default "abcd" | The default value is outside the limits. (proposed copy) | Default value |
|
||||
| select | no choice | Add at least one choice. (proposed copy) | the choice list|
|
||||
| select | a blank choice | Choices can't be blank. | that row |
|
||||
| select | "Talk" twice | Two choices are the same. | the second row |
|
||||
| number | min 1, max 5000 | no error | - |
|
||||
| number | min 5, max 5 | no error | - |
|
||||
| text | min 0 | no error | - |
|
||||
|
||||
Scenario: Unique and default
|
||||
Given a Text field with the default "Draft"
|
||||
When the user turns "Unique values" on
|
||||
Then the Default value input is disabled with "A field with unique values can't have a default."
|
||||
And the default is not sent
|
||||
When the user turns "Unique values" off
|
||||
Then the default "Draft" is back
|
||||
|
||||
Scenario: Choice list editor (design state 8)
|
||||
Given a Choice field with the choices Talk, Workshop, Meetup
|
||||
Then "Choices" is shown with the required marker and "3 of 50"
|
||||
And each choice is a row with a grip, a text input, Remove, and the placeholder "Choice"
|
||||
And an input "Add a choice" with the button "Add" is shown
|
||||
And the help reads "Add 1 to 50 choices. Each one needs a different name. Removing a choice that entries use will be refused when you save."
|
||||
When the user types "Panel" in "Add a choice" and activates Add (or presses Enter)
|
||||
Then the choice is appended and the input is emptied and keeps the focus
|
||||
And the Default value select lists the choices
|
||||
When the selected default choice is removed or renamed
|
||||
Then the default is cleared
|
||||
|
||||
Scenario: Choice list limits and errors
|
||||
Given a Choice field with 5 choices, the fourth blank and the fifth equal to the first
|
||||
Then "Choices can't be blank." is shown under the fourth row and "Two choices are the same." under the fifth, each with an alert icon
|
||||
And the field counts as one problem
|
||||
And at 50 choices the Add button is disabled
|
||||
And choices are compared after trimming, case-sensitively
|
||||
|
||||
Scenario: Reordering choices
|
||||
Given a Choice field with 3 choices
|
||||
When the user uses the grip of the second choice
|
||||
Then Move up and Move down work as for fields, with the same live region ("Moving Workshop. Position 2 of 3. ...")
|
||||
|
||||
Scenario: Removing a field
|
||||
Given a list with 3 fields
|
||||
When the user activates Remove on the selected one
|
||||
Then it disappears with no undo row (an undo row only exists for saved fields, slice 4)
|
||||
And the next row is selected, or the previous one at the end, or none if the list is empty
|
||||
And the header counts one less
|
||||
|
||||
Scenario Outline: Moving fields with the buttons
|
||||
Given a list Title, Starts on, Ends on with <selected> selected
|
||||
When the user activates <button>
|
||||
Then the order becomes <order>
|
||||
And the selected row stays selected and keeps the focus on the same button when possible
|
||||
|
||||
Examples:
|
||||
| selected | button | order |
|
||||
| Starts on | Move up | Starts on, Title, Ends on |
|
||||
| Starts on | Move down | Title, Ends on, Starts on |
|
||||
|
||||
Scenario Outline: Disabled ends
|
||||
Given a list of 3 fields with <row> selected
|
||||
Then <button> is disabled
|
||||
|
||||
Examples:
|
||||
| row | button |
|
||||
| Title | Move up |
|
||||
| Ends on | Move down |
|
||||
|
||||
Scenario: Keyboard reorder (design state 9)
|
||||
Given the list Title, Starts on, Ends on
|
||||
When the user focuses the grip "Move Starts on" and presses Space
|
||||
Then the row is picked up with the primary outline
|
||||
And the polite live region reads "Moving Starts on. Position 2 of 3. Press Up or Down to move, Space to drop, Esc to cancel."
|
||||
When the user presses Up
|
||||
Then the row is first and the live region reads "Moving Starts on. Position 1 of 3. ..."
|
||||
When the user presses Space
|
||||
Then the new order is kept and the live region announces the drop
|
||||
When the user picks it up again, moves it and presses Esc
|
||||
Then the order returns to what it was before the pick-up
|
||||
|
||||
Scenario: No pointer drag in this slice
|
||||
Then rows cannot be dragged with the pointer
|
||||
|
||||
Scenario: Selecting a field on mobile
|
||||
Given a 390 px viewport
|
||||
When the user activates a row
|
||||
Then a bottom sheet titled "Field: <label>" opens with the inspector, Move up, Move down and Remove (proposed placement), and a "Done" button
|
||||
And focus moves to the sheet title
|
||||
When the user activates Done
|
||||
Then the sheet closes and the focus returns to the row
|
||||
|
||||
Scenario: API preview (design state 10)
|
||||
Given the type key "events" and the fields Title, Starts on, Online, Capacity, Category
|
||||
When the user opens "API preview"
|
||||
Then the panel shows "Where entries of this type live" with these five routes
|
||||
| GET | /v1/content-type/events/entry |
|
||||
| POST | /v1/content-type/events/entry |
|
||||
| GET | /v1/content-type/events/entry/{id} |
|
||||
| PUT | /v1/content-type/events/entry/{id} |
|
||||
| DELETE | /v1/content-type/events/entry/{id} |
|
||||
And "A sample entry. Values come from each field's type and default." with the JSON of a sample entry
|
||||
And "Updates as you edit. Fields you add or remove here appear after you save."
|
||||
And the collapsed bar shows "API preview", "GET /v1/content-type/events/entry" in mono and "Copy JSON"
|
||||
And the key falls back to "events" while the Key is empty
|
||||
|
||||
Scenario Outline: Sample values in the preview
|
||||
Given a field "f" of type "<type>" with <settings>
|
||||
Then the sample entry data holds <value> for "f"
|
||||
|
||||
Examples:
|
||||
| type | settings | value |
|
||||
| text | no default | "Example text" |
|
||||
| text | default "Draft" | "Draft" |
|
||||
| richtext | no default | "Markdown text" |
|
||||
| number | no default, no min | 0 |
|
||||
| number | min 1 | 1 |
|
||||
| number | min 1, default 20 | 20 |
|
||||
| boolean | no default | false |
|
||||
| boolean | on by default | true |
|
||||
| date | no default | "2026-11-21" |
|
||||
| select | choices Talk, Workshop | "Talk" |
|
||||
| select | default "Workshop" | "Workshop" |
|
||||
| select | no choice yet | null |
|
||||
|
||||
Scenario: The sample entry envelope and live updates
|
||||
Then the sample is {"id": 42, "content_type": <key>, "data": {...}, "created_by": 3, "updated_by": 3, "created_at": "2026-10-15T11:02:00Z", "updated_at": "2026-10-15T11:02:00Z"} indented by 2 spaces
|
||||
And the data keys follow the field order and skip fields without a valid key
|
||||
And it updates on every edit, reorder and removal
|
||||
And it contains no date from the clock
|
||||
|
||||
Scenario: Copy JSON
|
||||
When the user activates "Copy JSON"
|
||||
Then the sample entry JSON is written to the clipboard
|
||||
And the button reads "Copied" for 2 seconds, then "Copy JSON" again
|
||||
And the same happens in the mobile sheet
|
||||
|
||||
Scenario: API preview on mobile
|
||||
Given a 390 px viewport
|
||||
When the user activates the row "API preview"
|
||||
Then a bottom sheet titled "API preview" shows "GET /v1/content-type/events/entry", the JSON and a full-width "Copy JSON" button
|
||||
|
||||
Scenario: Saving a valid type
|
||||
Given the name "Events", the key "events" and these fields
|
||||
| label | key | type | required | unique | default | min | max | options |
|
||||
| Title | title | text | true | true | | | 80 | |
|
||||
| Starts on | starts_on | date | true | false | | 2026-01-01 | 2027-12-31 | |
|
||||
| Online | online | boolean | false | false | on | | | |
|
||||
| Capacity | capacity | number | false | false | 20 | 1 | 5000 | |
|
||||
| Category | category | select | false | false | Talk | | | Talk, Workshop, Meetup |
|
||||
| Description | description | richtext | false | false | | | 5000 | |
|
||||
When the user activates Save
|
||||
Then POST /v1/content-type is called once with the key "events", the name "Events" and the fields in list order
|
||||
And each field carries exactly key, label, type, required, unique, default, min, max, options
|
||||
And unset default, min and max are null, and options are [] for a non-select field
|
||||
And the boolean default is true, an off switch sends null, a date bound is "YYYY-MM-DD", the name is trimmed, choices are trimmed
|
||||
And the body has no "icon" key
|
||||
|
||||
Scenario: Saving (design state 11, create)
|
||||
Given the POST has not answered
|
||||
Then the chip reads "Saving…" with a loader, Save shows a loader and is disabled, Discard is disabled
|
||||
And Name, Key and the inspector are dimmed and not editable
|
||||
And Add field, the grips, the Move and Remove buttons are disabled
|
||||
And a second activation of Save sends no second request
|
||||
|
||||
Scenario: Saved (design state 12, create)
|
||||
Given the POST answers 201 with the content type "events" named "Events" and 6 fields
|
||||
Then a success toast "Content type saved" with "Events now has 6 fields." is shown
|
||||
And the content types store holds "events" in name order when its list was loaded
|
||||
And the sidebar Content group lists "Events"
|
||||
And the router replaces the URL with /schema, where the Events row is listed
|
||||
And no "Leave without saving?" prompt is shown
|
||||
And with one field the toast reads "Events now has 1 field." (proposed copy)
|
||||
|
||||
Scenario: Key already taken (design state 14)
|
||||
Given the name "Events", the key "events" and the fields Title (required) and Starts on (date)
|
||||
When the POST answers 409 with the body "content_type_key_taken"
|
||||
Then under Key: The key "events" is already used by another content type. Choose a different key.
|
||||
And the Key is editable (manual mode), has the error and the focus, and the Auto badge is hidden
|
||||
And the chip reads "1 problem to fix" and Save is disabled
|
||||
And the fields and the name are untouched
|
||||
When the user edits the key
|
||||
Then the error goes and Save is enabled
|
||||
|
||||
Scenario: The server refuses a key or a field
|
||||
Given the POST answers <status> with <body>
|
||||
Then <outcome>
|
||||
|
||||
Examples:
|
||||
| status | body | outcome |
|
||||
| 400 | invalid_content_type_key | the key message under Key, as in the rules above |
|
||||
| 422 | invalid_field | the banner "Check the fields marked in the list." (proposed title and placement), edits kept |
|
||||
| 403 | Only ... | the page "You don't have access to Schema" |
|
||||
| 401 | (any) | the existing redirect to /login with the expired reason |
|
||||
|
||||
Scenario Outline: Save failed (design state 13)
|
||||
Given the POST <failure>
|
||||
Then an error alert titled "Couldn't save the content type" shows "Something went wrong on our side. Your changes are still here. Try again in a moment."
|
||||
And it has a "Try again" action and the chip reads "Save failed"
|
||||
And every edit is kept and Save stays enabled
|
||||
When the user activates "Try again"
|
||||
Then the same body is sent again
|
||||
|
||||
Examples:
|
||||
| failure |
|
||||
| answers 500 |
|
||||
| fails (network) |
|
||||
|
||||
Scenario: Offline (design state 16)
|
||||
Given the user has edited the form and navigator.onLine becomes false
|
||||
Then the banner "You're offline. Changes will be saved when you reconnect." with a wifi-off icon is shown
|
||||
And the chip reads "Waiting for connection"
|
||||
And Save is disabled and every edit is kept
|
||||
When the connection returns
|
||||
Then the banner goes, Save is enabled again and nothing is sent by itself
|
||||
|
||||
Scenario: Discard
|
||||
Given the user has edited the form
|
||||
When the user activates Discard
|
||||
Then Name and Key are empty, the list holds the starter field Title, errors and the chip are cleared
|
||||
And nothing is sent and the URL stays /schema/new
|
||||
And Discard and Save are disabled again
|
||||
|
||||
Scenario Outline: Leaving with unsaved edits
|
||||
Given <state>
|
||||
When the user leaves through the breadcrumb, the mobile arrow, the sidebar or the browser back
|
||||
Then <result>
|
||||
|
||||
Examples:
|
||||
| state | result |
|
||||
| a pristine form | the router navigates with no prompt |
|
||||
| an edited name, key or field | the dialog "Leave without saving?" with "Keep editing" and "Discard changes" is shown |
|
||||
| after Save answered 201 | the router navigates with no prompt |
|
||||
|
||||
Scenario: Keep editing or discard on leave
|
||||
Given the leave dialog is open
|
||||
When the user chooses "Keep editing" (or dismisses the dialog)
|
||||
Then the user stays on /schema/new with every edit
|
||||
When the user chooses "Discard changes"
|
||||
Then the router navigates away
|
||||
|
||||
Scenario: Accessibility of the builder
|
||||
Then every control has a visible label
|
||||
And the type tiles are a radio group
|
||||
And the field list is one tab stop and arrow keys move between rows
|
||||
And controls are 44 px high on mobile
|
||||
And focus is a 2 px ring in the primary colour
|
||||
Reference in new issue
Block a user