193fb6ee3Sopenharmony_ciimport { Writable, finished as finishedCb, type Readable } from 'node:stream'; 293fb6ee3Sopenharmony_ciimport * as assert from 'node:assert'; 393fb6ee3Sopenharmony_ciimport { type TreeAdapter, type Token, defaultTreeAdapter } from 'parse5'; 493fb6ee3Sopenharmony_ciimport { adapter as htmlparser2Adapter } from 'parse5-htmlparser2-tree-adapter'; 593fb6ee3Sopenharmony_ci 693fb6ee3Sopenharmony_ci// Ensure the default tree adapter matches the expected type. 793fb6ee3Sopenharmony_ciexport const treeAdapters = { 893fb6ee3Sopenharmony_ci default: defaultTreeAdapter, 993fb6ee3Sopenharmony_ci htmlparser2: htmlparser2Adapter, 1093fb6ee3Sopenharmony_ci} as const; 1193fb6ee3Sopenharmony_ci 1293fb6ee3Sopenharmony_ciexport function addSlashes(str: string): string { 1393fb6ee3Sopenharmony_ci return str 1493fb6ee3Sopenharmony_ci .replace(/\t/g, '\\t') 1593fb6ee3Sopenharmony_ci .replace(/\n/g, '\\n') 1693fb6ee3Sopenharmony_ci .replace(/\f/g, '\\f') 1793fb6ee3Sopenharmony_ci .replace(/\r/g, '\\r') 1893fb6ee3Sopenharmony_ci .replace(/\0/g, '\\u0000'); 1993fb6ee3Sopenharmony_ci} 2093fb6ee3Sopenharmony_ci 2193fb6ee3Sopenharmony_cifunction createDiffMarker(markerPosition: number): string { 2293fb6ee3Sopenharmony_ci return '^\n'.padStart(markerPosition + 1, ' '); 2393fb6ee3Sopenharmony_ci} 2493fb6ee3Sopenharmony_ci 2593fb6ee3Sopenharmony_cifunction getRandomChunkSize(min = 1, max = 10): number { 2693fb6ee3Sopenharmony_ci return min + Math.floor(Math.random() * (max - min + 1)); 2793fb6ee3Sopenharmony_ci} 2893fb6ee3Sopenharmony_ci 2993fb6ee3Sopenharmony_ciexport function makeChunks(str: string, minSize?: number, maxSize?: number): string[] { 3093fb6ee3Sopenharmony_ci if (str.length === 0) { 3193fb6ee3Sopenharmony_ci return ['']; 3293fb6ee3Sopenharmony_ci } 3393fb6ee3Sopenharmony_ci 3493fb6ee3Sopenharmony_ci const chunks = []; 3593fb6ee3Sopenharmony_ci let start = 0; 3693fb6ee3Sopenharmony_ci 3793fb6ee3Sopenharmony_ci // NOTE: start with 1, so we avoid situation when we have just one huge chunk 3893fb6ee3Sopenharmony_ci let end = 1; 3993fb6ee3Sopenharmony_ci 4093fb6ee3Sopenharmony_ci while (start < str.length) { 4193fb6ee3Sopenharmony_ci chunks.push(str.substring(start, end)); 4293fb6ee3Sopenharmony_ci start = end; 4393fb6ee3Sopenharmony_ci end = Math.min(end + getRandomChunkSize(minSize, maxSize), str.length); 4493fb6ee3Sopenharmony_ci } 4593fb6ee3Sopenharmony_ci 4693fb6ee3Sopenharmony_ci return chunks; 4793fb6ee3Sopenharmony_ci} 4893fb6ee3Sopenharmony_ci 4993fb6ee3Sopenharmony_ciexport class WritableStreamStub extends Writable { 5093fb6ee3Sopenharmony_ci writtenData = ''; 5193fb6ee3Sopenharmony_ci 5293fb6ee3Sopenharmony_ci constructor() { 5393fb6ee3Sopenharmony_ci super({ decodeStrings: false }); 5493fb6ee3Sopenharmony_ci } 5593fb6ee3Sopenharmony_ci 5693fb6ee3Sopenharmony_ci override _write(chunk: string, _encoding: string, callback: () => void): void { 5793fb6ee3Sopenharmony_ci assert.strictEqual(typeof chunk, 'string', 'Expected output to be a string stream'); 5893fb6ee3Sopenharmony_ci this.writtenData += chunk; 5993fb6ee3Sopenharmony_ci callback(); 6093fb6ee3Sopenharmony_ci } 6193fb6ee3Sopenharmony_ci} 6293fb6ee3Sopenharmony_ci 6393fb6ee3Sopenharmony_ciexport function normalizeNewLine(str: string): string { 6493fb6ee3Sopenharmony_ci return str.replace(/\r\n/g, '\n'); 6593fb6ee3Sopenharmony_ci} 6693fb6ee3Sopenharmony_ci 6793fb6ee3Sopenharmony_ciexport function removeNewLines(str: string): string { 6893fb6ee3Sopenharmony_ci return str.replace(/\r/g, '').replace(/\n/g, ''); 6993fb6ee3Sopenharmony_ci} 7093fb6ee3Sopenharmony_ci 7193fb6ee3Sopenharmony_ciexport function writeChunkedToStream(str: string, stream: Writable): void { 7293fb6ee3Sopenharmony_ci const chunks = makeChunks(str); 7393fb6ee3Sopenharmony_ci const lastChunkIdx = chunks.length - 1; 7493fb6ee3Sopenharmony_ci 7593fb6ee3Sopenharmony_ci for (const [idx, chunk] of chunks.entries()) { 7693fb6ee3Sopenharmony_ci if (idx === lastChunkIdx) { 7793fb6ee3Sopenharmony_ci stream.end(chunk); 7893fb6ee3Sopenharmony_ci } else { 7993fb6ee3Sopenharmony_ci stream.write(chunk); 8093fb6ee3Sopenharmony_ci } 8193fb6ee3Sopenharmony_ci } 8293fb6ee3Sopenharmony_ci} 8393fb6ee3Sopenharmony_ci 8493fb6ee3Sopenharmony_ciexport function generateTestsForEachTreeAdapter(name: string, ctor: (adapter: TreeAdapter) => void): void { 8593fb6ee3Sopenharmony_ci describe(name, () => { 8693fb6ee3Sopenharmony_ci for (const adapterName of Object.keys(treeAdapters)) { 8793fb6ee3Sopenharmony_ci const adapter = treeAdapters[adapterName as keyof typeof treeAdapters] as TreeAdapter; 8893fb6ee3Sopenharmony_ci 8993fb6ee3Sopenharmony_ci describe(`Tree adapter: ${adapterName}`, () => { 9093fb6ee3Sopenharmony_ci ctor(adapter); 9193fb6ee3Sopenharmony_ci }); 9293fb6ee3Sopenharmony_ci } 9393fb6ee3Sopenharmony_ci }); 9493fb6ee3Sopenharmony_ci} 9593fb6ee3Sopenharmony_ci 9693fb6ee3Sopenharmony_ciexport function getStringDiffMsg(actual: string, expected: string): string { 9793fb6ee3Sopenharmony_ci for (let i = 0; i < expected.length; i++) { 9893fb6ee3Sopenharmony_ci if (actual[i] !== expected[i]) { 9993fb6ee3Sopenharmony_ci let diffMsg = `\nString differ at index ${i}\n`; 10093fb6ee3Sopenharmony_ci 10193fb6ee3Sopenharmony_ci const expectedStr = `Expected: ${addSlashes(expected.substring(i - 100, i + 1))}`; 10293fb6ee3Sopenharmony_ci const expectedDiffMarker = createDiffMarker(expectedStr.length); 10393fb6ee3Sopenharmony_ci 10493fb6ee3Sopenharmony_ci diffMsg += `${expectedStr}${addSlashes(expected.substring(i + 1, i + 20))}\n${expectedDiffMarker}`; 10593fb6ee3Sopenharmony_ci 10693fb6ee3Sopenharmony_ci const actualStr = `Actual: ${addSlashes(actual.substring(i - 100, i + 1))}`; 10793fb6ee3Sopenharmony_ci const actualDiffMarker = createDiffMarker(actualStr.length); 10893fb6ee3Sopenharmony_ci 10993fb6ee3Sopenharmony_ci diffMsg += `${actualStr}${addSlashes(actual.substring(i + 1, i + 20))}\n${actualDiffMarker}`; 11093fb6ee3Sopenharmony_ci 11193fb6ee3Sopenharmony_ci return diffMsg; 11293fb6ee3Sopenharmony_ci } 11393fb6ee3Sopenharmony_ci } 11493fb6ee3Sopenharmony_ci 11593fb6ee3Sopenharmony_ci return ''; 11693fb6ee3Sopenharmony_ci} 11793fb6ee3Sopenharmony_ci 11893fb6ee3Sopenharmony_ciexport function getSubstringByLineCol(lines: string[], loc: Token.Location): string { 11993fb6ee3Sopenharmony_ci lines = lines.slice(loc.startLine - 1, loc.endLine); 12093fb6ee3Sopenharmony_ci 12193fb6ee3Sopenharmony_ci const last = lines.length - 1; 12293fb6ee3Sopenharmony_ci 12393fb6ee3Sopenharmony_ci lines[last] = lines[last].substring(0, loc.endCol - 1); 12493fb6ee3Sopenharmony_ci lines[0] = lines[0].substring(loc.startCol - 1); 12593fb6ee3Sopenharmony_ci 12693fb6ee3Sopenharmony_ci return lines.join('\n'); 12793fb6ee3Sopenharmony_ci} 12893fb6ee3Sopenharmony_ci 12993fb6ee3Sopenharmony_ci// TODO [engine:node@>=16]: Replace this with `finished` from 'node:stream/promises'. 13093fb6ee3Sopenharmony_ci 13193fb6ee3Sopenharmony_ciexport function finished(stream: Writable | Readable): Promise<void> { 13293fb6ee3Sopenharmony_ci return new Promise((resolve, reject) => finishedCb(stream, (err) => (err ? reject(err) : resolve()))); 13393fb6ee3Sopenharmony_ci} 134