@mdfn/testing 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/index.cjs +228 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +54 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +194 -0
- package/dist/index.js.map +1 -0
- package/package.json +26 -0
package/README.md
ADDED
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
MDFN_TESTING_VERSION: () => MDFN_TESTING_VERSION,
|
|
24
|
+
authoritativeGfmCorpus: () => authoritativeGfmCorpus,
|
|
25
|
+
certifyExtension: () => certifyExtension,
|
|
26
|
+
compareSemanticTraces: () => compareSemanticTraces,
|
|
27
|
+
deterministicFuzzCorpus: () => deterministicFuzzCorpus,
|
|
28
|
+
representativeCorpus: () => representativeCorpus,
|
|
29
|
+
runPreservationCorpus: () => runPreservationCorpus,
|
|
30
|
+
runSecurityCorpus: () => runSecurityCorpus,
|
|
31
|
+
runSemanticCorpus: () => runSemanticCorpus,
|
|
32
|
+
traceState: () => traceState
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
var import_core = require("@mdfn/core");
|
|
36
|
+
var import_markdown = require("@mdfn/markdown");
|
|
37
|
+
var import_render = require("@mdfn/render");
|
|
38
|
+
var import_parse5 = require("parse5");
|
|
39
|
+
function runPreservationCorpus(fixtures) {
|
|
40
|
+
const findings = [];
|
|
41
|
+
let passed = 0;
|
|
42
|
+
for (const fixture of fixtures) {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = (0, import_markdown.parseMarkdown)(fixture.source, fixture.options);
|
|
45
|
+
const serialized = (0, import_markdown.serializeMarkdown)({ document: parsed.document, originalSource: fixture.source, options: fixture.options });
|
|
46
|
+
if (serialized.markdown !== fixture.source) {
|
|
47
|
+
findings.push({ code: "MDFN_CONFORMANCE_NO_EDIT_CHANGED", fixture: fixture.id, message: "No-edit round trip changed source bytes" });
|
|
48
|
+
} else if (!serialized.preservation.exactUntouched) {
|
|
49
|
+
findings.push({ code: "MDFN_CONFORMANCE_EXACT_NOT_REPORTED", fixture: fixture.id, message: "Exact preservation was not reported" });
|
|
50
|
+
} else {
|
|
51
|
+
passed += 1;
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
findings.push({ code: "MDFN_CONFORMANCE_EXCEPTION", fixture: fixture.id, message: error instanceof Error ? error.message : String(error) });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { ok: findings.length === 0, passed, failed: findings.length, findings };
|
|
58
|
+
}
|
|
59
|
+
function certifyExtension(extension, fixture) {
|
|
60
|
+
const configured = fixture.options?.extensions;
|
|
61
|
+
const existing = configured ? "schemaHash" in configured ? configured.extensions : configured : [];
|
|
62
|
+
const options = { ...fixture.options ?? {}, extensions: [...existing, extension] };
|
|
63
|
+
const findings = [...runPreservationCorpus([{ ...fixture, options }]).findings];
|
|
64
|
+
let passed = findings.length === 0 ? 1 : 0;
|
|
65
|
+
try {
|
|
66
|
+
const registry = (0, import_core.resolveExtensions)(options.extensions);
|
|
67
|
+
const parsed = (0, import_markdown.parseMarkdown)(fixture.source, options);
|
|
68
|
+
const owns = (node) => extension.serializeMarkdown?.({ node }) != null || (node.content?.some(owns) ?? false);
|
|
69
|
+
const dirty = (node) => owns(node) ? { ...node, source: node.source ? { ...node.source, dirty: true } : void 0, content: node.content?.map(dirty) } : { ...node, content: node.content?.map(dirty) };
|
|
70
|
+
const edited = dirty(parsed.document);
|
|
71
|
+
const ownedNodes = [];
|
|
72
|
+
const collectOwned = (node) => {
|
|
73
|
+
if (extension.serializeMarkdown?.({ node }) != null) ownedNodes.push(node);
|
|
74
|
+
node.content?.forEach(collectOwned);
|
|
75
|
+
};
|
|
76
|
+
collectOwned(parsed.document);
|
|
77
|
+
if (ownedNodes.length === 0) findings.push({ code: "MDFN_CERT_FIXTURE_NOT_OWNED", fixture: fixture.id, message: "Extension did not claim any syntax in its certification fixture" });
|
|
78
|
+
const serialized = (0, import_markdown.serializeMarkdown)({ document: edited, originalSource: fixture.source, options });
|
|
79
|
+
if (!serialized.markdown) findings.push({ code: "MDFN_CERT_EDITED_SERIALIZATION_EMPTY", fixture: fixture.id, message: "Edited serialization produced no source" });
|
|
80
|
+
else passed += 1;
|
|
81
|
+
(0, import_render.renderHtml)(edited, { extensions: registry, rawHtml: { enabled: false } });
|
|
82
|
+
passed += 1;
|
|
83
|
+
if (extension.visual) {
|
|
84
|
+
const visual = ownedNodes.map((node) => extension.visual?.({ node, escape: (value) => value })).filter(Boolean);
|
|
85
|
+
if (visual.length === 0 || visual.some((node) => typeof node !== "object" || !node?.tag)) findings.push({ code: "MDFN_CERT_VISUAL_INVALID", fixture: fixture.id, message: "Visual lifecycle did not produce a structured node" });
|
|
86
|
+
else passed += 1;
|
|
87
|
+
}
|
|
88
|
+
registry.diagnose(edited);
|
|
89
|
+
passed += 1;
|
|
90
|
+
for (const migration of extension.migrations ?? []) migration.migrate(parsed.document);
|
|
91
|
+
passed += 1;
|
|
92
|
+
const manifest = extension.certification;
|
|
93
|
+
if (manifest) {
|
|
94
|
+
if (manifest.schemaVersion !== 1 || !manifest.fixtures.includes(fixture.id)) findings.push({ code: "MDFN_CERT_MANIFEST_INVALID", fixture: fixture.id, message: "Certification manifest does not cover the fixture" });
|
|
95
|
+
const hooks = { parse: extension.parseMarkdown, serialize: extension.serializeMarkdown, render: extension.render, visual: extension.visual, commands: extension.commands, keymap: extension.keymap, input: extension.inputRules, paste: extension.pasteRules, plugins: extension.plugins, diagnostics: extension.diagnostics, migrations: extension.migrations, security: extension.security };
|
|
96
|
+
for (const capability of manifest.capabilities) if (!hooks[capability]) findings.push({ code: "MDFN_CERT_CAPABILITY_MISSING", fixture: fixture.id, message: `Manifest capability ${capability} has no hook` });
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
findings.push({ code: "MDFN_CERT_LIFECYCLE_EXCEPTION", fixture: fixture.id, message: error instanceof Error ? error.message : String(error) });
|
|
100
|
+
}
|
|
101
|
+
return { ok: findings.length === 0, passed, failed: findings.length, findings };
|
|
102
|
+
}
|
|
103
|
+
function canonicalHtml(value) {
|
|
104
|
+
const fragment = (0, import_parse5.parseFragment)(value);
|
|
105
|
+
const visit = (node, preserveWhitespace = false) => {
|
|
106
|
+
if (!node || typeof node !== "object") return;
|
|
107
|
+
const candidate = node;
|
|
108
|
+
const preserve = preserveWhitespace || candidate.nodeName === "pre" || candidate.nodeName === "code";
|
|
109
|
+
if (candidate.nodeName === "#text" && !preserve && typeof candidate.value === "string") candidate.value = candidate.value.replace(/\s+/g, " ");
|
|
110
|
+
if (candidate.attrs) candidate.attrs = candidate.attrs.filter((attr) => !["target", "rel", "loading", "decoding", "href", "src"].includes(attr.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
111
|
+
if (candidate.childNodes) {
|
|
112
|
+
candidate.childNodes = candidate.childNodes.filter((child) => {
|
|
113
|
+
const text = child;
|
|
114
|
+
return preserve || text.nodeName !== "#text" || Boolean(text.value?.trim());
|
|
115
|
+
});
|
|
116
|
+
for (let index = 0; index < candidate.childNodes.length; index += 1) {
|
|
117
|
+
const child = candidate.childNodes[index];
|
|
118
|
+
const previous = candidate.childNodes[index - 1];
|
|
119
|
+
const next = candidate.childNodes[index + 1];
|
|
120
|
+
if (child.nodeName === "#text" && typeof child.value === "string") {
|
|
121
|
+
if (previous?.nodeName === "br" || previous?.nodeName === "ul" || previous?.nodeName === "ol" || previous?.nodeName === "blockquote" || previous?.nodeName === "pre" || /^h[1-6]$/.test(previous?.nodeName ?? "")) child.value = child.value.trimStart();
|
|
122
|
+
if (next?.nodeName === "ul" || next?.nodeName === "ol" || next?.nodeName === "blockquote" || next?.nodeName === "pre" || /^h[1-6]$/.test(next?.nodeName ?? "")) child.value = child.value.trimEnd();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
candidate.childNodes.forEach((child) => visit(child, preserve));
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
visit(fragment);
|
|
129
|
+
return (0, import_parse5.serialize)(fragment).trim();
|
|
130
|
+
}
|
|
131
|
+
function runSemanticCorpus(fixtures) {
|
|
132
|
+
const findings = [];
|
|
133
|
+
let passed = 0;
|
|
134
|
+
for (const fixture of fixtures) {
|
|
135
|
+
try {
|
|
136
|
+
const parsed = (0, import_markdown.parseMarkdown)(fixture.source, fixture.options);
|
|
137
|
+
const neutralizeUrls = (node) => node.type === "link" ? { ...node, attrs: { ...node.attrs, url: "#" }, content: node.content?.map(neutralizeUrls) } : node.type === "image" ? { ...node, attrs: { ...node.attrs, url: "/image" } } : { ...node, content: node.content?.map(neutralizeUrls) };
|
|
138
|
+
const document = neutralizeUrls(parsed.document);
|
|
139
|
+
const actual = (0, import_render.renderHtml)(document, { extensions: fixture.options?.extensions, rawHtml: { enabled: true, sanitize: (raw) => raw }, links: { externalTarget: null, externalRel: "" } }).html;
|
|
140
|
+
if (canonicalHtml(actual) !== canonicalHtml(fixture.expectedHtml)) findings.push({ code: "MDFN_CONFORMANCE_SEMANTIC_MISMATCH", fixture: fixture.id, message: `Expected ${canonicalHtml(fixture.expectedHtml)} but received ${canonicalHtml(actual)}` });
|
|
141
|
+
else passed += 1;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
findings.push({ code: "MDFN_CONFORMANCE_SEMANTIC_EXCEPTION", fixture: fixture.id, message: error instanceof Error ? error.message : String(error) });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { ok: findings.length === 0, passed, failed: findings.length, findings };
|
|
147
|
+
}
|
|
148
|
+
var authoritativeGfmCorpus = Object.freeze([
|
|
149
|
+
{ id: "gfm-autolink-www", source: "www.commonmark.org\n", expectedHtml: '<p><a href="http://www.commonmark.org">www.commonmark.org</a></p>', options: { dialect: "gfm" } },
|
|
150
|
+
{ id: "gfm-autolink-email", source: "foo.bar.baz@google.com\n", expectedHtml: '<p><a href="mailto:foo.bar.baz@google.com">foo.bar.baz@google.com</a></p>', options: { dialect: "gfm" } },
|
|
151
|
+
{ id: "gfm-strikethrough", source: "~~Hi~~ Hello, world!\n", expectedHtml: "<p><del>Hi</del> Hello, world!</p>", options: { dialect: "gfm" } },
|
|
152
|
+
{ id: "gfm-table", source: "| foo | bar |\n| --- | --- |\n| baz | bim |\n", expectedHtml: "<table><thead><tr><th>foo</th><th>bar</th></tr></thead><tbody><tr><td>baz</td><td>bim</td></tr></tbody></table>", options: { dialect: "gfm" } },
|
|
153
|
+
{ id: "gfm-table-align", source: "| abc | defghi |\n:-: | -----------:\nbar | baz\n", expectedHtml: '<table><thead><tr><th align="center">abc</th><th align="right">defghi</th></tr></thead><tbody><tr><td align="center">bar</td><td align="right">baz</td></tr></tbody></table>', options: { dialect: "gfm" } },
|
|
154
|
+
{ id: "gfm-task-list", source: "- [x] foo\n- [ ] bar\n", expectedHtml: '<ul class="contains-task-list"><li class="task-list-item"><input type="checkbox" disabled checked> foo</li><li class="task-list-item"><input type="checkbox" disabled> bar</li></ul>', options: { dialect: "gfm" } }
|
|
155
|
+
]);
|
|
156
|
+
function runSecurityCorpus(document, policy) {
|
|
157
|
+
const findings = [];
|
|
158
|
+
try {
|
|
159
|
+
const result = (0, import_render.renderHtml)(document, policy);
|
|
160
|
+
if (/<script\b|\son[a-z]+\s*=|javascript:/i.test(result.html)) {
|
|
161
|
+
findings.push({ code: "MDFN_CONFORMANCE_UNSAFE_RENDER", fixture: "security", message: "Unsafe executable output was rendered" });
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
findings.push({ code: "MDFN_CONFORMANCE_RENDER_EXCEPTION", fixture: "security", message: error instanceof Error ? error.message : String(error) });
|
|
165
|
+
}
|
|
166
|
+
return { ok: findings.length === 0, passed: findings.length === 0 ? 1 : 0, failed: findings.length, findings };
|
|
167
|
+
}
|
|
168
|
+
function traceState(framework, fixture, states) {
|
|
169
|
+
const final = states.at(-1);
|
|
170
|
+
if (!final) throw new Error("MDFN_TRACE_STATE_MISSING");
|
|
171
|
+
return {
|
|
172
|
+
schemaVersion: 1,
|
|
173
|
+
framework,
|
|
174
|
+
fixture,
|
|
175
|
+
steps: states.map((state) => ({ name: `version:${state.version}`, state: { markdown: state.markdown, dirty: state.dirty, selection: state.selection, diagnostics: state.diagnostics.map((entry) => entry.code) } })),
|
|
176
|
+
finalMarkdown: final.markdown,
|
|
177
|
+
finalSelection: final.selection,
|
|
178
|
+
diagnostics: final.diagnostics.map((entry) => entry.code),
|
|
179
|
+
cleanup: { subscriptions: 0, mounted: false }
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function compareSemanticTraces(reference, traces) {
|
|
183
|
+
const findings = [];
|
|
184
|
+
for (const trace of traces) {
|
|
185
|
+
const comparable = (value) => JSON.stringify({ steps: value.steps, finalMarkdown: value.finalMarkdown, finalSelection: value.finalSelection, diagnostics: value.diagnostics, cleanup: value.cleanup });
|
|
186
|
+
if (comparable(trace) !== comparable(reference)) findings.push({ code: "MDFN_ADAPTER_TRACE_DIVERGED", fixture: trace.fixture, message: `${trace.framework} diverged from vanilla semantics` });
|
|
187
|
+
}
|
|
188
|
+
return { ok: findings.length === 0, passed: traces.length - findings.length, failed: findings.length, findings };
|
|
189
|
+
}
|
|
190
|
+
var representativeCorpus = Object.freeze([
|
|
191
|
+
{ id: "commonmark", source: "# Heading\n\nParagraph with **bold** and *emphasis*.\n" },
|
|
192
|
+
{ id: "gfm-table", source: "| A | B |\n| - | - |\n| 1 | 2 |\n" },
|
|
193
|
+
{ id: "unicode-crlf", source: "# \u65E5\u672C\u8A9E\r\n\r\n\u0645\u0631\u062D\u0628\u0627 \u{1F44B}\r\n" },
|
|
194
|
+
{ id: "lone-cr", source: "# Heading\r\rParagraph.\r" },
|
|
195
|
+
{ id: "opaque-html", source: '<custom-element unsafe="no">value</custom-element>\n' },
|
|
196
|
+
{ id: "malformed", source: "```ts\nconst value = 1;\n" }
|
|
197
|
+
]);
|
|
198
|
+
function deterministicFuzzCorpus(seed = 1296320078, cases = 100) {
|
|
199
|
+
let value = seed >>> 0;
|
|
200
|
+
const next = () => {
|
|
201
|
+
value = Math.imul(value, 1664525) + 1013904223 >>> 0;
|
|
202
|
+
return value;
|
|
203
|
+
};
|
|
204
|
+
const tokens = ["plain", "**bold**", "_em_", "`code`", "[link](https://example.com)", "\u65E5\u672C\u8A9E", "\u{1F44B}", "<opaque-tag>", "\\*escaped", "~~strike~~"];
|
|
205
|
+
return Array.from({ length: Math.max(0, cases) }, (_, index) => {
|
|
206
|
+
const lines = Array.from({ length: 1 + next() % 8 }, () => {
|
|
207
|
+
const prefix = ["", "# ", "- ", "> ", "1. "][next() % 5];
|
|
208
|
+
return `${prefix}${tokens[next() % tokens.length]} ${tokens[next() % tokens.length]}`;
|
|
209
|
+
});
|
|
210
|
+
const newline = next() % 3 === 0 ? "\r\n" : "\n";
|
|
211
|
+
return { id: `fuzz-${seed}-${index}`, source: `${lines.join(newline)}${newline}` };
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
var MDFN_TESTING_VERSION = "0.1.0";
|
|
215
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
216
|
+
0 && (module.exports = {
|
|
217
|
+
MDFN_TESTING_VERSION,
|
|
218
|
+
authoritativeGfmCorpus,
|
|
219
|
+
certifyExtension,
|
|
220
|
+
compareSemanticTraces,
|
|
221
|
+
deterministicFuzzCorpus,
|
|
222
|
+
representativeCorpus,
|
|
223
|
+
runPreservationCorpus,
|
|
224
|
+
runSecurityCorpus,
|
|
225
|
+
runSemanticCorpus,
|
|
226
|
+
traceState
|
|
227
|
+
});
|
|
228
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { resolveExtensions, type EditorState, type MdfnDocument, type MdfnExtension, type MdfnJsonValue, type MdfnNode } from \"@mdfn/core\";\nimport { parseMarkdown, serializeMarkdown, type MarkdownOptions } from \"@mdfn/markdown\";\nimport { renderHtml, type RenderPolicy } from \"@mdfn/render\";\nimport { parseFragment, serialize } from \"parse5\";\n\nexport interface PreservationFixture {\n readonly id: string;\n readonly source: string;\n readonly options?: MarkdownOptions;\n}\n\nexport interface ConformanceFinding {\n readonly code: string;\n readonly fixture: string;\n readonly message: string;\n}\n\nexport interface ConformanceResult {\n readonly ok: boolean;\n readonly passed: number;\n readonly failed: number;\n readonly findings: readonly ConformanceFinding[];\n}\n\nexport function runPreservationCorpus(fixtures: readonly PreservationFixture[]): ConformanceResult {\n const findings: ConformanceFinding[] = [];\n let passed = 0;\n for (const fixture of fixtures) {\n try {\n const parsed = parseMarkdown(fixture.source, fixture.options);\n const serialized = serializeMarkdown({ document: parsed.document, originalSource: fixture.source, options: fixture.options });\n if (serialized.markdown !== fixture.source) {\n findings.push({ code: \"MDFN_CONFORMANCE_NO_EDIT_CHANGED\", fixture: fixture.id, message: \"No-edit round trip changed source bytes\" });\n } else if (!serialized.preservation.exactUntouched) {\n findings.push({ code: \"MDFN_CONFORMANCE_EXACT_NOT_REPORTED\", fixture: fixture.id, message: \"Exact preservation was not reported\" });\n } else {\n passed += 1;\n }\n } catch (error) {\n findings.push({ code: \"MDFN_CONFORMANCE_EXCEPTION\", fixture: fixture.id, message: error instanceof Error ? error.message : String(error) });\n }\n }\n return { ok: findings.length === 0, passed, failed: findings.length, findings };\n}\n\nexport function certifyExtension(extension: MdfnExtension, fixture: PreservationFixture): ConformanceResult {\n const configured = fixture.options?.extensions;\n const existing = configured ? (\"schemaHash\" in configured ? configured.extensions : configured) : [];\n const options = { ...(fixture.options ?? {}), extensions: [...existing, extension] };\n const findings = [...runPreservationCorpus([{ ...fixture, options }]).findings];\n let passed = findings.length === 0 ? 1 : 0;\n try {\n const registry = resolveExtensions(options.extensions);\n const parsed = parseMarkdown(fixture.source, options);\n const owns = (node: MdfnNode): boolean => extension.serializeMarkdown?.({ node }) != null || (node.content?.some(owns) ?? false);\n const dirty = (node: MdfnNode): MdfnNode => owns(node)\n ? { ...node, source: node.source ? { ...node.source, dirty: true } : undefined, content: node.content?.map(dirty) }\n : { ...node, content: node.content?.map(dirty) };\n const edited = dirty(parsed.document) as MdfnDocument;\n const ownedNodes: MdfnNode[] = [];\n const collectOwned = (node: MdfnNode): void => {\n if (extension.serializeMarkdown?.({ node }) != null) ownedNodes.push(node);\n node.content?.forEach(collectOwned);\n };\n collectOwned(parsed.document);\n if (ownedNodes.length === 0) findings.push({ code: \"MDFN_CERT_FIXTURE_NOT_OWNED\", fixture: fixture.id, message: \"Extension did not claim any syntax in its certification fixture\" });\n const serialized = serializeMarkdown({ document: edited, originalSource: fixture.source, options });\n if (!serialized.markdown) findings.push({ code: \"MDFN_CERT_EDITED_SERIALIZATION_EMPTY\", fixture: fixture.id, message: \"Edited serialization produced no source\" });\n else passed += 1;\n renderHtml(edited, { extensions: registry, rawHtml: { enabled: false } });\n passed += 1;\n if (extension.visual) {\n const visual = ownedNodes.map((node) => extension.visual?.({ node, escape: (value) => value })).filter(Boolean);\n if (visual.length === 0 || visual.some((node) => typeof node !== \"object\" || !node?.tag)) findings.push({ code: \"MDFN_CERT_VISUAL_INVALID\", fixture: fixture.id, message: \"Visual lifecycle did not produce a structured node\" });\n else passed += 1;\n }\n registry.diagnose(edited);\n passed += 1;\n for (const migration of extension.migrations ?? []) migration.migrate(parsed.document);\n passed += 1;\n const manifest = extension.certification;\n if (manifest) {\n if (manifest.schemaVersion !== 1 || !manifest.fixtures.includes(fixture.id)) findings.push({ code: \"MDFN_CERT_MANIFEST_INVALID\", fixture: fixture.id, message: \"Certification manifest does not cover the fixture\" });\n const hooks: Record<string, unknown> = { parse: extension.parseMarkdown, serialize: extension.serializeMarkdown, render: extension.render, visual: extension.visual, commands: extension.commands, keymap: extension.keymap, input: extension.inputRules, paste: extension.pasteRules, plugins: extension.plugins, diagnostics: extension.diagnostics, migrations: extension.migrations, security: extension.security };\n for (const capability of manifest.capabilities) if (!hooks[capability]) findings.push({ code: \"MDFN_CERT_CAPABILITY_MISSING\", fixture: fixture.id, message: `Manifest capability ${capability} has no hook` });\n }\n } catch (error) {\n findings.push({ code: \"MDFN_CERT_LIFECYCLE_EXCEPTION\", fixture: fixture.id, message: error instanceof Error ? error.message : String(error) });\n }\n return { ok: findings.length === 0, passed, failed: findings.length, findings };\n}\n\nexport interface SemanticFixture extends PreservationFixture { readonly expectedHtml: string; }\n\nfunction canonicalHtml(value: string): string {\n const fragment = parseFragment(value);\n const visit = (node: unknown, preserveWhitespace = false): void => {\n if (!node || typeof node !== \"object\") return;\n const candidate = node as { nodeName?: string; value?: string; attrs?: Array<{ name: string; value: string }>; childNodes?: unknown[] };\n const preserve = preserveWhitespace || candidate.nodeName === \"pre\" || candidate.nodeName === \"code\";\n if (candidate.nodeName === \"#text\" && !preserve && typeof candidate.value === \"string\") candidate.value = candidate.value.replace(/\\s+/g, \" \");\n if (candidate.attrs) candidate.attrs = candidate.attrs.filter((attr) => ![\"target\", \"rel\", \"loading\", \"decoding\", \"href\", \"src\"].includes(attr.name)).sort((a, b) => a.name.localeCompare(b.name));\n if (candidate.childNodes) {\n candidate.childNodes = candidate.childNodes.filter((child) => {\n const text = child as { nodeName?: string; value?: string };\n return preserve || text.nodeName !== \"#text\" || Boolean(text.value?.trim());\n });\n for (let index = 0; index < candidate.childNodes.length; index += 1) {\n const child = candidate.childNodes[index] as { nodeName?: string; value?: string };\n const previous = candidate.childNodes[index - 1] as { nodeName?: string } | undefined;\n const next = candidate.childNodes[index + 1] as { nodeName?: string } | undefined;\n if (child.nodeName === \"#text\" && typeof child.value === \"string\") {\n if (previous?.nodeName === \"br\" || previous?.nodeName === \"ul\" || previous?.nodeName === \"ol\" || previous?.nodeName === \"blockquote\" || previous?.nodeName === \"pre\" || /^h[1-6]$/.test(previous?.nodeName ?? \"\")) child.value = child.value.trimStart();\n if (next?.nodeName === \"ul\" || next?.nodeName === \"ol\" || next?.nodeName === \"blockquote\" || next?.nodeName === \"pre\" || /^h[1-6]$/.test(next?.nodeName ?? \"\")) child.value = child.value.trimEnd();\n }\n }\n candidate.childNodes.forEach((child) => visit(child, preserve));\n }\n };\n visit(fragment);\n return serialize(fragment).trim();\n}\n\n/** Compare MDFN's projected semantics with authoritative expected HTML. */\nexport function runSemanticCorpus(fixtures: readonly SemanticFixture[]): ConformanceResult {\n const findings: ConformanceFinding[] = [];\n let passed = 0;\n for (const fixture of fixtures) {\n try {\n const parsed = parseMarkdown(fixture.source, fixture.options);\n const neutralizeUrls = (node: MdfnNode): MdfnNode => node.type === \"link\"\n ? { ...node, attrs: { ...node.attrs, url: \"#\" }, content: node.content?.map(neutralizeUrls) }\n : node.type === \"image\" ? { ...node, attrs: { ...node.attrs, url: \"/image\" } }\n : { ...node, content: node.content?.map(neutralizeUrls) };\n const document = neutralizeUrls(parsed.document) as MdfnDocument;\n const actual = renderHtml(document, { extensions: fixture.options?.extensions, rawHtml: { enabled: true, sanitize: (raw) => raw }, links: { externalTarget: null, externalRel: \"\" } }).html;\n if (canonicalHtml(actual) !== canonicalHtml(fixture.expectedHtml)) findings.push({ code: \"MDFN_CONFORMANCE_SEMANTIC_MISMATCH\", fixture: fixture.id, message: `Expected ${canonicalHtml(fixture.expectedHtml)} but received ${canonicalHtml(actual)}` });\n else passed += 1;\n } catch (error) {\n findings.push({ code: \"MDFN_CONFORMANCE_SEMANTIC_EXCEPTION\", fixture: fixture.id, message: error instanceof Error ? error.message : String(error) });\n }\n }\n return { ok: findings.length === 0, passed, failed: findings.length, findings };\n}\n\n/** Representative examples copied from the normative GFM 0.29 test blocks. */\nexport const authoritativeGfmCorpus: readonly SemanticFixture[] = Object.freeze([\n { id: \"gfm-autolink-www\", source: \"www.commonmark.org\\n\", expectedHtml: '<p><a href=\"http://www.commonmark.org\">www.commonmark.org</a></p>', options: { dialect: \"gfm\" } },\n { id: \"gfm-autolink-email\", source: \"foo.bar.baz@google.com\\n\", expectedHtml: '<p><a href=\"mailto:foo.bar.baz@google.com\">foo.bar.baz@google.com</a></p>', options: { dialect: \"gfm\" } },\n { id: \"gfm-strikethrough\", source: \"~~Hi~~ Hello, world!\\n\", expectedHtml: \"<p><del>Hi</del> Hello, world!</p>\", options: { dialect: \"gfm\" } },\n { id: \"gfm-table\", source: \"| foo | bar |\\n| --- | --- |\\n| baz | bim |\\n\", expectedHtml: \"<table><thead><tr><th>foo</th><th>bar</th></tr></thead><tbody><tr><td>baz</td><td>bim</td></tr></tbody></table>\", options: { dialect: \"gfm\" } },\n { id: \"gfm-table-align\", source: \"| abc | defghi |\\n:-: | -----------:\\nbar | baz\\n\", expectedHtml: '<table><thead><tr><th align=\"center\">abc</th><th align=\"right\">defghi</th></tr></thead><tbody><tr><td align=\"center\">bar</td><td align=\"right\">baz</td></tr></tbody></table>', options: { dialect: \"gfm\" } },\n { id: \"gfm-task-list\", source: \"- [x] foo\\n- [ ] bar\\n\", expectedHtml: '<ul class=\"contains-task-list\"><li class=\"task-list-item\"><input type=\"checkbox\" disabled checked> foo</li><li class=\"task-list-item\"><input type=\"checkbox\" disabled> bar</li></ul>', options: { dialect: \"gfm\" } },\n]);\n\nexport function runSecurityCorpus(document: MdfnDocument, policy?: RenderPolicy): ConformanceResult {\n const findings: ConformanceFinding[] = [];\n try {\n const result = renderHtml(document, policy);\n if (/<script\\b|\\son[a-z]+\\s*=|javascript:/i.test(result.html)) {\n findings.push({ code: \"MDFN_CONFORMANCE_UNSAFE_RENDER\", fixture: \"security\", message: \"Unsafe executable output was rendered\" });\n }\n } catch (error) {\n findings.push({ code: \"MDFN_CONFORMANCE_RENDER_EXCEPTION\", fixture: \"security\", message: error instanceof Error ? error.message : String(error) });\n }\n return { ok: findings.length === 0, passed: findings.length === 0 ? 1 : 0, failed: findings.length, findings };\n}\n\nexport interface SemanticTraceStep {\n readonly name: string;\n readonly state: MdfnJsonValue;\n}\n\nexport interface AdapterSemanticTrace {\n readonly schemaVersion: 1;\n readonly framework: \"vanilla\" | \"react\" | \"svelte\" | \"solid\";\n readonly fixture: string;\n readonly steps: readonly SemanticTraceStep[];\n readonly finalMarkdown: string;\n readonly finalSelection: MdfnJsonValue;\n readonly diagnostics: readonly string[];\n readonly cleanup: { readonly subscriptions: number; readonly mounted: boolean };\n}\n\nexport function traceState(framework: AdapterSemanticTrace[\"framework\"], fixture: string, states: readonly EditorState[]): AdapterSemanticTrace {\n const final = states.at(-1);\n if (!final) throw new Error(\"MDFN_TRACE_STATE_MISSING\");\n return {\n schemaVersion: 1,\n framework,\n fixture,\n steps: states.map((state) => ({ name: `version:${state.version}`, state: { markdown: state.markdown, dirty: state.dirty, selection: state.selection as MdfnJsonValue, diagnostics: state.diagnostics.map((entry) => entry.code) } })),\n finalMarkdown: final.markdown,\n finalSelection: final.selection as MdfnJsonValue,\n diagnostics: final.diagnostics.map((entry) => entry.code),\n cleanup: { subscriptions: 0, mounted: false },\n };\n}\n\nexport function compareSemanticTraces(reference: AdapterSemanticTrace, traces: readonly AdapterSemanticTrace[]): ConformanceResult {\n const findings: ConformanceFinding[] = [];\n for (const trace of traces) {\n const comparable = (value: AdapterSemanticTrace) => JSON.stringify({ steps: value.steps, finalMarkdown: value.finalMarkdown, finalSelection: value.finalSelection, diagnostics: value.diagnostics, cleanup: value.cleanup });\n if (comparable(trace) !== comparable(reference)) findings.push({ code: \"MDFN_ADAPTER_TRACE_DIVERGED\", fixture: trace.fixture, message: `${trace.framework} diverged from vanilla semantics` });\n }\n return { ok: findings.length === 0, passed: traces.length - findings.length, failed: findings.length, findings };\n}\n\nexport const representativeCorpus: readonly PreservationFixture[] = Object.freeze([\n { id: \"commonmark\", source: \"# Heading\\n\\nParagraph with **bold** and *emphasis*.\\n\" },\n { id: \"gfm-table\", source: \"| A | B |\\n| - | - |\\n| 1 | 2 |\\n\" },\n { id: \"unicode-crlf\", source: \"# 日本語\\r\\n\\r\\nمرحبا 👋\\r\\n\" },\n { id: \"lone-cr\", source: \"# Heading\\r\\rParagraph.\\r\" },\n { id: \"opaque-html\", source: \"<custom-element unsafe=\\\"no\\\">value</custom-element>\\n\" },\n { id: \"malformed\", source: \"```ts\\nconst value = 1;\\n\" },\n]);\n\nexport function deterministicFuzzCorpus(seed = 0x4d44464e, cases = 100): readonly PreservationFixture[] {\n let value = seed >>> 0;\n const next = (): number => {\n value = (Math.imul(value, 1664525) + 1013904223) >>> 0;\n return value;\n };\n const tokens = [\"plain\", \"**bold**\", \"_em_\", \"`code`\", \"[link](https://example.com)\", \"日本語\", \"👋\", \"<opaque-tag>\", \"\\\\*escaped\", \"~~strike~~\"];\n return Array.from({ length: Math.max(0, cases) }, (_, index) => {\n const lines = Array.from({ length: 1 + (next() % 8) }, () => {\n const prefix = [\"\", \"# \", \"- \", \"> \", \"1. \"][next() % 5];\n return `${prefix}${tokens[next() % tokens.length]} ${tokens[next() % tokens.length]}`;\n });\n const newline = next() % 3 === 0 ? \"\\r\\n\" : \"\\n\";\n return { id: `fuzz-${seed}-${index}`, source: `${lines.join(newline)}${newline}` };\n });\n}\n\nexport const MDFN_TESTING_VERSION = \"0.1.0\" as const;\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { MdfnJsonValue, MdfnExtension, MdfnDocument, EditorState } from '@mdfn/core';
|
|
2
|
+
import { MarkdownOptions } from '@mdfn/markdown';
|
|
3
|
+
import { RenderPolicy } from '@mdfn/render';
|
|
4
|
+
|
|
5
|
+
interface PreservationFixture {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly source: string;
|
|
8
|
+
readonly options?: MarkdownOptions;
|
|
9
|
+
}
|
|
10
|
+
interface ConformanceFinding {
|
|
11
|
+
readonly code: string;
|
|
12
|
+
readonly fixture: string;
|
|
13
|
+
readonly message: string;
|
|
14
|
+
}
|
|
15
|
+
interface ConformanceResult {
|
|
16
|
+
readonly ok: boolean;
|
|
17
|
+
readonly passed: number;
|
|
18
|
+
readonly failed: number;
|
|
19
|
+
readonly findings: readonly ConformanceFinding[];
|
|
20
|
+
}
|
|
21
|
+
declare function runPreservationCorpus(fixtures: readonly PreservationFixture[]): ConformanceResult;
|
|
22
|
+
declare function certifyExtension(extension: MdfnExtension, fixture: PreservationFixture): ConformanceResult;
|
|
23
|
+
interface SemanticFixture extends PreservationFixture {
|
|
24
|
+
readonly expectedHtml: string;
|
|
25
|
+
}
|
|
26
|
+
/** Compare MDFN's projected semantics with authoritative expected HTML. */
|
|
27
|
+
declare function runSemanticCorpus(fixtures: readonly SemanticFixture[]): ConformanceResult;
|
|
28
|
+
/** Representative examples copied from the normative GFM 0.29 test blocks. */
|
|
29
|
+
declare const authoritativeGfmCorpus: readonly SemanticFixture[];
|
|
30
|
+
declare function runSecurityCorpus(document: MdfnDocument, policy?: RenderPolicy): ConformanceResult;
|
|
31
|
+
interface SemanticTraceStep {
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly state: MdfnJsonValue;
|
|
34
|
+
}
|
|
35
|
+
interface AdapterSemanticTrace {
|
|
36
|
+
readonly schemaVersion: 1;
|
|
37
|
+
readonly framework: "vanilla" | "react" | "svelte" | "solid";
|
|
38
|
+
readonly fixture: string;
|
|
39
|
+
readonly steps: readonly SemanticTraceStep[];
|
|
40
|
+
readonly finalMarkdown: string;
|
|
41
|
+
readonly finalSelection: MdfnJsonValue;
|
|
42
|
+
readonly diagnostics: readonly string[];
|
|
43
|
+
readonly cleanup: {
|
|
44
|
+
readonly subscriptions: number;
|
|
45
|
+
readonly mounted: boolean;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
declare function traceState(framework: AdapterSemanticTrace["framework"], fixture: string, states: readonly EditorState[]): AdapterSemanticTrace;
|
|
49
|
+
declare function compareSemanticTraces(reference: AdapterSemanticTrace, traces: readonly AdapterSemanticTrace[]): ConformanceResult;
|
|
50
|
+
declare const representativeCorpus: readonly PreservationFixture[];
|
|
51
|
+
declare function deterministicFuzzCorpus(seed?: number, cases?: number): readonly PreservationFixture[];
|
|
52
|
+
declare const MDFN_TESTING_VERSION: "0.1.0";
|
|
53
|
+
|
|
54
|
+
export { type AdapterSemanticTrace, type ConformanceFinding, type ConformanceResult, MDFN_TESTING_VERSION, type PreservationFixture, type SemanticFixture, type SemanticTraceStep, authoritativeGfmCorpus, certifyExtension, compareSemanticTraces, deterministicFuzzCorpus, representativeCorpus, runPreservationCorpus, runSecurityCorpus, runSemanticCorpus, traceState };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { MdfnJsonValue, MdfnExtension, MdfnDocument, EditorState } from '@mdfn/core';
|
|
2
|
+
import { MarkdownOptions } from '@mdfn/markdown';
|
|
3
|
+
import { RenderPolicy } from '@mdfn/render';
|
|
4
|
+
|
|
5
|
+
interface PreservationFixture {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly source: string;
|
|
8
|
+
readonly options?: MarkdownOptions;
|
|
9
|
+
}
|
|
10
|
+
interface ConformanceFinding {
|
|
11
|
+
readonly code: string;
|
|
12
|
+
readonly fixture: string;
|
|
13
|
+
readonly message: string;
|
|
14
|
+
}
|
|
15
|
+
interface ConformanceResult {
|
|
16
|
+
readonly ok: boolean;
|
|
17
|
+
readonly passed: number;
|
|
18
|
+
readonly failed: number;
|
|
19
|
+
readonly findings: readonly ConformanceFinding[];
|
|
20
|
+
}
|
|
21
|
+
declare function runPreservationCorpus(fixtures: readonly PreservationFixture[]): ConformanceResult;
|
|
22
|
+
declare function certifyExtension(extension: MdfnExtension, fixture: PreservationFixture): ConformanceResult;
|
|
23
|
+
interface SemanticFixture extends PreservationFixture {
|
|
24
|
+
readonly expectedHtml: string;
|
|
25
|
+
}
|
|
26
|
+
/** Compare MDFN's projected semantics with authoritative expected HTML. */
|
|
27
|
+
declare function runSemanticCorpus(fixtures: readonly SemanticFixture[]): ConformanceResult;
|
|
28
|
+
/** Representative examples copied from the normative GFM 0.29 test blocks. */
|
|
29
|
+
declare const authoritativeGfmCorpus: readonly SemanticFixture[];
|
|
30
|
+
declare function runSecurityCorpus(document: MdfnDocument, policy?: RenderPolicy): ConformanceResult;
|
|
31
|
+
interface SemanticTraceStep {
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly state: MdfnJsonValue;
|
|
34
|
+
}
|
|
35
|
+
interface AdapterSemanticTrace {
|
|
36
|
+
readonly schemaVersion: 1;
|
|
37
|
+
readonly framework: "vanilla" | "react" | "svelte" | "solid";
|
|
38
|
+
readonly fixture: string;
|
|
39
|
+
readonly steps: readonly SemanticTraceStep[];
|
|
40
|
+
readonly finalMarkdown: string;
|
|
41
|
+
readonly finalSelection: MdfnJsonValue;
|
|
42
|
+
readonly diagnostics: readonly string[];
|
|
43
|
+
readonly cleanup: {
|
|
44
|
+
readonly subscriptions: number;
|
|
45
|
+
readonly mounted: boolean;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
declare function traceState(framework: AdapterSemanticTrace["framework"], fixture: string, states: readonly EditorState[]): AdapterSemanticTrace;
|
|
49
|
+
declare function compareSemanticTraces(reference: AdapterSemanticTrace, traces: readonly AdapterSemanticTrace[]): ConformanceResult;
|
|
50
|
+
declare const representativeCorpus: readonly PreservationFixture[];
|
|
51
|
+
declare function deterministicFuzzCorpus(seed?: number, cases?: number): readonly PreservationFixture[];
|
|
52
|
+
declare const MDFN_TESTING_VERSION: "0.1.0";
|
|
53
|
+
|
|
54
|
+
export { type AdapterSemanticTrace, type ConformanceFinding, type ConformanceResult, MDFN_TESTING_VERSION, type PreservationFixture, type SemanticFixture, type SemanticTraceStep, authoritativeGfmCorpus, certifyExtension, compareSemanticTraces, deterministicFuzzCorpus, representativeCorpus, runPreservationCorpus, runSecurityCorpus, runSemanticCorpus, traceState };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { resolveExtensions } from "@mdfn/core";
|
|
3
|
+
import { parseMarkdown, serializeMarkdown } from "@mdfn/markdown";
|
|
4
|
+
import { renderHtml } from "@mdfn/render";
|
|
5
|
+
import { parseFragment, serialize } from "parse5";
|
|
6
|
+
function runPreservationCorpus(fixtures) {
|
|
7
|
+
const findings = [];
|
|
8
|
+
let passed = 0;
|
|
9
|
+
for (const fixture of fixtures) {
|
|
10
|
+
try {
|
|
11
|
+
const parsed = parseMarkdown(fixture.source, fixture.options);
|
|
12
|
+
const serialized = serializeMarkdown({ document: parsed.document, originalSource: fixture.source, options: fixture.options });
|
|
13
|
+
if (serialized.markdown !== fixture.source) {
|
|
14
|
+
findings.push({ code: "MDFN_CONFORMANCE_NO_EDIT_CHANGED", fixture: fixture.id, message: "No-edit round trip changed source bytes" });
|
|
15
|
+
} else if (!serialized.preservation.exactUntouched) {
|
|
16
|
+
findings.push({ code: "MDFN_CONFORMANCE_EXACT_NOT_REPORTED", fixture: fixture.id, message: "Exact preservation was not reported" });
|
|
17
|
+
} else {
|
|
18
|
+
passed += 1;
|
|
19
|
+
}
|
|
20
|
+
} catch (error) {
|
|
21
|
+
findings.push({ code: "MDFN_CONFORMANCE_EXCEPTION", fixture: fixture.id, message: error instanceof Error ? error.message : String(error) });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { ok: findings.length === 0, passed, failed: findings.length, findings };
|
|
25
|
+
}
|
|
26
|
+
function certifyExtension(extension, fixture) {
|
|
27
|
+
const configured = fixture.options?.extensions;
|
|
28
|
+
const existing = configured ? "schemaHash" in configured ? configured.extensions : configured : [];
|
|
29
|
+
const options = { ...fixture.options ?? {}, extensions: [...existing, extension] };
|
|
30
|
+
const findings = [...runPreservationCorpus([{ ...fixture, options }]).findings];
|
|
31
|
+
let passed = findings.length === 0 ? 1 : 0;
|
|
32
|
+
try {
|
|
33
|
+
const registry = resolveExtensions(options.extensions);
|
|
34
|
+
const parsed = parseMarkdown(fixture.source, options);
|
|
35
|
+
const owns = (node) => extension.serializeMarkdown?.({ node }) != null || (node.content?.some(owns) ?? false);
|
|
36
|
+
const dirty = (node) => owns(node) ? { ...node, source: node.source ? { ...node.source, dirty: true } : void 0, content: node.content?.map(dirty) } : { ...node, content: node.content?.map(dirty) };
|
|
37
|
+
const edited = dirty(parsed.document);
|
|
38
|
+
const ownedNodes = [];
|
|
39
|
+
const collectOwned = (node) => {
|
|
40
|
+
if (extension.serializeMarkdown?.({ node }) != null) ownedNodes.push(node);
|
|
41
|
+
node.content?.forEach(collectOwned);
|
|
42
|
+
};
|
|
43
|
+
collectOwned(parsed.document);
|
|
44
|
+
if (ownedNodes.length === 0) findings.push({ code: "MDFN_CERT_FIXTURE_NOT_OWNED", fixture: fixture.id, message: "Extension did not claim any syntax in its certification fixture" });
|
|
45
|
+
const serialized = serializeMarkdown({ document: edited, originalSource: fixture.source, options });
|
|
46
|
+
if (!serialized.markdown) findings.push({ code: "MDFN_CERT_EDITED_SERIALIZATION_EMPTY", fixture: fixture.id, message: "Edited serialization produced no source" });
|
|
47
|
+
else passed += 1;
|
|
48
|
+
renderHtml(edited, { extensions: registry, rawHtml: { enabled: false } });
|
|
49
|
+
passed += 1;
|
|
50
|
+
if (extension.visual) {
|
|
51
|
+
const visual = ownedNodes.map((node) => extension.visual?.({ node, escape: (value) => value })).filter(Boolean);
|
|
52
|
+
if (visual.length === 0 || visual.some((node) => typeof node !== "object" || !node?.tag)) findings.push({ code: "MDFN_CERT_VISUAL_INVALID", fixture: fixture.id, message: "Visual lifecycle did not produce a structured node" });
|
|
53
|
+
else passed += 1;
|
|
54
|
+
}
|
|
55
|
+
registry.diagnose(edited);
|
|
56
|
+
passed += 1;
|
|
57
|
+
for (const migration of extension.migrations ?? []) migration.migrate(parsed.document);
|
|
58
|
+
passed += 1;
|
|
59
|
+
const manifest = extension.certification;
|
|
60
|
+
if (manifest) {
|
|
61
|
+
if (manifest.schemaVersion !== 1 || !manifest.fixtures.includes(fixture.id)) findings.push({ code: "MDFN_CERT_MANIFEST_INVALID", fixture: fixture.id, message: "Certification manifest does not cover the fixture" });
|
|
62
|
+
const hooks = { parse: extension.parseMarkdown, serialize: extension.serializeMarkdown, render: extension.render, visual: extension.visual, commands: extension.commands, keymap: extension.keymap, input: extension.inputRules, paste: extension.pasteRules, plugins: extension.plugins, diagnostics: extension.diagnostics, migrations: extension.migrations, security: extension.security };
|
|
63
|
+
for (const capability of manifest.capabilities) if (!hooks[capability]) findings.push({ code: "MDFN_CERT_CAPABILITY_MISSING", fixture: fixture.id, message: `Manifest capability ${capability} has no hook` });
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
findings.push({ code: "MDFN_CERT_LIFECYCLE_EXCEPTION", fixture: fixture.id, message: error instanceof Error ? error.message : String(error) });
|
|
67
|
+
}
|
|
68
|
+
return { ok: findings.length === 0, passed, failed: findings.length, findings };
|
|
69
|
+
}
|
|
70
|
+
function canonicalHtml(value) {
|
|
71
|
+
const fragment = parseFragment(value);
|
|
72
|
+
const visit = (node, preserveWhitespace = false) => {
|
|
73
|
+
if (!node || typeof node !== "object") return;
|
|
74
|
+
const candidate = node;
|
|
75
|
+
const preserve = preserveWhitespace || candidate.nodeName === "pre" || candidate.nodeName === "code";
|
|
76
|
+
if (candidate.nodeName === "#text" && !preserve && typeof candidate.value === "string") candidate.value = candidate.value.replace(/\s+/g, " ");
|
|
77
|
+
if (candidate.attrs) candidate.attrs = candidate.attrs.filter((attr) => !["target", "rel", "loading", "decoding", "href", "src"].includes(attr.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
78
|
+
if (candidate.childNodes) {
|
|
79
|
+
candidate.childNodes = candidate.childNodes.filter((child) => {
|
|
80
|
+
const text = child;
|
|
81
|
+
return preserve || text.nodeName !== "#text" || Boolean(text.value?.trim());
|
|
82
|
+
});
|
|
83
|
+
for (let index = 0; index < candidate.childNodes.length; index += 1) {
|
|
84
|
+
const child = candidate.childNodes[index];
|
|
85
|
+
const previous = candidate.childNodes[index - 1];
|
|
86
|
+
const next = candidate.childNodes[index + 1];
|
|
87
|
+
if (child.nodeName === "#text" && typeof child.value === "string") {
|
|
88
|
+
if (previous?.nodeName === "br" || previous?.nodeName === "ul" || previous?.nodeName === "ol" || previous?.nodeName === "blockquote" || previous?.nodeName === "pre" || /^h[1-6]$/.test(previous?.nodeName ?? "")) child.value = child.value.trimStart();
|
|
89
|
+
if (next?.nodeName === "ul" || next?.nodeName === "ol" || next?.nodeName === "blockquote" || next?.nodeName === "pre" || /^h[1-6]$/.test(next?.nodeName ?? "")) child.value = child.value.trimEnd();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
candidate.childNodes.forEach((child) => visit(child, preserve));
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
visit(fragment);
|
|
96
|
+
return serialize(fragment).trim();
|
|
97
|
+
}
|
|
98
|
+
function runSemanticCorpus(fixtures) {
|
|
99
|
+
const findings = [];
|
|
100
|
+
let passed = 0;
|
|
101
|
+
for (const fixture of fixtures) {
|
|
102
|
+
try {
|
|
103
|
+
const parsed = parseMarkdown(fixture.source, fixture.options);
|
|
104
|
+
const neutralizeUrls = (node) => node.type === "link" ? { ...node, attrs: { ...node.attrs, url: "#" }, content: node.content?.map(neutralizeUrls) } : node.type === "image" ? { ...node, attrs: { ...node.attrs, url: "/image" } } : { ...node, content: node.content?.map(neutralizeUrls) };
|
|
105
|
+
const document = neutralizeUrls(parsed.document);
|
|
106
|
+
const actual = renderHtml(document, { extensions: fixture.options?.extensions, rawHtml: { enabled: true, sanitize: (raw) => raw }, links: { externalTarget: null, externalRel: "" } }).html;
|
|
107
|
+
if (canonicalHtml(actual) !== canonicalHtml(fixture.expectedHtml)) findings.push({ code: "MDFN_CONFORMANCE_SEMANTIC_MISMATCH", fixture: fixture.id, message: `Expected ${canonicalHtml(fixture.expectedHtml)} but received ${canonicalHtml(actual)}` });
|
|
108
|
+
else passed += 1;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
findings.push({ code: "MDFN_CONFORMANCE_SEMANTIC_EXCEPTION", fixture: fixture.id, message: error instanceof Error ? error.message : String(error) });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { ok: findings.length === 0, passed, failed: findings.length, findings };
|
|
114
|
+
}
|
|
115
|
+
var authoritativeGfmCorpus = Object.freeze([
|
|
116
|
+
{ id: "gfm-autolink-www", source: "www.commonmark.org\n", expectedHtml: '<p><a href="http://www.commonmark.org">www.commonmark.org</a></p>', options: { dialect: "gfm" } },
|
|
117
|
+
{ id: "gfm-autolink-email", source: "foo.bar.baz@google.com\n", expectedHtml: '<p><a href="mailto:foo.bar.baz@google.com">foo.bar.baz@google.com</a></p>', options: { dialect: "gfm" } },
|
|
118
|
+
{ id: "gfm-strikethrough", source: "~~Hi~~ Hello, world!\n", expectedHtml: "<p><del>Hi</del> Hello, world!</p>", options: { dialect: "gfm" } },
|
|
119
|
+
{ id: "gfm-table", source: "| foo | bar |\n| --- | --- |\n| baz | bim |\n", expectedHtml: "<table><thead><tr><th>foo</th><th>bar</th></tr></thead><tbody><tr><td>baz</td><td>bim</td></tr></tbody></table>", options: { dialect: "gfm" } },
|
|
120
|
+
{ id: "gfm-table-align", source: "| abc | defghi |\n:-: | -----------:\nbar | baz\n", expectedHtml: '<table><thead><tr><th align="center">abc</th><th align="right">defghi</th></tr></thead><tbody><tr><td align="center">bar</td><td align="right">baz</td></tr></tbody></table>', options: { dialect: "gfm" } },
|
|
121
|
+
{ id: "gfm-task-list", source: "- [x] foo\n- [ ] bar\n", expectedHtml: '<ul class="contains-task-list"><li class="task-list-item"><input type="checkbox" disabled checked> foo</li><li class="task-list-item"><input type="checkbox" disabled> bar</li></ul>', options: { dialect: "gfm" } }
|
|
122
|
+
]);
|
|
123
|
+
function runSecurityCorpus(document, policy) {
|
|
124
|
+
const findings = [];
|
|
125
|
+
try {
|
|
126
|
+
const result = renderHtml(document, policy);
|
|
127
|
+
if (/<script\b|\son[a-z]+\s*=|javascript:/i.test(result.html)) {
|
|
128
|
+
findings.push({ code: "MDFN_CONFORMANCE_UNSAFE_RENDER", fixture: "security", message: "Unsafe executable output was rendered" });
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
findings.push({ code: "MDFN_CONFORMANCE_RENDER_EXCEPTION", fixture: "security", message: error instanceof Error ? error.message : String(error) });
|
|
132
|
+
}
|
|
133
|
+
return { ok: findings.length === 0, passed: findings.length === 0 ? 1 : 0, failed: findings.length, findings };
|
|
134
|
+
}
|
|
135
|
+
function traceState(framework, fixture, states) {
|
|
136
|
+
const final = states.at(-1);
|
|
137
|
+
if (!final) throw new Error("MDFN_TRACE_STATE_MISSING");
|
|
138
|
+
return {
|
|
139
|
+
schemaVersion: 1,
|
|
140
|
+
framework,
|
|
141
|
+
fixture,
|
|
142
|
+
steps: states.map((state) => ({ name: `version:${state.version}`, state: { markdown: state.markdown, dirty: state.dirty, selection: state.selection, diagnostics: state.diagnostics.map((entry) => entry.code) } })),
|
|
143
|
+
finalMarkdown: final.markdown,
|
|
144
|
+
finalSelection: final.selection,
|
|
145
|
+
diagnostics: final.diagnostics.map((entry) => entry.code),
|
|
146
|
+
cleanup: { subscriptions: 0, mounted: false }
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function compareSemanticTraces(reference, traces) {
|
|
150
|
+
const findings = [];
|
|
151
|
+
for (const trace of traces) {
|
|
152
|
+
const comparable = (value) => JSON.stringify({ steps: value.steps, finalMarkdown: value.finalMarkdown, finalSelection: value.finalSelection, diagnostics: value.diagnostics, cleanup: value.cleanup });
|
|
153
|
+
if (comparable(trace) !== comparable(reference)) findings.push({ code: "MDFN_ADAPTER_TRACE_DIVERGED", fixture: trace.fixture, message: `${trace.framework} diverged from vanilla semantics` });
|
|
154
|
+
}
|
|
155
|
+
return { ok: findings.length === 0, passed: traces.length - findings.length, failed: findings.length, findings };
|
|
156
|
+
}
|
|
157
|
+
var representativeCorpus = Object.freeze([
|
|
158
|
+
{ id: "commonmark", source: "# Heading\n\nParagraph with **bold** and *emphasis*.\n" },
|
|
159
|
+
{ id: "gfm-table", source: "| A | B |\n| - | - |\n| 1 | 2 |\n" },
|
|
160
|
+
{ id: "unicode-crlf", source: "# \u65E5\u672C\u8A9E\r\n\r\n\u0645\u0631\u062D\u0628\u0627 \u{1F44B}\r\n" },
|
|
161
|
+
{ id: "lone-cr", source: "# Heading\r\rParagraph.\r" },
|
|
162
|
+
{ id: "opaque-html", source: '<custom-element unsafe="no">value</custom-element>\n' },
|
|
163
|
+
{ id: "malformed", source: "```ts\nconst value = 1;\n" }
|
|
164
|
+
]);
|
|
165
|
+
function deterministicFuzzCorpus(seed = 1296320078, cases = 100) {
|
|
166
|
+
let value = seed >>> 0;
|
|
167
|
+
const next = () => {
|
|
168
|
+
value = Math.imul(value, 1664525) + 1013904223 >>> 0;
|
|
169
|
+
return value;
|
|
170
|
+
};
|
|
171
|
+
const tokens = ["plain", "**bold**", "_em_", "`code`", "[link](https://example.com)", "\u65E5\u672C\u8A9E", "\u{1F44B}", "<opaque-tag>", "\\*escaped", "~~strike~~"];
|
|
172
|
+
return Array.from({ length: Math.max(0, cases) }, (_, index) => {
|
|
173
|
+
const lines = Array.from({ length: 1 + next() % 8 }, () => {
|
|
174
|
+
const prefix = ["", "# ", "- ", "> ", "1. "][next() % 5];
|
|
175
|
+
return `${prefix}${tokens[next() % tokens.length]} ${tokens[next() % tokens.length]}`;
|
|
176
|
+
});
|
|
177
|
+
const newline = next() % 3 === 0 ? "\r\n" : "\n";
|
|
178
|
+
return { id: `fuzz-${seed}-${index}`, source: `${lines.join(newline)}${newline}` };
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
var MDFN_TESTING_VERSION = "0.1.0";
|
|
182
|
+
export {
|
|
183
|
+
MDFN_TESTING_VERSION,
|
|
184
|
+
authoritativeGfmCorpus,
|
|
185
|
+
certifyExtension,
|
|
186
|
+
compareSemanticTraces,
|
|
187
|
+
deterministicFuzzCorpus,
|
|
188
|
+
representativeCorpus,
|
|
189
|
+
runPreservationCorpus,
|
|
190
|
+
runSecurityCorpus,
|
|
191
|
+
runSemanticCorpus,
|
|
192
|
+
traceState
|
|
193
|
+
};
|
|
194
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { resolveExtensions, type EditorState, type MdfnDocument, type MdfnExtension, type MdfnJsonValue, type MdfnNode } from \"@mdfn/core\";\nimport { parseMarkdown, serializeMarkdown, type MarkdownOptions } from \"@mdfn/markdown\";\nimport { renderHtml, type RenderPolicy } from \"@mdfn/render\";\nimport { parseFragment, serialize } from \"parse5\";\n\nexport interface PreservationFixture {\n readonly id: string;\n readonly source: string;\n readonly options?: MarkdownOptions;\n}\n\nexport interface ConformanceFinding {\n readonly code: string;\n readonly fixture: string;\n readonly message: string;\n}\n\nexport interface ConformanceResult {\n readonly ok: boolean;\n readonly passed: number;\n readonly failed: number;\n readonly findings: readonly ConformanceFinding[];\n}\n\nexport function runPreservationCorpus(fixtures: readonly PreservationFixture[]): ConformanceResult {\n const findings: ConformanceFinding[] = [];\n let passed = 0;\n for (const fixture of fixtures) {\n try {\n const parsed = parseMarkdown(fixture.source, fixture.options);\n const serialized = serializeMarkdown({ document: parsed.document, originalSource: fixture.source, options: fixture.options });\n if (serialized.markdown !== fixture.source) {\n findings.push({ code: \"MDFN_CONFORMANCE_NO_EDIT_CHANGED\", fixture: fixture.id, message: \"No-edit round trip changed source bytes\" });\n } else if (!serialized.preservation.exactUntouched) {\n findings.push({ code: \"MDFN_CONFORMANCE_EXACT_NOT_REPORTED\", fixture: fixture.id, message: \"Exact preservation was not reported\" });\n } else {\n passed += 1;\n }\n } catch (error) {\n findings.push({ code: \"MDFN_CONFORMANCE_EXCEPTION\", fixture: fixture.id, message: error instanceof Error ? error.message : String(error) });\n }\n }\n return { ok: findings.length === 0, passed, failed: findings.length, findings };\n}\n\nexport function certifyExtension(extension: MdfnExtension, fixture: PreservationFixture): ConformanceResult {\n const configured = fixture.options?.extensions;\n const existing = configured ? (\"schemaHash\" in configured ? configured.extensions : configured) : [];\n const options = { ...(fixture.options ?? {}), extensions: [...existing, extension] };\n const findings = [...runPreservationCorpus([{ ...fixture, options }]).findings];\n let passed = findings.length === 0 ? 1 : 0;\n try {\n const registry = resolveExtensions(options.extensions);\n const parsed = parseMarkdown(fixture.source, options);\n const owns = (node: MdfnNode): boolean => extension.serializeMarkdown?.({ node }) != null || (node.content?.some(owns) ?? false);\n const dirty = (node: MdfnNode): MdfnNode => owns(node)\n ? { ...node, source: node.source ? { ...node.source, dirty: true } : undefined, content: node.content?.map(dirty) }\n : { ...node, content: node.content?.map(dirty) };\n const edited = dirty(parsed.document) as MdfnDocument;\n const ownedNodes: MdfnNode[] = [];\n const collectOwned = (node: MdfnNode): void => {\n if (extension.serializeMarkdown?.({ node }) != null) ownedNodes.push(node);\n node.content?.forEach(collectOwned);\n };\n collectOwned(parsed.document);\n if (ownedNodes.length === 0) findings.push({ code: \"MDFN_CERT_FIXTURE_NOT_OWNED\", fixture: fixture.id, message: \"Extension did not claim any syntax in its certification fixture\" });\n const serialized = serializeMarkdown({ document: edited, originalSource: fixture.source, options });\n if (!serialized.markdown) findings.push({ code: \"MDFN_CERT_EDITED_SERIALIZATION_EMPTY\", fixture: fixture.id, message: \"Edited serialization produced no source\" });\n else passed += 1;\n renderHtml(edited, { extensions: registry, rawHtml: { enabled: false } });\n passed += 1;\n if (extension.visual) {\n const visual = ownedNodes.map((node) => extension.visual?.({ node, escape: (value) => value })).filter(Boolean);\n if (visual.length === 0 || visual.some((node) => typeof node !== \"object\" || !node?.tag)) findings.push({ code: \"MDFN_CERT_VISUAL_INVALID\", fixture: fixture.id, message: \"Visual lifecycle did not produce a structured node\" });\n else passed += 1;\n }\n registry.diagnose(edited);\n passed += 1;\n for (const migration of extension.migrations ?? []) migration.migrate(parsed.document);\n passed += 1;\n const manifest = extension.certification;\n if (manifest) {\n if (manifest.schemaVersion !== 1 || !manifest.fixtures.includes(fixture.id)) findings.push({ code: \"MDFN_CERT_MANIFEST_INVALID\", fixture: fixture.id, message: \"Certification manifest does not cover the fixture\" });\n const hooks: Record<string, unknown> = { parse: extension.parseMarkdown, serialize: extension.serializeMarkdown, render: extension.render, visual: extension.visual, commands: extension.commands, keymap: extension.keymap, input: extension.inputRules, paste: extension.pasteRules, plugins: extension.plugins, diagnostics: extension.diagnostics, migrations: extension.migrations, security: extension.security };\n for (const capability of manifest.capabilities) if (!hooks[capability]) findings.push({ code: \"MDFN_CERT_CAPABILITY_MISSING\", fixture: fixture.id, message: `Manifest capability ${capability} has no hook` });\n }\n } catch (error) {\n findings.push({ code: \"MDFN_CERT_LIFECYCLE_EXCEPTION\", fixture: fixture.id, message: error instanceof Error ? error.message : String(error) });\n }\n return { ok: findings.length === 0, passed, failed: findings.length, findings };\n}\n\nexport interface SemanticFixture extends PreservationFixture { readonly expectedHtml: string; }\n\nfunction canonicalHtml(value: string): string {\n const fragment = parseFragment(value);\n const visit = (node: unknown, preserveWhitespace = false): void => {\n if (!node || typeof node !== \"object\") return;\n const candidate = node as { nodeName?: string; value?: string; attrs?: Array<{ name: string; value: string }>; childNodes?: unknown[] };\n const preserve = preserveWhitespace || candidate.nodeName === \"pre\" || candidate.nodeName === \"code\";\n if (candidate.nodeName === \"#text\" && !preserve && typeof candidate.value === \"string\") candidate.value = candidate.value.replace(/\\s+/g, \" \");\n if (candidate.attrs) candidate.attrs = candidate.attrs.filter((attr) => ![\"target\", \"rel\", \"loading\", \"decoding\", \"href\", \"src\"].includes(attr.name)).sort((a, b) => a.name.localeCompare(b.name));\n if (candidate.childNodes) {\n candidate.childNodes = candidate.childNodes.filter((child) => {\n const text = child as { nodeName?: string; value?: string };\n return preserve || text.nodeName !== \"#text\" || Boolean(text.value?.trim());\n });\n for (let index = 0; index < candidate.childNodes.length; index += 1) {\n const child = candidate.childNodes[index] as { nodeName?: string; value?: string };\n const previous = candidate.childNodes[index - 1] as { nodeName?: string } | undefined;\n const next = candidate.childNodes[index + 1] as { nodeName?: string } | undefined;\n if (child.nodeName === \"#text\" && typeof child.value === \"string\") {\n if (previous?.nodeName === \"br\" || previous?.nodeName === \"ul\" || previous?.nodeName === \"ol\" || previous?.nodeName === \"blockquote\" || previous?.nodeName === \"pre\" || /^h[1-6]$/.test(previous?.nodeName ?? \"\")) child.value = child.value.trimStart();\n if (next?.nodeName === \"ul\" || next?.nodeName === \"ol\" || next?.nodeName === \"blockquote\" || next?.nodeName === \"pre\" || /^h[1-6]$/.test(next?.nodeName ?? \"\")) child.value = child.value.trimEnd();\n }\n }\n candidate.childNodes.forEach((child) => visit(child, preserve));\n }\n };\n visit(fragment);\n return serialize(fragment).trim();\n}\n\n/** Compare MDFN's projected semantics with authoritative expected HTML. */\nexport function runSemanticCorpus(fixtures: readonly SemanticFixture[]): ConformanceResult {\n const findings: ConformanceFinding[] = [];\n let passed = 0;\n for (const fixture of fixtures) {\n try {\n const parsed = parseMarkdown(fixture.source, fixture.options);\n const neutralizeUrls = (node: MdfnNode): MdfnNode => node.type === \"link\"\n ? { ...node, attrs: { ...node.attrs, url: \"#\" }, content: node.content?.map(neutralizeUrls) }\n : node.type === \"image\" ? { ...node, attrs: { ...node.attrs, url: \"/image\" } }\n : { ...node, content: node.content?.map(neutralizeUrls) };\n const document = neutralizeUrls(parsed.document) as MdfnDocument;\n const actual = renderHtml(document, { extensions: fixture.options?.extensions, rawHtml: { enabled: true, sanitize: (raw) => raw }, links: { externalTarget: null, externalRel: \"\" } }).html;\n if (canonicalHtml(actual) !== canonicalHtml(fixture.expectedHtml)) findings.push({ code: \"MDFN_CONFORMANCE_SEMANTIC_MISMATCH\", fixture: fixture.id, message: `Expected ${canonicalHtml(fixture.expectedHtml)} but received ${canonicalHtml(actual)}` });\n else passed += 1;\n } catch (error) {\n findings.push({ code: \"MDFN_CONFORMANCE_SEMANTIC_EXCEPTION\", fixture: fixture.id, message: error instanceof Error ? error.message : String(error) });\n }\n }\n return { ok: findings.length === 0, passed, failed: findings.length, findings };\n}\n\n/** Representative examples copied from the normative GFM 0.29 test blocks. */\nexport const authoritativeGfmCorpus: readonly SemanticFixture[] = Object.freeze([\n { id: \"gfm-autolink-www\", source: \"www.commonmark.org\\n\", expectedHtml: '<p><a href=\"http://www.commonmark.org\">www.commonmark.org</a></p>', options: { dialect: \"gfm\" } },\n { id: \"gfm-autolink-email\", source: \"foo.bar.baz@google.com\\n\", expectedHtml: '<p><a href=\"mailto:foo.bar.baz@google.com\">foo.bar.baz@google.com</a></p>', options: { dialect: \"gfm\" } },\n { id: \"gfm-strikethrough\", source: \"~~Hi~~ Hello, world!\\n\", expectedHtml: \"<p><del>Hi</del> Hello, world!</p>\", options: { dialect: \"gfm\" } },\n { id: \"gfm-table\", source: \"| foo | bar |\\n| --- | --- |\\n| baz | bim |\\n\", expectedHtml: \"<table><thead><tr><th>foo</th><th>bar</th></tr></thead><tbody><tr><td>baz</td><td>bim</td></tr></tbody></table>\", options: { dialect: \"gfm\" } },\n { id: \"gfm-table-align\", source: \"| abc | defghi |\\n:-: | -----------:\\nbar | baz\\n\", expectedHtml: '<table><thead><tr><th align=\"center\">abc</th><th align=\"right\">defghi</th></tr></thead><tbody><tr><td align=\"center\">bar</td><td align=\"right\">baz</td></tr></tbody></table>', options: { dialect: \"gfm\" } },\n { id: \"gfm-task-list\", source: \"- [x] foo\\n- [ ] bar\\n\", expectedHtml: '<ul class=\"contains-task-list\"><li class=\"task-list-item\"><input type=\"checkbox\" disabled checked> foo</li><li class=\"task-list-item\"><input type=\"checkbox\" disabled> bar</li></ul>', options: { dialect: \"gfm\" } },\n]);\n\nexport function runSecurityCorpus(document: MdfnDocument, policy?: RenderPolicy): ConformanceResult {\n const findings: ConformanceFinding[] = [];\n try {\n const result = renderHtml(document, policy);\n if (/<script\\b|\\son[a-z]+\\s*=|javascript:/i.test(result.html)) {\n findings.push({ code: \"MDFN_CONFORMANCE_UNSAFE_RENDER\", fixture: \"security\", message: \"Unsafe executable output was rendered\" });\n }\n } catch (error) {\n findings.push({ code: \"MDFN_CONFORMANCE_RENDER_EXCEPTION\", fixture: \"security\", message: error instanceof Error ? error.message : String(error) });\n }\n return { ok: findings.length === 0, passed: findings.length === 0 ? 1 : 0, failed: findings.length, findings };\n}\n\nexport interface SemanticTraceStep {\n readonly name: string;\n readonly state: MdfnJsonValue;\n}\n\nexport interface AdapterSemanticTrace {\n readonly schemaVersion: 1;\n readonly framework: \"vanilla\" | \"react\" | \"svelte\" | \"solid\";\n readonly fixture: string;\n readonly steps: readonly SemanticTraceStep[];\n readonly finalMarkdown: string;\n readonly finalSelection: MdfnJsonValue;\n readonly diagnostics: readonly string[];\n readonly cleanup: { readonly subscriptions: number; readonly mounted: boolean };\n}\n\nexport function traceState(framework: AdapterSemanticTrace[\"framework\"], fixture: string, states: readonly EditorState[]): AdapterSemanticTrace {\n const final = states.at(-1);\n if (!final) throw new Error(\"MDFN_TRACE_STATE_MISSING\");\n return {\n schemaVersion: 1,\n framework,\n fixture,\n steps: states.map((state) => ({ name: `version:${state.version}`, state: { markdown: state.markdown, dirty: state.dirty, selection: state.selection as MdfnJsonValue, diagnostics: state.diagnostics.map((entry) => entry.code) } })),\n finalMarkdown: final.markdown,\n finalSelection: final.selection as MdfnJsonValue,\n diagnostics: final.diagnostics.map((entry) => entry.code),\n cleanup: { subscriptions: 0, mounted: false },\n };\n}\n\nexport function compareSemanticTraces(reference: AdapterSemanticTrace, traces: readonly AdapterSemanticTrace[]): ConformanceResult {\n const findings: ConformanceFinding[] = [];\n for (const trace of traces) {\n const comparable = (value: AdapterSemanticTrace) => JSON.stringify({ steps: value.steps, finalMarkdown: value.finalMarkdown, finalSelection: value.finalSelection, diagnostics: value.diagnostics, cleanup: value.cleanup });\n if (comparable(trace) !== comparable(reference)) findings.push({ code: \"MDFN_ADAPTER_TRACE_DIVERGED\", fixture: trace.fixture, message: `${trace.framework} diverged from vanilla semantics` });\n }\n return { ok: findings.length === 0, passed: traces.length - findings.length, failed: findings.length, findings };\n}\n\nexport const representativeCorpus: readonly PreservationFixture[] = Object.freeze([\n { id: \"commonmark\", source: \"# Heading\\n\\nParagraph with **bold** and *emphasis*.\\n\" },\n { id: \"gfm-table\", source: \"| A | B |\\n| - | - |\\n| 1 | 2 |\\n\" },\n { id: \"unicode-crlf\", source: \"# 日本語\\r\\n\\r\\nمرحبا 👋\\r\\n\" },\n { id: \"lone-cr\", source: \"# Heading\\r\\rParagraph.\\r\" },\n { id: \"opaque-html\", source: \"<custom-element unsafe=\\\"no\\\">value</custom-element>\\n\" },\n { id: \"malformed\", source: \"```ts\\nconst value = 1;\\n\" },\n]);\n\nexport function deterministicFuzzCorpus(seed = 0x4d44464e, cases = 100): readonly PreservationFixture[] {\n let value = seed >>> 0;\n const next = (): number => {\n value = (Math.imul(value, 1664525) + 1013904223) >>> 0;\n return value;\n };\n const tokens = [\"plain\", \"**bold**\", \"_em_\", \"`code`\", \"[link](https://example.com)\", \"日本語\", \"👋\", \"<opaque-tag>\", \"\\\\*escaped\", \"~~strike~~\"];\n return Array.from({ length: Math.max(0, cases) }, (_, index) => {\n const lines = Array.from({ length: 1 + (next() % 8) }, () => {\n const prefix = [\"\", \"# \", \"- \", \"> \", \"1. \"][next() % 5];\n return `${prefix}${tokens[next() % tokens.length]} ${tokens[next() % tokens.length]}`;\n });\n const newline = next() % 3 === 0 ? \"\\r\\n\" : \"\\n\";\n return { id: `fuzz-${seed}-${index}`, source: `${lines.join(newline)}${newline}` };\n });\n}\n\nexport const MDFN_TESTING_VERSION = \"0.1.0\" as const;\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mdfn/testing",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Corpus, semantic traces, fixtures, and extension certification for mdfn",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" } },
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
14
|
+
"lint": "tsc --noEmit -p tsconfig.json",
|
|
15
|
+
"prepack": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": { "@mdfn/core": "0.1.0", "@mdfn/markdown": "0.1.0", "@mdfn/render": "0.1.0", "parse5": "^7.3.0" },
|
|
18
|
+
"devDependencies": { "commonmark-spec": "0.31.2", "tsup": "^8.0.0", "typescript": "^5.3.0", "vitest": "3.2.4" },
|
|
19
|
+
"files": ["dist", "README.md"],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"engines": { "node": ">=20 <25" },
|
|
22
|
+
"repository": { "type": "git", "url": "git+https://github.com/21nCo/super-functions.git", "directory": "mdfn/testing" },
|
|
23
|
+
"publishConfig": { "access": "public" },
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"mdfn": { "status": "stable-candidate", "layer": "testing", "schemaVersion": 1 }
|
|
26
|
+
}
|