MatchMaster/tools/test-cloud-rise-package.cjs
2026-09-21 12:44:48 +08:00

114 lines
9.0 KiB
JavaScript

const test = require('node:test'), assert = require('node:assert/strict');
const fs = require('node:fs'), path = require('node:path'), vm = require('node:vm');
const { pack, names } = require('../packages/cloud-rise-package/pack');
const runtime = fs.readFileSync('packages/cloud-rise-package/runtime.js', 'utf8');
const settle = async () => { for (let i = 0; i < 20; i++) await Promise.resolve(); };
function buildFixture() {
fs.mkdirSync('temp', {recursive:true});
const root = fs.mkdtempSync(path.resolve('temp/cloud-rise-package-test-'));
fs.mkdirSync(path.join(root, 'src'));
fs.writeFileSync(path.join(root, 'src/settings.js'), 'window._CCSettings=' + JSON.stringify({subpackages:[...names,'shop'],bundleVers:Object.fromEntries(names.map(n=>[n,'abc']))}) + ';');
fs.writeFileSync(path.join(root, 'game.json'), JSON.stringify({subpackages:[...names.map(name=>({name,root:'subpackages/'+name})),{name:'shop',root:'subpackages/shop'}]}));
fs.writeFileSync(path.join(root, 'game.js'), "require('./src/settings');\nwindow.boot();");
for (const name of names) {
const dir = path.join(root,'subpackages',name);fs.mkdirSync(dir,{recursive:true});
fs.writeFileSync(path.join(dir,'config.abc.json'),JSON.stringify({name,deps:['cloud_rise_home'],uuids:['unchanged']}));
fs.writeFileSync(path.join(dir,'game.js'),'/* '+name+' */');
}
return root;
}
test('Creator GUI mini-game options invoke the hook using actualPlatform; non-WeChat builds are untouched',()=>{
const hooks={},logs=[],module={exports:{}};
vm.runInNewContext(fs.readFileSync('packages/cloud-rise-package/main.js','utf8'),{
module,require:name=>{assert.equal(name,'./pack');return {pack};},
Editor:{log:message=>logs.push(message),error:error=>{throw error;},Builder:{on:(event,fn)=>hooks[event]=fn,removeListener:(event,fn)=>{assert.equal(hooks[event],fn);delete hooks[event];}}}
});
module.exports.load();
for(const options of [{platform:'mini-game',actualPlatform:'wechatgame'},{platform:'wechatgame'}]){
const root=buildFixture();let called=0;
hooks['build-finished']({...options,dest:root},error=>{assert.ifError(error);called++;});
assert.equal(called,1);assert(fs.existsSync(path.join(root,'subpackages/cloud_rise/game.js')));
}
let skipped=0;hooks['build-finished']({platform:'mini-game',actualPlatform:'bytedance'},()=>skipped++);
assert.equal(skipped,1);assert.equal(logs.length,2);
module.exports.unload();assert.equal(hooks['build-finished'],undefined);
});
test('unprocessed template references Creator output; only a successful hook changes it to the grouped root',()=>{
const template=JSON.parse(fs.readFileSync('build-templates/wechatgame/game.json'));
assert.deepEqual(template.subpackages.filter(p=>p.name.startsWith('cloud_rise')),names.map(name=>({name,root:'subpackages/'+name})));
const root=buildFixture();fs.writeFileSync(path.join(root,'game.json'),JSON.stringify(template));pack(root);
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(root,'game.json'))).subpackages.filter(p=>p.name.startsWith('cloud_rise')),[{name:'cloud_rise',root:'subpackages/cloud_rise'}]);
});
test('build hook groups shared art and four pages, preserves dependencies and other packages, and boots the loader first',()=>{
const root=buildFixture();pack(root);pack(root);
const manifest=JSON.parse(fs.readFileSync(path.join(root,'game.json')));
assert.deepEqual(manifest.subpackages,[{name:'cloud_rise',root:'subpackages/cloud_rise'},{name:'shop',root:'subpackages/shop'}]);
const settings={window:{}};vm.runInNewContext(fs.readFileSync(path.join(root,'src/settings.js'),'utf8'),settings);
assert.deepEqual(Array.from(settings.window._CCSettings.subpackages),['shop']);
const scripts=[];vm.runInNewContext(fs.readFileSync(path.join(root,'subpackages/cloud_rise/game.js'),'utf8'),{require:p=>scripts.push(p)});
assert.deepEqual(scripts,names.map(n=>'./'+n+'/game.js'));
for(const name of names){
assert(!fs.existsSync(path.join(root,'subpackages',name)));
const data=JSON.parse(fs.readFileSync(path.join(root,'subpackages/cloud_rise',name,'config.abc.json')));
assert.deepEqual(data,{name,deps:['cloud_rise_home'],uuids:['unchanged']});
}
const boot=fs.readFileSync(path.join(root,'game.js'),'utf8');
assert.equal(boot.split("require('./cloud-rise-package');").length,2);
assert(boot.indexOf("require('./cloud-rise-package');")<boot.indexOf('window.boot();'));
// A subsequent Creator build re-emits the standalone directories; the hook replaces its own old output.
for(const name of names){const dir=path.join(root,'subpackages',name);fs.mkdirSync(dir);fs.writeFileSync(path.join(dir,'config.abc.json'),'{}');fs.writeFileSync(path.join(dir,'game.js'),'/* new build */');}
pack(root);assert.equal(fs.readFileSync(path.join(root,'subpackages/cloud_rise',names[0],'game.js'),'utf8'),'/* new build */');
});
test('incomplete build fails before changing the manifest or moving any bundle',()=>{
const root=buildFixture(),manifest=fs.readFileSync(path.join(root,'game.json'),'utf8');
fs.unlinkSync(path.join(root,'subpackages',names[3],'config.abc.json'));
assert.throws(()=>pack(root),/Missing Cloud Rise bundle/);
assert.equal(fs.readFileSync(path.join(root,'game.json'),'utf8'),manifest);
assert(fs.existsSync(path.join(root,'subpackages',names[0])));
});
function loaderFixture() {
const downloads=[],reads=[],other=[],handlers={bundle:(...args)=>other.push(args)};
const window={fsUtils:{readJson:(file,done)=>{reads.push(file);done(null,{name:path.basename(path.dirname(file)),deps:['cloud_rise_home']});}}};
const cc={path:{basename:path.basename},assetManager:{downloader:{_downloaders:handlers,bundleVers:Object.fromEntries(names.map(n=>[n,'abc'])),register:(type,handler)=>handlers[type]=handler}}};
vm.runInNewContext(runtime,{window,cc,wx:{loadSubpackage:options=>downloads.push(options)}});
const load=(name,options={})=>new Promise((resolve,reject)=>handlers.bundle(name,options,(err,data)=>err?reject(err):resolve(data)));
return {window,load,downloads,reads,other};
}
test('shared art and four pages share one package download and leave other bundle loaders alone',async()=>{
const f=loaderFixture(),warm=f.window.__prepareCloudRisePackage(),matching=f.load(names[0]);
assert.equal(f.downloads.length,1);assert.equal(f.downloads[0].name,'cloud_rise');assert.equal(f.reads.length,0);
f.downloads[0].success();await warm;const data=await matching;
assert.equal(data.base,'subpackages/cloud_rise/cloud_rise_matching/');assert.equal(f.reads.length,1);
const stage=await f.load(names[2],{version:'new'});
assert.equal(stage.base,'subpackages/cloud_rise/cloud_rise_stage2/');assert.equal(f.downloads.length,1);
assert.equal(f.reads[1],'subpackages/cloud_rise/cloud_rise_stage2/config.new.json');
const art=await f.load('cloud_rise_art');
assert.equal(art.base,'subpackages/cloud_rise/cloud_rise_art/');assert.equal(f.downloads.length,1);
void f.load('cloud_rise_home');assert.equal(f.other[0][0],'cloud_rise_home');
});
test('failed unified downloads reject the page request and allow a clean retry',async()=>{
const f=loaderFixture(),first=f.load(names[0]);f.downloads[0].fail(Error('offline'));
await assert.rejects(first,/offline/);assert.equal(f.reads.length,0);
const retry=f.load(names[0]);assert.equal(f.downloads.length,2);f.downloads[1].success();await retry;
assert.equal(f.reads.length,1);
});
function serviceFixture(data) {
const ts=require('typescript'),calls=[],window={},exports={};
let ready;window.__prepareCloudRisePackage=()=>{calls.push('download');return new Promise(resolve=>ready=resolve);};
const cc={fx:{GameConfig:{GM_INFO:{userId:'1',level:200}}},warn(){}};
vm.runInNewContext(ts.transpileModule(fs.readFileSync('assets/Script/module/Config/CloudRiseService.ts','utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS}}).outputText,{exports,cc,window,require:()=>({default:{}})});
const service=exports.default;service.data=()=>data;service.now=()=>100;
service.pagePrefab=async()=>{calls.push('home');};service.pageFragment=async kind=>{calls.push(kind);};
return {service,calls,ready:()=>ready()};
}
test('eligible activity warms matching after unified download without loading any stage or enrolling',async()=>{
const f=serviceFixture({available:true,period:{startsAt:0,endsAt:200,unlockLevel:100}});
f.service.preloadActivity();assert.deepEqual(f.calls,['home','download']);f.ready();await settle();
assert.deepEqual(f.calls,['home','download','matching']);
});
test('closed activity does not preload; personal runs beyond signup closing still prepare the package',async()=>{
const closed=serviceFixture({available:false,period:{startsAt:0,endsAt:50}});closed.service.preloadActivity();assert.deepEqual(closed.calls,[]);
const active=serviceFixture({available:false,run:{status:'playing',expiresAt:200,stages:[]},period:{startsAt:0,endsAt:50}});
active.service.preloadActivity();active.ready();await settle();assert.deepEqual(active.calls,['home','download']);
});