Additive test coverage across server, editor-ext, client and mcp. #192 — AiChatService.stream integration (Section 3, against real Postgres): - new apps/server/test/integration/ai-chat-stream.int-spec.ts drives the real streamText through a seeded ai/test MockLanguageModelV3 and a real Node ServerResponse, covering: onError persists an assistant error record (status 'error' + partial answer + provider cause in metadata); external MCP client closed exactly once on BOTH onFinish and onError; anti-tamper — history is rebuilt from the DB transcript, not from body.messages. #206 — red-team findings (most already fixed+tested in #212): - mdrt-2 (UNFIXED, data loss): turndown.dataloss.test.ts documents that pageBreak / transclusionReference / mention are silently dropped on Markdown export (characterization + it.fails for the desired survive-export contract). - persist-6 (UNFIXED, data loss): persistence-store.spec.ts adds an it.failing documenting that a momentarily-empty live doc overwrites non-empty content (left unfixed — a store-side empty-guard is a behaviour change). #204 — test-strategy plan, highest-priority subset: - Phase 1: mcp-clients.lease.spec.ts covers the external MCP client lease/refcount/eviction lifecycle (leak / premature-close / double-close). - Phase 2 data-integrity pure functions: editor-ext table-utils (transpose/moveRow/convert round-trip) and math tokenizer false-positive guard; client emoji-menu (+ it.fails for the unguarded localStorage JSON.parse bug), sort-cells, normalizeTableColumnWidths; mcp htmlEmbed/ pageBreak markdown data-loss + footnote-diff; server export getInternalLinkPageName extensionless-path bug — FIXED (small/clear) + tested. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
3.0 KiB
TypeScript
78 lines
3.0 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { htmlToMarkdown } from "./turndown.utils";
|
|
|
|
/**
|
|
* #206 mdrt-2 — Markdown export must never SILENTLY drop a block.
|
|
*
|
|
* `htmlToMarkdown` (turndown) only registers rules for a fixed set of custom
|
|
* nodes (callout, taskItem, details, math, iframe, htmlEmbed, image, video,
|
|
* footnote). Any other custom node — `transclusionReference`, `pageBreak`,
|
|
* `mention`, `status` — falls through to turndown's default handling: an empty
|
|
* wrapper is "blank" and removed, so the block disappears from the exported
|
|
* Markdown with no trace. The invariant "never silently lose a block" is broken.
|
|
*
|
|
* The `it.fails` cases assert the DESIRED contract (the block survives export in
|
|
* SOME form) and are RED today: they document the unfixed data loss and flip to
|
|
* green the moment a turndown rule (real syntax or a lossless HTML-comment
|
|
* placeholder) is added. A normal characterization `it` pins the exact current
|
|
* lossy output so the regression is unambiguous.
|
|
*/
|
|
describe("htmlToMarkdown — custom nodes without a turndown rule (#206 mdrt-2)", () => {
|
|
const wrap = (inner: string) =>
|
|
`<p>before</p>${inner}<p>after</p>`;
|
|
|
|
it("CURRENTLY drops a pageBreak entirely (data loss)", () => {
|
|
const md = htmlToMarkdown(
|
|
wrap('<div data-type="pageBreak" class="page-break"></div>'),
|
|
);
|
|
// The page break vanishes: only the two paragraphs remain, nothing between.
|
|
expect(md).toContain("before");
|
|
expect(md).toContain("after");
|
|
expect(md).not.toMatch(/page-?break/i);
|
|
expect(md).not.toContain("---"); // not even a horizontal-rule fallback
|
|
});
|
|
|
|
it("CURRENTLY drops a transclusionReference entirely (data loss)", () => {
|
|
const md = htmlToMarkdown(
|
|
wrap('<div data-type="transclusionReference" data-id="abc"></div>'),
|
|
);
|
|
expect(md).toContain("before");
|
|
expect(md).toContain("after");
|
|
// The data-id (the only thing that gives the reference identity) is gone.
|
|
expect(md).not.toContain("abc");
|
|
});
|
|
|
|
it.fails(
|
|
"should NOT lose a pageBreak block on Markdown export",
|
|
() => {
|
|
const md = htmlToMarkdown(
|
|
wrap('<div data-type="pageBreak" class="page-break"></div>'),
|
|
);
|
|
// Desired: the break survives in some form (e.g. a `---` rule or marker).
|
|
expect(md).toMatch(/(-{3,}|page-?break)/i);
|
|
},
|
|
);
|
|
|
|
it.fails(
|
|
"should NOT lose a transclusionReference's identity on Markdown export",
|
|
() => {
|
|
const md = htmlToMarkdown(
|
|
wrap('<div data-type="transclusionReference" data-id="abc"></div>'),
|
|
);
|
|
// Desired: the referenced id survives so the block can be rebuilt.
|
|
expect(md).toContain("abc");
|
|
},
|
|
);
|
|
|
|
it.fails(
|
|
"should NOT lose a mention's data-id on Markdown export",
|
|
() => {
|
|
const md = htmlToMarkdown(
|
|
'<p>hi <span data-type="mention" data-id="u1" data-label="Bob">@Bob</span> there</p>',
|
|
);
|
|
// Desired: the mention keeps its stable identity (data-id), not just text.
|
|
expect(md).toContain("u1");
|
|
},
|
|
);
|
|
});
|