云
昨天 74b0d9c5ab1dc316b29f8525dcffe45225d4c43c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
const fs=require('node:fs');
const path=require('node:path');
const assert=require('node:assert/strict');
const front=path.resolve(__dirname,'../../mom-pro2-before');
const vue=require(require.resolve('vue',{paths:[front]}));
const sfc=require(require.resolve('vue/compiler-sfc',{paths:[front]}));
const ts=require(require.resolve('typescript',{paths:[front]}));
const folder=path.join(front,'src/views/crm/backlog');
const row={id:11,customerId:22,businessId:33,signContactId:44,orderId:55,contractId:66,invoiceId:77};
 
function load(file,{failedCount}={}){
 const source=fs.readFileSync(file,'utf8');const parsed=sfc.parse(source,{filename:file});
 assert.equal(parsed.errors.length,0);
 assert.equal(sfc.compileTemplate({source:parsed.descriptor.template.content,filename:file,id:file}).errors.length,0);
 const compiled=sfc.compileScript(parsed.descriptor,{id:file});
 const js=ts.transpileModule(compiled.content,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2020}}).outputText;
 const state={calls:[],routes:[],events:[],queries:0,opens:0,modalData:null,grid:null};
 const stub={name:'Stub',setup(){return()=>null;}};
 const policy=vue.ref(false);
 function req(name){return async (...args)=>{state.calls.push({name,args});if(name===failedCount)throw Error('simulated API failure');return name.endsWith('Count')?7:{list:[],total:0};};}
 function mock(name){
  if(name==='vue')return {...vue,onMounted(){},onActivated(){}};
  if(name==='vue-router')return {useRouter:()=>({push:(r)=>state.routes.push(r)})};
  if(name==='ant-design-vue')return {Button:stub,Tag:stub,Badge:stub,Card:stub,List:stub,message:{loading:()=>()=>{},success(){}}};
  if(name==='@vben/common-ui')return {Page:stub,useVbenModal:()=>[stub,{setData(data){state.modalData=data;return this;},open(){state.opens++;}}]};
  if(name==='@vben/utils')return {erpNumberFormatter:(x)=>String(x)};
  if(name==='#/store/audit-policy')return {automaticApproval:policy};
  if(name==='#/adapter/vxe-table')return {TableAction:stub,ACTION_ICON:{VIEW:'view'},useVbenVxeGrid:(options)=>{state.grid=options;return[stub,{query:async()=>{state.queries++;}}];}};
  if(name.includes('/api/'))return new Proxy({},{get:(_,key)=>req(key)});
  if(name.includes('/views/'))return {useGridColumns:()=>[]};
  if(name.endsWith('.vue'))return {default:stub};
  if(name==='../data')return {AUDIT_STATUS:[{value:10}],CONTRACT_EXPIRY_TYPE:[{value:1}],RECEIVABLE_REMIND_TYPE:[{value:1}],CONTACT_STATUS:[{value:1}],SCENE_TYPES:[{value:1}],FOLLOWUP_STATUS:[]};
  if(name==='./data')return {useLeftSides:()=>[]};
  throw Error('Missing mock '+name);
 }
 const module={exports:{}};new Function('require','module','exports',js)(mock,module,module.exports);
 const vm=module.exports.default.setup({}, {expose(){},emit:(...args)=>state.events.push(args)});
 return {vm,state,descriptor:parsed.descriptor,policy};
}
 
