Expand recipe form with metadata fields and wire up submit - #1201
Conversation
f903273 to
d353eb2
Compare
d353eb2 to
34fe6f2
Compare
34fe6f2 to
0d8d02e
Compare
0d8d02e to
372e73d
Compare
Introduces a reusable numeric input atom (number-field.vue) for the recipe form's metadata fields, and extends text-field.vue with optional `rules`/`type` props so form fields can opt into Quasar validation and textarea rendering.
The recipe name field previously accepted empty submissions; the create/update form now blocks submit on an empty or whitespace-only title.
new-recipe-form.vue previously rendered only a name field with no submit handling. It now exposes the recipe's metadata fields (summary, yield, servings, prep/cook/total time), validates numeric fields as non-negative whole numbers, and submits via the existing useCreateRecipe() mutation, navigating to the new recipe's detail page on success. Mutation failures are caught and surfaced as an inline banner instead of becoming an unhandled promise rejection. Ingredients/steps are submitted as empty arrays for now; editors for those are added in later PRs in this stack (#1120, #1121), as is the visibility selector (#1122) — accessScope is hardcoded to "Private" here in the interim. Part of #1100. Closes #1119.
372e73d to
844b3a6
Compare
PR #1201 added number-field.vue and changed text-field.vue, recipe-name-field.vue and new-recipe-form.vue. This adds the missing test coverage for all four. Stories (co-located, all four components): - text-field: textarea rendering, rule accept/reject - number-field: min/step, cleared-to-null, negative/fractional/valid rules - recipe-name-field: empty, whitespace-only and non-blank names - new-recipe-form: a fully populated form that clears validation Unit tests (new `unit` Vitest project, jsdom + @vue/test-utils): 45 tests covering the number coercion in number-field, the required rule on recipe-name-field, and — for new-recipe-form — the submitted payload, the numeric validation rules, navigation to the new recipe on success, the error banner on failure, and the pending state. These mock @/services/recipe-api rather than the service layer, so the real useCreateRecipe()/TanStack Query wiring behind isPending and isError is still exercised. Also corrects the NOTE on SubmitFailureShowsError. MSW does intercept POST requests under `vitest --project=storybook`; a POST to a sub-path such as `*/api/recipe/probe-ok` is served correctly with its body. The actual gap is that handlers registered for `*/api/recipe` are not served, so the request fails at the network layer instead — which is what drives that story down its failure path. The same gap affects the RecipeList and RecipeDetail stories. Response-specific behaviour is now asserted in the unit tests rather than through MSW. Co-authored-by: Claude <noreply@anthropic.com>
|
Pushed Stories
Unit tests45 tests across four co-located The Full suite is 24 files / 101 tests, up from 20 / 44. One correctionThe NOTE on The actual gap is narrower: handlers registered on the exact path I've left that story in place with an accurate NOTE and moved the response-specific assertions into the unit tests, where they're deterministic. Root cause and fix are tracked in #1208; the stories that assert non-behaviour as a result are tracked in #1209, blocked by it. Neither is in scope here. |
There was a problem hiding this comment.
Pull request overview
This PR expands the frontend recipe creation form to capture additional metadata fields, adds client-side validation, and wires submission into the existing create-recipe mutation with navigation on success.
Changes:
- Expanded
new-recipe-form.vuewith Summary/Yield/Servings and Prep/Cook/Total time fields plus inline error handling. - Added/extended reusable form field atoms (
text-field, newnumber-field) and strengthened recipe title validation. - Added unit tests + Storybook interaction coverage; updated Vitest projects/scripts and ESLint test globs.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| ui/menu-website/vitest.config.ts | Names the unit Vitest project to support --project=unit execution. |
| ui/menu-website/src/components/organisms/recipe/new-recipe-form.vue | Adds metadata fields, submit handling via useCreateRecipe(), and error banner UI. |
| ui/menu-website/src/components/organisms/recipe/new-recipe-form.test.ts | Unit coverage for validation, payload shape, navigation on success, and error banner behavior. |
| ui/menu-website/src/components/organisms/recipe/new-recipe-form.stories.ts | Storybook interaction coverage for validation and submit success/failure UX. |
| ui/menu-website/src/components/molecules/recipe/fields/recipe-name-field.vue | Switches model to nullable and adds trimmed required validation rules. |
| ui/menu-website/src/components/molecules/recipe/fields/recipe-name-field.test.ts | Unit tests for recipe name validation and v-model behavior. |
| ui/menu-website/src/components/molecules/recipe/fields/recipe-name-field.stories.ts | Storybook coverage for valid/invalid name validation states. |
| ui/menu-website/src/components/atoms/form/text-field.vue | Extends text field to support textarea + Quasar rules and nullable model. |
| ui/menu-website/src/components/atoms/form/text-field.test.ts | Unit tests for textarea mode, rule validation, and nullable model behavior. |
| ui/menu-website/src/components/atoms/form/text-field.stories.ts | Storybook coverage for textarea and rule acceptance/rejection states. |
| ui/menu-website/src/components/atoms/form/number-field.vue | Introduces a numeric field wrapper that parses input to `number |
| ui/menu-website/src/components/atoms/form/number-field.test.ts | Unit tests for parsing behavior, forwarding min/step, and rule validation. |
| ui/menu-website/src/components/atoms/form/number-field.stories.ts | Storybook coverage for numeric entry, clearing to null, and rule validation. |
| ui/menu-website/package.json | Adds test:unit script to run the unit project directly. |
| ui/menu-website/eslint.config.ts | Expands Vitest lint globs to include src/**/*.test.ts. |
| ui/menu-website/.storybook/msw-handlers.ts | Adds MSW handlers for recipe-create success and error scenarios. |
| AGENTS.md | Updates frontend command docs and adds component test coverage guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| const onSubmit = async () => { | ||
| const recipe: UpsertRecipe = { | ||
| title: title.value ?? '', |
new-recipe-form.test.ts landed on main via #1201, after this branch was cut, and asserts the exact list of rendered field labels. The visibility selector adds a "Visibility" field between Name and Summary, so the expected list needs it too. Co-authored-by: Claude <noreply@anthropic.com>
new-recipe-form.test.ts landed on main via #1201, after this branch was cut, and asserts the exact list of rendered field labels. The visibility selector adds a "Visibility" field between Name and Summary, so the expected list needs it too. Co-authored-by: Claude <noreply@anthropic.com>
new-recipe-form.test.ts landed on main via #1201, after this branch was cut, and asserts the exact list of rendered field labels. The visibility selector adds a "Visibility" field between Name and Summary, so the expected list needs it too. Co-authored-by: Claude <noreply@anthropic.com>
* Add visibility selector to recipe form (#1122) Adds a RecipeVisibilityField molecule wrapping the existing select-field atom, translating between UpsertRecipe.accessScope's raw string values ("Private" / "AuthenticatedUsers") and the underlying QSelect's {label, value} option shape. Wired into new-recipe-form.vue, replacing the hardcoded "Private" literal in the submit payload; defaults to Private for new recipes. Part of #1100. Closes #1122. * Include Visibility in the metadata field assertion new-recipe-form.test.ts landed on main via #1201, after this branch was cut, and asserts the exact list of rendered field labels. The visibility selector adds a "Visibility" field between Name and Summary, so the expected list needs it too. Co-authored-by: Claude <noreply@anthropic.com> * Address review feedback on the recipe visibility selector - Add the co-located unit test the component conventions require, covering the default, the label/scope translation in both directions, and the empty states. - Assert in new-recipe-form.test.ts that a non-default selection actually reaches the submitted payload, so the wiring is verified rather than assumed. - Narrow accessScope to a RecipeAccessScope union in recipe-api.ts (the generated type is a bare string) and use it in the field and the form. - Move the Private default to the owning form. A defineModel default never writes back to the parent, so a parent binding an unset ref would show "Private" while submitting undefined. - Render an unrecognised or absent scope as empty instead of silently falling back to Private, which would misreport the value the form is about to submit. - Drop the `as const` so the options array can be passed without a per-render spread, and add a hint to match the sibling fields. Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>



Summary
The recipe creation form (
new-recipe-form.vue) previously rendered only a name field with no submit handling at all. This PR:useCreateRecipe()mutation and navigates to the new recipe's detail page (added in Add authenticated recipe detail page #1118) on success.*.test.tsunit tests for every component this PR creates or changes (see below).Ingredients/steps are submitted as empty arrays for now (validated as acceptable by the backend's
UpsertRecipeValidatorfrom #1116) — row editors for those come in #1120/#1121.accessScopeis hardcoded to"Private"until the visibility selector lands in #1122.Test coverage
All four touched components (
number-field.vue,text-field.vue,recipe-name-field.vue,new-recipe-form.vue) now have both a co-located story and a co-located unit test.The unit tests run in a new
unitVitest project (jsdom +@vue/test-utils) — the project already existed invitest.config.tsbut was unnamed and had no test files, so it is now named and reachable viapnpm test:unit. They mock@/services/recipe-apirather than the service layer, so the realuseCreateRecipe()/TanStack Query wiring behindisPendingandisErroris still exercised. That covers the submitted payload, the numeric validation rules, navigation to the new recipe on success, the error banner on failure, and the pending state.The split between stories and unit tests is deliberate and documented in
AGENTS.md: stories cover rendering, props and validation messages; unit tests cover request payloads, success/error branches and routing. This is because MSW handlers registered for*/api/recipeare not currently served undervitest --project=storybook— tracked in #1208, with the stories that assert non-behaviour as a result tracked in #1209.That also corrects the NOTE on
SubmitFailureShowsError. MSW does intercept POST requests here; a POST to a sub-path such as*/api/recipe/probe-okis served correctly with its body. The real gap is narrower — handlers on the exact path*/api/recipeare not served, so the request fails at the network layer, and that is what drives the story down its failure path.Test plan
pnpm build(type-check + production build)pnpm lintpnpm test— 24 files / 101 tests passing (45 unit, 56 Storybook)pnpm test:storybook— 20 files / 56 tests passing, including new stories for empty-title validation, valid-title submit, negative/non-integer servings validation, submit-failure error banner, textarea rendering and rule accept/reject on the field atoms, and a fully populated formpnpm test:unit— 4 files / 45 tests passingPart of #1100.
Closes #1119.