refactor(tests): replace simple fakes with generated mocks
- commit
- a605e6ba451c3c5fdc903d830cb0a17a24d9ac72
- author
- brice · · 2026-06-23 14:59 UTC
- committer
- brice · · 2026-06-23 20:01 UTC
- parent
- 94cc600
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +171 -257
M internal/application/usecase/execute_context_menu_action_test.go +88 -144
| @@ -6,79 +6,21 @@ import ( | |||
| 6 | 6 | "testing" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/dumber/internal/application/port" | |
| 9 | + | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |
| 9 | 10 | "github.com/bnema/dumber/internal/domain/entity" | |
| 11 | + | "github.com/stretchr/testify/mock" | |
| 10 | 12 | "github.com/stretchr/testify/require" | |
| 11 | 13 | ) | |
| 12 | 14 | ||
| 13 | - | type fakeClipboard struct { | |
| 14 | - | writeTextCalls int | |
| 15 | - | writeImageCalls int | |
| 16 | - | text string | |
| 17 | - | image entity.ImageData | |
| 18 | - | } | |
| 19 | - | ||
| 20 | - | func (f *fakeClipboard) WriteText(_ context.Context, text string) error { | |
| 21 | - | f.writeTextCalls++ | |
| 22 | - | f.text = text | |
| 23 | - | return nil | |
| 24 | - | } | |
| 25 | - | ||
| 26 | - | func (f *fakeClipboard) WriteImage(_ context.Context, image entity.ImageData) error { | |
| 27 | - | f.writeImageCalls++ | |
| 28 | - | f.image = image | |
| 29 | - | return nil | |
| 30 | - | } | |
| 31 | - | ||
| 32 | - | func (*fakeClipboard) ReadText(context.Context) (string, error) { return "", nil } | |
| 33 | - | func (*fakeClipboard) Clear(context.Context) error { return nil } | |
| 34 | - | func (*fakeClipboard) HasText(context.Context) (bool, error) { return false, nil } | |
| 35 | - | ||
| 36 | - | type fakeImageResolver struct { | |
| 37 | - | resolveCalls int | |
| 38 | - | uri string | |
| 39 | - | image entity.ImageData | |
| 40 | - | err error | |
| 41 | - | } | |
| 42 | - | ||
| 43 | - | func (f *fakeImageResolver) ResolveImageData(_ context.Context, uri string) (entity.ImageData, error) { | |
| 44 | - | f.resolveCalls++ | |
| 45 | - | f.uri = uri | |
| 46 | - | return f.image, f.err | |
| 47 | - | } | |
| 48 | - | ||
| 49 | - | type fakeResolvedImageSaver struct { | |
| 50 | - | saveCalls int | |
| 51 | - | image entity.ImageData | |
| 52 | - | menuContext port.MenuContext | |
| 53 | - | err error | |
| 54 | - | } | |
| 55 | - | ||
| 56 | - | func (f *fakeResolvedImageSaver) SaveResolvedImage(_ context.Context, image entity.ImageData, menuContext port.MenuContext) error { | |
| 57 | - | f.saveCalls++ | |
| 58 | - | f.image = image | |
| 59 | - | f.menuContext = menuContext | |
| 60 | - | return f.err | |
| 61 | - | } | |
| 62 | - | ||
| 63 | - | type fakeMenuActionDelegator struct { | |
| 64 | - | delegateCalls int | |
| 65 | - | action port.MenuAction | |
| 66 | - | menuContext port.MenuContext | |
| 67 | - | err error | |
| 68 | - | } | |
| 69 | - | ||
| 70 | - | func (f *fakeMenuActionDelegator) DelegateMenuAction(_ context.Context, action port.MenuAction, menuContext port.MenuContext) error { | |
| 71 | - | f.delegateCalls++ | |
| 72 | - | f.action = action | |
| 73 | - | f.menuContext = menuContext | |
| 74 | - | return f.err | |
| 75 | - | } | |
| 76 | - | ||
| 77 | 15 | func TestExecuteContextMenuActionUseCase_CopyImageFailsWithoutResolvedData(t *testing.T) { | |
| 78 | - | clipboard := &fakeClipboard{} | |
| 79 | - | resolver := &fakeImageResolver{} | |
| 80 | - | saver := &fakeResolvedImageSaver{} | |
| 81 | - | delegator := &fakeMenuActionDelegator{} | |
| 16 | + | clipboard := portmocks.NewMockClipboard(t) | |
| 17 | + | resolver := portmocks.NewMockImageDataResolver(t) | |
| 18 | + | saver := portmocks.NewMockResolvedImageSaver(t) | |
| 19 | + | delegator := portmocks.NewMockMenuActionDelegator(t) | |
| 20 | + | resolver.EXPECT(). | |
| 21 | + | ResolveImageData(mock.Anything, "https://example.com/image.png"). | |
| 22 | + | Return(entity.ImageData{}, nil). | |
| 23 | + | Once() | |
| 82 | 24 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | |
| 83 | 25 | ||
| 84 | 26 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | |
| @@ -89,16 +31,22 @@ func TestExecuteContextMenuActionUseCase_CopyImageFailsWithoutResolvedData(t *te | |||
| 89 | 31 | require.Error(t, err) | |
| 90 | 32 | require.Contains(t, err.Error(), "copy image:") | |
| 91 | 33 | require.Contains(t, err.Error(), "image data not available") | |
| 92 | - | require.Zero(t, clipboard.writeImageCalls) | |
| 93 | - | require.Zero(t, saver.saveCalls) | |
| 94 | - | require.Zero(t, delegator.delegateCalls) | |
| 95 | 34 | } | |
| 96 | 35 | ||
| 97 | 36 | func TestExecuteContextMenuActionUseCase_CopyImageWritesResolvedImage(t *testing.T) { | |
| 98 | - | clipboard := &fakeClipboard{} | |
| 99 | - | resolver := &fakeImageResolver{image: entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"}} | |
| 100 | - | saver := &fakeResolvedImageSaver{} | |
| 101 | - | delegator := &fakeMenuActionDelegator{} | |
| 37 | + | clipboard := portmocks.NewMockClipboard(t) | |
| 38 | + | resolver := portmocks.NewMockImageDataResolver(t) | |
| 39 | + | saver := portmocks.NewMockResolvedImageSaver(t) | |
| 40 | + | delegator := portmocks.NewMockMenuActionDelegator(t) | |
| 41 | + | image := entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"} | |
| 42 | + | resolver.EXPECT(). | |
| 43 | + | ResolveImageData(mock.Anything, "https://example.com/image.png"). | |
| 44 | + | Return(image, nil). | |
| 45 | + | Once() | |
| 46 | + | clipboard.EXPECT(). | |
| 47 | + | WriteImage(mock.Anything, image). | |
| 48 | + | Return(nil). | |
| 49 | + | Once() | |
| 102 | 50 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | |
| 103 | 51 | ||
| 104 | 52 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | |
| @@ -107,18 +55,12 @@ func TestExecuteContextMenuActionUseCase_CopyImageWritesResolvedImage(t *testing | |||
| 107 | 55 | }) | |
| 108 | 56 | ||
| 109 | 57 | require.NoError(t, err) | |
| 110 | - | require.Equal(t, 1, resolver.resolveCalls) | |
| 111 | - | require.Equal(t, "https://example.com/image.png", resolver.uri) | |
| 112 | - | require.Equal(t, 1, clipboard.writeImageCalls) | |
| 113 | - | require.Equal(t, entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"}, clipboard.image) | |
| 114 | - | require.Zero(t, saver.saveCalls) | |
| 115 | - | require.Zero(t, delegator.delegateCalls) | |
| 116 | 58 | } | |
| 117 | 59 | ||
| 118 | 60 | func TestExecuteContextMenuActionUseCase_CopyImageFailsFastWithoutClipboard(t *testing.T) { | |
| 119 | - | resolver := &fakeImageResolver{} | |
| 120 | - | saver := &fakeResolvedImageSaver{} | |
| 121 | - | delegator := &fakeMenuActionDelegator{} | |
| 61 | + | resolver := portmocks.NewMockImageDataResolver(t) | |
| 62 | + | saver := portmocks.NewMockResolvedImageSaver(t) | |
| 63 | + | delegator := portmocks.NewMockMenuActionDelegator(t) | |
| 122 | 64 | uc := NewExecuteContextMenuActionUseCase(nil, resolver, saver, delegator) | |
| 123 | 65 | ||
| 124 | 66 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | |
| @@ -128,38 +70,37 @@ func TestExecuteContextMenuActionUseCase_CopyImageFailsFastWithoutClipboard(t *t | |||
| 128 | 70 | ||
| 129 | 71 | require.Error(t, err) | |
| 130 | 72 | require.Contains(t, err.Error(), "clipboard not available") | |
| 131 | - | require.Zero(t, resolver.resolveCalls) | |
| 132 | - | require.Zero(t, saver.saveCalls) | |
| 133 | - | require.Zero(t, delegator.delegateCalls) | |
| 134 | 73 | } | |
| 135 | 74 | ||
| 136 | 75 | func TestExecuteContextMenuActionUseCase_SaveImageDelegatesResolvedImage(t *testing.T) { | |
| 137 | - | clipboard := &fakeClipboard{} | |
| 138 | - | resolver := &fakeImageResolver{image: entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"}} | |
| 139 | - | saver := &fakeResolvedImageSaver{} | |
| 140 | - | delegator := &fakeMenuActionDelegator{} | |
| 76 | + | clipboard := portmocks.NewMockClipboard(t) | |
| 77 | + | resolver := portmocks.NewMockImageDataResolver(t) | |
| 78 | + | saver := portmocks.NewMockResolvedImageSaver(t) | |
| 79 | + | delegator := portmocks.NewMockMenuActionDelegator(t) | |
| 80 | + | image := entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"} | |
| 81 | + | menuContext := port.MenuContext{ImageURI: "https://example.com/image.png"} | |
| 82 | + | resolver.EXPECT(). | |
| 83 | + | ResolveImageData(mock.Anything, menuContext.ImageURI). | |
| 84 | + | Return(image, nil). | |
| 85 | + | Once() | |
| 86 | + | saver.EXPECT(). | |
| 87 | + | SaveResolvedImage(mock.Anything, image, menuContext). | |
| 88 | + | Return(nil). | |
| 89 | + | Once() | |
| 141 | 90 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | |
| 142 | 91 | ||
| 143 | 92 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | |
| 144 | 93 | Action: port.MenuActionSaveImage, | |
| 145 | - | Context: port.MenuContext{ImageURI: "https://example.com/image.png"}, | |
| 94 | + | Context: menuContext, | |
| 146 | 95 | }) | |
| 147 | 96 | ||
| 148 | 97 | require.NoError(t, err) | |
| 149 | - | require.Equal(t, 1, resolver.resolveCalls) | |
| 150 | - | require.Equal(t, "https://example.com/image.png", resolver.uri) | |
| 151 | - | require.Equal(t, 1, saver.saveCalls) | |
| 152 | - | require.Equal(t, entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"}, saver.image) | |
| 153 | - | require.Equal(t, port.MenuContext{ImageURI: "https://example.com/image.png"}, saver.menuContext) | |
| 154 | - | require.Zero(t, clipboard.writeTextCalls) | |
| 155 | - | require.Zero(t, clipboard.writeImageCalls) | |
| 156 | - | require.Zero(t, delegator.delegateCalls) | |
| 157 | 98 | } | |
| 158 | 99 | ||
| 159 | 100 | func TestExecuteContextMenuActionUseCase_SaveImageFailsFastWithoutSaver(t *testing.T) { | |
| 160 | - | clipboard := &fakeClipboard{} | |
| 161 | - | resolver := &fakeImageResolver{} | |
| 162 | - | delegator := &fakeMenuActionDelegator{} | |
| 101 | + | clipboard := portmocks.NewMockClipboard(t) | |
| 102 | + | resolver := portmocks.NewMockImageDataResolver(t) | |
| 103 | + | delegator := portmocks.NewMockMenuActionDelegator(t) | |
| 163 | 104 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, nil, delegator) | |
| 164 | 105 | ||
| 165 | 106 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | |
| @@ -169,42 +110,41 @@ func TestExecuteContextMenuActionUseCase_SaveImageFailsFastWithoutSaver(t *testi | |||
| 169 | 110 | ||
| 170 | 111 | require.Error(t, err) | |
| 171 | 112 | require.Contains(t, err.Error(), "image saver not available") | |
| 172 | - | require.Zero(t, resolver.resolveCalls) | |
| 173 | - | require.Zero(t, delegator.delegateCalls) | |
| 174 | 113 | } | |
| 175 | 114 | ||
| 176 | 115 | func TestExecuteContextMenuActionUseCase_DelegatesInspect(t *testing.T) { | |
| 177 | - | clipboard := &fakeClipboard{} | |
| 178 | - | resolver := &fakeImageResolver{} | |
| 179 | - | saver := &fakeResolvedImageSaver{} | |
| 180 | - | delegator := &fakeMenuActionDelegator{} | |
| 181 | - | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | |
| 182 | - | ||
| 183 | 116 | menuContext := port.MenuContext{ | |
| 184 | 117 | PageURI: "https://example.com", | |
| 185 | 118 | X: 17, | |
| 186 | 119 | Y: 42, | |
| 187 | 120 | } | |
| 121 | + | clipboard := portmocks.NewMockClipboard(t) | |
| 122 | + | resolver := portmocks.NewMockImageDataResolver(t) | |
| 123 | + | saver := portmocks.NewMockResolvedImageSaver(t) | |
| 124 | + | delegator := portmocks.NewMockMenuActionDelegator(t) | |
| 125 | + | delegator.EXPECT(). | |
| 126 | + | DelegateMenuAction(mock.Anything, port.MenuActionInspectElement, menuContext). | |
| 127 | + | Return(nil). | |
| 128 | + | Once() | |
| 129 | + | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | |
| 130 | + | ||
| 188 | 131 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | |
| 189 | 132 | Action: port.MenuActionInspectElement, | |
| 190 | 133 | Context: menuContext, | |
| 191 | 134 | }) | |
| 192 | 135 | ||
| 193 | 136 | require.NoError(t, err) | |
| 194 | - | require.Equal(t, 1, delegator.delegateCalls) | |
| 195 | - | require.Equal(t, port.MenuActionInspectElement, delegator.action) | |
| 196 | - | require.Equal(t, menuContext, delegator.menuContext) | |
| 197 | - | require.Zero(t, clipboard.writeTextCalls) | |
| 198 | - | require.Zero(t, clipboard.writeImageCalls) | |
| 199 | - | require.Zero(t, resolver.resolveCalls) | |
| 200 | - | require.Zero(t, saver.saveCalls) | |
| 201 | 137 | } | |
| 202 | 138 | ||
| 203 | 139 | func TestExecuteContextMenuActionUseCase_WrapsDelegateErrors(t *testing.T) { | |
| 204 | - | clipboard := &fakeClipboard{} | |
| 205 | - | resolver := &fakeImageResolver{} | |
| 206 | - | saver := &fakeResolvedImageSaver{} | |
| 207 | - | delegator := &fakeMenuActionDelegator{err: errors.New("boom")} | |
| 140 | + | clipboard := portmocks.NewMockClipboard(t) | |
| 141 | + | resolver := portmocks.NewMockImageDataResolver(t) | |
| 142 | + | saver := portmocks.NewMockResolvedImageSaver(t) | |
| 143 | + | delegator := portmocks.NewMockMenuActionDelegator(t) | |
| 144 | + | delegator.EXPECT(). | |
| 145 | + | DelegateMenuAction(mock.Anything, port.MenuActionCopySelection, port.MenuContext{HasSelection: true}). | |
| 146 | + | Return(errors.New("boom")). | |
| 147 | + | Once() | |
| 208 | 148 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | |
| 209 | 149 | ||
| 210 | 150 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | |
| @@ -218,10 +158,14 @@ func TestExecuteContextMenuActionUseCase_WrapsDelegateErrors(t *testing.T) { | |||
| 218 | 158 | } | |
| 219 | 159 | ||
| 220 | 160 | func TestExecuteContextMenuActionUseCase_CopySelectionWritesSelectedText(t *testing.T) { | |
| 221 | - | clipboard := &fakeClipboard{} | |
| 222 | - | resolver := &fakeImageResolver{} | |
| 223 | - | saver := &fakeResolvedImageSaver{} | |
| 224 | - | delegator := &fakeMenuActionDelegator{} | |
| 161 | + | clipboard := portmocks.NewMockClipboard(t) | |
| 162 | + | resolver := portmocks.NewMockImageDataResolver(t) | |
| 163 | + | saver := portmocks.NewMockResolvedImageSaver(t) | |
| 164 | + | delegator := portmocks.NewMockMenuActionDelegator(t) | |
| 165 | + | clipboard.EXPECT(). | |
| 166 | + | WriteText(mock.Anything, "selected text"). | |
| 167 | + | Return(nil). | |
| 168 | + | Once() | |
| 225 | 169 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | |
| 226 | 170 | ||
| 227 | 171 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | |
| @@ -233,15 +177,19 @@ func TestExecuteContextMenuActionUseCase_CopySelectionWritesSelectedText(t *test | |||
| 233 | 177 | }) | |
| 234 | 178 | ||
| 235 | 179 | require.NoError(t, err) | |
| 236 | - | require.Equal(t, 1, clipboard.writeTextCalls) | |
| 237 | - | require.Equal(t, "selected text", clipboard.text) | |
| 238 | - | require.Zero(t, delegator.delegateCalls) | |
| 239 | 180 | } | |
| 240 | 181 | ||
| 241 | 182 | func TestExecuteContextMenuActionUseCase_CopySelectionFallsBackToDelegatorWhenClipboardMissing(t *testing.T) { | |
| 242 | - | resolver := &fakeImageResolver{} | |
| 243 | - | saver := &fakeResolvedImageSaver{} | |
| 244 | - | delegator := &fakeMenuActionDelegator{} | |
| 183 | + | resolver := portmocks.NewMockImageDataResolver(t) | |
| 184 | + | saver := portmocks.NewMockResolvedImageSaver(t) | |
| 185 | + | delegator := portmocks.NewMockMenuActionDelegator(t) | |
| 186 | + | delegator.EXPECT(). | |
| 187 | + | DelegateMenuAction(mock.Anything, port.MenuActionCopySelection, port.MenuContext{ | |
| 188 | + | HasSelection: true, | |
| 189 | + | SelectionText: "selected text", | |
| 190 | + | }). | |
| 191 | + | Return(nil). | |
| 192 | + | Once() | |
| 245 | 193 | uc := NewExecuteContextMenuActionUseCase(nil, resolver, saver, delegator) | |
| 246 | 194 | ||
| 247 | 195 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | |
| @@ -253,15 +201,17 @@ func TestExecuteContextMenuActionUseCase_CopySelectionFallsBackToDelegatorWhenCl | |||
| 253 | 201 | }) | |
| 254 | 202 | ||
| 255 | 203 | require.NoError(t, err) | |
| 256 | - | require.Equal(t, 1, delegator.delegateCalls) | |
| 257 | - | require.Equal(t, port.MenuActionCopySelection, delegator.action) | |
| 258 | 204 | } | |
| 259 | 205 | ||
| 260 | 206 | func TestExecuteContextMenuActionUseCase_CopyLinkWritesText(t *testing.T) { | |
| 261 | - | clipboard := &fakeClipboard{} | |
| 262 | - | resolver := &fakeImageResolver{} | |
| 263 | - | saver := &fakeResolvedImageSaver{} | |
| 264 | - | delegator := &fakeMenuActionDelegator{} | |
| 207 | + | clipboard := portmocks.NewMockClipboard(t) | |
| 208 | + | resolver := portmocks.NewMockImageDataResolver(t) | |
| 209 | + | saver := portmocks.NewMockResolvedImageSaver(t) | |
| 210 | + | delegator := portmocks.NewMockMenuActionDelegator(t) | |
| 211 | + | clipboard.EXPECT(). | |
| 212 | + | WriteText(mock.Anything, "https://example.com/link"). | |
| 213 | + | Return(nil). | |
| 214 | + | Once() | |
| 265 | 215 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | |
| 266 | 216 | ||
| 267 | 217 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | |
| @@ -270,10 +220,4 @@ func TestExecuteContextMenuActionUseCase_CopyLinkWritesText(t *testing.T) { | |||
| 270 | 220 | }) | |
| 271 | 221 | ||
| 272 | 222 | require.NoError(t, err) | |
| 273 | - | require.Equal(t, 1, clipboard.writeTextCalls) | |
| 274 | - | require.Equal(t, "https://example.com/link", clipboard.text) | |
| 275 | - | require.Zero(t, clipboard.writeImageCalls) | |
| 276 | - | require.Zero(t, resolver.resolveCalls) | |
| 277 | - | require.Zero(t, saver.saveCalls) | |
| 278 | - | require.Zero(t, delegator.delegateCalls) | |
| 279 | 223 | } | |
| @@ -6,79 +6,21 @@ import ( | |||||
| 6 | "testing" | 6 | "testing" | ||
| 7 | 7 | ||||
| 8 | "github.com/bnema/dumber/internal/application/port" | 8 | "github.com/bnema/dumber/internal/application/port" | ||
| 9 | + | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |||
| 9 | "github.com/bnema/dumber/internal/domain/entity" | 10 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 11 | + | "github.com/stretchr/testify/mock" | |||
| 10 | "github.com/stretchr/testify/require" | 12 | "github.com/stretchr/testify/require" | ||
| 11 | ) | 13 | ) | ||
| 12 | 14 | ||||
| 13 | - | type fakeClipboard struct { | |||
| 14 | - | writeTextCalls int | |||
| 15 | - | writeImageCalls int | |||
| 16 | - | text string | |||
| 17 | - | image entity.ImageData | |||
| 18 | - | } | |||
| 19 | - | ||||
| 20 | - | func (f *fakeClipboard) WriteText(_ context.Context, text string) error { | |||
| 21 | - | f.writeTextCalls++ | |||
| 22 | - | f.text = text | |||
| 23 | - | return nil | |||
| 24 | - | } | |||
| 25 | - | ||||
| 26 | - | func (f *fakeClipboard) WriteImage(_ context.Context, image entity.ImageData) error { | |||
| 27 | - | f.writeImageCalls++ | |||
| 28 | - | f.image = image | |||
| 29 | - | return nil | |||
| 30 | - | } | |||
| 31 | - | ||||
| 32 | - | func (*fakeClipboard) ReadText(context.Context) (string, error) { return "", nil } | |||
| 33 | - | func (*fakeClipboard) Clear(context.Context) error { return nil } | |||
| 34 | - | func (*fakeClipboard) HasText(context.Context) (bool, error) { return false, nil } | |||
| 35 | - | ||||
| 36 | - | type fakeImageResolver struct { | |||
| 37 | - | resolveCalls int | |||
| 38 | - | uri string | |||
| 39 | - | image entity.ImageData | |||
| 40 | - | err error | |||
| 41 | - | } | |||
| 42 | - | ||||
| 43 | - | func (f *fakeImageResolver) ResolveImageData(_ context.Context, uri string) (entity.ImageData, error) { | |||
| 44 | - | f.resolveCalls++ | |||
| 45 | - | f.uri = uri | |||
| 46 | - | return f.image, f.err | |||
| 47 | - | } | |||
| 48 | - | ||||
| 49 | - | type fakeResolvedImageSaver struct { | |||
| 50 | - | saveCalls int | |||
| 51 | - | image entity.ImageData | |||
| 52 | - | menuContext port.MenuContext | |||
| 53 | - | err error | |||
| 54 | - | } | |||
| 55 | - | ||||
| 56 | - | func (f *fakeResolvedImageSaver) SaveResolvedImage(_ context.Context, image entity.ImageData, menuContext port.MenuContext) error { | |||
| 57 | - | f.saveCalls++ | |||
| 58 | - | f.image = image | |||
| 59 | - | f.menuContext = menuContext | |||
| 60 | - | return f.err | |||
| 61 | - | } | |||
| 62 | - | ||||
| 63 | - | type fakeMenuActionDelegator struct { | |||
| 64 | - | delegateCalls int | |||
| 65 | - | action port.MenuAction | |||
| 66 | - | menuContext port.MenuContext | |||
| 67 | - | err error | |||
| 68 | - | } | |||
| 69 | - | ||||
| 70 | - | func (f *fakeMenuActionDelegator) DelegateMenuAction(_ context.Context, action port.MenuAction, menuContext port.MenuContext) error { | |||
| 71 | - | f.delegateCalls++ | |||
| 72 | - | f.action = action | |||
| 73 | - | f.menuContext = menuContext | |||
| 74 | - | return f.err | |||
| 75 | - | } | |||
| 76 | - | ||||
| 77 | func TestExecuteContextMenuActionUseCase_CopyImageFailsWithoutResolvedData(t *testing.T) { | 15 | func TestExecuteContextMenuActionUseCase_CopyImageFailsWithoutResolvedData(t *testing.T) { | ||
| 78 | - | clipboard := &fakeClipboard{} | 16 | + | clipboard := portmocks.NewMockClipboard(t) |
| 79 | - | resolver := &fakeImageResolver{} | 17 | + | resolver := portmocks.NewMockImageDataResolver(t) |
| 80 | - | saver := &fakeResolvedImageSaver{} | 18 | + | saver := portmocks.NewMockResolvedImageSaver(t) |
| 81 | - | delegator := &fakeMenuActionDelegator{} | 19 | + | delegator := portmocks.NewMockMenuActionDelegator(t) |
| 20 | + | resolver.EXPECT(). | |||
| 21 | + | ResolveImageData(mock.Anything, "https://example.com/image.png"). | |||
| 22 | + | Return(entity.ImageData{}, nil). | |||
| 23 | + | Once() | |||
| 82 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | 24 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | ||
| 83 | 25 | ||||
| 84 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | 26 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | ||
| @@ -89,16 +31,22 @@ func TestExecuteContextMenuActionUseCase_CopyImageFailsWithoutResolvedData(t *te | |||||
| 89 | require.Error(t, err) | 31 | require.Error(t, err) | ||
| 90 | require.Contains(t, err.Error(), "copy image:") | 32 | require.Contains(t, err.Error(), "copy image:") | ||
| 91 | require.Contains(t, err.Error(), "image data not available") | 33 | require.Contains(t, err.Error(), "image data not available") | ||
| 92 | - | require.Zero(t, clipboard.writeImageCalls) | |||
| 93 | - | require.Zero(t, saver.saveCalls) | |||
| 94 | - | require.Zero(t, delegator.delegateCalls) | |||
| 95 | } | 34 | } | ||
| 96 | 35 | ||||
| 97 | func TestExecuteContextMenuActionUseCase_CopyImageWritesResolvedImage(t *testing.T) { | 36 | func TestExecuteContextMenuActionUseCase_CopyImageWritesResolvedImage(t *testing.T) { | ||
| 98 | - | clipboard := &fakeClipboard{} | 37 | + | clipboard := portmocks.NewMockClipboard(t) |
| 99 | - | resolver := &fakeImageResolver{image: entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"}} | 38 | + | resolver := portmocks.NewMockImageDataResolver(t) |
| 100 | - | saver := &fakeResolvedImageSaver{} | 39 | + | saver := portmocks.NewMockResolvedImageSaver(t) |
| 101 | - | delegator := &fakeMenuActionDelegator{} | 40 | + | delegator := portmocks.NewMockMenuActionDelegator(t) |
| 41 | + | image := entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"} | |||
| 42 | + | resolver.EXPECT(). | |||
| 43 | + | ResolveImageData(mock.Anything, "https://example.com/image.png"). | |||
| 44 | + | Return(image, nil). | |||
| 45 | + | Once() | |||
| 46 | + | clipboard.EXPECT(). | |||
| 47 | + | WriteImage(mock.Anything, image). | |||
| 48 | + | Return(nil). | |||
| 49 | + | Once() | |||
| 102 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | 50 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | ||
| 103 | 51 | ||||
| 104 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | 52 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | ||
| @@ -107,18 +55,12 @@ func TestExecuteContextMenuActionUseCase_CopyImageWritesResolvedImage(t *testing | |||||
| 107 | }) | 55 | }) | ||
| 108 | 56 | ||||
| 109 | require.NoError(t, err) | 57 | require.NoError(t, err) | ||
| 110 | - | require.Equal(t, 1, resolver.resolveCalls) | |||
| 111 | - | require.Equal(t, "https://example.com/image.png", resolver.uri) | |||
| 112 | - | require.Equal(t, 1, clipboard.writeImageCalls) | |||
| 113 | - | require.Equal(t, entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"}, clipboard.image) | |||
| 114 | - | require.Zero(t, saver.saveCalls) | |||
| 115 | - | require.Zero(t, delegator.delegateCalls) | |||
| 116 | } | 58 | } | ||
| 117 | 59 | ||||
| 118 | func TestExecuteContextMenuActionUseCase_CopyImageFailsFastWithoutClipboard(t *testing.T) { | 60 | func TestExecuteContextMenuActionUseCase_CopyImageFailsFastWithoutClipboard(t *testing.T) { | ||
| 119 | - | resolver := &fakeImageResolver{} | 61 | + | resolver := portmocks.NewMockImageDataResolver(t) |
| 120 | - | saver := &fakeResolvedImageSaver{} | 62 | + | saver := portmocks.NewMockResolvedImageSaver(t) |
| 121 | - | delegator := &fakeMenuActionDelegator{} | 63 | + | delegator := portmocks.NewMockMenuActionDelegator(t) |
| 122 | uc := NewExecuteContextMenuActionUseCase(nil, resolver, saver, delegator) | 64 | uc := NewExecuteContextMenuActionUseCase(nil, resolver, saver, delegator) | ||
| 123 | 65 | ||||
| 124 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | 66 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | ||
| @@ -128,38 +70,37 @@ func TestExecuteContextMenuActionUseCase_CopyImageFailsFastWithoutClipboard(t *t | |||||
| 128 | 70 | ||||
| 129 | require.Error(t, err) | 71 | require.Error(t, err) | ||
| 130 | require.Contains(t, err.Error(), "clipboard not available") | 72 | require.Contains(t, err.Error(), "clipboard not available") | ||
| 131 | - | require.Zero(t, resolver.resolveCalls) | |||
| 132 | - | require.Zero(t, saver.saveCalls) | |||
| 133 | - | require.Zero(t, delegator.delegateCalls) | |||
| 134 | } | 73 | } | ||
| 135 | 74 | ||||
| 136 | func TestExecuteContextMenuActionUseCase_SaveImageDelegatesResolvedImage(t *testing.T) { | 75 | func TestExecuteContextMenuActionUseCase_SaveImageDelegatesResolvedImage(t *testing.T) { | ||
| 137 | - | clipboard := &fakeClipboard{} | 76 | + | clipboard := portmocks.NewMockClipboard(t) |
| 138 | - | resolver := &fakeImageResolver{image: entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"}} | 77 | + | resolver := portmocks.NewMockImageDataResolver(t) |
| 139 | - | saver := &fakeResolvedImageSaver{} | 78 | + | saver := portmocks.NewMockResolvedImageSaver(t) |
| 140 | - | delegator := &fakeMenuActionDelegator{} | 79 | + | delegator := portmocks.NewMockMenuActionDelegator(t) |
| 80 | + | image := entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"} | |||
| 81 | + | menuContext := port.MenuContext{ImageURI: "https://example.com/image.png"} | |||
| 82 | + | resolver.EXPECT(). | |||
| 83 | + | ResolveImageData(mock.Anything, menuContext.ImageURI). | |||
| 84 | + | Return(image, nil). | |||
| 85 | + | Once() | |||
| 86 | + | saver.EXPECT(). | |||
| 87 | + | SaveResolvedImage(mock.Anything, image, menuContext). | |||
| 88 | + | Return(nil). | |||
| 89 | + | Once() | |||
| 141 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | 90 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | ||
| 142 | 91 | ||||
| 143 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | 92 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | ||
| 144 | Action: port.MenuActionSaveImage, | 93 | Action: port.MenuActionSaveImage, | ||
| 145 | - | Context: port.MenuContext{ImageURI: "https://example.com/image.png"}, | 94 | + | Context: menuContext, |
| 146 | }) | 95 | }) | ||
| 147 | 96 | ||||
| 148 | require.NoError(t, err) | 97 | require.NoError(t, err) | ||
| 149 | - | require.Equal(t, 1, resolver.resolveCalls) | |||
| 150 | - | require.Equal(t, "https://example.com/image.png", resolver.uri) | |||
| 151 | - | require.Equal(t, 1, saver.saveCalls) | |||
| 152 | - | require.Equal(t, entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"}, saver.image) | |||
| 153 | - | require.Equal(t, port.MenuContext{ImageURI: "https://example.com/image.png"}, saver.menuContext) | |||
| 154 | - | require.Zero(t, clipboard.writeTextCalls) | |||
| 155 | - | require.Zero(t, clipboard.writeImageCalls) | |||
| 156 | - | require.Zero(t, delegator.delegateCalls) | |||
| 157 | } | 98 | } | ||
| 158 | 99 | ||||
| 159 | func TestExecuteContextMenuActionUseCase_SaveImageFailsFastWithoutSaver(t *testing.T) { | 100 | func TestExecuteContextMenuActionUseCase_SaveImageFailsFastWithoutSaver(t *testing.T) { | ||
| 160 | - | clipboard := &fakeClipboard{} | 101 | + | clipboard := portmocks.NewMockClipboard(t) |
| 161 | - | resolver := &fakeImageResolver{} | 102 | + | resolver := portmocks.NewMockImageDataResolver(t) |
| 162 | - | delegator := &fakeMenuActionDelegator{} | 103 | + | delegator := portmocks.NewMockMenuActionDelegator(t) |
| 163 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, nil, delegator) | 104 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, nil, delegator) | ||
| 164 | 105 | ||||
| 165 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | 106 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | ||
| @@ -169,42 +110,41 @@ func TestExecuteContextMenuActionUseCase_SaveImageFailsFastWithoutSaver(t *testi | |||||
| 169 | 110 | ||||
| 170 | require.Error(t, err) | 111 | require.Error(t, err) | ||
| 171 | require.Contains(t, err.Error(), "image saver not available") | 112 | require.Contains(t, err.Error(), "image saver not available") | ||
| 172 | - | require.Zero(t, resolver.resolveCalls) | |||
| 173 | - | require.Zero(t, delegator.delegateCalls) | |||
| 174 | } | 113 | } | ||
| 175 | 114 | ||||
| 176 | func TestExecuteContextMenuActionUseCase_DelegatesInspect(t *testing.T) { | 115 | func TestExecuteContextMenuActionUseCase_DelegatesInspect(t *testing.T) { | ||
| 177 | - | clipboard := &fakeClipboard{} | |||
| 178 | - | resolver := &fakeImageResolver{} | |||
| 179 | - | saver := &fakeResolvedImageSaver{} | |||
| 180 | - | delegator := &fakeMenuActionDelegator{} | |||
| 181 | - | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | |||
| 182 | - | ||||
| 183 | menuContext := port.MenuContext{ | 116 | menuContext := port.MenuContext{ | ||
| 184 | PageURI: "https://example.com", | 117 | PageURI: "https://example.com", | ||
| 185 | X: 17, | 118 | X: 17, | ||
| 186 | Y: 42, | 119 | Y: 42, | ||
| 187 | } | 120 | } | ||
| 121 | + | clipboard := portmocks.NewMockClipboard(t) | |||
| 122 | + | resolver := portmocks.NewMockImageDataResolver(t) | |||
| 123 | + | saver := portmocks.NewMockResolvedImageSaver(t) | |||
| 124 | + | delegator := portmocks.NewMockMenuActionDelegator(t) | |||
| 125 | + | delegator.EXPECT(). | |||
| 126 | + | DelegateMenuAction(mock.Anything, port.MenuActionInspectElement, menuContext). | |||
| 127 | + | Return(nil). | |||
| 128 | + | Once() | |||
| 129 | + | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | |||
| 130 | + | ||||
| 188 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | 131 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | ||
| 189 | Action: port.MenuActionInspectElement, | 132 | Action: port.MenuActionInspectElement, | ||
| 190 | Context: menuContext, | 133 | Context: menuContext, | ||
| 191 | }) | 134 | }) | ||
| 192 | 135 | ||||
| 193 | require.NoError(t, err) | 136 | require.NoError(t, err) | ||
| 194 | - | require.Equal(t, 1, delegator.delegateCalls) | |||
| 195 | - | require.Equal(t, port.MenuActionInspectElement, delegator.action) | |||
| 196 | - | require.Equal(t, menuContext, delegator.menuContext) | |||
| 197 | - | require.Zero(t, clipboard.writeTextCalls) | |||
| 198 | - | require.Zero(t, clipboard.writeImageCalls) | |||
| 199 | - | require.Zero(t, resolver.resolveCalls) | |||
| 200 | - | require.Zero(t, saver.saveCalls) | |||
| 201 | } | 137 | } | ||
| 202 | 138 | ||||
| 203 | func TestExecuteContextMenuActionUseCase_WrapsDelegateErrors(t *testing.T) { | 139 | func TestExecuteContextMenuActionUseCase_WrapsDelegateErrors(t *testing.T) { | ||
| 204 | - | clipboard := &fakeClipboard{} | 140 | + | clipboard := portmocks.NewMockClipboard(t) |
| 205 | - | resolver := &fakeImageResolver{} | 141 | + | resolver := portmocks.NewMockImageDataResolver(t) |
| 206 | - | saver := &fakeResolvedImageSaver{} | 142 | + | saver := portmocks.NewMockResolvedImageSaver(t) |
| 207 | - | delegator := &fakeMenuActionDelegator{err: errors.New("boom")} | 143 | + | delegator := portmocks.NewMockMenuActionDelegator(t) |
| 144 | + | delegator.EXPECT(). | |||
| 145 | + | DelegateMenuAction(mock.Anything, port.MenuActionCopySelection, port.MenuContext{HasSelection: true}). | |||
| 146 | + | Return(errors.New("boom")). | |||
| 147 | + | Once() | |||
| 208 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | 148 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | ||
| 209 | 149 | ||||
| 210 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | 150 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | ||
| @@ -218,10 +158,14 @@ func TestExecuteContextMenuActionUseCase_WrapsDelegateErrors(t *testing.T) { | |||||
| 218 | } | 158 | } | ||
| 219 | 159 | ||||
| 220 | func TestExecuteContextMenuActionUseCase_CopySelectionWritesSelectedText(t *testing.T) { | 160 | func TestExecuteContextMenuActionUseCase_CopySelectionWritesSelectedText(t *testing.T) { | ||
| 221 | - | clipboard := &fakeClipboard{} | 161 | + | clipboard := portmocks.NewMockClipboard(t) |
| 222 | - | resolver := &fakeImageResolver{} | 162 | + | resolver := portmocks.NewMockImageDataResolver(t) |
| 223 | - | saver := &fakeResolvedImageSaver{} | 163 | + | saver := portmocks.NewMockResolvedImageSaver(t) |
| 224 | - | delegator := &fakeMenuActionDelegator{} | 164 | + | delegator := portmocks.NewMockMenuActionDelegator(t) |
| 165 | + | clipboard.EXPECT(). | |||
| 166 | + | WriteText(mock.Anything, "selected text"). | |||
| 167 | + | Return(nil). | |||
| 168 | + | Once() | |||
| 225 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | 169 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | ||
| 226 | 170 | ||||
| 227 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | 171 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | ||
| @@ -233,15 +177,19 @@ func TestExecuteContextMenuActionUseCase_CopySelectionWritesSelectedText(t *test | |||||
| 233 | }) | 177 | }) | ||
| 234 | 178 | ||||
| 235 | require.NoError(t, err) | 179 | require.NoError(t, err) | ||
| 236 | - | require.Equal(t, 1, clipboard.writeTextCalls) | |||
| 237 | - | require.Equal(t, "selected text", clipboard.text) | |||
| 238 | - | require.Zero(t, delegator.delegateCalls) | |||
| 239 | } | 180 | } | ||
| 240 | 181 | ||||
| 241 | func TestExecuteContextMenuActionUseCase_CopySelectionFallsBackToDelegatorWhenClipboardMissing(t *testing.T) { | 182 | func TestExecuteContextMenuActionUseCase_CopySelectionFallsBackToDelegatorWhenClipboardMissing(t *testing.T) { | ||
| 242 | - | resolver := &fakeImageResolver{} | 183 | + | resolver := portmocks.NewMockImageDataResolver(t) |
| 243 | - | saver := &fakeResolvedImageSaver{} | 184 | + | saver := portmocks.NewMockResolvedImageSaver(t) |
| 244 | - | delegator := &fakeMenuActionDelegator{} | 185 | + | delegator := portmocks.NewMockMenuActionDelegator(t) |
| 186 | + | delegator.EXPECT(). | |||
| 187 | + | DelegateMenuAction(mock.Anything, port.MenuActionCopySelection, port.MenuContext{ | |||
| 188 | + | HasSelection: true, | |||
| 189 | + | SelectionText: "selected text", | |||
| 190 | + | }). | |||
| 191 | + | Return(nil). | |||
| 192 | + | Once() | |||
| 245 | uc := NewExecuteContextMenuActionUseCase(nil, resolver, saver, delegator) | 193 | uc := NewExecuteContextMenuActionUseCase(nil, resolver, saver, delegator) | ||
| 246 | 194 | ||||
| 247 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | 195 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | ||
| @@ -253,15 +201,17 @@ func TestExecuteContextMenuActionUseCase_CopySelectionFallsBackToDelegatorWhenCl | |||||
| 253 | }) | 201 | }) | ||
| 254 | 202 | ||||
| 255 | require.NoError(t, err) | 203 | require.NoError(t, err) | ||
| 256 | - | require.Equal(t, 1, delegator.delegateCalls) | |||
| 257 | - | require.Equal(t, port.MenuActionCopySelection, delegator.action) | |||
| 258 | } | 204 | } | ||
| 259 | 205 | ||||
| 260 | func TestExecuteContextMenuActionUseCase_CopyLinkWritesText(t *testing.T) { | 206 | func TestExecuteContextMenuActionUseCase_CopyLinkWritesText(t *testing.T) { | ||
| 261 | - | clipboard := &fakeClipboard{} | 207 | + | clipboard := portmocks.NewMockClipboard(t) |
| 262 | - | resolver := &fakeImageResolver{} | 208 | + | resolver := portmocks.NewMockImageDataResolver(t) |
| 263 | - | saver := &fakeResolvedImageSaver{} | 209 | + | saver := portmocks.NewMockResolvedImageSaver(t) |
| 264 | - | delegator := &fakeMenuActionDelegator{} | 210 | + | delegator := portmocks.NewMockMenuActionDelegator(t) |
| 211 | + | clipboard.EXPECT(). | |||
| 212 | + | WriteText(mock.Anything, "https://example.com/link"). | |||
| 213 | + | Return(nil). | |||
| 214 | + | Once() | |||
| 265 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | 215 | uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator) | ||
| 266 | 216 | ||||
| 267 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | 217 | err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{ | ||
| @@ -270,10 +220,4 @@ func TestExecuteContextMenuActionUseCase_CopyLinkWritesText(t *testing.T) { | |||||
| 270 | }) | 220 | }) | ||
| 271 | 221 | ||||
| 272 | require.NoError(t, err) | 222 | require.NoError(t, err) | ||
| 273 | - | require.Equal(t, 1, clipboard.writeTextCalls) | |||
| 274 | - | require.Equal(t, "https://example.com/link", clipboard.text) | |||
| 275 | - | require.Zero(t, clipboard.writeImageCalls) | |||
| 276 | - | require.Zero(t, resolver.resolveCalls) | |||
| 277 | - | require.Zero(t, saver.saveCalls) | |||
| 278 | - | require.Zero(t, delegator.delegateCalls) | |||
| 279 | } | 223 | } | ||
M internal/bootstrap/session_test.go +6 -10
| @@ -5,6 +5,7 @@ import ( | |||
| 5 | 5 | "testing" | |
| 6 | 6 | "time" | |
| 7 | 7 | ||
| 8 | + | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |
| 8 | 9 | "github.com/bnema/dumber/internal/application/usecase" | |
| 9 | 10 | "github.com/bnema/dumber/internal/domain/entity" | |
| 10 | 11 | repomocks "github.com/bnema/dumber/internal/domain/repository/mocks" | |
| @@ -13,20 +14,13 @@ import ( | |||
| 13 | 14 | "github.com/stretchr/testify/require" | |
| 14 | 15 | ) | |
| 15 | 16 | ||
| 16 | - | type fakeProcessProbe struct { | |
| 17 | - | alive map[int]bool | |
| 18 | - | } | |
| 19 | - | ||
| 20 | - | func (p fakeProcessProbe) IsProcessAlive(_ context.Context, pid int) (bool, error) { | |
| 21 | - | return p.alive[pid], nil | |
| 22 | - | } | |
| 23 | - | ||
| 24 | 17 | func TestRunSessionCleanup_DoesNotEndOtherActiveBrowserSessionsWithoutLivenessProof(t *testing.T) { | |
| 25 | 18 | t.Parallel() | |
| 26 | 19 | ||
| 27 | 20 | ctx := context.Background() | |
| 28 | 21 | repo := repomocks.NewMockSessionRepository(t) | |
| 29 | - | cleanupUC := usecase.NewCleanupSessionsUseCase(repo, fakeProcessProbe{alive: map[int]bool{1234: true}}) | |
| 22 | + | probe := portmocks.NewMockSessionProcessProbe(t) | |
| 23 | + | cleanupUC := usecase.NewCleanupSessionsUseCase(repo, probe) | |
| 30 | 24 | cfg := config.DefaultConfig() | |
| 31 | 25 | logDir := t.TempDir() | |
| 32 | 26 | ||
| @@ -45,6 +39,7 @@ func TestRunSessionCleanup_DoesNotEndOtherActiveBrowserSessionsWithoutLivenessPr | |||
| 45 | 39 | } | |
| 46 | 40 | ||
| 47 | 41 | repo.EXPECT().GetRecent(mock.Anything, recentSessionsLimit).Return([]*entity.Session{current, live}, nil).Once() | |
| 42 | + | probe.EXPECT().IsProcessAlive(mock.Anything, livePID).Return(true, nil).Once() | |
| 48 | 43 | repo.EXPECT().DeleteExitedBefore(mock.Anything, mock.MatchedBy(func(time.Time) bool { return true })).Return(int64(0), nil).Once() | |
| 49 | 44 | repo.EXPECT().DeleteOldestExited(mock.Anything, cfg.Session.MaxExitedSessions).Return(int64(0), nil).Once() | |
| 50 | 45 | ||
| @@ -56,7 +51,8 @@ func TestRunSessionCleanup_NoPanicOnNilSessions(t *testing.T) { | |||
| 56 | 51 | ||
| 57 | 52 | ctx := context.Background() | |
| 58 | 53 | repo := repomocks.NewMockSessionRepository(t) | |
| 59 | - | cleanupUC := usecase.NewCleanupSessionsUseCase(repo, fakeProcessProbe{}) | |
| 54 | + | probe := portmocks.NewMockSessionProcessProbe(t) | |
| 55 | + | cleanupUC := usecase.NewCleanupSessionsUseCase(repo, probe) | |
| 60 | 56 | cfg := config.DefaultConfig() | |
| 61 | 57 | logDir := t.TempDir() | |
| 62 | 58 | ||
| @@ -5,6 +5,7 @@ import ( | |||||
| 5 | "testing" | 5 | "testing" | ||
| 6 | "time" | 6 | "time" | ||
| 7 | 7 | ||||
| 8 | + | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |||
| 8 | "github.com/bnema/dumber/internal/application/usecase" | 9 | "github.com/bnema/dumber/internal/application/usecase" | ||
| 9 | "github.com/bnema/dumber/internal/domain/entity" | 10 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 10 | repomocks "github.com/bnema/dumber/internal/domain/repository/mocks" | 11 | repomocks "github.com/bnema/dumber/internal/domain/repository/mocks" | ||
| @@ -13,20 +14,13 @@ import ( | |||||
| 13 | "github.com/stretchr/testify/require" | 14 | "github.com/stretchr/testify/require" | ||
| 14 | ) | 15 | ) | ||
| 15 | 16 | ||||
| 16 | - | type fakeProcessProbe struct { | |||
| 17 | - | alive map[int]bool | |||
| 18 | - | } | |||
| 19 | - | ||||
| 20 | - | func (p fakeProcessProbe) IsProcessAlive(_ context.Context, pid int) (bool, error) { | |||
| 21 | - | return p.alive[pid], nil | |||
| 22 | - | } | |||
| 23 | - | ||||
| 24 | func TestRunSessionCleanup_DoesNotEndOtherActiveBrowserSessionsWithoutLivenessProof(t *testing.T) { | 17 | func TestRunSessionCleanup_DoesNotEndOtherActiveBrowserSessionsWithoutLivenessProof(t *testing.T) { | ||
| 25 | t.Parallel() | 18 | t.Parallel() | ||
| 26 | 19 | ||||
| 27 | ctx := context.Background() | 20 | ctx := context.Background() | ||
| 28 | repo := repomocks.NewMockSessionRepository(t) | 21 | repo := repomocks.NewMockSessionRepository(t) | ||
| 29 | - | cleanupUC := usecase.NewCleanupSessionsUseCase(repo, fakeProcessProbe{alive: map[int]bool{1234: true}}) | 22 | + | probe := portmocks.NewMockSessionProcessProbe(t) |
| 23 | + | cleanupUC := usecase.NewCleanupSessionsUseCase(repo, probe) | |||
| 30 | cfg := config.DefaultConfig() | 24 | cfg := config.DefaultConfig() | ||
| 31 | logDir := t.TempDir() | 25 | logDir := t.TempDir() | ||
| 32 | 26 | ||||
| @@ -45,6 +39,7 @@ func TestRunSessionCleanup_DoesNotEndOtherActiveBrowserSessionsWithoutLivenessPr | |||||
| 45 | } | 39 | } | ||
| 46 | 40 | ||||
| 47 | repo.EXPECT().GetRecent(mock.Anything, recentSessionsLimit).Return([]*entity.Session{current, live}, nil).Once() | 41 | repo.EXPECT().GetRecent(mock.Anything, recentSessionsLimit).Return([]*entity.Session{current, live}, nil).Once() | ||
| 42 | + | probe.EXPECT().IsProcessAlive(mock.Anything, livePID).Return(true, nil).Once() | |||
| 48 | repo.EXPECT().DeleteExitedBefore(mock.Anything, mock.MatchedBy(func(time.Time) bool { return true })).Return(int64(0), nil).Once() | 43 | repo.EXPECT().DeleteExitedBefore(mock.Anything, mock.MatchedBy(func(time.Time) bool { return true })).Return(int64(0), nil).Once() | ||
| 49 | repo.EXPECT().DeleteOldestExited(mock.Anything, cfg.Session.MaxExitedSessions).Return(int64(0), nil).Once() | 44 | repo.EXPECT().DeleteOldestExited(mock.Anything, cfg.Session.MaxExitedSessions).Return(int64(0), nil).Once() | ||
| 50 | 45 | ||||
| @@ -56,7 +51,8 @@ func TestRunSessionCleanup_NoPanicOnNilSessions(t *testing.T) { | |||||
| 56 | 51 | ||||
| 57 | ctx := context.Background() | 52 | ctx := context.Background() | ||
| 58 | repo := repomocks.NewMockSessionRepository(t) | 53 | repo := repomocks.NewMockSessionRepository(t) | ||
| 59 | - | cleanupUC := usecase.NewCleanupSessionsUseCase(repo, fakeProcessProbe{}) | 54 | + | probe := portmocks.NewMockSessionProcessProbe(t) |
| 55 | + | cleanupUC := usecase.NewCleanupSessionsUseCase(repo, probe) | |||
| 60 | cfg := config.DefaultConfig() | 56 | cfg := config.DefaultConfig() | ||
| 61 | logDir := t.TempDir() | 57 | logDir := t.TempDir() | ||
| 62 | 58 | ||||
M internal/infrastructure/webkit/resolved_image_saver_test.go +17 -19
| @@ -7,38 +7,37 @@ import ( | |||
| 7 | 7 | "testing" | |
| 8 | 8 | ||
| 9 | 9 | "github.com/bnema/dumber/internal/application/port" | |
| 10 | + | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |
| 10 | 11 | "github.com/bnema/dumber/internal/domain/entity" | |
| 12 | + | "github.com/stretchr/testify/mock" | |
| 11 | 13 | "github.com/stretchr/testify/require" | |
| 12 | 14 | ) | |
| 13 | 15 | ||
| 14 | - | type fakeDownloadPreparer struct { | |
| 15 | - | calls int | |
| 16 | - | input port.DownloadPrepareInput | |
| 17 | - | output *port.DownloadPrepareOutput | |
| 18 | - | } | |
| 19 | - | ||
| 20 | - | func (f *fakeDownloadPreparer) Execute(_ context.Context, input port.DownloadPrepareInput) *port.DownloadPrepareOutput { | |
| 21 | - | f.calls++ | |
| 22 | - | f.input = input | |
| 23 | - | return f.output | |
| 24 | - | } | |
| 25 | - | ||
| 26 | 16 | func TestResolvedImageSaver_SaveResolvedImage(t *testing.T) { | |
| 27 | 17 | downloadDir := t.TempDir() | |
| 28 | 18 | destPath := filepath.Join(downloadDir, "image.png") | |
| 29 | - | preparer := &fakeDownloadPreparer{output: &port.DownloadPrepareOutput{Filename: "image.png", DestinationPath: destPath}} | |
| 19 | + | preparer := portmocks.NewMockDownloadPreparer(t) | |
| 20 | + | preparer.EXPECT(). | |
| 21 | + | Execute(mock.Anything, mock.MatchedBy(func(input port.DownloadPrepareInput) bool { | |
| 22 | + | return input.DownloadDir == downloadDir && | |
| 23 | + | input.Response.GetUri() == "https://example.com/assets/image" && | |
| 24 | + | input.Response.GetMimeType() == "image/jpeg" | |
| 25 | + | })). | |
| 26 | + | Return(&port.DownloadPrepareOutput{Filename: "image.png", DestinationPath: destPath}). | |
| 27 | + | Once() | |
| 30 | 28 | saver := NewResolvedImageSaver(preparer, downloadDir) | |
| 31 | 29 | ||
| 32 | 30 | err := saver.SaveResolvedImage(context.Background(), entity.ImageData{Bytes: []byte{1, 2, 3, 4}, MimeType: "image/jpeg"}, port.MenuContext{ImageURI: "https://example.com/assets/image"}) | |
| 33 | 31 | require.NoError(t, err) | |
| 34 | - | require.Equal(t, downloadDir, preparer.input.DownloadDir) | |
| 35 | - | require.Equal(t, "https://example.com/assets/image", preparer.input.Response.GetUri()) | |
| 36 | - | require.Equal(t, "image/jpeg", preparer.input.Response.GetMimeType()) | |
| 37 | 32 | require.Equal(t, []byte{1, 2, 3, 4}, readFile(t, destPath)) | |
| 38 | 33 | } | |
| 39 | 34 | ||
| 40 | 35 | func TestResolvedImageSaver_SaveResolvedImageRequiresDestination(t *testing.T) { | |
| 41 | - | preparer := &fakeDownloadPreparer{output: &port.DownloadPrepareOutput{Filename: "image.png"}} | |
| 36 | + | preparer := portmocks.NewMockDownloadPreparer(t) | |
| 37 | + | preparer.EXPECT(). | |
| 38 | + | Execute(mock.Anything, mock.Anything). | |
| 39 | + | Return(&port.DownloadPrepareOutput{Filename: "image.png"}). | |
| 40 | + | Once() | |
| 42 | 41 | saver := NewResolvedImageSaver(preparer, t.TempDir()) | |
| 43 | 42 | ||
| 44 | 43 | err := saver.SaveResolvedImage(context.Background(), entity.ImageData{Bytes: []byte{1}}, port.MenuContext{}) | |
| @@ -47,13 +46,12 @@ func TestResolvedImageSaver_SaveResolvedImageRequiresDestination(t *testing.T) { | |||
| 47 | 46 | } | |
| 48 | 47 | ||
| 49 | 48 | func TestResolvedImageSaver_SaveResolvedImageRejectsEmptyBytes(t *testing.T) { | |
| 50 | - | preparer := &fakeDownloadPreparer{output: &port.DownloadPrepareOutput{Filename: "image.png", DestinationPath: filepath.Join(t.TempDir(), "image.png")}} | |
| 49 | + | preparer := portmocks.NewMockDownloadPreparer(t) | |
| 51 | 50 | saver := NewResolvedImageSaver(preparer, t.TempDir()) | |
| 52 | 51 | ||
| 53 | 52 | err := saver.SaveResolvedImage(context.Background(), entity.ImageData{}, port.MenuContext{}) | |
| 54 | 53 | require.Error(t, err) | |
| 55 | 54 | require.Contains(t, err.Error(), "empty image data") | |
| 56 | - | require.Zero(t, preparer.calls) | |
| 57 | 55 | } | |
| 58 | 56 | ||
| 59 | 57 | func readFile(t *testing.T, path string) []byte { | |
| @@ -7,38 +7,37 @@ import ( | |||||
| 7 | "testing" | 7 | "testing" | ||
| 8 | 8 | ||||
| 9 | "github.com/bnema/dumber/internal/application/port" | 9 | "github.com/bnema/dumber/internal/application/port" | ||
| 10 | + | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |||
| 10 | "github.com/bnema/dumber/internal/domain/entity" | 11 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 12 | + | "github.com/stretchr/testify/mock" | |||
| 11 | "github.com/stretchr/testify/require" | 13 | "github.com/stretchr/testify/require" | ||
| 12 | ) | 14 | ) | ||
| 13 | 15 | ||||
| 14 | - | type fakeDownloadPreparer struct { | |||
| 15 | - | calls int | |||
| 16 | - | input port.DownloadPrepareInput | |||
| 17 | - | output *port.DownloadPrepareOutput | |||
| 18 | - | } | |||
| 19 | - | ||||
| 20 | - | func (f *fakeDownloadPreparer) Execute(_ context.Context, input port.DownloadPrepareInput) *port.DownloadPrepareOutput { | |||
| 21 | - | f.calls++ | |||
| 22 | - | f.input = input | |||
| 23 | - | return f.output | |||
| 24 | - | } | |||
| 25 | - | ||||
| 26 | func TestResolvedImageSaver_SaveResolvedImage(t *testing.T) { | 16 | func TestResolvedImageSaver_SaveResolvedImage(t *testing.T) { | ||
| 27 | downloadDir := t.TempDir() | 17 | downloadDir := t.TempDir() | ||
| 28 | destPath := filepath.Join(downloadDir, "image.png") | 18 | destPath := filepath.Join(downloadDir, "image.png") | ||
| 29 | - | preparer := &fakeDownloadPreparer{output: &port.DownloadPrepareOutput{Filename: "image.png", DestinationPath: destPath}} | 19 | + | preparer := portmocks.NewMockDownloadPreparer(t) |
| 20 | + | preparer.EXPECT(). | |||
| 21 | + | Execute(mock.Anything, mock.MatchedBy(func(input port.DownloadPrepareInput) bool { | |||
| 22 | + | return input.DownloadDir == downloadDir && | |||
| 23 | + | input.Response.GetUri() == "https://example.com/assets/image" && | |||
| 24 | + | input.Response.GetMimeType() == "image/jpeg" | |||
| 25 | + | })). | |||
| 26 | + | Return(&port.DownloadPrepareOutput{Filename: "image.png", DestinationPath: destPath}). | |||
| 27 | + | Once() | |||
| 30 | saver := NewResolvedImageSaver(preparer, downloadDir) | 28 | saver := NewResolvedImageSaver(preparer, downloadDir) | ||
| 31 | 29 | ||||
| 32 | err := saver.SaveResolvedImage(context.Background(), entity.ImageData{Bytes: []byte{1, 2, 3, 4}, MimeType: "image/jpeg"}, port.MenuContext{ImageURI: "https://example.com/assets/image"}) | 30 | err := saver.SaveResolvedImage(context.Background(), entity.ImageData{Bytes: []byte{1, 2, 3, 4}, MimeType: "image/jpeg"}, port.MenuContext{ImageURI: "https://example.com/assets/image"}) | ||
| 33 | require.NoError(t, err) | 31 | require.NoError(t, err) | ||
| 34 | - | require.Equal(t, downloadDir, preparer.input.DownloadDir) | |||
| 35 | - | require.Equal(t, "https://example.com/assets/image", preparer.input.Response.GetUri()) | |||
| 36 | - | require.Equal(t, "image/jpeg", preparer.input.Response.GetMimeType()) | |||
| 37 | require.Equal(t, []byte{1, 2, 3, 4}, readFile(t, destPath)) | 32 | require.Equal(t, []byte{1, 2, 3, 4}, readFile(t, destPath)) | ||
| 38 | } | 33 | } | ||
| 39 | 34 | ||||
| 40 | func TestResolvedImageSaver_SaveResolvedImageRequiresDestination(t *testing.T) { | 35 | func TestResolvedImageSaver_SaveResolvedImageRequiresDestination(t *testing.T) { | ||
| 41 | - | preparer := &fakeDownloadPreparer{output: &port.DownloadPrepareOutput{Filename: "image.png"}} | 36 | + | preparer := portmocks.NewMockDownloadPreparer(t) |
| 37 | + | preparer.EXPECT(). | |||
| 38 | + | Execute(mock.Anything, mock.Anything). | |||
| 39 | + | Return(&port.DownloadPrepareOutput{Filename: "image.png"}). | |||
| 40 | + | Once() | |||
| 42 | saver := NewResolvedImageSaver(preparer, t.TempDir()) | 41 | saver := NewResolvedImageSaver(preparer, t.TempDir()) | ||
| 43 | 42 | ||||
| 44 | err := saver.SaveResolvedImage(context.Background(), entity.ImageData{Bytes: []byte{1}}, port.MenuContext{}) | 43 | err := saver.SaveResolvedImage(context.Background(), entity.ImageData{Bytes: []byte{1}}, port.MenuContext{}) | ||
| @@ -47,13 +46,12 @@ func TestResolvedImageSaver_SaveResolvedImageRequiresDestination(t *testing.T) { | |||||
| 47 | } | 46 | } | ||
| 48 | 47 | ||||
| 49 | func TestResolvedImageSaver_SaveResolvedImageRejectsEmptyBytes(t *testing.T) { | 48 | func TestResolvedImageSaver_SaveResolvedImageRejectsEmptyBytes(t *testing.T) { | ||
| 50 | - | preparer := &fakeDownloadPreparer{output: &port.DownloadPrepareOutput{Filename: "image.png", DestinationPath: filepath.Join(t.TempDir(), "image.png")}} | 49 | + | preparer := portmocks.NewMockDownloadPreparer(t) |
| 51 | saver := NewResolvedImageSaver(preparer, t.TempDir()) | 50 | saver := NewResolvedImageSaver(preparer, t.TempDir()) | ||
| 52 | 51 | ||||
| 53 | err := saver.SaveResolvedImage(context.Background(), entity.ImageData{}, port.MenuContext{}) | 52 | err := saver.SaveResolvedImage(context.Background(), entity.ImageData{}, port.MenuContext{}) | ||
| 54 | require.Error(t, err) | 53 | require.Error(t, err) | ||
| 55 | require.Contains(t, err.Error(), "empty image data") | 54 | require.Contains(t, err.Error(), "empty image data") | ||
| 56 | - | require.Zero(t, preparer.calls) | |||
| 57 | } | 55 | } | ||
| 58 | 56 | ||||
| 59 | func readFile(t *testing.T, path string) []byte { | 57 | func readFile(t *testing.T, path string) []byte { | ||
M internal/ui/app_browser_launch_test.go +34 -36
| @@ -21,7 +21,7 @@ import ( | |||
| 21 | 21 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |
| 22 | 22 | "github.com/bnema/dumber/internal/application/usecase" | |
| 23 | 23 | "github.com/bnema/dumber/internal/domain/entity" | |
| 24 | - | "github.com/bnema/dumber/internal/domain/repository" | |
| 24 | + | repomocks "github.com/bnema/dumber/internal/domain/repository/mocks" | |
| 25 | 25 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 26 | 26 | "github.com/bnema/dumber/internal/shared/syncdispatch" | |
| 27 | 27 | "github.com/bnema/dumber/internal/ui/component" | |
| @@ -35,6 +35,7 @@ import ( | |||
| 35 | 35 | "github.com/bnema/puregotk/v4/gdk" | |
| 36 | 36 | "github.com/bnema/puregotk/v4/gio" | |
| 37 | 37 | "github.com/bnema/puregotk/v4/gtk" | |
| 38 | + | "github.com/stretchr/testify/mock" | |
| 38 | 39 | "github.com/stretchr/testify/require" | |
| 39 | 40 | ) | |
| 40 | 41 | ||
| @@ -62,29 +63,33 @@ func (c *testCloser) Close() error { | |||
| 62 | 63 | return nil | |
| 63 | 64 | } | |
| 64 | 65 | ||
| 65 | - | type fakeSessionStateRepo struct { | |
| 66 | - | state *entity.SessionState | |
| 67 | - | } | |
| 68 | - | ||
| 69 | - | var _ repository.SessionStateRepository = (*fakeSessionStateRepo)(nil) | |
| 70 | - | ||
| 71 | - | func (r *fakeSessionStateRepo) SaveSnapshot(context.Context, *entity.SessionState) error { return nil } | |
| 72 | - | ||
| 73 | - | func (r *fakeSessionStateRepo) GetSnapshot(context.Context, entity.SessionID) (*entity.SessionState, error) { | |
| 74 | - | return r.state, nil | |
| 66 | + | func testPathIsSocket(path string) bool { | |
| 67 | + | info, err := os.Stat(path) | |
| 68 | + | return err == nil && info.Mode()&os.ModeSocket != 0 | |
| 75 | 69 | } | |
| 76 | 70 | ||
| 77 | - | func (r *fakeSessionStateRepo) DeleteSnapshot(context.Context, entity.SessionID) error { return nil } | |
| 78 | - | ||
| 79 | - | func (r *fakeSessionStateRepo) GetAllSnapshots(context.Context) ([]*entity.SessionState, error) { | |
| 80 | - | return nil, nil | |
| 71 | + | func mockSessionStateRepoWithSnapshot(t *testing.T, sessionID entity.SessionID, state *entity.SessionState) *repomocks.MockSessionStateRepository { | |
| 72 | + | t.Helper() | |
| 73 | + | repo := repomocks.NewMockSessionStateRepository(t) | |
| 74 | + | repo.EXPECT(). | |
| 75 | + | GetSnapshot(mock.Anything, sessionID). | |
| 76 | + | Return(state, nil). | |
| 77 | + | Once() | |
| 78 | + | return repo | |
| 81 | 79 | } | |
| 82 | 80 | ||
| 83 | - | func (r *fakeSessionStateRepo) GetTotalSnapshotsSize(context.Context) (int64, error) { return 0, nil } | |
| 84 | - | ||
| 85 | - | func testPathIsSocket(path string) bool { | |
| 86 | - | info, err := os.Stat(path) | |
| 87 | - | return err == nil && info.Mode()&os.ModeSocket != 0 | |
| 81 | + | func mockZoomRepo(t *testing.T) *repomocks.MockZoomRepository { | |
| 82 | + | t.Helper() | |
| 83 | + | repo := repomocks.NewMockZoomRepository(t) | |
| 84 | + | repo.EXPECT(). | |
| 85 | + | Get(mock.Anything, mock.Anything). | |
| 86 | + | Return((*entity.ZoomLevel)(nil), nil). | |
| 87 | + | Maybe() | |
| 88 | + | repo.EXPECT(). | |
| 89 | + | Set(mock.Anything, mock.Anything). | |
| 90 | + | Return(nil). | |
| 91 | + | Maybe() | |
| 92 | + | return repo | |
| 88 | 93 | } | |
| 89 | 94 | ||
| 90 | 95 | func testGDKBackendAllows(backend string) bool { | |
| @@ -1128,7 +1133,7 @@ func TestApp_RestoreSessionHandlesEmptyWindowSnapshotAsSuccess(t *testing.T) { | |||
| 1128 | 1133 | app := &App{ | |
| 1129 | 1134 | deps: &Dependencies{ | |
| 1130 | 1135 | Config: &config.Config{}, | |
| 1131 | - | SessionStateRepo: &fakeSessionStateRepo{state: state}, | |
| 1136 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 1132 | 1137 | }, | |
| 1133 | 1138 | tabs: entity.NewTabList(), | |
| 1134 | 1139 | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | |
| @@ -1169,7 +1174,7 @@ func TestApp_CreateInitialTabNoFallbackAfterEmptyWindowRestore(t *testing.T) { | |||
| 1169 | 1174 | Config: &config.Config{}, | |
| 1170 | 1175 | RestoreSessionID: string(sessionID), | |
| 1171 | 1176 | InitialURL: "https://fallback.example", | |
| 1172 | - | SessionStateRepo: &fakeSessionStateRepo{state: state}, | |
| 1177 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 1173 | 1178 | }, | |
| 1174 | 1179 | tabs: entity.NewTabList(), | |
| 1175 | 1180 | tabsUC: tabsUC, | |
| @@ -1209,7 +1214,7 @@ func TestApp_RestoreSessionClearsStaleUIStateBeforeApplyingRestoredTabs(t *testi | |||
| 1209 | 1214 | app := &App{ | |
| 1210 | 1215 | deps: &Dependencies{ | |
| 1211 | 1216 | Config: &config.Config{}, | |
| 1212 | - | SessionStateRepo: &fakeSessionStateRepo{state: entity.SnapshotFromTabList(restoredSessionID, restoredTabs)}, | |
| 1217 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, restoredSessionID, entity.SnapshotFromTabList(restoredSessionID, restoredTabs)), | |
| 1213 | 1218 | }, | |
| 1214 | 1219 | mainWindow: mainWindow, | |
| 1215 | 1220 | widgetFactory: layout.NewGtkWidgetFactory(), | |
| @@ -1276,7 +1281,7 @@ func TestApp_RestoreSessionWiresStackedPaneTitleBarCallbacks(t *testing.T) { | |||
| 1276 | 1281 | app := &App{ | |
| 1277 | 1282 | deps: &Dependencies{ | |
| 1278 | 1283 | Config: &config.Config{}, | |
| 1279 | - | SessionStateRepo: &fakeSessionStateRepo{state: entity.SnapshotFromTabList(stackedSessionID, stackedTabs)}, | |
| 1284 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, stackedSessionID, entity.SnapshotFromTabList(stackedSessionID, stackedTabs)), | |
| 1280 | 1285 | }, | |
| 1281 | 1286 | mainWindow: mainWindow, | |
| 1282 | 1287 | widgetFactory: layout.NewGtkWidgetFactory(), | |
| @@ -2615,13 +2620,6 @@ func TestApp_AttachPopupToTabReleasesRegistrationWhenWrapFails(t *testing.T) { | |||
| 2615 | 2620 | } | |
| 2616 | 2621 | } | |
| 2617 | 2622 | ||
| 2618 | - | type fakeZoomRepo struct{} | |
| 2619 | - | ||
| 2620 | - | func (f *fakeZoomRepo) Get(context.Context, string) (*entity.ZoomLevel, error) { return nil, nil } | |
| 2621 | - | func (f *fakeZoomRepo) Set(context.Context, *entity.ZoomLevel) error { return nil } | |
| 2622 | - | func (f *fakeZoomRepo) Delete(context.Context, string) error { return nil } | |
| 2623 | - | func (f *fakeZoomRepo) GetAll(context.Context) ([]*entity.ZoomLevel, error) { return nil, nil } | |
| 2624 | - | ||
| 2625 | 2623 | func TestApp_BrowserWindowWebViewActionsIgnoreStaleFocusedWindow(t *testing.T) { | |
| 2626 | 2624 | ctx := context.Background() | |
| 2627 | 2625 | ||
| @@ -2857,7 +2855,7 @@ func TestApp_DispatchBrowserWindowActionUsesSourceWindow(t *testing.T) { | |||
| 2857 | 2855 | lastFocusedWindowID: first.id, // stale! should NOT be used | |
| 2858 | 2856 | contentCoord: contentCoord, | |
| 2859 | 2857 | navCoord: navCoord, | |
| 2860 | - | deps: &Dependencies{ZoomUC: usecase.NewManageZoomUseCase(&fakeZoomRepo{}, 1.0, nil)}, | |
| 2858 | + | deps: &Dependencies{ZoomUC: usecase.NewManageZoomUseCase(mockZoomRepo(t), 1.0, nil)}, | |
| 2861 | 2859 | workspaceViews: map[entity.TabID]*component.WorkspaceView{ | |
| 2862 | 2860 | tab1.ID: &component.WorkspaceView{}, | |
| 2863 | 2861 | tab2.ID: &component.WorkspaceView{}, | |
| @@ -2907,7 +2905,7 @@ func TestApp_DispatchBrowserWindowActionZoomInSupportsFileURLs(t *testing.T) { | |||
| 2907 | 2905 | tabs: entity.NewTabList(), | |
| 2908 | 2906 | windowForTab: map[entity.TabID]*browserWindow{tab.ID: bw}, | |
| 2909 | 2907 | contentCoord: contentCoord, | |
| 2910 | - | deps: &Dependencies{ZoomUC: usecase.NewManageZoomUseCase(&fakeZoomRepo{}, 1.0, nil)}, | |
| 2908 | + | deps: &Dependencies{ZoomUC: usecase.NewManageZoomUseCase(mockZoomRepo(t), 1.0, nil)}, | |
| 2911 | 2909 | workspaceViews: map[entity.TabID]*component.WorkspaceView{ | |
| 2912 | 2910 | tab.ID: &component.WorkspaceView{}, | |
| 2913 | 2911 | }, | |
| @@ -3266,7 +3264,7 @@ func TestApp_RestoreSessionDoesNotLeakStaleWindowsIntoTabMerge(t *testing.T) { | |||
| 3266 | 3264 | app := &App{ | |
| 3267 | 3265 | deps: &Dependencies{ | |
| 3268 | 3266 | Config: &config.Config{}, | |
| 3269 | - | SessionStateRepo: &fakeSessionStateRepo{state: state}, | |
| 3267 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 3270 | 3268 | }, | |
| 3271 | 3269 | mainWindow: mainWindow, | |
| 3272 | 3270 | browserWindows: map[string]*browserWindow{ | |
| @@ -3346,7 +3344,7 @@ func TestApp_RestoreSessionHonorsActiveWindowIndex(t *testing.T) { | |||
| 3346 | 3344 | app := &App{ | |
| 3347 | 3345 | deps: &Dependencies{ | |
| 3348 | 3346 | Config: &config.Config{}, | |
| 3349 | - | SessionStateRepo: &fakeSessionStateRepo{state: state}, | |
| 3347 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 3350 | 3348 | }, | |
| 3351 | 3349 | mainWindow: mainWindow, | |
| 3352 | 3350 | widgetFactory: layout.NewGtkWidgetFactory(), | |
| @@ -3413,7 +3411,7 @@ func TestApp_RestoreSessionFailsOnAdditionalWindowCreationError(t *testing.T) { | |||
| 3413 | 3411 | app := &App{ | |
| 3414 | 3412 | deps: &Dependencies{ | |
| 3415 | 3413 | Config: &config.Config{}, | |
| 3416 | - | SessionStateRepo: &fakeSessionStateRepo{state: state}, | |
| 3414 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), | |
| 3417 | 3415 | }, | |
| 3418 | 3416 | mainWindow: mainWindow, | |
| 3419 | 3417 | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | |
| @@ -21,7 +21,7 @@ import ( | |||||
| 21 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | 21 | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | ||
| 22 | "github.com/bnema/dumber/internal/application/usecase" | 22 | "github.com/bnema/dumber/internal/application/usecase" | ||
| 23 | "github.com/bnema/dumber/internal/domain/entity" | 23 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 24 | - | "github.com/bnema/dumber/internal/domain/repository" | 24 | + | repomocks "github.com/bnema/dumber/internal/domain/repository/mocks" |
| 25 | "github.com/bnema/dumber/internal/infrastructure/config" | 25 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 26 | "github.com/bnema/dumber/internal/shared/syncdispatch" | 26 | "github.com/bnema/dumber/internal/shared/syncdispatch" | ||
| 27 | "github.com/bnema/dumber/internal/ui/component" | 27 | "github.com/bnema/dumber/internal/ui/component" | ||
| @@ -35,6 +35,7 @@ import ( | |||||
| 35 | "github.com/bnema/puregotk/v4/gdk" | 35 | "github.com/bnema/puregotk/v4/gdk" | ||
| 36 | "github.com/bnema/puregotk/v4/gio" | 36 | "github.com/bnema/puregotk/v4/gio" | ||
| 37 | "github.com/bnema/puregotk/v4/gtk" | 37 | "github.com/bnema/puregotk/v4/gtk" | ||
| 38 | + | "github.com/stretchr/testify/mock" | |||
| 38 | "github.com/stretchr/testify/require" | 39 | "github.com/stretchr/testify/require" | ||
| 39 | ) | 40 | ) | ||
| 40 | 41 | ||||
| @@ -62,29 +63,33 @@ func (c *testCloser) Close() error { | |||||
| 62 | return nil | 63 | return nil | ||
| 63 | } | 64 | } | ||
| 64 | 65 | ||||
| 65 | - | type fakeSessionStateRepo struct { | 66 | + | func testPathIsSocket(path string) bool { |
| 66 | - | state *entity.SessionState | 67 | + | info, err := os.Stat(path) |
| 67 | - | } | 68 | + | return err == nil && info.Mode()&os.ModeSocket != 0 |
| 68 | - | ||||
| 69 | - | var _ repository.SessionStateRepository = (*fakeSessionStateRepo)(nil) | |||
| 70 | - | ||||
| 71 | - | func (r *fakeSessionStateRepo) SaveSnapshot(context.Context, *entity.SessionState) error { return nil } | |||
| 72 | - | ||||
| 73 | - | func (r *fakeSessionStateRepo) GetSnapshot(context.Context, entity.SessionID) (*entity.SessionState, error) { | |||
| 74 | - | return r.state, nil | |||
| 75 | } | 69 | } | ||
| 76 | 70 | ||||
| 77 | - | func (r *fakeSessionStateRepo) DeleteSnapshot(context.Context, entity.SessionID) error { return nil } | 71 | + | func mockSessionStateRepoWithSnapshot(t *testing.T, sessionID entity.SessionID, state *entity.SessionState) *repomocks.MockSessionStateRepository { |
| 78 | - | 72 | + | t.Helper() | |
| 79 | - | func (r *fakeSessionStateRepo) GetAllSnapshots(context.Context) ([]*entity.SessionState, error) { | 73 | + | repo := repomocks.NewMockSessionStateRepository(t) |
| 80 | - | return nil, nil | 74 | + | repo.EXPECT(). |
| 75 | + | GetSnapshot(mock.Anything, sessionID). | |||
| 76 | + | Return(state, nil). | |||
| 77 | + | Once() | |||
| 78 | + | return repo | |||
| 81 | } | 79 | } | ||
| 82 | 80 | ||||
| 83 | - | func (r *fakeSessionStateRepo) GetTotalSnapshotsSize(context.Context) (int64, error) { return 0, nil } | 81 | + | func mockZoomRepo(t *testing.T) *repomocks.MockZoomRepository { |
| 84 | - | 82 | + | t.Helper() | |
| 85 | - | func testPathIsSocket(path string) bool { | 83 | + | repo := repomocks.NewMockZoomRepository(t) |
| 86 | - | info, err := os.Stat(path) | 84 | + | repo.EXPECT(). |
| 87 | - | return err == nil && info.Mode()&os.ModeSocket != 0 | 85 | + | Get(mock.Anything, mock.Anything). |
| 86 | + | Return((*entity.ZoomLevel)(nil), nil). | |||
| 87 | + | Maybe() | |||
| 88 | + | repo.EXPECT(). | |||
| 89 | + | Set(mock.Anything, mock.Anything). | |||
| 90 | + | Return(nil). | |||
| 91 | + | Maybe() | |||
| 92 | + | return repo | |||
| 88 | } | 93 | } | ||
| 89 | 94 | ||||
| 90 | func testGDKBackendAllows(backend string) bool { | 95 | func testGDKBackendAllows(backend string) bool { | ||
| @@ -1128,7 +1133,7 @@ func TestApp_RestoreSessionHandlesEmptyWindowSnapshotAsSuccess(t *testing.T) { | |||||
| 1128 | app := &App{ | 1133 | app := &App{ | ||
| 1129 | deps: &Dependencies{ | 1134 | deps: &Dependencies{ | ||
| 1130 | Config: &config.Config{}, | 1135 | Config: &config.Config{}, | ||
| 1131 | - | SessionStateRepo: &fakeSessionStateRepo{state: state}, | 1136 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), |
| 1132 | }, | 1137 | }, | ||
| 1133 | tabs: entity.NewTabList(), | 1138 | tabs: entity.NewTabList(), | ||
| 1134 | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | 1139 | browserWindows: map[string]*browserWindow{staleWindow.id: staleWindow}, | ||
| @@ -1169,7 +1174,7 @@ func TestApp_CreateInitialTabNoFallbackAfterEmptyWindowRestore(t *testing.T) { | |||||
| 1169 | Config: &config.Config{}, | 1174 | Config: &config.Config{}, | ||
| 1170 | RestoreSessionID: string(sessionID), | 1175 | RestoreSessionID: string(sessionID), | ||
| 1171 | InitialURL: "https://fallback.example", | 1176 | InitialURL: "https://fallback.example", | ||
| 1172 | - | SessionStateRepo: &fakeSessionStateRepo{state: state}, | 1177 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), |
| 1173 | }, | 1178 | }, | ||
| 1174 | tabs: entity.NewTabList(), | 1179 | tabs: entity.NewTabList(), | ||
| 1175 | tabsUC: tabsUC, | 1180 | tabsUC: tabsUC, | ||
| @@ -1209,7 +1214,7 @@ func TestApp_RestoreSessionClearsStaleUIStateBeforeApplyingRestoredTabs(t *testi | |||||
| 1209 | app := &App{ | 1214 | app := &App{ | ||
| 1210 | deps: &Dependencies{ | 1215 | deps: &Dependencies{ | ||
| 1211 | Config: &config.Config{}, | 1216 | Config: &config.Config{}, | ||
| 1212 | - | SessionStateRepo: &fakeSessionStateRepo{state: entity.SnapshotFromTabList(restoredSessionID, restoredTabs)}, | 1217 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, restoredSessionID, entity.SnapshotFromTabList(restoredSessionID, restoredTabs)), |
| 1213 | }, | 1218 | }, | ||
| 1214 | mainWindow: mainWindow, | 1219 | mainWindow: mainWindow, | ||
| 1215 | widgetFactory: layout.NewGtkWidgetFactory(), | 1220 | widgetFactory: layout.NewGtkWidgetFactory(), | ||
| @@ -1276,7 +1281,7 @@ func TestApp_RestoreSessionWiresStackedPaneTitleBarCallbacks(t *testing.T) { | |||||
| 1276 | app := &App{ | 1281 | app := &App{ | ||
| 1277 | deps: &Dependencies{ | 1282 | deps: &Dependencies{ | ||
| 1278 | Config: &config.Config{}, | 1283 | Config: &config.Config{}, | ||
| 1279 | - | SessionStateRepo: &fakeSessionStateRepo{state: entity.SnapshotFromTabList(stackedSessionID, stackedTabs)}, | 1284 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, stackedSessionID, entity.SnapshotFromTabList(stackedSessionID, stackedTabs)), |
| 1280 | }, | 1285 | }, | ||
| 1281 | mainWindow: mainWindow, | 1286 | mainWindow: mainWindow, | ||
| 1282 | widgetFactory: layout.NewGtkWidgetFactory(), | 1287 | widgetFactory: layout.NewGtkWidgetFactory(), | ||
| @@ -2615,13 +2620,6 @@ func TestApp_AttachPopupToTabReleasesRegistrationWhenWrapFails(t *testing.T) { | |||||
| 2615 | } | 2620 | } | ||
| 2616 | } | 2621 | } | ||
| 2617 | 2622 | ||||
| 2618 | - | type fakeZoomRepo struct{} | |||
| 2619 | - | ||||
| 2620 | - | func (f *fakeZoomRepo) Get(context.Context, string) (*entity.ZoomLevel, error) { return nil, nil } | |||
| 2621 | - | func (f *fakeZoomRepo) Set(context.Context, *entity.ZoomLevel) error { return nil } | |||
| 2622 | - | func (f *fakeZoomRepo) Delete(context.Context, string) error { return nil } | |||
| 2623 | - | func (f *fakeZoomRepo) GetAll(context.Context) ([]*entity.ZoomLevel, error) { return nil, nil } | |||
| 2624 | - | ||||
| 2625 | func TestApp_BrowserWindowWebViewActionsIgnoreStaleFocusedWindow(t *testing.T) { | 2623 | func TestApp_BrowserWindowWebViewActionsIgnoreStaleFocusedWindow(t *testing.T) { | ||
| 2626 | ctx := context.Background() | 2624 | ctx := context.Background() | ||
| 2627 | 2625 | ||||
| @@ -2857,7 +2855,7 @@ func TestApp_DispatchBrowserWindowActionUsesSourceWindow(t *testing.T) { | |||||
| 2857 | lastFocusedWindowID: first.id, // stale! should NOT be used | 2855 | lastFocusedWindowID: first.id, // stale! should NOT be used | ||
| 2858 | contentCoord: contentCoord, | 2856 | contentCoord: contentCoord, | ||
| 2859 | navCoord: navCoord, | 2857 | navCoord: navCoord, | ||
| 2860 | - | deps: &Dependencies{ZoomUC: usecase.NewManageZoomUseCase(&fakeZoomRepo{}, 1.0, nil)}, | 2858 | + | deps: &Dependencies{ZoomUC: usecase.NewManageZoomUseCase(mockZoomRepo(t), 1.0, nil)}, |
| 2861 | workspaceViews: map[entity.TabID]*component.WorkspaceView{ | 2859 | workspaceViews: map[entity.TabID]*component.WorkspaceView{ | ||
| 2862 | tab1.ID: &component.WorkspaceView{}, | 2860 | tab1.ID: &component.WorkspaceView{}, | ||
| 2863 | tab2.ID: &component.WorkspaceView{}, | 2861 | tab2.ID: &component.WorkspaceView{}, | ||
| @@ -2907,7 +2905,7 @@ func TestApp_DispatchBrowserWindowActionZoomInSupportsFileURLs(t *testing.T) { | |||||
| 2907 | tabs: entity.NewTabList(), | 2905 | tabs: entity.NewTabList(), | ||
| 2908 | windowForTab: map[entity.TabID]*browserWindow{tab.ID: bw}, | 2906 | windowForTab: map[entity.TabID]*browserWindow{tab.ID: bw}, | ||
| 2909 | contentCoord: contentCoord, | 2907 | contentCoord: contentCoord, | ||
| 2910 | - | deps: &Dependencies{ZoomUC: usecase.NewManageZoomUseCase(&fakeZoomRepo{}, 1.0, nil)}, | 2908 | + | deps: &Dependencies{ZoomUC: usecase.NewManageZoomUseCase(mockZoomRepo(t), 1.0, nil)}, |
| 2911 | workspaceViews: map[entity.TabID]*component.WorkspaceView{ | 2909 | workspaceViews: map[entity.TabID]*component.WorkspaceView{ | ||
| 2912 | tab.ID: &component.WorkspaceView{}, | 2910 | tab.ID: &component.WorkspaceView{}, | ||
| 2913 | }, | 2911 | }, | ||
| @@ -3266,7 +3264,7 @@ func TestApp_RestoreSessionDoesNotLeakStaleWindowsIntoTabMerge(t *testing.T) { | |||||
| 3266 | app := &App{ | 3264 | app := &App{ | ||
| 3267 | deps: &Dependencies{ | 3265 | deps: &Dependencies{ | ||
| 3268 | Config: &config.Config{}, | 3266 | Config: &config.Config{}, | ||
| 3269 | - | SessionStateRepo: &fakeSessionStateRepo{state: state}, | 3267 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), |
| 3270 | }, | 3268 | }, | ||
| 3271 | mainWindow: mainWindow, | 3269 | mainWindow: mainWindow, | ||
| 3272 | browserWindows: map[string]*browserWindow{ | 3270 | browserWindows: map[string]*browserWindow{ | ||
| @@ -3346,7 +3344,7 @@ func TestApp_RestoreSessionHonorsActiveWindowIndex(t *testing.T) { | |||||
| 3346 | app := &App{ | 3344 | app := &App{ | ||
| 3347 | deps: &Dependencies{ | 3345 | deps: &Dependencies{ | ||
| 3348 | Config: &config.Config{}, | 3346 | Config: &config.Config{}, | ||
| 3349 | - | SessionStateRepo: &fakeSessionStateRepo{state: state}, | 3347 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), |
| 3350 | }, | 3348 | }, | ||
| 3351 | mainWindow: mainWindow, | 3349 | mainWindow: mainWindow, | ||
| 3352 | widgetFactory: layout.NewGtkWidgetFactory(), | 3350 | widgetFactory: layout.NewGtkWidgetFactory(), | ||
| @@ -3413,7 +3411,7 @@ func TestApp_RestoreSessionFailsOnAdditionalWindowCreationError(t *testing.T) { | |||||
| 3413 | app := &App{ | 3411 | app := &App{ | ||
| 3414 | deps: &Dependencies{ | 3412 | deps: &Dependencies{ | ||
| 3415 | Config: &config.Config{}, | 3413 | Config: &config.Config{}, | ||
| 3416 | - | SessionStateRepo: &fakeSessionStateRepo{state: state}, | 3414 | + | SessionStateRepo: mockSessionStateRepoWithSnapshot(t, sessionID, state), |
| 3417 | }, | 3415 | }, | ||
| 3418 | mainWindow: mainWindow, | 3416 | mainWindow: mainWindow, | ||
| 3419 | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | 3417 | browserWindows: map[string]*browserWindow{firstBW.id: firstBW}, | ||
M internal/ui/component/history_sidebar_search_test.go +26 -48
| @@ -3,16 +3,17 @@ package component | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "errors" | |
| 6 | - | "fmt" | |
| 7 | 6 | "sync" | |
| 8 | 7 | "testing" | |
| 9 | 8 | "time" | |
| 10 | 9 | ||
| 11 | 10 | "github.com/bnema/dumber/internal/application/dto" | |
| 11 | + | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |
| 12 | 12 | "github.com/bnema/dumber/internal/domain/entity" | |
| 13 | 13 | "github.com/bnema/puregotk/v4/glib" | |
| 14 | 14 | "github.com/rs/zerolog" | |
| 15 | 15 | "github.com/stretchr/testify/assert" | |
| 16 | + | "github.com/stretchr/testify/mock" | |
| 16 | 17 | "github.com/stretchr/testify/require" | |
| 17 | 18 | ) | |
| 18 | 19 | ||
| @@ -155,49 +156,20 @@ func TestApplyDeletedEntryLocked_RecomputesBrowseState(t *testing.T) { | |||
| 155 | 156 | assert.Equal(t, historyDisplayRowEntry, hs.displayRows[2].Kind) | |
| 156 | 157 | } | |
| 157 | 158 | ||
| 158 | - | // ============================================================================= | |
| 159 | - | // Async search seam: controllable history port fake | |
| 160 | - | // ============================================================================= | |
| 161 | - | ||
| 162 | - | type fakeHistorySidebarHistory struct { | |
| 163 | - | getRecentFn func(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error) | |
| 164 | - | searchFn func(ctx context.Context, input dto.HistorySearchInput) (*dto.HistorySearchOutput, error) | |
| 165 | - | deleteFn func(ctx context.Context, id int64) error | |
| 166 | - | } | |
| 167 | - | ||
| 168 | - | func (f *fakeHistorySidebarHistory) GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error) { | |
| 169 | - | if f.getRecentFn != nil { | |
| 170 | - | return f.getRecentFn(ctx, limit, offset) | |
| 171 | - | } | |
| 172 | - | return nil, fmt.Errorf("unexpected GetRecent call in fakeHistorySidebarHistory") | |
| 173 | - | } | |
| 174 | - | ||
| 175 | - | func (f *fakeHistorySidebarHistory) Search(ctx context.Context, input dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |
| 176 | - | if f.searchFn != nil { | |
| 177 | - | return f.searchFn(ctx, input) | |
| 178 | - | } | |
| 179 | - | return &dto.HistorySearchOutput{Matches: []entity.HistoryMatch{}}, nil | |
| 180 | - | } | |
| 181 | - | ||
| 182 | - | func (f *fakeHistorySidebarHistory) Delete(ctx context.Context, id int64) error { | |
| 183 | - | if f.deleteFn != nil { | |
| 184 | - | return f.deleteFn(ctx, id) | |
| 185 | - | } | |
| 186 | - | return nil | |
| 187 | - | } | |
| 188 | - | ||
| 189 | 159 | func TestApplySearchResults_StaleGenerationDropsResultsAfterSearch(t *testing.T) { | |
| 190 | 160 | searchCalled := make(chan struct{}, 1) | |
| 191 | 161 | idleCalled := make(chan glib.SourceFunc, 1) | |
| 192 | 162 | ||
| 193 | - | history := &fakeHistorySidebarHistory{ | |
| 194 | - | searchFn: func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |
| 163 | + | history := portmocks.NewMockHistorySidebarHistory(t) | |
| 164 | + | history.EXPECT(). | |
| 165 | + | Search(mock.Anything, mock.Anything). | |
| 166 | + | RunAndReturn(func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |
| 195 | 167 | searchCalled <- struct{}{} | |
| 196 | 168 | return &dto.HistorySearchOutput{Matches: []entity.HistoryMatch{ | |
| 197 | 169 | {Entry: &entity.HistoryEntry{ID: 1, URL: "https://result.com", Title: "Result", LastVisited: time.Now()}}, | |
| 198 | 170 | }}, nil | |
| 199 | - | }, | |
| 200 | - | } | |
| 171 | + | }). | |
| 172 | + | Once() | |
| 201 | 173 | ||
| 202 | 174 | hs := newTestSidebarSearchHarness() | |
| 203 | 175 | hs.ctx = t.Context() | |
| @@ -238,14 +210,16 @@ func TestApplySearchResults_CurrentGenerationAppliedAfterSearch(t *testing.T) { | |||
| 238 | 210 | searchCalled := make(chan struct{}, 1) | |
| 239 | 211 | idleCalled := make(chan glib.SourceFunc, 1) | |
| 240 | 212 | ||
| 241 | - | history := &fakeHistorySidebarHistory{ | |
| 242 | - | searchFn: func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |
| 213 | + | history := portmocks.NewMockHistorySidebarHistory(t) | |
| 214 | + | history.EXPECT(). | |
| 215 | + | Search(mock.Anything, mock.Anything). | |
| 216 | + | RunAndReturn(func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |
| 243 | 217 | searchCalled <- struct{}{} | |
| 244 | 218 | return &dto.HistorySearchOutput{Matches: []entity.HistoryMatch{ | |
| 245 | 219 | {Entry: &entity.HistoryEntry{ID: 1, URL: "https://live.com", Title: "Live", LastVisited: time.Now()}}, | |
| 246 | 220 | }}, nil | |
| 247 | - | }, | |
| 248 | - | } | |
| 221 | + | }). | |
| 222 | + | Once() | |
| 249 | 223 | ||
| 250 | 224 | hs := newTestSidebarSearchHarness() | |
| 251 | 225 | hs.ctx = t.Context() | |
| @@ -286,12 +260,14 @@ func TestApplySearchResults_CurrentGenerationAppliedAfterSearch(t *testing.T) { | |||
| 286 | 260 | // active query while resetting browse/search state before the refreshed load. | |
| 287 | 261 | func TestHistorySidebar_ReloadPreservesQuery(t *testing.T) { | |
| 288 | 262 | searchCalled := make(chan struct{}, 1) | |
| 289 | - | history := &fakeHistorySidebarHistory{ | |
| 290 | - | searchFn: func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |
| 263 | + | history := portmocks.NewMockHistorySidebarHistory(t) | |
| 264 | + | history.EXPECT(). | |
| 265 | + | Search(mock.Anything, mock.Anything). | |
| 266 | + | RunAndReturn(func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |
| 291 | 267 | searchCalled <- struct{}{} | |
| 292 | 268 | return &dto.HistorySearchOutput{Matches: []entity.HistoryMatch{}}, nil | |
| 293 | - | }, | |
| 294 | - | } | |
| 269 | + | }). | |
| 270 | + | Once() | |
| 295 | 271 | ||
| 296 | 272 | hs := newTestSidebarSearchHarness() | |
| 297 | 273 | hs.currentQuery = "preserved" | |
| @@ -341,13 +317,15 @@ func TestFetchPage_StaleGenerationDoesNotMutateLoadingState(t *testing.T) { | |||
| 341 | 317 | getRecentCalled := make(chan struct{}) | |
| 342 | 318 | proceed := make(chan struct{}) | |
| 343 | 319 | ||
| 344 | - | history := &fakeHistorySidebarHistory{ | |
| 345 | - | getRecentFn: func(context.Context, int, int) ([]*entity.HistoryEntry, error) { | |
| 320 | + | history := portmocks.NewMockHistorySidebarHistory(t) | |
| 321 | + | history.EXPECT(). | |
| 322 | + | GetRecent(mock.Anything, sidebarPageSize, 0). | |
| 323 | + | RunAndReturn(func(context.Context, int, int) ([]*entity.HistoryEntry, error) { | |
| 346 | 324 | close(getRecentCalled) | |
| 347 | 325 | <-proceed | |
| 348 | 326 | return []*entity.HistoryEntry{}, nil | |
| 349 | - | }, | |
| 350 | - | } | |
| 327 | + | }). | |
| 328 | + | Once() | |
| 351 | 329 | ||
| 352 | 330 | hs := newTestSidebarSearchHarness() | |
| 353 | 331 | hs.ctx = t.Context() | |
| @@ -3,16 +3,17 @@ package component | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "errors" | 5 | "errors" | ||
| 6 | - | "fmt" | |||
| 7 | "sync" | 6 | "sync" | ||
| 8 | "testing" | 7 | "testing" | ||
| 9 | "time" | 8 | "time" | ||
| 10 | 9 | ||||
| 11 | "github.com/bnema/dumber/internal/application/dto" | 10 | "github.com/bnema/dumber/internal/application/dto" | ||
| 11 | + | portmocks "github.com/bnema/dumber/internal/application/port/mocks" | |||
| 12 | "github.com/bnema/dumber/internal/domain/entity" | 12 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 13 | "github.com/bnema/puregotk/v4/glib" | 13 | "github.com/bnema/puregotk/v4/glib" | ||
| 14 | "github.com/rs/zerolog" | 14 | "github.com/rs/zerolog" | ||
| 15 | "github.com/stretchr/testify/assert" | 15 | "github.com/stretchr/testify/assert" | ||
| 16 | + | "github.com/stretchr/testify/mock" | |||
| 16 | "github.com/stretchr/testify/require" | 17 | "github.com/stretchr/testify/require" | ||
| 17 | ) | 18 | ) | ||
| 18 | 19 | ||||
| @@ -155,49 +156,20 @@ func TestApplyDeletedEntryLocked_RecomputesBrowseState(t *testing.T) { | |||||
| 155 | assert.Equal(t, historyDisplayRowEntry, hs.displayRows[2].Kind) | 156 | assert.Equal(t, historyDisplayRowEntry, hs.displayRows[2].Kind) | ||
| 156 | } | 157 | } | ||
| 157 | 158 | ||||
| 158 | - | // ============================================================================= | |||
| 159 | - | // Async search seam: controllable history port fake | |||
| 160 | - | // ============================================================================= | |||
| 161 | - | ||||
| 162 | - | type fakeHistorySidebarHistory struct { | |||
| 163 | - | getRecentFn func(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error) | |||
| 164 | - | searchFn func(ctx context.Context, input dto.HistorySearchInput) (*dto.HistorySearchOutput, error) | |||
| 165 | - | deleteFn func(ctx context.Context, id int64) error | |||
| 166 | - | } | |||
| 167 | - | ||||
| 168 | - | func (f *fakeHistorySidebarHistory) GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error) { | |||
| 169 | - | if f.getRecentFn != nil { | |||
| 170 | - | return f.getRecentFn(ctx, limit, offset) | |||
| 171 | - | } | |||
| 172 | - | return nil, fmt.Errorf("unexpected GetRecent call in fakeHistorySidebarHistory") | |||
| 173 | - | } | |||
| 174 | - | ||||
| 175 | - | func (f *fakeHistorySidebarHistory) Search(ctx context.Context, input dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |||
| 176 | - | if f.searchFn != nil { | |||
| 177 | - | return f.searchFn(ctx, input) | |||
| 178 | - | } | |||
| 179 | - | return &dto.HistorySearchOutput{Matches: []entity.HistoryMatch{}}, nil | |||
| 180 | - | } | |||
| 181 | - | ||||
| 182 | - | func (f *fakeHistorySidebarHistory) Delete(ctx context.Context, id int64) error { | |||
| 183 | - | if f.deleteFn != nil { | |||
| 184 | - | return f.deleteFn(ctx, id) | |||
| 185 | - | } | |||
| 186 | - | return nil | |||
| 187 | - | } | |||
| 188 | - | ||||
| 189 | func TestApplySearchResults_StaleGenerationDropsResultsAfterSearch(t *testing.T) { | 159 | func TestApplySearchResults_StaleGenerationDropsResultsAfterSearch(t *testing.T) { | ||
| 190 | searchCalled := make(chan struct{}, 1) | 160 | searchCalled := make(chan struct{}, 1) | ||
| 191 | idleCalled := make(chan glib.SourceFunc, 1) | 161 | idleCalled := make(chan glib.SourceFunc, 1) | ||
| 192 | 162 | ||||
| 193 | - | history := &fakeHistorySidebarHistory{ | 163 | + | history := portmocks.NewMockHistorySidebarHistory(t) |
| 194 | - | searchFn: func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | 164 | + | history.EXPECT(). |
| 165 | + | Search(mock.Anything, mock.Anything). | |||
| 166 | + | RunAndReturn(func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |||
| 195 | searchCalled <- struct{}{} | 167 | searchCalled <- struct{}{} | ||
| 196 | return &dto.HistorySearchOutput{Matches: []entity.HistoryMatch{ | 168 | return &dto.HistorySearchOutput{Matches: []entity.HistoryMatch{ | ||
| 197 | {Entry: &entity.HistoryEntry{ID: 1, URL: "https://result.com", Title: "Result", LastVisited: time.Now()}}, | 169 | {Entry: &entity.HistoryEntry{ID: 1, URL: "https://result.com", Title: "Result", LastVisited: time.Now()}}, | ||
| 198 | }}, nil | 170 | }}, nil | ||
| 199 | - | }, | 171 | + | }). |
| 200 | - | } | 172 | + | Once() |
| 201 | 173 | ||||
| 202 | hs := newTestSidebarSearchHarness() | 174 | hs := newTestSidebarSearchHarness() | ||
| 203 | hs.ctx = t.Context() | 175 | hs.ctx = t.Context() | ||
| @@ -238,14 +210,16 @@ func TestApplySearchResults_CurrentGenerationAppliedAfterSearch(t *testing.T) { | |||||
| 238 | searchCalled := make(chan struct{}, 1) | 210 | searchCalled := make(chan struct{}, 1) | ||
| 239 | idleCalled := make(chan glib.SourceFunc, 1) | 211 | idleCalled := make(chan glib.SourceFunc, 1) | ||
| 240 | 212 | ||||
| 241 | - | history := &fakeHistorySidebarHistory{ | 213 | + | history := portmocks.NewMockHistorySidebarHistory(t) |
| 242 | - | searchFn: func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | 214 | + | history.EXPECT(). |
| 215 | + | Search(mock.Anything, mock.Anything). | |||
| 216 | + | RunAndReturn(func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |||
| 243 | searchCalled <- struct{}{} | 217 | searchCalled <- struct{}{} | ||
| 244 | return &dto.HistorySearchOutput{Matches: []entity.HistoryMatch{ | 218 | return &dto.HistorySearchOutput{Matches: []entity.HistoryMatch{ | ||
| 245 | {Entry: &entity.HistoryEntry{ID: 1, URL: "https://live.com", Title: "Live", LastVisited: time.Now()}}, | 219 | {Entry: &entity.HistoryEntry{ID: 1, URL: "https://live.com", Title: "Live", LastVisited: time.Now()}}, | ||
| 246 | }}, nil | 220 | }}, nil | ||
| 247 | - | }, | 221 | + | }). |
| 248 | - | } | 222 | + | Once() |
| 249 | 223 | ||||
| 250 | hs := newTestSidebarSearchHarness() | 224 | hs := newTestSidebarSearchHarness() | ||
| 251 | hs.ctx = t.Context() | 225 | hs.ctx = t.Context() | ||
| @@ -286,12 +260,14 @@ func TestApplySearchResults_CurrentGenerationAppliedAfterSearch(t *testing.T) { | |||||
| 286 | // active query while resetting browse/search state before the refreshed load. | 260 | // active query while resetting browse/search state before the refreshed load. | ||
| 287 | func TestHistorySidebar_ReloadPreservesQuery(t *testing.T) { | 261 | func TestHistorySidebar_ReloadPreservesQuery(t *testing.T) { | ||
| 288 | searchCalled := make(chan struct{}, 1) | 262 | searchCalled := make(chan struct{}, 1) | ||
| 289 | - | history := &fakeHistorySidebarHistory{ | 263 | + | history := portmocks.NewMockHistorySidebarHistory(t) |
| 290 | - | searchFn: func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | 264 | + | history.EXPECT(). |
| 265 | + | Search(mock.Anything, mock.Anything). | |||
| 266 | + | RunAndReturn(func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |||
| 291 | searchCalled <- struct{}{} | 267 | searchCalled <- struct{}{} | ||
| 292 | return &dto.HistorySearchOutput{Matches: []entity.HistoryMatch{}}, nil | 268 | return &dto.HistorySearchOutput{Matches: []entity.HistoryMatch{}}, nil | ||
| 293 | - | }, | 269 | + | }). |
| 294 | - | } | 270 | + | Once() |
| 295 | 271 | ||||
| 296 | hs := newTestSidebarSearchHarness() | 272 | hs := newTestSidebarSearchHarness() | ||
| 297 | hs.currentQuery = "preserved" | 273 | hs.currentQuery = "preserved" | ||
| @@ -341,13 +317,15 @@ func TestFetchPage_StaleGenerationDoesNotMutateLoadingState(t *testing.T) { | |||||
| 341 | getRecentCalled := make(chan struct{}) | 317 | getRecentCalled := make(chan struct{}) | ||
| 342 | proceed := make(chan struct{}) | 318 | proceed := make(chan struct{}) | ||
| 343 | 319 | ||||
| 344 | - | history := &fakeHistorySidebarHistory{ | 320 | + | history := portmocks.NewMockHistorySidebarHistory(t) |
| 345 | - | getRecentFn: func(context.Context, int, int) ([]*entity.HistoryEntry, error) { | 321 | + | history.EXPECT(). |
| 322 | + | GetRecent(mock.Anything, sidebarPageSize, 0). | |||
| 323 | + | RunAndReturn(func(context.Context, int, int) ([]*entity.HistoryEntry, error) { | |||
| 346 | close(getRecentCalled) | 324 | close(getRecentCalled) | ||
| 347 | <-proceed | 325 | <-proceed | ||
| 348 | return []*entity.HistoryEntry{}, nil | 326 | return []*entity.HistoryEntry{}, nil | ||
| 349 | - | }, | 327 | + | }). |
| 350 | - | } | 328 | + | Once() |
| 351 | 329 | ||||
| 352 | hs := newTestSidebarSearchHarness() | 330 | hs := newTestSidebarSearchHarness() | ||
| 353 | hs.ctx = t.Context() | 331 | hs.ctx = t.Context() | ||