const assert = require("node:assert/strict"); const fs = require("node:fs"); const path = require("node:path"); const test = require("node:test"); const ts = require("../node_modules/typescript"); const filename = path.resolve( __dirname, "../src/components/creator/WorksGallery.tsx", ); const file = ts.createSourceFile( filename, fs.readFileSync(filename, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX, ); const cover = file.statements.find( (node) => ts.isFunctionDeclaration(node) && node.name?.text === "WorkCover", ); const compiled = ts.transpileModule(cover.getText(file), { compilerOptions: { module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.React }, }).outputText; function harness(fetch) { const state = []; const effects = []; const calls = []; const revoked = []; let cursor = 0; let effect; let effectIndex; const react = { createElement: (type, props, ...children) => ({ type, props: { ...props, children }, }), }; const useState = (initial) => { const index = cursor++; if (!(index in state)) state[index] = initial; return [ state[index], (value) => { state[index] = value; }, ]; }; const useEffect = (fn, deps) => { const index = cursor++; if (!effects[index]) { effect = fn; effectIndex = index; effects[index] = { deps }; } }; const url = { createObjectURL: () => "blob:local-cover", revokeObjectURL: (value) => revoked.push(value), }; const Component = new Function( "exports", "React", "useState", "useEffect", "fetchCreatorBinary", "Flex", "Typography", "URL", compiled + "\nreturn exports.WorkCover;", )( {}, react, useState, useEffect, (endpoint) => { calls.push(endpoint); return fetch(); }, "Flex", { Text: "Text" }, url, ); return { calls, revoked, render(props) { cursor = 0; const tree = Component(props); if (effect) { effects[effectIndex].cleanup = effect(); effect = null; } return tree; }, cleanup() { effects[effectIndex]?.cleanup?.(); }, }; } const tick = async () => { for (let i = 0; i < 6; i++) await Promise.resolve(); }; test("comment thumbnails load only the cached local cover and release its image URL", async () => { const h = harness(async () => ({ cover: true })); const props = { workID: "work/id", title: "作品标题" }; const loading = h.render(props); assert.equal(loading.type, "Flex"); assert.equal(loading.props.children[0].props.children[0], "封面加载中"); await tick(); const image = h.render(props); assert.equal(image.type, "img"); assert.equal(image.props.src, "blob:local-cover"); assert.equal(image.props.alt, "作品标题"); assert.deepEqual(h.calls, ["/creator/works/work%2Fid/cover"]); h.cleanup(); assert.deepEqual(h.revoked, ["blob:local-cover"]); }); test("missing cached cover is explicit and never replaced by a remote image", async () => { const h = harness(async () => { throw Object.assign(new Error("作品封面尚未缓存"), {status:404}); }); const props = { workID: "missing-cover", title: "作品封面" }; h.render(props); await tick(); const missing = h.render(props); assert.equal(missing.type, "Flex"); assert.equal(missing.props.children[0].props.children[0], "封面未缓存"); assert.deepEqual(h.calls, ["/creator/works/missing-cover/cover"]); h.cleanup(); assert.deepEqual(h.revoked, []); }); test("event thumbnail uses its event endpoint at a restrained width", async () => { const h=harness(async()=>({image:true}));const props={workID:'123',coverPath:'/creator/events/7/cover',width:48}; h.render(props);await tick();const tree=h.render(props); assert.equal(tree.props.style.width,48);assert.deepEqual(h.calls,['/creator/events/7/cover']);h.cleanup(); }); test("server errors and invalid image data are not disguised as uncached covers", async () => { const h=harness(async()=>{throw Object.assign(new Error('read failed'),{status:500})}); const props={workID:'123'};h.render(props);await tick(); assert.equal(h.render(props).props.children[0].props.children[0],'封面读取失败'); const loaded=harness(async()=>({image:true}));loaded.render(props);await tick();loaded.render(props).props.onError(); assert.equal(loaded.render(props).props.children[0].props.children[0],'封面读取失败');loaded.cleanup(); }); test("a cover resolved after unmount cannot allocate an image URL", async () => { let resolve;const h=harness(()=>new Promise(r=>resolve=r));const props={workID:'123'}; h.render(props);h.cleanup();resolve({image:true});await tick(); assert.equal(h.render(props).type,'Flex');assert.deepEqual(h.revoked,[]); });