合并 origin/main: psText 非 ASCII 修复移植到 rpa-core 新结构
- driver/windows 下助手模块新增导出 psText(远端 1c5d981 修复实现)
- WindowsDriver: 旧 psCharLiteral([char] 相加 bug)改为委托 psText;
窗口查找/激活的错误消息与剪贴板粘贴/SendKeys 内嵌文本统一 psText
- system-extended: 保留 driver 抽象, Clipboard_SetTextV1 走 psText
- window-extended: 取本地 driver 版(远端 findWindow 由 driver 承担)
- src/main/capabilities/desktop.ts 保持删除(已迁移 rpa-core)
- compat-audit.test.ts 迁至 packages/rpa-core/src/capabilities/
This commit is contained in:
commit
3a9a8b63c2
5 files changed
+121
-16
No files matched your search
@@ -0,0 +1,75 @@
|
||||
import { describe, it } from "vitest";
|
||||
import { capabilityIds } from "../engine/registry";
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { readFileSync } from "node:fs";
|
||||
import "./index"; // 汇总导入全部能力模块(与运行时同构)
|
||||
|
||||
interface CatalogComp {
|
||||
id: string;
|
||||
name?: string;
|
||||
fields?: { key: string; label?: string; widget?: string; options?: { label: string; value: string }[]; defaultValue?: unknown }[];
|
||||
}
|
||||
|
||||
describe("系统组件兼容性全量审查", () => {
|
||||
it("注册覆盖 + 面板可见性 + 字段契约核对", async () => {
|
||||
const catalog: CatalogComp[] = JSON.parse(readFileSync("resources/catalog.json", "utf-8")).components ?? [];
|
||||
const dup = JSON.parse(readFileSync("resources/capability-duplicates.json", "utf-8"));
|
||||
const dupMap = new Map<string, { status: string; canonical?: string }>(
|
||||
(Array.isArray(dup) ? dup : []).map((d: { id: string; status: string; canonical?: string }) => [d.id, { status: d.status, canonical: d.canonical }])
|
||||
);
|
||||
const registered = new Set(capabilityIds());
|
||||
|
||||
// 1) catalog 组件 → 运行时注册
|
||||
const unregistered: { id: string; name?: string }[] = [];
|
||||
for (const c of catalog) {
|
||||
if (!registered.has(c.id)) unregistered.push({ id: c.id, name: c.name });
|
||||
}
|
||||
|
||||
// 2) 幽灵注册:handler 有但 catalog 没有(可能是内部别名,不一定是问题,但要列出来)
|
||||
const catalogIds = new Set(catalog.map((c) => c.id));
|
||||
const ghosts: string[] = [];
|
||||
for (const id of registered) {
|
||||
if (!catalogIds.has(id)) ghosts.push(id);
|
||||
}
|
||||
|
||||
// 3) duplicates 一致性:hide 必须有 canonical 且 canonical 已注册;keep/隐藏组件的执行器必须在
|
||||
const dupIssues: string[] = [];
|
||||
for (const [id, info] of dupMap) {
|
||||
if (info.status === "hide" && info.canonical && !registered.has(info.canonical)) {
|
||||
dupIssues.push(`hide 项 ${id} 的 canonical ${info.canonical} 未注册`);
|
||||
}
|
||||
if (info.status === "keep" && !registered.has(id)) {
|
||||
dupIssues.push(`keep 项 ${id} 未注册(面板可见但运行会报未实现)`);
|
||||
}
|
||||
if (info.status === "hide" && !info.canonical && !registered.has(id)) {
|
||||
dupIssues.push(`hide 项 ${id} 无 canonical 且未注册(旧流程节点将无法运行)`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4) 字段契约抽查:select 枚举 defaultValue 必须在 options 里;hidden 字段不能 required
|
||||
const fieldIssues: string[] = [];
|
||||
for (const c of catalog) {
|
||||
for (const f of c.fields ?? []) {
|
||||
if (f.widget === "select" && Array.isArray(f.options) && f.options.length > 0) {
|
||||
const values = f.options.map((o) => String(o.value));
|
||||
if (f.defaultValue !== undefined && f.defaultValue !== "" && !values.includes(String(f.defaultValue))) {
|
||||
fieldIssues.push(`${c.id}.${f.key} select 默认值 "${f.defaultValue}" 不在选项值 [${values.join(",")}] 中`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const report = {
|
||||
catalogTotal: catalog.length,
|
||||
registeredTotal: registered.size,
|
||||
unregisteredCount: unregistered.length,
|
||||
unregistered: unregistered.slice(0, 50),
|
||||
ghostCount: ghosts.length,
|
||||
ghosts: ghosts.slice(0, 40),
|
||||
dupIssues,
|
||||
fieldIssues: fieldIssues.slice(0, 40),
|
||||
fieldIssueCount: fieldIssues.length
|
||||
};
|
||||
writeFileSync(".compat-report.txt", JSON.stringify(report, null, 1), "utf8");
|
||||
}, 120000);
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import type { CatalogComponent, FlowNode } from "../types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import "./desktop";
|
||||
import "./common-final";
|
||||
|
||||
const component = {} as CatalogComponent;
|
||||
const node = (id: string): FlowNode => ({ id: "smoke-kb", componentId: id, position: { x: 0, y: 0 }, values: {}, options: {} });
|
||||
@@ -45,6 +46,27 @@ describe("键盘输入组件真实冒烟", () => {
|
||||
console.log("[键盘输入] 人工模拟+中文标点+智能引号 ✓");
|
||||
}, 60000);
|
||||
|
||||
it("兼容性: Keyboard_Press 组合键语法(^s {ENTER} %{F4})不被文本转义破坏", async () => {
|
||||
// 组合键必须保留 SendKeys 原语法:^=Ctrl %=Alt {ENTER}=回车
|
||||
const out = await invoke("Keyboard_Press", { keys: "^s", interval: "10" });
|
||||
expect(out).toBe("^s");
|
||||
const out2 = await invoke("Keyboard_PressNew", { keys: "{ENTER}%{F4}" });
|
||||
expect(out2).toBe("{ENTER}%{F4}");
|
||||
console.log("[键盘输入] 组合键 SendKeys 语法保留 ✓");
|
||||
}, 60000);
|
||||
|
||||
it("兼容性: Keyboard_DriveSend_V2(键盘驱动输入,委托组件)按文本输入语义转义特殊字符", async () => {
|
||||
const out = await invoke("Keyboard_DriveSend_V2", { words: "驱动(a*100%)", interval: "5", keyboard_type: "1" });
|
||||
expect(out).toBe("驱动(a*100%)");
|
||||
console.log("[键盘输入] DriveSend 委托文本转义 ✓:", out);
|
||||
}, 60000);
|
||||
|
||||
it("兼容性: 基础版 Keyboard_Send interval 秒级换算", async () => {
|
||||
const out = await invoke("Keyboard_Send", { words: "秒", interval: "0.01" });
|
||||
expect(out).toBe("秒");
|
||||
console.log("[键盘输入] 基础版秒级间隔 ✓");
|
||||
}, 60000);
|
||||
|
||||
it("剪切板粘贴模式: 原文进入剪贴板并发送 Ctrl+V", async () => {
|
||||
const content = `粘贴${Date.now()}`;
|
||||
const out = await invoke("Keyboard_Send_V1", { words: content, fuctype: "2" });
|
||||
|
||||
@@ -2,7 +2,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError, type CapabilityHandler } from "../engine/types";
|
||||
import { powershell, psString } from "../driver/windows/powershell";
|
||||
import { powershell, psString, psText } from "../driver/windows/powershell";
|
||||
import { defaultEngine } from "../engine/engine";
|
||||
|
||||
function value(args: Record<string, unknown>, keys: string[], fallback = ""): string {
|
||||
@@ -94,7 +94,7 @@ register(["App_Kill", "App_KillV1"], async (_ctx, node, args) => {
|
||||
register("Clipboard_GetTextV1", () => powershell("Get-Clipboard -Raw"));
|
||||
register("Clipboard_SetTextV1", async (_ctx, _node, args) => {
|
||||
const content = value(args, ["text", "value"]);
|
||||
await powershell(`Set-Clipboard -Value ${psString(content)}`);
|
||||
await powershell(`Set-Clipboard -Value ${psText(content)}`);
|
||||
return content;
|
||||
});
|
||||
register("Clipboard_ClearClipboard", async () => {
|
||||
|
||||
Reference in new issue
Block a user