Files
gitmost/packages/prosemirror-markdown/test/markdown-document.test.ts
claude code agent 227 d6d7dd82f6 feat(prosemirror-markdown): new headless converter package seeded from git-sync (#293 stage 1)
Create @docmost/prosemirror-markdown — the single framework-free ProseMirror<->
Markdown converter + schema mirror that git-sync and mcp will both consume,
ending the three-hand-synced-copies drift (#293). This step only CREATES the
package (no consumer yet; git-sync untouched); the switch of git-sync and mcp
onto it, plus the canonical format decisions, come in later commits of this PR.

- packages/prosemirror-markdown/src/lib/: the 8 converter-core files copied
  VERBATIM from packages/git-sync/src/lib (docmost-schema, markdown-converter,
  markdown-to-prosemirror, canonicalize, markdown-document, node-ops, page-file,
  index). Confirmed byte-identical — no behavioral drift introduced.
- src/index.ts barrel; package.json (@tiptap/* + jsdom/marked/zod, editor-ext
  workspace devDep for the contract test); tsconfig/vitest configs.
- 24 converter-core test files + fixtures copied (engine-coupled layout/
  redteam-layout-title tests correctly excluded — they import ../src/engine).
- pnpm-lock importer added; build/ gitignored (CI-built).

Verified (clean checkout, no network): pnpm --frozen-lockfile EXIT 0; tsc EXIT 0;
vitest 23 files, 443 passed | 1 expected-fail (the same image-diagrams
known-limitation carried from git-sync) — faithful extraction. git-sync untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 07:10:04 +03:00

67 lines
2.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
serializeDocmostMarkdownBody,
parseDocmostMarkdown,
type DocmostMdMeta,
} from 'docmost-client';
describe('serializeDocmostMarkdownBody round-trip (SPEC §3)', () => {
it('serialize -> parse preserves meta and the trimmed body, with no comments block', () => {
const meta: DocmostMdMeta = {
version: 1,
pageId: 'page-123',
slugId: 'slug-abc',
title: 'My Page',
spaceId: 'space-1',
parentPageId: 'parent-9',
};
const body = 'Hello\n\nWorld';
const file = serializeDocmostMarkdownBody(meta, body);
const parsed = parseDocmostMarkdown(file);
expect(parsed.meta).toEqual(meta);
expect(parsed.body).toBe(body);
// No trailing docmost:comments block was emitted (SPEC §3).
expect(parsed.comments).toBeNull();
});
it('preserves a null parentPageId for a root page', () => {
const meta: DocmostMdMeta = {
version: 1,
pageId: 'root-1',
slugId: 'root-slug',
title: 'Root',
spaceId: 'space-1',
parentPageId: null,
};
const file = serializeDocmostMarkdownBody(meta, 'body text');
const parsed = parseDocmostMarkdown(file);
expect(parsed.meta).toEqual(meta);
expect(parsed.comments).toBeNull();
});
it('produces a parseable file for an empty/missing body', () => {
const meta: DocmostMdMeta = { version: 1, pageId: 'p-empty' };
// Empty string body.
const emptyFile = serializeDocmostMarkdownBody(meta, '');
expect(() => parseDocmostMarkdown(emptyFile)).not.toThrow();
const parsedEmpty = parseDocmostMarkdown(emptyFile);
expect(parsedEmpty.meta).toEqual(meta);
expect(parsedEmpty.body).toBe('');
expect(parsedEmpty.comments).toBeNull();
// Missing body (undefined) — serializer coalesces to "".
const missingFile = serializeDocmostMarkdownBody(
meta,
undefined as unknown as string,
);
expect(() => parseDocmostMarkdown(missingFile)).not.toThrow();
const parsedMissing = parseDocmostMarkdown(missingFile);
expect(parsedMissing.meta).toEqual(meta);
expect(parsedMissing.body).toBe('');
expect(parsedMissing.comments).toBeNull();
});
});