douyin-release-gate / verify (push) Failing after 18m54s
Add Douyin notification polling, event details, and manual multi-account private messaging. Refine environment memory settings, account operations, login collection recovery, and message UI; update tests and documentation.
154 lines
4.7 KiB
JavaScript
154 lines
4.7 KiB
JavaScript
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,[]);
|
|
});
|