Skip to content

test: assert corpus round-trip convergence for supported syntax - #249

Merged
Azganoth merged 1 commit into
mainfrom
spike/corpus-round-trip
Aug 16, 2026
Merged

test: assert corpus round-trip convergence for supported syntax#249
Azganoth merged 1 commit into
mainfrom
spike/corpus-round-trip

Conversation

@Azganoth

@Azganoth Azganoth commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Summary

Delivers the spike outcome from #135: an automated round-trip guard over the corpus subtrees whose syntax Leafdown supports.

  • Adds src/features/editor/tests/corpusRoundTrip.test.tsx, asserting that serialize(parse(source)) and a second pass over that result produce the same string for 16 files across corpus/commonmark/, corpus/gfm/, and corpus/isolated/end-of-file/. The editor may normalize on first open; it must not keep changing the document afterwards.
  • Notes in CONTRIBUTING.md that those three directories now carry automated convergence coverage, so the manual corpus pass over them is for rendering, interaction, and navigator behavior. The remaining subtrees stay fully manual.

Why convergence, and why it is interim

Byte identity is the property worth wanting here: open a file, save it, the file is unchanged. It is not assertable today. Measured over the scoped set, 12 of the 16 files are rewritten on first open, several by around 100 lines. Three causes:

  1. Escaping applied out of context, the dominant one by volume. The serializer escapes characters that would not parse as syntax where they sit — garden_sensor_namegarden\_sensor\_name (intraword _ is never emphasis), *opening-only\*opening-only (an unpaired * is already literal), name@examplename\@example. This is not forced by the document model, and it is the open question in How a Markdown escape should be represented in the editor #245.
  2. Delimiter form the model does not retain: - bullets → *, ~strike~~~strike~~, _em_*em*. Fixable per construct, as feat: preserve the authored autolink form on save #243 did for autolinks.
  3. Structural spelling the model does not retain: lazy blockquote continuation gains an explicit >, tight lists are written loose.

The 4 files that already round-trip byte-identically do so because they contain almost no inline constructs to escape, not because anything preserves authored form. So there is no meaningful identity-holding subset to pin, and no useful hybrid between the two assertions.

Convergence is therefore chosen as an interim: it holds the editor to a stable document without blessing any particular normalization, needs no baseline or deviation list, and is what catches the #247 class of defect. It should tighten toward identity as #245 settles. A green run here does not mean the corpus saves unchanged, and this file should not be read as endorsing the current normalization.

Nothing new was built to support this. setupMilkdownEditorMount, createMarkdownReferenceContext, and mockTauriApiCommand already cover it, and reading corpus files from disk follows editorPresentation.test.tsx.

Related Issue

Closes #135
Refs #247

Verification

corpusRoundTrip.test.tsx proves the property directly: all 16 scoped files converge. The same test at the spike's baseline failed on three of them (commonmark/lists-and-blockquotes.md, commonmark/text-and-breaks.md, gfm/tables.md), which is what surfaced #247; those three pass here on top of its fix, so the file is a real guard rather than a tautology.

Ran pnpm check:frontend for this frontend-only change.

Not verified: rendering, interaction, and navigator behavior, which convergence says nothing about. This test never types, undoes, or copies.

Notes

Scope, so a green run is not over-read:

Deliberately excluded, per the analysis in #135:

  • corpus/boundaries/bytes/, which exists to pin CR, CRLF, BOM, NUL, and a missing final newline — precisely what reading and serializing normalizes away.
  • corpus/environment/, whose subject needs the Rust backend and is already covered by the createArticleTree and createFolderContext factories.
  • corpus/extensions/, which mostly asserts that unsupported syntax survives as escaped literal text, already covered in markdownCompatibility.test.tsx.
  • corpus/practical/, whose local image and link references would turn this into per-path resolution mocks.

Three counts in the #135 analysis were off and are corrected by this branch and its Outcome section:

  • isolated/end-of-file/ holds 4 files rather than 3, so the scoped total is 16 rather than 15.
  • Only commonmark/links-and-images.md needs the image resolver mock, not two files.
  • "Nine of the fifteen candidate files contain a construct the serializer normalizes" understates it substantially. Measured, 12 of 16 differ from source, and the listed constructs are not the main driver — out-of-context escaping is.

Observed while measuring, not investigated and not claimed as a defect: tight lists are serialized loose (- A\n- B* A\n\n* B), which changes rendered output (<li>A</li> versus <li><p>A</p></li>). Whether the schema distinguishes tight from loose at all is unchecked. Recorded in #135 for whoever picks up #245.

Twelve of the sixteen scoped files are rewritten on first open, so byte identity is
not assertable today. The dominant cause is the serializer escaping characters that
would not parse as syntax in place, which is the open question in #245; the remainder
is delimiter and structural form the document model does not retain.

Convergence is an interim property chosen for that reason. It holds the editor to a
stable document without blessing any particular normalization, and it is what catches
the structural loss fixed in #247. Byte identity stays the target, and this assertion
should tighten toward it as the escaping question is settled.

The other corpus subtrees stay manual. The byte fixtures exist to pin CR, CRLF, BOM,
NUL, and a missing final newline, which is precisely what reading and serializing
normalizes away; the environment and extension trees assert behavior this test cannot
reach.
@Azganoth
Azganoth force-pushed the spike/corpus-round-trip branch from 7f9372f to 98a299b Compare August 16, 2026 07:49
@Azganoth
Azganoth marked this pull request as ready for review August 16, 2026 07:53
@Azganoth Azganoth self-assigned this Aug 16, 2026
@Azganoth Azganoth added the Maintenance Chore, refactor, dependency, or test work without user-facing change label Aug 16, 2026
@Azganoth
Azganoth merged commit 9626471 into main Aug 16, 2026
3 checks passed
@Azganoth
Azganoth deleted the spike/corpus-round-trip branch August 16, 2026 07:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Maintenance Chore, refactor, dependency, or test work without user-facing change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Determine whether the Markdown corpus can drive automated round-trip tests

1 participant