import { describe, expect, it } from 'vitest'; // Import DIRECTLY from src (NOT the docmost-client barrel, which pulls in // collaboration.ts and mutates global DOM at import time). import { serializeDocmostMarkdown, parseDocmostMarkdown, serializeDocmostMarkdownBody, type DocmostMdMeta, } from '../src/lib/markdown-document.js'; const meta: DocmostMdMeta = { version: 1, pageId: 'p1', slugId: 's1', title: 'Hello', spaceId: 'sp1', parentPageId: null, }; describe('serializeDocmostMarkdown / parseDocmostMarkdown', () => { // --------------------------------------------------------------------------- describe('round-trip', () => { it('round-trips meta, body, and comments', () => { const body = '# Title\n\nSome **body** text.'; const comments = [{ id: 'c1', text: 'a note' }]; const full = serializeDocmostMarkdown(meta, body, comments); const parsed = parseDocmostMarkdown(full); expect(parsed.meta).toEqual(meta); expect(parsed.body).toBe(body); expect(parsed.comments).toEqual(comments); }); it('emits a comments block with [] even when there are no comments', () => { const full = serializeDocmostMarkdown(meta, 'body', []); expect(full).toContain(''); const parsed = parseDocmostMarkdown(full); expect(parsed.comments).toEqual([]); expect(parsed.body).toBe('body'); }); it('non-array comments arg is normalized to [] in the serialized output', () => { const full = serializeDocmostMarkdown(meta, 'body', null as any); expect(full).toContain(''); }); it('trims surrounding whitespace from the body on serialize', () => { const full = serializeDocmostMarkdown(meta, '\n\n body \n\n', []); const parsed = parseDocmostMarkdown(full); expect(parsed.body).toBe('body'); }); }); // --------------------------------------------------------------------------- describe('missing blocks (tolerant parsing)', () => { it('missing meta block yields meta:null', () => { const input = 'Just a body.\n\n\n'; const parsed = parseDocmostMarkdown(input); expect(parsed.meta).toBeNull(); expect(parsed.body).toBe('Just a body.'); expect(parsed.comments).toEqual([]); }); it('missing comments block yields comments:null and treats all as body', () => { const input = '\n\nbody only'; const parsed = parseDocmostMarkdown(input); expect(parsed.meta).toEqual(meta); expect(parsed.comments).toBeNull(); expect(parsed.body).toBe('body only'); }); it('plain markdown with neither block: meta and comments null, whole input is body', () => { const input = '# Plain\n\nNo envelope here.'; const parsed = parseDocmostMarkdown(input); expect(parsed.meta).toBeNull(); expect(parsed.comments).toBeNull(); expect(parsed.body).toBe(input); }); }); // --------------------------------------------------------------------------- describe('CRLF normalization', () => { it('parses a CRLF-encoded document the same as LF', () => { const lf = serializeDocmostMarkdown(meta, 'line one\nline two', [ { id: 'c1' }, ]); const crlf = lf.replace(/\n/g, '\r\n'); const parsed = parseDocmostMarkdown(crlf); expect(parsed.meta).toEqual(meta); expect(parsed.body).toBe('line one\nline two'); expect(parsed.comments).toEqual([{ id: 'c1' }]); }); }); // --------------------------------------------------------------------------- describe('only the final document-ending comments block is captured', () => { it('an earlier literal docmost:comments opener inside the body stays in the body', () => { // The body documents the format and contains a literal opener that does // NOT end the document. Only the trailing block is treated as metadata. const bodyWithLiteral = 'Here is how the format looks:\n\n\n\nand more prose after it.'; const full = serializeDocmostMarkdown(meta, bodyWithLiteral, [ { id: 'real' }, ]); const parsed = parseDocmostMarkdown(full); // The real (final) block parses into the comments... expect(parsed.comments).toEqual([{ id: 'real' }]); // ...and the earlier literal opener is preserved verbatim in the body. expect(parsed.body).toContain( '', ); expect(parsed.body).toContain('and more prose after it.'); }); it('a literal opener whose closer does NOT end the doc is left entirely in the body', () => { // No real trailing block: the opener is not document-ending, so comments // stays null and nothing is stripped. const input = '\n\nbody start\n\n\n\ntrailing text not ending the doc'; const parsed = parseDocmostMarkdown(input); expect(parsed.comments).toBeNull(); expect(parsed.body).toContain('" closer has CRLF and trailing spaces', () => { // The closer regex is /\r?\n-->[ \t]*\r?\n?\s*$/. Build a document whose // trailing comments block uses CRLF line endings AND has trailing spaces // after the "-->" closer, then assert it is still recognised as the // document-ending block (and the body is not polluted by it). const metaLine = JSON.stringify(meta); const crlfDoc = `\r\n\r\n` + `the body line\r\n\r\n` + ` \r\n`; const parsed = parseDocmostMarkdown(crlfDoc); expect(parsed.meta).toEqual(meta); expect(parsed.body).toBe('the body line'); expect(parsed.comments).toEqual([{ id: 'c-crlf' }]); }); }); // --------------------------------------------------------------------------- describe('malformed JSON throws a clear error', () => { it('throws on malformed meta JSON', () => { const input = '\n\nbody'; expect(() => parseDocmostMarkdown(input)).toThrow(/docmost:meta JSON/); }); it('throws on malformed comments JSON', () => { const input = 'body\n\n\n'; expect(() => parseDocmostMarkdown(input)).toThrow(/docmost:comments JSON/); }); }); }); describe('serializeDocmostMarkdownBody', () => { it('emits NO comments block', () => { const out = serializeDocmostMarkdownBody(meta, 'just the body'); expect(out).not.toContain('docmost:comments'); expect(out).toContain('