diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e12412..7aaadce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -131,7 +131,7 @@ Pull request requirements: After submission, CI runs the automated checks. Maintainers apply a type label, assign the pull request's owner, and review the scope, implementation, and verification evidence; priority and Project status stay on the issue. Contributors should address review feedback or explain unresolved trade-offs. Maintainers squash merge accepted pull requests using the pull request title as the commit title on `main`. The pull request body becomes that commit's body and is the permanent record of the change; intermediate commits do not survive the merge. That is why the pull request body carries verification evidence and intermediate commit messages do not. -Verify changes locally before merging. Use `pnpm check:frontend` for frontend-only work, `pnpm check:backend` for Rust/Tauri-only work, and `pnpm check` for cross-cutting updates. For manual testing of Markdown and the article navigator, open the committed `corpus/` directory or one of its focused scenario directories in the app. +Verify changes locally before merging. Use `pnpm check:frontend` for frontend-only work, `pnpm check:backend` for Rust/Tauri-only work, and `pnpm check` for cross-cutting updates. For manual testing of Markdown and the article navigator, open the committed `corpus/` directory or one of its focused scenario directories in the app. Markdown round-trip convergence over `corpus/commonmark/`, `corpus/gfm/`, and `corpus/isolated/end-of-file/` is asserted automatically by `src/features/editor/tests/corpusRoundTrip.test.tsx`, so the manual pass over those directories covers rendering, interaction, and navigator behavior; the remaining subtrees are verified only by hand. Frontend checks enforce a coverage floor. It is a ratchet set just below the measured numbers rather than a target: a change that falls below it needs tests, not a lower floor, and the floor is raised when the measured numbers move up. diff --git a/src/features/editor/tests/corpusRoundTrip.test.tsx b/src/features/editor/tests/corpusRoundTrip.test.tsx new file mode 100644 index 0000000..e5953a8 --- /dev/null +++ b/src/features/editor/tests/corpusRoundTrip.test.tsx @@ -0,0 +1,51 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { beforeEach, describe, expect, it } from "vitest"; + +import { createMarkdownReferenceContext } from "@/test/factories/editor"; +import { setupMilkdownEditorMount } from "@/test/utils/milkdown"; +import { mockTauriApiCommand } from "@/test/utils/tauriApi"; + +const mountEditor = setupMilkdownEditorMount(createMarkdownReferenceContext()); + +const corpusFiles = [ + "commonmark/blocks.md", + "commonmark/code.md", + "commonmark/emphasis.md", + "commonmark/html.md", + "commonmark/links-and-images.md", + "commonmark/lists-and-blockquotes.md", + "commonmark/text-and-breaks.md", + "gfm/autolinks.md", + "gfm/strikethrough.md", + "gfm/tables.md", + "gfm/tagfilter.md", + "gfm/task-lists.md", + "isolated/end-of-file/incomplete-html-comment.md", + "isolated/end-of-file/unclosed-code-fence.md", + "isolated/end-of-file/unclosed-directive.md", + "isolated/end-of-file/unclosed-html-block.md", +]; + +const readCorpusFile = (relativePath: string) => + readFileSync(resolve(process.cwd(), "corpus", relativePath), "utf8"); + +// The editor is allowed to normalize on first open, so the baseline is the first +// serialization rather than the corpus file. +describe("Corpus round trip", () => { + beforeEach(() => { + mockTauriApiCommand("resolveMarkdownImageTarget", ({ target }) => ({ + kind: "renderable", + path: `C:/Notes/${target}`, + })); + }); + + it.each(corpusFiles)("converges on a stable serialization for %s", async (relativePath) => { + const source = readCorpusFile(relativePath); + + const first = (await mountEditor(source)).getMarkdown(); + const second = (await mountEditor(first)).getMarkdown(); + + expect(second).toBe(first); + }); +});