Skip to content

Expand recipe form with metadata fields and wire up submit - #1201

Merged
dgee2 merged 4 commits into
mainfrom
issue-1119-recipe-form-metadata
Aug 4, 2026
Merged

Expand recipe form with metadata fields and wire up submit#1201
dgee2 merged 4 commits into
mainfrom
issue-1119-recipe-form-metadata

Conversation

@dgee2

@dgee2 dgee2 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

The recipe creation form (new-recipe-form.vue) previously rendered only a name field with no submit handling at all. This PR:

  • Adds Summary, Yield, Servings, Prep/Cook/Total time fields to the form.
  • Requires a non-blank recipe title before submit is allowed.
  • Validates the numeric time/servings fields as non-negative whole numbers.
  • Wires the form up to the existing useCreateRecipe() mutation and navigates to the new recipe's detail page (added in Add authenticated recipe detail page #1118) on success.
  • Surfaces mutation failures as an inline banner instead of letting them become an unhandled promise rejection.
  • Adds Storybook stories and co-located *.test.ts unit 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 UpsertRecipeValidator from #1116) — row editors for those come in #1120/#1121. accessScope is 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 unit Vitest project (jsdom + @vue/test-utils) — the project already existed in vitest.config.ts but was unnamed and had no test files, so it is now named and reachable via pnpm test:unit. They mock @/services/recipe-api rather than the service layer, so the real useCreateRecipe()/TanStack Query wiring behind isPending and isError is 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/recipe are not currently served under vitest --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-ok is served correctly with its body. The real gap is narrower — handlers on the exact path */api/recipe are 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 lint
  • pnpm 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 form
  • pnpm test:unit — 4 files / 45 tests passing
  • Manually verified in the Storybook dev server: empty-title blocks submit with a visible error, a valid title clears validation and triggers the mutation, and a failed mutation shows the error banner without crashing

Part of #1100.

Closes #1119.

@dgee2 dgee2 changed the title issue 1119 recipe form metadata Expand recipe form with metadata fields and wire up submit Aug 2, 2026
@dgee2
dgee2 force-pushed the issue-1119-recipe-form-metadata branch from f903273 to d353eb2 Compare August 3, 2026 08:16
@dgee2
dgee2 force-pushed the issue-1119-recipe-form-metadata branch from d353eb2 to 34fe6f2 Compare August 3, 2026 20:09
@dgee2
dgee2 force-pushed the issue-1119-recipe-form-metadata branch from 34fe6f2 to 0d8d02e Compare August 4, 2026 07:05
Base automatically changed from issue-1118-recipe-detail-page to main August 4, 2026 07:26
@dgee2
dgee2 force-pushed the issue-1119-recipe-form-metadata branch from 0d8d02e to 372e73d Compare August 4, 2026 07:48
dgee2 added 3 commits August 4, 2026 08:52
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.
@dgee2
dgee2 force-pushed the issue-1119-recipe-form-metadata branch from 372e73d to 844b3a6 Compare August 4, 2026 07:59
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>
@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

@dgee2
dgee2 marked this pull request as ready for review August 4, 2026 18:35
Copilot AI lite review requested due to automatic review settings August 4, 2026 18:35
@dgee2

dgee2 commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

🤖 This comment was written by Claude.

Pushed edcc81e, adding the missing test coverage for the four Vue components this PR creates or changes.

Stories

Component Added
text-field.vue textarea rendering, rule accept/reject — the new type and rules props had no story coverage
number-field.vue min/step attributes, cleared-to-null, negative/fractional/valid rules
recipe-name-field.vue empty, whitespace-only and non-blank names — the new required rule had no story coverage
new-recipe-form.vue a fully populated form that clears validation

Unit tests

45 tests across four co-located *.test.ts files. There were no *.test.ts files anywhere in the repo, so this also names the previously-unnamed jsdom Vitest project unit and adds a pnpm test:unit script. The convention is documented in AGENTS.md.

The new-recipe-form tests mock @/services/recipe-api rather than recipe-service, so the real useCreateRecipe()/TanStack Query wiring behind isPending and isError still runs. They assert the exact submitted payload (including accessScope: "Private" and the empty ingredients/steps), the numeric rules, navigation to /recipe/1 on success, the error banner on failure, and the loading state.

Full suite is 24 files / 101 tests, up from 20 / 44. pnpm lint, pnpm type-check and pnpm build all pass.

One correction

The NOTE on SubmitFailureShowsError said MSW isn't intercepting POST requests under vitest --project=storybook. I probed that rather than taking it as given, and it isn't the case — a POST to a sub-path such as */api/recipe/probe-ok is served correctly, body included.

The actual gap is narrower: handlers registered on the exact path */api/recipe are not served, so the request fails at the network layer instead. That is what drives this story down its failure path, meaning it passes but not for the reason it names. The same gap affects the RecipeList and RecipeDetail stories from earlier PRs.

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.

@dgee2
dgee2 merged commit f1d6da1 into main Aug 4, 2026
14 checks passed
@dgee2
dgee2 deleted the issue-1119-recipe-form-metadata branch August 4, 2026 18:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.vue with Summary/Yield/Servings and Prep/Cook/Total time fields plus inline error handling.
  • Added/extended reusable form field atoms (text-field, new number-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 ?? '',
dgee2 added a commit that referenced this pull request Aug 4, 2026
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>
dgee2 added a commit that referenced this pull request Aug 4, 2026
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>
dgee2 added a commit that referenced this pull request Aug 4, 2026
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>
dgee2 added a commit that referenced this pull request Aug 4, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expand recipe form with metadata fields

2 participants