fix: Windows spawn, prefab path, preview API, asset deps, error messages; add execute-context resource, tool descriptions, scene/script/asset tools
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
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
'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 });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user