mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-05 16:57:32 +00:00
## 迁移了什么
chat_ack_timeout_test.go → test/
chat_attachment_img_test.go → test/
cmd_history_render_test.go → test/
config_namespace_test.go → test/
dashboard_tabs_deps_test.go → test/
plugin_status_render_test.go → test/
starmap_perf_test.go → test/
包名 `webui` → `test`。`webui/` 目录少 7 个测试文件。
## 为什么是这 7 个(分类依据)
先按「对未导出符号的依赖数」把 29 个测试文件测了一遍,这 7 个是
**0 依赖**(既不用 handleXxx,也不用 testSDK/newTestHandler 等测试辅助)。
其余 22 个按强度递增留在包内,后续逐步处理。
## 新增两个导出入口
### webui.FrontendAsset(name)
返回打包进二进制的前端资源原文(dashboard.js / dashboard.css /
mascot.webp / logo.svg …)。
判据要检查「实际会被发出去的那份文本」:dashboard.js 在严格模式下
会不会抛 ReferenceError(用 node 真跑)、mascot.webp 的魔数是否合法、
HTML 属性里有没有残留转义引号。
这些无法通过 HTTP 端点可靠断言(要登录态、且拿到的是拼接后整页,
定位不到单个 asset)。而留在包内测就得读 `dashboardFS` 这个**未导出变量**
—— 那正是"测试耦合实现"。导出后,判据变成「输入 asset 名 → 输出字节」,
与资源存在哪个变量、怎么 embed 无关。
### webui.DashboardPage()
返回组装完成的整页 HTML(外壳 + 内联 CSS/JS)—— 浏览器实际收到的那份。
与 FrontendAsset 的分工:那个取单个源资产,这个取成品。
## 顺带修掉迁移暴露的两个隐患
① 三个判据原本 `os.ReadFile("dashboard.js")` 读**磁盘相对路径**。
在包内恰好能跑(工作目录就是包目录),但那读的是**工作区文件**,
不是**二进制里实际嵌入的那份** —— 两者在生产上可能不同
(比如打包后 dashboard.js 被替换)。统一改走 FrontendAsset。
② starmap_perf_test 的相对路径 `../../../shared/starmap/layout.js`
与 `static/starmap-layout-worker.js` 在移入子目录后失效,已修正为
`../../../../shared/...` 与 `../static/...`。
## 判据仍有效(变异验证)
把 dashboard.js 里 ack 超时 `}, 600000);` 改成 `}, 30000);`:
--- FAIL: TestChatAckTimeoutIsGenerous
★ 受理超时只有 30000 ms —— 太小。
超时太短会让前端 abort,而 agent 仍在正常干活
恢复后全绿。迁移不是搬完就废。
## 验证
go build ./... PASS
go vet ./... PASS
gofmt -l 干净
go test ./... -count=1 63 包全过,0 FAIL
(webui/test 是新包,故 62 → 63)
干净检出(worktree) build + webui/... 全绿
## 后续
webui 剩余 22 个文件(依赖 handleXxx / testSDK 等),按强度递增处理
agent/core 739 处白盒引用,先分类再动
CI 补 coverage + staticcheck
94 lines
4.5 KiB
Go
94 lines
4.5 KiB
Go
package test
|
||
|
||
// 聊天图片附件的 HTML 必须**能被浏览器正确切分**(2026-10-30)。
|
||
//
|
||
// ## 缺陷现场
|
||
//
|
||
// 生产截图:图片明明加载成功了(img 元素在、src 正确),界面上却多出一行
|
||
// 蓝色文字「图片加载失败"/>」。
|
||
//
|
||
// 根因是内联事件属性里用了反斜杠转义:
|
||
//
|
||
// onerror="this.parentElement.innerHTML='<span class=\"att-err\">图片加载失败</span>'"
|
||
//
|
||
// **HTML 属性值里没有反斜杠转义** —— 那个 \" 就是引号本身,属性在
|
||
// `<span class=` 之后就闭合了。浏览器切出来的是:
|
||
//
|
||
// onerror = "this.parentElement.innerHTML='<span class="
|
||
//
|
||
// 剩下的 `att-err">图片加载失败</span>'"/>` 变成**文本节点**直接显示。
|
||
// 而那个 onerror 因为语法错误永远不会执行 —— 真失败时反而什么都不显示。
|
||
//
|
||
// ## 为什么这条判据值得写
|
||
//
|
||
// 这种缺陷**不会让任何构建或测试失败**:JSON 合法、JS 语法合法、
|
||
// 字符串拼接合法。它只在浏览器里错,而且症状("加载成功却显示加载失败")
|
||
// 指向的方向与真因(引号归属)完全不同。
|
||
//
|
||
// 判据做法:按正则把 HTML 里所有 `name="value"` 属性切出来,
|
||
// 断言每个值内部**不含裸引号**,并断言页面里没有残留的 `"/>` 文本片段。
|
||
|
||
import (
|
||
"regexp"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// attrRe 匹配 HTML 属性。用它模拟浏览器"走到下一个引号就闭合"的行为。
|
||
var attrRe = regexp.MustCompile(`([a-zA-Z_:][-a-zA-Z0-9_:.]*)="([^"]*)"`)
|
||
|
||
// TestChatAttachmentImgHasNoBrokenInlineHandler 钉住附件图片的 HTML 形态。
|
||
func TestChatAttachmentImgHasNoBrokenInlineHandler(t *testing.T) {
|
||
js := stripJSComments(dashboardJS(t))
|
||
if !strings.Contains(js, "chat-attachment-img") {
|
||
t.Fatal("dashboardJS 里找不到 chat-attachment-img —— 附件图片渲染被改名或删了?")
|
||
}
|
||
|
||
// ① 内联 onerror 不能再出现:它是本缺陷的载体。
|
||
//
|
||
// 真正的失败处理已改为**捕获阶段的事件委托**(error 事件不冒泡,
|
||
// 只能在捕获阶段拿到)。所以这里断言"没有内联 onerror"是合理的收紧,
|
||
// 而不是"不许处理错误"。
|
||
if strings.Contains(js, `onerror="this.parentElement`) {
|
||
t.Error("★ 附件图片仍在用内联 onerror —— " +
|
||
"HTML 属性不支持反斜杠转义,属性会被提前闭合、剩余部分变成可见文本。" +
|
||
"请用捕获阶段的 error 事件委托(见 renderChat 里的 addEventListener(\"error\", ..., true))")
|
||
}
|
||
|
||
// ② 失败占位必须走 DOM API,不能拼 innerHTML 字符串。
|
||
//
|
||
// innerHTML 拼接是这条缺陷的来源(要在字符串里同时处理 HTML 引号、
|
||
// JS 引号、CSS 选择器)。用 createElement + textContent 只需处理数据。
|
||
if !strings.Contains(js, `span.className = "att-err"`) {
|
||
t.Error("找不到 .att-err 的 DOM 构造 —— 失败占位应改用 createElement/textContent")
|
||
}
|
||
|
||
// ③ 必须有捕获阶段的 error 委托(缺了它,图片失败时用户看到破图无提示)。
|
||
//
|
||
// 注意:error 事件不冒泡,`addEventListener("error", fn)` 不带 true
|
||
// 是**收不到** img 加载失败的 —— 这条注释是为了让改的人别再踩。
|
||
if !regexp.MustCompile(`addEventListener\(\s*"error"`).MatchString(js) {
|
||
t.Error("缺少 error 事件监听 —— 图片加载失败时不会显示占位")
|
||
}
|
||
}
|
||
|
||
// TestDashboardNoEscapedQuoteInHTMLAttributes 是**通用**的同类缺陷扫描。
|
||
//
|
||
// 它不针对某一行,而是扫整个 dashboard.js:凡是形如 `="...\"..."` 的
|
||
// 拼接,都会在浏览器里被提前闭合。
|
||
//
|
||
// 这条判据的价值在于:下次别人再写内联事件(onclick/onerror/onload…)
|
||
// 并试图用 \" 转义时,这里会红,而不是等到生产上出现一行诡异的可见文本。
|
||
func TestDashboardNoEscapedQuoteInHTMLAttributes(t *testing.T) {
|
||
js := stripJSComments(dashboardJS(t))
|
||
// 找 `="` 之后到下一个 `"` 之间出现反斜杠转义引号的片段。
|
||
// 注意要排除 JS 字符串里的普通转义(如 "a\"b" 作为纯数据),
|
||
// 所以只匹配**紧跟在 HTML 属性名后面**的形态:空白 + 名字 + = "
|
||
bad := regexp.MustCompile(`[a-zA-Z-]+="[^"]*\\"`)
|
||
if m := bad.FindString(js); m != "" {
|
||
t.Errorf("★ 发现 HTML 属性内用反斜杠转义引号(浏览器会提前闭合属性):\n %s\n"+
|
||
"HTML 属性值里没有转义序列 —— 需要嵌套引号时改用单引号包属性值,"+
|
||
"或改用 DOM API / 事件委托。", m)
|
||
}
|
||
}
|