Files
HomeAgent/internal/plugins/webui/kernel_buttons_render_test.go
JianFeeeee b8ec1e84d2 refactor(webui): 再迁 3 个判据到 test/ —— 去重辅助函数
## 迁移

    dashboard_js_scope_test.go  → webui/test/
    embed_read_test.go          → webui/test/
    mascot_asset_test.go        → webui/test/

这三个原先都直接读未导出的 `dashboardFS`(`go:embed` 的变量)。
上一批已经加了导出入口 `FrontendAsset(name)`,所以只需换调用即可,
不涉及行为改动。

## 迁移暴露的三处重复(一并去重)

迁过去之后编译报了三组重复定义:

    stripJSComments      helpers_test.go 与 dashboard_js_scope_test.go 各一份
    dashboardJSSource    helpers_test.go 与 dashboard_js_scope_test.go 各一份
    mustReadEmbedded     embed_read_test.go 里定义,kernel_buttons_render_test.go 在用

前两个是**迁移造成的镜像**:原本包内的辅助函数在迁到 test/ 时被复制了一份。
逐字比较确认两份 `stripJSComments` 实现完全相同,删掉包内那份、保留
`test/helpers_test.go` 里的共享版本。

第三个不同:`mustReadEmbedded` 只有包内的 `kernel_buttons_render_test.go` 在用。
把它改成直接调导出的 `FrontendAsset`,不再需要这个辅助函数 ——
包内测试从此也不再依赖"哪个变量装着 embed 资源"这种实现细节。

## 顺带清掉一批未使用导入

批量迁移+脚本改包名之后,多个文件留着已经不用的 `webui` 导入
(有的只在注释里提到 `webui.FrontendAsset`)。用 `go vet` 的输出逐个清理,
最后 `go vet ./internal/plugins/webui/...` 干净。

## 判据未失效(变异验证)

把 dashboard.js 里 ack 超时 `}, 600000);` 改成 `}, 30000);`:

    --- FAIL: TestChatAckTimeoutIsGenerous
        chat_ack_timeout_test.go:59: ★ 受理超时只有 30000 ms —— 太小。

恢复后全绿。

## 进度

    webui/        180 条(原 186,迁走 6 条)
    webui/test/    22 条
    合计          202 条,与迁移前一致(+ 之前新增的契约判据)

`webui/` 剩下 20 个测试文件,全部依赖 `handleXxx` / `newTestHandler` /
`testSDK` 等未导出 handler 与装配辅助,需要先有导出入口才能迁。
其中 `proxy_test.go`(45 处)和 `handler_test.go`(90 处)是最大两块,
要迁必须先给 webui 导出"起一个带路由的 handler"的测试入口。

## 验证

    go build ./...                          PASS
    go vet ./internal/plugins/webui/...     PASS
    gofmt -l                                干净
    go test ./... -count=1                  65 包全过,0 FAIL
    webui 连跑 3 次                          0 失败
2026-11-04 20:23:34 +08:00

88 lines
3.1 KiB
Go
Raw 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.

package webui
// 内核按钮必须**真的出现在渲染后的页面里**(2026-10-17)。
//
// ## 为什么这条判据必须存在
//
// dashboard.html 与 dashboard.js 都是 `//go:embed` 进二进制的,然后由
// init 组装成 dashboardHTML(把 {{DASHBOARD_JS}} 等占位符替换成实际内容)。
// 这里有两个**各自都可能坏掉**的环节:
//
// 1. 按钮写进了 dashboard.html,但占位符没被替换/替换错位
// ⇒ 页面里看不到按钮
// 2. JS 函数写进了 dashboard.js,但它被包在 IIFE 里(不在全局)
// ⇒ onclick="kernelRestart()" 点了报 "kernelRestart is not defined"
//
// 只查源码有没有那个字符串,这两种情况都照不出来。
// 所以判据查的是**组装后的 dashboardHTML**(就是实际发给浏览器的字节)。
import (
"strings"
"testing"
)
// ★★★ 判据:渲染后的页面含两个按钮的 id 与 onclick。
func TestKernelButtonsRenderedInPage(t *testing.T) {
h := dashboardHTML
if h == "" {
t.Fatal("dashboardHTML 为空 —— init 没跑或 embed 失败")
}
for _, want := range []struct {
what string
s string
}{
{"重启按钮 id", `id="kernel-restart-btn"`},
{"关机按钮 id", `id="kernel-shutdown-btn"`},
{"重启按钮 onclick", `onclick="kernelRestart()"`},
{"关机按钮 onclick", `onclick="kernelShutdown()"`},
} {
if !strings.Contains(h, want.s) {
t.Fatalf("★ 渲染页面缺少%s(%q)—— 用户在界面上看不到它", want.what, want.s)
}
}
}
// ★★★ 判据:JS 函数必须在**全局作用域**(onclick 才找得到)。
//
// 检查方式:函数定义前不能有包裹它的 IIFE 缩进层级 —— 用"行首即 function"
// 判定它处于顶层。jsdom 里的行为测试做不到这点(node 不强求),
// 而浏览器里 onclick 走的是全局查找。
func TestKernelFunctionsAreGlobal(t *testing.T) {
js, err := FrontendAsset("dashboard.js")
if err != nil {
t.Fatalf("读 dashboard.js: %v", err)
}
src := string(js)
for _, fn := range []string{"kernelRestart", "kernelShutdown", "doKernelControl", "waitForKernelBack"} {
// 顶层定义:行首(可有 async 前缀),不带缩进
if !strings.Contains(src, "\nasync function "+fn+"(") &&
!strings.Contains(src, "\nfunction "+fn+"(") {
t.Fatalf("★ %s 不是顶层函数定义 —— "+
"若它被包在 IIFE/模块里,按钮 onclick 会报 is not defined", fn)
}
}
}
// ★★★ 判据:两个按钮都必须在 nav-footer 里(不是被误放到某个标签页内)。
//
// 它们作用于整个服务,任何标签页都该能点到。
func TestKernelButtonsInNavFooter(t *testing.T) {
h := dashboardHTML
navIdx := strings.Index(h, `class="nav-footer"`)
if navIdx < 0 {
t.Fatal("找不到 nav-footer")
}
navEnd := strings.Index(h[navIdx:], "</nav>")
if navEnd < 0 {
t.Fatal("找不到 nav 结束标签")
}
footer := h[navIdx : navIdx+navEnd]
for _, id := range []string{"kernel-restart-btn", "kernel-shutdown-btn"} {
if !strings.Contains(footer, id) {
t.Fatalf("★ %s 不在 nav-footer 内 —— 用户切到别的标签页就找不到它了", id)
}
}
}