@mrrisega/dsh-remote 0.7.11-beta.3 → 0.7.12-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/clients/dsh-remote/dingtalk-bot.mjs +1318 -0
- package/clients/dsh-remote/docs/feishu-bot-contract.md +177 -0
- package/clients/dsh-remote/dsh-bridge.mjs +398 -80
- package/clients/dsh-remote/feishu-bot.mjs +1668 -0
- package/clients/dsh-remote/im-format.mjs +372 -0
- package/clients/dsh-remote/mirror-diag-script.js +53 -14
- package/clients/dsh-remote/mobile-adapter.mjs +355 -40
- package/clients/dsh-remote/test/bridge-http-abort.test.mjs +143 -0
- package/clients/dsh-remote/test/bridge-slow-response.test.mjs +107 -0
- package/clients/dsh-remote/test/bridge-tunnel-receiver.test.mjs +135 -0
- package/clients/dsh-remote/test/dingtalk-bot.test.mjs +550 -0
- package/clients/dsh-remote/test/feishu-bot.test.mjs +839 -0
- package/clients/dsh-remote/test/im-format.test.mjs +229 -0
- package/clients/dsh-remote/test/mirror-diag.test.mjs +47 -2
- package/clients/dsh-remote/test/mobile-adapter-load-watch.test.mjs +208 -9
- package/clients/dsh-remote/test/upstream-discovery.test.mjs +203 -2
- package/clients/dsh-remote/test/wecom-bot.test.mjs +281 -1
- package/clients/dsh-remote/upstream-discovery.mjs +495 -43
- package/clients/dsh-remote/wecom-bot.mjs +238 -12
- package/dsh-setup.mjs +78 -7
- package/package.json +2 -2
- package/packages/dsh-remote-web/lib/client.js +703 -115
- package/packages/dsh-remote-web/lib/index.js +487 -31
- package/packages/dsh-remote-web/package.json +1 -1
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/dsh-bridge.mjs +7 -1
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/im-format.mjs +372 -0
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/upstream-discovery.mjs +495 -43
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/wecom-bot.mjs +238 -12
- package/packages/dsh-remote-web/runtime/dsh-setup.mjs +94 -8
- package/packages/dsh-remote-web/test/im-bots.test.mjs +265 -2
- package/packages/dsh-remote-web/test/login-fast.test.mjs +104 -0
- package/packages/dsh-remote-web/test/login-selfheal-ui.test.mjs +4 -1
- package/packages/dsh-remote-web/test/panel-ia.test.mjs +397 -34
- package/packages/dsh-remote-web/test/purchase-agreement-ui.test.mjs +4 -4
- package/packages/dsh-remote-web/test/remote-access-ui.test.mjs +51 -113
- package/packages/dsh-remote-web/test/self-manage.test.mjs +5 -1
- package/packages/dsh-remote-web/test/settings-entry.test.mjs +13 -5
- package/packages/dsh-remote-web/test/telemetry.test.mjs +2 -2
|
@@ -178,7 +178,9 @@ test("📱 远程访问卡:bridge 在线 → 绿点文案;登录态点「生
|
|
|
178
178
|
|
|
179
179
|
// 状态行:bridge 运行 → “已连接(可远程访问)”
|
|
180
180
|
assert.ok(textHas(tree, "已连接(可远程访问)"), "bridge 在线状态文案应显示");
|
|
181
|
-
|
|
181
|
+
// A1(2026-11-07 业主截图):正文里不再重复「远程访问」标题 —— 顶部与弹层标题已经表达了。
|
|
182
|
+
assert.equal(find(tree, (n) => n.children?.includes("📱 远程访问")), undefined,
|
|
183
|
+
"正文里不得再有重复的「远程访问」卡片标题(业主截图框出的那行)");
|
|
182
184
|
|
|
183
185
|
// 未生成 key 的空态按钮 → 点击触发 /dsh-remote/access-key
|
|
184
186
|
const genBtn = find(tree, (n) => (n.children || []).some((c) => typeof c === "string" && c.includes("生成访问链接")));
|
|
@@ -422,130 +424,66 @@ test("E2EE 徽标:未登录 / host 未下发 e2ee → 不打扰(不渲染状
|
|
|
422
424
|
assert.equal(e2eeLine(oldHost.render()), undefined, "未下发 service.e2ee 时不应渲染状态行");
|
|
423
425
|
});
|
|
424
426
|
|
|
425
|
-
test("
|
|
426
|
-
// 收纳态(localStorage 里记着用户关掉过引导)=「更多」里会出现那两条折叠入口的场景
|
|
427
|
+
test("账号弹层平铺(A2):所有功能直接可见,不再有「更多」折叠;且不渲染档位数值", () => {
|
|
427
428
|
const plugin = loadPlugin({ autoModal: "account", localStorageSeed: { "dsh-remote-marketing-more-v1": "1" } });
|
|
428
429
|
plugin.states[0] = { config: { phone: "13800000000", deviceId: "dev-x" }, service: { running: true } };
|
|
429
|
-
|
|
430
|
+
const tree = plugin.render();
|
|
430
431
|
|
|
431
|
-
//
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
assert.equal(
|
|
435
|
-
|
|
436
|
-
tree = plugin.render();
|
|
437
|
-
const body = find(tree, (n) => n.props?.className === "dru-more-body");
|
|
438
|
-
assert.ok(body, "「更多」内容体应在 DOM 里");
|
|
439
|
-
assert.equal(body.props.hidden, false, "点击后应展开(hidden=false)");
|
|
440
|
-
|
|
441
|
-
// 【0.6.11】档位明细整块删除:标题「套餐与额度」、PRO / Pro Max 规格行、价格与结论句都不再渲染
|
|
442
|
-
assert.ok(!textHas(body, "套餐与额度"), "「套餐与额度」标题应删除");
|
|
443
|
-
assert.ok(!textHas(body, "Mbps"), "不得再渲染带宽数值(用户约定:本地面板不展示 PRO 版本流量带宽)");
|
|
444
|
-
assert.ok(!textHas(body, "GB"), "不得再渲染流量数值");
|
|
445
|
-
assert.ok(!textHas(body, "¥"), "不得再渲染价格");
|
|
446
|
-
assert.ok(!textHas(body, "以套餐页为准"), "随档位明细一起删掉的结论句不得残留");
|
|
447
|
-
// 整张账号卡同样一个规格数字都不许有
|
|
448
|
-
assert.ok(!textHas(tree, "Mbps"), "账号卡里不得出现 Mbps");
|
|
449
|
-
assert.ok(!textHas(tree, "¥"), "账号卡里不得出现价格");
|
|
450
|
-
|
|
451
|
-
// 2026-10-17 版式简化(业主):正面只留「升级/续费 + 用户反馈 + 更多」;
|
|
452
|
-
// 「带新用户换会员 / 修改密码」收进「更多」。升级入口**永远在正面**(不再随收纳状态搬家)。
|
|
453
|
-
const upsell = find(tree, (n) => n.type === "button" && (n.children || []).some((c) => typeof c === "string" && c.includes("升级 PRO")));
|
|
454
|
-
const invite = find(body, (n) => n.type === "button" && (n.children || []).some((c) => typeof c === "string" && c.includes("带新用户换会员")));
|
|
455
|
-
assert.ok(upsell, "正面应保留「🚀 升级 PRO」入口(主行动,位置稳定)");
|
|
456
|
-
assert.ok(invite, "「更多」里应保留「🎁 带新用户换会员」入口");
|
|
457
|
-
assert.equal(typeof upsell.props.onClick, "function");
|
|
458
|
-
assert.equal(typeof invite.props.onClick, "function");
|
|
459
|
-
// 正面不应再出现「带新用户换会员」(已收进「更多」)
|
|
460
|
-
const actions = find(tree, (n) => n.props?.className === "dru-actions");
|
|
461
|
-
assert.ok(actions, "应有主按钮行");
|
|
462
|
-
assert.ok(!textHas(actions, "带新用户换会员"), "带新用户入口不再占正面按钮行");
|
|
463
|
-
// 「更多」现在与主行动同排:按钮本体带 .dru-btn(原来的整宽 .dru-disclose 披露条已去掉)
|
|
464
|
-
const moreBtn = find(tree, (n) => n.props?.id === "dru-account-more");
|
|
465
|
-
assert.ok(moreBtn, "「更多」按钮应存在");
|
|
466
|
-
assert.match(String(moreBtn.props.className || ""), /dru-btn/, "「更多」必须与主行动同排(按钮样式,不再是整宽披露条)");
|
|
467
|
-
assert.doesNotMatch(String(moreBtn.props.className || ""), /dru-disclose(?!-inline)/, "不得再继承整宽披露条样式");
|
|
468
|
-
|
|
469
|
-
// 源码级:函数与其调用都删掉了(不留死代码)。
|
|
470
|
-
// ⚠️ 断言「代码形态」而不是裸标识符 —— 注释里**故意**留着「这里原本是什么、为什么删」的说明
|
|
471
|
-
// (与 settings-entry.test.mjs 的同款约定:注释里允许出现说明文字)。
|
|
472
|
-
assert.doesNotMatch(SOURCE, /function renderPlanDetail\(/, "renderPlanDetail 函数必须删除");
|
|
473
|
-
assert.doesNotMatch(SOURCE, /^\s*renderPlanDetail\(\),/m, "renderPlanDetail 的调用点必须删除");
|
|
474
|
-
assert.doesNotMatch(SOURCE, /function planSpecText\(/, "planSpecText 定义必须删除");
|
|
475
|
-
assert.doesNotMatch(SOURCE, /planSpecText\("/, "planSpecText 的调用必须删除");
|
|
476
|
-
assert.doesNotMatch(SOURCE, /function freeMbpsOf\(/, "freeMbpsOf 定义必须删除");
|
|
477
|
-
assert.doesNotMatch(SOURCE, /=\s*freeMbpsOf\(\)/, "freeMbpsOf 的调用必须删除");
|
|
478
|
-
assert.doesNotMatch(SOURCE, /var FREE_FACTS\s*=/, "FREE_FACTS 定义必须删除(只被 freeMbpsOf 使用)");
|
|
479
|
-
assert.doesNotMatch(SOURCE, /" Mbps"|" GB\/月,¥"/, "不得残留渲染规格数字的字符串拼接");
|
|
480
|
-
// 不变量仍在:planFactsOf 仍被 upgradePath 用来取档位展示名(不是死代码)
|
|
481
|
-
assert.match(SOURCE, /var name = planFactsOf\(key\)\.name;/);
|
|
482
|
-
});
|
|
432
|
+
// 不再有「更多」披露按钮 / 折叠体(业主:账号弹层不要折叠,空间足够)
|
|
433
|
+
assert.equal(find(tree, (n) => n.props?.id === "dru-account-more"), undefined,
|
|
434
|
+
"账号弹层不得再有「更多」折叠按钮");
|
|
435
|
+
assert.equal(find(tree, (n) => n.props?.className === "dru-more-body"), undefined,
|
|
436
|
+
"账号弹层不得再有「更多」折叠体");
|
|
483
437
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
assert.doesNotMatch(SOURCE, /等待服务端开启 E2EE/); // 已正式开启,不再出现“灰度等待”措辞
|
|
490
|
-
assert.match(SOURCE, /当前为普通安全连接(HTTPS)/);
|
|
491
|
-
assert.match(SOURCE, /server_disabled/);
|
|
492
|
-
assert.match(SOURCE, /params_unreachable/);
|
|
493
|
-
assert.match(SOURCE, /disabled_by_config/);
|
|
494
|
-
assert.match(SOURCE, /derive_failed/);
|
|
495
|
-
assert.match(SOURCE, /describeE2ee/);
|
|
496
|
-
assert.match(SOURCE, /dru-e2ee-line/);
|
|
497
|
-
});
|
|
438
|
+
// 各功能直接平铺(无需展开就能看到)
|
|
439
|
+
for (const label of ["用户反馈", "带新用户换会员", "修改密码"]) {
|
|
440
|
+
assert.ok(find(tree, (n) => n.type === "button" && (n.children || []).some((c) => typeof c === "string" && c.includes(label))),
|
|
441
|
+
"应直接平铺按钮: " + label);
|
|
442
|
+
}
|
|
498
443
|
|
|
499
|
-
//
|
|
500
|
-
|
|
444
|
+
// 档位明细/数值始终不渲染(用户约定:本地面板不展示 PRO 版本流量带宽)
|
|
445
|
+
assert.ok(!textHas(tree, "套餐与额度"), "「套餐与额度」标题应删除");
|
|
446
|
+
assert.ok(!textHas(tree, "Mbps"), "不得再渲染带宽数值");
|
|
447
|
+
assert.ok(!textHas(tree, "GB"), "不得再渲染流量数值");
|
|
448
|
+
assert.ok(!textHas(tree, "¥"), "不得再渲染价格");
|
|
449
|
+
});
|
|
501
450
|
|
|
502
|
-
test("
|
|
451
|
+
test("账号弹层:一屏一个主操作(只有升级/续费是 primary);退出登录在状态头(A3)", () => {
|
|
503
452
|
const plugin = loadPlugin({ autoModal: "account" });
|
|
504
453
|
plugin.states[0] = { config: { phone: "13800000000", deviceId: "dev-x" }, service: { running: true } };
|
|
505
454
|
const tree = plugin.render();
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
assert.ok(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const labels = btns.map((b) => JSON.stringify(b.children || []));
|
|
520
|
-
assert.ok(labels.some((t) => t.includes("升级 PRO") || t.includes("续费会员")), "第一个是主行动");
|
|
521
|
-
assert.ok(labels.some((t) => t.includes("用户反馈")), "用户反馈留在正面");
|
|
522
|
-
assert.ok(labels.some((t) => t.includes("更多")), "「更多」与主行动同排");
|
|
523
|
-
|
|
524
|
-
// 退出登录**不在**主按钮行里,而在账号行(.dru-user)右侧
|
|
525
|
-
const logoutInActions = btns.some((b) => (b.children || []).some((c) => c === "退出登录"));
|
|
526
|
-
assert.equal(logoutInActions, false, "退出登录不得再占主按钮行");
|
|
527
|
-
const userRow = find(tree, (n) => n.props?.className === "dru-user");
|
|
528
|
-
assert.ok(userRow, "应有账号行");
|
|
529
|
-
const logout = find(userRow, (n) => n.type === "button" && (n.children || []).includes("退出登录"));
|
|
530
|
-
assert.ok(logout, "退出登录应在账号行里(业主:放到账号那一行旁边)");
|
|
531
|
-
assert.match(String(logout.props.className || ""), /dru-user-logout/);
|
|
455
|
+
|
|
456
|
+
const primary = findAll(tree, (n) => n.type === "button" && String(n.props?.className || "").includes("dru-btn-primary"));
|
|
457
|
+
assert.ok(primary.length <= 1, `一屏最多一个主操作,实际 ${primary.length} 个 primary 按钮`);
|
|
458
|
+
|
|
459
|
+
// 登录/退出登录同一位置:状态头右侧(`.dru-ia-head-actions`)
|
|
460
|
+
const headActions = find(tree, (n) => String(n.props?.className || "").split(/\s+/).includes("dru-ia-head-actions"));
|
|
461
|
+
assert.ok(headActions, "状态头右侧应有按钮组(登录/退出登录与打开手机端同一处)");
|
|
462
|
+
assert.ok(find(headActions, (n) => n.type === "button" && (n.children || []).includes("退出登录")),
|
|
463
|
+
"已登录时状态头应有「退出登录」");
|
|
464
|
+
// 账号弹层内部不再重复一个退出登录按钮
|
|
465
|
+
const dlg = find(tree, (n) => String(n.props?.className || "").includes("dru-ia-dialog"));
|
|
466
|
+
assert.equal(find(dlg, (n) => n.type === "button" && (n.children || []).includes("退出登录")), undefined,
|
|
467
|
+
"退出登录不得在账号弹层里重复出现(同一动作只在一个位置)");
|
|
532
468
|
});
|
|
533
469
|
|
|
534
|
-
test("
|
|
535
|
-
const plugin = loadPlugin(
|
|
536
|
-
|
|
470
|
+
test("未登录:状态头显示「登录 / 注册」;点「打开手机端」直接弹登录弹层并说明原因(A3/A4)", () => {
|
|
471
|
+
const plugin = loadPlugin();
|
|
472
|
+
// 该 harness 无 setSignedIn:直接给状态槽一个"未登录"的 st(useEffect 在本 harness 是空实现)
|
|
473
|
+
plugin.states[0] = { ok: true, config: {}, service: { running: false } };
|
|
537
474
|
let tree = plugin.render();
|
|
538
|
-
const
|
|
539
|
-
assert.ok(
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
assert.ok(
|
|
475
|
+
const headActions = find(tree, (n) => String(n.props?.className || "").split(/\s+/).includes("dru-ia-head-actions"));
|
|
476
|
+
assert.ok(headActions, "未登录也要有状态头按钮组");
|
|
477
|
+
const loginBtn = find(headActions, (n) => n.type === "button" && (n.children || []).some((c) => typeof c === "string" && c.includes("登录 / 注册")));
|
|
478
|
+
assert.ok(loginBtn, "未登录时同一位置显示「登录 / 注册」入口");
|
|
479
|
+
|
|
480
|
+
// 点状态头的「打开手机端」→ 不许静默失败:直接弹登录弹层 + 说明
|
|
481
|
+
const openPhone = find(headActions, (n) => n.type === "button" && (n.children || []).some((c) => typeof c === "string" && c.includes("打开手机端")));
|
|
482
|
+
assert.ok(openPhone, "应有「打开手机端」主按钮");
|
|
483
|
+
openPhone.props.onClick();
|
|
484
|
+
tree = plugin.settle ? plugin.render() : plugin.render();
|
|
485
|
+
assert.ok(find(tree, (n) => String(n.props?.className || "").includes("dru-ia-dialog")), "应弹出登录弹层(不是静默失败)");
|
|
486
|
+
assert.ok(textHas(tree, "登录后才能用手机访问"), "弹层里要说明「登录后才能用手机访问」");
|
|
549
487
|
});
|
|
550
488
|
|
|
551
489
|
test("★ 删除了两段解释性文案:升级带登录态说明 / 重启 DeepSeek Harness 说明", () => {
|
|
@@ -231,7 +231,11 @@ test("源码约束:浏览器半提供版本与更新卡片(含彻底卸载
|
|
|
231
231
|
assert.match(source, /一键更新/);
|
|
232
232
|
assert.match(source, /彻底卸载/);
|
|
233
233
|
assert.match(source, /dsh-remote\/self\/uninstall/);
|
|
234
|
-
|
|
234
|
+
// A7(2026-11-07 业主):彻底卸载前先弹**挽留反馈**(原因单选 + 建议),提交后继续卸载。
|
|
235
|
+
assert.match(source, /为什么要卸载/, "必须询问卸载原因(挽留反馈)");
|
|
236
|
+
assert.match(source, /我再想想/, "必须给「我再想想」的退出选项");
|
|
237
|
+
assert.match(source, /仍要卸载/, "确认按钮必须是「仍要卸载」");
|
|
238
|
+
assert.match(source, /kind: "uninstall"/, "反馈必须复用现有反馈通道(kind=uninstall)");
|
|
235
239
|
});
|
|
236
240
|
|
|
237
241
|
test("★ 用户反馈锁:已是最新版时,修复入口必须**看得出是修复入口**(不能只叫「重新检查」)", () => {
|
|
@@ -238,15 +238,19 @@ test("登录态账号区:无「切换账号」,有「退出登录」,头
|
|
|
238
238
|
let tree = plugin.renderSection();
|
|
239
239
|
|
|
240
240
|
// 栏目头部:标题「远程访问」+ 副文案(人在哪都能用,免公网 IP)
|
|
241
|
-
|
|
242
|
-
assert.ok(find(tree, (n) => n.children?.includes("
|
|
241
|
+
// 2026-11-07 品牌统一:顶部面板标题改为插件全名 dsh-remote-web(「远程访问」保留在副标题/栏目名里)
|
|
242
|
+
assert.ok(find(tree, (n) => n.children?.includes("dsh-remote-web")), "头部标题应为品牌全名 dsh-remote-web");
|
|
243
|
+
assert.ok(find(tree, (n) => (n.children || []).some((c) => typeof c === "string" && c.includes("远程访问"))),
|
|
244
|
+
"副标题仍说明这是「远程访问」能力");
|
|
245
|
+
assert.ok(find(tree, (n) => (n.children || []).some((c) => typeof c === "string" && c.includes("通过手机或另一台电脑远程使用同一份 dsh web,人在哪都能用(免公网 IP)"))),
|
|
246
|
+
"应渲染新副文案(人在哪都能用,免公网 IP)");
|
|
243
247
|
|
|
244
248
|
// 账号区按钮
|
|
245
249
|
assert.ok(find(tree, (n) => n.children?.includes("退出登录")), "应保留「退出登录」");
|
|
246
250
|
assert.ok(!find(tree, (n) => n.children?.includes("切换账号")), "「切换账号」按钮应移除");
|
|
247
251
|
|
|
248
252
|
// 关于 dsh-remote 卡片:在「更多设置」折叠区里 —— 标题不再带 emoji(图标一律内联 SVG)
|
|
249
|
-
assert.ok(find(tree, (n) => n.children?.includes("关于 dsh-remote")), "应渲染「关于 dsh-remote」卡片标题");
|
|
253
|
+
assert.ok(find(tree, (n) => n.children?.includes("关于 dsh-remote-web")), "应渲染「关于 dsh-remote-web」卡片标题");
|
|
250
254
|
// 价值要点仍要在(不要求逐字长文案:IA 重做后关于卡精简成结论句,避免文字堆砌)
|
|
251
255
|
const aboutBits = ["免公网 IP", "端到端加密", "匿名装机统计"];
|
|
252
256
|
for (const bit of aboutBits) {
|
|
@@ -377,11 +381,15 @@ test("源码约束:侧栏入口走官方槽位/无浮动面板/无切换账号
|
|
|
377
381
|
assert.doesNotMatch(SOURCE, /dru-backdrop\{/);
|
|
378
382
|
assert.doesNotMatch(SOURCE, /切换账号/);
|
|
379
383
|
assert.doesNotMatch(SOURCE, /dsh-remote-panel/);
|
|
380
|
-
|
|
384
|
+
// 旧称呼「远程控制」不再作为**面板名字**出现(label/头部/菜单)。
|
|
385
|
+
// 注:2026-11-07 业主新文案「保持在线,才能用手机端远程控制这台电脑」把该词当**动词**用,
|
|
386
|
+
// 因此不再对全文做一刀切禁止;这里锁"仍然叫远程访问"这个真正的约束。
|
|
387
|
+
assert.match(SOURCE, /"远程访问"/);
|
|
388
|
+
assert.doesNotMatch(SOURCE, /"远程控制"|远程控制(/);
|
|
381
389
|
assert.match(SOURCE, /settings\.section/);
|
|
382
390
|
assert.match(SOURCE, /dsh-remote-seen-dot/);
|
|
383
391
|
assert.match(SOURCE, /fbClearThreads/);
|
|
384
|
-
assert.match(SOURCE, /关于 dsh-remote/);
|
|
392
|
+
assert.match(SOURCE, /关于 dsh-remote-web/);
|
|
385
393
|
assert.match(SOURCE, /免公网 IP/); // 关于卡 v0.5+ 价值要点
|
|
386
394
|
assert.match(SOURCE, /端到端/);
|
|
387
395
|
assert.match(SOURCE, /已授权设备/);
|
|
@@ -810,13 +810,13 @@ test("面板可见性:关于卡片写明「匿名统计 + 关闭方式」,
|
|
|
810
810
|
// 现在取片边界是「关于卡片开头 → renderHome 结尾」,并**显式断言切片非空且像样**。
|
|
811
811
|
// IA 重做(2026-11-07):关于卡不再用 card() 助手(标题图标改成内联 SVG,禁止 emoji)→
|
|
812
812
|
// 取片锚点改成"关于卡标题 → 弹层段开头",仍然要求切片非空且真的包含卡片本体。
|
|
813
|
-
const aboutStart = client.indexOf('h("h3", null, "关于 dsh-remote")');
|
|
813
|
+
const aboutStart = client.indexOf('h("h3", null, "关于 dsh-remote-web")');
|
|
814
814
|
assert.ok(aboutStart > 0, "必须能定位到「关于」卡片的开头");
|
|
815
815
|
const aboutEnd = client.indexOf("// ── 弹层内容", aboutStart);
|
|
816
816
|
assert.ok(aboutEnd > aboutStart, "必须能定位到「关于」卡片的结束边界(弹层段开始之前)");
|
|
817
817
|
const about = client.slice(aboutStart, aboutEnd);
|
|
818
818
|
assert.ok(about.length > 400, "「关于」卡片切片不能是空串/碎片(空串会让下面四条断言全部平凡通过)");
|
|
819
|
-
assert.ok(about.includes("关于 dsh-remote"), "切片必须真的包含「关于」卡片本体");
|
|
819
|
+
assert.ok(about.includes("关于 dsh-remote-web"), "切片必须真的包含「关于」卡片本体");
|
|
820
820
|
// ⚠️ 断言的是**渲染出来的文案**:上面那条"不再单列隐私说明行"的解释性注释本身就写着这些词,
|
|
821
821
|
// 所以先剥掉整行注释再断言(注释不会出现在用户屏幕上,不该被当成"面板里放了链接")。
|
|
822
822
|
const aboutText = about.split("\n").filter((l) => !/^\s*\/\//.test(l)).join("\n");
|