Files
rond-with-you/internal/web/tb_static_test.go
T
tamakyi d5f6dfa381 审计修复:前端(模板 / CSS / JS 与无障碍)
- 地图脚本:localStorage 读写走 lsGet/lsSet 兜底(隐私模式下裸访问会打断整段脚本);
  preview() 在 /api/map-config 还没回来时不丢点(previewQueue 在 if (live) 之前排队);
  异步回包一律发序号作废,取消后回来的包不再画
- photo-exif.js:EXIF 时间全 0 不再变成 1899-11-30 写进到访时间;读 count 前先按版本卡宽度
- 数据编辑页:三个筛选表单都把其余条件用隐藏域原样带上,提交不再把别的列表的筛选清掉
- 补全页:sessionStorage 的读写一律包 try(脏数据/隐私模式都会抛)
- 窄屏:删掉被后面同特异性规则盖掉的死内边距;宽表堆叠类名成对、判据扩到所有 tb 表
- 无障碍:图片删除按钮补 aria-label,访客画廊图片补 alt
- report.html 补上漏掉的 </tr>,fog-mask 的宽表带 tb-stack

本批不碰数据库与导出路径:往返包 823/323/8/30/791/557/2246 全等、
坐标偏差 0.000000000、时间偏差 0s、迷雾 8701 块一致。
2026-09-24 20:37:57 +08:00

136 lines
4.0 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 web
import (
"io/fs"
"path/filepath"
"regexp"
"strings"
"testing"
)
// tbStackExempt 是明确不参与窄屏堆叠的表格类名,必须写清理由。
var tbStackExempt = map[string]string{
"photo-table": "照片表由脚本逐格生成、列数固定,移动端走「隐藏第 3 列」那条路",
}
// TestAllTBTablesAreStackable 静态扫全部模板:只要用了 `tb` 这个宽表基类,就必须带 `tb-stack`。
//
// 这是对 wide_table_layout_test 的补充——那条是**渲染后**按 DOM 栈判的,两种表能躲过去:
// ① 运行期的数据为空(比如库里没有遮罩区),那张表压根没渲染;
// ② 模板忘了写 .tw 包裹,判据里的「在 .tw 里」就不成立。
// /admin/fog/mask 的遮罩表两条都占了,2026-09-21 审计才发现。
func TestAllTBTablesAreStackable(t *testing.T) {
tableRe := regexp.MustCompile(`<table\b[^>]*class="([^"]*)"`)
scanned, tables := 0, 0
err := fs.WalkDir(tmplFS, "templates", func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || filepath.Ext(path) != ".html" {
return err
}
raw, err := fs.ReadFile(tmplFS, path)
if err != nil {
return err
}
scanned++
for _, m := range tableRe.FindAllStringSubmatch(string(raw), -1) {
classes := strings.Fields(m[1])
if !containsStr(classes, "tb") {
continue
}
tables++
if containsStr(classes, "tb-stack") {
continue
}
exempt := false
for _, c := range classes {
if _, ok := tbStackExempt[c]; ok {
exempt = true
}
}
if exempt {
continue
}
t.Errorf("%s:用了 tb 却没带 tb-stack(class=%q)——窄屏没有任何堆叠规则可命中,只能横滑",
path, m[1])
}
return nil
})
if err != nil {
t.Fatalf("遍历模板失败: %v", err)
}
if scanned == 0 || tables == 0 {
t.Fatalf("没扫到模板/表格(模板目录或类名写法变了?):模板 %d 个、tb 表 %d 张", scanned, tables)
}
t.Logf("扫了 %d 个模板里的 %d 张 tb 表", scanned, tables)
}
// TestTemplatesHaveBalancedRows 静态钉住模板里 `<tr>` 与 `</tr>` 数量相等。
//
// 浏览器会自己把漏掉的 `</tr>` 补上,页面看着完全正常——但 tb_stack_test 的行列校验
// 是按 `<tr>…</tr>` 配对的,一个没闭合的 `<tr>` 会让**那一行整行不被检查**
// (2026-09-21 审计在 report.html 的「最南」行上发现,之前一直静默跳过)。
//
// 先剥掉 <script> 块:照片表的行是脚本拼字符串生成的,那边的 `<tr>` 不算数。
func TestTemplatesHaveBalancedRows(t *testing.T) {
checked := 0
err := fs.WalkDir(tmplFS, "templates", func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || filepath.Ext(path) != ".html" {
return err
}
raw, err := fs.ReadFile(tmplFS, path)
if err != nil {
return err
}
body := stripScriptBlocks(string(raw))
open := len(regexp.MustCompile(`<tr\b`).FindAllString(body, -1))
close := strings.Count(body, "</tr>")
if open == 0 {
return nil
}
checked++
if open != close {
t.Errorf("%s:<tr> 有 %d 个、</tr> 只有 %d 个 —— 缺口那一行会被 tb_stack_test 静默跳过",
path, open, close)
}
return nil
})
if err != nil {
t.Fatalf("遍历模板失败: %v", err)
}
if checked == 0 {
t.Fatal("一个含表格行的模板都没扫到:模板目录变了?")
}
t.Logf("检查了 %d 个含表格行的模板", checked)
}
// stripScriptBlocks 去掉所有 <script>…</script> 的内容(含标签本身)。
func stripScriptBlocks(s string) string {
var b strings.Builder
for {
i := strings.Index(s, "<script")
if i < 0 {
b.WriteString(s)
return b.String()
}
b.WriteString(s[:i])
j := strings.Index(s[i:], ">")
if j < 0 {
return b.String()
}
end := strings.Index(s[i+j:], "</script>")
if end < 0 {
return b.String()
}
s = s[i+j+end+len("</script>"):]
}
}
func containsStr(list []string, want string) bool {
for _, v := range list {
if v == want {
return true
}
}
return false
}