147 lines
9.3 KiB
Gherkin
147 lines
9.3 KiB
Gherkin
Feature: Creating a new article from NewArticleModal
|
|
|
|
As a verified RenewCMS owner
|
|
I want NewArticleModal to take a title and a first paragraph, create the article as a draft
|
|
over POST /v1/article/, and take me straight to the editor to keep writing
|
|
So that starting a new article is a two-field, low-friction step instead of a full form, and I
|
|
land where I can actually write it
|
|
|
|
Background:
|
|
Given NewArticleModal is a UModal opened from Home's "New article" action (mobile header
|
|
button, desktop header button, and the empty state's "New article" action), per
|
|
docs/design/routes.md and docs/design/screens/new-article.md; the Articles-page trigger and
|
|
the /article/new deep link are out of scope for this slice, deferred until ArticlesPage exists
|
|
And every time it opens, it starts in the Default state: title "New article", text "It's
|
|
saved as a draft. Titles can't be changed later, so check it now.", an empty, focused Title
|
|
field with placeholder "e.g. Choosing tiles for a small bathroom", a CharacterCounter
|
|
reading "0 / 200" and help "3 to 200 characters.", an empty First paragraph textarea with
|
|
placeholder "Start writing. You'll continue in the editor." and help "You can format it and
|
|
add images in the editor.", a "Cancel" button and a "Create draft" button
|
|
And the Title field is sent as the request's title and is validated required, 3 to 200
|
|
characters, counted the same way as the CharacterCounter ([...str].length, which is rune
|
|
count, matching article_title_rules.feature's server-side rune counting)
|
|
And the First paragraph field is sent as the request's body and is validated required (at
|
|
least one character)
|
|
And each field validates on blur and again on submit; submitting while either field is
|
|
invalid sends no request, and shows both fields' errors at once if both are invalid: Title
|
|
under 3 characters shows "Use at least 3 characters." in place of its help text, with the
|
|
counter in its error style; an empty First paragraph shows "Add a first paragraph. It can
|
|
be a single sentence." in place of its help text
|
|
And activating "Create draft" while both fields are valid sends POST /v1/article/ with JSON
|
|
{"title": "<title>", "body": "<first paragraph>"}; while the request is in flight both
|
|
fields and both buttons are disabled, the confirm button reads "Creating…" with a loading
|
|
indicator, and the modal cannot be dismissed by Cancel, Esc or a backdrop click
|
|
And a 201 response closes the modal, emits created(article) with the response body, and (for
|
|
this slice) HomeView.vue's created handler navigates to /article/{id}/edit and raises a
|
|
toast: color success, icon i-lucide-circle-check, title "Draft created", description
|
|
"\"{title}\" is saved as a draft.", with id "article-created-{id}" per
|
|
docs/design/components.md's toast-id convention
|
|
And a 400 response — whether apperr.ArticleTitleErrorMessage for a title that passed this
|
|
form's own check but still failed the server's, or any other 400 — is never shown verbatim;
|
|
it re-enables the form with both fields' current values kept, shows "Check this title and
|
|
try again." in place of the Title field's help text, and shows an alert "Something in the
|
|
form isn't valid. Check the highlighted fields."; the First paragraph field shows no error
|
|
And a 500 response or a request that never reaches the server re-enables the form with both
|
|
fields' current values kept and shows an alert "Couldn't create the article. Your text is
|
|
still here. Try again in a moment.", with no field-level error
|
|
And "Cancel" always closes the modal immediately and discards both fields, with no
|
|
confirmation prompt, whenever the form is not disabled by an in-flight request
|
|
And a 401 response is handled entirely by useApi's existing global handling
|
|
(features/api_client_global_error_handling.feature): the session is cleared and the app
|
|
navigates to /login?redirect=<path>&reason=expired; this slice adds no draft-preservation
|
|
behavior for the modal's fields, unlike the editor's session-expiry handling
|
|
|
|
Scenario: Opening the modal shows the Default state
|
|
Given the user is on /home
|
|
When the user activates "New article"
|
|
Then NewArticleModal opens with title "New article" and the text "It's saved as a draft.
|
|
Titles can't be changed later, so check it now."
|
|
And the Title field is empty, focused, and its counter reads "0 / 200"
|
|
And the First paragraph field is empty
|
|
And "Create draft" is present but the form has not been submitted
|
|
|
|
Scenario: Validation — a title under 3 characters on blur
|
|
Given the modal is open
|
|
When the user types "Ti" into the Title field and moves focus away
|
|
Then the Title field shows the error "Use at least 3 characters." in place of its help text
|
|
And the counter reads "2 / 200" in its error style
|
|
And no request is sent
|
|
|
|
Scenario: Validation — an empty first paragraph on blur
|
|
Given the modal is open with a valid Title
|
|
When the user leaves the First paragraph field empty and moves focus away
|
|
Then the First paragraph field shows the error "Add a first paragraph. It can be a single
|
|
sentence."
|
|
And no request is sent
|
|
|
|
Scenario: Validation — submitting with both fields invalid shows both errors and sends nothing
|
|
Given the modal is open with Title "Ti" and an empty First paragraph
|
|
When the user activates "Create draft"
|
|
Then the Title field shows "Use at least 3 characters." and the First paragraph field shows
|
|
"Add a first paragraph. It can be a single sentence."
|
|
And POST /v1/article/ is never sent
|
|
|
|
Scenario: Submitting disables the form and cannot be dismissed
|
|
Given the modal is open with Title "Choosing tiles for a small bathroom" and First paragraph
|
|
"Small rooms show every grout line, so we start with the floor."
|
|
When the user activates "Create draft"
|
|
Then POST /v1/article/ is sent with JSON {"title": "Choosing tiles for a small bathroom",
|
|
"body": "Small rooms show every grout line, so we start with the floor."}
|
|
And both fields become disabled, "Cancel" becomes disabled, and the confirm button reads
|
|
"Creating…" with a loading indicator
|
|
And pressing Esc or clicking the backdrop does not close the modal
|
|
|
|
Scenario: A successful create closes the modal and hands off to the editor with a toast
|
|
Given the user is on /home with the modal open, Title "Choosing tiles for a small bathroom"
|
|
and First paragraph "Small rooms show every grout line, so we start with the floor.", and
|
|
"Create draft" has been activated
|
|
When POST /v1/article/ resolves 201 with the created article, id 42, is_online false
|
|
Then the modal closes
|
|
And the app navigates to /article/42/edit
|
|
And a toast appears: color success, icon i-lucide-circle-check, title "Draft created",
|
|
description "\"Choosing tiles for a small bathroom\" is saved as a draft.", id
|
|
"article-created-42"
|
|
|
|
Scenario: Title rejected — a 400 that got past this form's own check
|
|
Given the modal is open with Title "Choosing tiles for a small bathroom" (already valid by
|
|
this form's own rule) and First paragraph "Small rooms show every grout line, so we start
|
|
with the floor.", and "Create draft" has been activated
|
|
When POST /v1/article/ resolves 400
|
|
Then the modal re-enables, keeping both field values
|
|
And the Title field shows "Check this title and try again." in place of its help text
|
|
And an alert reads "Something in the form isn't valid. Check the highlighted fields."
|
|
And the First paragraph field shows no error
|
|
|
|
Scenario Outline: Error — a 500 or a network failure keeps the text and offers to retry
|
|
Given the modal is open with Title "Choosing tiles for a small bathroom" and First paragraph
|
|
"Small rooms show every grout line, so we start with the floor.", and "Create draft" has
|
|
been activated
|
|
When POST /v1/article/ <failure>
|
|
Then the modal re-enables, keeping both field values
|
|
And an alert reads "Couldn't create the article. Your text is still here. Try again in a
|
|
moment."
|
|
And neither field shows a field-level error
|
|
|
|
Examples:
|
|
| failure |
|
|
| resolves with a 500 response |
|
|
| never reaches the server |
|
|
|
|
Scenario: Retrying after an Error succeeds
|
|
Given the modal is in the Error state after a 500, with Title "Choosing tiles for a small
|
|
bathroom" and First paragraph "Small rooms show every grout line, so we start with the
|
|
floor." still in the fields
|
|
When the user activates "Create draft" again
|
|
Then POST /v1/article/ is sent again with the same JSON body
|
|
When it resolves 201 with the created article, id 42
|
|
Then the modal closes, the app navigates to /article/42/edit, and a toast appears: color
|
|
success, icon i-lucide-circle-check, title "Draft created", description "\"Choosing tiles
|
|
for a small bathroom\" is saved as a draft.", id "article-created-42"
|
|
|
|
Scenario: Cancel discards the fields with no confirmation
|
|
Given the modal is open with Title "Choosing tiles for a small bathroom" and First paragraph
|
|
"Small rooms show every grout line, so we start with the floor."
|
|
When the user activates "Cancel"
|
|
Then the modal closes immediately, with no confirmation prompt
|
|
And no request is sent
|