Files

217 lines
8.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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);
});
test('CLI open --dry-run: accepts positional project and builds --project command', () => {
const project = fs.mkdtempSync(path.join(os.tmpdir(), 'cocos-open-project-'));
fs.mkdirSync(path.join(project, 'assets'));
const result = run(['open', project, '--cocos', process.execPath, '--dry-run']);
assert.equal(result.status, 0, `stderr: ${result.stderr}`);
assert.match(result.stdout, /\[dry-run\]/);
assert.match(result.stdout, /--project/);
assert.match(result.stdout, /--nologin/);
assert.match(result.stdout, new RegExp(project.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')));
fs.rmSync(project, { recursive: true, force: true });
});
test('CLI open --with-login: dry-run omits --nologin', () => {
const project = fs.mkdtempSync(path.join(os.tmpdir(), 'cocos-open-project-'));
fs.mkdirSync(path.join(project, 'assets'));
const result = run(['open', '--project', project, '--cocos', process.execPath, '--with-login', '--dry-run']);
assert.equal(result.status, 0, `stderr: ${result.stderr}`);
assert.doesNotMatch(result.stdout, /--nologin/);
fs.rmSync(project, { recursive: true, force: true });
});