Files
HomeAgent/internal/plugins/webui/kernel_buttons_render_test.go
JianFeeeee 683f0126af test(webui): 内核按钮必须真的出现在**渲染后**的页面里
## 为什么需要这组判据

dashboard.html 与 dashboard.js 都是 `//go:embed` 进二进制的,再由 init
组装成 dashboardHTML(替换 {{DASHBOARD_JS}} 等占位符)。这里有两个
**各自都可能坏掉**的环节:

 1. 按钮写进了 dashboard.html,但占位符没被替换 / 替换错位
    ⇒ 页面里看不到按钮
 2. JS 函数写进了 dashboard.js,但被包在 IIFE 里(不在全局)
    ⇒ onclick="kernelRestart()" 点了报 "kernelRestart is not defined"

只查源码里有没有那个字符串,这两种情况都照不出来。

## 判据(3 条,都查组装后的 dashboardHTML / embed 源)

 · TestKernelButtonsRenderedInPage:渲染后的页面含两个按钮的 id 与 onclick
 · TestKernelFunctionsAreGlobal:4 个函数必须是**顶层**定义
   (浏览器 onclick 走全局查找,jsdom 的行为测试做不到这点)
 · TestKernelButtonsInNavFooter:两个按钮必须落在 nav-footer 内
   —— 它们作用于整个服务,切到任何标签页都该能点到

实测:渲染页面 408057 字节,六项断言全在。

顺带把 dashboard.js 的格式化差异一并提交(-w 对比为空的纯格式调整)。
2026-10-17 21:26:12 +08:00

84 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) {
src := string(mustReadEmbedded(t, "dashboard.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)
}
}
}