Files
creator-hub/web/tests/event-aggregation-page.test.cjs
T
rogee 51091cf0c5
douyin-release-gate / verify (push) Failing after 18m33s
fix: preserve pagination and filters across list navigation
2026-10-07 14:41:11 +08:00

134 lines
11 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 { loadPageState } = require('./helpers/page-state.cjs');
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{useMemo:factory=>factory(),useCallback:callback=>callback,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==='@/hooks/usePageState')return loadPageState(loaded.require('react'),{storage:options.storage});
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('event aggregation restores pagination and filters together after returning', async t => {
const storage = new Map();
const options = {storage, accounts:[{id:'on',name:'账号',listener_enabled:true}],page:{data:[],total:150}};
const first = harness(options); t.after(()=>first.dispose());
first.render(); await first.effects(); let tree = first.render();
select(first,tree,'所属账号').props.onChange('on');
select(first,tree,'事件类型').props.onChange('comment');
select(first,tree,'接收时间范围').props.onChange(6);
first.render(); await first.effects(); tree = first.render();
table(first,tree).props.pagination.onChange(1,50);
first.render(); await first.effects(); tree = first.render();
table(first,tree).props.pagination.onChange(3,50);
first.render(); await first.effects();
const returned = harness(options); t.after(()=>returned.dispose());
returned.render(); await returned.effects(); tree = returned.render();
assert.equal(table(returned,tree).props.pagination.current,3);
assert.equal(table(returned,tree).props.pagination.pageSize,50);
assert.equal(select(returned,tree,'所属账号').props.value,'on');
assert.equal(select(returned,tree,'事件类型').props.value,'comment');
assert.equal(select(returned,tree,'接收时间范围').props.value,6);
const query = new URLSearchParams(returned.calls.find(url=>url.startsWith('/creator/events')).split('?')[1]);
assert.equal(query.get('page'),'3'); assert.equal(query.get('page_size'),'50');
assert.equal(query.get('account_id'),'on'); assert.equal(query.get('event_type'),'comment');
select(returned,tree,'接收时间范围').props.onChange(12);
returned.render(); await returned.effects(); tree = returned.render();
assert.equal(table(returned,tree).props.pagination.current,1);
});
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);
});