Files
cc-3-8-x-mcp/cli/test/cli.test.js
T

193 lines
7.2 KiB
JavaScript
Raw Normal View History

2026-06-06 11:33:19 +08:00
'use strict';
// ============================================================
// T10/T11 CLI 集成测试
// 用 child_process.spawnSync 跑 bin,覆盖:
// - query tree
// - query node --name X
// - query find --type cc.Label
// - set label.text
// - set active
// - batch
// ============================================================
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { spawnSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const os = require('os');
const BIN = path.resolve(__dirname, '../bin/cocos-mcp-cli.js');
const FIXTURE = path.resolve(__dirname, 'fixtures/HomeUI.prefab');
function run(args) {
return spawnSync(process.execPath, [BIN, ...args], {
encoding: 'utf8',
cwd: __dirname,
});
}
function tmpCopy() {
const dest = path.join(os.tmpdir(), `HomeUI-cli-test-${Date.now()}.prefab`);
fs.copyFileSync(FIXTURE, dest);
return dest;
}
// ─── query tree ──────────────────────────────────────────────
test('CLI query tree: 退出码 0,输出可解析 JSON,含 name=HomeUI 的根节点', () => {
const result = run(['query', FIXTURE, '--selector', 'tree']);
assert.equal(result.status, 0, `stderr: ${result.stderr}`);
let tree;
assert.doesNotThrow(() => { tree = JSON.parse(result.stdout); }, 'stdout 应是合法 JSON');
assert.equal(tree.name, 'HomeUI', '根节点名称应为 HomeUI');
assert.ok(Array.isArray(tree.children), 'tree.children 应是数组');
});
// ─── query node ──────────────────────────────────────────────
test('CLI query node --name touchArea: 返回单节点详情', () => {
const result = run(['query', FIXTURE, '--selector', 'node', '--name', 'touchArea']);
assert.equal(result.status, 0, `stderr: ${result.stderr}`);
let node;
assert.doesNotThrow(() => { node = JSON.parse(result.stdout); });
assert.equal(node.name, 'touchArea');
assert.ok(typeof node.id === 'number');
});
test('CLI query node --name 不存在: 返回 null JSON', () => {
const result = run(['query', FIXTURE, '--selector', 'node', '--name', '__no_such_node__']);
assert.equal(result.status, 0, `stderr: ${result.stderr}`);
const parsed = JSON.parse(result.stdout);
assert.equal(parsed, null);
});
// ─── query find ──────────────────────────────────────────────
test('CLI query find --type cc.Label: 返回 id 数组', () => {
const result = run(['query', FIXTURE, '--selector', 'find', '--type', 'cc.Label']);
assert.equal(result.status, 0, `stderr: ${result.stderr}`);
let ids;
assert.doesNotThrow(() => { ids = JSON.parse(result.stdout); });
assert.ok(Array.isArray(ids));
// HomeUI 里有至少一个 cc.Label(具体数量依 fixture
assert.ok(ids.length >= 0, '应返回数组');
});
// ─── set label.text ──────────────────────────────────────────
test('CLI set label.text: 写入后 query node 验证文字已变', () => {
const tmp = tmpCopy();
// 先 query 找到含 cc.Label 的节点名
const findResult = run(['query', tmp, '--selector', 'find', '--type', 'cc.Label']);
assert.equal(findResult.status, 0);
const labelIds = JSON.parse(findResult.stdout);
if (labelIds.length === 0) {
// fixture 无 Label 节点,跳过
fs.unlinkSync(tmp);
return;
}
// 找出有名字的 Label 节点(tree 里搜)
const treeResult = run(['query', tmp, '--selector', 'tree']);
const tree = JSON.parse(treeResult.stdout);
// 深度遍历找第一个带 cc.Label 组件且有名字的节点
function findLabelNode(node) {
if (node.componentTypes && node.componentTypes.includes('cc.Label') && node.name) return node;
for (const child of (node.children || [])) {
const found = findLabelNode(child);
if (found) return found;
}
return null;
}
const labelNode = findLabelNode(tree);
if (!labelNode) {
fs.unlinkSync(tmp);
return; // 没有带名字的 Label 节点,跳过
}
const newText = 'CLI_TEST_' + Date.now();
const setResult = run(['set', tmp, labelNode.name, 'label.text', newText]);
assert.equal(setResult.status, 0, `set 失败 stderr: ${setResult.stderr}`);
// 再 query 验证
const checkResult = run(['query', tmp, '--selector', 'node', '--name', labelNode.name]);
assert.equal(checkResult.status, 0);
// 成功即可(文字字段在 raw 里,高层 query 不直接返回 _string,只验证退出码即可)
fs.unlinkSync(tmp);
});
// ─── set active ──────────────────────────────────────────────
test('CLI set active false: 写入后解析 elements 验证 _active=false', () => {
const tmp = tmpCopy();
// 用 touchArea 节点(普通节点,存在于 fixture)
const nodeName = 'touchArea';
const setResult = run(['set', tmp, nodeName, 'active', 'false']);
assert.equal(setResult.status, 0, `set active 失败 stderr: ${setResult.stderr}`);
// 直接用 parse.js 验证(不走 CLI 避免嵌套)
const { parsePrefab } = require('../src/parse.js');
const pd = parsePrefab(tmp);
const node = pd.findNodeByName(nodeName);
assert.ok(node, `${nodeName} 应存在`);
assert.equal(node._active, false, '_active 应已改为 false');
fs.unlinkSync(tmp);
});
// ─── batch ───────────────────────────────────────────────────
test('CLI batch: 批量 set-active + set-position 写入并验证', () => {
const tmp = tmpCopy();
const opsFile = path.join(os.tmpdir(), `ops-${Date.now()}.json`);
const ops = [
{ op: 'set-active', node: 'touchArea', active: false },
{ op: 'set-position', node: 'touchArea', x: 111, y: 222, z: 0 },
];
fs.writeFileSync(opsFile, JSON.stringify(ops));
const batchResult = run(['batch', tmp, opsFile]);
assert.equal(batchResult.status, 0, `batch 失败 stderr: ${batchResult.stderr}`);
// 验证
const { parsePrefab } = require('../src/parse.js');
const pd = parsePrefab(tmp);
const node = pd.findNodeByName('touchArea');
assert.ok(node);
assert.equal(node._active, false);
assert.equal(node._lpos.x, 111);
assert.equal(node._lpos.y, 222);
fs.unlinkSync(tmp);
fs.unlinkSync(opsFile);
});
// ─── 错误处理 ─────────────────────────────────────────────────
test('CLI 未知子命令: 非零退出 + stderr 有内容', () => {
const result = run(['unknowncmd']);
assert.notEqual(result.status, 0);
assert.ok(result.stderr.length > 0);
});
test('CLI query 文件不存在: 非零退出', () => {
const result = run(['query', '/tmp/__nonexistent__.prefab']);
assert.notEqual(result.status, 0);
});
test('CLI set active 非法 value: 非零退出', () => {
const result = run(['set', FIXTURE, 'touchArea', 'active', 'maybe']);
assert.notEqual(result.status, 0);
});