Skip to content

Commit 8aec0b0

Browse files
committed
fix: tests
1 parent da90016 commit 8aec0b0

3 files changed

Lines changed: 115 additions & 50 deletions

File tree

internal/ui/screenshot_test.go

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package ui
44

55
import (
66
"encoding/json"
7+
"fmt"
78
"image"
89
"image/color"
910
"image/png"
@@ -27,10 +28,10 @@ import (
2728
"github.com/nanorele/gio/unit"
2829
)
2930

30-
const (
31-
shotW = 1280
32-
shotH = 800
33-
)
31+
var shotSizes = []image.Point{
32+
{X: 1280, Y: 800},
33+
{X: 480, Y: 360},
34+
}
3435

3536
var fixedTime = time.Unix(1700000000, 0)
3637

@@ -95,7 +96,7 @@ func sceneList() []scene {
9596
withTab(ui)
9697
ui.TabBar.TabCtxMenuOpen = true
9798
ui.TabBar.TabCtxMenuIdx = 0
98-
ui.TabBar.TabCtxMenuPos = f32.Pt(220, 64)
99+
ui.TabBar.TabCtxMenuPos = f32.Pt(40, 20)
99100
}},
100101
{"overlay-cols-menu", func(ui *AppUI) { withTab(ui); ui.ColsMenuOpen = true }},
101102
{"overlay-envs-menu", func(ui *AppUI) { withTab(ui); ui.EnvsMenuOpen = true }},
@@ -106,6 +107,17 @@ func sceneList() []scene {
106107
}},
107108
{"overlay-send-menu", func(ui *AppUI) { withTab(ui); ui.Tabs[0].SendMenuOpen = true }},
108109
{"overlay-method-list", func(ui *AppUI) { withTab(ui); ui.Tabs[0].MethodListOpen = true }},
110+
{"overlay-protocol-list", func(ui *AppUI) { withTab(ui); ui.Tabs[0].ProtocolListOpen = true }},
111+
{"requests-multitab", func(ui *AppUI) {
112+
ui.SidebarSection = "requests"
113+
ui.Tabs = []*workspace.RequestTab{
114+
workspace.NewRequestTab("Get users"),
115+
workspace.NewRequestTab("Create user"),
116+
workspace.NewRequestTab("Delete user"),
117+
workspace.NewRequestTab("List orders"),
118+
}
119+
ui.ActiveIdx = 1
120+
}},
109121
}
110122
}
111123

@@ -115,27 +127,26 @@ type regionJSON struct {
115127
}
116128

117129
type manifestJSON struct {
118-
Scene string `json:"scene"`
119-
Size [2]int `json:"size"`
120-
Regions []regionJSON `json:"regions"`
121-
Diag map[string]int `json:"diag,omitempty"`
130+
Scene string `json:"scene"`
131+
Size [2]int `json:"size"`
132+
Regions []regionJSON `json:"regions"`
122133
}
123134

