1package ui
2
3import (
4 "os"
5 "testing"
6
7 "github.com/bnema/nefergui/internal/css"
8
9 "github.com/bnema/nefergui/internal/text"
10)
11
12// demoFrame mirrors the product example's home view without opening a display.
13func demoFrame(f *Frame) {
14 root := f.Root(Class("app"), Class("light"), Class("comfortable"))
15 header := root.Header(Class("header"))
16 header.Heading("NeferGUI", Level(1), Class("logo"))
17 header.Button("New", Key("new-document"))
18 header.Text("Untitled.txt", Class("muted"))
19 header.Spacer(Class("stretch"))
20 header.Button("Dark mode", Key("dark-mode"), Class("theme-toggle"))
21 body := root.Row(Class("body"))
22 sidebar := body.Aside(Class("sidebar"))
23 sidebar.Text("DOCUMENTS", Class("eyebrow"))
24 menu := sidebar.Nav(Class("menu"))
25 menu.Button("Untitled.txt", Key("home"), Class("menu-item"), Class("active"))
26 menu.Button("Preferences", Key("settings"), Class("menu-item"))
27 sidebar.Spacer(Class("stretch"))
28 sidebar.Text("Local workspace", Class("sidebar-note"))
29 sidebar.Text("1 document · in memory", Class("hint"))
30 content := body.Main(Class("content"))
31 workspace := content.Row(Class("workspace"))
32 editor := workspace.Column(Class("editor-pane"))
33 tab := editor.Row(Class("pane-bar"))
34 tab.Text("Untitled.txt", Class("document-title"))
35 tab.Spacer(Class("stretch"))
36 tab.Text("Plain text", Class("hint"))
37 notes := "Project notes\n\nA native text workspace built with NeferGUI.\n\nSelect text, edit a paragraph, or paste from your clipboard.\nUse the properties pane to try radio buttons and the slider.\n\nEverything stays in memory for this session."
38 editor.Textarea("Document", ¬es, Key("document"), Class("document-editor"), Placeholder("Start writing…"))
39 inspector := workspace.Aside(Class("inspector"))
40 inspector.Text("PROPERTIES", Class("eyebrow"))
41 inspector.Text("Author", Class("field-label"))
42 name := ""
43 inspector.Input("Author", &name, Key("name"), Placeholder("Your name"))
44 inspector.Button("Apply", Key("continue"), Disabled(true))
45 inspector.Separator()
46 inspector.Text("Interface density", Class("field-label"))
47 density := "comfortable"
48 inspector.Radio("Comfortable", "comfortable", &density, Key("comfortable"))
49 inspector.Radio("Compact", "compact", &density, Key("compact"))
50 inspector.Separator()
51 volume := 40.0
52 inspector.Slider("Volume 40 %", &volume, 0, 100, 5, Key("volume"))
53 inspector.Spacer(Class("stretch"))
54 inspector.Text("Changes stay in this session.", Class("hint"))
55 footer := root.Footer(Class("status-bar"))
56 footer.Text("Ready")
57 footer.Spacer(Class("stretch"))
58 footer.Text("UTF-8 • Plain text", Class("hint"))
59}
60
61func demoRuntime(tb testing.TB) *runtime {
62 tb.Helper()
63 catalog, err := text.Load(text.DirectorySource("../../testdata/fonts"))
64 if err != nil {
65 tb.Fatal(err)
66 }
67 r := newRuntime()
68 stylesheet, err := os.ReadFile("../../examples/demo/app.css")
69 if err != nil {
70 tb.Fatal(err)
71 }
72 r.styles = css.Compile(css.UA(), css.Parse(string(stylesheet)))
73 r.setTextEngine(text.NewEngine(catalog))
74 return r
75}
76
77func BenchmarkDemoFrame(b *testing.B) {
78 r := demoRuntime(b)
79 if !r.Build(demoFrame) {
80 b.Fatal("initial build failed")
81 }
82 b.ReportAllocs()
83 b.ResetTimer()
84 for i := 0; i < b.N; i++ {
85 r.Redraw()
86 if !r.Build(demoFrame) {
87 b.Fatal("build failed")
88 }
89 if len(r.output.Display) == 0 {
90 b.Fatal("empty display list")
91 }
92 }
93}
94
95func TestDemoFrameAllocationBudget(t *testing.T) {
96 r := demoRuntime(t)
97 if !r.Build(demoFrame) {
98 t.Fatal("initial build failed")
99 }
100 allocs := testing.AllocsPerRun(10, func() {
101 r.Redraw()
102 if !r.Build(demoFrame) || len(r.output.Display) == 0 {
103 panic("demo frame failed")
104 }
105 })
106 const budget = 1100
107 if allocs > budget {
108 t.Fatalf("demo frame allocations: %.0f > %d", allocs, budget)
109 }
110}