test(app-shell): value-shape parity net for action params (#2714)#2720
Merged
Conversation
Since ADR-0059 ActionParamDialog routes every param through the shared form field-widget renderer, so each param emits its widget's own value shape on confirm and those shapes differ per type. Nothing pinned that contract, so a future widget swap could silently change a param's emitted shape and break an endpoint's input contract with no failing test. Add the value-shape parity net: - `paramValueShape.ts` — the emitted-shape contract (`PARAM_VALUE_SHAPES`, one entry per FORM_FIELD_TYPES widget key) plus `expectedParamShape()`, which reuses the SAME `paramToField()` the dialog renders with (so it can't drift from the dialog's own type resolution) and folds in `multiple` and the file/image fileId serialization. `classifyValueShape()` backs the render proofs. - `paramValueShape.test.ts` — the drift guard (mirrors paramToField.test.ts's style): coverage of every FORM_FIELD_TYPES key, and the endpoint contracts pinned by value (number→number, boolean→boolean, date→string, select→string / select+multiple→string[], lookup→id(s), file→fileId(s)). - ActionParamDialog.test.tsx — contract-tied render proofs that drive the REAL widget (number/datetime/time/select+multiple/file) and assert the value the dialog resolves classifies to the declared contract, so a silent onChange swap fails at the render layer too, not just in the table. - Document the emitted-shape table in ADR-0059 and the app-shell README. The file/image → fileId serialization itself is owned by #2698/#2710 (`serializeParamValues`); this net asserts the post-serialize endpoint shape and the general contract for the non-upload types. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2714.
Why
Since ADR-0059 (#2700 / #2704),
ActionParamDialogroutes every param through the shared form field-widget renderer (paramToField()→getLazyFieldWidget). That was the whole win — but it also means each param now emits its widget's own value shape on confirm, and those shapes differ (number→number,boolean→boolean,date→string,select→string /select+multiple→string[],lookup/user→id(s),file/image→fileId(s)). #2700's acceptance called for value-shape parity coverage, but no test net pinned it — so a future widget swap could silently change a param's emitted shape and break an endpoint's input contract with no failing test.What
The contract (
packages/app-shell/src/utils/paramValueShape.ts)PARAM_VALUE_SHAPES— one entry perFORM_FIELD_TYPESwidget key, describing the shape the dialog POSTs (i.e. afterserializeParamValues, sofile/imageare already their fileId string).expectedParamShape(param)— resolves a concreteActionParamDefto its emitted shape by reusing the sameparamToField()the dialog renders with, so the contract can't drift from the dialog's own type resolution; it then folds inmultipleand the fileId serialization.classifyValueShape(value)— runtime classifier backing the render proofs.The net (
paramValueShape.test.ts, mirroring theparamToFielddrift test)FORM_FIELD_TYPESkey has a declared shape — a new widget can't land without stating what a param of that type POSTs.number→number,boolean→boolean,date/datetime/time→string,select→string,select+multiple→string[](feat(fields): select+multiple → multi-value chip picker; restore fields/core lint gates #2709),lookup/user→id(s),file→fileId(s),multiselect/tags→string[],object/address→object,grid→object[], computed types (formula/summary/auto_number/vector)→none.Reality tie (
ActionParamDialog.test.tsx)onChangeswap fails at the render layer too, not just in the table.Docs
file→descriptor line to the post-feat(approvals-inbox): retire the approve/reject composer for declared actions with file attachments (#2698) #2710 fileId contract) and referenced from the app-shell README.Out of scope (owned elsewhere)
The
file/image→ fileId serialization itself is #2698 / #2710 (serializeParamValues). This net adopts that (asserts the post-serialize endpoint shape) rather than duplicating it.Test / verify
paramValueShape.test.ts— 54 pass (unitproject, node env).ActionParamDialog.test.tsx— 28 pass (domproject), incl. the 5 new contract-tied proofs.paramToField.test.ts— still green.turbo run build --filter=@object-ui/app-shell— 29/29 successful (app-shelltscexecuted clean); eslint clean on new lines.No changeset: internal test-net + contract module (not exported from the package's public
index.ts) + docs; no runtime or public-API change.🤖 Generated with Claude Code