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