@markedjs/testutils 17.0.1-1 → 17.0.1-2
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/lib/get-tests.d.ts +19 -0
- package/lib/get-tests.js +28 -0
- package/lib/helpers.d.ts +1 -0
- package/lib/helpers.js +6 -0
- package/lib/html-differ.d.ts +18 -0
- package/lib/html-differ.js +57 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +5 -0
- package/lib/load-tests.d.ts +2 -0
- package/lib/load-tests.js +78 -0
- package/lib/output-table.d.ts +7 -0
- package/lib/output-table.js +28 -0
- package/lib/run-tests.d.ts +25 -0
- package/lib/run-tests.js +100 -0
- package/lib/types.d.ts +19 -0
- package/lib/types.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Tests } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Get tests from a directory or file
|
|
4
|
+
* @param dirs Can be a string or an array of strings
|
|
5
|
+
* @returns The return type will match the input, a tests object or an array of tests objects
|
|
6
|
+
*/
|
|
7
|
+
export declare function getTests(dir: string[]): Promise<Tests[]>;
|
|
8
|
+
export declare function getTests(dir: string): Promise<Tests>;
|
|
9
|
+
/**
|
|
10
|
+
* Get all marked tests
|
|
11
|
+
* @returns All marked spec tests
|
|
12
|
+
*/
|
|
13
|
+
export declare function getAllMarkedSpecTests(): Promise<{
|
|
14
|
+
CommonMark: Tests;
|
|
15
|
+
GFM: Tests;
|
|
16
|
+
New: Tests;
|
|
17
|
+
Original: Tests;
|
|
18
|
+
ReDOS: Tests;
|
|
19
|
+
}>;
|
package/lib/get-tests.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { loadTests } from "./load-tests.js";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
export async function getTests(dirs) {
|
|
4
|
+
if (Array.isArray(dirs)) {
|
|
5
|
+
return await Promise.all(dirs.map((dir) => loadTests(dir)));
|
|
6
|
+
}
|
|
7
|
+
return await loadTests(dirs);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get all marked tests
|
|
11
|
+
* @returns All marked spec tests
|
|
12
|
+
*/
|
|
13
|
+
export async function getAllMarkedSpecTests() {
|
|
14
|
+
const tests = await getTests([
|
|
15
|
+
resolve("./node_modules/marked-repo/test/specs/commonmark"),
|
|
16
|
+
resolve("./node_modules/marked-repo/test/specs/gfm"),
|
|
17
|
+
resolve("./node_modules/marked-repo/test/specs/new"),
|
|
18
|
+
resolve("./node_modules/marked-repo/test/specs/original"),
|
|
19
|
+
resolve("./node_modules/marked-repo/test/specs/redos"),
|
|
20
|
+
]);
|
|
21
|
+
return {
|
|
22
|
+
CommonMark: tests[0],
|
|
23
|
+
GFM: tests[1],
|
|
24
|
+
New: tests[2],
|
|
25
|
+
Original: tests[3],
|
|
26
|
+
ReDOS: tests[4],
|
|
27
|
+
};
|
|
28
|
+
}
|
package/lib/helpers.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolvePath(p: string): string;
|
package/lib/helpers.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if html will display the same
|
|
3
|
+
* @param actual The actual HTML
|
|
4
|
+
* @param expected The expected HTML
|
|
5
|
+
* @returns HTML is the same
|
|
6
|
+
*/
|
|
7
|
+
export declare function htmlIsEqual(actual: string, expected: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Get the first difference between actual and expected HTML
|
|
10
|
+
* @param actual The actual HTML
|
|
11
|
+
* @param expected The expected HTML
|
|
12
|
+
* @param padding The number of characters to show around the first difference
|
|
13
|
+
* @returns An object with the characters around the index of the first difference in the expected and actual strings
|
|
14
|
+
*/
|
|
15
|
+
export declare function firstDiff(actual: string, expected: string, padding?: number): {
|
|
16
|
+
actual: string;
|
|
17
|
+
expected: string;
|
|
18
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { HtmlDiffer } from "@markedjs/html-differ";
|
|
2
|
+
const htmlDiffer = new HtmlDiffer({
|
|
3
|
+
ignoreSelfClosingSlash: true,
|
|
4
|
+
ignoreComments: false,
|
|
5
|
+
});
|
|
6
|
+
/**
|
|
7
|
+
* Check if html will display the same
|
|
8
|
+
* @param actual The actual HTML
|
|
9
|
+
* @param expected The expected HTML
|
|
10
|
+
* @returns HTML is the same
|
|
11
|
+
*/
|
|
12
|
+
export function htmlIsEqual(actual, expected) {
|
|
13
|
+
return htmlDiffer.isEqual(actual, expected);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get the first difference between actual and expected HTML
|
|
17
|
+
* @param actual The actual HTML
|
|
18
|
+
* @param expected The expected HTML
|
|
19
|
+
* @param padding The number of characters to show around the first difference
|
|
20
|
+
* @returns An object with the characters around the index of the first difference in the expected and actual strings
|
|
21
|
+
*/
|
|
22
|
+
export function firstDiff(actual, expected, padding = 30) {
|
|
23
|
+
const diffHtml = htmlDiffer.diffHtml(actual, expected);
|
|
24
|
+
const result = diffHtml.reduce((obj, diff) => {
|
|
25
|
+
if (diff.added) {
|
|
26
|
+
if (obj.firstIndex === null) {
|
|
27
|
+
obj.firstIndex = obj.expected.length;
|
|
28
|
+
}
|
|
29
|
+
obj.expected += diff.value;
|
|
30
|
+
}
|
|
31
|
+
else if (diff.removed) {
|
|
32
|
+
if (obj.firstIndex === null) {
|
|
33
|
+
obj.firstIndex = obj.actual.length;
|
|
34
|
+
}
|
|
35
|
+
obj.actual += diff.value;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
obj.actual += diff.value;
|
|
39
|
+
obj.expected += diff.value;
|
|
40
|
+
}
|
|
41
|
+
return obj;
|
|
42
|
+
}, {
|
|
43
|
+
firstIndex: null,
|
|
44
|
+
actual: "",
|
|
45
|
+
expected: "",
|
|
46
|
+
});
|
|
47
|
+
if (result.firstIndex === null) {
|
|
48
|
+
return {
|
|
49
|
+
actual: "",
|
|
50
|
+
expected: "",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
actual: result.actual.substring(result.firstIndex - padding, result.firstIndex + padding),
|
|
55
|
+
expected: result.expected.substring(result.firstIndex - padding, result.firstIndex + padding),
|
|
56
|
+
};
|
|
57
|
+
}
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fm from "front-matter";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
export async function loadTests(fileOrDir) {
|
|
7
|
+
const isFile = fs.statSync(fileOrDir).isFile();
|
|
8
|
+
const files = isFile ? [path.basename(fileOrDir)] : fs.readdirSync(fileOrDir);
|
|
9
|
+
const dir = isFile ? path.dirname(fileOrDir) : fileOrDir;
|
|
10
|
+
const obj = {};
|
|
11
|
+
for (const file of files) {
|
|
12
|
+
const ext = path.extname(file);
|
|
13
|
+
const name = path.basename(file, ext);
|
|
14
|
+
const absFile = path.join(dir, file);
|
|
15
|
+
let specs = [];
|
|
16
|
+
switch (ext) {
|
|
17
|
+
case ".md": {
|
|
18
|
+
const content = fm(fs.readFileSync(absFile, "utf8"));
|
|
19
|
+
const skip = content.attributes.skip;
|
|
20
|
+
delete content.attributes.skip;
|
|
21
|
+
const only = content.attributes.only;
|
|
22
|
+
delete content.attributes.only;
|
|
23
|
+
specs.push({
|
|
24
|
+
section: name,
|
|
25
|
+
markdown: content.body,
|
|
26
|
+
html: fs.readFileSync(absFile.replace(/[^.]+$/, "html"), "utf8"),
|
|
27
|
+
options: content.attributes,
|
|
28
|
+
only,
|
|
29
|
+
skip,
|
|
30
|
+
});
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case ".js":
|
|
34
|
+
case ".mjs":
|
|
35
|
+
case ".cjs":
|
|
36
|
+
case ".json": {
|
|
37
|
+
let json;
|
|
38
|
+
try {
|
|
39
|
+
// try require first
|
|
40
|
+
json = await require(absFile);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (typeof err === "object" &&
|
|
44
|
+
err !== null &&
|
|
45
|
+
"code" in err &&
|
|
46
|
+
err.code !== "ERR_REQUIRE_ESM") {
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
// must import esm
|
|
50
|
+
json = await import(absFile);
|
|
51
|
+
}
|
|
52
|
+
specs = specs.concat(json);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
default:
|
|
56
|
+
// do nothing
|
|
57
|
+
}
|
|
58
|
+
for (let i = 0; i < specs.length; i++) {
|
|
59
|
+
const spec = specs[i];
|
|
60
|
+
if (!spec.section) {
|
|
61
|
+
spec.section = `${name}[${i}]`;
|
|
62
|
+
}
|
|
63
|
+
if (!obj[spec.section]) {
|
|
64
|
+
obj[spec.section] = {
|
|
65
|
+
total: 0,
|
|
66
|
+
pass: 0,
|
|
67
|
+
specs: [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
obj[spec.section].total++;
|
|
71
|
+
if (!spec.shouldFail) {
|
|
72
|
+
obj[spec.section].pass++;
|
|
73
|
+
}
|
|
74
|
+
obj[spec.section].specs.push(spec);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return obj;
|
|
78
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Tests } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Display a table in stdout that lists the sections and what percentage of the tests are not marked shouldFail
|
|
4
|
+
* @param title The title to display above the table
|
|
5
|
+
* @param tests The tests to display a table for
|
|
6
|
+
*/
|
|
7
|
+
export declare function outputCompletionTable(title: string, tests: Tests): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display a table in stdout that lists the sections and what percentage of the tests are not marked shouldFail
|
|
3
|
+
* @param title The title to display above the table
|
|
4
|
+
* @param tests The tests to display a table for
|
|
5
|
+
*/
|
|
6
|
+
export function outputCompletionTable(title, tests) {
|
|
7
|
+
let longestName = 0;
|
|
8
|
+
let maxTests = 0;
|
|
9
|
+
for (const section in tests) {
|
|
10
|
+
longestName = Math.max(section.length, longestName);
|
|
11
|
+
maxTests = Math.max(tests[section].total, maxTests);
|
|
12
|
+
}
|
|
13
|
+
const maxTestsLen = ("" + maxTests).length;
|
|
14
|
+
const spaces = maxTestsLen * 2 + longestName + 11;
|
|
15
|
+
console.log("-".padEnd(spaces + 4, "-"));
|
|
16
|
+
console.log(`| ${title
|
|
17
|
+
.padStart(Math.ceil((spaces + title.length) / 2))
|
|
18
|
+
.padEnd(spaces)} |`);
|
|
19
|
+
console.log(`| ${" ".padEnd(spaces)} |`);
|
|
20
|
+
for (const section in tests) {
|
|
21
|
+
console.log(`| ${section.padEnd(longestName)} ${("" + tests[section].pass).padStart(maxTestsLen)} of ${("" + tests[section].total).padStart(maxTestsLen)} ${((100 * tests[section].pass) /
|
|
22
|
+
tests[section].total)
|
|
23
|
+
.toFixed()
|
|
24
|
+
.padStart(4)}% |`);
|
|
25
|
+
}
|
|
26
|
+
console.log("-".padEnd(spaces + 4, "-"));
|
|
27
|
+
console.log();
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Marked, MarkedExtension } from "marked";
|
|
2
|
+
import { Tests } from "./types.js";
|
|
3
|
+
interface RunTestsOptions {
|
|
4
|
+
tests?: Tests | string;
|
|
5
|
+
defaultMarkedOptions?: MarkedExtension;
|
|
6
|
+
parse?: (markedown: string, options: MarkedExtension, addExtension: (marked: Marked) => void) => string | Promise<string>;
|
|
7
|
+
addExtension?: (marked: Marked) => void;
|
|
8
|
+
isEqual?: (actual: string, expected: string) => boolean;
|
|
9
|
+
diff?: (actual: string, expected: string, padding?: number) => {
|
|
10
|
+
actual: string;
|
|
11
|
+
expected: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Run spec tests
|
|
16
|
+
*/
|
|
17
|
+
export declare function runTests({ tests, defaultMarkedOptions, parse, addExtension, isEqual, diff, }?: RunTestsOptions): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Run all marked specs with an added extension and optionally output completion table
|
|
20
|
+
*/
|
|
21
|
+
export declare function runAllMarkedSpecTests({ addExtension, outputCompletionTables, }?: {
|
|
22
|
+
addExtension?: (marked: Marked) => void;
|
|
23
|
+
outputCompletionTables?: boolean;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
export {};
|
package/lib/run-tests.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { htmlIsEqual, firstDiff } from "./html-differ.js";
|
|
2
|
+
import { getTests, getAllMarkedSpecTests } from "./get-tests.js";
|
|
3
|
+
import nodeTest from "node:test";
|
|
4
|
+
import assert from "node:assert";
|
|
5
|
+
import { Marked } from "marked";
|
|
6
|
+
import { outputCompletionTable } from "./output-table.js";
|
|
7
|
+
/**
|
|
8
|
+
* Run spec tests
|
|
9
|
+
*/
|
|
10
|
+
export async function runTests({ tests = {}, defaultMarkedOptions = {}, parse = parseMarked, addExtension = () => { }, isEqual = htmlIsEqual, diff = firstDiff, } = {}) {
|
|
11
|
+
if (typeof tests === "string") {
|
|
12
|
+
tests = await getTests(tests);
|
|
13
|
+
}
|
|
14
|
+
for (const section of Object.keys(tests)) {
|
|
15
|
+
const sectionTests = tests[section];
|
|
16
|
+
const hasOnly = sectionTests.specs.some((test) => test.only);
|
|
17
|
+
await nodeTest(section, { only: hasOnly }, async (t) => {
|
|
18
|
+
for (const test of sectionTests.specs) {
|
|
19
|
+
const options = {
|
|
20
|
+
...defaultMarkedOptions,
|
|
21
|
+
...(test.options || {}),
|
|
22
|
+
};
|
|
23
|
+
const example = test.example ? " example " + test.example : "";
|
|
24
|
+
const passFail = test.shouldFail ? "fail" : "pass";
|
|
25
|
+
if (typeof options.silent === "undefined") {
|
|
26
|
+
options.silent = true;
|
|
27
|
+
}
|
|
28
|
+
await t.test(`${section} should ${passFail}${example}`, {
|
|
29
|
+
only: test.only,
|
|
30
|
+
skip: test.skip,
|
|
31
|
+
}, async () => {
|
|
32
|
+
const before = process.hrtime();
|
|
33
|
+
const parsed = await parse(test.markdown, options, addExtension);
|
|
34
|
+
const elapsed = process.hrtime(before);
|
|
35
|
+
const pass = isEqual(parsed, test.html);
|
|
36
|
+
if (test.shouldFail) {
|
|
37
|
+
assert.ok(!pass, `${test.markdown}\n------\n\nExpected: Should Fail`);
|
|
38
|
+
}
|
|
39
|
+
else if (options.renderExact) {
|
|
40
|
+
assert.strictEqual(test.html, parsed);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const testDiff = diff(parsed, test.html);
|
|
44
|
+
assert.ok(pass, `Expected: ${testDiff.expected}\n Actual: ${testDiff.actual}`);
|
|
45
|
+
}
|
|
46
|
+
if (!options.async && elapsed[0] > 0) {
|
|
47
|
+
const s = (elapsed[0] + elapsed[1] * 1e-9).toFixed(3);
|
|
48
|
+
assert.fail(`took too long: ${s}s`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Run all marked specs with an added extension and optionally output completion table
|
|
57
|
+
*/
|
|
58
|
+
export async function runAllMarkedSpecTests({ addExtension = () => { }, outputCompletionTables = true, } = {}) {
|
|
59
|
+
const specTests = await getAllMarkedSpecTests();
|
|
60
|
+
await Promise.all(Object.keys(specTests).map((title) => {
|
|
61
|
+
const tests = specTests[title];
|
|
62
|
+
switch (title) {
|
|
63
|
+
case "CommonMark":
|
|
64
|
+
if (outputCompletionTables) {
|
|
65
|
+
outputCompletionTable(title, tests);
|
|
66
|
+
}
|
|
67
|
+
return runTests({
|
|
68
|
+
tests,
|
|
69
|
+
defaultMarkedOptions: { gfm: false, pedantic: false },
|
|
70
|
+
addExtension,
|
|
71
|
+
});
|
|
72
|
+
case "GFM":
|
|
73
|
+
if (outputCompletionTables) {
|
|
74
|
+
outputCompletionTable(title, tests);
|
|
75
|
+
}
|
|
76
|
+
return runTests({
|
|
77
|
+
tests,
|
|
78
|
+
defaultMarkedOptions: { gfm: true, pedantic: false },
|
|
79
|
+
addExtension,
|
|
80
|
+
});
|
|
81
|
+
case "New":
|
|
82
|
+
return runTests({ tests, addExtension });
|
|
83
|
+
case "Original":
|
|
84
|
+
return runTests({
|
|
85
|
+
tests,
|
|
86
|
+
defaultMarkedOptions: { gfm: false, pedantic: true },
|
|
87
|
+
addExtension,
|
|
88
|
+
});
|
|
89
|
+
case "ReDOS":
|
|
90
|
+
return runTests({ tests, addExtension });
|
|
91
|
+
default:
|
|
92
|
+
throw new Error("invalid title");
|
|
93
|
+
}
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
function parseMarked(markdown, options, addExtension) {
|
|
97
|
+
const marked = new Marked(options);
|
|
98
|
+
addExtension(marked);
|
|
99
|
+
return marked.parse(markdown);
|
|
100
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { MarkedExtension } from "marked";
|
|
2
|
+
export interface Spec {
|
|
3
|
+
section: string;
|
|
4
|
+
markdown: string;
|
|
5
|
+
html: string;
|
|
6
|
+
options?: MarkedExtension & {
|
|
7
|
+
renderExact?: boolean;
|
|
8
|
+
};
|
|
9
|
+
only?: boolean;
|
|
10
|
+
skip?: boolean;
|
|
11
|
+
example?: string;
|
|
12
|
+
shouldFail?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface TestSection {
|
|
15
|
+
total: number;
|
|
16
|
+
pass: number;
|
|
17
|
+
specs: Spec[];
|
|
18
|
+
}
|
|
19
|
+
export type Tests = Record<string, TestSection>;
|
package/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|