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');"){ 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']); });