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 失败
This commit is contained in:
JianFeeeee
2026-11-04 20:23:34 +08:00
parent 06f3161e1d
commit b8ec1e84d2
5 changed files with 18 additions and 78 deletions

View File

@ -48,7 +48,11 @@ func TestKernelButtonsRenderedInPage(t *testing.T) {
// 判定它处于顶层。jsdom 里的行为测试做不到这点(node 不强求),
// 而浏览器里 onclick 走的是全局查找。
func TestKernelFunctionsAreGlobal(t *testing.T) {
src := string(mustReadEmbedded(t, "dashboard.js"))
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 前缀),不带缩进

View File

@ -1,4 +1,4 @@
package webui
package test
import (
"os"
@ -43,15 +43,7 @@ import (
// 判据做法:用 node 在沙箱里把 dashboard.js 跑一遍,抓未捕获异常。
// node 不在时 t.Skip(CI 里 GO 环境可能没有 node)。
// dashboardJSSource 取出待测脚本文本(测试内复用以免重复读 embed)。
func dashboardJSSource(t *testing.T) string {
t.Helper()
b, err := dashboardFS.ReadFile("dashboard.js")
if err != nil {
t.Fatalf("读 dashboard.js: %v", err)
}
return string(b)
}
// dashboardJSSource 见 helpers_test.go(迁移时去重)。
// TestDashboardJS_HasNoReferenceErrorInStrictMode 用 node 跑真脚本抓 ReferenceError。
//
@ -350,63 +342,4 @@ func TestDashboardJS_NoWriteOnlyGlobals(t *testing.T) {
}
}
func stripJSComments(s string) string {
var b strings.Builder
b.Grow(len(s))
inLine, inBlock := false, false
var inStr byte
for i := 0; i < len(s); i++ {
c := s[i]
if inLine {
if c == '\n' {
inLine = false
b.WriteByte(c)
}
continue
}
if inBlock {
if c == '*' && i+1 < len(s) && s[i+1] == '/' {
inBlock = false
i++
}
continue
}
if c == '\n' {
b.WriteByte(c)
continue
}
if inStr != 0 {
b.WriteByte(c)
if c == '\\' {
if i+1 < len(s) {
b.WriteByte(s[i+1])
i++
}
continue
}
if c == inStr {
inStr = 0
}
continue
}
if c == '"' || c == '\'' || c == '`' {
// 反引号也是字符串定界符,但它可跨行 —— 本判据只统计标识符,
// 跨行模板串里出现标识符属正常,此处按普通字符串处理即可。
inStr = c
b.WriteByte(c)
continue
}
if c == '/' && i+1 < len(s) && s[i+1] == '/' {
inLine = true
i++
continue
}
if c == '/' && i+1 < len(s) && s[i+1] == '*' {
inBlock = true
i++
continue
}
b.WriteByte(c)
}
return b.String()
}
// stripJSComments 见 helpers_test.go(迁移时去重)。

View File

@ -1,10 +1,13 @@
package webui
package test
import "testing"
import (
"github.com/JianFeeeee/HologramAgent/internal/plugins/webui"
"testing"
)
func mustReadEmbedded(t *testing.T, name string) []byte {
t.Helper()
b, err := dashboardFS.ReadFile(name)
b, err := webui.FrontendAsset(name)
if err != nil {
t.Fatalf("读 embed %s: %v", name, err)
}

View File

@ -6,10 +6,9 @@ package test
// 而在"测试与源码分离"之后,它们不能再从包内符号借用。
import (
"github.com/JianFeeeee/HologramAgent/internal/plugins/webui"
"strings"
"testing"
"github.com/JianFeeeee/HologramAgent/internal/plugins/webui"
)
// stripJSComments 剥掉 // 行注释与 /* */ 块注释(粗粒度,但足够用于词频统计)。

View File

@ -1,9 +1,10 @@
package webui
package test
import (
"bytes"
"encoding/binary"
"errors"
"github.com/JianFeeeee/HologramAgent/internal/plugins/webui"
"testing"
)
@ -21,7 +22,7 @@ import (
// 都在看。所以用容器格式本身来判定:判据看的是 VP8X 的 Alpha 标志位与
// ALPH chunk 是否**一致存在**,而不是「渲染出来像不像透明」。
func TestMascotAssetHasRealAlpha(t *testing.T) {
data, err := dashboardFS.ReadFile("mascot.webp")
data, err := webui.FrontendAsset("mascot.webp")
if err != nil {
t.Fatalf("读 mascot.webp: %v", err)
}