test(ui): cover Renderer with a Mockery GPU seam
Adds the testify dependency and a same-package mock of the unexported target interface.
- commit
- 62a7058d7bdad91adcc2051cbeaa165c12a72792
- author
- brice · · 2026-10-01 16:31 UTC
- parent
- 008e36d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +588 -12
A .mockery.yml +16 -0
| @@ -0,0 +1,16 @@ | |||
| 1 | + | all: false | |
| 2 | + | formatter: goimports | |
| 3 | + | force-file-write: true | |
| 4 | + | structname: '{{.Mock}}{{.InterfaceName}}' | |
| 5 | + | template: testify | |
| 6 | + | template-schema: '{{.Template}}.schema.json' | |
| 7 | + | require-template-schema-exists: true | |
| 8 | + | packages: | |
| 9 | + | # Unexported GPU seam of Renderer: the mock lives next to the code, in a test file. | |
| 10 | + | github.com/bnema/nefergui/internal/ui: | |
| 11 | + | config: | |
| 12 | + | dir: internal/ui | |
| 13 | + | pkgname: ui | |
| 14 | + | filename: '{{.InterfaceName}}_mock_test.go' | |
| 15 | + | interfaces: | |
| 16 | + | target: | |
| @@ -0,0 +1,16 @@ | |||||
| 1 | + | all: false | |||
| 2 | + | formatter: goimports | |||
| 3 | + | force-file-write: true | |||
| 4 | + | structname: '{{.Mock}}{{.InterfaceName}}' | |||
| 5 | + | template: testify | |||
| 6 | + | template-schema: '{{.Template}}.schema.json' | |||
| 7 | + | require-template-schema-exists: true | |||
| 8 | + | packages: | |||
| 9 | + | # Unexported GPU seam of Renderer: the mock lives next to the code, in a test file. | |||
| 10 | + | github.com/bnema/nefergui/internal/ui: | |||
| 11 | + | config: | |||
| 12 | + | dir: internal/ui | |||
| 13 | + | pkgname: ui | |||
| 14 | + | filename: '{{.InterfaceName}}_mock_test.go' | |||
| 15 | + | interfaces: | |||
| 16 | + | target: | |||
M go.mod +6 -1
| @@ -8,8 +8,13 @@ require ( | |||
| 8 | 8 | github.com/bnema/purego-xkbcommon v0.1.0 | |
| 9 | 9 | github.com/bnema/wlturbo v0.5.0 | |
| 10 | 10 | github.com/go-text/typesetting v0.3.5 | |
| 11 | + | github.com/stretchr/testify v1.12.1 | |
| 11 | 12 | golang.org/x/image v0.46.0 | |
| 12 | 13 | golang.org/x/sys v0.48.0 | |
| 13 | 14 | ) | |
| 14 | 15 | ||
| 15 | - | require golang.org/x/text v0.42.0 // indirect | |
| 16 | + | require ( | |
| 17 | + | github.com/stretchr/objx v0.5.3 // indirect | |
| 18 | + | go.yaml.in/yaml/v3 v3.0.5 // indirect | |
| 19 | + | golang.org/x/text v0.42.0 // indirect | |
| 20 | + | ) | |
| @@ -8,8 +8,13 @@ require ( | |||||
| 8 | github.com/bnema/purego-xkbcommon v0.1.0 | 8 | github.com/bnema/purego-xkbcommon v0.1.0 | ||
| 9 | github.com/bnema/wlturbo v0.5.0 | 9 | github.com/bnema/wlturbo v0.5.0 | ||
| 10 | github.com/go-text/typesetting v0.3.5 | 10 | github.com/go-text/typesetting v0.3.5 | ||
| 11 | + | github.com/stretchr/testify v1.12.1 | |||
| 11 | golang.org/x/image v0.46.0 | 12 | golang.org/x/image v0.46.0 | ||
| 12 | golang.org/x/sys v0.48.0 | 13 | golang.org/x/sys v0.48.0 | ||
| 13 | ) | 14 | ) | ||
| 14 | 15 | ||||
| 15 | - | require golang.org/x/text v0.42.0 // indirect | 16 | + | require ( |
| 17 | + | github.com/stretchr/objx v0.5.3 // indirect | |||
| 18 | + | go.yaml.in/yaml/v3 v3.0.5 // indirect | |||
| 19 | + | golang.org/x/text v0.42.0 // indirect | |||
| 20 | + | ) | |||
M go.sum +6 -0 generated file: collapsed
M internal/ui/renderer.go +24 -11
| @@ -8,6 +8,7 @@ import ( | |||
| 8 | 8 | "fmt" | |
| 9 | 9 | "math" | |
| 10 | 10 | "os" | |
| 11 | + | "slices" | |
| 11 | 12 | ||
| 12 | 13 | "github.com/bnema/nefergui/internal/css" | |
| 13 | 14 | "github.com/bnema/nefergui/internal/edit" | |
| @@ -112,6 +113,11 @@ type Renderer struct { | |||
| 112 | 113 | scale float64 | |
| 113 | 114 | pw, ph int32 | |
| 114 | 115 | closed bool | |
| 116 | + | ||
| 117 | + | // Last input region handed out, to report only changes. | |
| 118 | + | region []Rect | |
| 119 | + | regionNil bool | |
| 120 | + | haveRegion bool | |
| 115 | 121 | } | |
| 116 | 122 | ||
| 117 | 123 | // asyncClipboard lets an AsyncClipboard satisfy the editor's Clipboard port; | |
| @@ -281,18 +287,25 @@ func (r *Renderer) export(out *Output) { | |||
| 281 | 287 | Damage: damage, Cursor: cursorOf(r.rt.cursor()), InputRects: rects[:0], | |
| 282 | 288 | } | |
| 283 | 289 | rt := r.rt | |
| 284 | - | if rt.inputStaged { | |
| 285 | - | out.InputRectsChanged = true | |
| 286 | - | if rt.inputNil { | |
| 287 | - | out.InputRects = nil | |
| 288 | - | } else { | |
| 289 | - | if out.InputRects == nil { | |
| 290 | - | out.InputRects = []Rect{} | |
| 291 | - | } | |
| 292 | - | out.InputRects = append(out.InputRects, rt.inputRects...) | |
| 293 | - | } | |
| 294 | - | rt.inputStaged = false | |
| 290 | + | if !rt.inputStaged { | |
| 291 | + | return | |
| 292 | + | } | |
| 293 | + | rt.inputStaged = false | |
| 294 | + | // A region equal to the last reported one costs nothing. | |
| 295 | + | if r.haveRegion && r.regionNil == rt.inputNil && slices.Equal(r.region, rt.inputRects) { | |
| 296 | + | return | |
| 297 | + | } | |
| 298 | + | r.haveRegion, r.regionNil = true, rt.inputNil | |
| 299 | + | r.region = append(r.region[:0], rt.inputRects...) | |
| 300 | + | out.InputRectsChanged = true | |
| 301 | + | if rt.inputNil { | |
| 302 | + | out.InputRects = nil | |
| 303 | + | return | |
| 304 | + | } | |
| 305 | + | if out.InputRects == nil { | |
| 306 | + | out.InputRects = []Rect{} | |
| 295 | 307 | } | |
| 308 | + | out.InputRects = append(out.InputRects, rt.inputRects...) | |
| 296 | 309 | } | |
| 297 | 310 | ||
| 298 | 311 | func cursorOf(k css.Keyword) Cursor { | |
| @@ -8,6 +8,7 @@ import ( | |||||
| 8 | "fmt" | 8 | "fmt" | ||
| 9 | "math" | 9 | "math" | ||
| 10 | "os" | 10 | "os" | ||
| 11 | + | "slices" | |||
| 11 | 12 | ||||
| 12 | "github.com/bnema/nefergui/internal/css" | 13 | "github.com/bnema/nefergui/internal/css" | ||
| 13 | "github.com/bnema/nefergui/internal/edit" | 14 | "github.com/bnema/nefergui/internal/edit" | ||
| @@ -112,6 +113,11 @@ type Renderer struct { | |||||
| 112 | scale float64 | 113 | scale float64 | ||
| 113 | pw, ph int32 | 114 | pw, ph int32 | ||
| 114 | closed bool | 115 | closed bool | ||
| 116 | + | ||||
| 117 | + | // Last input region handed out, to report only changes. | |||
| 118 | + | region []Rect | |||
| 119 | + | regionNil bool | |||
| 120 | + | haveRegion bool | |||
| 115 | } | 121 | } | ||
| 116 | 122 | ||||
| 117 | // asyncClipboard lets an AsyncClipboard satisfy the editor's Clipboard port; | 123 | // asyncClipboard lets an AsyncClipboard satisfy the editor's Clipboard port; | ||
| @@ -281,18 +287,25 @@ func (r *Renderer) export(out *Output) { | |||||
| 281 | Damage: damage, Cursor: cursorOf(r.rt.cursor()), InputRects: rects[:0], | 287 | Damage: damage, Cursor: cursorOf(r.rt.cursor()), InputRects: rects[:0], | ||
| 282 | } | 288 | } | ||
| 283 | rt := r.rt | 289 | rt := r.rt | ||
| 284 | - | if rt.inputStaged { | 290 | + | if !rt.inputStaged { |
| 285 | - | out.InputRectsChanged = true | 291 | + | return |
| 286 | - | if rt.inputNil { | 292 | + | } |
| 287 | - | out.InputRects = nil | 293 | + | rt.inputStaged = false |
| 288 | - | } else { | 294 | + | // A region equal to the last reported one costs nothing. |
| 289 | - | if out.InputRects == nil { | 295 | + | if r.haveRegion && r.regionNil == rt.inputNil && slices.Equal(r.region, rt.inputRects) { |
| 290 | - | out.InputRects = []Rect{} | 296 | + | return |
| 291 | - | } | 297 | + | } |
| 292 | - | out.InputRects = append(out.InputRects, rt.inputRects...) | 298 | + | r.haveRegion, r.regionNil = true, rt.inputNil |
| 293 | - | } | 299 | + | r.region = append(r.region[:0], rt.inputRects...) |
| 294 | - | rt.inputStaged = false | 300 | + | out.InputRectsChanged = true |
| 301 | + | if rt.inputNil { | |||
| 302 | + | out.InputRects = nil | |||
| 303 | + | return | |||
| 304 | + | } | |||
| 305 | + | if out.InputRects == nil { | |||
| 306 | + | out.InputRects = []Rect{} | |||
| 295 | } | 307 | } | ||
| 308 | + | out.InputRects = append(out.InputRects, rt.inputRects...) | |||
| 296 | } | 309 | } | ||
| 297 | 310 | ||||
| 298 | func cursorOf(k css.Keyword) Cursor { | 311 | func cursorOf(k css.Keyword) Cursor { | ||
A internal/ui/renderer_test.go +276 -0
| @@ -0,0 +1,276 @@ | |||
| 1 | + | //go:build linux | |
| 2 | + | ||
| 3 | + | package ui | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "errors" | |
| 7 | + | "testing" | |
| 8 | + | ||
| 9 | + | "github.com/bnema/nefergui/internal/layout" | |
| 10 | + | "github.com/bnema/nefergui/internal/presentation/session" | |
| 11 | + | "github.com/bnema/nefergui/internal/text" | |
| 12 | + | "github.com/stretchr/testify/mock" | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | type testModel struct { | |
| 16 | + | clicks int | |
| 17 | + | rects []Rect | |
| 18 | + | inline string | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | func testView(f *Frame, m *testModel) { | |
| 22 | + | root := f.Root() | |
| 23 | + | if root.Button("Go", Key("go"), Inline("width:60px;height:20px")).Activated() { | |
| 24 | + | m.clicks++ | |
| 25 | + | } | |
| 26 | + | if m.rects != nil { | |
| 27 | + | _ = f.SetInputRects(m.rects) | |
| 28 | + | } | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | func newTestRenderer(t *testing.T) (*Renderer, *mocktarget) { | |
| 32 | + | t.Helper() | |
| 33 | + | tg := newMocktarget(t) | |
| 34 | + | r, err := newRenderer(RendererConfig{}, tg, text.DirectorySource("../../testdata/fonts")) | |
| 35 | + | if err != nil { | |
| 36 | + | t.Fatal(err) | |
| 37 | + | } | |
| 38 | + | return r, tg | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | func drawOK(tg *mocktarget, fill func(*session.Output)) *mocktarget_Draw_Call { | |
| 42 | + | return tg.EXPECT().Draw(mock.Anything, mock.Anything, mock.Anything). | |
| 43 | + | RunAndReturn(func(_ []layout.Command, _ float64, out *session.Output) (bool, error) { | |
| 44 | + | *out = session.Output{Buffer: 1, Width: 100, Height: 50} | |
| 45 | + | if fill != nil { | |
| 46 | + | fill(out) | |
| 47 | + | } | |
| 48 | + | return true, nil | |
| 49 | + | }) | |
| 50 | + | } | |
| 51 | + | ||
| 52 | + | func TestKeysymNames(t *testing.T) { | |
| 53 | + | for sym, want := range map[uint32]string{ | |
| 54 | + | 0x20: "space", 0xff09: "tab", 0xfe20: "iso_left_tab", 0xff51: "left", 0xff53: "right", | |
| 55 | + | 0xff52: "up", 0xff54: "down", 0xff50: "home", 0xff57: "end", 0xff55: "page_up", | |
| 56 | + | 0xff56: "page_down", 0xff08: "backspace", 0xffff: "delete", 'a': "a", 'A': "a", | |
| 57 | + | 'c': "c", 'x': "x", 'v': "v", 0xff0d: "return", 0xff8d: "kp_enter", 0xff1b: "escape", | |
| 58 | + | 'z': "", 0xffe1: "", | |
| 59 | + | } { | |
| 60 | + | if got := keysymName(sym); got != want { | |
| 61 | + | t.Errorf("keysym %#x: got %q want %q", sym, got, want) | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | func TestRendererRenderBeforeResizeAndWhenUnchanged(t *testing.T) { | |
| 67 | + | r, tg := newTestRenderer(t) | |
| 68 | + | var m testModel | |
| 69 | + | var out Output | |
| 70 | + | if ok, err := r.Render(&out, &m, testView); ok || err != nil { | |
| 71 | + | t.Fatalf("before Resize: ok=%v err=%v", ok, err) | |
| 72 | + | } | |
| 73 | + | tg.EXPECT().Resize(mock.Anything, mock.Anything).Return().Once() | |
| 74 | + | r.Resize(100, 50, 2) | |
| 75 | + | drawOK(tg, nil).Once() | |
| 76 | + | if ok, err := r.Render(&out, &m, testView); !ok || err != nil { | |
| 77 | + | t.Fatalf("first render: ok=%v err=%v", ok, err) | |
| 78 | + | } | |
| 79 | + | if out.Buffer != 1 || out.Width != 100 || len(out.Damage) != 1 || out.Damage[0] != (Rect{Width: 100, Height: 50}) { | |
| 80 | + | t.Fatalf("output: %+v", out) | |
| 81 | + | } | |
| 82 | + | // The follow-up frame a Build requests after settling still draws once; after that nothing changes. | |
| 83 | + | for i := 0; i < 3; i++ { | |
| 84 | + | ok, err := r.Render(&out, &m, testView) | |
| 85 | + | if err != nil { | |
| 86 | + | t.Fatal(err) | |
| 87 | + | } | |
| 88 | + | if ok { | |
| 89 | + | drawOK(tg, nil).Maybe() | |
| 90 | + | } | |
| 91 | + | } | |
| 92 | + | // tg asserts no Draw beyond the allowed ones when unchanged. | |
| 93 | + | if ok, _ := r.Render(&out, &m, testView); ok { | |
| 94 | + | t.Fatal("unchanged view drew") | |
| 95 | + | } | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | func TestRendererKeepsFrameWhenNoBufferFree(t *testing.T) { | |
| 99 | + | r, tg := newTestRenderer(t) | |
| 100 | + | var m testModel | |
| 101 | + | var out Output | |
| 102 | + | tg.EXPECT().Resize(mock.Anything, mock.Anything).Return().Once() | |
| 103 | + | r.Resize(100, 50, 1) | |
| 104 | + | tg.EXPECT().Draw(mock.Anything, mock.Anything, mock.Anything).Return(false, nil).Once() | |
| 105 | + | if ok, err := r.Render(&out, &m, testView); ok || err != nil { | |
| 106 | + | t.Fatalf("no buffer: ok=%v err=%v", ok, err) | |
| 107 | + | } | |
| 108 | + | drawOK(tg, nil).Once() | |
| 109 | + | if ok, err := r.Render(&out, &m, testView); !ok || err != nil { | |
| 110 | + | t.Fatalf("retry: ok=%v err=%v", ok, err) | |
| 111 | + | } | |
| 112 | + | } | |
| 113 | + | ||
| 114 | + | func TestRendererDrawError(t *testing.T) { | |
| 115 | + | r, tg := newTestRenderer(t) | |
| 116 | + | var m testModel | |
| 117 | + | var out Output | |
| 118 | + | tg.EXPECT().Resize(mock.Anything, mock.Anything).Return().Once() | |
| 119 | + | r.Resize(100, 50, 1) | |
| 120 | + | boom := errors.New("boom") | |
| 121 | + | tg.EXPECT().Draw(mock.Anything, mock.Anything, mock.Anything).Return(false, boom).Once() | |
| 122 | + | if ok, err := r.Render(&out, &m, testView); ok || !errors.Is(err, boom) { | |
| 123 | + | t.Fatalf("ok=%v err=%v", ok, err) | |
| 124 | + | } | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | func TestRendererResizeReachesTargetInPhysicalPixels(t *testing.T) { | |
| 128 | + | r, tg := newTestRenderer(t) | |
| 129 | + | tg.EXPECT().Resize(int32(150), int32(75)).Return().Once() | |
| 130 | + | r.Resize(100, 50, 1.5) | |
| 131 | + | r.Resize(100, 50, 1.5) // unchanged: no second call | |
| 132 | + | r.Resize(0, 50, 1) // invalid: ignored | |
| 133 | + | r.Resize(10, 10, 0) // invalid: ignored | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | func TestRendererInputActivatesButtonAndRequestsRedraw(t *testing.T) { | |
| 137 | + | r, tg := newTestRenderer(t) | |
| 138 | + | var m testModel | |
| 139 | + | var out Output | |
| 140 | + | tg.EXPECT().Resize(mock.Anything, mock.Anything).Return().Once() | |
| 141 | + | r.Resize(200, 100, 1) | |
| 142 | + | drawOK(tg, nil).Maybe() | |
| 143 | + | for i := 0; i < 3; i++ { | |
| 144 | + | if _, err := r.Render(&out, &m, testView); err != nil { | |
| 145 | + | t.Fatal(err) | |
| 146 | + | } | |
| 147 | + | } | |
| 148 | + | if r.Input(&Input{Kind: InputPointerMotion, X: 5, Y: 5}) != true { | |
| 149 | + | t.Fatal("hover over a button needs a redraw") | |
| 150 | + | } | |
| 151 | + | r.Input(&Input{Kind: InputPointerPress, X: 5, Y: 5, Button: 0x110, Pressed: true}) | |
| 152 | + | if !r.Input(&Input{Kind: InputPointerRelease, X: 5, Y: 5, Button: 0x110}) { | |
| 153 | + | t.Fatal("release did not request redraw") | |
| 154 | + | } | |
| 155 | + | for i := 0; i < 3; i++ { | |
| 156 | + | if _, err := r.Render(&out, &m, testView); err != nil { | |
| 157 | + | t.Fatal(err) | |
| 158 | + | } | |
| 159 | + | } | |
| 160 | + | if m.clicks != 1 { | |
| 161 | + | t.Fatalf("clicks=%d", m.clicks) | |
| 162 | + | } | |
| 163 | + | // Keyboard: Tab focuses, Return activates, text comes from Text. | |
| 164 | + | r.Input(&Input{Kind: InputPointerLeave}) | |
| 165 | + | r.Input(&Input{Kind: InputKey, Keysym: 0xff09, Pressed: true}) | |
| 166 | + | r.Input(&Input{Kind: InputKey, Keysym: 0xff0d, Pressed: true}) | |
| 167 | + | for i := 0; i < 3; i++ { | |
| 168 | + | if _, err := r.Render(&out, &m, testView); err != nil { | |
| 169 | + | t.Fatal(err) | |
| 170 | + | } | |
| 171 | + | } | |
| 172 | + | if m.clicks != 2 { | |
| 173 | + | t.Fatalf("Return did not activate the focused button: clicks=%d", m.clicks) | |
| 174 | + | } | |
| 175 | + | if r.Input(&Input{Kind: 99}) || r.Input(nil) { | |
| 176 | + | t.Fatal("unknown input requested a redraw") | |
| 177 | + | } | |
| 178 | + | if r.Input(&Input{Kind: InputPointerAxis}) { | |
| 179 | + | t.Fatal("zero scroll requested a redraw") | |
| 180 | + | } | |
| 181 | + | } | |
| 182 | + | ||
| 183 | + | func TestRendererTextInputUsesTextBytes(t *testing.T) { | |
| 184 | + | r, tg := newTestRenderer(t) | |
| 185 | + | value := "" | |
| 186 | + | var out Output | |
| 187 | + | view := func(f *Frame, v *string) { f.Root().Input("name", v, Key("name"), Inline("width:100px;height:20px")) } | |
| 188 | + | tg.EXPECT().Resize(mock.Anything, mock.Anything).Return().Once() | |
| 189 | + | r.Resize(200, 100, 1) | |
| 190 | + | drawOK(tg, nil).Maybe() | |
| 191 | + | settle := func() { | |
| 192 | + | for i := 0; i < 3; i++ { | |
| 193 | + | if _, err := r.Render(&out, &value, view); err != nil { | |
| 194 | + | t.Fatal(err) | |
| 195 | + | } | |
| 196 | + | } | |
| 197 | + | } | |
| 198 | + | settle() | |
| 199 | + | r.Input(&Input{Kind: InputPointerPress, X: 5, Y: 5, Button: 0x110, Pressed: true}) | |
| 200 | + | r.Input(&Input{Kind: InputPointerRelease, X: 5, Y: 5, Button: 0x110}) | |
| 201 | + | settle() | |
| 202 | + | text := []byte("é") | |
| 203 | + | r.Input(&Input{Kind: InputKey, Keysym: 0xe9, Pressed: true, Text: text}) | |
| 204 | + | text[0] = 0 // the caller may reuse the buffer after the call | |
| 205 | + | settle() | |
| 206 | + | if value != "é" { | |
| 207 | + | t.Fatalf("value=%q", value) | |
| 208 | + | } | |
| 209 | + | } | |
| 210 | + | ||
| 211 | + | func TestRendererOutputCarriesCursorAndInputRects(t *testing.T) { | |
| 212 | + | r, tg := newTestRenderer(t) | |
| 213 | + | m := testModel{rects: []Rect{{1, 2, 30, 20}}} | |
| 214 | + | var out Output | |
| 215 | + | tg.EXPECT().Resize(mock.Anything, mock.Anything).Return().Once() | |
| 216 | + | r.Resize(200, 100, 1) | |
| 217 | + | drawOK(tg, nil).Maybe() | |
| 218 | + | r.Input(&Input{Kind: InputPointerMotion, X: 5, Y: 5}) // over the button: pointer cursor | |
| 219 | + | if ok, err := r.Render(&out, &m, testView); !ok || err != nil { | |
| 220 | + | t.Fatalf("ok=%v err=%v", ok, err) | |
| 221 | + | } | |
| 222 | + | if out.Cursor != CursorPointer || !out.InputRectsChanged || len(out.InputRects) != 1 || out.InputRects[0] != (Rect{1, 2, 30, 20}) { | |
| 223 | + | t.Fatalf("output: cursor=%v changed=%v rects=%v", out.Cursor, out.InputRectsChanged, out.InputRects) | |
| 224 | + | } | |
| 225 | + | // An unchanged region is not reported again. | |
| 226 | + | r.Invalidate() | |
| 227 | + | ok, err := r.Render(&out, &m, testView) | |
| 228 | + | if err != nil { | |
| 229 | + | t.Fatal(err) | |
| 230 | + | } | |
| 231 | + | if ok && out.InputRectsChanged && len(out.InputRects) != 0 { | |
| 232 | + | t.Fatalf("region reported twice: %+v", out) | |
| 233 | + | } | |
| 234 | + | // nil restores the whole surface: changed with no rects. | |
| 235 | + | m.rects = nil | |
| 236 | + | var nilView = func(f *Frame, _ *testModel) { f.Root(); _ = f.SetInputRects(nil) } | |
| 237 | + | r.Invalidate() | |
| 238 | + | if ok, err = r.Render(&out, &m, nilView); err != nil || !ok || !out.InputRectsChanged || out.InputRects != nil { | |
| 239 | + | t.Fatalf("nil region: ok=%v err=%v %+v", ok, err, out) | |
| 240 | + | } | |
| 241 | + | } | |
| 242 | + | ||
| 243 | + | func TestRendererForwardsReleasedAndClose(t *testing.T) { | |
| 244 | + | r, tg := newTestRenderer(t) | |
| 245 | + | tg.EXPECT().Released(uint64(3)).Return(nil).Once() | |
| 246 | + | if err := r.Released(3); err != nil { | |
| 247 | + | t.Fatal(err) | |
| 248 | + | } | |
| 249 | + | tg.EXPECT().Close().Return(nil).Once() | |
| 250 | + | if err := r.Close(); err != nil { | |
| 251 | + | t.Fatal(err) | |
| 252 | + | } | |
| 253 | + | if err := r.Close(); err != nil { // second Close is a no-op | |
| 254 | + | t.Fatal(err) | |
| 255 | + | } | |
| 256 | + | var out Output | |
| 257 | + | var m testModel | |
| 258 | + | if _, err := r.Render(&out, &m, testView); err == nil { | |
| 259 | + | t.Fatal("Render after Close succeeded") | |
| 260 | + | } | |
| 261 | + | } | |
| 262 | + | ||
| 263 | + | func TestTextIntAndMasked(t *testing.T) { | |
| 264 | + | r := newRuntime() | |
| 265 | + | r.Build(func(f *Frame) { | |
| 266 | + | root := f.Root() | |
| 267 | + | root.TextInt("Count: ", -42) | |
| 268 | + | root.Masked(3) | |
| 269 | + | root.Masked(0) | |
| 270 | + | root.Masked(300) | |
| 271 | + | }) | |
| 272 | + | c := r.committed.children | |
| 273 | + | if len(c) != 4 || c[0].text != "Count: -42" || c[1].text != "•••" || c[2].text != "" || len([]rune(c[3].text)) != 300 { | |
| 274 | + | t.Fatalf("texts: %q %q %q", c[0].text, c[1].text, c[2].text) | |
| 275 | + | } | |
| 276 | + | } | |
| @@ -0,0 +1,276 @@ | |||||
| 1 | + | //go:build linux | |||
| 2 | + | ||||
| 3 | + | package ui | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "errors" | |||
| 7 | + | "testing" | |||
| 8 | + | ||||
| 9 | + | "github.com/bnema/nefergui/internal/layout" | |||
| 10 | + | "github.com/bnema/nefergui/internal/presentation/session" | |||
| 11 | + | "github.com/bnema/nefergui/internal/text" | |||
| 12 | + | "github.com/stretchr/testify/mock" | |||
| 13 | + | ) | |||
| 14 | + | ||||
| 15 | + | type testModel struct { | |||
| 16 | + | clicks int | |||
| 17 | + | rects []Rect | |||
| 18 | + | inline string | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | func testView(f *Frame, m *testModel) { | |||
| 22 | + | root := f.Root() | |||
| 23 | + | if root.Button("Go", Key("go"), Inline("width:60px;height:20px")).Activated() { | |||
| 24 | + | m.clicks++ | |||
| 25 | + | } | |||
| 26 | + | if m.rects != nil { | |||
| 27 | + | _ = f.SetInputRects(m.rects) | |||
| 28 | + | } | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | func newTestRenderer(t *testing.T) (*Renderer, *mocktarget) { | |||
| 32 | + | t.Helper() | |||
| 33 | + | tg := newMocktarget(t) | |||
| 34 | + | r, err := newRenderer(RendererConfig{}, tg, text.DirectorySource("../../testdata/fonts")) | |||
| 35 | + | if err != nil { | |||
| 36 | + | t.Fatal(err) | |||
| 37 | + | } | |||
| 38 | + | return r, tg | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | func drawOK(tg *mocktarget, fill func(*session.Output)) *mocktarget_Draw_Call { | |||
| 42 | + | return tg.EXPECT().Draw(mock.Anything, mock.Anything, mock.Anything). | |||
| 43 | + | RunAndReturn(func(_ []layout.Command, _ float64, out *session.Output) (bool, error) { | |||
| 44 | + | *out = session.Output{Buffer: 1, Width: 100, Height: 50} | |||
| 45 | + | if fill != nil { | |||
| 46 | + | fill(out) | |||
| 47 | + | } | |||
| 48 | + | return true, nil | |||
| 49 | + | }) | |||
| 50 | + | } | |||
| 51 | + | ||||
| 52 | + | func TestKeysymNames(t *testing.T) { | |||
| 53 | + | for sym, want := range map[uint32]string{ | |||
| 54 | + | 0x20: "space", 0xff09: "tab", 0xfe20: "iso_left_tab", 0xff51: "left", 0xff53: "right", | |||
| 55 | + | 0xff52: "up", 0xff54: "down", 0xff50: "home", 0xff57: "end", 0xff55: "page_up", | |||
| 56 | + | 0xff56: "page_down", 0xff08: "backspace", 0xffff: "delete", 'a': "a", 'A': "a", | |||
| 57 | + | 'c': "c", 'x': "x", 'v': "v", 0xff0d: "return", 0xff8d: "kp_enter", 0xff1b: "escape", | |||
| 58 | + | 'z': "", 0xffe1: "", | |||
| 59 | + | } { | |||
| 60 | + | if got := keysymName(sym); got != want { | |||
| 61 | + | t.Errorf("keysym %#x: got %q want %q", sym, got, want) | |||
| 62 | + | } | |||
| 63 | + | } | |||
| 64 | + | } | |||
| 65 | + | ||||
| 66 | + | func TestRendererRenderBeforeResizeAndWhenUnchanged(t *testing.T) { | |||
| 67 | + | r, tg := newTestRenderer(t) | |||
| 68 | + | var m testModel | |||
| 69 | + | var out Output | |||
| 70 | + | if ok, err := r.Render(&out, &m, testView); ok || err != nil { | |||
| 71 | + | t.Fatalf("before Resize: ok=%v err=%v", ok, err) | |||
| 72 | + | } | |||
| 73 | + | tg.EXPECT().Resize(mock.Anything, mock.Anything).Return().Once() | |||
| 74 | + | r.Resize(100, 50, 2) | |||
| 75 | + | drawOK(tg, nil).Once() | |||
| 76 | + | if ok, err := r.Render(&out, &m, testView); !ok || err != nil { | |||
| 77 | + | t.Fatalf("first render: ok=%v err=%v", ok, err) | |||
| 78 | + | } | |||
| 79 | + | if out.Buffer != 1 || out.Width != 100 || len(out.Damage) != 1 || out.Damage[0] != (Rect{Width: 100, Height: 50}) { | |||
| 80 | + | t.Fatalf("output: %+v", out) | |||
| 81 | + | } | |||
| 82 | + | // The follow-up frame a Build requests after settling still draws once; after that nothing changes. | |||
| 83 | + | for i := 0; i < 3; i++ { | |||
| 84 | + | ok, err := r.Render(&out, &m, testView) | |||
| 85 | + | if err != nil { | |||
| 86 | + | t.Fatal(err) | |||
| 87 | + | } | |||
| 88 | + | if ok { | |||
| 89 | + | drawOK(tg, nil).Maybe() | |||
| 90 | + | } | |||
| 91 | + | } | |||
| 92 | + | // tg asserts no Draw beyond the allowed ones when unchanged. | |||
| 93 | + | if ok, _ := r.Render(&out, &m, testView); ok { | |||
| 94 | + | t.Fatal("unchanged view drew") | |||
| 95 | + | } | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | func TestRendererKeepsFrameWhenNoBufferFree(t *testing.T) { | |||
| 99 | + | r, tg := newTestRenderer(t) | |||
| 100 | + | var m testModel | |||
| 101 | + | var out Output | |||
| 102 | + | tg.EXPECT().Resize(mock.Anything, mock.Anything).Return().Once() | |||
| 103 | + | r.Resize(100, 50, 1) | |||
| 104 | + | tg.EXPECT().Draw(mock.Anything, mock.Anything, mock.Anything).Return(false, nil).Once() | |||
| 105 | + | if ok, err := r.Render(&out, &m, testView); ok || err != nil { | |||
| 106 | + | t.Fatalf("no buffer: ok=%v err=%v", ok, err) | |||
| 107 | + | } | |||
| 108 | + | drawOK(tg, nil).Once() | |||
| 109 | + | if ok, err := r.Render(&out, &m, testView); !ok || err != nil { | |||
| 110 | + | t.Fatalf("retry: ok=%v err=%v", ok, err) | |||
| 111 | + | } | |||
| 112 | + | } | |||
| 113 | + | ||||
| 114 | + | func TestRendererDrawError(t *testing.T) { | |||
| 115 | + | r, tg := newTestRenderer(t) | |||
| 116 | + | var m testModel | |||
| 117 | + | var out Output | |||
| 118 | + | tg.EXPECT().Resize(mock.Anything, mock.Anything).Return().Once() | |||
| 119 | + | r.Resize(100, 50, 1) | |||
| 120 | + | boom := errors.New("boom") | |||
| 121 | + | tg.EXPECT().Draw(mock.Anything, mock.Anything, mock.Anything).Return(false, boom).Once() | |||
| 122 | + | if ok, err := r.Render(&out, &m, testView); ok || !errors.Is(err, boom) { | |||
| 123 | + | t.Fatalf("ok=%v err=%v", ok, err) | |||
| 124 | + | } | |||
| 125 | + | } | |||
| 126 | + | ||||
| 127 | + | func TestRendererResizeReachesTargetInPhysicalPixels(t *testing.T) { | |||
| 128 | + | r, tg := newTestRenderer(t) | |||
| 129 | + | tg.EXPECT().Resize(int32(150), int32(75)).Return().Once() | |||
| 130 | + | r.Resize(100, 50, 1.5) | |||
| 131 | + | r.Resize(100, 50, 1.5) // unchanged: no second call | |||
| 132 | + | r.Resize(0, 50, 1) // invalid: ignored | |||
| 133 | + | r.Resize(10, 10, 0) // invalid: ignored | |||
| 134 | + | } | |||
| 135 | + | ||||
| 136 | + | func TestRendererInputActivatesButtonAndRequestsRedraw(t *testing.T) { | |||
| 137 | + | r, tg := newTestRenderer(t) | |||
| 138 | + | var m testModel | |||
| 139 | + | var out Output | |||
| 140 | + | tg.EXPECT().Resize(mock.Anything, mock.Anything).Return().Once() | |||
| 141 | + | r.Resize(200, 100, 1) | |||
| 142 | + | drawOK(tg, nil).Maybe() | |||
| 143 | + | for i := 0; i < 3; i++ { | |||
| 144 | + | if _, err := r.Render(&out, &m, testView); err != nil { | |||
| 145 | + | t.Fatal(err) | |||
| 146 | + | } | |||
| 147 | + | } | |||
| 148 | + | if r.Input(&Input{Kind: InputPointerMotion, X: 5, Y: 5}) != true { | |||
| 149 | + | t.Fatal("hover over a button needs a redraw") | |||
| 150 | + | } | |||
| 151 | + | r.Input(&Input{Kind: InputPointerPress, X: 5, Y: 5, Button: 0x110, Pressed: true}) | |||
| 152 | + | if !r.Input(&Input{Kind: InputPointerRelease, X: 5, Y: 5, Button: 0x110}) { | |||
| 153 | + | t.Fatal("release did not request redraw") | |||
| 154 | + | } | |||
| 155 | + | for i := 0; i < 3; i++ { | |||
| 156 | + | if _, err := r.Render(&out, &m, testView); err != nil { | |||
| 157 | + | t.Fatal(err) | |||
| 158 | + | } | |||
| 159 | + | } | |||
| 160 | + | if m.clicks != 1 { | |||
| 161 | + | t.Fatalf("clicks=%d", m.clicks) | |||
| 162 | + | } | |||
| 163 | + | // Keyboard: Tab focuses, Return activates, text comes from Text. | |||
| 164 | + | r.Input(&Input{Kind: InputPointerLeave}) | |||
| 165 | + | r.Input(&Input{Kind: InputKey, Keysym: 0xff09, Pressed: true}) | |||
| 166 | + | r.Input(&Input{Kind: InputKey, Keysym: 0xff0d, Pressed: true}) | |||
| 167 | + | for i := 0; i < 3; i++ { | |||
| 168 | + | if _, err := r.Render(&out, &m, testView); err != nil { | |||
| 169 | + | t.Fatal(err) | |||
| 170 | + | } | |||
| 171 | + | } | |||
| 172 | + | if m.clicks != 2 { | |||
| 173 | + | t.Fatalf("Return did not activate the focused button: clicks=%d", m.clicks) | |||
| 174 | + | } | |||
| 175 | + | if r.Input(&Input{Kind: 99}) || r.Input(nil) { | |||
| 176 | + | t.Fatal("unknown input requested a redraw") | |||
| 177 | + | } | |||
| 178 | + | if r.Input(&Input{Kind: InputPointerAxis}) { | |||
| 179 | + | t.Fatal("zero scroll requested a redraw") | |||
| 180 | + | } | |||
| 181 | + | } | |||
| 182 | + | ||||
| 183 | + | func TestRendererTextInputUsesTextBytes(t *testing.T) { | |||
| 184 | + | r, tg := newTestRenderer(t) | |||
| 185 | + | value := "" | |||
| 186 | + | var out Output | |||
| 187 | + | view := func(f *Frame, v *string) { f.Root().Input("name", v, Key("name"), Inline("width:100px;height:20px")) } | |||
| 188 | + | tg.EXPECT().Resize(mock.Anything, mock.Anything).Return().Once() | |||
| 189 | + | r.Resize(200, 100, 1) | |||
| 190 | + | drawOK(tg, nil).Maybe() | |||
| 191 | + | settle := func() { | |||
| 192 | + | for i := 0; i < 3; i++ { | |||
| 193 | + | if _, err := r.Render(&out, &value, view); err != nil { | |||
| 194 | + | t.Fatal(err) | |||
| 195 | + | } | |||
| 196 | + | } | |||
| 197 | + | } | |||
| 198 | + | settle() | |||
| 199 | + | r.Input(&Input{Kind: InputPointerPress, X: 5, Y: 5, Button: 0x110, Pressed: true}) | |||
| 200 | + | r.Input(&Input{Kind: InputPointerRelease, X: 5, Y: 5, Button: 0x110}) | |||
| 201 | + | settle() | |||
| 202 | + | text := []byte("é") | |||
| 203 | + | r.Input(&Input{Kind: InputKey, Keysym: 0xe9, Pressed: true, Text: text}) | |||
| 204 | + | text[0] = 0 // the caller may reuse the buffer after the call | |||
| 205 | + | settle() | |||
| 206 | + | if value != "é" { | |||
| 207 | + | t.Fatalf("value=%q", value) | |||
| 208 | + | } | |||
| 209 | + | } | |||
| 210 | + | ||||
| 211 | + | func TestRendererOutputCarriesCursorAndInputRects(t *testing.T) { | |||
| 212 | + | r, tg := newTestRenderer(t) | |||
| 213 | + | m := testModel{rects: []Rect{{1, 2, 30, 20}}} | |||
| 214 | + | var out Output | |||
| 215 | + | tg.EXPECT().Resize(mock.Anything, mock.Anything).Return().Once() | |||
| 216 | + | r.Resize(200, 100, 1) | |||
| 217 | + | drawOK(tg, nil).Maybe() | |||
| 218 | + | r.Input(&Input{Kind: InputPointerMotion, X: 5, Y: 5}) // over the button: pointer cursor | |||
| 219 | + | if ok, err := r.Render(&out, &m, testView); !ok || err != nil { | |||
| 220 | + | t.Fatalf("ok=%v err=%v", ok, err) | |||
| 221 | + | } | |||
| 222 | + | if out.Cursor != CursorPointer || !out.InputRectsChanged || len(out.InputRects) != 1 || out.InputRects[0] != (Rect{1, 2, 30, 20}) { | |||
| 223 | + | t.Fatalf("output: cursor=%v changed=%v rects=%v", out.Cursor, out.InputRectsChanged, out.InputRects) | |||
| 224 | + | } | |||
| 225 | + | // An unchanged region is not reported again. | |||
| 226 | + | r.Invalidate() | |||
| 227 | + | ok, err := r.Render(&out, &m, testView) | |||
| 228 | + | if err != nil { | |||
| 229 | + | t.Fatal(err) | |||
| 230 | + | } | |||
| 231 | + | if ok && out.InputRectsChanged && len(out.InputRects) != 0 { | |||
| 232 | + | t.Fatalf("region reported twice: %+v", out) | |||
| 233 | + | } | |||
| 234 | + | // nil restores the whole surface: changed with no rects. | |||
| 235 | + | m.rects = nil | |||
| 236 | + | var nilView = func(f *Frame, _ *testModel) { f.Root(); _ = f.SetInputRects(nil) } | |||
| 237 | + | r.Invalidate() | |||
| 238 | + | if ok, err = r.Render(&out, &m, nilView); err != nil || !ok || !out.InputRectsChanged || out.InputRects != nil { | |||
| 239 | + | t.Fatalf("nil region: ok=%v err=%v %+v", ok, err, out) | |||
| 240 | + | } | |||
| 241 | + | } | |||
| 242 | + | ||||
| 243 | + | func TestRendererForwardsReleasedAndClose(t *testing.T) { | |||
| 244 | + | r, tg := newTestRenderer(t) | |||
| 245 | + | tg.EXPECT().Released(uint64(3)).Return(nil).Once() | |||
| 246 | + | if err := r.Released(3); err != nil { | |||
| 247 | + | t.Fatal(err) | |||
| 248 | + | } | |||
| 249 | + | tg.EXPECT().Close().Return(nil).Once() | |||
| 250 | + | if err := r.Close(); err != nil { | |||
| 251 | + | t.Fatal(err) | |||
| 252 | + | } | |||
| 253 | + | if err := r.Close(); err != nil { // second Close is a no-op | |||
| 254 | + | t.Fatal(err) | |||
| 255 | + | } | |||
| 256 | + | var out Output | |||
| 257 | + | var m testModel | |||
| 258 | + | if _, err := r.Render(&out, &m, testView); err == nil { | |||
| 259 | + | t.Fatal("Render after Close succeeded") | |||
| 260 | + | } | |||
| 261 | + | } | |||
| 262 | + | ||||
| 263 | + | func TestTextIntAndMasked(t *testing.T) { | |||
| 264 | + | r := newRuntime() | |||
| 265 | + | r.Build(func(f *Frame) { | |||
| 266 | + | root := f.Root() | |||
| 267 | + | root.TextInt("Count: ", -42) | |||
| 268 | + | root.Masked(3) | |||
| 269 | + | root.Masked(0) | |||
| 270 | + | root.Masked(300) | |||
| 271 | + | }) | |||
| 272 | + | c := r.committed.children | |||
| 273 | + | if len(c) != 4 || c[0].text != "Count: -42" || c[1].text != "•••" || c[2].text != "" || len([]rune(c[3].text)) != 300 { | |||
| 274 | + | t.Fatalf("texts: %q %q %q", c[0].text, c[1].text, c[2].text) | |||
| 275 | + | } | |||
| 276 | + | } | |||
A internal/ui/target_mock_test.go +260 -0
| @@ -0,0 +1,260 @@ | |||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |
| 2 | + | // github.com/vektra/mockery | |
| 3 | + | // template: testify | |
| 4 | + | ||
| 5 | + | package ui | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | "github.com/bnema/nefergui/internal/layout" | |
| 9 | + | "github.com/bnema/nefergui/internal/presentation/session" | |
| 10 | + | mock "github.com/stretchr/testify/mock" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // newMocktarget creates a new instance of mocktarget. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 14 | + | // The first argument is typically a *testing.T value. | |
| 15 | + | func newMocktarget(t interface { | |
| 16 | + | mock.TestingT | |
| 17 | + | Cleanup(func()) | |
| 18 | + | }) *mocktarget { | |
| 19 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 20 | + | helper.Helper() | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | mock := &mocktarget{} | |
| 24 | + | mock.Mock.Test(t) | |
| 25 | + | ||
| 26 | + | t.Cleanup(func() { | |
| 27 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 28 | + | helper.Helper() | |
| 29 | + | } | |
| 30 | + | mock.AssertExpectations(t) | |
| 31 | + | }) | |
| 32 | + | ||
| 33 | + | return mock | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | // mocktarget is an autogenerated mock type for the target type | |
| 37 | + | type mocktarget struct { | |
| 38 | + | mock.Mock | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | type mocktarget_Expecter struct { | |
| 42 | + | mock *mock.Mock | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | func (_m *mocktarget) EXPECT() *mocktarget_Expecter { | |
| 46 | + | return &mocktarget_Expecter{mock: &_m.Mock} | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | // Close provides a mock function for the type mocktarget | |
| 50 | + | func (_mock *mocktarget) Close() error { | |
| 51 | + | ret := _mock.Called() | |
| 52 | + | ||
| 53 | + | if len(ret) == 0 { | |
| 54 | + | panic("no return value specified for Close") | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | var r0 error | |
| 58 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |
| 59 | + | r0 = returnFunc() | |
| 60 | + | } else { | |
| 61 | + | r0 = ret.Error(0) | |
| 62 | + | } | |
| 63 | + | return r0 | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | // mocktarget_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |
| 67 | + | type mocktarget_Close_Call struct { | |
| 68 | + | *mock.Call | |
| 69 | + | } | |
| 70 | + | ||
| 71 | + | // Close is a helper method to define mock.On call | |
| 72 | + | func (_e *mocktarget_Expecter) Close() *mocktarget_Close_Call { | |
| 73 | + | return &mocktarget_Close_Call{Call: _e.mock.On("Close")} | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | func (_c *mocktarget_Close_Call) Run(run func()) *mocktarget_Close_Call { | |
| 77 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 78 | + | run() | |
| 79 | + | }) | |
| 80 | + | return _c | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | func (_c *mocktarget_Close_Call) Return(err error) *mocktarget_Close_Call { | |
| 84 | + | _c.Call.Return(err) | |
| 85 | + | return _c | |
| 86 | + | } | |
| 87 | + | ||
| 88 | + | func (_c *mocktarget_Close_Call) RunAndReturn(run func() error) *mocktarget_Close_Call { | |
| 89 | + | _c.Call.Return(run) | |
| 90 | + | return _c | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | // Draw provides a mock function for the type mocktarget | |
| 94 | + | func (_mock *mocktarget) Draw(display []layout.Command, scale float64, out *session.Output) (bool, error) { | |
| 95 | + | ret := _mock.Called(display, scale, out) | |
| 96 | + | ||
| 97 | + | if len(ret) == 0 { | |
| 98 | + | panic("no return value specified for Draw") | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | var r0 bool | |
| 102 | + | var r1 error | |
| 103 | + | if returnFunc, ok := ret.Get(0).(func([]layout.Command, float64, *session.Output) (bool, error)); ok { | |
| 104 | + | return returnFunc(display, scale, out) | |
| 105 | + | } | |
| 106 | + | if returnFunc, ok := ret.Get(0).(func([]layout.Command, float64, *session.Output) bool); ok { | |
| 107 | + | r0 = returnFunc(display, scale, out) | |
| 108 | + | } else { | |
| 109 | + | r0 = ret.Get(0).(bool) | |
| 110 | + | } | |
| 111 | + | if returnFunc, ok := ret.Get(1).(func([]layout.Command, float64, *session.Output) error); ok { | |
| 112 | + | r1 = returnFunc(display, scale, out) | |
| 113 | + | } else { | |
| 114 | + | r1 = ret.Error(1) | |
| 115 | + | } | |
| 116 | + | return r0, r1 | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | // mocktarget_Draw_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Draw' | |
| 120 | + | type mocktarget_Draw_Call struct { | |
| 121 | + | *mock.Call | |
| 122 | + | } | |
| 123 | + | ||
| 124 | + | // Draw is a helper method to define mock.On call | |
| 125 | + | // - display []layout.Command | |
| 126 | + | // - scale float64 | |
| 127 | + | // - out *session.Output | |
| 128 | + | func (_e *mocktarget_Expecter) Draw(display any, scale any, out any) *mocktarget_Draw_Call { | |
| 129 | + | return &mocktarget_Draw_Call{Call: _e.mock.On("Draw", display, scale, out)} | |
| 130 | + | } | |
| 131 | + | ||
| 132 | + | func (_c *mocktarget_Draw_Call) Run(run func(display []layout.Command, scale float64, out *session.Output)) *mocktarget_Draw_Call { | |
| 133 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 134 | + | var arg0 []layout.Command | |
| 135 | + | if args[0] != nil { | |
| 136 | + | arg0 = args[0].([]layout.Command) | |
| 137 | + | } | |
| 138 | + | var arg1 float64 | |
| 139 | + | if args[1] != nil { | |
| 140 | + | arg1 = args[1].(float64) | |
| 141 | + | } | |
| 142 | + | var arg2 *session.Output | |
| 143 | + | if args[2] != nil { | |
| 144 | + | arg2 = args[2].(*session.Output) | |
| 145 | + | } | |
| 146 | + | run( | |
| 147 | + | arg0, | |
| 148 | + | arg1, | |
| 149 | + | arg2, | |
| 150 | + | ) | |
| 151 | + | }) | |
| 152 | + | return _c | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | func (_c *mocktarget_Draw_Call) Return(b bool, err error) *mocktarget_Draw_Call { | |
| 156 | + | _c.Call.Return(b, err) | |
| 157 | + | return _c | |
| 158 | + | } | |
| 159 | + | ||
| 160 | + | func (_c *mocktarget_Draw_Call) RunAndReturn(run func(display []layout.Command, scale float64, out *session.Output) (bool, error)) *mocktarget_Draw_Call { | |
| 161 | + | _c.Call.Return(run) | |
| 162 | + | return _c | |
| 163 | + | } | |
| 164 | + | ||
| 165 | + | // Released provides a mock function for the type mocktarget | |
| 166 | + | func (_mock *mocktarget) Released(buffer uint64) error { | |
| 167 | + | ret := _mock.Called(buffer) | |
| 168 | + | ||
| 169 | + | if len(ret) == 0 { | |
| 170 | + | panic("no return value specified for Released") | |
| 171 | + | } | |
| 172 | + | ||
| 173 | + | var r0 error | |
| 174 | + | if returnFunc, ok := ret.Get(0).(func(uint64) error); ok { | |
| 175 | + | r0 = returnFunc(buffer) | |
| 176 | + | } else { | |
| 177 | + | r0 = ret.Error(0) | |
| 178 | + | } | |
| 179 | + | return r0 | |
| 180 | + | } | |
| 181 | + | ||
| 182 | + | // mocktarget_Released_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Released' | |
| 183 | + | type mocktarget_Released_Call struct { | |
| 184 | + | *mock.Call | |
| 185 | + | } | |
| 186 | + | ||
| 187 | + | // Released is a helper method to define mock.On call | |
| 188 | + | // - buffer uint64 | |
| 189 | + | func (_e *mocktarget_Expecter) Released(buffer any) *mocktarget_Released_Call { | |
| 190 | + | return &mocktarget_Released_Call{Call: _e.mock.On("Released", buffer)} | |
| 191 | + | } | |
| 192 | + | ||
| 193 | + | func (_c *mocktarget_Released_Call) Run(run func(buffer uint64)) *mocktarget_Released_Call { | |
| 194 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 195 | + | var arg0 uint64 | |
| 196 | + | if args[0] != nil { | |
| 197 | + | arg0 = args[0].(uint64) | |
| 198 | + | } | |
| 199 | + | run( | |
| 200 | + | arg0, | |
| 201 | + | ) | |
| 202 | + | }) | |
| 203 | + | return _c | |
| 204 | + | } | |
| 205 | + | ||
| 206 | + | func (_c *mocktarget_Released_Call) Return(err error) *mocktarget_Released_Call { | |
| 207 | + | _c.Call.Return(err) | |
| 208 | + | return _c | |
| 209 | + | } | |
| 210 | + | ||
| 211 | + | func (_c *mocktarget_Released_Call) RunAndReturn(run func(buffer uint64) error) *mocktarget_Released_Call { | |
| 212 | + | _c.Call.Return(run) | |
| 213 | + | return _c | |
| 214 | + | } | |
| 215 | + | ||
| 216 | + | // Resize provides a mock function for the type mocktarget | |
| 217 | + | func (_mock *mocktarget) Resize(width int32, height int32) { | |
| 218 | + | _mock.Called(width, height) | |
| 219 | + | return | |
| 220 | + | } | |
| 221 | + | ||
| 222 | + | // mocktarget_Resize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Resize' | |
| 223 | + | type mocktarget_Resize_Call struct { | |
| 224 | + | *mock.Call | |
| 225 | + | } | |
| 226 | + | ||
| 227 | + | // Resize is a helper method to define mock.On call | |
| 228 | + | // - width int32 | |
| 229 | + | // - height int32 | |
| 230 | + | func (_e *mocktarget_Expecter) Resize(width any, height any) *mocktarget_Resize_Call { | |
| 231 | + | return &mocktarget_Resize_Call{Call: _e.mock.On("Resize", width, height)} | |
| 232 | + | } | |
| 233 | + | ||
| 234 | + | func (_c *mocktarget_Resize_Call) Run(run func(width int32, height int32)) *mocktarget_Resize_Call { | |
| 235 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 236 | + | var arg0 int32 | |
| 237 | + | if args[0] != nil { | |
| 238 | + | arg0 = args[0].(int32) | |
| 239 | + | } | |
| 240 | + | var arg1 int32 | |
| 241 | + | if args[1] != nil { | |
| 242 | + | arg1 = args[1].(int32) | |
| 243 | + | } | |
| 244 | + | run( | |
| 245 | + | arg0, | |
| 246 | + | arg1, | |
| 247 | + | ) | |
| 248 | + | }) | |
| 249 | + | return _c | |
| 250 | + | } | |
| 251 | + | ||
| 252 | + | func (_c *mocktarget_Resize_Call) Return() *mocktarget_Resize_Call { | |
| 253 | + | _c.Call.Return() | |
| 254 | + | return _c | |
| 255 | + | } | |
| 256 | + | ||
| 257 | + | func (_c *mocktarget_Resize_Call) RunAndReturn(run func(width int32, height int32)) *mocktarget_Resize_Call { | |
| 258 | + | _c.Run(run) | |
| 259 | + | return _c | |
| 260 | + | } | |
| @@ -0,0 +1,260 @@ | |||||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |||
| 2 | + | // github.com/vektra/mockery | |||
| 3 | + | // template: testify | |||
| 4 | + | ||||
| 5 | + | package ui | |||
| 6 | + | ||||
| 7 | + | import ( | |||
| 8 | + | "github.com/bnema/nefergui/internal/layout" | |||
| 9 | + | "github.com/bnema/nefergui/internal/presentation/session" | |||
| 10 | + | mock "github.com/stretchr/testify/mock" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // newMocktarget creates a new instance of mocktarget. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 14 | + | // The first argument is typically a *testing.T value. | |||
| 15 | + | func newMocktarget(t interface { | |||
| 16 | + | mock.TestingT | |||
| 17 | + | Cleanup(func()) | |||
| 18 | + | }) *mocktarget { | |||
| 19 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 20 | + | helper.Helper() | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | mock := &mocktarget{} | |||
| 24 | + | mock.Mock.Test(t) | |||
| 25 | + | ||||
| 26 | + | t.Cleanup(func() { | |||
| 27 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 28 | + | helper.Helper() | |||
| 29 | + | } | |||
| 30 | + | mock.AssertExpectations(t) | |||
| 31 | + | }) | |||
| 32 | + | ||||
| 33 | + | return mock | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | // mocktarget is an autogenerated mock type for the target type | |||
| 37 | + | type mocktarget struct { | |||
| 38 | + | mock.Mock | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | type mocktarget_Expecter struct { | |||
| 42 | + | mock *mock.Mock | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | func (_m *mocktarget) EXPECT() *mocktarget_Expecter { | |||
| 46 | + | return &mocktarget_Expecter{mock: &_m.Mock} | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | // Close provides a mock function for the type mocktarget | |||
| 50 | + | func (_mock *mocktarget) Close() error { | |||
| 51 | + | ret := _mock.Called() | |||
| 52 | + | ||||
| 53 | + | if len(ret) == 0 { | |||
| 54 | + | panic("no return value specified for Close") | |||
| 55 | + | } | |||
| 56 | + | ||||
| 57 | + | var r0 error | |||
| 58 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |||
| 59 | + | r0 = returnFunc() | |||
| 60 | + | } else { | |||
| 61 | + | r0 = ret.Error(0) | |||
| 62 | + | } | |||
| 63 | + | return r0 | |||
| 64 | + | } | |||
| 65 | + | ||||
| 66 | + | // mocktarget_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' | |||
| 67 | + | type mocktarget_Close_Call struct { | |||
| 68 | + | *mock.Call | |||
| 69 | + | } | |||
| 70 | + | ||||
| 71 | + | // Close is a helper method to define mock.On call | |||
| 72 | + | func (_e *mocktarget_Expecter) Close() *mocktarget_Close_Call { | |||
| 73 | + | return &mocktarget_Close_Call{Call: _e.mock.On("Close")} | |||
| 74 | + | } | |||
| 75 | + | ||||
| 76 | + | func (_c *mocktarget_Close_Call) Run(run func()) *mocktarget_Close_Call { | |||
| 77 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 78 | + | run() | |||
| 79 | + | }) | |||
| 80 | + | return _c | |||
| 81 | + | } | |||
| 82 | + | ||||
| 83 | + | func (_c *mocktarget_Close_Call) Return(err error) *mocktarget_Close_Call { | |||
| 84 | + | _c.Call.Return(err) | |||
| 85 | + | return _c | |||
| 86 | + | } | |||
| 87 | + | ||||
| 88 | + | func (_c *mocktarget_Close_Call) RunAndReturn(run func() error) *mocktarget_Close_Call { | |||
| 89 | + | _c.Call.Return(run) | |||
| 90 | + | return _c | |||
| 91 | + | } | |||
| 92 | + | ||||
| 93 | + | // Draw provides a mock function for the type mocktarget | |||
| 94 | + | func (_mock *mocktarget) Draw(display []layout.Command, scale float64, out *session.Output) (bool, error) { | |||
| 95 | + | ret := _mock.Called(display, scale, out) | |||
| 96 | + | ||||
| 97 | + | if len(ret) == 0 { | |||
| 98 | + | panic("no return value specified for Draw") | |||
| 99 | + | } | |||
| 100 | + | ||||
| 101 | + | var r0 bool | |||
| 102 | + | var r1 error | |||
| 103 | + | if returnFunc, ok := ret.Get(0).(func([]layout.Command, float64, *session.Output) (bool, error)); ok { | |||
| 104 | + | return returnFunc(display, scale, out) | |||
| 105 | + | } | |||
| 106 | + | if returnFunc, ok := ret.Get(0).(func([]layout.Command, float64, *session.Output) bool); ok { | |||
| 107 | + | r0 = returnFunc(display, scale, out) | |||
| 108 | + | } else { | |||
| 109 | + | r0 = ret.Get(0).(bool) | |||
| 110 | + | } | |||
| 111 | + | if returnFunc, ok := ret.Get(1).(func([]layout.Command, float64, *session.Output) error); ok { | |||
| 112 | + | r1 = returnFunc(display, scale, out) | |||
| 113 | + | } else { | |||
| 114 | + | r1 = ret.Error(1) | |||
| 115 | + | } | |||
| 116 | + | return r0, r1 | |||
| 117 | + | } | |||
| 118 | + | ||||
| 119 | + | // mocktarget_Draw_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Draw' | |||
| 120 | + | type mocktarget_Draw_Call struct { | |||
| 121 | + | *mock.Call | |||
| 122 | + | } | |||
| 123 | + | ||||
| 124 | + | // Draw is a helper method to define mock.On call | |||
| 125 | + | // - display []layout.Command | |||
| 126 | + | // - scale float64 | |||
| 127 | + | // - out *session.Output | |||
| 128 | + | func (_e *mocktarget_Expecter) Draw(display any, scale any, out any) *mocktarget_Draw_Call { | |||
| 129 | + | return &mocktarget_Draw_Call{Call: _e.mock.On("Draw", display, scale, out)} | |||
| 130 | + | } | |||
| 131 | + | ||||
| 132 | + | func (_c *mocktarget_Draw_Call) Run(run func(display []layout.Command, scale float64, out *session.Output)) *mocktarget_Draw_Call { | |||
| 133 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 134 | + | var arg0 []layout.Command | |||
| 135 | + | if args[0] != nil { | |||
| 136 | + | arg0 = args[0].([]layout.Command) | |||
| 137 | + | } | |||
| 138 | + | var arg1 float64 | |||
| 139 | + | if args[1] != nil { | |||
| 140 | + | arg1 = args[1].(float64) | |||
| 141 | + | } | |||
| 142 | + | var arg2 *session.Output | |||
| 143 | + | if args[2] != nil { | |||
| 144 | + | arg2 = args[2].(*session.Output) | |||
| 145 | + | } | |||
| 146 | + | run( | |||
| 147 | + | arg0, | |||
| 148 | + | arg1, | |||
| 149 | + | arg2, | |||
| 150 | + | ) | |||
| 151 | + | }) | |||
| 152 | + | return _c | |||
| 153 | + | } | |||
| 154 | + | ||||
| 155 | + | func (_c *mocktarget_Draw_Call) Return(b bool, err error) *mocktarget_Draw_Call { | |||
| 156 | + | _c.Call.Return(b, err) | |||
| 157 | + | return _c | |||
| 158 | + | } | |||
| 159 | + | ||||
| 160 | + | func (_c *mocktarget_Draw_Call) RunAndReturn(run func(display []layout.Command, scale float64, out *session.Output) (bool, error)) *mocktarget_Draw_Call { | |||
| 161 | + | _c.Call.Return(run) | |||
| 162 | + | return _c | |||
| 163 | + | } | |||
| 164 | + | ||||
| 165 | + | // Released provides a mock function for the type mocktarget | |||
| 166 | + | func (_mock *mocktarget) Released(buffer uint64) error { | |||
| 167 | + | ret := _mock.Called(buffer) | |||
| 168 | + | ||||
| 169 | + | if len(ret) == 0 { | |||
| 170 | + | panic("no return value specified for Released") | |||
| 171 | + | } | |||
| 172 | + | ||||
| 173 | + | var r0 error | |||
| 174 | + | if returnFunc, ok := ret.Get(0).(func(uint64) error); ok { | |||
| 175 | + | r0 = returnFunc(buffer) | |||
| 176 | + | } else { | |||
| 177 | + | r0 = ret.Error(0) | |||
| 178 | + | } | |||
| 179 | + | return r0 | |||
| 180 | + | } | |||
| 181 | + | ||||
| 182 | + | // mocktarget_Released_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Released' | |||
| 183 | + | type mocktarget_Released_Call struct { | |||
| 184 | + | *mock.Call | |||
| 185 | + | } | |||
| 186 | + | ||||
| 187 | + | // Released is a helper method to define mock.On call | |||
| 188 | + | // - buffer uint64 | |||
| 189 | + | func (_e *mocktarget_Expecter) Released(buffer any) *mocktarget_Released_Call { | |||
| 190 | + | return &mocktarget_Released_Call{Call: _e.mock.On("Released", buffer)} | |||
| 191 | + | } | |||
| 192 | + | ||||
| 193 | + | func (_c *mocktarget_Released_Call) Run(run func(buffer uint64)) *mocktarget_Released_Call { | |||
| 194 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 195 | + | var arg0 uint64 | |||
| 196 | + | if args[0] != nil { | |||
| 197 | + | arg0 = args[0].(uint64) | |||
| 198 | + | } | |||
| 199 | + | run( | |||
| 200 | + | arg0, | |||
| 201 | + | ) | |||
| 202 | + | }) | |||
| 203 | + | return _c | |||
| 204 | + | } | |||
| 205 | + | ||||
| 206 | + | func (_c *mocktarget_Released_Call) Return(err error) *mocktarget_Released_Call { | |||
| 207 | + | _c.Call.Return(err) | |||
| 208 | + | return _c | |||
| 209 | + | } | |||
| 210 | + | ||||
| 211 | + | func (_c *mocktarget_Released_Call) RunAndReturn(run func(buffer uint64) error) *mocktarget_Released_Call { | |||
| 212 | + | _c.Call.Return(run) | |||
| 213 | + | return _c | |||
| 214 | + | } | |||
| 215 | + | ||||
| 216 | + | // Resize provides a mock function for the type mocktarget | |||
| 217 | + | func (_mock *mocktarget) Resize(width int32, height int32) { | |||
| 218 | + | _mock.Called(width, height) | |||
| 219 | + | return | |||
| 220 | + | } | |||
| 221 | + | ||||
| 222 | + | // mocktarget_Resize_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Resize' | |||
| 223 | + | type mocktarget_Resize_Call struct { | |||
| 224 | + | *mock.Call | |||
| 225 | + | } | |||
| 226 | + | ||||
| 227 | + | // Resize is a helper method to define mock.On call | |||
| 228 | + | // - width int32 | |||
| 229 | + | // - height int32 | |||
| 230 | + | func (_e *mocktarget_Expecter) Resize(width any, height any) *mocktarget_Resize_Call { | |||
| 231 | + | return &mocktarget_Resize_Call{Call: _e.mock.On("Resize", width, height)} | |||
| 232 | + | } | |||
| 233 | + | ||||
| 234 | + | func (_c *mocktarget_Resize_Call) Run(run func(width int32, height int32)) *mocktarget_Resize_Call { | |||
| 235 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 236 | + | var arg0 int32 | |||
| 237 | + | if args[0] != nil { | |||
| 238 | + | arg0 = args[0].(int32) | |||
| 239 | + | } | |||
| 240 | + | var arg1 int32 | |||
| 241 | + | if args[1] != nil { | |||
| 242 | + | arg1 = args[1].(int32) | |||
| 243 | + | } | |||
| 244 | + | run( | |||
| 245 | + | arg0, | |||
| 246 | + | arg1, | |||
| 247 | + | ) | |||
| 248 | + | }) | |||
| 249 | + | return _c | |||
| 250 | + | } | |||
| 251 | + | ||||
| 252 | + | func (_c *mocktarget_Resize_Call) Return() *mocktarget_Resize_Call { | |||
| 253 | + | _c.Call.Return() | |||
| 254 | + | return _c | |||
| 255 | + | } | |||
| 256 | + | ||||
| 257 | + | func (_c *mocktarget_Resize_Call) RunAndReturn(run func(width int32, height int32)) *mocktarget_Resize_Call { | |||
| 258 | + | _c.Run(run) | |||
| 259 | + | return _c | |||
| 260 | + | } | |||