Files
creator-hub/web/tests/event-aggregation-page.test.cjs
T
rogee e50bed9ad8
douyin-release-gate / verify (push) Failing after 18m54s
feat: add account events and private message management
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.
2026-10-07 10:23:05 +08:00

104 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const test = require('node:test');
const assert = require('node:assert/strict');
const { readFileSync } = require('node:fs');
const { resolve } = require('node:path');
const Module = require('node:module');
const ts = require('typescript');
const React = require('react');
function transpile(file) { return ts.transpileModule(readFileSync(file,'utf8'), {compilerOptions:{module:ts.ModuleKind.CommonJS,jsx:ts.JsxEmit.ReactJSX,target:ts.ScriptTarget.ES2020}}).outputText; }
function harness(options={}) {
const states=[],effects=[],queued=[],calls=[];
let cursor=0;
const antd={};
for(const name of ['Alert','Avatar','Button','Card','Flex','Select','Table','Tag','Tooltip']){antd[name]=()=>null;antd[name].displayName=name;}
antd.Typography={Text:()=>null,Paragraph:()=>null,Link:()=>null};
const utilityFile=resolve(__dirname,'../src/utils/account-events.ts');
const utility=new Module(utilityFile,module);utility._compile(transpile(utilityFile),utilityFile);
const file=resolve(__dirname,'../src/pages/creator/events/index.tsx');
const loaded=new Module(file,module);loaded.filename=file;
loaded.require=id=>{
if(id==='react')return{useState(initial){const index=cursor++;if(!(index in states))states[index]=initial;return[states[index],value=>{states[index]=typeof value==='function'?value(states[index]):value;}]},
useEffect(callback,deps){const index=cursor++;const prior=effects[index];if(!prior||deps.some((v,i)=>!Object.is(v,prior.deps[i]))){if(prior?.cleanup)prior.cleanup();effects[index]={deps};queued.push(()=>{effects[index].cleanup=callback();});}}};
if(id==='react/jsx-runtime')return require(id);
if(id==='antd')return antd;
if(id==='@ant-design/icons')return{ReloadOutlined:()=>null,VideoCameraOutlined:()=>null};
if(id==='@/components/creator/WorksGallery')return{WorkCover:Object.assign(()=>null,{displayName:'WorkCover'})};
if(id==='@/utils/account-events')return utility.exports;
if(id==='@/utils/table')return{useTableSticky:()=>false,tablePagination:()=>({})};
if(id==='@/utils/helpers')return{dateTime:value=>`time:${value}`,conflictMessage:(cause,fallback)=>cause.message||fallback};
if(id==='@/services/api')return{creatorGet:async path=>{calls.push(path);if(options.get)return options.get(path);if(path==='/creator/accounts')return options.accounts||[];return options.page||{data:[],total:0};}};
return require(id);
};
loaded._compile(transpile(file),file);
return{antd,calls,render(){cursor=0;return loaded.exports.default();},async effects(){while(queued.length)queued.shift()();for(let i=0;i<8;i++)await Promise.resolve();},dispose(){for(const effect of effects)effect?.cleanup?.();}};
}
function all(node,predicate){const result=[];function visit(value){if(Array.isArray(value))return value.forEach(visit);if(!React.isValidElement(value))return;if(predicate(value))result.push(value);visit(value.props.children);}visit(node);return result;}
function text(node){if(node==null)return'';if(typeof node==='string'||typeof node==='number')return String(node);if(Array.isArray(node))return node.map(text).join('');return React.isValidElement(node)?text(node.props.children):'';}
function select(h,tree,label){return all(tree,node=>node.type===h.antd.Select&&node.props['aria-label']===label)[0];}
function table(h,tree){return all(tree,node=>node.type===h.antd.Table)[0];}
test('only enabled accounts are offered, event filters are server-wide and changes reset page',async t=>{
const h=harness({accounts:[{id:'on',name:'开启账号',platform_account_key:'1',listener_enabled:true},{id:'off',name:'关闭账号',listener_enabled:false}],page:{data:[{id:'event'}],total:40}});t.after(()=>h.dispose());
h.render();await h.effects();let tree=h.render();
assert.deepEqual(select(h,tree,'所属账号').props.options.map(o=>o.value),['on']);
assert.equal(select(h,tree,'所属账号').props.options[0].label,'开启账号');
assert.equal(table(h,tree).props.pagination.total,40);
table(h,tree).props.pagination.onChange(2,25);h.render();await h.effects();tree=h.render();
assert.equal(table(h,tree).props.pagination.current,2);
select(h,tree,'所属账号').props.onChange('on');h.render();await h.effects();tree=h.render();
assert.equal(table(h,tree).props.pagination.current,1);
select(h,tree,'事件类型').props.onChange('dm');h.render();await h.effects();tree=h.render();
select(h,tree,'接收时间范围').props.onChange(24);h.render();await h.effects();tree=h.render();
const query=new URLSearchParams(h.calls.filter(p=>p.startsWith('/creator/events')).at(-1).split('?')[1]);
assert.equal(query.get('event_type'),'dm');assert.equal(query.get('account_id'),'on');assert.equal(query.get('hours'),'24');assert.equal(query.get('page'),'1');
table(h,tree).props.pagination.onChange(3,50);h.render();await h.effects();tree=h.render();
assert.equal(table(h,tree).props.pagination.current,1);assert.equal(table(h,tree).props.pagination.pageSize,50);
all(tree,node=>node.type===h.antd.Button)[0].props.onClick();h.render();await h.effects();
assert.equal(h.calls.filter(p=>p.startsWith('/creator/events')).length,7);
});
test('event contents distinguish missing data, non-text messages and baseline history',async t=>{
const h=harness();t.after(()=>h.dispose());h.render();await h.effects();const cols=table(h,h.render()).props.columns;
const column=title=>cols.find(c=>c.title===title);
assert.equal(text(column('事件类型').render('comment')),'评论');
assert.equal(text(column('内容').render(null,{message_text:'测试内容',message_type:'text'})),'测试内容');
assert.equal(text(column('内容').render(null,{message_text:'',message_type:'image'})),'图片消息');
assert.equal(text(column('内容').render(null,{message_text:'',message_type:'text'})),'—');
assert.equal(column('发生时间').render(null),'—');assert.equal(column('发生时间').render('date'),'time:date');
assert.equal(column('对应作品').render(''),'—');
const link=column('对应作品').render('123',{id:'event',work_id:'123',work_url:'https://www.douyin.com/video/123',has_work_cover:true});
assert.equal(link.props.href,'https://www.douyin.com/video/123');
const cover=all(link,n=>n.type.displayName==='WorkCover')[0];
assert.equal(cover.props.width,48);assert.equal(cover.props.coverPath,'/creator/events/event/cover');
const missing=column('对应作品').render('123',{id:'event',work_id:'123',work_url:'',has_work_cover:false,work_cover_error:'下载失败'});
assert.equal(missing.props.href,'https://www.douyin.com/video/123');
assert.equal(all(missing,n=>n.type===h.antd.Avatar)[0].props.size,48);
assert.equal(all(missing,n=>n.type===h.antd.Tooltip)[0].props.title,'下载失败');
assert.equal(text(column('来源').render(true)),'历史记录');assert.equal(text(column('来源').render(false)),'开启后通知');
const user=column('互动用户').render(null,{interactor_uid:'22',interactor_name:'用户',interactor_sec_uid:'MS4wLjABAAAAprofile'});
const profile=all(user,n=>n.type===h.antd.Typography.Link)[0];
assert.equal(profile.props.href,'https://www.douyin.com/user/MS4wLjABAAAAprofile');
assert.equal(profile.props.target,'_blank');assert.match(text(user),/用户/);assert.match(text(user),/UID:22/);
const unknown=column('互动用户').render(null,{interactor_uid:'22',interactor_name:'',interactor_sec_uid:''});
assert.equal(all(unknown,n=>n.type===h.antd.Typography.Link).length,0);assert.match(text(unknown),/昵称未获取/);
assert.equal(text(column('所属账号').render(null,{account_name:'账号',account_uid:'1'})),'账号');
assert.deepEqual(cols.slice(0,2).map(c=>c.title),['发生时间','接收时间']);
});
test('read failures are visible and retries do not fake an empty success',async t=>{
let failed=true;const h=harness({get:async path=>{if(failed)throw new Error('服务不可用');return path==='/creator/accounts'?[]:{data:[{id:'1'}],total:1};}});t.after(()=>h.dispose());
h.render();await h.effects();let tree=h.render();
const error=all(tree,n=>n.type===h.antd.Alert)[0];assert.equal(error.props.title,'事件读取失败');assert.equal(error.props.description,'服务不可用');
failed=false;all(tree,n=>n.type===h.antd.Button)[0].props.onClick();h.render();await h.effects();tree=h.render();
assert.equal(all(tree,n=>n.type===h.antd.Alert).length,0);assert.equal(table(h,tree).props.dataSource.length,1);
});
test('a late response for previous filters cannot overwrite current events',async t=>{
let resolveOld;
const h=harness({get:path=>{if(path==='/creator/accounts')return Promise.resolve([]);if(path.includes('event_type=comment'))return Promise.resolve({data:[{id:'current'}],total:1});return new Promise(resolve=>{resolveOld=resolve;});}});t.after(()=>h.dispose());
let tree=h.render();await h.effects();select(h,tree,'事件类型').props.onChange('comment');h.render();await h.effects();
resolveOld({data:[{id:'old'}],total:99});await h.effects();tree=h.render();
assert.equal(table(h,tree).props.dataSource[0].id,'current');assert.equal(table(h,tree).props.pagination.total,1);
});