thenavisapp
Version:
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
164 lines (133 loc) • 5.27 kB
text/typescript
import type { ParserOptions, TreeAdapter, TreeAdapterTypeMap, ParserError } from 'parse5';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as assert from 'node:assert';
import { serializeToDatFileFormat } from './serialize-to-dat-file-format.js';
import { generateTestsForEachTreeAdapter } from './common.js';
import { parseDatFile, type DatFile } from './parse-dat-file.js';
export interface TreeConstructionTestData<T extends TreeAdapterTypeMap> extends DatFile<T> {
idx: number;
setName: string;
dirName: string;
}
export function loadTreeConstructionTestData<T extends TreeAdapterTypeMap>(
dataDir: URL,
treeAdapter: TreeAdapter<T>,
): TreeConstructionTestData<T>[] {
const tests: TreeConstructionTestData<T>[] = [];
const dataDirPath = dataDir.pathname;
const testSetFileNames = fs.readdirSync(dataDir);
const dirName = path.basename(dataDirPath);
for (const fileName of testSetFileNames) {
if (path.extname(fileName) !== '.dat') {
continue;
}
const filePath = path.join(dataDirPath, fileName);
const testSet = fs.readFileSync(filePath, 'utf8');
const setName = fileName.replace('.dat', '');
for (const [idx, test] of parseDatFile(testSet, treeAdapter).entries()) {
tests.push({
...test,
idx,
setName,
dirName,
});
}
}
return tests;
}
function prettyPrintParserAssertionArgs(actual: string, expected: string, chunks?: string[]): string {
let msg = '\nExpected:\n';
msg += '-----------------\n';
msg += `${expected}\n`;
msg += '\nActual:\n';
msg += '-----------------\n';
msg += `${actual}\n`;
if (chunks) {
msg += 'Chunks:\n';
msg += JSON.stringify(chunks);
}
return msg;
}
interface ParseMethodOptions<T extends TreeAdapterTypeMap> extends ParserOptions<T> {
treeAdapter: TreeAdapter<T>;
}
interface ParseResult<T extends TreeAdapterTypeMap> {
node: T['node'];
chunks?: string[];
}
type ParseMethod<T extends TreeAdapterTypeMap> = (
input: TreeConstructionTestData<T>,
options: ParseMethodOptions<T>,
) => ParseResult<T> | Promise<ParseResult<T>>;
function createParsingTest<T extends TreeAdapterTypeMap>(
test: TreeConstructionTestData<T>,
treeAdapter: TreeAdapter<T>,
parse: ParseMethod<T>,
{ withoutErrors, expectError }: { withoutErrors?: boolean; expectError?: boolean } = {},
): () => Promise<void> {
return async (): Promise<void> => {
const errs: string[] = [];
const opts = {
scriptingEnabled: test.scriptingEnabled,
treeAdapter,
onParseError: (err: ParserError): void => {
let errStr = `(${err.startLine}:${err.startCol}`;
// NOTE: use ranges for token errors
if (err.startLine !== err.endLine || err.startCol !== err.endCol) {
errStr += `-${err.endLine}:${err.endCol}`;
}
errStr += `) ${err.code}`;
errs.push(errStr);
},
};
const { node, chunks } = await parse(test, opts);
const actual = serializeToDatFileFormat(node, opts.treeAdapter);
const msg = prettyPrintParserAssertionArgs(actual, test.expected, chunks);
let sawError = false;
try {
assert.ok(actual === test.expected, msg);
if (!withoutErrors) {
assert.deepEqual(errs.sort(), test.expectedErrors.sort());
}
} catch (error) {
if (expectError) {
return;
}
sawError = true;
throw error;
}
if (!sawError && expectError) {
throw new Error(`Expected error but none was thrown`);
}
};
}
// TODO: Stop using the fork here.
const treePath = new URL('../data/html5lib-tests-fork/tree-construction', import.meta.url);
export function generateParsingTests(
name: string,
prefix: string,
{
withoutErrors,
expectErrors: expectError = [],
suitePath = treePath,
}: { withoutErrors?: boolean; expectErrors?: string[]; suitePath?: URL },
parse: ParseMethod<TreeAdapterTypeMap>,
): void {
generateTestsForEachTreeAdapter(name, (treeAdapter) => {
const errorsToExpect = new Set(expectError);
for (const test of loadTreeConstructionTestData(suitePath, treeAdapter)) {
const expectError = errorsToExpect.delete(`${test.idx}.${test.setName}`);
it(
`${prefix}(${test.dirName}) - ${test.idx}.${test.setName} - \`${test.input}\` (line ${test.lineNum})`,
createParsingTest<TreeAdapterTypeMap>(test, treeAdapter, parse, {
withoutErrors,
expectError,
}),
);
}
if (errorsToExpect.size > 0) {
throw new Error(`Expected errors were not found: ${[...errorsToExpect].join(', ')}`);
}
});
}