117 lines
3.3 KiB
Go
117 lines
3.3 KiB
Go
package webui
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
"kjol/wasmruntime/vdom"
|
|
)
|
|
|
|
// An unregistered icon name renders as an empty box: right size, no glyph. Nothing
|
|
// errors, nothing logs — the icons just quietly aren't there. That is exactly how
|
|
// this broke, so scan our own source for every name we ask for and assert the
|
|
// registry has it.
|
|
func TestEveryIconTheKitUsesIsRegistered(t *testing.T) {
|
|
// Icon("name", …), IconInline("name", …), and the Icon: "name" struct field.
|
|
calls := regexp.MustCompile(`\bIcon(?:Inline|Success|Error)?\("([a-z0-9-]+)"`)
|
|
fields := 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") || f == "icons.go" {
|
|
continue // icons.go is the registry itself
|
|
}
|
|
src, err := os.ReadFile(f)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, re := range []*regexp.Regexp{calls, fields} {
|
|
for _, m := range re.FindAllStringSubmatch(string(src), -1) {
|
|
if m[1] == "" {
|
|
continue
|
|
}
|
|
used[m[1]] = append(used[m[1]], f)
|
|
}
|
|
}
|
|
}
|
|
// Icon names also reach Icon() through lookup tables. Those cannot be found by
|
|
// scanning call sites, so add them from the tables themselves.
|
|
for _, name := range toastTypeIcons {
|
|
if name != "" {
|
|
used[name] = append(used[name], "toast.go (toastTypeIcons)")
|
|
}
|
|
}
|
|
if len(used) == 0 {
|
|
t.Fatal("scanned no icon usages — the regexes have rotted")
|
|
}
|
|
|
|
var missing []string
|
|
for name, where := range used {
|
|
if _, ok := iconRegistry[name]; !ok {
|
|
missing = append(missing, name+" (used in "+strings.Join(dedupe(where), ", ")+")")
|
|
}
|
|
}
|
|
sort.Strings(missing)
|
|
for _, m := range missing {
|
|
t.Errorf("icon not registered, renders as an empty box: %s", m)
|
|
}
|
|
}
|
|
|
|
// A registered icon must actually draw something. An entry with empty content is
|
|
// the same silent failure as a missing one.
|
|
func TestRegisteredIconsHaveContent(t *testing.T) {
|
|
for name, def := range iconRegistry {
|
|
if strings.TrimSpace(def.content) == "" {
|
|
t.Errorf("icon %q is registered with no SVG content", name)
|
|
}
|
|
if !strings.Contains(def.content, "<path") {
|
|
t.Errorf("icon %q has no <path>: %q", name, def.content)
|
|
}
|
|
if def.viewBox == "" {
|
|
t.Errorf("icon %q has no viewBox", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIconRendersRegisteredContent(t *testing.T) {
|
|
html := renderNode(Icon("check", 20, "text-red-500"))
|
|
if !strings.Contains(html, "<path") {
|
|
t.Errorf("Icon did not render its path: %s", html)
|
|
}
|
|
for _, want := range []string{`width="20"`, `height="20"`, "text-red-500", `viewBox="0 0 24 24"`} {
|
|
if !strings.Contains(html, want) {
|
|
t.Errorf("Icon output missing %s: %s", want, html)
|
|
}
|
|
}
|
|
// An unknown name still renders a correctly sized, empty box rather than
|
|
// collapsing the layout.
|
|
if empty := renderNode(Icon("no-such-icon", 16, "")); strings.Contains(empty, "<path") {
|
|
t.Errorf("unknown icon drew something: %s", empty)
|
|
}
|
|
}
|
|
|
|
// renderNode serializes a component's VNode the way the server does.
|
|
func renderNode(n *vdom.VNode) string { return vdom.RenderHTML(n) }
|
|
|
|
func dedupe(xs []string) []string {
|
|
seen := map[string]bool{}
|
|
out := []string{}
|
|
for _, x := range xs {
|
|
if !seen[x] {
|
|
seen[x] = true
|
|
out = append(out, x)
|
|
}
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|