Skip to content

test(parity): failing-test scoreboard for #4406 muyajs→@muyajs/core gaps - #4407

Merged
Jocs merged 4 commits into
developfrom
test/parity-scoreboard-4406
Jun 8, 2026
Merged

Jocs merged 4 commits into
developfrom
test/parity-scoreboard-4406

Conversation

@Jocs

@Jocs Jocs commented Jun 8, 2026

Copy link
Copy Markdown
Member

What

Foundation PR for the desktop migration to @muyajs/core (#4406). It encodes all 15 confirmed functional-parity gaps as regression tests that fail on develop today (proving each gap), marked as expected-failures so both suites stay GREEN. Later fix PRs flip each entry to passing by removing its marker.

This PR is tests + docs only — no behavior change.

Mechanism

  • muya engine (packages/muya/src/**/__tests__/parity*.spec.ts): vitest it.fails(...) — asserts the correct (pre-migration) behavior, fails today (counts as pass). When a fix lands the test passes and it.fails then errors, forcing the fixer to delete .fails.
  • desktop e2e (packages/desktop/test/e2e/parity-*.spec.ts): Playwright test.fail() — runs headless and currently fails (counts as pass). Remove test.fail() to flip green.
  • manual-QA (packages/desktop/test/PARITY_QA.md): precise checklists for gaps not drivable headless (real OS clipboard bitmaps, drag-drop gestures).
  • Every test name is prefixed with its gap id (PGn:) so fix PRs can grep -rn "PGn:" and flip it.
  • The visible board is packages/desktop/test/PARITY_SCOREBOARD.md.

The 15 gaps and their test locations

Gap Sev Test location · marker
PG1 selection-change affiliation major packages/muya/src/selection/__tests__/paritySelectionChange.spec.ts it.fails · packages/desktop/test/e2e/parity-pg1-menu-state.spec.ts test.fail()
PG2 source→WYSIWYG caret not restored major packages/desktop/test/e2e/parity-source-undo-saved.spec.ts PG2: test.fail()
PG3 autoCheck cascade lost major packages/muya/src/block/gfm/taskListCheckbox/__tests__/parityAutoCheck.spec.ts it.fails
PG4 drag-drop image insertion absent major packages/desktop/test/PARITY_QA.md § PG4 (manual-QA)
PG5 binary/bitmap clipboard paste lost major packages/muya/src/clipboard/__tests__/parityImagePaste.spec.ts PG5: it.fails + PARITY_QA.md § PG5
PG6 pasted image FILE bypasses imageAction major packages/muya/src/clipboard/__tests__/parityImagePaste.spec.ts PG6: it.fails
PG7 export CSS from CDN not inlined major packages/muya/src/state/__tests__/parityExportHtml.spec.ts PG7: it.fails
PG8 exported headings carry no id major packages/muya/src/state/__tests__/parityExportHtml.spec.ts PG8: it.fails
PG9 copyAsRich pastes HTML source not rich major packages/muya/src/clipboard/__tests__/parityCopyAsRich.spec.ts it.fails
PG10 preview-image not emitted (Space) minor packages/muya/src/selection/__tests__/parityPreviewImage.spec.ts it.fails
PG11 heading-copy-link not emitted minor packages/muya/src/__tests__/parityHeadingCopyLink.spec.ts it.fails
PG12 hideLinkPopup not consumed minor packages/muya/src/editor/__tests__/parityHideLinkPopup.spec.ts it.fails (+ positive control)
PG13 insertParagraph nested anchor minor packages/muya/src/__tests__/parityInsertParagraphNested.spec.ts it.fails
PG14 first undo after source-mode minor packages/desktop/test/e2e/parity-source-undo-saved.spec.ts PG14: test.fail()
PG15 undo doesn't restore saved indicator minor packages/desktop/test/e2e/parity-source-undo-saved.spec.ts PG15: test.fail()

How fix PRs flip a gap green

  1. Implement the fix.
  2. grep -rn "PGn:" to find the test(s).
  3. it.fails→it (muya) or delete test.fail() (desktop e2e), or run + tick the manual-QA entry.
  4. Confirm it now PASSES; update the Status column in PARITY_SCOREBOARD.md.

Verification

  • muya: lint (0 err) · lint:types · check-circular · test (512 pass + 20 xfail) · test:spec (1347 pass) — all GREEN.
  • desktop: build:unpack · typecheck · lint (0 err) · test (560 pass) · the 4 parity e2e (all xfail/GREEN) — all GREEN.
  • Each it.fails/test.fail was confirmed to fail for the right reason (the asserted gap) by temporarily removing the marker.

A small scoped eslint tweak lets the greppable PG test-title prefix through antfu's prefer-lowercase-title rule (spec files only); no safety rule relaxed.

Refs #4406.

🤖 Generated with Claude Code

Jocs and others added 3 commits June 9, 2026 00:56
…10,11,12,13

Encode 10 of the 15 confirmed muyajs→@muyajs/core functional-parity gaps as
vitest `it.fails` regression tests in the engine. Each asserts the desired
pre-migration behaviour and fails on develop today (counted as a pass by
`it.fails`); when a fix lands the test passes and `it.fails` errors, forcing the
fixer to drop the marker. Every test name is prefixed with its gap id (`PGn:`)
so fix PRs can grep + flip it.

- PG1  selection-change lacks block affiliation / ancestor type
- PG3  autoCheck preference not consumed (task-list cascade lost)
- PG5  binary/bitmap clipboard image paste not persisted via imageAction
- PG6  pasted image FILE bypasses imageAction (insert preference ignored)
- PG7  export links core CSS from CDN instead of inlining it
- PG8  exported headings carry no slug id (dead TOC anchors)
- PG9  copyAsRich writes HTML source not rich text (no copyAsRich path)
- PG10 preview-image not emitted (Space on a selected image)
- PG11 heading-copy-link not emitted (hover-to-copy affordance gone)
- PG12 hideLinkPopup not consumed (link hover popover not gated)
- PG13 insertParagraph anchors to outermost not immediate nested block

Allow the uppercase `PG` test-title prefix through antfu's
`test/prefer-lowercase-title` rule (scoped to spec files) so the greppable
gap-id naming coexists with the lint rule; all other test titles still must
start lowercase. Suite stays GREEN: 512 pass + 20 expected-fail.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Add Playwright `test.fail()` regression specs for the desktop-side parity gaps.
Each runs headless and currently fails (the gap), which `test.fail()` counts as
a pass; removing `test.fail()` flips the entry green once fixed. Test names are
prefixed with the gap id (`PGn:`) for grep-and-flip.

- PG1  Paragraph menu does not check the current block type (affiliation gap;
       caret placed directly in an H1 content span — the engine-unit half lives
       in paritySelectionChange.spec.ts)
- PG2  WYSIWYG caret not restored after a source-mode edit (handleFileChange
       drops muyaIndexCursor; no index→path cursor conversion in the engine)
- PG14 first undo after exiting source mode does not revert the bulk source-mode
       change as one step
- PG15 undo back to on-disk content leaves the tab marked unsaved (synthetic
       history id never re-matches the saved id)

Undo is driven through the same `mt::editor-edit-action` IPC the Edit › Undo
menu uses. Suite stays GREEN (4 expected-fail).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Add the visible "how many gaps remain" board (PARITY_SCOREBOARD.md): a table of
all 15 muyajs→@muyajs/core parity gaps → severity → test location(s) → status
(all xfail now), plus the flip-to-green workflow for fix PRs.

Add PARITY_QA.md: precise manual-QA checklists (exact steps + expected vs
current result) for the two gaps that cannot be driven headless —
PG4 drag-drop image insertion and the OS-clipboard/screenshot half of
PG5 binary image paste.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Copilot AI review requested due to automatic review settings June 8, 2026 16:58

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 introduces a “failing-test scoreboard” to track functional parity gaps from the packages/muyajs → @muyajs/core migration (PR #4406), encoding each confirmed gap as an expected-failure test (Vitest it.fails(...) for engine-unit tests, Playwright test.fail() for desktop e2e) plus manual-QA checklists for gaps that can’t be driven headlessly.

Changes:

  • Adds parity regression specs in packages/muya that assert legacy/desired behavior and are marked as expected failures.
  • Adds desktop Playwright parity specs (expected failures) and manual QA checklist + a central scoreboard document.
  • Tweaks packages/muya ESLint config to allow PGn:-prefixed test titles while keeping the lowercase-title rule for all other tests.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/muya/src/state/tests/parityExportHtml.spec.ts Adds xfail tests for export HTML CSS inlining + heading id parity (PG7/PG8).
packages/muya/src/selection/tests/paritySelectionChange.spec.ts Adds xfail tests for selection-change payload parity (affiliation + block type) (PG1).
packages/muya/src/selection/tests/parityPreviewImage.spec.ts Adds xfail tests for Space-to-preview-image event emission (PG10).
packages/muya/src/editor/tests/parityHideLinkPopup.spec.ts Adds control + xfail test to assert hideLinkPopup suppresses link hover popover (PG12).
packages/muya/src/clipboard/tests/parityImagePaste.spec.ts Adds xfail tests for bitmap paste + imageAction routing for pasted image files (PG5/PG6).
packages/muya/src/clipboard/tests/parityCopyAsRich.spec.ts Adds xfail tests for “Copy as Rich Text” behavior / copyAsRich support (PG9).
packages/muya/src/block/gfm/taskListCheckbox/tests/parityAutoCheck.spec.ts Adds xfail tests for autoCheck cascade behavior on task lists (PG3).
packages/muya/src/tests/parityInsertParagraphNested.spec.ts Adds xfail tests for nested insertParagraph anchor behavior (PG13).
packages/muya/src/tests/parityHeadingCopyLink.spec.ts Adds xfail tests for heading hover copy-link affordance + event emission (PG11).
packages/muya/eslint.config.mjs Allows PG prefix for test titles under test/prefer-lowercase-title (spec/test files).
packages/desktop/test/PARITY_SCOREBOARD.md Adds the parity scoreboard doc listing gaps, tests, and flip-to-green workflow.
packages/desktop/test/PARITY_QA.md Adds manual QA checklists for gaps not reliably testable headlessly (PG4/PG5).
packages/desktop/test/e2e/parity-source-undo-saved.spec.ts Adds Playwright xfail coverage for source-mode caret/undo/saved-indicator gaps (PG2/PG14/PG15).
packages/desktop/test/e2e/parity-pg1-menu-state.spec.ts Adds Playwright xfail coverage for Paragraph menu checkmarks driven by selection affiliation gap (PG1).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +38 to +56
afterEach(() => {
while (bootedHosts.length) {
const host = bootedHosts.pop()!;
host.remove();
}
if (hadVersion)
window.MUYA_VERSION = originalVersion as string;
else
delete (window as Partial<Window>).MUYA_VERSION;
});

function bootMuya(markdown: string): Muya {
const host = document.createElement('div');
document.body.appendChild(host);
const muya = new Muya(host, { markdown } as ConstructorParameters<typeof Muya>[1]);
muya.init();
bootedHosts.push(muya.domNode);
return muya;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — fixed in 824deb2. The cleanup now tracks each booted Muya and calls muya.destroy() in afterEach, which runs eventCenter.detachAllDomEvents() (removing the document-level keydown/click listeners selection registers) and removes the host node, so listeners no longer leak across tests.

Comment on lines +32 to +50
afterEach(() => {
while (bootedHosts.length) {
const host = bootedHosts.pop()!;
host.remove();
}
if (hadVersion)
window.MUYA_VERSION = originalVersion as string;
else
delete (window as Partial<Window>).MUYA_VERSION;
});

function bootMuya(markdown: string): Muya {
const host = document.createElement('div');
document.body.appendChild(host);
const muya = new Muya(host, { markdown } as ConstructorParameters<typeof Muya>[1]);
muya.init();
bootedHosts.push(muya.domNode);
return muya;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — fixed in 824deb2. The cleanup now tracks each booted Muya and calls muya.destroy() in afterEach, which runs eventCenter.detachAllDomEvents() (removing the document-level keydown/click listeners selection registers) and removes the host node, so listeners no longer leak across tests.

Comment on lines +34 to +55
afterEach(() => {
while (bootedHosts.length) {
const host = bootedHosts.pop()!;
host.remove();
}
if (hadVersion)
window.MUYA_VERSION = originalVersion as string;
else
delete (window as Partial<Window>).MUYA_VERSION;
});

function bootMuya(markdown: string, options: Record<string, unknown> = {}): Muya {
const host = document.createElement('div');
document.body.appendChild(host);
const muya = new Muya(host, {
markdown,
...options,
} as ConstructorParameters<typeof Muya>[1]);
muya.init();
bootedHosts.push(muya.domNode);
return muya;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — fixed in 824deb2. The cleanup now tracks each booted Muya and calls muya.destroy() in afterEach, which runs eventCenter.detachAllDomEvents() (removing the document-level keydown/click listeners selection registers) and removes the host node, so listeners no longer leak across tests.

Comment on lines +37 to +58
afterEach(() => {
while (bootedHosts.length) {
const host = bootedHosts.pop()!;
host.remove();
}
if (hadVersion)
window.MUYA_VERSION = originalVersion as string;
else
delete (window as Partial<Window>).MUYA_VERSION;
});

function bootMuya(markdown: string, options: Record<string, unknown> = {}): Muya {
const host = document.createElement('div');
document.body.appendChild(host);
const muya = new Muya(host, {
markdown,
...options,
} as ConstructorParameters<typeof Muya>[1]);
muya.init();
bootedHosts.push(muya.domNode);
return muya;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — fixed in 824deb2. The cleanup now tracks each booted Muya and calls muya.destroy() in afterEach, which runs eventCenter.detachAllDomEvents() (removing the document-level keydown/click listeners selection registers) and removes the host node, so listeners no longer leak across tests.

Comment on lines +34 to +52
afterEach(() => {
while (bootedHosts.length) {
const host = bootedHosts.pop()!;
host.remove();
}
if (hadVersion)
window.MUYA_VERSION = originalVersion as string;
else
delete (window as Partial<Window>).MUYA_VERSION;
});

function bootMuya(markdown: string): Muya {
const host = document.createElement('div');
document.body.appendChild(host);
const muya = new Muya(host, { markdown } as ConstructorParameters<typeof Muya>[1]);
muya.init();
bootedHosts.push(muya.domNode);
return muya;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — fixed in 824deb2. The cleanup now tracks each booted Muya and calls muya.destroy() in afterEach, which runs eventCenter.detachAllDomEvents() (removing the document-level keydown/click listeners selection registers) and removes the host node, so listeners no longer leak across tests.

Comment on lines +31 to +49
afterEach(() => {
while (bootedHosts.length) {
const host = bootedHosts.pop()!;
host.remove();
}
if (hadVersion)
window.MUYA_VERSION = originalVersion as string;
else
delete (window as Partial<Window>).MUYA_VERSION;
});

function bootMuya(markdown: string): Muya {
const host = document.createElement('div');
document.body.appendChild(host);
const muya = new Muya(host, { markdown } as ConstructorParameters<typeof Muya>[1]);
muya.init();
bootedHosts.push(muya.domNode);
return muya;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — fixed in 824deb2. The cleanup now tracks each booted Muya and calls muya.destroy() in afterEach, which runs eventCenter.detachAllDomEvents() (removing the document-level keydown/click listeners selection registers) and removes the host node, so listeners no longer leak across tests.

@github-actions

github-actions Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

Build artifacts for PR #4407:

Run: https://github.com/marktext/marktext/actions/runs/27154157934

Artifact Size Link
marktext-windows-arm64 256.2 MB Download
marktext-linux 556.2 MB Download
marktext-windows-x64 257.5 MB Download
marktext-macos-x64 256.7 MB Download
marktext-macos-arm64 246.5 MB Download

…teners

Address Copilot review: the parity engine specs' cleanup only removed the host
DOM node, never calling `muya.destroy()`, so the `document`-level keydown/click
listeners selection registers during init leaked across tests — a flake / order-
dependence risk as the suite grows. Track booted Muya instances and `destroy()`
each in afterEach (detaches all DOM events + removes the node).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
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.

2 participants