async function main(){
 let checked=0;
 for(const name of fs.readdirSync(path.join(folder,'modules'))){
  if(!name.endsWith('.vue'))continue;
  const file=path.join(folder,'modules',name),{vm,state,descriptor}=load(file);
  const imported=fs.readFileSync(file,'utf8').match(/import \{ useGridColumns \} from '#\/views\/([^']+)'/);
  assert(imported,name+' schema import');
  const schema=path.join(front,'src/views',imported[1]+'.ts');
  const ast=ts.createSourceFile(schema,fs.readFileSync(schema,'utf8'),ts.ScriptTarget.Latest,true);
  const fn=ast.statements.find(n=>ts.isFunctionDeclaration(n)&&n.name?.text==='useGridColumns');assert(fn);
  const slots=[...fn.getText(ast).matchAll(/default:\s*'([^']+)'/g)].map(x=>x[1]);
  for(const slot of slots)assert(descriptor.template.content.includes('#'+slot+'='),name+' missing column slot '+slot);
  const values=Object.fromEntries(state.grid.formOptions.schema.filter(x=>x.defaultValue!==undefined).map(x=>[x.fieldName,x.defaultValue]));
  await state.grid.gridOptions.proxyConfig.ajax.query({page:{currentPage:2,pageSize:20}},values);
  const call=state.calls.at(-1);assert.equal(call.args[0].pageNo,2);assert.equal(call.args[0].pageSize,20);
  if(name.startsWith('contract-')){
   const checks=[['handleContractDetail','CrmContractDetail','id'],['handleCustomerDetail','CrmCustomerDetail','customerId'],['handleBusinessDetail','CrmBusinessDetail','businessId'],['handleContactDetail','CrmContactDetail','signContactId']];
   for(const [handler,route,key] of checks){vm[handler](row);assert.deepEqual(state.routes.at(-1),{name:route,params:{id:row[key]}});}
   const count=state.routes.length;vm.handleCustomerDetail({});vm.handleBusinessDetail({});vm.handleContactDetail({});assert.equal(state.routes.length,count);
   vm.handleViewOrder(row);assert.deepEqual(state.routes.at(-1),{name:'ErpSaleOrder',query:{id:55}});
  }
  if(name==='customer-put-pool-remind-list.vue'){assert.equal(call.name,'getPutPoolRemindCustomerPage');assert(!('pool' in call.args[0]));}
  if(name==='receivable-plan-remind-list.vue'){
   assert.equal(call.args[0].sceneType,1);vm.handleDetail(row);assert.deepEqual(state.routes.at(-1),{name:'CrmReceivablePlanDetail',params:{id:11}});
   vm.handleCreateReceivable({...row,receivableId:123});assert.equal(state.opens,0);
   vm.handleCreateReceivable(row);assert.equal(state.opens,1);assert.equal(state.modalData.plan,row);
   vm.handleRefresh();assert.equal(state.queries,1);assert.deepEqual(state.events,[['refresh']]);
  }
  if(name==='receivable-audit-list.vue'){
   assert.equal(call.args[0].sceneType,1);vm.handleContractDetail(row);assert.equal(state.routes.at(-1).params.id,66);
   vm.handleInvoiceDetail(row);assert.equal(state.routes.at(-1).params.id,77);
   await vm.handleAudit(row);assert(state.calls.some(x=>x.name==='updateReceivableStatus'&&x.args[0]===11&&x.args[1]===20));
   assert.equal(state.queries,1);assert.deepEqual(state.events,[['refresh']]);
  }
  checked++;
 }
 const {vm,state}=load(path.join(folder,'index.vue'),{failedCount:'getTodayContactCustomerCount'});
 const first=vm.getCount();assert.equal(vm.getCount(),first,'Overlapping mount/activation requests should be deduplicated');await first;
 assert.equal(state.calls.length,8);assert.equal(vm.customerTodayContactCount.value,0);assert.equal(vm.contractRemindCount.value,7);assert.equal(vm.receivablePlanRemindCount.value,7);
 await vm.getCount();assert.equal(state.calls.length,16,'Refresh should request fresh counts');
 const result={passed:true,module_components:checked,vue_compilation:true,all_shared_column_slots_present:true,related_entity_routes:true,pool_reminder_endpoint:true,receivable_plan_route_and_repeat_guard:true,mutation_refresh_events:true,counter_partial_failure_isolation:true,counter_request_deduplication:true};
 fs.writeFileSync(path.join(__dirname,'verification.json'),JSON.stringify(result,null,2));console.log(JSON.stringify(result));
}
main().catch(e=>{console.error(e);process.exitCode=1;});