package app import ( "os" "path/filepath" "regexp" "sort" "strings" "testing" ui "kjol/webui" ) // Every icon name this app names must actually resolve. // // An unregistered name renders an empty, correctly-sized box. That is the right thing // at runtime — a missing icon should not collapse the layout — but it means a typo is // invisible: the icon is simply absent, and nothing says why. Two of them (shapes, // layer-group, which the kit calls squares and layers) shipped in the sidebar looking // like blank squares before this test existed. // // It scans the SOURCE rather than a hand-kept list, so an icon added to a page tomorrow // is checked tomorrow, without anyone remembering to add it here. func TestEveryIconNameResolves(t *testing.T) { // ui.Icon("x", …) / ui.IconInline("x", …), and the Icon: "x" field on the props // structs (buttons, menu items, docs nav). patterns := []*regexp.Regexp{ regexp.MustCompile(`Icon(?:Inline)?\("([a-z0-9-]+)"`), regexp.MustCompile(`\bIcon:\s*"([a-z0-9-]+)"`), } files, err := filepath.Glob("*.go") if err != nil { t.Fatal(err) } used := map[string][]string{} // icon name -> files that ask for it for _, f := range files { if strings.HasSuffix(f, "_test.go") { continue } src, err := os.ReadFile(f) if err != nil { t.Fatal(err) } for _, re := range patterns { for _, m := range re.FindAllStringSubmatch(string(src), -1) { used[m[1]] = append(used[m[1]], f) } } } if len(used) == 0 { t.Fatal("scanned the package and found no icon names at all — the patterns have gone stale") } names := make([]string, 0, len(used)) for n := range used { names = append(names, n) } sort.Strings(names) for _, n := range names { if !ui.HasIcon(n) { t.Errorf("icon %q is not registered (used in %s) — it will render as an empty box", n, strings.Join(dedupe(used[n]), ", ")) } } t.Logf("checked %d icon names", len(names)) } func dedupe(in []string) []string { seen := map[string]bool{} out := in[:0:0] for _, s := range in { if !seen[s] { seen[s] = true out = append(out, s) } } return out }