Repository navigation
test: cover @muyajs/core migration regression gaps (Phase G) - #4434
Conversation
Fill the highest-value missing automated tests from the @muyajs/core migration regression audit — the recently-fixed bugs and data-loss-adjacent paths that had no engine coverage. - headingLocaleCrash.spec.ts (#4424/#4427 TOP priority): boots Muya under a non-en (zh-CN) locale and types `#` to create a heading, exercising the HeadingCopyLink `i18n.t` path that used to crash. Asserts no throw, the translated copy-anchor label resolves per-locale, the affordance still emits `heading-copy-link`, and `I18n.t` stays crash-safe (raw-key fall-through) for a key missing from a non-en locale. - formatToggle.spec.ts: the public `Format.format()` toggle-OFF and `clear` paths over a real boot (strong/em/del/u/mark/link/inline-code) — only the apply-side `_addFormat` was previously covered. - backspaceUnwrap.spec.ts (data-loss): Backspace-at-offset-0 cross-block surgery — paragraph merge + caret join point, and block-quote / list unwrap across all four branches (only/first/middle), asserting no item is dropped. - tableCell backspaceSafety.spec.ts (data-loss): empty-table Backspace → paragraph replacement, and previous-cell caret jump without destructive merge. - table insertRowColumn.spec.ts: insertRow/insertColumn shape, per-column alignment inheritance, delimiter round-trip, and returned caret cell — only the removal paths were previously unit-tested. All assert already-correct behavior (coverage gaps, not failing-first) and pass on current develop. Gates: lint, lint:types, check-circular, test, test:spec. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
There was a problem hiding this comment.
Pull request overview
This PR adds targeted Vitest coverage in packages/muya to close high-value regression gaps found during the @muyajs/core migration audit—especially data-loss-adjacent backspace/formatting paths and a previously fixed non-en locale heading crash (#4424/#4427). No production code changes are included.
Changes:
- Add regression spec booting Muya under
zh-CNand exercising the real#→ heading conversion path (incl.HeadingCopyLinki18n + event emission). - Add engine-boot tests covering inline format toggle-OFF +
clearover real selections. - Add table/paragraph backspace and table insert row/column behavior tests to guard document-shape, caret placement, and alignment preservation.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/muya/src/block/gfm/table/tests/insertRowColumn.spec.ts | Adds coverage for Table.insertRow / insertColumn shape + alignment + caret cell return. |
| packages/muya/src/block/content/tableCell/tests/backspaceSafety.spec.ts | Guards table-cell Backspace branches (empty table replace + previous-cell caret jump). |
| packages/muya/src/block/content/paragraphContent/tests/backspaceUnwrap.spec.ts | Covers paragraph backspace-at-offset-0 merge/unwrap branches across blockquote/list contexts. |
| packages/muya/src/block/base/tests/formatToggle.spec.ts | Covers Format.format() toggle-off and clear paths on booted content. |
| packages/muya/src/tests/headingLocaleCrash.spec.ts | Adds non-en locale regression coverage for heading conversion + HeadingCopyLink i18n + event emission. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| afterEach(() => { | ||
| while (bootedHosts.length) { | ||
| const host = bootedHosts.pop()!; | ||
| host.remove(); | ||
| } |
There was a problem hiding this comment.
Fixed forward in #4436: afterEach now calls window.getSelection()?.removeAllRanges() so a stale Range can no longer point into a removed host node and break a later test's setCursor.
| it('renders the copy-anchor affordance with the zh-CN translated label', () => { | ||
| const muya = bootMuya('\n', zhCN); | ||
| typeHeading(muya, '# Heading'); | ||
|
|
||
| const affordance = copyLinkAffordance(muya); |
There was a problem hiding this comment.
Fixed forward in #4436: this test is now async and await flush()es after typeHeading() before querying the affordance, so the next-frame conversion + HeadingCopyLink render is awaited rather than raced.
| it('resolves the affordance label per-locale (en vs zh-CN) for the same heading', () => { | ||
| const enMuya = bootMuya('\n', en); | ||
| typeHeading(enMuya, '# Heading'); | ||
| const enLabel = copyLinkAffordance(enMuya)!.getAttribute('aria-label'); | ||
|
|
||
| const zhMuya = bootMuya('\n', zhCN); | ||
| typeHeading(zhMuya, '# Heading'); | ||
| const zhLabel = copyLinkAffordance(zhMuya)!.getAttribute('aria-label'); | ||
|
|
There was a problem hiding this comment.
Fixed forward in #4436: the per-locale (en vs zh-CN) test is now async and await flush()es after each typeHeading() before reading the aria-label, so both conversions are flushed before the DOM is queried.
| it('still emits heading-copy-link when the zh-CN affordance is activated', () => { | ||
| const muya = bootMuya('\n', zhCN); | ||
| typeHeading(muya, '# Heading'); | ||
|
|
||
| const handler = vi.fn(); | ||
| muya.on('heading-copy-link', handler); | ||
| copyLinkAffordance(muya)!.dispatchEvent( | ||
| new MouseEvent('click', { bubbles: true, cancelable: true }), | ||
| ); |
There was a problem hiding this comment.
Fixed forward in #4436: this test is now async and await flush()es after typeHeading() before dispatching the click, so the affordance is guaranteed to exist (the conversion flush has landed) when the click fires.
| it('every shipped locale defines the heading copy-anchor key (no raw-key fall-through)', () => { | ||
| // The crash's second half was the missing locale key. Lock it in for | ||
| // every shipped locale so a future locale addition can't silently | ||
| // reintroduce the raw-key fall-through (and, under a non-en locale, the | ||
| // crash before the optional-chaining guard). | ||
| for (const locale of [en, zhCN]) { | ||
| const muya = bootMuya('\n', locale); | ||
| typeHeading(muya, '# Heading'); | ||
| const label = copyLinkAffordance(muya)!.getAttribute('aria-label'); | ||
| expect(label).toBe(locale.resource['Copy anchor link to this heading']); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Fixed forward in #4436 on both counts: the test now loops all nine shipped locales (de, en, es, fr, ja, ko, pt, zh-CN, zh-TW) instead of just [en, zhCN], so it lives up to its name — typing # is proven crash-safe under every shipped locale. It is also now async and await flush()es after each typeHeading() before reading the aria-label.
| it('lands the caret at the join point (end of the former first paragraph)', () => { | ||
| const muya = bootMuya('alpha\n\nbeta\n'); | ||
| const beta = contentByText(muya, 'beta'); | ||
|
|
||
| backspaceAtStart(muya, beta); | ||
|
|
||
| // After the merge the active block is `alpha`'s content; the caret sits | ||
| // at offset 5 (the original `alpha` length), where the two joined. | ||
| const merged = contentByText(muya, 'alphabeta'); | ||
| const cursor = merged.getCursor(); | ||
| expect(cursor).not.toBeNull(); | ||
| expect(cursor!.start.offset).toBe(5); | ||
| }); |
There was a problem hiding this comment.
Fixed forward in #4436: the caret/join-point test is now async and await flush()es before reading the merged tree, so contentByText('alphabeta') / getCursor() observe the post-merge state instead of racing the next-frame json1 flush.
… review) (#4436) Post-merge Copilot review on #4434 flagged real timing-dependence in two regression specs now on develop. Harden both so they deterministically exercise their bug guards: headingLocaleCrash.spec.ts - afterEach now clears the document-global Selection (window.getSelection()?.removeAllRanges()) so a stale Range can't point into a removed host node and break a later test's setCursor. - Every test that queries the HeadingCopyLink affordance / its aria-label / dispatches its click now `await flush()` after typeHeading(), since the paragraph->atx-heading conversion and the attachment render land on the next animation frame. - The "every shipped locale" test now actually loops all nine shipped locales (de, en, es, fr, ja, ko, pt, zh-CN, zh-TW) instead of just [en, zhCN], so typing `#` is proven crash-safe under every locale — the whole point of the guard. backspaceUnwrap.spec.ts - The caret/join-point assertion now `await flush()` before reading the merged tree, since the merge op flushes on the next frame; without it contentByText('alphabeta')/getCursor() could observe the pre-merge state. Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
What
Fills the highest-value missing automated tests identified by the @muyajs/core migration regression audit — the recently-fixed bugs and data-loss-adjacent paths that had no engine coverage. All tests assert already-correct behavior (coverage gaps, not failing-first) and pass on current
develop.Gaps covered
headingLocaleCrash.spec.ts#to create a heading under a non-enlocale used to crash on theHeadingCopyLinki18n.tpath. No test booted Muya with a non-en locale and hit it.formatToggle.spec.tsFormat.format()toggle-OFF +clearover a real selection (only the apply-side_addFormatwas covered)paragraphContent/backspaceUnwrap.spec.tstableCell/backspaceSafety.spec.tstable/insertRowColumn.spec.tsinsertRow/insertColumnshape, align inheritance, caret cell (only removal was tested)TOP-priority detail (#4424/#4427)
The new spec boots Muya with the bundled zh-CN locale, drives the real
#→ heading conversion input path (the path that buildsHeadingCopyLinkand callsmuya.i18n.t('Copy anchor link to this heading')), and asserts:heading-copy-linkstill emits on activation,I18n.tstays crash-safe and falls back to the raw key for a key missing from a non-en locale (guards the optional-chaining fix directly).Scope
Engine-only (
packages/muya). 5 new spec files, 33 test cases. No production code changed.Verification
pnpm -C packages/muya lint && lint:types && check-circular && test && test:specall green (656 unit + 1347 conformance + the 33 new tests).🤖 Generated with Claude Code