test(superdoc): pin runtime event payload shapes (SD-673)#3526
Conversation
Add key-set assertions for the six public events whose Config callbacks have named payload types (introduced in #3503). Existing tests used objectContaining({...}) which would not catch a missing or extra field; these new assertions pin Object.keys(payload).sort() to the exact declared shape. This protects the bug class found and fixed in #3503: typed callback payloads silently drifted from the runtime emit (onLocked, onAwarenessUpdate, onCommentsUpdate had wrong shapes for months). Events covered: - ready -> { superdoc } (SuperDoc.test.js) - editorBeforeCreate -> { editor } (SuperDoc.test.js) - editorCreate -> { editor } (SuperDoc.test.js) - locked -> { isLocked, lockedBy } (SuperDoc.test.js) - awareness-update -> { states, added, removed, superdoc} (collaboration.test.js) - comments-update -> { type, comment } (comments-store.test.js) Out of scope for this PR (pass-through events from upstream emitters, runtime shape is already enforced by SuperDocEventMap in the consumer- typecheck fixtures): - collaboration-ready (emitted from SuperDoc.vue verbatim) - list-definitions-change (emitted from super-editor, re-emitted by SuperDoc.vue verbatim) No new gate, no new scanner, no production code touched. Verified: - vitest run on the three touched files: 250/250 pass - pnpm check:public:superdoc --skip-build -> PASS (11/12, 133.7s)
…ts shape (SD-673)
Two follow-up coverage additions to the runtime payload tests:
1. SuperDoc.vue's list-definitions-change bridge. The vue layer's
onEditorListdefinitionsChange is a verbatim pass-through, but nothing
in the SuperDoc-side suite pinned it. Adds an assertion to the existing
'wires editor lifecycle events' test that calls options.
onListDefinitionsChange(payload) and verifies superdoc.emit was called
with ('list-definitions-change', payload). Catches a regression where
the bridge starts re-shaping or dropping fields. Upstream emission of
ListDefinitionsPayload is owned by super-editor and tested there.
2. The DELETED variant of SuperDocCommentsUpdatePayload. The first PR
only pinned the UPDATE variant ({ comment, type }). The DELETED path
(pruneStaleTrackedChangeComments -> emit) produces the three-key
shape { changes, comment, type }. Extends the existing 'emits deleted
events when replay sync prunes' test to assert the exact key set, so
a regression that dropped 'changes' would fail.
No production code touched.
Verified:
- vitest run on all four touched test files: 317/317 pass
…673) Inspect the emitted payload directly instead of using toHaveBeenCalledWith(name, listDefsPayload). That call's deep-equal compares the actual call argument against the expected reference; if the bridge mutated the payload in place before emit (e.g. delete params.editor), both sides would point at the same now-mutated object and the assertion would pass trivially. Now pins two distinct properties: - emittedListDefsPayload toBe listDefsPayload (verbatim pass-through) - Object.keys(emittedListDefsPayload).sort() (no in-place key drop/add) A regression that clones the payload (breaks reference equality) or mutates a field out of it (breaks key set) now fails. Verified: - vitest run on SuperDoc.test.js: the lifecycle test still passes
There was a problem hiding this comment.
cubic analysis
No issues found across 4 files
Linked issue analysis
Linked issue: SD-673: TypeScript-first public API: SuperDoc and Document API
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Emit `ready` with payload keys ['superdoc'] and value is the SuperDoc instance | Test added that listens for 'ready', asserts Object.keys(payload).sort() === ['superdoc'], and that payload.superdoc === instance. |
| ✅ | Emit `editorBeforeCreate` with payload keys ['editor'] and editor identity-by-property | Test added that listens for 'editorBeforeCreate', asserts keys === ['editor'], and that payload.editor.id matches the provided editor id. |
| ✅ | Emit `editorCreate` with payload keys ['editor'] and editor identity-by-property | Test added that listens for 'editorCreate', asserts keys === ['editor'], and checks payload.editor.id. |
| ✅ | Emit `locked` with payload keys ['isLocked','lockedBy'] and lockedBy is User | null (correct values for lock/unlock) | Test added that listens for 'locked', asserts keys === ['isLocked','lockedBy'], and verifies emitted payload values for both lock (user object) and unlock (null). |
| ✅ | Emit `awareness-update` with payload keys ['added','removed','states','superdoc'] and expected shapes | Test added in collaboration tests asserts event name and that payload Object.keys matches the four keys, and checks states/added/removed/superdoc values. |
| ✅ | Emit `comments-update` (UPDATE) with payload keys ['comment','type'] and type is string | Test additions inspect the emitted comments-update call and assert keys === ['comment','type'] and typeof type === 'string'. |
| ✅ | Emit `comments-update` (DELETED) with payload keys ['changes','comment','type'] and changes is an array containing deleted marker | Test finds the emitted comments-update with type DELETED and asserts keys === ['changes','comment','type'], changes is array, and first change matches { key: 'deleted' }. |
| ✅ | Bridge `list-definitions-change` emits payload keys ['change','editor','numbering'] and forwards the same reference | Test added in SuperDoc.vue test triggers onListDefinitionsChange with a payload, finds the emitted call, asserts emitted payload is the same reference (.toBe) and Object.keys(...) === ['change','editor','numbering']. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f71ea6497a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…ge bridge (SD-673) Per Codex review on PR #3526: the previous synthetic { change, numbering, editor } payload didn't match any production emit site. The only producers, in super-editor's numbering-part- descriptor, emit { editor, numbering } (no 'change' field). ListDefinitionsPayload marks all three fields optional, so the synthetic test passed trivially without pinning the real shape. Now pins the current production numbering variant: - input payload is { editor, numbering } (matches numbering-part- descriptor.ts:222,242 verbatim) - Object.keys(emittedListDefsPayload).sort() === ['editor', 'numbering'] - reference equality still asserts verbatim pass-through The comment block is explicit that this pins the production numbering variant + bridge pass-through, not every possible ListDefinitionsPayload shape. Verified: - vitest run on SuperDoc.test.js lifecycle test still passes
### Bug Fixes - copy-pasted text in suggestion mode (#3576) - center virtualized matches after mount in find nav (SD-3315) - stop find navigation jumping to the reverted caret (SD-3315) - honor the focus() contract + fix dangling docs reference (SD-3312) - don't re-center visible matches on find navigation (SD-3315) - report 'zoom' not 'mixed' for a zoom repaint (SD-3311) - wire pointer-source tracking on all init paths; update export snapshot - reset state on unload, dedupe + export payload types, add core tests - skip empty block SDT content selection - type modules.contentControls exactly (no pass-through index) - allow block SDT wrapper deletion to follow lock rules - promote image-bearing inline SDT wrappers to inline-block - keep block sdt fill behind content - hide block sdt fills in output modes - paint block SDT background on chrome layer - use logical inset for inline SDT label position - anchor inline SDT label to start of chrome - route smartTag through export and preserve smartTagPr in SDT flatten (SD-2647) - include inline SDT chrome width in block SDT bounds - keep block SDT chrome at full fragment width - render and round-trip w:smartTag content (SD-2647) - decouple base64 image helper imports - preserve block ids during metadata updates - ignore covered sdt label clicks - sync block sdt label selection updates - show empty SDT placeholder text in viewing and print modes - skip empty sdt scan on arrow right - preserve permission-only sdt placeholders - preserve comment-only sdt placeholders - preserve empty sdt bookmark placeholders - collapse hidden sdt placeholder text - keep sdt placeholder pm range atomic - trust empty sdt paragraph conversion - keep vanished sdt paragraph side effects - preserve vanished block sdt paragraphs - suppress hidden block sdt chrome - keep remeasured sdt placeholder atomic - transform sdt placeholder measure - remeasure sdt placeholders - hide sdt placeholders in print - hide sdt placeholders in viewing - ignore collapsed inline sdt cut - expose block sdt appearance - hide empty block sdt placeholder - align empty block sdt caret - use measured width for empty SDT placeholders - size SDT block labels to content width - collapse selection on sdtContentLocked delete - allow history transactions through sdt lock - drop unreachable move fallback - target marker-only textblock end - ignore empty block sdt key targets - cap block sdt label width - handle sdt marker gaps and block atoms - skip hidden field annotations in sdt navigation - skip hidden metadata sdt markers - skip hidden block sdt markers - handle marker-only sdt paragraphs - keep visible atoms in sdt navigation - skip hidden sdt navigation markers - target nearest sdt cursor position - respect inline atoms in sdt navigation - handle empty block sdt navigation - avoid restoring dragged block to its source position - exclude sdt chrome labels from caret position lookup - keep text-align enabled in locked SDT paragraphs - block disabled toolbar execution - guard unlisted locked toolbar commands - block locked sdt toolbar execution - reject malformed base64 image data URIs - reuse colliding data uri media targets - enforce upload byte cap for data uris - avoid non-image data uri extensions - reject raw raster data uri dimensions - validate oversized async svg images - register preset raster data uris in place - narrow sdt metadata overrides - normalize image data uri extensions - centralize image data url policy - reuse target image relationships - validate in-place svg payloads - validate field annotation data uri exports - reject malformed data uri files - avoid duplicate image rids - block raw raster data uri exports - reject malformed svg data uri payloads - warn on skipped image exports - reject separatorless data uri files - block non-image data uri exports - read svg data uri dimensions - share data uri media parsing - skip invalid data uri image targets - normalize svg data uri filenames - validate in-place svg image data - guard non-base64 data uri exports - export field annotation svgs as svg - allow non-base64 SVG data URLs in image rendering - mirror in-place image media to parent - decode non-base64 data URI exports - extract shared hash helpers - reuse data URI image exports - support non-base64 data URI images in registration - scope inline SDT placeholder to structuredContent metadata - register sized SVG data URI images without canvas processing - persist data URI images set via setPresetContent - share SDT lock predicates - version inline image metadata - align RTL SDT chrome to text - size SDT chrome for justified lines - honor ancestor image SDT locks - dirty inline image SDT changes - align SDT chrome within paragraph width - preserve SDT chrome continuation offsets - offset block SDT chrome for indents - suppress SDT pseudo hover in viewing mode - allow top-aligned inline images - fit block SDT chrome to actual content width - bottom-align text on lines with inline images - keep block SDT chrome and inline images out of paragraph geometry - disable image resize inside content-locked SDTs - detect inline image run changes in paragraph diff - mark block SDT selected when contained image is selected - select inline SDT content as text on Delete - intercept beforeinput insertText at inline SDT boundaries - delete contentLocked SDT wrapper in one step - bump sdBlockRev on ancestors of inline edits - select inline SDT content as text on Backspace - select inline SDT on Backspace at start of following run (SD-3165) - share structured content chrome label set - resolve block labels at node boundary - use contract label selectors - clear label gesture state on cancel - scope label clicks to owning editor - defer label selection to mouseup so native drag still fires - share structured content label classes - avoid deferred block label retry - select labels with active editor - focus editor after label selection - correct cursor placement and label interactions for structured content - handle cell-level SDT in vMerge column lookup (SD-3289) - preserve cell-level SDT wrapping table cells (SD-3289) - preserve recipient identity attrs on replay (SD-3279) - accept legacy fingerprints in compare/apply (SD-3279) - drop misleading pnpm run type-check hint from audit - structural-fail on missing dist + sync check-jsdoc header - strip session-local sdBlockId from diff fingerprint (SD-3279) - autoFit table width overflow from cell preferences (#3522) - flip public-method-coverage to strict-zero gate (SD-673) - flip jsdoc-hygiene-ts to strict-zero gate (SD-673) - rename jsdoc-hygiene-ts self-tests + wire into CI (fixes vitest discovery on #3511) - jsdoc-hygiene-ts handles private-identifier symbols + README - more bugs - add ui for overlapping delete, other fixes - replacement pair - remaining collab bugs - coalesce tracked inserts across run gaps - restore tracked change comment interactions - expose tracked mark predicate option - more cases - tc fixes - collab mode bug - floating comments fixes - make jsdoc-hygiene-ts baseline key line-independent + update wrapper docs - preserve tab underline via runProperties fallback in collab ### Changes - Merge branch 'main' into caio/sd-3315-find-replace-scroll - Merge pull request #3509 from superdoc-dev/artem/SD-3232 - Merge pull request #3555 from superdoc-dev/luccas/delete-image-content-locked-sdt - Merge branch 'main' into artem/SD-3159 - Merge pull request #3550 from superdoc-dev/luccas/sd-3302-bug-sdt-in-template-builder-shows-grey-background-highlight - Merge branch 'main' into artem/SD-3159 - SD-2676 - fix: table selection not providing a feedback (#3508) - Merge pull request #3549 from superdoc-dev/luccas/left-align-inline-sdt-label - Merge pull request #3546 from superdoc-dev/caio/sd-2647-bug-render-and-round-trip-content-wrapped-in-wsmarttag - Merge remote-tracking branch 'origin/stable' into sync/stable-to-main-20260527-230540 - Merge pull request #3539 from superdoc-dev/caio-pizzol/sd-3289-preserve-cell-level-sdt - Merge pull request #3527 from superdoc-dev/caio-pizzol/sd-3279-strip-sdblockid-from-diff-fingerprint - Merge branch 'main' into caio-pizzol/SD-js-contract-owner-audit - Merge pull request #3531 from superdoc-dev/caio-pizzol/SD-docs-snippet-typecheck-2 - Merge pull request #3526 from superdoc-dev/caio-pizzol/SD-runtime-payload-tests - Merge pull request #3521 from superdoc-dev/caio-pizzol/SD-public-method-coverage-strict-zero - Merge branch 'stable' - Merge pull request #3485 from superdoc-dev/artem/SD-3200 - Merge pull request #3517 from superdoc-dev/caio-pizzol/SD-jsdoc-hygiene-zero-flip - Merge pull request #3513 from superdoc-dev/caio-pizzol/SD-jsdoc-hygiene-cleanup - Merge branch 'stable' - Merge pull request #3511 from superdoc-dev/caio-pizzol/SD-jsdoc-hygiene-scanner - Merge pull request #3435 from superdoc-dev/nick/sd-3220-overlapping-suggestion-contract - Merge pull request #3429 from superdoc-dev/artem/underlined-tab-collab ### Documentation - note content controls in entityAt hit types (SD-3313) - clarify data uri buffer conversion - document image data uri helpers - clarify image registration comments - sync README exit semantics for js contract-owner audit - clarify wrapper-stages prose + add ts-jsdoc to check:public summary ### Features - add ui.contentControls.focus to place the caret in a control (SD-3312) - add ui.viewport.observe geometry-invalidation signal (SD-3311) - add ui.contentControls.scrollIntoView (SD-3310) - add activePath (full active stack) to content-control:active-change - expose public sdt events - select adjacent block SDT content at textblock boundaries - inherit run styles in empty block SDT placeholders - render placeholder text for empty SDTs - move caret into following block sdt on delete - move caret into preceding block sdt on backspace - disable mutation toolbar controls inside content-locked SDTs - render empty inline SDTs as a visible placeholder - add modules.contentControls.chrome - js contract-owner audit, report-only (SD-673) - add snippet typecheck for editor/superdoc/** + fix stale examples (SD-673) - add non-hover field color for sdt (#3506) - comments and tc on small screen (#3446) - overlapping tracked changes - add type-bearing JSDoc hygiene gate for .ts source (SD-673) - add anchored metadata orphan status ### Tests - resolve handleBase64 source path from package or repo root - cover locked block SDT Delete selection - cover nested block SDT boundary selection - update SDT keymap chain coverage - cover contentControlsChrome plumbing; clarify chrome-none comment - assert chrome-none hover suppression and cascade order - add v2 bridge unit + round-trip behavior coverage (SD-2647) - assert smartTag child text survives export round-trip (SD-2647) - cover nested block sdt navigation - roundtrip mixed image block sdts - cover image data uri length boundary - repaint saved sdt images through painter - cover structured content image edges - cover inline image diff fields - cover locked inline SDT beforeinput - cover inline SDT Cmd+X selection - cover inline SDT content Delete flow - clarify inline SDT boundary lock comment - cover two-step inline SDT Backspace - cover inline SDT selection meta escape - use real production payload for list-definitions-change bridge (SD-673) - tighten list-definitions-change bridge assertion (SD-673) - pin list-definitions-change bridge and DELETED comments shape (SD-673) - pin runtime event payload shapes (SD-673) ### Refactoring - consolidate type imports in dom painter - share block sdt navigation helpers - share box model between block and inline sdt labels - share structured content predicates - wrap shared tryDecodeDataUriText re-export - reuse shared data uri export policy - share image relationship export lookup - centralize image data uri parsing - share data uri text decoding - trim data uri metadata fields - extract CHECKED_FILES to shared module + drop audit from wrapper - drop list-item fragment renderer (SD-2851) (#3269) - drain 85 type-bearing JSDoc entries from .ts source (SD-673) ### Performance - avoid scanning data uri media ### Chores - 1.37.0 [skip ci] - fix import breaking delinstrtext orphans (#3535) - fix tests (#3533) - fixes - more fixes - run generate:all and fixes - more fixes - soec fixes - type fixes - ui and more - fix regression - type fixes - more fixes - more fixes - ci fixes - type fixes - add dispatch test for collab bug - review fix, type fix - add tests for metadata issue - tests for review issues
|
🎉 This PR is included in superdoc-cli v0.15.0 The release is available on GitHub release |
|
🎉 This PR is included in superdoc-sdk v1.14.0 |
|
🎉 This PR is included in @superdoc-dev/mcp v0.10.0 The release is available on GitHub release |
|
🎉 This PR is included in superdoc v1.38.0 The release is available on GitHub release |
|
🎉 This PR is included in @superdoc-dev/react v1.9.0 The release is available on GitHub release |
|
🎉 This PR is included in vscode-ext v2.10.0 |
Pins the runtime emit shape for the public events whose Config callbacks have named payload types (the ones added in #3503). Tests-only PR. No production code, no new gate, no new scanner.
Why: the named payload types (
SuperDocReadyPayload,SuperDocEditorPayload,SuperDocLockedPayload,SuperDocAwarenessUpdatePayload,SuperDocCommentsUpdatePayload,ListDefinitionsPayload) declare what the runtime is supposed to emit. The consumer-typecheck fixtures cover the declared types. Nothing currently proves the runtime emits the same shape. That's the bug class fixed in #3503 itself -onLocked,onAwarenessUpdate,onCommentsUpdatehad wrong shapes for months because no test pinned the exact key set.Existing event tests use
objectContaining({...}), which does not fail on extra fields, and does not fail on missing fields the test never asserted. The new assertions useObject.keys(payload).sort()so a drift in either direction fails immediately.Coverage:
readycore/SuperDoc.test.js['superdoc']editorBeforeCreatecore/SuperDoc.test.js['editor']editorCreatecore/SuperDoc.test.js['editor']lockedcore/SuperDoc.test.js['isLocked', 'lockedBy'](withUserandnull)awareness-updatecore/collaboration/collaboration.test.js['added', 'removed', 'states', 'superdoc']comments-update(UPDATE)stores/comments-store.test.js['comment', 'type']comments-update(DELETED)stores/comments-store.test.js['changes', 'comment', 'type']list-definitions-changeSuperDoc.test.js(vue bridge)['editor', 'numbering']+ reference equality (current production numbering variant;ListDefinitionsPayloadhas all fields optional)Note on the bridges in
SuperDoc.vue:collaboration-readyis already pinned by the existingwires editor lifecycle eventstest (SuperDoc.test.js:545-546). The bridge deliberately narrows the upstream{ editor, ydoc }to{ editor }and the existingtoHaveBeenCalledWithassertion would fail if that narrowing regressed.list-definitions-changeis added in this PR via the same test path, using the actual production payload shape ({ editor, numbering }pernumbering-part-descriptor.ts:222,242). The assertion snapshots the emitted payload's key set independently of the input reference, so an in-place mutation by the bridge (e.g.delete params.editor) would also fail. This pins the current production numbering variant plus the SuperDoc.vue pass-through; it does not pin every possibleListDefinitionsPayloadshape (the type marks all fields optional, and a real editor integration test belongs in super-editor).Must stay the same:
pnpm check:public:superdoccontinues to pass.Review: check that the key-set assertions match the named type declarations in
packages/superdoc/src/core/types/index.ts(SuperDocReadyPayload, etc.). If any of those types add or remove a field, the matching test here should be updated in the same PR.Verified:
vitest runon the four touched files -> 317/317 passpnpm check:public:superdoc --skip-build-> PASS (11/12, 133.7s on first commit; CI will re-run on the merge ref)