import test from "node:test"; import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; const server = await readFile(new URL("../src/server.js", import.meta.url), "utf8"); const html = await readFile(new URL("../public/index.html", import.meta.url), "utf8"); const app = await readFile(new URL("../public/app.js", import.meta.url), "utf8"); const ocr = await readFile(new URL("../src/ocr.js", import.meta.url), "utf8"); test("temporary query API collects without appending to history", () => { assert.match(server, /url\.pathname === "\/api\/query-once"/); const block = server.match(/if \(url\.pathname === "\/api\/query-once"[\s\S]*?return sendJson\(response, 200, \{ items \}\);\s*\}/)?.[0] || ""; assert.match(block, /collectTemporaryQueryItems\(/); assert.doesNotMatch(block, /appendCollection\(/); }); test("temporary query UI has isolated input, action, and CSV export", () => { assert.match(html, /id="temporary-query-panel"/); assert.match(html, /id="temporary-file-input"[^>]*type="file"/); assert.match(html, /id="temporary-query-text"/); assert.match(html, /id="temporary-query-button"[^>]*>一键查询<\/button>/); assert.match(html, /id="temporary-export-button"[^>]*>导出临时 CSV<\/button>/); assert.match(app, /temporaryQueryButton\s*=\s*document\.querySelector\("#temporary-query-button"\)/); assert.match(app, /temporaryFileInput\.addEventListener\("change"/); assert.match(app, /postJson\("\/api\/query-once"/); assert.match(app, /downloadTemporaryCsv\(/); }); test("temporary query renders platform results as each request finishes", () => { assert.match(app, /runTemporaryQueryProgressively\(names, platforms\)/); assert.match(app, /temporaryQueryTasks\(names, platforms\)/); assert.match(app, /postJson\("\/api\/query-once", \{\s*names: \[task\.name\],\s*platforms: \[task\.platform\]/); assert.match(app, /mergeTemporaryQueryResult\(task\.name, payload\.items\?.\[0\]/); assert.match(app, /renderTemporaryResults\(temporaryQueryItems\)/); assert.match(app, /clientMapLimit\(tasks, 6/); }); test("temporary query supports dropping Excel-style files into the list box", () => { assert.match(html, /accept="\.txt,\.csv,\.xlsx,text\/plain,text\/csv/); assert.match(app, /temporaryQueryText\.addEventListener\("drop"/); assert.match(app, /loadTemporaryImportFile\(/); assert.match(app, /extractXlsxText\(/); assert.match(app, /normalizeTemporaryListText\(/); }); test("temporary query supports OCR import for screenshot images", () => { assert.match(server, /recognizeImageText/); assert.match(server, /url\.pathname === "\/api\/temporary-ocr"/); assert.match(server, /readImageUploadBody\(request\)/); assert.match(app, /postJson\("\/api\/temporary-ocr"/); assert.match(app, /readFileAsDataUrl\(/); assert.doesNotMatch(app, /截图需要 OCR/); assert.match(ocr, /windows-ocr\.ps1/); assert.match(ocr, /mkdtemp/); }); test("temporary query runs with bounded concurrency and per-program failures", () => { assert.match(server, /async function collectTemporaryQueryItems/); assert.match(server, /const history = await readHistory\(\)/); assert.match(server, /mapLimit\(names, concurrency/); assert.match(server, /const concurrency = Math\.min\(Math\.max\(Number\(body\.concurrency \|\| 3\), 1\), 5\)/); assert.match(server, /const manualUrls = sanitizeUrls\(body\.urls \|\| \{\}\)/); assert.match(server, /urls: \{ \.\.\.historyProgramUrls\(history, name\), \.\.\.manualUrls \}/); assert.match(server, /freshSearchPlatforms: body\.freshSearch \? platforms\.filter\(\(platform\) => !manualUrls\[platform\]\) : \[\]/); assert.match(server, /delayMs: 0/); assert.match(server, /quickSearch: body\.quickSearch !== false/); assert.match(server, /parallelPlatforms: true/); assert.match(server, /temporaryErrorCollection/); assert.match(server, /async function mapLimit/); assert.match(server, /function historyProgramUrls/); });