124-
func buildRegions(ui *AppUI, probes map[string]layout.Dimensions) []regionJSON {
135+
func buildRegions(ui *AppUI, probes map[string]layout.Dimensions, sz image.Point) []regionJSON {
125136
var out []regionJSON
126137
titleH := 0
127138
if d, ok := probes["titlebar"]; ok {
128139
w := d.Size.X
129140
if w == 0 {
130-
w = shotW
141+
w = sz.X
131142
}
132143
titleH = d.Size.Y
133144
out = append(out, regionJSON{"titlebar", [4]int{0, 0, w, titleH}})
134145
}
135146
if d, ok := probes["content"]; ok {
136147
w := d.Size.X
137148
if w == 0 {
138-
w = shotW
149+
w = sz.X
139150
}
140151
out = append(out, regionJSON{"content", [4]int{0, titleH, w, titleH + d.Size.Y}})
141152
}
@@ -147,21 +158,21 @@ func buildRegions(ui *AppUI, probes map[string]layout.Dimensions) []regionJSON {
147158
if ui.hideSidebar() {
148159
divider = 0
149160
}
150-
out = append(out, regionJSON{"main", [4]int{sw + divider, titleH, shotW, titleH + sh}})
161+
out = append(out, regionJSON{"main", [4]int{sw + divider, titleH, sz.X, titleH + sh}})
151162
}
152163
return out
153164
}
154165

155-
func newShotGtx(ops *op.Ops) layout.Context {
166+
func newShotGtx(ops *op.Ops, sz image.Point) layout.Context {
156167
return layout.Context{
157168
Ops: ops,
158169
Metric: unit.Metric{PxPerDp: 1, PxPerSp: 1},
159-
Constraints: layout.Exact(image.Pt(shotW, shotH)),
170+
Constraints: layout.Exact(sz),
160171
Now: fixedTime,
161172
}
162173
}
163174

164-
func renderScene(t *testing.T, sc scene) {
175+
func renderScene(t *testing.T, sc scene, sz image.Point) {
165176
t.Helper()
166177
setupTestConfigDir(t)
167178
ui := NewAppUI()
@@ -172,21 +183,20 @@ func renderScene(t *testing.T, sc scene) {
172183
sc.setup(ui)
173184
}
174185

175-
win, err := headless.NewWindow(shotW, shotH)
186+
win, err := headless.NewWindow(sz.X, sz.Y)
176187
if err != nil {
177188
t.Skipf("headless GPU backend unavailable: %v", err)
178189
}
179190
defer win.Release()
180191

181192
for i := 0; i < 2; i++ {
182-
ui.layoutApp(newShotGtx(new(op.Ops)))
193+
ui.layoutApp(newShotGtx(new(op.Ops), sz))
183194
}
184195

185196
probes := map[string]layout.Dimensions{}
186197
probeRegion = func(name string, d layout.Dimensions) { probes[name] = d }
187198
ops := new(op.Ops)
188-
gtx := newShotGtx(ops)
189-
ui.layoutApp(gtx)
199+
ui.layoutApp(newShotGtx(ops, sz))
190200
probeRegion = nil
191201

192202
if err := win.Frame(ops); err != nil {
@@ -202,7 +212,8 @@ func renderScene(t *testing.T, sc scene) {
202212
t.Fatal(err)
203213
}
204214

205-
f, err := os.Create(filepath.Join(dir, sc.name+".png"))
215+
name := fmt.Sprintf("%s_%dx%d", sc.name, sz.X, sz.Y)
216+
f, err := os.Create(filepath.Join(dir, name+".png"))
206217
if err != nil {
207218
t.Fatal(err)
208219
}
@@ -214,40 +225,24 @@ func renderScene(t *testing.T, sc scene) {
214225
t.Fatal(err)
215226
}
216227

217-
diag := map[string]int{
218-
"sidebar_width": ui.SidebarWidth,
219-
"win_w": win.Size().X,
220-
"win_h": win.Size().Y,
221-
"px_per_dp_x1000": int(gtx.Metric.PxPerDp * 1000),
222-
"dp8": gtx.Dp(unit.Dp(8)),
223-
}
224-
if d, ok := probes["titlebar"]; ok {
225-
diag["titlebar_h"] = d.Size.Y
226-
}
227-
if ui.TabBar.TabCtxMenuOpen {
228-
offX := int(ui.TabBar.TabCtxMenuPos.X) + ui.SidebarWidth + gtx.Dp(unit.Dp(8))
229-
offY := int(ui.TabBar.TabCtxMenuPos.Y) + gtx.Dp(unit.Dp(8))
230-
diag["tabctx_pos_x"] = int(ui.TabBar.TabCtxMenuPos.X)
231-
diag["tabctx_pos_y"] = int(ui.TabBar.TabCtxMenuPos.Y)
232-
diag["menu_off_x"] = offX
233-
diag["menu_screen_y"] = diag["titlebar_h"] + offY
234-
}
235-
236-
man := manifestJSON{Scene: sc.name, Size: [2]int{shotW, shotH}, Regions: buildRegions(ui, probes), Diag: diag}
228+
man := manifestJSON{Scene: name, Size: [2]int{sz.X, sz.Y}, Regions: buildRegions(ui, probes, sz)}
237229
mb, err := json.MarshalIndent(man, "", " ")
238230
if err != nil {
239231
t.Fatal(err)
240232
}
241-
if err := os.WriteFile(filepath.Join(dir, sc.name+".layout.json"), mb, 0o644); err != nil {
233+
if err := os.WriteFile(filepath.Join(dir, name+".layout.json"), mb, 0o644); err != nil {
242234
t.Fatal(err)
243235
}
244236
}
245237

246238
func TestScreenshots(t *testing.T) {
247-
for _, sc := range sceneList() {
248-
sc := sc
249-
t.Run(sc.name, func(t *testing.T) {
250-
renderScene(t, sc)
251-
})
239+
for _, sz := range shotSizes {
240+
sz := sz
241+
for _, sc := range sceneList() {
242+
sc := sc
243+
t.Run(fmt.Sprintf("%s_%dx%d", sc.name, sz.X, sz.Y), func(t *testing.T) {
244+
renderScene(t, sc, sz)
245+
})
246+
}
252247
}
253248
}

internal/ui/tabbar/tabbar_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import (
66

77
"tracto/internal/ui/workspace"
88

9+
"github.com/nanorele/gio/f32"
910
"github.com/nanorele/gio/font/gofont"
11+
"github.com/nanorele/gio/io/input"
12+
"github.com/nanorele/gio/io/pointer"
1013
"github.com/nanorele/gio/layout"
1114
"github.com/nanorele/gio/op"
1215
"github.com/nanorele/gio/text"
@@ -422,3 +425,68 @@ func TestLayout_ZeroMaxWidthDegenerate(t *testing.T) {
422425
active := 0
423426
s.Layout(gtx, th, &tabs, &active, nil, nil)
424427
}
428+
429+
func rightClickTabBar(t *testing.T, titles []string, pos f32.Point, w, h int) *Strip {
430+
t.Helper()
431+
th := newTestTheme()
432+
s := NewStrip()
433+
var tabs []*workspace.RequestTab
434+
for _, ti := range titles {
435+
tabs = append(tabs, workspace.NewRequestTab(ti))
436+
}
437+
active := 0
438+
439+
var router input.Router
440+
ops := new(op.Ops)
441+
gtx := layout.Context{
442+
Ops: ops,
443+
Metric: unit.Metric{PxPerDp: 1, PxPerSp: 1},
444+
Constraints: layout.Exact(image.Pt(w, h)),
445+
Source: router.Source(),
446+
}
447+
448+
s.Layout(gtx, th, &tabs, &active, nil, nil)
449+
router.Frame(ops)
450+
router.Queue(
451+
pointer.Event{Kind: pointer.Press, Position: pos, Buttons: pointer.ButtonSecondary, Source: pointer.Mouse},
452+
pointer.Event{Kind: pointer.Release, Position: pos, Buttons: pointer.ButtonSecondary, Source: pointer.Mouse},
453+
)
454+
ops.Reset()
455+
gtx.Ops = ops
456+
s.Layout(gtx, th, &tabs, &active, nil, nil)
457+
return s
458+
}
459+
460+
func TestTabContextMenu_IgnoresEmptySpace(t *testing.T) {
461+
for _, pos := range []f32.Point{
462+
f32.Pt(220, 18),
463+
f32.Pt(220, 64),
464+
f32.Pt(500, 18),
465+
} {
466+
s := rightClickTabBar(t, []string{"Get users"}, pos, 1000, 200)
467+
if s.TabCtxMenuOpen {
468+
t.Errorf("right-click at empty tab-bar point %v must not open the context menu", pos)
469+
}
470+
}
471+
}
472+
473+
func TestTabContextMenu_OpensOnClickedTab(t *testing.T) {
474+
s := rightClickTabBar(t, []string{"Get users"}, f32.Pt(30, 18), 1000, 200)
475+
if !s.TabCtxMenuOpen {
476+
t.Fatal("right-click on a tab must open the context menu")
477+
}
478+
if s.TabCtxMenuIdx != 0 {
479+
t.Errorf("expected TabCtxMenuIdx=0, got %d", s.TabCtxMenuIdx)
480+
}
481+
}
482+
483+
func TestTabContextMenu_PicksSecondTab(t *testing.T) {
484+
w0 := measureTabWidth(makeGtx(1000, 200), newTestTheme(), "alpha")
485+
s := rightClickTabBar(t, []string{"alpha", "bravo"}, f32.Pt(float32(4+w0+6), 18), 1000, 200)
486+
if !s.TabCtxMenuOpen {
487+
t.Fatal("right-click on the second tab must open the context menu")
488+
}
489+
if s.TabCtxMenuIdx != 1 {
490+
t.Errorf("expected TabCtxMenuIdx=1, got %d", s.TabCtxMenuIdx)
491+
}
492+
}

tools/uitest/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ that all widgets actually drew, and that nothing regressed against a golden base
66
## Layers
77

88
1. **Render (Go).** `internal/ui/screenshot_test.go` (build tag `screenshots`) builds an
9-
`AppUI` per scene and rasterizes the full UI through `gpu/headless`. Output per scene:
10-
`internal/ui/testdata/screenshots/<scene>.png` and `<scene>.layout.json` (expected region rects:
11-
`titlebar`, `content`, `sidebar`, `main`).
9+
`AppUI` per scene and rasterizes the full UI through `gpu/headless`. Each scene is rendered at
10+
every size in `shotSizes` (currently `1280x800` and the app's `480x360` minimum), so layout is
11+
checked at both a roomy and a cramped extent. Output per scene/size:
12+
`internal/ui/testdata/screenshots/<scene>_<W>x<H>.png` and `<scene>_<W>x<H>.layout.json`
13+
(expected region rects: `titlebar`, `content`, `sidebar`, `main`).
1214
2. **Verify (Python).** `verify.py` checks region bounds / non-overlap / drawn edges, checks each
1315
region is non-blank, diffs against `golden/<scene>.png`, and writes `report.html`.
1416

0 commit comments

Comments
 (0)