package lua import ( "os" "regexp" "strings" "testing" ) // 侧边栏图标判据 —— 覆盖**所有**带页面的插件,不只是 billing。 // // 为什么需要这个测试 // ------------------ // billing 当年声明了 icon = "",WebUI 把空串原样塞进按钮 ⇒ 侧边栏那条是空白。 // 当时只给 billing 加了判据(TestPluginIconIsNotARawEmoji)。后来 channels // 声明 icon = ""、quota 连 icon 字段都没有 ⇒ 同样的"空白条目"缺陷又一次出现, // 而判据只管 billing,所以全绿。 // // 这就是"判据只覆盖出错的那一个文件"的代价:同类缺陷换个文件复发时没有任何 // 东西会响。本文件把范围改成**按页面遍历**,新增带页面的插件自动纳入。 // pageIcons 返回 插件文件 -> 其 page.icon 的声明值。 // // 只认 page.icon(侧边栏读的就是它),不认顶层的其他 icon 字段 —— 那些不影响 // 侧边栏渲染,混进来会给出假绿。 func pageIcons(t *testing.T, path string) []string { t.Helper() b, err := os.ReadFile(path) if err != nil { t.Fatalf("读 %s 失败: %v", path, err) } src := string(b) // 短字符串:icon = "..." shortRe := regexp.MustCompile(`(?m)^\s*icon\s*=\s*"([^"]*)"`) // 长字符串:icon = [==[ ... ]==](inline SVG 没法写成短字符串而不转义所有引号) longRe := regexp.MustCompile(`(?s)\bicon\s*=\s*\[==\[(.*?)\]==\]`) var out []string for _, m := range shortRe.FindAllStringSubmatch(src, -1) { out = append(out, m[1]) } for _, m := range longRe.FindAllStringSubmatch(src, -1) { out = append(out, m[1]) } return out } // 带页面(plugin.ui.page)的插件。新增页面插件要登记在这里 —— // 这是一份"必须被覆盖的清单",漏登记会让新插件逃避判据。 var pagePlugins = []string{ "plugins/billing.lua", "plugins/channels.lua", "plugins/quota.lua", } // TestEveryPagePluginDeclaresSVGIcon 钉住"每个带页面的插件都有 inline SVG 图标"。 // // 两个必查项,各自对应一种真实缺陷: // - 缺 icon / 空串 ⇒ 侧边栏空白(channels 的实测症状) // - emoji ⇒ 尺寸不跟原生 tab、忽略主题色(billing 的历史缺陷) func TestEveryPagePluginDeclaresSVGIcon(t *testing.T) { for _, p := range pagePlugins { t.Run(p, func(t *testing.T) { icons := pageIcons(t, p) if len(icons) == 0 { t.Fatalf("%s 有任何 page 却没有任何 icon 声明 —— 侧边栏那条会是空白", p) } // 只断言"至少有一个非空且是 SVG":一个文件可能有多个页面, // 但不能为了通过而声明一个空图标。 var ok bool for _, ic := range icons { if strings.TrimSpace(ic) == "" { continue } if isEmojiIcon(ic) { t.Errorf("%s 的 page icon 是 emoji %q —— 原生 tab 用 inline SVG"+ "(stroke: currentColor),emoji 尺寸不一致且不跟主题", p, ic) continue } if !strings.Contains(ic, "