Files
funplay-cocos-mcp/test/scripts.test.js
T
mingyuansi b65a4f22c3 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
2026-06-30 21:53:06 +08:00

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 });
}
});