Files
youle_cocos/cocoscreator_projects/scripts/test/version-manifest-build.test.mjs
T
joywayer 35216f75e7 feat: integrate room framework and isolated subgame bundles
Complete room UI and protocol integration, move game definitions and resources behind bundle entries, publish authoritative version XML, and document single-game builds. Include all current resource changes and experiment artifacts.
2026-09-09 03:13:18 +08:00

106 lines
6.6 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {createRequire} from 'node:module';
import {mkdtempSync, mkdirSync, readFileSync, writeFileSync, rmSync} from 'node:fs';
import {tmpdir} from 'node:os';
import {join, resolve} from 'node:path';
const require = createRequire(import.meta.url);
const corePath = '../../YouleNexus/extensions/youle-version-manifest/core.js';
const uuid = 'ec19e470-cf36-40e8-b420-e4314e5fbf07';
const type = 'ec19eRwzzZA6LQg5DFOX78H';
const source = readFileSync(new URL('../../YouleNexus/assets/games/erqiwang/resources/erqiwang/version.xml', import.meta.url));
function fixture(t, key = 'erqiwang') {
const dir = mkdtempSync(join(tmpdir(), 'youle-version-build-'));
t.after(() => rmSync(dir, {recursive: true, force: true}));
const write = (p, data) => {mkdirSync(resolve(p, '..'), {recursive: true}); writeFileSync(p, data);};
write(join(dir, 'assets/app/composition/SubgameAssets.ts.meta'), JSON.stringify({uuid}));
const scenePath = join(dir, 'assets/scenes/Chosen.scene');
const scene = [{__type__: 'cc.Scene', _active: true}, {__type__: 'cc.Node', _active: true, _parent: {__id__: 0}}, {__type__: type, _enabled: true, node: {__id__: 1}, gameKey: key}];
write(scenePath, JSON.stringify(scene));
const xmlPath = join(dir, 'assets/games/anything/resources', key, 'version.xml');
write(xmlPath, source);
const bundleMetaPath = join(dir, 'assets/games/anything/resources.meta');
write(bundleMetaPath, JSON.stringify({userData: {isBundle: true, bundleName: 'version-' + key}}));
const output = join(dir, 'custom-output/web');
write(join(output, 'index.html'), '<html></html>');
const options = {startScene: 'scene-id', platform: 'web-desktop'};
const result = {paths: {output}, settings: {launch: {launchScene: 'db://assets/scenes/Chosen.scene'}, assets: {projectBundles: ['main', 'version-' + key]}}};
return {dir, write, scenePath, scene, xmlPath, bundleMetaPath, output, options, result,
input: {projectPath: dir, scenePath, startScene: options.startScene}};
}
test('publishes the selected scene manifest byte-for-byte beside the actual index.html', t => {
const {createSnapshot, publishSnapshot} = require(corePath);
const f = fixture(t);
const snapshot = createSnapshot(f.input);
publishSnapshot(snapshot, f.options, f.result);
assert.deepEqual(readFileSync(join(f.output, 'version.xml')), source);
assert.equal(snapshot.gameKey, 'erqiwang');
});
test('selects another game from its scene without a hardcoded game registry', t => {
const {createSnapshot, publishSnapshot} = require(corePath);
const f = fixture(t, 'Game_Surface_3');
const template = readFileSync(new URL('../../YouleNexus/assets/games/template/resources/Game_Surface_3/version.xml', import.meta.url));
f.write(f.xmlPath, template);
const snapshot = createSnapshot(f.input);
publishSnapshot(snapshot, f.options, f.result);
assert.deepEqual(readFileSync(join(f.output, 'version.xml')), template);
});
for (const problem of ['missing', 'invalid', 'duplicate', 'unselected', 'disabled', 'inactive', 'ambiguous', 'missing bundle metadata', 'disabled bundle', 'mismatched bundle name']) {
test(`rejects ${problem} source or selection before build`, t => {
const {createSnapshot} = require(corePath);
const f = fixture(t);
if (problem === 'missing') rmSync(f.xmlPath);
if (problem === 'invalid') f.write(f.xmlPath, '<broken>');
if (problem === 'duplicate') f.write(join(f.dir, 'assets/games/second/resources/erqiwang/version.xml'), source);
if (problem === 'unselected') delete f.scene[2].gameKey;
if (problem === 'disabled') f.scene[2]._enabled = false;
if (problem === 'inactive') f.scene[1]._active = false;
if (problem === 'ambiguous') f.scene.push({...f.scene[2]});
if (problem === 'missing bundle metadata') rmSync(f.bundleMetaPath);
if (problem === 'disabled bundle') f.write(f.bundleMetaPath, JSON.stringify({userData: {isBundle: false, bundleName: 'version-erqiwang'}}));
if (problem === 'mismatched bundle name') f.write(f.bundleMetaPath, JSON.stringify({userData: {isBundle: true, bundleName: 'version-other'}}));
f.write(f.scenePath, JSON.stringify(f.scene));
assert.throws(() => createSnapshot(f.input));
});
}
for (const problem of ['xml changed', 'scene changed', 'start scene changed', 'launch scene changed', 'missing index', 'bundle metadata changed', 'excluded version bundle', 'missing packaged bundle list']) {
test(`rejects ${problem} after build`, t => {
const {createSnapshot, publishSnapshot} = require(corePath);
const f = fixture(t);
const snapshot = createSnapshot(f.input);
if (problem === 'xml changed') f.write(f.xmlPath, Buffer.concat([source, Buffer.from('\n')]));
if (problem === 'scene changed') f.write(f.scenePath, '[]');
if (problem === 'start scene changed') f.options.startScene = 'other';
if (problem === 'launch scene changed') f.result.settings.launch.launchScene = 'db://assets/scenes/Other.scene';
if (problem === 'missing index') rmSync(join(f.output, 'index.html'));
if (problem === 'bundle metadata changed') f.write(f.bundleMetaPath, JSON.stringify({userData: {isBundle: true, bundleName: 'version-other'}}));
if (problem === 'excluded version bundle') f.result.settings.assets.projectBundles = ['main', 'version-other'];
if (problem === 'missing packaged bundle list') delete f.result.settings.assets.projectBundles;
assert.throws(() => publishSnapshot(snapshot, f.options, f.result));
});
}
test('Creator hooks use the task startScene and publish through the actual output path', async t => {
const f = fixture(t);
const previous = globalThis.Editor;
t.after(() => {globalThis.Editor = previous;});
const calls = [];
globalThis.Editor = {
Project: {path: f.dir},
Message: {request: async (...args) => {calls.push(args); return f.scenePath;}},
};
const hooks = require('../../YouleNexus/extensions/youle-version-manifest/hooks.js');
assert.equal(hooks.throwError, true);
await hooks.onBeforeBuild(f.options);
await hooks.onAfterBuild(f.options, f.result);
assert.deepEqual(calls, [['asset-db', 'query-path', 'scene-id']]);
assert.deepEqual(readFileSync(join(f.output, 'version.xml')), source);
await assert.rejects(hooks.onAfterBuild(f.options, f.result), /Missing pre-build/);
});
test('only Web Desktop and Web Mobile register the required manifest hooks', () => {
const {configs} = require('../../YouleNexus/extensions/youle-version-manifest/builder.js');
assert.deepEqual(Object.keys(configs).sort(), ['web-desktop', 'web-mobile']);
assert.equal(configs['web-desktop'].hooks, './hooks');
assert.equal(configs['web-mobile'].hooks, './hooks');
});