detached 8b015e3

test: replace handwritten clipboard and IME doubles with Mockery mocks

commit
8b015e39a00264c7da62e176a50a0edf030c2596
author
brice · · 2026-10-01 17:53 UTC
parent
2543591
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
9 files · +672 -82

this diff holds invisible characters (direction controls, zero-width characters). They are shown as <U+XXXX> markers, never applied.

M .mockery.yml +13 -0
@@ -22,4 +22,17 @@ packages:
2222 filename: '{{.InterfaceName}}_mock_test.go'
2323 interfaces:
2424 IME:
25+ configs:
26+ - {}
27+ # Also mocked next to the UI tests that drive the editors.
28+ - dir: internal/ui
29+ pkgname: ui
2530 Clipboard:
31+ configs:
32+ - {}
33+ - dir: internal/ui
34+ pkgname: ui
35+ AsyncClipboard:
36+ configs:
37+ - dir: internal/ui
38+ pkgname: ui
@@ -22,4 +22,17 @@ packages:
22 filename: '{{.InterfaceName}}_mock_test.go'22 filename: '{{.InterfaceName}}_mock_test.go'
23 interfaces:23 interfaces:
24 IME:24 IME:
25+ configs:
26+ - {}
27+ # Also mocked next to the UI tests that drive the editors.
28+ - dir: internal/ui
29+ pkgname: ui
25 Clipboard:30 Clipboard:
31+ configs:
32+ - {}
33+ - dir: internal/ui
34+ pkgname: ui
35+ AsyncClipboard:
36+ configs:
37+ - dir: internal/ui
38+ pkgname: ui
M internal/edit/edit_test.go +8 -10
@@ -8,11 +8,6 @@ import (
88 "github.com/stretchr/testify/mock"
99 )
1010
11-type memoryClipboard struct{ data []byte }
12-
13-func (m *memoryClipboard) ReadText(limit int) ([]byte, error) { return m.data, nil }
14-func (m *memoryClipboard) WriteText(v []byte) error { m.data = append([]byte(nil), v...); return nil }
15-
1611 func TestGraphemesAndIME(t *testing.T) {
1712 s := State{}
1813 s.Sync("e\u0301👩<U+200D>💻a")
@@ -53,22 +48,25 @@ func TestClipboardAndMask(t *testing.T) {
5348 s := State{}
5449 s.Sync("s3crét")
5550 s.Select(0, len(s.Value))
56- c := &memoryClipboard{}
57- if err := s.Copy(c, true); err != nil || len(c.data) != 0 {
51+ c := NewMockClipboard(t) // a masked copy must not write
52+ if err := s.Copy(c, true); err != nil {
5853 t.Fatal(err)
5954 }
6055 if s.Mask() != "••••••" {
6156 t.Fatal(s.Mask())
6257 }
63- c.data = []byte{0xff}
58+ read := func(data []byte) {
59+ c.EXPECT().ReadText(mock.Anything).Return(data, nil).Once()
60+ }
61+ read([]byte{0xff})
6462 if changed, _ := s.Paste(c); changed {
6563 t.Fatal(s)
6664 }
67- c.data = []byte(strings.Repeat("x", MaxClipboardBytes+1))
65+ read([]byte(strings.Repeat("x", MaxClipboardBytes+1)))
6866 if changed, _ := s.Paste(c); changed {
6967 t.Fatal(s)
7068 }
71- c.data = []byte("ok")
69+ read([]byte("ok"))
7270 if changed, _ := s.Paste(c); !changed || s.Value != "ok" {
7371 t.Fatal(s)
7472 }
@@ -8,11 +8,6 @@ import (
8 "github.com/stretchr/testify/mock"8 "github.com/stretchr/testify/mock"
9 )9 )
10 10
11-type memoryClipboard struct{ data []byte }
12-
13-func (m *memoryClipboard) ReadText(limit int) ([]byte, error) { return m.data, nil }
14-func (m *memoryClipboard) WriteText(v []byte) error { m.data = append([]byte(nil), v...); return nil }
15-
16 func TestGraphemesAndIME(t *testing.T) {11 func TestGraphemesAndIME(t *testing.T) {
17 s := State{}12 s := State{}
18 s.Sync("e\u0301👩<U+200D>💻a")13 s.Sync("e\u0301👩<U+200D>💻a")
@@ -53,22 +48,25 @@ func TestClipboardAndMask(t *testing.T) {
53 s := State{}48 s := State{}
54 s.Sync("s3crét")49 s.Sync("s3crét")
55 s.Select(0, len(s.Value))50 s.Select(0, len(s.Value))
56- c := &memoryClipboard{}51+ c := NewMockClipboard(t) // a masked copy must not write
57- if err := s.Copy(c, true); err != nil || len(c.data) != 0 {52+ if err := s.Copy(c, true); err != nil {
58 t.Fatal(err)53 t.Fatal(err)
59 }54 }
60 if s.Mask() != "••••••" {55 if s.Mask() != "••••••" {
61 t.Fatal(s.Mask())56 t.Fatal(s.Mask())
62 }57 }
63- c.data = []byte{0xff}58+ read := func(data []byte) {
59+ c.EXPECT().ReadText(mock.Anything).Return(data, nil).Once()
60+ }
61+ read([]byte{0xff})
64 if changed, _ := s.Paste(c); changed {62 if changed, _ := s.Paste(c); changed {
65 t.Fatal(s)63 t.Fatal(s)
66 }64 }
67- c.data = []byte(strings.Repeat("x", MaxClipboardBytes+1))65+ read([]byte(strings.Repeat("x", MaxClipboardBytes+1)))
68 if changed, _ := s.Paste(c); changed {66 if changed, _ := s.Paste(c); changed {
69 t.Fatal(s)67 t.Fatal(s)
70 }68 }
71- c.data = []byte("ok")69+ read([]byte("ok"))
72 if changed, _ := s.Paste(c); !changed || s.Value != "ok" {70 if changed, _ := s.Paste(c); !changed || s.Value != "ok" {
73 t.Fatal(s)71 t.Fatal(s)
74 }72 }
A internal/ui/AsyncClipboard_mock_test.go +161 -0
@@ -0,0 +1,161 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package ui
6+
7+import (
8+ "context"
9+
10+ mock "github.com/stretchr/testify/mock"
11+)
12+
13+// NewMockAsyncClipboard creates a new instance of MockAsyncClipboard. 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 NewMockAsyncClipboard(t interface {
16+ mock.TestingT
17+ Cleanup(func())
18+}) *MockAsyncClipboard {
19+ if helper, ok := t.(interface{ Helper() }); ok {
20+ helper.Helper()
21+ }
22+
23+ mock := &MockAsyncClipboard{}
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+// MockAsyncClipboard is an autogenerated mock type for the AsyncClipboard type
37+type MockAsyncClipboard struct {
38+ mock.Mock
39+}
40+
41+type MockAsyncClipboard_Expecter struct {
42+ mock *mock.Mock
43+}
44+
45+func (_m *MockAsyncClipboard) EXPECT() *MockAsyncClipboard_Expecter {
46+ return &MockAsyncClipboard_Expecter{mock: &_m.Mock}
47+}
48+
49+// ReadTextAsync provides a mock function for the type MockAsyncClipboard
50+func (_mock *MockAsyncClipboard) ReadTextAsync(ctx context.Context, limit int, deliver func([]byte, error)) error {
51+ ret := _mock.Called(ctx, limit, deliver)
52+
53+ if len(ret) == 0 {
54+ panic("no return value specified for ReadTextAsync")
55+ }
56+
57+ var r0 error
58+ if returnFunc, ok := ret.Get(0).(func(context.Context, int, func([]byte, error)) error); ok {
59+ r0 = returnFunc(ctx, limit, deliver)
60+ } else {
61+ r0 = ret.Error(0)
62+ }
63+ return r0
64+}
65+
66+// MockAsyncClipboard_ReadTextAsync_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadTextAsync'
67+type MockAsyncClipboard_ReadTextAsync_Call struct {
68+ *mock.Call
69+}
70+
71+// ReadTextAsync is a helper method to define mock.On call
72+// - ctx context.Context
73+// - limit int
74+// - deliver func([]byte, error)
75+func (_e *MockAsyncClipboard_Expecter) ReadTextAsync(ctx any, limit any, deliver any) *MockAsyncClipboard_ReadTextAsync_Call {
76+ return &MockAsyncClipboard_ReadTextAsync_Call{Call: _e.mock.On("ReadTextAsync", ctx, limit, deliver)}
77+}
78+
79+func (_c *MockAsyncClipboard_ReadTextAsync_Call) Run(run func(ctx context.Context, limit int, deliver func([]byte, error))) *MockAsyncClipboard_ReadTextAsync_Call {
80+ _c.Call.Run(func(args mock.Arguments) {
81+ var arg0 context.Context
82+ if args[0] != nil {
83+ arg0 = args[0].(context.Context)
84+ }
85+ var arg1 int
86+ if args[1] != nil {
87+ arg1 = args[1].(int)
88+ }
89+ var arg2 func([]byte, error)
90+ if args[2] != nil {
91+ arg2 = args[2].(func([]byte, error))
92+ }
93+ run(
94+ arg0,
95+ arg1,
96+ arg2,
97+ )
98+ })
99+ return _c
100+}
101+
102+func (_c *MockAsyncClipboard_ReadTextAsync_Call) Return(err error) *MockAsyncClipboard_ReadTextAsync_Call {
103+ _c.Call.Return(err)
104+ return _c
105+}
106+
107+func (_c *MockAsyncClipboard_ReadTextAsync_Call) RunAndReturn(run func(ctx context.Context, limit int, deliver func([]byte, error)) error) *MockAsyncClipboard_ReadTextAsync_Call {
108+ _c.Call.Return(run)
109+ return _c
110+}
111+
112+// WriteText provides a mock function for the type MockAsyncClipboard
113+func (_mock *MockAsyncClipboard) WriteText(bytes []byte) error {
114+ ret := _mock.Called(bytes)
115+
116+ if len(ret) == 0 {
117+ panic("no return value specified for WriteText")
118+ }
119+
120+ var r0 error
121+ if returnFunc, ok := ret.Get(0).(func([]byte) error); ok {
122+ r0 = returnFunc(bytes)
123+ } else {
124+ r0 = ret.Error(0)
125+ }
126+ return r0
127+}
128+
129+// MockAsyncClipboard_WriteText_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteText'
130+type MockAsyncClipboard_WriteText_Call struct {
131+ *mock.Call
132+}
133+
134+// WriteText is a helper method to define mock.On call
135+// - bytes []byte
136+func (_e *MockAsyncClipboard_Expecter) WriteText(bytes any) *MockAsyncClipboard_WriteText_Call {
137+ return &MockAsyncClipboard_WriteText_Call{Call: _e.mock.On("WriteText", bytes)}
138+}
139+
140+func (_c *MockAsyncClipboard_WriteText_Call) Run(run func(bytes []byte)) *MockAsyncClipboard_WriteText_Call {
141+ _c.Call.Run(func(args mock.Arguments) {
142+ var arg0 []byte
143+ if args[0] != nil {
144+ arg0 = args[0].([]byte)
145+ }
146+ run(
147+ arg0,
148+ )
149+ })
150+ return _c
151+}
152+
153+func (_c *MockAsyncClipboard_WriteText_Call) Return(err error) *MockAsyncClipboard_WriteText_Call {
154+ _c.Call.Return(err)
155+ return _c
156+}
157+
158+func (_c *MockAsyncClipboard_WriteText_Call) RunAndReturn(run func(bytes []byte) error) *MockAsyncClipboard_WriteText_Call {
159+ _c.Call.Return(run)
160+ return _c
161+}
@@ -0,0 +1,161 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package ui
6+
7+import (
8+ "context"
9+
10+ mock "github.com/stretchr/testify/mock"
11+)
12+
13+// NewMockAsyncClipboard creates a new instance of MockAsyncClipboard. 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 NewMockAsyncClipboard(t interface {
16+ mock.TestingT
17+ Cleanup(func())
18+}) *MockAsyncClipboard {
19+ if helper, ok := t.(interface{ Helper() }); ok {
20+ helper.Helper()
21+ }
22+
23+ mock := &MockAsyncClipboard{}
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+// MockAsyncClipboard is an autogenerated mock type for the AsyncClipboard type
37+type MockAsyncClipboard struct {
38+ mock.Mock
39+}
40+
41+type MockAsyncClipboard_Expecter struct {
42+ mock *mock.Mock
43+}
44+
45+func (_m *MockAsyncClipboard) EXPECT() *MockAsyncClipboard_Expecter {
46+ return &MockAsyncClipboard_Expecter{mock: &_m.Mock}
47+}
48+
49+// ReadTextAsync provides a mock function for the type MockAsyncClipboard
50+func (_mock *MockAsyncClipboard) ReadTextAsync(ctx context.Context, limit int, deliver func([]byte, error)) error {
51+ ret := _mock.Called(ctx, limit, deliver)
52+
53+ if len(ret) == 0 {
54+ panic("no return value specified for ReadTextAsync")
55+ }
56+
57+ var r0 error
58+ if returnFunc, ok := ret.Get(0).(func(context.Context, int, func([]byte, error)) error); ok {
59+ r0 = returnFunc(ctx, limit, deliver)
60+ } else {
61+ r0 = ret.Error(0)
62+ }
63+ return r0
64+}
65+
66+// MockAsyncClipboard_ReadTextAsync_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadTextAsync'
67+type MockAsyncClipboard_ReadTextAsync_Call struct {
68+ *mock.Call
69+}
70+
71+// ReadTextAsync is a helper method to define mock.On call
72+// - ctx context.Context
73+// - limit int
74+// - deliver func([]byte, error)
75+func (_e *MockAsyncClipboard_Expecter) ReadTextAsync(ctx any, limit any, deliver any) *MockAsyncClipboard_ReadTextAsync_Call {
76+ return &MockAsyncClipboard_ReadTextAsync_Call{Call: _e.mock.On("ReadTextAsync", ctx, limit, deliver)}
77+}
78+
79+func (_c *MockAsyncClipboard_ReadTextAsync_Call) Run(run func(ctx context.Context, limit int, deliver func([]byte, error))) *MockAsyncClipboard_ReadTextAsync_Call {
80+ _c.Call.Run(func(args mock.Arguments) {
81+ var arg0 context.Context
82+ if args[0] != nil {
83+ arg0 = args[0].(context.Context)
84+ }
85+ var arg1 int
86+ if args[1] != nil {
87+ arg1 = args[1].(int)
88+ }
89+ var arg2 func([]byte, error)
90+ if args[2] != nil {
91+ arg2 = args[2].(func([]byte, error))
92+ }
93+ run(
94+ arg0,
95+ arg1,
96+ arg2,
97+ )
98+ })
99+ return _c
100+}
101+
102+func (_c *MockAsyncClipboard_ReadTextAsync_Call) Return(err error) *MockAsyncClipboard_ReadTextAsync_Call {
103+ _c.Call.Return(err)
104+ return _c
105+}
106+
107+func (_c *MockAsyncClipboard_ReadTextAsync_Call) RunAndReturn(run func(ctx context.Context, limit int, deliver func([]byte, error)) error) *MockAsyncClipboard_ReadTextAsync_Call {
108+ _c.Call.Return(run)
109+ return _c
110+}
111+
112+// WriteText provides a mock function for the type MockAsyncClipboard
113+func (_mock *MockAsyncClipboard) WriteText(bytes []byte) error {
114+ ret := _mock.Called(bytes)
115+
116+ if len(ret) == 0 {
117+ panic("no return value specified for WriteText")
118+ }
119+
120+ var r0 error
121+ if returnFunc, ok := ret.Get(0).(func([]byte) error); ok {
122+ r0 = returnFunc(bytes)
123+ } else {
124+ r0 = ret.Error(0)
125+ }
126+ return r0
127+}
128+
129+// MockAsyncClipboard_WriteText_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteText'
130+type MockAsyncClipboard_WriteText_Call struct {
131+ *mock.Call
132+}
133+
134+// WriteText is a helper method to define mock.On call
135+// - bytes []byte
136+func (_e *MockAsyncClipboard_Expecter) WriteText(bytes any) *MockAsyncClipboard_WriteText_Call {
137+ return &MockAsyncClipboard_WriteText_Call{Call: _e.mock.On("WriteText", bytes)}
138+}
139+
140+func (_c *MockAsyncClipboard_WriteText_Call) Run(run func(bytes []byte)) *MockAsyncClipboard_WriteText_Call {
141+ _c.Call.Run(func(args mock.Arguments) {
142+ var arg0 []byte
143+ if args[0] != nil {
144+ arg0 = args[0].([]byte)
145+ }
146+ run(
147+ arg0,
148+ )
149+ })
150+ return _c
151+}
152+
153+func (_c *MockAsyncClipboard_WriteText_Call) Return(err error) *MockAsyncClipboard_WriteText_Call {
154+ _c.Call.Return(err)
155+ return _c
156+}
157+
158+func (_c *MockAsyncClipboard_WriteText_Call) RunAndReturn(run func(bytes []byte) error) *MockAsyncClipboard_WriteText_Call {
159+ _c.Call.Return(run)
160+ return _c
161+}
A internal/ui/Clipboard_mock_test.go +158 -0
@@ -0,0 +1,158 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package ui
6+
7+import (
8+ mock "github.com/stretchr/testify/mock"
9+)
10+
11+// NewMockClipboard creates a new instance of MockClipboard. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
12+// The first argument is typically a *testing.T value.
13+func NewMockClipboard(t interface {
14+ mock.TestingT
15+ Cleanup(func())
16+}) *MockClipboard {
17+ if helper, ok := t.(interface{ Helper() }); ok {
18+ helper.Helper()
19+ }
20+
21+ mock := &MockClipboard{}
22+ mock.Mock.Test(t)
23+
24+ t.Cleanup(func() {
25+ if helper, ok := t.(interface{ Helper() }); ok {
26+ helper.Helper()
27+ }
28+ mock.AssertExpectations(t)
29+ })
30+
31+ return mock
32+}
33+
34+// MockClipboard is an autogenerated mock type for the Clipboard type
35+type MockClipboard struct {
36+ mock.Mock
37+}
38+
39+type MockClipboard_Expecter struct {
40+ mock *mock.Mock
41+}
42+
43+func (_m *MockClipboard) EXPECT() *MockClipboard_Expecter {
44+ return &MockClipboard_Expecter{mock: &_m.Mock}
45+}
46+
47+// ReadText provides a mock function for the type MockClipboard
48+func (_mock *MockClipboard) ReadText(limit int) ([]byte, error) {
49+ ret := _mock.Called(limit)
50+
51+ if len(ret) == 0 {
52+ panic("no return value specified for ReadText")
53+ }
54+
55+ var r0 []byte
56+ var r1 error
57+ if returnFunc, ok := ret.Get(0).(func(int) ([]byte, error)); ok {
58+ return returnFunc(limit)
59+ }
60+ if returnFunc, ok := ret.Get(0).(func(int) []byte); ok {
61+ r0 = returnFunc(limit)
62+ } else {
63+ if ret.Get(0) != nil {
64+ r0 = ret.Get(0).([]byte)
65+ }
66+ }
67+ if returnFunc, ok := ret.Get(1).(func(int) error); ok {
68+ r1 = returnFunc(limit)
69+ } else {
70+ r1 = ret.Error(1)
71+ }
72+ return r0, r1
73+}
74+
75+// MockClipboard_ReadText_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadText'
76+type MockClipboard_ReadText_Call struct {
77+ *mock.Call
78+}
79+
80+// ReadText is a helper method to define mock.On call
81+// - limit int
82+func (_e *MockClipboard_Expecter) ReadText(limit any) *MockClipboard_ReadText_Call {
83+ return &MockClipboard_ReadText_Call{Call: _e.mock.On("ReadText", limit)}
84+}
85+
86+func (_c *MockClipboard_ReadText_Call) Run(run func(limit int)) *MockClipboard_ReadText_Call {
87+ _c.Call.Run(func(args mock.Arguments) {
88+ var arg0 int
89+ if args[0] != nil {
90+ arg0 = args[0].(int)
91+ }
92+ run(
93+ arg0,
94+ )
95+ })
96+ return _c
97+}
98+
99+func (_c *MockClipboard_ReadText_Call) Return(bytes []byte, err error) *MockClipboard_ReadText_Call {
100+ _c.Call.Return(bytes, err)
101+ return _c
102+}
103+
104+func (_c *MockClipboard_ReadText_Call) RunAndReturn(run func(limit int) ([]byte, error)) *MockClipboard_ReadText_Call {
105+ _c.Call.Return(run)
106+ return _c
107+}
108+
109+// WriteText provides a mock function for the type MockClipboard
110+func (_mock *MockClipboard) WriteText(bytes []byte) error {
111+ ret := _mock.Called(bytes)
112+
113+ if len(ret) == 0 {
114+ panic("no return value specified for WriteText")
115+ }
116+
117+ var r0 error
118+ if returnFunc, ok := ret.Get(0).(func([]byte) error); ok {
119+ r0 = returnFunc(bytes)
120+ } else {
121+ r0 = ret.Error(0)
122+ }
123+ return r0
124+}
125+
126+// MockClipboard_WriteText_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteText'
127+type MockClipboard_WriteText_Call struct {
128+ *mock.Call
129+}
130+
131+// WriteText is a helper method to define mock.On call
132+// - bytes []byte
133+func (_e *MockClipboard_Expecter) WriteText(bytes any) *MockClipboard_WriteText_Call {
134+ return &MockClipboard_WriteText_Call{Call: _e.mock.On("WriteText", bytes)}
135+}
136+
137+func (_c *MockClipboard_WriteText_Call) Run(run func(bytes []byte)) *MockClipboard_WriteText_Call {
138+ _c.Call.Run(func(args mock.Arguments) {
139+ var arg0 []byte
140+ if args[0] != nil {
141+ arg0 = args[0].([]byte)
142+ }
143+ run(
144+ arg0,
145+ )
146+ })
147+ return _c
148+}
149+
150+func (_c *MockClipboard_WriteText_Call) Return(err error) *MockClipboard_WriteText_Call {
151+ _c.Call.Return(err)
152+ return _c
153+}
154+
155+func (_c *MockClipboard_WriteText_Call) RunAndReturn(run func(bytes []byte) error) *MockClipboard_WriteText_Call {
156+ _c.Call.Return(run)
157+ return _c
158+}
@@ -0,0 +1,158 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package ui
6+
7+import (
8+ mock "github.com/stretchr/testify/mock"
9+)
10+
11+// NewMockClipboard creates a new instance of MockClipboard. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
12+// The first argument is typically a *testing.T value.
13+func NewMockClipboard(t interface {
14+ mock.TestingT
15+ Cleanup(func())
16+}) *MockClipboard {
17+ if helper, ok := t.(interface{ Helper() }); ok {
18+ helper.Helper()
19+ }
20+
21+ mock := &MockClipboard{}
22+ mock.Mock.Test(t)
23+
24+ t.Cleanup(func() {
25+ if helper, ok := t.(interface{ Helper() }); ok {
26+ helper.Helper()
27+ }
28+ mock.AssertExpectations(t)
29+ })
30+
31+ return mock
32+}
33+
34+// MockClipboard is an autogenerated mock type for the Clipboard type
35+type MockClipboard struct {
36+ mock.Mock
37+}
38+
39+type MockClipboard_Expecter struct {
40+ mock *mock.Mock
41+}
42+
43+func (_m *MockClipboard) EXPECT() *MockClipboard_Expecter {
44+ return &MockClipboard_Expecter{mock: &_m.Mock}
45+}
46+
47+// ReadText provides a mock function for the type MockClipboard
48+func (_mock *MockClipboard) ReadText(limit int) ([]byte, error) {
49+ ret := _mock.Called(limit)
50+
51+ if len(ret) == 0 {
52+ panic("no return value specified for ReadText")
53+ }
54+
55+ var r0 []byte
56+ var r1 error
57+ if returnFunc, ok := ret.Get(0).(func(int) ([]byte, error)); ok {
58+ return returnFunc(limit)
59+ }
60+ if returnFunc, ok := ret.Get(0).(func(int) []byte); ok {
61+ r0 = returnFunc(limit)
62+ } else {
63+ if ret.Get(0) != nil {
64+ r0 = ret.Get(0).([]byte)
65+ }
66+ }
67+ if returnFunc, ok := ret.Get(1).(func(int) error); ok {
68+ r1 = returnFunc(limit)
69+ } else {
70+ r1 = ret.Error(1)
71+ }
72+ return r0, r1
73+}
74+
75+// MockClipboard_ReadText_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReadText'
76+type MockClipboard_ReadText_Call struct {
77+ *mock.Call
78+}
79+
80+// ReadText is a helper method to define mock.On call
81+// - limit int
82+func (_e *MockClipboard_Expecter) ReadText(limit any) *MockClipboard_ReadText_Call {
83+ return &MockClipboard_ReadText_Call{Call: _e.mock.On("ReadText", limit)}
84+}
85+
86+func (_c *MockClipboard_ReadText_Call) Run(run func(limit int)) *MockClipboard_ReadText_Call {
87+ _c.Call.Run(func(args mock.Arguments) {
88+ var arg0 int
89+ if args[0] != nil {
90+ arg0 = args[0].(int)
91+ }
92+ run(
93+ arg0,
94+ )
95+ })
96+ return _c
97+}
98+
99+func (_c *MockClipboard_ReadText_Call) Return(bytes []byte, err error) *MockClipboard_ReadText_Call {
100+ _c.Call.Return(bytes, err)
101+ return _c
102+}
103+
104+func (_c *MockClipboard_ReadText_Call) RunAndReturn(run func(limit int) ([]byte, error)) *MockClipboard_ReadText_Call {
105+ _c.Call.Return(run)
106+ return _c
107+}
108+
109+// WriteText provides a mock function for the type MockClipboard
110+func (_mock *MockClipboard) WriteText(bytes []byte) error {
111+ ret := _mock.Called(bytes)
112+
113+ if len(ret) == 0 {
114+ panic("no return value specified for WriteText")
115+ }
116+
117+ var r0 error
118+ if returnFunc, ok := ret.Get(0).(func([]byte) error); ok {
119+ r0 = returnFunc(bytes)
120+ } else {
121+ r0 = ret.Error(0)
122+ }
123+ return r0
124+}
125+
126+// MockClipboard_WriteText_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteText'
127+type MockClipboard_WriteText_Call struct {
128+ *mock.Call
129+}
130+
131+// WriteText is a helper method to define mock.On call
132+// - bytes []byte
133+func (_e *MockClipboard_Expecter) WriteText(bytes any) *MockClipboard_WriteText_Call {
134+ return &MockClipboard_WriteText_Call{Call: _e.mock.On("WriteText", bytes)}
135+}
136+
137+func (_c *MockClipboard_WriteText_Call) Run(run func(bytes []byte)) *MockClipboard_WriteText_Call {
138+ _c.Call.Run(func(args mock.Arguments) {
139+ var arg0 []byte
140+ if args[0] != nil {
141+ arg0 = args[0].([]byte)
142+ }
143+ run(
144+ arg0,
145+ )
146+ })
147+ return _c
148+}
149+
150+func (_c *MockClipboard_WriteText_Call) Return(err error) *MockClipboard_WriteText_Call {
151+ _c.Call.Return(err)
152+ return _c
153+}
154+
155+func (_c *MockClipboard_WriteText_Call) RunAndReturn(run func(bytes []byte) error) *MockClipboard_WriteText_Call {
156+ _c.Call.Return(run)
157+ return _c
158+}
A internal/ui/IME_mock_test.go +267 -0
@@ -0,0 +1,267 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package ui
6+
7+import (
8+ mock "github.com/stretchr/testify/mock"
9+)
10+
11+// NewMockIME creates a new instance of MockIME. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
12+// The first argument is typically a *testing.T value.
13+func NewMockIME(t interface {
14+ mock.TestingT
15+ Cleanup(func())
16+}) *MockIME {
17+ if helper, ok := t.(interface{ Helper() }); ok {
18+ helper.Helper()
19+ }
20+
21+ mock := &MockIME{}
22+ mock.Mock.Test(t)
23+
24+ t.Cleanup(func() {
25+ if helper, ok := t.(interface{ Helper() }); ok {
26+ helper.Helper()
27+ }
28+ mock.AssertExpectations(t)
29+ })
30+
31+ return mock
32+}
33+
34+// MockIME is an autogenerated mock type for the IME type
35+type MockIME struct {
36+ mock.Mock
37+}
38+
39+type MockIME_Expecter struct {
40+ mock *mock.Mock
41+}
42+
43+func (_m *MockIME) EXPECT() *MockIME_Expecter {
44+ return &MockIME_Expecter{mock: &_m.Mock}
45+}
46+
47+// ContentType provides a mock function for the type MockIME
48+func (_mock *MockIME) ContentType(password bool, multiline bool) {
49+ _mock.Called(password, multiline)
50+ return
51+}
52+
53+// MockIME_ContentType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ContentType'
54+type MockIME_ContentType_Call struct {
55+ *mock.Call
56+}
57+
58+// ContentType is a helper method to define mock.On call
59+// - password bool
60+// - multiline bool
61+func (_e *MockIME_Expecter) ContentType(password any, multiline any) *MockIME_ContentType_Call {
62+ return &MockIME_ContentType_Call{Call: _e.mock.On("ContentType", password, multiline)}
63+}
64+
65+func (_c *MockIME_ContentType_Call) Run(run func(password bool, multiline bool)) *MockIME_ContentType_Call {
66+ _c.Call.Run(func(args mock.Arguments) {
67+ var arg0 bool
68+ if args[0] != nil {
69+ arg0 = args[0].(bool)
70+ }
71+ var arg1 bool
72+ if args[1] != nil {
73+ arg1 = args[1].(bool)
74+ }
75+ run(
76+ arg0,
77+ arg1,
78+ )
79+ })
80+ return _c
81+}
82+
83+func (_c *MockIME_ContentType_Call) Return() *MockIME_ContentType_Call {
84+ _c.Call.Return()
85+ return _c
86+}
87+
88+func (_c *MockIME_ContentType_Call) RunAndReturn(run func(password bool, multiline bool)) *MockIME_ContentType_Call {
89+ _c.Run(run)
90+ return _c
91+}
92+
93+// CursorRect provides a mock function for the type MockIME
94+func (_mock *MockIME) CursorRect(x float64, y float64, w float64, h float64) {
95+ _mock.Called(x, y, w, h)
96+ return
97+}
98+
99+// MockIME_CursorRect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CursorRect'
100+type MockIME_CursorRect_Call struct {
101+ *mock.Call
102+}
103+
104+// CursorRect is a helper method to define mock.On call
105+// - x float64
106+// - y float64
107+// - w float64
108+// - h float64
109+func (_e *MockIME_Expecter) CursorRect(x any, y any, w any, h any) *MockIME_CursorRect_Call {
110+ return &MockIME_CursorRect_Call{Call: _e.mock.On("CursorRect", x, y, w, h)}
111+}
112+
113+func (_c *MockIME_CursorRect_Call) Run(run func(x float64, y float64, w float64, h float64)) *MockIME_CursorRect_Call {
114+ _c.Call.Run(func(args mock.Arguments) {
115+ var arg0 float64
116+ if args[0] != nil {
117+ arg0 = args[0].(float64)
118+ }
119+ var arg1 float64
120+ if args[1] != nil {
121+ arg1 = args[1].(float64)
122+ }
123+ var arg2 float64
124+ if args[2] != nil {
125+ arg2 = args[2].(float64)
126+ }
127+ var arg3 float64
128+ if args[3] != nil {
129+ arg3 = args[3].(float64)
130+ }
131+ run(
132+ arg0,
133+ arg1,
134+ arg2,
135+ arg3,
136+ )
137+ })
138+ return _c
139+}
140+
141+func (_c *MockIME_CursorRect_Call) Return() *MockIME_CursorRect_Call {
142+ _c.Call.Return()
143+ return _c
144+}
145+
146+func (_c *MockIME_CursorRect_Call) RunAndReturn(run func(x float64, y float64, w float64, h float64)) *MockIME_CursorRect_Call {
147+ _c.Run(run)
148+ return _c
149+}
150+
151+// Disable provides a mock function for the type MockIME
152+func (_mock *MockIME) Disable() {
153+ _mock.Called()
154+ return
155+}
156+
157+// MockIME_Disable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Disable'
158+type MockIME_Disable_Call struct {
159+ *mock.Call
160+}
161+
162+// Disable is a helper method to define mock.On call
163+func (_e *MockIME_Expecter) Disable() *MockIME_Disable_Call {
164+ return &MockIME_Disable_Call{Call: _e.mock.On("Disable")}
165+}
166+
167+func (_c *MockIME_Disable_Call) Run(run func()) *MockIME_Disable_Call {
168+ _c.Call.Run(func(args mock.Arguments) {
169+ run()
170+ })
171+ return _c
172+}
173+
174+func (_c *MockIME_Disable_Call) Return() *MockIME_Disable_Call {
175+ _c.Call.Return()
176+ return _c
177+}
178+
179+func (_c *MockIME_Disable_Call) RunAndReturn(run func()) *MockIME_Disable_Call {
180+ _c.Run(run)
181+ return _c
182+}
183+
184+// Enable provides a mock function for the type MockIME
185+func (_mock *MockIME) Enable() {
186+ _mock.Called()
187+ return
188+}
189+
190+// MockIME_Enable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Enable'
191+type MockIME_Enable_Call struct {
192+ *mock.Call
193+}
194+
195+// Enable is a helper method to define mock.On call
196+func (_e *MockIME_Expecter) Enable() *MockIME_Enable_Call {
197+ return &MockIME_Enable_Call{Call: _e.mock.On("Enable")}
198+}
199+
200+func (_c *MockIME_Enable_Call) Run(run func()) *MockIME_Enable_Call {
201+ _c.Call.Run(func(args mock.Arguments) {
202+ run()
203+ })
204+ return _c
205+}
206+
207+func (_c *MockIME_Enable_Call) Return() *MockIME_Enable_Call {
208+ _c.Call.Return()
209+ return _c
210+}
211+
212+func (_c *MockIME_Enable_Call) RunAndReturn(run func()) *MockIME_Enable_Call {
213+ _c.Run(run)
214+ return _c
215+}
216+
217+// Surrounding provides a mock function for the type MockIME
218+func (_mock *MockIME) Surrounding(text string, cursor int, anchor int) {
219+ _mock.Called(text, cursor, anchor)
220+ return
221+}
222+
223+// MockIME_Surrounding_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Surrounding'
224+type MockIME_Surrounding_Call struct {
225+ *mock.Call
226+}
227+
228+// Surrounding is a helper method to define mock.On call
229+// - text string
230+// - cursor int
231+// - anchor int
232+func (_e *MockIME_Expecter) Surrounding(text any, cursor any, anchor any) *MockIME_Surrounding_Call {
233+ return &MockIME_Surrounding_Call{Call: _e.mock.On("Surrounding", text, cursor, anchor)}
234+}
235+
236+func (_c *MockIME_Surrounding_Call) Run(run func(text string, cursor int, anchor int)) *MockIME_Surrounding_Call {
237+ _c.Call.Run(func(args mock.Arguments) {
238+ var arg0 string
239+ if args[0] != nil {
240+ arg0 = args[0].(string)
241+ }
242+ var arg1 int
243+ if args[1] != nil {
244+ arg1 = args[1].(int)
245+ }
246+ var arg2 int
247+ if args[2] != nil {
248+ arg2 = args[2].(int)
249+ }
250+ run(
251+ arg0,
252+ arg1,
253+ arg2,
254+ )
255+ })
256+ return _c
257+}
258+
259+func (_c *MockIME_Surrounding_Call) Return() *MockIME_Surrounding_Call {
260+ _c.Call.Return()
261+ return _c
262+}
263+
264+func (_c *MockIME_Surrounding_Call) RunAndReturn(run func(text string, cursor int, anchor int)) *MockIME_Surrounding_Call {
265+ _c.Run(run)
266+ return _c
267+}
@@ -0,0 +1,267 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package ui
6+
7+import (
8+ mock "github.com/stretchr/testify/mock"
9+)
10+
11+// NewMockIME creates a new instance of MockIME. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
12+// The first argument is typically a *testing.T value.
13+func NewMockIME(t interface {
14+ mock.TestingT
15+ Cleanup(func())
16+}) *MockIME {
17+ if helper, ok := t.(interface{ Helper() }); ok {
18+ helper.Helper()
19+ }
20+
21+ mock := &MockIME{}
22+ mock.Mock.Test(t)
23+
24+ t.Cleanup(func() {
25+ if helper, ok := t.(interface{ Helper() }); ok {
26+ helper.Helper()
27+ }
28+ mock.AssertExpectations(t)
29+ })
30+
31+ return mock
32+}
33+
34+// MockIME is an autogenerated mock type for the IME type
35+type MockIME struct {
36+ mock.Mock
37+}
38+
39+type MockIME_Expecter struct {
40+ mock *mock.Mock
41+}
42+
43+func (_m *MockIME) EXPECT() *MockIME_Expecter {
44+ return &MockIME_Expecter{mock: &_m.Mock}
45+}
46+
47+// ContentType provides a mock function for the type MockIME
48+func (_mock *MockIME) ContentType(password bool, multiline bool) {
49+ _mock.Called(password, multiline)
50+ return
51+}
52+
53+// MockIME_ContentType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ContentType'
54+type MockIME_ContentType_Call struct {
55+ *mock.Call
56+}
57+
58+// ContentType is a helper method to define mock.On call
59+// - password bool
60+// - multiline bool
61+func (_e *MockIME_Expecter) ContentType(password any, multiline any) *MockIME_ContentType_Call {
62+ return &MockIME_ContentType_Call{Call: _e.mock.On("ContentType", password, multiline)}
63+}
64+
65+func (_c *MockIME_ContentType_Call) Run(run func(password bool, multiline bool)) *MockIME_ContentType_Call {
66+ _c.Call.Run(func(args mock.Arguments) {
67+ var arg0 bool
68+ if args[0] != nil {
69+ arg0 = args[0].(bool)
70+ }
71+ var arg1 bool
72+ if args[1] != nil {
73+ arg1 = args[1].(bool)
74+ }
75+ run(
76+ arg0,
77+ arg1,
78+ )
79+ })
80+ return _c
81+}
82+
83+func (_c *MockIME_ContentType_Call) Return() *MockIME_ContentType_Call {
84+ _c.Call.Return()
85+ return _c
86+}
87+
88+func (_c *MockIME_ContentType_Call) RunAndReturn(run func(password bool, multiline bool)) *MockIME_ContentType_Call {
89+ _c.Run(run)
90+ return _c
91+}
92+
93+// CursorRect provides a mock function for the type MockIME
94+func (_mock *MockIME) CursorRect(x float64, y float64, w float64, h float64) {
95+ _mock.Called(x, y, w, h)
96+ return
97+}
98+
99+// MockIME_CursorRect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CursorRect'
100+type MockIME_CursorRect_Call struct {
101+ *mock.Call
102+}
103+
104+// CursorRect is a helper method to define mock.On call
105+// - x float64
106+// - y float64
107+// - w float64
108+// - h float64
109+func (_e *MockIME_Expecter) CursorRect(x any, y any, w any, h any) *MockIME_CursorRect_Call {
110+ return &MockIME_CursorRect_Call{Call: _e.mock.On("CursorRect", x, y, w, h)}
111+}
112+
113+func (_c *MockIME_CursorRect_Call) Run(run func(x float64, y float64, w float64, h float64)) *MockIME_CursorRect_Call {
114+ _c.Call.Run(func(args mock.Arguments) {
115+ var arg0 float64
116+ if args[0] != nil {
117+ arg0 = args[0].(float64)
118+ }
119+ var arg1 float64
120+ if args[1] != nil {
121+ arg1 = args[1].(float64)
122+ }
123+ var arg2 float64
124+ if args[2] != nil {
125+ arg2 = args[2].(float64)
126+ }
127+ var arg3 float64
128+ if args[3] != nil {
129+ arg3 = args[3].(float64)
130+ }
131+ run(
132+ arg0,
133+ arg1,
134+ arg2,
135+ arg3,
136+ )
137+ })
138+ return _c
139+}
140+
141+func (_c *MockIME_CursorRect_Call) Return() *MockIME_CursorRect_Call {
142+ _c.Call.Return()
143+ return _c
144+}
145+
146+func (_c *MockIME_CursorRect_Call) RunAndReturn(run func(x float64, y float64, w float64, h float64)) *MockIME_CursorRect_Call {
147+ _c.Run(run)
148+ return _c
149+}
150+
151+// Disable provides a mock function for the type MockIME
152+func (_mock *MockIME) Disable() {
153+ _mock.Called()
154+ return
155+}
156+
157+// MockIME_Disable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Disable'
158+type MockIME_Disable_Call struct {
159+ *mock.Call
160+}
161+
162+// Disable is a helper method to define mock.On call
163+func (_e *MockIME_Expecter) Disable() *MockIME_Disable_Call {
164+ return &MockIME_Disable_Call{Call: _e.mock.On("Disable")}
165+}
166+
167+func (_c *MockIME_Disable_Call) Run(run func()) *MockIME_Disable_Call {
168+ _c.Call.Run(func(args mock.Arguments) {
169+ run()
170+ })
171+ return _c
172+}
173+
174+func (_c *MockIME_Disable_Call) Return() *MockIME_Disable_Call {
175+ _c.Call.Return()
176+ return _c
177+}
178+
179+func (_c *MockIME_Disable_Call) RunAndReturn(run func()) *MockIME_Disable_Call {
180+ _c.Run(run)
181+ return _c
182+}
183+
184+// Enable provides a mock function for the type MockIME
185+func (_mock *MockIME) Enable() {
186+ _mock.Called()
187+ return
188+}
189+
190+// MockIME_Enable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Enable'
191+type MockIME_Enable_Call struct {
192+ *mock.Call
193+}
194+
195+// Enable is a helper method to define mock.On call
196+func (_e *MockIME_Expecter) Enable() *MockIME_Enable_Call {
197+ return &MockIME_Enable_Call{Call: _e.mock.On("Enable")}
198+}
199+
200+func (_c *MockIME_Enable_Call) Run(run func()) *MockIME_Enable_Call {
201+ _c.Call.Run(func(args mock.Arguments) {
202+ run()
203+ })
204+ return _c
205+}
206+
207+func (_c *MockIME_Enable_Call) Return() *MockIME_Enable_Call {
208+ _c.Call.Return()
209+ return _c
210+}
211+
212+func (_c *MockIME_Enable_Call) RunAndReturn(run func()) *MockIME_Enable_Call {
213+ _c.Run(run)
214+ return _c
215+}
216+
217+// Surrounding provides a mock function for the type MockIME
218+func (_mock *MockIME) Surrounding(text string, cursor int, anchor int) {
219+ _mock.Called(text, cursor, anchor)
220+ return
221+}
222+
223+// MockIME_Surrounding_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Surrounding'
224+type MockIME_Surrounding_Call struct {
225+ *mock.Call
226+}
227+
228+// Surrounding is a helper method to define mock.On call
229+// - text string
230+// - cursor int
231+// - anchor int
232+func (_e *MockIME_Expecter) Surrounding(text any, cursor any, anchor any) *MockIME_Surrounding_Call {
233+ return &MockIME_Surrounding_Call{Call: _e.mock.On("Surrounding", text, cursor, anchor)}
234+}
235+
236+func (_c *MockIME_Surrounding_Call) Run(run func(text string, cursor int, anchor int)) *MockIME_Surrounding_Call {
237+ _c.Call.Run(func(args mock.Arguments) {
238+ var arg0 string
239+ if args[0] != nil {
240+ arg0 = args[0].(string)
241+ }
242+ var arg1 int
243+ if args[1] != nil {
244+ arg1 = args[1].(int)
245+ }
246+ var arg2 int
247+ if args[2] != nil {
248+ arg2 = args[2].(int)
249+ }
250+ run(
251+ arg0,
252+ arg1,
253+ arg2,
254+ )
255+ })
256+ return _c
257+}
258+
259+func (_c *MockIME_Surrounding_Call) Return() *MockIME_Surrounding_Call {
260+ _c.Call.Return()
261+ return _c
262+}
263+
264+func (_c *MockIME_Surrounding_Call) RunAndReturn(run func(text string, cursor int, anchor int)) *MockIME_Surrounding_Call {
265+ _c.Run(run)
266+ return _c
267+}
M internal/ui/controls_extra_test.go +1 -5
@@ -6,10 +6,6 @@ import (
66 "testing"
77 )
88
9-type clip struct{ data []byte }
10-
11-func (c *clip) ReadText(limit int) ([]byte, error) { return c.data, nil }
12-func (c *clip) WriteText(v []byte) error { c.data = append([]byte(nil), v...); return nil }
139 func TestRadioSliderAndEditingIdentity(t *testing.T) {
1410 r := newRuntime()
1511 r.styles = css.Compile(css.UA(), css.Parse(`input,radio,slider {height:20px}`))
@@ -97,7 +93,7 @@ func TestEditFocusIMEAndClipboard(t *testing.T) {
9793 var value string
9894 view := func(f *Frame) { f.Root().Input("secret", &value, Key("secret"), Password(true)) }
9995 r.Build(view)
100- r.clipboard = &clip{data: []byte("paste")}
96+ r.clipboard = NewMockClipboard(t) // no clipboard access expected
10197 r.route(key("Tab"))
10298 r.Build(view)
10399 r.route(platformInput{Kind: "key", Key: keyEvent{Name: "q", Text: "x", Pressed: true}})
@@ -6,10 +6,6 @@ import (
6 "testing"6 "testing"
7 )7 )
8 8
9-type clip struct{ data []byte }
10-
11-func (c *clip) ReadText(limit int) ([]byte, error) { return c.data, nil }
12-func (c *clip) WriteText(v []byte) error { c.data = append([]byte(nil), v...); return nil }
13 func TestRadioSliderAndEditingIdentity(t *testing.T) {9 func TestRadioSliderAndEditingIdentity(t *testing.T) {
14 r := newRuntime()10 r := newRuntime()
15 r.styles = css.Compile(css.UA(), css.Parse(`input,radio,slider {height:20px}`))11 r.styles = css.Compile(css.UA(), css.Parse(`input,radio,slider {height:20px}`))
@@ -97,7 +93,7 @@ func TestEditFocusIMEAndClipboard(t *testing.T) {
97 var value string93 var value string
98 view := func(f *Frame) { f.Root().Input("secret", &value, Key("secret"), Password(true)) }94 view := func(f *Frame) { f.Root().Input("secret", &value, Key("secret"), Password(true)) }
99 r.Build(view)95 r.Build(view)
100- r.clipboard = &clip{data: []byte("paste")}96+ r.clipboard = NewMockClipboard(t) // no clipboard access expected
101 r.route(key("Tab"))97 r.route(key("Tab"))
102 r.Build(view)98 r.Build(view)
103 r.route(platformInput{Kind: "key", Key: keyEvent{Name: "q", Text: "x", Pressed: true}})99 r.route(platformInput{Kind: "key", Key: keyEvent{Name: "q", Text: "x", Pressed: true}})
M internal/ui/ime_focus_test.go +43 -41
@@ -3,60 +3,62 @@ package ui
33 import (
44 "reflect"
55 "testing"
6-)
7-
8-type orderedIME struct {
9- calls []string
10- active bool
11-}
12-
13-func (i *orderedIME) Enable() {
14- if i.active {
15- panic("IME enable before disable")
16- }
17- i.active = true
18- i.calls = append(i.calls, "enable")
19-}
20-func (i *orderedIME) Disable() {
21- if !i.active {
22- panic("IME double disable")
23- }
24- i.active = false
25- i.calls = append(i.calls, "disable")
26-}
27-func (i *orderedIME) Surrounding(string, int, int) {}
28-func (i *orderedIME) CursorRect(float64, float64, float64, float64) {}
29-func (i *orderedIME) ContentType(bool, bool) {}
306
31-type recordingIME struct {
32- surrounding []string
33- password bool
34-}
7+ "github.com/stretchr/testify/mock"
8+)
359
36-func (i *recordingIME) Enable() {}
37-func (i *recordingIME) Disable() {}
38-func (i *recordingIME) Surrounding(text string, _, _ int) {
39- i.surrounding = append(i.surrounding, text)
10+// newOrderedIME returns an IME mock that records Enable and Disable in order
11+// and fails on an enable before a disable or a double disable.
12+func newOrderedIME(t *testing.T) (*MockIME, *[]string) {
13+ t.Helper()
14+ ime := NewMockIME(t)
15+ calls := new([]string)
16+ active := false
17+ ime.EXPECT().Enable().Run(func() {
18+ if active {
19+ t.Error("IME enable before disable")
20+ }
21+ active = true
22+ *calls = append(*calls, "enable")
23+ }).Maybe()
24+ ime.EXPECT().Disable().Run(func() {
25+ if !active {
26+ t.Error("IME double disable")
27+ }
28+ active = false
29+ *calls = append(*calls, "disable")
30+ }).Maybe()
31+ ime.EXPECT().Surrounding(mock.Anything, mock.Anything, mock.Anything).Maybe()
32+ ime.EXPECT().CursorRect(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe()
33+ ime.EXPECT().ContentType(mock.Anything, mock.Anything).Maybe()
34+ return ime, calls
4035 }
41-func (i *recordingIME) CursorRect(float64, float64, float64, float64) {}
42-func (i *recordingIME) ContentType(password, _ bool) { i.password = password }
4336
4437 func TestPasswordEditorNeverSendsSecretToIME(t *testing.T) {
4538 r := newRuntime()
46- ime := &recordingIME{}
39+ ime := NewMockIME(t)
40+ var surrounding []string
41+ password := false
42+ ime.EXPECT().Enable().Maybe()
43+ ime.EXPECT().Disable().Maybe()
44+ ime.EXPECT().CursorRect(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe()
45+ ime.EXPECT().Surrounding(mock.Anything, mock.Anything, mock.Anything).Run(func(text string, _, _ int) {
46+ surrounding = append(surrounding, text)
47+ }).Maybe()
48+ ime.EXPECT().ContentType(mock.Anything, mock.Anything).Run(func(p, _ bool) { password = p }).Maybe()
4749 r.ime = ime
4850 value := "hunter2"
4951 view := func(f *Frame) { f.Root().Input("secret", &value, Key("secret"), Password(true)) }
5052 r.Build(view)
5153 r.route(key("Tab"))
5254 r.Build(view)
53- if !ime.password {
55+ if !password {
5456 t.Fatal("password content type not sent")
5557 }
56- if len(ime.surrounding) == 0 {
58+ if len(surrounding) == 0 {
5759 t.Fatal("no surrounding text update")
5860 }
59- for _, s := range ime.surrounding {
61+ for _, s := range surrounding {
6062 if s != "" {
6163 t.Fatalf("surrounding text leaked %q", s)
6264 }
@@ -65,7 +67,7 @@ func TestPasswordEditorNeverSendsSecretToIME(t *testing.T) {
6567
6668 func TestIMEFocusTransferDisablesBeforeEnable(t *testing.T) {
6769 r := newRuntime()
68- ime := &orderedIME{}
70+ ime, calls := newOrderedIME(t)
6971 r.ime = ime
7072 a, b := "one", "two"
7173 view := func(f *Frame) { root := f.Root(); root.Input("a", &a, Key("a")); root.Textarea("b", &b, Key("b")) }
@@ -76,8 +78,8 @@ func TestIMEFocusTransferDisablesBeforeEnable(t *testing.T) {
7678 r.Build(view)
7779 r.route(key("Tab"))
7880 r.Build(view)
79- if want := []string{"enable", "disable", "enable", "disable", "enable"}; !reflect.DeepEqual(ime.calls, want) {
80- t.Fatalf("focus transition calls %v want %v", ime.calls, want)
81+ if want := []string{"enable", "disable", "enable", "disable", "enable"}; !reflect.DeepEqual(*calls, want) {
82+ t.Fatalf("focus transition calls %v want %v", *calls, want)
8183 }
8284 if !r.edits[idKey(r.Target(0))].IMEActive() || r.edits[idKey(r.Target(1))].IMEActive() {
8385 t.Fatal("wrong active editor")
@@ -3,60 +3,62 @@ package ui
3 import (3 import (
4 "reflect"4 "reflect"
5 "testing"5 "testing"
6-)
7-
8-type orderedIME struct {
9- calls []string
10- active bool
11-}
12-
13-func (i *orderedIME) Enable() {
14- if i.active {
15- panic("IME enable before disable")
16- }
17- i.active = true
18- i.calls = append(i.calls, "enable")
19-}
20-func (i *orderedIME) Disable() {
21- if !i.active {
22- panic("IME double disable")
23- }
24- i.active = false
25- i.calls = append(i.calls, "disable")
26-}
27-func (i *orderedIME) Surrounding(string, int, int) {}
28-func (i *orderedIME) CursorRect(float64, float64, float64, float64) {}
29-func (i *orderedIME) ContentType(bool, bool) {}
30 6
31-type recordingIME struct {7+ "github.com/stretchr/testify/mock"
32- surrounding []string8+)
33- password bool
34-}
35 9
36-func (i *recordingIME) Enable() {}10+// newOrderedIME returns an IME mock that records Enable and Disable in order
37-func (i *recordingIME) Disable() {}11+// and fails on an enable before a disable or a double disable.
38-func (i *recordingIME) Surrounding(text string, _, _ int) {12+func newOrderedIME(t *testing.T) (*MockIME, *[]string) {
39- i.surrounding = append(i.surrounding, text)13+ t.Helper()
14+ ime := NewMockIME(t)
15+ calls := new([]string)
16+ active := false
17+ ime.EXPECT().Enable().Run(func() {
18+ if active {
19+ t.Error("IME enable before disable")
20+ }
21+ active = true
22+ *calls = append(*calls, "enable")
23+ }).Maybe()
24+ ime.EXPECT().Disable().Run(func() {
25+ if !active {
26+ t.Error("IME double disable")
27+ }
28+ active = false
29+ *calls = append(*calls, "disable")
30+ }).Maybe()
31+ ime.EXPECT().Surrounding(mock.Anything, mock.Anything, mock.Anything).Maybe()
32+ ime.EXPECT().CursorRect(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe()
33+ ime.EXPECT().ContentType(mock.Anything, mock.Anything).Maybe()
34+ return ime, calls
40 }35 }
41-func (i *recordingIME) CursorRect(float64, float64, float64, float64) {}
42-func (i *recordingIME) ContentType(password, _ bool) { i.password = password }
43 36
44 func TestPasswordEditorNeverSendsSecretToIME(t *testing.T) {37 func TestPasswordEditorNeverSendsSecretToIME(t *testing.T) {
45 r := newRuntime()38 r := newRuntime()
46- ime := &recordingIME{}39+ ime := NewMockIME(t)
40+ var surrounding []string
41+ password := false
42+ ime.EXPECT().Enable().Maybe()
43+ ime.EXPECT().Disable().Maybe()
44+ ime.EXPECT().CursorRect(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe()
45+ ime.EXPECT().Surrounding(mock.Anything, mock.Anything, mock.Anything).Run(func(text string, _, _ int) {
46+ surrounding = append(surrounding, text)
47+ }).Maybe()
48+ ime.EXPECT().ContentType(mock.Anything, mock.Anything).Run(func(p, _ bool) { password = p }).Maybe()
47 r.ime = ime49 r.ime = ime
48 value := "hunter2"50 value := "hunter2"
49 view := func(f *Frame) { f.Root().Input("secret", &value, Key("secret"), Password(true)) }51 view := func(f *Frame) { f.Root().Input("secret", &value, Key("secret"), Password(true)) }
50 r.Build(view)52 r.Build(view)
51 r.route(key("Tab"))53 r.route(key("Tab"))
52 r.Build(view)54 r.Build(view)
53- if !ime.password {55+ if !password {
54 t.Fatal("password content type not sent")56 t.Fatal("password content type not sent")
55 }57 }
56- if len(ime.surrounding) == 0 {58+ if len(surrounding) == 0 {
57 t.Fatal("no surrounding text update")59 t.Fatal("no surrounding text update")
58 }60 }
59- for _, s := range ime.surrounding {61+ for _, s := range surrounding {
60 if s != "" {62 if s != "" {
61 t.Fatalf("surrounding text leaked %q", s)63 t.Fatalf("surrounding text leaked %q", s)
62 }64 }
@@ -65,7 +67,7 @@ func TestPasswordEditorNeverSendsSecretToIME(t *testing.T) {
65 67
66 func TestIMEFocusTransferDisablesBeforeEnable(t *testing.T) {68 func TestIMEFocusTransferDisablesBeforeEnable(t *testing.T) {
67 r := newRuntime()69 r := newRuntime()
68- ime := &orderedIME{}70+ ime, calls := newOrderedIME(t)
69 r.ime = ime71 r.ime = ime
70 a, b := "one", "two"72 a, b := "one", "two"
71 view := func(f *Frame) { root := f.Root(); root.Input("a", &a, Key("a")); root.Textarea("b", &b, Key("b")) }73 view := func(f *Frame) { root := f.Root(); root.Input("a", &a, Key("a")); root.Textarea("b", &b, Key("b")) }
@@ -76,8 +78,8 @@ func TestIMEFocusTransferDisablesBeforeEnable(t *testing.T) {
76 r.Build(view)78 r.Build(view)
77 r.route(key("Tab"))79 r.route(key("Tab"))
78 r.Build(view)80 r.Build(view)
79- if want := []string{"enable", "disable", "enable", "disable", "enable"}; !reflect.DeepEqual(ime.calls, want) {81+ if want := []string{"enable", "disable", "enable", "disable", "enable"}; !reflect.DeepEqual(*calls, want) {
80- t.Fatalf("focus transition calls %v want %v", ime.calls, want)82+ t.Fatalf("focus transition calls %v want %v", *calls, want)
81 }83 }
82 if !r.edits[idKey(r.Target(0))].IMEActive() || r.edits[idKey(r.Target(1))].IMEActive() {84 if !r.edits[idKey(r.Target(0))].IMEActive() || r.edits[idKey(r.Target(1))].IMEActive() {
83 t.Fatal("wrong active editor")85 t.Fatal("wrong active editor")
M internal/ui/ime_return_test.go +2 -2
@@ -14,7 +14,7 @@ func typed(name, text string) platformInput {
1414 // keys that still arrive on wl_keyboard were not consumed and must be typed.
1515 func TestKeyboardTextInsertedWhileIMEEnabled(t *testing.T) {
1616 r := newRuntime()
17- ime := &orderedIME{}
17+ ime, calls := newOrderedIME(t)
1818 r.ime = ime
1919 value := "ab"
2020 view := func(f *Frame) { f.Root().Textarea("message", &value) }
@@ -28,7 +28,7 @@ func TestKeyboardTextInsertedWhileIMEEnabled(t *testing.T) {
2828 r.route(k)
2929 r.Build(view)
3030 }
31- if value != "abca\n\n" || len(ime.calls) == 0 {
31+ if value != "abca\n\n" || len(*calls) == 0 {
3232 t.Fatalf("keyboard text lost while IME enabled: %q", value)
3333 }
3434
@@ -14,7 +14,7 @@ func typed(name, text string) platformInput {
14 // keys that still arrive on wl_keyboard were not consumed and must be typed.14 // keys that still arrive on wl_keyboard were not consumed and must be typed.
15 func TestKeyboardTextInsertedWhileIMEEnabled(t *testing.T) {15 func TestKeyboardTextInsertedWhileIMEEnabled(t *testing.T) {
16 r := newRuntime()16 r := newRuntime()
17- ime := &orderedIME{}17+ ime, calls := newOrderedIME(t)
18 r.ime = ime18 r.ime = ime
19 value := "ab"19 value := "ab"
20 view := func(f *Frame) { f.Root().Textarea("message", &value) }20 view := func(f *Frame) { f.Root().Textarea("message", &value) }
@@ -28,7 +28,7 @@ func TestKeyboardTextInsertedWhileIMEEnabled(t *testing.T) {
28 r.route(k)28 r.route(k)
29 r.Build(view)29 r.Build(view)
30 }30 }
31- if value != "abca\n\n" || len(ime.calls) == 0 {31+ if value != "abca\n\n" || len(*calls) == 0 {
32 t.Fatalf("keyboard text lost while IME enabled: %q", value)32 t.Fatalf("keyboard text lost while IME enabled: %q", value)
33 }33 }
34 34
M internal/ui/paste_test.go +19 -24
@@ -3,29 +3,24 @@ package ui
33 import (
44 "context"
55 "errors"
6- "github.com/bnema/nefergui/internal/edit"
76 "testing"
8-)
97
10-type asyncClip struct {
11- contexts []context.Context
12- callbacks []func([]byte, error)
13-}
8+ "github.com/bnema/nefergui/internal/edit"
9+ "github.com/stretchr/testify/mock"
10+)
1411
15-func (*asyncClip) ReadText(int) ([]byte, error) { panic("sync clipboard read on loop") }
16-func (*asyncClip) WriteText([]byte) error { return nil }
17-func (c *asyncClip) ReadTextAsync(ctx context.Context, limit int, deliver func([]byte, error)) error {
18- if limit != edit.MaxClipboardBytes {
19- panic("wrong limit")
20- }
21- c.contexts = append(c.contexts, ctx)
22- c.callbacks = append(c.callbacks, deliver)
23- return nil
24-}
2512 func TestAsyncPasteFrameAndCancellation(t *testing.T) {
2613 r := newRuntime()
27- c := &asyncClip{}
28- r.clipboard = c
14+ c := NewMockAsyncClipboard(t) // synchronous ReadText is not expected
15+ var contexts []context.Context
16+ var callbacks []func([]byte, error)
17+ c.EXPECT().ReadTextAsync(mock.Anything, edit.MaxClipboardBytes, mock.Anything).
18+ RunAndReturn(func(ctx context.Context, _ int, deliver func([]byte, error)) error {
19+ contexts = append(contexts, ctx)
20+ callbacks = append(callbacks, deliver)
21+ return nil
22+ })
23+ r.clipboard = asyncClipboard{c}
2924 name := ""
3025 var changed bool
3126 var diagnostics []string
@@ -45,28 +40,28 @@ func TestAsyncPasteFrameAndCancellation(t *testing.T) {
4540 }
4641 paste()
4742 paste()
48- if c.contexts[0].Err() != context.Canceled {
43+ if contexts[0].Err() != context.Canceled {
4944 t.Fatal("old read not cancelled")
5045 }
51- c.callbacks[0]([]byte("old"), nil)
52- c.callbacks[1]([]byte("Ada"), nil)
46+ callbacks[0]([]byte("old"), nil)
47+ callbacks[1]([]byte("Ada"), nil)
5348 if !r.Build(view) || !changed || name != "Ada" {
5449 t.Fatalf("paste: changed=%v value=%q", changed, name)
5550 }
5651 paste()
57- c.callbacks[2]([]byte{0xff}, nil)
52+ callbacks[2]([]byte{0xff}, nil)
5853 r.Build(view)
5954 if name != "Ada" || changed {
6055 t.Fatal("invalid UTF-8 accepted")
6156 }
6257 paste()
63- c.callbacks[3](make([]byte, edit.MaxClipboardBytes+1), nil)
58+ callbacks[3](make([]byte, edit.MaxClipboardBytes+1), nil)
6459 r.Build(view)
6560 if name != "Ada" || changed {
6661 t.Fatal("oversize accepted")
6762 }
6863 paste()
69- c.callbacks[4](nil, errors.New("transfer failed"))
64+ callbacks[4](nil, errors.New("transfer failed"))
7065 r.Build(view)
7166 if debugDiagnostics && len(diagnostics) != 1 {
7267 t.Fatalf("missing error diagnostic: %v", diagnostics)
@@ -3,29 +3,24 @@ package ui
3 import (3 import (
4 "context"4 "context"
5 "errors"5 "errors"
6- "github.com/bnema/nefergui/internal/edit"
7 "testing"6 "testing"
8-)
9 7
10-type asyncClip struct {8+ "github.com/bnema/nefergui/internal/edit"
11- contexts []context.Context9+ "github.com/stretchr/testify/mock"
12- callbacks []func([]byte, error)10+)
13-}
14 11
15-func (*asyncClip) ReadText(int) ([]byte, error) { panic("sync clipboard read on loop") }
16-func (*asyncClip) WriteText([]byte) error { return nil }
17-func (c *asyncClip) ReadTextAsync(ctx context.Context, limit int, deliver func([]byte, error)) error {
18- if limit != edit.MaxClipboardBytes {
19- panic("wrong limit")
20- }
21- c.contexts = append(c.contexts, ctx)
22- c.callbacks = append(c.callbacks, deliver)
23- return nil
24-}
25 func TestAsyncPasteFrameAndCancellation(t *testing.T) {12 func TestAsyncPasteFrameAndCancellation(t *testing.T) {
26 r := newRuntime()13 r := newRuntime()
27- c := &asyncClip{}14+ c := NewMockAsyncClipboard(t) // synchronous ReadText is not expected
28- r.clipboard = c15+ var contexts []context.Context
16+ var callbacks []func([]byte, error)
17+ c.EXPECT().ReadTextAsync(mock.Anything, edit.MaxClipboardBytes, mock.Anything).
18+ RunAndReturn(func(ctx context.Context, _ int, deliver func([]byte, error)) error {
19+ contexts = append(contexts, ctx)
20+ callbacks = append(callbacks, deliver)
21+ return nil
22+ })
23+ r.clipboard = asyncClipboard{c}
29 name := ""24 name := ""
30 var changed bool25 var changed bool
31 var diagnostics []string26 var diagnostics []string
@@ -45,28 +40,28 @@ func TestAsyncPasteFrameAndCancellation(t *testing.T) {
45 }40 }
46 paste()41 paste()
47 paste()42 paste()
48- if c.contexts[0].Err() != context.Canceled {43+ if contexts[0].Err() != context.Canceled {
49 t.Fatal("old read not cancelled")44 t.Fatal("old read not cancelled")
50 }45 }
51- c.callbacks[0]([]byte("old"), nil)46+ callbacks[0]([]byte("old"), nil)
52- c.callbacks[1]([]byte("Ada"), nil)47+ callbacks[1]([]byte("Ada"), nil)
53 if !r.Build(view) || !changed || name != "Ada" {48 if !r.Build(view) || !changed || name != "Ada" {
54 t.Fatalf("paste: changed=%v value=%q", changed, name)49 t.Fatalf("paste: changed=%v value=%q", changed, name)
55 }50 }
56 paste()51 paste()
57- c.callbacks[2]([]byte{0xff}, nil)52+ callbacks[2]([]byte{0xff}, nil)
58 r.Build(view)53 r.Build(view)
59 if name != "Ada" || changed {54 if name != "Ada" || changed {
60 t.Fatal("invalid UTF-8 accepted")55 t.Fatal("invalid UTF-8 accepted")
61 }56 }
62 paste()57 paste()
63- c.callbacks[3](make([]byte, edit.MaxClipboardBytes+1), nil)58+ callbacks[3](make([]byte, edit.MaxClipboardBytes+1), nil)
64 r.Build(view)59 r.Build(view)
65 if name != "Ada" || changed {60 if name != "Ada" || changed {
66 t.Fatal("oversize accepted")61 t.Fatal("oversize accepted")
67 }62 }
68 paste()63 paste()
69- c.callbacks[4](nil, errors.New("transfer failed"))64+ callbacks[4](nil, errors.New("transfer failed"))
70 r.Build(view)65 r.Build(view)
71 if debugDiagnostics && len(diagnostics) != 1 {66 if debugDiagnostics && len(diagnostics) != 1 {
72 t.Fatalf("missing error diagnostic: %v", diagnostics)67 t.Fatalf("missing error diagnostic: %v", diagnostics)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 12

↑↓ moveenter openesc close