Bug fixes (verified against Cocos Creator 3.8.8): - diagnostics: shell:true for .cmd/.bat on Windows (T125/T126) - prefabs: duplicatePrefab target resolves under assets/ (T420) - cocos-project: add preview.open candidate for 3.8.8 (T444) - assets-advanced: detect directory assets in inspectAssetDependencies (T110) - scene: improve component-not-found errors with compilation hint (T429) Documentation improvements: - tool-registry: add enum and injected vars to execute_javascript, path format examples, asset ref limitation note - resources: add cocos://mcp/execute-context resource with variables, patterns, pitfalls New tools (core 37->38, full 101->110): - scene-management: create_scene, query_scene_state (core), copy_paste_node, rename_node, reparent_node - scripts: create_script with component/plain templates - prefabs: create_prefab - assets-advanced: batch_asset_ops, find_unused_assets
140 lines
5.3 KiB
JavaScript
140 lines
5.3 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const test = require('node:test');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const os = require('node:os');
|
|
const {
|
|
createScriptTools,
|
|
generateComponentTemplate,
|
|
generatePlainTemplate,
|
|
} = require('../lib/tools/scripts');
|
|
|
|
test('generateComponentTemplate produces valid component with start and update', () => {
|
|
const code = generateComponentTemplate('PlayerController');
|
|
assert.ok(code.includes("import { _decorator, Component } from 'cc'"));
|
|
assert.ok(code.includes("@ccclass('PlayerController')"));
|
|
assert.ok(code.includes('export class PlayerController extends Component'));
|
|
assert.ok(code.includes('start()'));
|
|
assert.ok(code.includes('update(deltaTime: number)'));
|
|
});
|
|
|
|
test('generateComponentTemplate includes primitive @property', () => {
|
|
const code = generateComponentTemplate('Enemy', [
|
|
{ name: 'speed', type: 'Number', default: 5 },
|
|
{ name: 'label', type: 'String', default: "'Enemy'" },
|
|
]);
|
|
assert.ok(code.includes('@property'));
|
|
assert.ok(code.includes('speed: number = 5'));
|
|
assert.ok(code.includes("label: string = 'Enemy'"));
|
|
assert.ok(!code.includes('Label'));
|
|
});
|
|
|
|
test('generateComponentTemplate includes CC type @property', () => {
|
|
const code = generateComponentTemplate('Weapon', [
|
|
{ name: 'target', type: 'Label' },
|
|
{ name: 'sprite', type: 'Sprite' },
|
|
]);
|
|
assert.ok(code.includes('@property(Label)'));
|
|
assert.ok(code.includes('target: Label | null = null'));
|
|
assert.ok(code.includes('@property(Sprite)'));
|
|
assert.ok(code.includes('sprite: Sprite | null = null'));
|
|
assert.ok(code.includes('import { _decorator, Component, Label, Sprite }'));
|
|
});
|
|
|
|
test('generateComponentTemplate uses defaults when property default is omitted', () => {
|
|
const code = generateComponentTemplate('Item', [
|
|
{ name: 'count', type: 'Number' },
|
|
{ name: 'flag', type: 'Boolean' },
|
|
]);
|
|
assert.ok(code.includes('count: number = 0'));
|
|
assert.ok(code.includes('flag: boolean = false'));
|
|
});
|
|
|
|
test('generateComponentTemplate with no properties has placeholder comment', () => {
|
|
const code = generateComponentTemplate('Empty');
|
|
assert.ok(code.includes('// Add properties here'));
|
|
});
|
|
|
|
test('generatePlainTemplate produces minimal class', () => {
|
|
const code = generatePlainTemplate('Utils');
|
|
assert.ok(code.includes('export class Utils'));
|
|
assert.ok(!code.includes('@ccclass'));
|
|
assert.ok(!code.includes('@property'));
|
|
});
|
|
|
|
test('create_script writes file and returns metadata', async () => {
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'funplay-script-test-'));
|
|
try {
|
|
const createSchema = (properties, required) => {
|
|
const schema = { type: 'object', properties };
|
|
if (required && required.length) {
|
|
schema.required = required;
|
|
}
|
|
return schema;
|
|
};
|
|
const getRuntimeContext = () => ({ projectPath: tmpDir });
|
|
const tools = createScriptTools({ createSchema, getRuntimeContext });
|
|
const tool = tools[0];
|
|
|
|
const result = await tool.handler({
|
|
scriptName: 'TestComp',
|
|
savePath: 'assets/scripts/TestComp.ts',
|
|
template: 'component',
|
|
properties: [{ name: 'speed', type: 'Number', default: 10 }],
|
|
});
|
|
|
|
assert.equal(result.created, true);
|
|
assert.equal(result.scriptName, 'TestComp');
|
|
assert.equal(result.template, 'component');
|
|
assert.equal(result.className, 'TestComp');
|
|
|
|
const written = fs.readFileSync(path.join(tmpDir, 'assets', 'scripts', 'TestComp.ts'), 'utf8');
|
|
assert.ok(written.includes('export class TestComp extends Component'));
|
|
assert.ok(written.includes('speed: number = 10'));
|
|
} finally {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('create_script rejects non-.ts savePath', async () => {
|
|
const createSchema = () => ({ type: 'object', properties: {} });
|
|
const getRuntimeContext = () => ({ projectPath: '/tmp' });
|
|
const tools = createScriptTools({ createSchema, getRuntimeContext });
|
|
await assert.rejects(
|
|
() => tools[0].handler({ scriptName: 'X', savePath: 'assets/scripts/X.js' }),
|
|
/must end with \.ts/
|
|
);
|
|
});
|
|
|
|
test('create_script requires scriptName and savePath', async () => {
|
|
const createSchema = () => ({ type: 'object', properties: {} });
|
|
const getRuntimeContext = () => ({ projectPath: '/tmp' });
|
|
const tools = createScriptTools({ createSchema, getRuntimeContext });
|
|
await assert.rejects(() => tools[0].handler({ savePath: 'X.ts' }), /scriptName is required/);
|
|
await assert.rejects(() => tools[0].handler({ scriptName: 'X' }), /savePath is required/);
|
|
});
|
|
|
|
test('create_script plain template writes minimal class', async () => {
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'funplay-script-test-'));
|
|
try {
|
|
const createSchema = () => ({ type: 'object', properties: {} });
|
|
const getRuntimeContext = () => ({ projectPath: tmpDir });
|
|
const tools = createScriptTools({ createSchema, getRuntimeContext });
|
|
|
|
const result = await tools[0].handler({
|
|
scriptName: 'Helper',
|
|
savePath: 'assets/scripts/Helper.ts',
|
|
template: 'plain',
|
|
});
|
|
|
|
assert.equal(result.template, 'plain');
|
|
const written = fs.readFileSync(path.join(tmpDir, 'assets', 'scripts', 'Helper.ts'), 'utf8');
|
|
assert.ok(written.includes('export class Helper'));
|
|
assert.ok(!written.includes('@ccclass'));
|
|
} finally {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
}
|
|
});
|