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.
106 lines
6.6 KiB
JavaScript
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');
|
|
});
|