Add unit tests for the browser/pane pure geometrical/binary tree logic with the deletion, hierarchy, siblingds, reatributions of root etc (vibe-kanban 2223cb34)
- commit
- c736f06460f101dc78a23ab68cfbd8d350533d46
- author
- bamen · · 2025-09-19 04:08 UTC
- committer
- bamen · · 2025-09-19 04:26 UTC
- parent
- bd90d73
- signature
- unsigned
9 files · +1992 -16
M internal/app/browser/browser.go +1 -1
| @@ -182,7 +182,7 @@ func (app *BrowserApp) cleanup() { | |||
| 182 | 182 | ||
| 183 | 183 | // Cleanup window shortcuts first | |
| 184 | 184 | if app.windowShortcutHandler != nil { | |
| 185 | - | log.Printf("Cleaning up window-level shortcuts") | |
| 185 | + | log.Printf("Cleaning up window shortcuts") | |
| 186 | 186 | app.windowShortcutHandler.Cleanup() | |
| 187 | 187 | app.windowShortcutHandler = nil | |
| 188 | 188 | } | |
| @@ -182,7 +182,7 @@ func (app *BrowserApp) cleanup() { | |||||
| 182 | 182 | ||||
| 183 | // Cleanup window shortcuts first | 183 | // Cleanup window shortcuts first | ||
| 184 | if app.windowShortcutHandler != nil { | 184 | if app.windowShortcutHandler != nil { | ||
| 185 | - | log.Printf("Cleaning up window-level shortcuts") | 185 | + | log.Printf("Cleaning up window shortcuts") |
| 186 | app.windowShortcutHandler.Cleanup() | 186 | app.windowShortcutHandler.Cleanup() | ||
| 187 | app.windowShortcutHandler = nil | 187 | app.windowShortcutHandler = nil | ||
| 188 | } | 188 | } | ||
A internal/app/browser/interfaces.go +83 -0
| @@ -0,0 +1,83 @@ | |||
| 1 | + | package browser | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "github.com/bnema/dumber/internal/app/messaging" | |
| 5 | + | "github.com/bnema/dumber/pkg/webkit" | |
| 6 | + | ) | |
| 7 | + | ||
| 8 | + | //go:generate mockgen -source=interfaces.go -destination=mocks/mock_browser.go -package=mock_browser | |
| 9 | + | ||
| 10 | + | // WebViewInterface abstracts WebKit WebView for testing | |
| 11 | + | type WebViewInterface interface { | |
| 12 | + | LoadURL(url string) error | |
| 13 | + | Show() error | |
| 14 | + | Hide() error | |
| 15 | + | Destroy() error | |
| 16 | + | Window() *webkit.Window | |
| 17 | + | GetCurrentURL() string | |
| 18 | + | Widget() uintptr | |
| 19 | + | RootWidget() uintptr | |
| 20 | + | InjectScript(script string) error | |
| 21 | + | DispatchCustomEvent(event string, detail map[string]any) error | |
| 22 | + | RegisterKeyboardShortcut(key string, handler func()) error | |
| 23 | + | RegisterURIChangedHandler(handler func(string)) | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | // PaneInterface abstracts BrowserPane for testing | |
| 27 | + | type PaneInterface interface { | |
| 28 | + | WebView() WebViewInterface | |
| 29 | + | MessageHandler() MessageHandlerInterface | |
| 30 | + | NavigationController() NavigationControllerInterface | |
| 31 | + | ZoomController() ZoomControllerInterface | |
| 32 | + | ClipboardController() ClipboardControllerInterface | |
| 33 | + | ShortcutHandler() ShortcutHandlerInterface | |
| 34 | + | ID() string | |
| 35 | + | HasGUI() bool | |
| 36 | + | SetHasGUI(bool) | |
| 37 | + | SetID(string) | |
| 38 | + | UpdateLastFocus() | |
| 39 | + | HasGUIComponent(component string) bool | |
| 40 | + | SetGUIComponent(component string, loaded bool) | |
| 41 | + | Cleanup() | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | // MessageHandlerInterface abstracts messaging.Handler | |
| 45 | + | type MessageHandlerInterface interface { | |
| 46 | + | SetWorkspaceObserver(observer messaging.WorkspaceObserver) | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | // NavigationControllerInterface abstracts navigation operations | |
| 50 | + | type NavigationControllerInterface interface { | |
| 51 | + | NavigateToURL(url string) error | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | // ZoomControllerInterface abstracts zoom operations | |
| 55 | + | type ZoomControllerInterface interface { | |
| 56 | + | ApplyInitialZoom() | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | // ClipboardControllerInterface abstracts clipboard operations | |
| 60 | + | type ClipboardControllerInterface interface { | |
| 61 | + | // Add methods as needed | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | // ShortcutHandlerInterface abstracts shortcut handling | |
| 65 | + | type ShortcutHandlerInterface interface { | |
| 66 | + | // Add methods as needed | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | // BrowserAppInterface abstracts BrowserApp for testing | |
| 70 | + | type BrowserAppInterface interface { | |
| 71 | + | GetPanes() []*BrowserPane | |
| 72 | + | SetActivePane(pane *BrowserPane) | |
| 73 | + | GetActivePane() *BrowserPane | |
| 74 | + | AppendPane(pane *BrowserPane) | |
| 75 | + | RemovePane(pane *BrowserPane) | |
| 76 | + | BuildWebkitConfig() (*webkit.Config, error) | |
| 77 | + | CreatePaneForView(view *webkit.WebView) (*BrowserPane, error) | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | // WindowInterface abstracts webkit.Window for testing | |
| 81 | + | type WindowInterface interface { | |
| 82 | + | SetChild(widget uintptr) | |
| 83 | + | } | |
| @@ -0,0 +1,83 @@ | |||||
| 1 | + | package browser | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "github.com/bnema/dumber/internal/app/messaging" | |||
| 5 | + | "github.com/bnema/dumber/pkg/webkit" | |||
| 6 | + | ) | |||
| 7 | + | ||||
| 8 | + | //go:generate mockgen -source=interfaces.go -destination=mocks/mock_browser.go -package=mock_browser | |||
| 9 | + | ||||
| 10 | + | // WebViewInterface abstracts WebKit WebView for testing | |||
| 11 | + | type WebViewInterface interface { | |||
| 12 | + | LoadURL(url string) error | |||
| 13 | + | Show() error | |||
| 14 | + | Hide() error | |||
| 15 | + | Destroy() error | |||
| 16 | + | Window() *webkit.Window | |||
| 17 | + | GetCurrentURL() string | |||
| 18 | + | Widget() uintptr | |||
| 19 | + | RootWidget() uintptr | |||
| 20 | + | InjectScript(script string) error | |||
| 21 | + | DispatchCustomEvent(event string, detail map[string]any) error | |||
| 22 | + | RegisterKeyboardShortcut(key string, handler func()) error | |||
| 23 | + | RegisterURIChangedHandler(handler func(string)) | |||
| 24 | + | } | |||
| 25 | + | ||||
| 26 | + | // PaneInterface abstracts BrowserPane for testing | |||
| 27 | + | type PaneInterface interface { | |||
| 28 | + | WebView() WebViewInterface | |||
| 29 | + | MessageHandler() MessageHandlerInterface | |||
| 30 | + | NavigationController() NavigationControllerInterface | |||
| 31 | + | ZoomController() ZoomControllerInterface | |||
| 32 | + | ClipboardController() ClipboardControllerInterface | |||
| 33 | + | ShortcutHandler() ShortcutHandlerInterface | |||
| 34 | + | ID() string | |||
| 35 | + | HasGUI() bool | |||
| 36 | + | SetHasGUI(bool) | |||
| 37 | + | SetID(string) | |||
| 38 | + | UpdateLastFocus() | |||
| 39 | + | HasGUIComponent(component string) bool | |||
| 40 | + | SetGUIComponent(component string, loaded bool) | |||
| 41 | + | Cleanup() | |||
| 42 | + | } | |||
| 43 | + | ||||
| 44 | + | // MessageHandlerInterface abstracts messaging.Handler | |||
| 45 | + | type MessageHandlerInterface interface { | |||
| 46 | + | SetWorkspaceObserver(observer messaging.WorkspaceObserver) | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | // NavigationControllerInterface abstracts navigation operations | |||
| 50 | + | type NavigationControllerInterface interface { | |||
| 51 | + | NavigateToURL(url string) error | |||
| 52 | + | } | |||
| 53 | + | ||||
| 54 | + | // ZoomControllerInterface abstracts zoom operations | |||
| 55 | + | type ZoomControllerInterface interface { | |||
| 56 | + | ApplyInitialZoom() | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | // ClipboardControllerInterface abstracts clipboard operations | |||
| 60 | + | type ClipboardControllerInterface interface { | |||
| 61 | + | // Add methods as needed | |||
| 62 | + | } | |||
| 63 | + | ||||
| 64 | + | // ShortcutHandlerInterface abstracts shortcut handling | |||
| 65 | + | type ShortcutHandlerInterface interface { | |||
| 66 | + | // Add methods as needed | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | // BrowserAppInterface abstracts BrowserApp for testing | |||
| 70 | + | type BrowserAppInterface interface { | |||
| 71 | + | GetPanes() []*BrowserPane | |||
| 72 | + | SetActivePane(pane *BrowserPane) | |||
| 73 | + | GetActivePane() *BrowserPane | |||
| 74 | + | AppendPane(pane *BrowserPane) | |||
| 75 | + | RemovePane(pane *BrowserPane) | |||
| 76 | + | BuildWebkitConfig() (*webkit.Config, error) | |||
| 77 | + | CreatePaneForView(view *webkit.WebView) (*BrowserPane, error) | |||
| 78 | + | } | |||
| 79 | + | ||||
| 80 | + | // WindowInterface abstracts webkit.Window for testing | |||
| 81 | + | type WindowInterface interface { | |||
| 82 | + | SetChild(widget uintptr) | |||
| 83 | + | } | |||
A internal/app/browser/mocks/mock_browser.go +717 -0
| @@ -0,0 +1,717 @@ | |||
| 1 | + | // Code generated by MockGen. DO NOT EDIT. | |
| 2 | + | // Source: interfaces.go | |
| 3 | + | ||
| 4 | + | // Package mock_browser is a generated GoMock package. | |
| 5 | + | package mock_browser | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | reflect "reflect" | |
| 9 | + | ||
| 10 | + | browser "github.com/bnema/dumber/internal/app/browser" | |
| 11 | + | messaging "github.com/bnema/dumber/internal/app/messaging" | |
| 12 | + | webkit "github.com/bnema/dumber/pkg/webkit" | |
| 13 | + | gomock "github.com/golang/mock/gomock" | |
| 14 | + | ) | |
| 15 | + | ||
| 16 | + | // MockWebViewInterface is a mock of WebViewInterface interface. | |
| 17 | + | type MockWebViewInterface struct { | |
| 18 | + | ctrl *gomock.Controller | |
| 19 | + | recorder *MockWebViewInterfaceMockRecorder | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | // MockWebViewInterfaceMockRecorder is the mock recorder for MockWebViewInterface. | |
| 23 | + | type MockWebViewInterfaceMockRecorder struct { | |
| 24 | + | mock *MockWebViewInterface | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | // NewMockWebViewInterface creates a new mock instance. | |
| 28 | + | func NewMockWebViewInterface(ctrl *gomock.Controller) *MockWebViewInterface { | |
| 29 | + | mock := &MockWebViewInterface{ctrl: ctrl} | |
| 30 | + | mock.recorder = &MockWebViewInterfaceMockRecorder{mock} | |
| 31 | + | return mock | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |
| 35 | + | func (m *MockWebViewInterface) EXPECT() *MockWebViewInterfaceMockRecorder { | |
| 36 | + | return m.recorder | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | // Destroy mocks base method. | |
| 40 | + | func (m *MockWebViewInterface) Destroy() error { | |
| 41 | + | m.ctrl.T.Helper() | |
| 42 | + | ret := m.ctrl.Call(m, "Destroy") | |
| 43 | + | ret0, _ := ret[0].(error) | |
| 44 | + | return ret0 | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | // Destroy indicates an expected call of Destroy. | |
| 48 | + | func (mr *MockWebViewInterfaceMockRecorder) Destroy() *gomock.Call { | |
| 49 | + | mr.mock.ctrl.T.Helper() | |
| 50 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockWebViewInterface)(nil).Destroy)) | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | // DispatchCustomEvent mocks base method. | |
| 54 | + | func (m *MockWebViewInterface) DispatchCustomEvent(event string, detail map[string]any) error { | |
| 55 | + | m.ctrl.T.Helper() | |
| 56 | + | ret := m.ctrl.Call(m, "DispatchCustomEvent", event, detail) | |
| 57 | + | ret0, _ := ret[0].(error) | |
| 58 | + | return ret0 | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | // DispatchCustomEvent indicates an expected call of DispatchCustomEvent. | |
| 62 | + | func (mr *MockWebViewInterfaceMockRecorder) DispatchCustomEvent(event, detail interface{}) *gomock.Call { | |
| 63 | + | mr.mock.ctrl.T.Helper() | |
| 64 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DispatchCustomEvent", reflect.TypeOf((*MockWebViewInterface)(nil).DispatchCustomEvent), event, detail) | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | // GetCurrentURL mocks base method. | |
| 68 | + | func (m *MockWebViewInterface) GetCurrentURL() string { | |
| 69 | + | m.ctrl.T.Helper() | |
| 70 | + | ret := m.ctrl.Call(m, "GetCurrentURL") | |
| 71 | + | ret0, _ := ret[0].(string) | |
| 72 | + | return ret0 | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | // GetCurrentURL indicates an expected call of GetCurrentURL. | |
| 76 | + | func (mr *MockWebViewInterfaceMockRecorder) GetCurrentURL() *gomock.Call { | |
| 77 | + | mr.mock.ctrl.T.Helper() | |
| 78 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentURL", reflect.TypeOf((*MockWebViewInterface)(nil).GetCurrentURL)) | |
| 79 | + | } | |
| 80 | + | ||
| 81 | + | // Hide mocks base method. | |
| 82 | + | func (m *MockWebViewInterface) Hide() error { | |
| 83 | + | m.ctrl.T.Helper() | |
| 84 | + | ret := m.ctrl.Call(m, "Hide") | |
| 85 | + | ret0, _ := ret[0].(error) | |
| 86 | + | return ret0 | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | // Hide indicates an expected call of Hide. | |
| 90 | + | func (mr *MockWebViewInterfaceMockRecorder) Hide() *gomock.Call { | |
| 91 | + | mr.mock.ctrl.T.Helper() | |
| 92 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hide", reflect.TypeOf((*MockWebViewInterface)(nil).Hide)) | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | // InjectScript mocks base method. | |
| 96 | + | func (m *MockWebViewInterface) InjectScript(script string) error { | |
| 97 | + | m.ctrl.T.Helper() | |
| 98 | + | ret := m.ctrl.Call(m, "InjectScript", script) | |
| 99 | + | ret0, _ := ret[0].(error) | |
| 100 | + | return ret0 | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | // InjectScript indicates an expected call of InjectScript. | |
| 104 | + | func (mr *MockWebViewInterfaceMockRecorder) InjectScript(script interface{}) *gomock.Call { | |
| 105 | + | mr.mock.ctrl.T.Helper() | |
| 106 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InjectScript", reflect.TypeOf((*MockWebViewInterface)(nil).InjectScript), script) | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | // LoadURL mocks base method. | |
| 110 | + | func (m *MockWebViewInterface) LoadURL(url string) error { | |
| 111 | + | m.ctrl.T.Helper() | |
| 112 | + | ret := m.ctrl.Call(m, "LoadURL", url) | |
| 113 | + | ret0, _ := ret[0].(error) | |
| 114 | + | return ret0 | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | // LoadURL indicates an expected call of LoadURL. | |
| 118 | + | func (mr *MockWebViewInterfaceMockRecorder) LoadURL(url interface{}) *gomock.Call { | |
| 119 | + | mr.mock.ctrl.T.Helper() | |
| 120 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadURL", reflect.TypeOf((*MockWebViewInterface)(nil).LoadURL), url) | |
| 121 | + | } | |
| 122 | + | ||
| 123 | + | // RegisterKeyboardShortcut mocks base method. | |
| 124 | + | func (m *MockWebViewInterface) RegisterKeyboardShortcut(key string, handler func()) error { | |
| 125 | + | m.ctrl.T.Helper() | |
| 126 | + | ret := m.ctrl.Call(m, "RegisterKeyboardShortcut", key, handler) | |
| 127 | + | ret0, _ := ret[0].(error) | |
| 128 | + | return ret0 | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | // RegisterKeyboardShortcut indicates an expected call of RegisterKeyboardShortcut. | |
| 132 | + | func (mr *MockWebViewInterfaceMockRecorder) RegisterKeyboardShortcut(key, handler interface{}) *gomock.Call { | |
| 133 | + | mr.mock.ctrl.T.Helper() | |
| 134 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterKeyboardShortcut", reflect.TypeOf((*MockWebViewInterface)(nil).RegisterKeyboardShortcut), key, handler) | |
| 135 | + | } | |
| 136 | + | ||
| 137 | + | // RegisterURIChangedHandler mocks base method. | |
| 138 | + | func (m *MockWebViewInterface) RegisterURIChangedHandler(handler func(string)) { | |
| 139 | + | m.ctrl.T.Helper() | |
| 140 | + | m.ctrl.Call(m, "RegisterURIChangedHandler", handler) | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | // RegisterURIChangedHandler indicates an expected call of RegisterURIChangedHandler. | |
| 144 | + | func (mr *MockWebViewInterfaceMockRecorder) RegisterURIChangedHandler(handler interface{}) *gomock.Call { | |
| 145 | + | mr.mock.ctrl.T.Helper() | |
| 146 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterURIChangedHandler", reflect.TypeOf((*MockWebViewInterface)(nil).RegisterURIChangedHandler), handler) | |
| 147 | + | } | |
| 148 | + | ||
| 149 | + | // RootWidget mocks base method. | |
| 150 | + | func (m *MockWebViewInterface) RootWidget() uintptr { | |
| 151 | + | m.ctrl.T.Helper() | |
| 152 | + | ret := m.ctrl.Call(m, "RootWidget") | |
| 153 | + | ret0, _ := ret[0].(uintptr) | |
| 154 | + | return ret0 | |
| 155 | + | } | |
| 156 | + | ||
| 157 | + | // RootWidget indicates an expected call of RootWidget. | |
| 158 | + | func (mr *MockWebViewInterfaceMockRecorder) RootWidget() *gomock.Call { | |
| 159 | + | mr.mock.ctrl.T.Helper() | |
| 160 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RootWidget", reflect.TypeOf((*MockWebViewInterface)(nil).RootWidget)) | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | // Show mocks base method. | |
| 164 | + | func (m *MockWebViewInterface) Show() error { | |
| 165 | + | m.ctrl.T.Helper() | |
| 166 | + | ret := m.ctrl.Call(m, "Show") | |
| 167 | + | ret0, _ := ret[0].(error) | |
| 168 | + | return ret0 | |
| 169 | + | } | |
| 170 | + | ||
| 171 | + | // Show indicates an expected call of Show. | |
| 172 | + | func (mr *MockWebViewInterfaceMockRecorder) Show() *gomock.Call { | |
| 173 | + | mr.mock.ctrl.T.Helper() | |
| 174 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Show", reflect.TypeOf((*MockWebViewInterface)(nil).Show)) | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | // Widget mocks base method. | |
| 178 | + | func (m *MockWebViewInterface) Widget() uintptr { | |
| 179 | + | m.ctrl.T.Helper() | |
| 180 | + | ret := m.ctrl.Call(m, "Widget") | |
| 181 | + | ret0, _ := ret[0].(uintptr) | |
| 182 | + | return ret0 | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | // Widget indicates an expected call of Widget. | |
| 186 | + | func (mr *MockWebViewInterfaceMockRecorder) Widget() *gomock.Call { | |
| 187 | + | mr.mock.ctrl.T.Helper() | |
| 188 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Widget", reflect.TypeOf((*MockWebViewInterface)(nil).Widget)) | |
| 189 | + | } | |
| 190 | + | ||
| 191 | + | // Window mocks base method. | |
| 192 | + | func (m *MockWebViewInterface) Window() *webkit.Window { | |
| 193 | + | m.ctrl.T.Helper() | |
| 194 | + | ret := m.ctrl.Call(m, "Window") | |
| 195 | + | ret0, _ := ret[0].(*webkit.Window) | |
| 196 | + | return ret0 | |
| 197 | + | } | |
| 198 | + | ||
| 199 | + | // Window indicates an expected call of Window. | |
| 200 | + | func (mr *MockWebViewInterfaceMockRecorder) Window() *gomock.Call { | |
| 201 | + | mr.mock.ctrl.T.Helper() | |
| 202 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Window", reflect.TypeOf((*MockWebViewInterface)(nil).Window)) | |
| 203 | + | } | |
| 204 | + | ||
| 205 | + | // MockPaneInterface is a mock of PaneInterface interface. | |
| 206 | + | type MockPaneInterface struct { | |
| 207 | + | ctrl *gomock.Controller | |
| 208 | + | recorder *MockPaneInterfaceMockRecorder | |
| 209 | + | } | |
| 210 | + | ||
| 211 | + | // MockPaneInterfaceMockRecorder is the mock recorder for MockPaneInterface. | |
| 212 | + | type MockPaneInterfaceMockRecorder struct { | |
| 213 | + | mock *MockPaneInterface | |
| 214 | + | } | |
| 215 | + | ||
| 216 | + | // NewMockPaneInterface creates a new mock instance. | |
| 217 | + | func NewMockPaneInterface(ctrl *gomock.Controller) *MockPaneInterface { | |
| 218 | + | mock := &MockPaneInterface{ctrl: ctrl} | |
| 219 | + | mock.recorder = &MockPaneInterfaceMockRecorder{mock} | |
| 220 | + | return mock | |
| 221 | + | } | |
| 222 | + | ||
| 223 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |
| 224 | + | func (m *MockPaneInterface) EXPECT() *MockPaneInterfaceMockRecorder { | |
| 225 | + | return m.recorder | |
| 226 | + | } | |
| 227 | + | ||
| 228 | + | // Cleanup mocks base method. | |
| 229 | + | func (m *MockPaneInterface) Cleanup() { | |
| 230 | + | m.ctrl.T.Helper() | |
| 231 | + | m.ctrl.Call(m, "Cleanup") | |
| 232 | + | } | |
| 233 | + | ||
| 234 | + | // Cleanup indicates an expected call of Cleanup. | |
| 235 | + | func (mr *MockPaneInterfaceMockRecorder) Cleanup() *gomock.Call { | |
| 236 | + | mr.mock.ctrl.T.Helper() | |
| 237 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cleanup", reflect.TypeOf((*MockPaneInterface)(nil).Cleanup)) | |
| 238 | + | } | |
| 239 | + | ||
| 240 | + | // ClipboardController mocks base method. | |
| 241 | + | func (m *MockPaneInterface) ClipboardController() browser.ClipboardControllerInterface { | |
| 242 | + | m.ctrl.T.Helper() | |
| 243 | + | ret := m.ctrl.Call(m, "ClipboardController") | |
| 244 | + | ret0, _ := ret[0].(browser.ClipboardControllerInterface) | |
| 245 | + | return ret0 | |
| 246 | + | } | |
| 247 | + | ||
| 248 | + | // ClipboardController indicates an expected call of ClipboardController. | |
| 249 | + | func (mr *MockPaneInterfaceMockRecorder) ClipboardController() *gomock.Call { | |
| 250 | + | mr.mock.ctrl.T.Helper() | |
| 251 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClipboardController", reflect.TypeOf((*MockPaneInterface)(nil).ClipboardController)) | |
| 252 | + | } | |
| 253 | + | ||
| 254 | + | // HasGUI mocks base method. | |
| 255 | + | func (m *MockPaneInterface) HasGUI() bool { | |
| 256 | + | m.ctrl.T.Helper() | |
| 257 | + | ret := m.ctrl.Call(m, "HasGUI") | |
| 258 | + | ret0, _ := ret[0].(bool) | |
| 259 | + | return ret0 | |
| 260 | + | } | |
| 261 | + | ||
| 262 | + | // HasGUI indicates an expected call of HasGUI. | |
| 263 | + | func (mr *MockPaneInterfaceMockRecorder) HasGUI() *gomock.Call { | |
| 264 | + | mr.mock.ctrl.T.Helper() | |
| 265 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasGUI", reflect.TypeOf((*MockPaneInterface)(nil).HasGUI)) | |
| 266 | + | } | |
| 267 | + | ||
| 268 | + | // HasGUIComponent mocks base method. | |
| 269 | + | func (m *MockPaneInterface) HasGUIComponent(component string) bool { | |
| 270 | + | m.ctrl.T.Helper() | |
| 271 | + | ret := m.ctrl.Call(m, "HasGUIComponent", component) | |
| 272 | + | ret0, _ := ret[0].(bool) | |
| 273 | + | return ret0 | |
| 274 | + | } | |
| 275 | + | ||
| 276 | + | // HasGUIComponent indicates an expected call of HasGUIComponent. | |
| 277 | + | func (mr *MockPaneInterfaceMockRecorder) HasGUIComponent(component interface{}) *gomock.Call { | |
| 278 | + | mr.mock.ctrl.T.Helper() | |
| 279 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasGUIComponent", reflect.TypeOf((*MockPaneInterface)(nil).HasGUIComponent), component) | |
| 280 | + | } | |
| 281 | + | ||
| 282 | + | // ID mocks base method. | |
| 283 | + | func (m *MockPaneInterface) ID() string { | |
| 284 | + | m.ctrl.T.Helper() | |
| 285 | + | ret := m.ctrl.Call(m, "ID") | |
| 286 | + | ret0, _ := ret[0].(string) | |
| 287 | + | return ret0 | |
| 288 | + | } | |
| 289 | + | ||
| 290 | + | // ID indicates an expected call of ID. | |
| 291 | + | func (mr *MockPaneInterfaceMockRecorder) ID() *gomock.Call { | |
| 292 | + | mr.mock.ctrl.T.Helper() | |
| 293 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockPaneInterface)(nil).ID)) | |
| 294 | + | } | |
| 295 | + | ||
| 296 | + | // MessageHandler mocks base method. | |
| 297 | + | func (m *MockPaneInterface) MessageHandler() browser.MessageHandlerInterface { | |
| 298 | + | m.ctrl.T.Helper() | |
| 299 | + | ret := m.ctrl.Call(m, "MessageHandler") | |
| 300 | + | ret0, _ := ret[0].(browser.MessageHandlerInterface) | |
| 301 | + | return ret0 | |
| 302 | + | } | |
| 303 | + | ||
| 304 | + | // MessageHandler indicates an expected call of MessageHandler. | |
| 305 | + | func (mr *MockPaneInterfaceMockRecorder) MessageHandler() *gomock.Call { | |
| 306 | + | mr.mock.ctrl.T.Helper() | |
| 307 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MessageHandler", reflect.TypeOf((*MockPaneInterface)(nil).MessageHandler)) | |
| 308 | + | } | |
| 309 | + | ||
| 310 | + | // NavigationController mocks base method. | |
| 311 | + | func (m *MockPaneInterface) NavigationController() browser.NavigationControllerInterface { | |
| 312 | + | m.ctrl.T.Helper() | |
| 313 | + | ret := m.ctrl.Call(m, "NavigationController") | |
| 314 | + | ret0, _ := ret[0].(browser.NavigationControllerInterface) | |
| 315 | + | return ret0 | |
| 316 | + | } | |
| 317 | + | ||
| 318 | + | // NavigationController indicates an expected call of NavigationController. | |
| 319 | + | func (mr *MockPaneInterfaceMockRecorder) NavigationController() *gomock.Call { | |
| 320 | + | mr.mock.ctrl.T.Helper() | |
| 321 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NavigationController", reflect.TypeOf((*MockPaneInterface)(nil).NavigationController)) | |
| 322 | + | } | |
| 323 | + | ||
| 324 | + | // SetGUIComponent mocks base method. | |
| 325 | + | func (m *MockPaneInterface) SetGUIComponent(component string, loaded bool) { | |
| 326 | + | m.ctrl.T.Helper() | |
| 327 | + | m.ctrl.Call(m, "SetGUIComponent", component, loaded) | |
| 328 | + | } | |
| 329 | + | ||
| 330 | + | // SetGUIComponent indicates an expected call of SetGUIComponent. | |
| 331 | + | func (mr *MockPaneInterfaceMockRecorder) SetGUIComponent(component, loaded interface{}) *gomock.Call { | |
| 332 | + | mr.mock.ctrl.T.Helper() | |
| 333 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetGUIComponent", reflect.TypeOf((*MockPaneInterface)(nil).SetGUIComponent), component, loaded) | |
| 334 | + | } | |
| 335 | + | ||
| 336 | + | // SetHasGUI mocks base method. | |
| 337 | + | func (m *MockPaneInterface) SetHasGUI(arg0 bool) { | |
| 338 | + | m.ctrl.T.Helper() | |
| 339 | + | m.ctrl.Call(m, "SetHasGUI", arg0) | |
| 340 | + | } | |
| 341 | + | ||
| 342 | + | // SetHasGUI indicates an expected call of SetHasGUI. | |
| 343 | + | func (mr *MockPaneInterfaceMockRecorder) SetHasGUI(arg0 interface{}) *gomock.Call { | |
| 344 | + | mr.mock.ctrl.T.Helper() | |
| 345 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHasGUI", reflect.TypeOf((*MockPaneInterface)(nil).SetHasGUI), arg0) | |
| 346 | + | } | |
| 347 | + | ||
| 348 | + | // SetID mocks base method. | |
| 349 | + | func (m *MockPaneInterface) SetID(arg0 string) { | |
| 350 | + | m.ctrl.T.Helper() | |
| 351 | + | m.ctrl.Call(m, "SetID", arg0) | |
| 352 | + | } | |
| 353 | + | ||
| 354 | + | // SetID indicates an expected call of SetID. | |
| 355 | + | func (mr *MockPaneInterfaceMockRecorder) SetID(arg0 interface{}) *gomock.Call { | |
| 356 | + | mr.mock.ctrl.T.Helper() | |
| 357 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetID", reflect.TypeOf((*MockPaneInterface)(nil).SetID), arg0) | |
| 358 | + | } | |
| 359 | + | ||
| 360 | + | // ShortcutHandler mocks base method. | |
| 361 | + | func (m *MockPaneInterface) ShortcutHandler() browser.ShortcutHandlerInterface { | |
| 362 | + | m.ctrl.T.Helper() | |
| 363 | + | ret := m.ctrl.Call(m, "ShortcutHandler") | |
| 364 | + | ret0, _ := ret[0].(browser.ShortcutHandlerInterface) | |
| 365 | + | return ret0 | |
| 366 | + | } | |
| 367 | + | ||
| 368 | + | // ShortcutHandler indicates an expected call of ShortcutHandler. | |
| 369 | + | func (mr *MockPaneInterfaceMockRecorder) ShortcutHandler() *gomock.Call { | |
| 370 | + | mr.mock.ctrl.T.Helper() | |
| 371 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShortcutHandler", reflect.TypeOf((*MockPaneInterface)(nil).ShortcutHandler)) | |
| 372 | + | } | |
| 373 | + | ||
| 374 | + | // UpdateLastFocus mocks base method. | |
| 375 | + | func (m *MockPaneInterface) UpdateLastFocus() { | |
| 376 | + | m.ctrl.T.Helper() | |
| 377 | + | m.ctrl.Call(m, "UpdateLastFocus") | |
| 378 | + | } | |
| 379 | + | ||
| 380 | + | // UpdateLastFocus indicates an expected call of UpdateLastFocus. | |
| 381 | + | func (mr *MockPaneInterfaceMockRecorder) UpdateLastFocus() *gomock.Call { | |
| 382 | + | mr.mock.ctrl.T.Helper() | |
| 383 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLastFocus", reflect.TypeOf((*MockPaneInterface)(nil).UpdateLastFocus)) | |
| 384 | + | } | |
| 385 | + | ||
| 386 | + | // WebView mocks base method. | |
| 387 | + | func (m *MockPaneInterface) WebView() browser.WebViewInterface { | |
| 388 | + | m.ctrl.T.Helper() | |
| 389 | + | ret := m.ctrl.Call(m, "WebView") | |
| 390 | + | ret0, _ := ret[0].(browser.WebViewInterface) | |
| 391 | + | return ret0 | |
| 392 | + | } | |
| 393 | + | ||
| 394 | + | // WebView indicates an expected call of WebView. | |
| 395 | + | func (mr *MockPaneInterfaceMockRecorder) WebView() *gomock.Call { | |
| 396 | + | mr.mock.ctrl.T.Helper() | |
| 397 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WebView", reflect.TypeOf((*MockPaneInterface)(nil).WebView)) | |
| 398 | + | } | |
| 399 | + | ||
| 400 | + | // ZoomController mocks base method. | |
| 401 | + | func (m *MockPaneInterface) ZoomController() browser.ZoomControllerInterface { | |
| 402 | + | m.ctrl.T.Helper() | |
| 403 | + | ret := m.ctrl.Call(m, "ZoomController") | |
| 404 | + | ret0, _ := ret[0].(browser.ZoomControllerInterface) | |
| 405 | + | return ret0 | |
| 406 | + | } | |
| 407 | + | ||
| 408 | + | // ZoomController indicates an expected call of ZoomController. | |
| 409 | + | func (mr *MockPaneInterfaceMockRecorder) ZoomController() *gomock.Call { | |
| 410 | + | mr.mock.ctrl.T.Helper() | |
| 411 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ZoomController", reflect.TypeOf((*MockPaneInterface)(nil).ZoomController)) | |
| 412 | + | } | |
| 413 | + | ||
| 414 | + | // MockMessageHandlerInterface is a mock of MessageHandlerInterface interface. | |
| 415 | + | type MockMessageHandlerInterface struct { | |
| 416 | + | ctrl *gomock.Controller | |
| 417 | + | recorder *MockMessageHandlerInterfaceMockRecorder | |
| 418 | + | } | |
| 419 | + | ||
| 420 | + | // MockMessageHandlerInterfaceMockRecorder is the mock recorder for MockMessageHandlerInterface. | |
| 421 | + | type MockMessageHandlerInterfaceMockRecorder struct { | |
| 422 | + | mock *MockMessageHandlerInterface | |
| 423 | + | } | |
| 424 | + | ||
| 425 | + | // NewMockMessageHandlerInterface creates a new mock instance. | |
| 426 | + | func NewMockMessageHandlerInterface(ctrl *gomock.Controller) *MockMessageHandlerInterface { | |
| 427 | + | mock := &MockMessageHandlerInterface{ctrl: ctrl} | |
| 428 | + | mock.recorder = &MockMessageHandlerInterfaceMockRecorder{mock} | |
| 429 | + | return mock | |
| 430 | + | } | |
| 431 | + | ||
| 432 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |
| 433 | + | func (m *MockMessageHandlerInterface) EXPECT() *MockMessageHandlerInterfaceMockRecorder { | |
| 434 | + | return m.recorder | |
| 435 | + | } | |
| 436 | + | ||
| 437 | + | // SetWorkspaceObserver mocks base method. | |
| 438 | + | func (m *MockMessageHandlerInterface) SetWorkspaceObserver(observer messaging.WorkspaceObserver) { | |
| 439 | + | m.ctrl.T.Helper() | |
| 440 | + | m.ctrl.Call(m, "SetWorkspaceObserver", observer) | |
| 441 | + | } | |
| 442 | + | ||
| 443 | + | // SetWorkspaceObserver indicates an expected call of SetWorkspaceObserver. | |
| 444 | + | func (mr *MockMessageHandlerInterfaceMockRecorder) SetWorkspaceObserver(observer interface{}) *gomock.Call { | |
| 445 | + | mr.mock.ctrl.T.Helper() | |
| 446 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWorkspaceObserver", reflect.TypeOf((*MockMessageHandlerInterface)(nil).SetWorkspaceObserver), observer) | |
| 447 | + | } | |
| 448 | + | ||
| 449 | + | // MockNavigationControllerInterface is a mock of NavigationControllerInterface interface. | |
| 450 | + | type MockNavigationControllerInterface struct { | |
| 451 | + | ctrl *gomock.Controller | |
| 452 | + | recorder *MockNavigationControllerInterfaceMockRecorder | |
| 453 | + | } | |
| 454 | + | ||
| 455 | + | // MockNavigationControllerInterfaceMockRecorder is the mock recorder for MockNavigationControllerInterface. | |
| 456 | + | type MockNavigationControllerInterfaceMockRecorder struct { | |
| 457 | + | mock *MockNavigationControllerInterface | |
| 458 | + | } | |
| 459 | + | ||
| 460 | + | // NewMockNavigationControllerInterface creates a new mock instance. | |
| 461 | + | func NewMockNavigationControllerInterface(ctrl *gomock.Controller) *MockNavigationControllerInterface { | |
| 462 | + | mock := &MockNavigationControllerInterface{ctrl: ctrl} | |
| 463 | + | mock.recorder = &MockNavigationControllerInterfaceMockRecorder{mock} | |
| 464 | + | return mock | |
| 465 | + | } | |
| 466 | + | ||
| 467 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |
| 468 | + | func (m *MockNavigationControllerInterface) EXPECT() *MockNavigationControllerInterfaceMockRecorder { | |
| 469 | + | return m.recorder | |
| 470 | + | } | |
| 471 | + | ||
| 472 | + | // NavigateToURL mocks base method. | |
| 473 | + | func (m *MockNavigationControllerInterface) NavigateToURL(url string) error { | |
| 474 | + | m.ctrl.T.Helper() | |
| 475 | + | ret := m.ctrl.Call(m, "NavigateToURL", url) | |
| 476 | + | ret0, _ := ret[0].(error) | |
| 477 | + | return ret0 | |
| 478 | + | } | |
| 479 | + | ||
| 480 | + | // NavigateToURL indicates an expected call of NavigateToURL. | |
| 481 | + | func (mr *MockNavigationControllerInterfaceMockRecorder) NavigateToURL(url interface{}) *gomock.Call { | |
| 482 | + | mr.mock.ctrl.T.Helper() | |
| 483 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NavigateToURL", reflect.TypeOf((*MockNavigationControllerInterface)(nil).NavigateToURL), url) | |
| 484 | + | } | |
| 485 | + | ||
| 486 | + | // MockZoomControllerInterface is a mock of ZoomControllerInterface interface. | |
| 487 | + | type MockZoomControllerInterface struct { | |
| 488 | + | ctrl *gomock.Controller | |
| 489 | + | recorder *MockZoomControllerInterfaceMockRecorder | |
| 490 | + | } | |
| 491 | + | ||
| 492 | + | // MockZoomControllerInterfaceMockRecorder is the mock recorder for MockZoomControllerInterface. | |
| 493 | + | type MockZoomControllerInterfaceMockRecorder struct { | |
| 494 | + | mock *MockZoomControllerInterface | |
| 495 | + | } | |
| 496 | + | ||
| 497 | + | // NewMockZoomControllerInterface creates a new mock instance. | |
| 498 | + | func NewMockZoomControllerInterface(ctrl *gomock.Controller) *MockZoomControllerInterface { | |
| 499 | + | mock := &MockZoomControllerInterface{ctrl: ctrl} | |
| 500 | + | mock.recorder = &MockZoomControllerInterfaceMockRecorder{mock} | |
| 501 | + | return mock | |
| 502 | + | } | |
| 503 | + | ||
| 504 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |
| 505 | + | func (m *MockZoomControllerInterface) EXPECT() *MockZoomControllerInterfaceMockRecorder { | |
| 506 | + | return m.recorder | |
| 507 | + | } | |
| 508 | + | ||
| 509 | + | // ApplyInitialZoom mocks base method. | |
| 510 | + | func (m *MockZoomControllerInterface) ApplyInitialZoom() { | |
| 511 | + | m.ctrl.T.Helper() | |
| 512 | + | m.ctrl.Call(m, "ApplyInitialZoom") | |
| 513 | + | } | |
| 514 | + | ||
| 515 | + | // ApplyInitialZoom indicates an expected call of ApplyInitialZoom. | |
| 516 | + | func (mr *MockZoomControllerInterfaceMockRecorder) ApplyInitialZoom() *gomock.Call { | |
| 517 | + | mr.mock.ctrl.T.Helper() | |
| 518 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyInitialZoom", reflect.TypeOf((*MockZoomControllerInterface)(nil).ApplyInitialZoom)) | |
| 519 | + | } | |
| 520 | + | ||
| 521 | + | // MockClipboardControllerInterface is a mock of ClipboardControllerInterface interface. | |
| 522 | + | type MockClipboardControllerInterface struct { | |
| 523 | + | ctrl *gomock.Controller | |
| 524 | + | recorder *MockClipboardControllerInterfaceMockRecorder | |
| 525 | + | } | |
| 526 | + | ||
| 527 | + | // MockClipboardControllerInterfaceMockRecorder is the mock recorder for MockClipboardControllerInterface. | |
| 528 | + | type MockClipboardControllerInterfaceMockRecorder struct { | |
| 529 | + | mock *MockClipboardControllerInterface | |
| 530 | + | } | |
| 531 | + | ||
| 532 | + | // NewMockClipboardControllerInterface creates a new mock instance. | |
| 533 | + | func NewMockClipboardControllerInterface(ctrl *gomock.Controller) *MockClipboardControllerInterface { | |
| 534 | + | mock := &MockClipboardControllerInterface{ctrl: ctrl} | |
| 535 | + | mock.recorder = &MockClipboardControllerInterfaceMockRecorder{mock} | |
| 536 | + | return mock | |
| 537 | + | } | |
| 538 | + | ||
| 539 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |
| 540 | + | func (m *MockClipboardControllerInterface) EXPECT() *MockClipboardControllerInterfaceMockRecorder { | |
| 541 | + | return m.recorder | |
| 542 | + | } | |
| 543 | + | ||
| 544 | + | // MockShortcutHandlerInterface is a mock of ShortcutHandlerInterface interface. | |
| 545 | + | type MockShortcutHandlerInterface struct { | |
| 546 | + | ctrl *gomock.Controller | |
| 547 | + | recorder *MockShortcutHandlerInterfaceMockRecorder | |
| 548 | + | } | |
| 549 | + | ||
| 550 | + | // MockShortcutHandlerInterfaceMockRecorder is the mock recorder for MockShortcutHandlerInterface. | |
| 551 | + | type MockShortcutHandlerInterfaceMockRecorder struct { | |
| 552 | + | mock *MockShortcutHandlerInterface | |
| 553 | + | } | |
| 554 | + | ||
| 555 | + | // NewMockShortcutHandlerInterface creates a new mock instance. | |
| 556 | + | func NewMockShortcutHandlerInterface(ctrl *gomock.Controller) *MockShortcutHandlerInterface { | |
| 557 | + | mock := &MockShortcutHandlerInterface{ctrl: ctrl} | |
| 558 | + | mock.recorder = &MockShortcutHandlerInterfaceMockRecorder{mock} | |
| 559 | + | return mock | |
| 560 | + | } | |
| 561 | + | ||
| 562 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |
| 563 | + | func (m *MockShortcutHandlerInterface) EXPECT() *MockShortcutHandlerInterfaceMockRecorder { | |
| 564 | + | return m.recorder | |
| 565 | + | } | |
| 566 | + | ||
| 567 | + | // MockBrowserAppInterface is a mock of BrowserAppInterface interface. | |
| 568 | + | type MockBrowserAppInterface struct { | |
| 569 | + | ctrl *gomock.Controller | |
| 570 | + | recorder *MockBrowserAppInterfaceMockRecorder | |
| 571 | + | } | |
| 572 | + | ||
| 573 | + | // MockBrowserAppInterfaceMockRecorder is the mock recorder for MockBrowserAppInterface. | |
| 574 | + | type MockBrowserAppInterfaceMockRecorder struct { | |
| 575 | + | mock *MockBrowserAppInterface | |
| 576 | + | } | |
| 577 | + | ||
| 578 | + | // NewMockBrowserAppInterface creates a new mock instance. | |
| 579 | + | func NewMockBrowserAppInterface(ctrl *gomock.Controller) *MockBrowserAppInterface { | |
| 580 | + | mock := &MockBrowserAppInterface{ctrl: ctrl} | |
| 581 | + | mock.recorder = &MockBrowserAppInterfaceMockRecorder{mock} | |
| 582 | + | return mock | |
| 583 | + | } | |
| 584 | + | ||
| 585 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |
| 586 | + | func (m *MockBrowserAppInterface) EXPECT() *MockBrowserAppInterfaceMockRecorder { | |
| 587 | + | return m.recorder | |
| 588 | + | } | |
| 589 | + | ||
| 590 | + | // AppendPane mocks base method. | |
| 591 | + | func (m *MockBrowserAppInterface) AppendPane(pane *browser.BrowserPane) { | |
| 592 | + | m.ctrl.T.Helper() | |
| 593 | + | m.ctrl.Call(m, "AppendPane", pane) | |
| 594 | + | } | |
| 595 | + | ||
| 596 | + | // AppendPane indicates an expected call of AppendPane. | |
| 597 | + | func (mr *MockBrowserAppInterfaceMockRecorder) AppendPane(pane interface{}) *gomock.Call { | |
| 598 | + | mr.mock.ctrl.T.Helper() | |
| 599 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppendPane", reflect.TypeOf((*MockBrowserAppInterface)(nil).AppendPane), pane) | |
| 600 | + | } | |
| 601 | + | ||
| 602 | + | // BuildWebkitConfig mocks base method. | |
| 603 | + | func (m *MockBrowserAppInterface) BuildWebkitConfig() (*webkit.Config, error) { | |
| 604 | + | m.ctrl.T.Helper() | |
| 605 | + | ret := m.ctrl.Call(m, "BuildWebkitConfig") | |
| 606 | + | ret0, _ := ret[0].(*webkit.Config) | |
| 607 | + | ret1, _ := ret[1].(error) | |
| 608 | + | return ret0, ret1 | |
| 609 | + | } | |
| 610 | + | ||
| 611 | + | // BuildWebkitConfig indicates an expected call of BuildWebkitConfig. | |
| 612 | + | func (mr *MockBrowserAppInterfaceMockRecorder) BuildWebkitConfig() *gomock.Call { | |
| 613 | + | mr.mock.ctrl.T.Helper() | |
| 614 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildWebkitConfig", reflect.TypeOf((*MockBrowserAppInterface)(nil).BuildWebkitConfig)) | |
| 615 | + | } | |
| 616 | + | ||
| 617 | + | // CreatePaneForView mocks base method. | |
| 618 | + | func (m *MockBrowserAppInterface) CreatePaneForView(view *webkit.WebView) (*browser.BrowserPane, error) { | |
| 619 | + | m.ctrl.T.Helper() | |
| 620 | + | ret := m.ctrl.Call(m, "CreatePaneForView", view) | |
| 621 | + | ret0, _ := ret[0].(*browser.BrowserPane) | |
| 622 | + | ret1, _ := ret[1].(error) | |
| 623 | + | return ret0, ret1 | |
| 624 | + | } | |
| 625 | + | ||
| 626 | + | // CreatePaneForView indicates an expected call of CreatePaneForView. | |
| 627 | + | func (mr *MockBrowserAppInterfaceMockRecorder) CreatePaneForView(view interface{}) *gomock.Call { | |
| 628 | + | mr.mock.ctrl.T.Helper() | |
| 629 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePaneForView", reflect.TypeOf((*MockBrowserAppInterface)(nil).CreatePaneForView), view) | |
| 630 | + | } | |
| 631 | + | ||
| 632 | + | // GetActivePane mocks base method. | |
| 633 | + | func (m *MockBrowserAppInterface) GetActivePane() *browser.BrowserPane { | |
| 634 | + | m.ctrl.T.Helper() | |
| 635 | + | ret := m.ctrl.Call(m, "GetActivePane") | |
| 636 | + | ret0, _ := ret[0].(*browser.BrowserPane) | |
| 637 | + | return ret0 | |
| 638 | + | } | |
| 639 | + | ||
| 640 | + | // GetActivePane indicates an expected call of GetActivePane. | |
| 641 | + | func (mr *MockBrowserAppInterfaceMockRecorder) GetActivePane() *gomock.Call { | |
| 642 | + | mr.mock.ctrl.T.Helper() | |
| 643 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActivePane", reflect.TypeOf((*MockBrowserAppInterface)(nil).GetActivePane)) | |
| 644 | + | } | |
| 645 | + | ||
| 646 | + | // GetPanes mocks base method. | |
| 647 | + | func (m *MockBrowserAppInterface) GetPanes() []*browser.BrowserPane { | |
| 648 | + | m.ctrl.T.Helper() | |
| 649 | + | ret := m.ctrl.Call(m, "GetPanes") | |
| 650 | + | ret0, _ := ret[0].([]*browser.BrowserPane) | |
| 651 | + | return ret0 | |
| 652 | + | } | |
| 653 | + | ||
| 654 | + | // GetPanes indicates an expected call of GetPanes. | |
| 655 | + | func (mr *MockBrowserAppInterfaceMockRecorder) GetPanes() *gomock.Call { | |
| 656 | + | mr.mock.ctrl.T.Helper() | |
| 657 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPanes", reflect.TypeOf((*MockBrowserAppInterface)(nil).GetPanes)) | |
| 658 | + | } | |
| 659 | + | ||
| 660 | + | // RemovePane mocks base method. | |
| 661 | + | func (m *MockBrowserAppInterface) RemovePane(pane *browser.BrowserPane) { | |
| 662 | + | m.ctrl.T.Helper() | |
| 663 | + | m.ctrl.Call(m, "RemovePane", pane) | |
| 664 | + | } | |
| 665 | + | ||
| 666 | + | // RemovePane indicates an expected call of RemovePane. | |
| 667 | + | func (mr *MockBrowserAppInterfaceMockRecorder) RemovePane(pane interface{}) *gomock.Call { | |
| 668 | + | mr.mock.ctrl.T.Helper() | |
| 669 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemovePane", reflect.TypeOf((*MockBrowserAppInterface)(nil).RemovePane), pane) | |
| 670 | + | } | |
| 671 | + | ||
| 672 | + | // SetActivePane mocks base method. | |
| 673 | + | func (m *MockBrowserAppInterface) SetActivePane(pane *browser.BrowserPane) { | |
| 674 | + | m.ctrl.T.Helper() | |
| 675 | + | m.ctrl.Call(m, "SetActivePane", pane) | |
| 676 | + | } | |
| 677 | + | ||
| 678 | + | // SetActivePane indicates an expected call of SetActivePane. | |
| 679 | + | func (mr *MockBrowserAppInterfaceMockRecorder) SetActivePane(pane interface{}) *gomock.Call { | |
| 680 | + | mr.mock.ctrl.T.Helper() | |
| 681 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetActivePane", reflect.TypeOf((*MockBrowserAppInterface)(nil).SetActivePane), pane) | |
| 682 | + | } | |
| 683 | + | ||
| 684 | + | // MockWindowInterface is a mock of WindowInterface interface. | |
| 685 | + | type MockWindowInterface struct { | |
| 686 | + | ctrl *gomock.Controller | |
| 687 | + | recorder *MockWindowInterfaceMockRecorder | |
| 688 | + | } | |
| 689 | + | ||
| 690 | + | // MockWindowInterfaceMockRecorder is the mock recorder for MockWindowInterface. | |
| 691 | + | type MockWindowInterfaceMockRecorder struct { | |
| 692 | + | mock *MockWindowInterface | |
| 693 | + | } | |
| 694 | + | ||
| 695 | + | // NewMockWindowInterface creates a new mock instance. | |
| 696 | + | func NewMockWindowInterface(ctrl *gomock.Controller) *MockWindowInterface { | |
| 697 | + | mock := &MockWindowInterface{ctrl: ctrl} | |
| 698 | + | mock.recorder = &MockWindowInterfaceMockRecorder{mock} | |
| 699 | + | return mock | |
| 700 | + | } | |
| 701 | + | ||
| 702 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |
| 703 | + | func (m *MockWindowInterface) EXPECT() *MockWindowInterfaceMockRecorder { | |
| 704 | + | return m.recorder | |
| 705 | + | } | |
| 706 | + | ||
| 707 | + | // SetChild mocks base method. | |
| 708 | + | func (m *MockWindowInterface) SetChild(widget uintptr) { | |
| 709 | + | m.ctrl.T.Helper() | |
| 710 | + | m.ctrl.Call(m, "SetChild", widget) | |
| 711 | + | } | |
| 712 | + | ||
| 713 | + | // SetChild indicates an expected call of SetChild. | |
| 714 | + | func (mr *MockWindowInterfaceMockRecorder) SetChild(widget interface{}) *gomock.Call { | |
| 715 | + | mr.mock.ctrl.T.Helper() | |
| 716 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetChild", reflect.TypeOf((*MockWindowInterface)(nil).SetChild), widget) | |
| 717 | + | } | |
| @@ -0,0 +1,717 @@ | |||||
| 1 | + | // Code generated by MockGen. DO NOT EDIT. | |||
| 2 | + | // Source: interfaces.go | |||
| 3 | + | ||||
| 4 | + | // Package mock_browser is a generated GoMock package. | |||
| 5 | + | package mock_browser | |||
| 6 | + | ||||
| 7 | + | import ( | |||
| 8 | + | reflect "reflect" | |||
| 9 | + | ||||
| 10 | + | browser "github.com/bnema/dumber/internal/app/browser" | |||
| 11 | + | messaging "github.com/bnema/dumber/internal/app/messaging" | |||
| 12 | + | webkit "github.com/bnema/dumber/pkg/webkit" | |||
| 13 | + | gomock "github.com/golang/mock/gomock" | |||
| 14 | + | ) | |||
| 15 | + | ||||
| 16 | + | // MockWebViewInterface is a mock of WebViewInterface interface. | |||
| 17 | + | type MockWebViewInterface struct { | |||
| 18 | + | ctrl *gomock.Controller | |||
| 19 | + | recorder *MockWebViewInterfaceMockRecorder | |||
| 20 | + | } | |||
| 21 | + | ||||
| 22 | + | // MockWebViewInterfaceMockRecorder is the mock recorder for MockWebViewInterface. | |||
| 23 | + | type MockWebViewInterfaceMockRecorder struct { | |||
| 24 | + | mock *MockWebViewInterface | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | // NewMockWebViewInterface creates a new mock instance. | |||
| 28 | + | func NewMockWebViewInterface(ctrl *gomock.Controller) *MockWebViewInterface { | |||
| 29 | + | mock := &MockWebViewInterface{ctrl: ctrl} | |||
| 30 | + | mock.recorder = &MockWebViewInterfaceMockRecorder{mock} | |||
| 31 | + | return mock | |||
| 32 | + | } | |||
| 33 | + | ||||
| 34 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |||
| 35 | + | func (m *MockWebViewInterface) EXPECT() *MockWebViewInterfaceMockRecorder { | |||
| 36 | + | return m.recorder | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | // Destroy mocks base method. | |||
| 40 | + | func (m *MockWebViewInterface) Destroy() error { | |||
| 41 | + | m.ctrl.T.Helper() | |||
| 42 | + | ret := m.ctrl.Call(m, "Destroy") | |||
| 43 | + | ret0, _ := ret[0].(error) | |||
| 44 | + | return ret0 | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | // Destroy indicates an expected call of Destroy. | |||
| 48 | + | func (mr *MockWebViewInterfaceMockRecorder) Destroy() *gomock.Call { | |||
| 49 | + | mr.mock.ctrl.T.Helper() | |||
| 50 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockWebViewInterface)(nil).Destroy)) | |||
| 51 | + | } | |||
| 52 | + | ||||
| 53 | + | // DispatchCustomEvent mocks base method. | |||
| 54 | + | func (m *MockWebViewInterface) DispatchCustomEvent(event string, detail map[string]any) error { | |||
| 55 | + | m.ctrl.T.Helper() | |||
| 56 | + | ret := m.ctrl.Call(m, "DispatchCustomEvent", event, detail) | |||
| 57 | + | ret0, _ := ret[0].(error) | |||
| 58 | + | return ret0 | |||
| 59 | + | } | |||
| 60 | + | ||||
| 61 | + | // DispatchCustomEvent indicates an expected call of DispatchCustomEvent. | |||
| 62 | + | func (mr *MockWebViewInterfaceMockRecorder) DispatchCustomEvent(event, detail interface{}) *gomock.Call { | |||
| 63 | + | mr.mock.ctrl.T.Helper() | |||
| 64 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DispatchCustomEvent", reflect.TypeOf((*MockWebViewInterface)(nil).DispatchCustomEvent), event, detail) | |||
| 65 | + | } | |||
| 66 | + | ||||
| 67 | + | // GetCurrentURL mocks base method. | |||
| 68 | + | func (m *MockWebViewInterface) GetCurrentURL() string { | |||
| 69 | + | m.ctrl.T.Helper() | |||
| 70 | + | ret := m.ctrl.Call(m, "GetCurrentURL") | |||
| 71 | + | ret0, _ := ret[0].(string) | |||
| 72 | + | return ret0 | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | // GetCurrentURL indicates an expected call of GetCurrentURL. | |||
| 76 | + | func (mr *MockWebViewInterfaceMockRecorder) GetCurrentURL() *gomock.Call { | |||
| 77 | + | mr.mock.ctrl.T.Helper() | |||
| 78 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentURL", reflect.TypeOf((*MockWebViewInterface)(nil).GetCurrentURL)) | |||
| 79 | + | } | |||
| 80 | + | ||||
| 81 | + | // Hide mocks base method. | |||
| 82 | + | func (m *MockWebViewInterface) Hide() error { | |||
| 83 | + | m.ctrl.T.Helper() | |||
| 84 | + | ret := m.ctrl.Call(m, "Hide") | |||
| 85 | + | ret0, _ := ret[0].(error) | |||
| 86 | + | return ret0 | |||
| 87 | + | } | |||
| 88 | + | ||||
| 89 | + | // Hide indicates an expected call of Hide. | |||
| 90 | + | func (mr *MockWebViewInterfaceMockRecorder) Hide() *gomock.Call { | |||
| 91 | + | mr.mock.ctrl.T.Helper() | |||
| 92 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hide", reflect.TypeOf((*MockWebViewInterface)(nil).Hide)) | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | // InjectScript mocks base method. | |||
| 96 | + | func (m *MockWebViewInterface) InjectScript(script string) error { | |||
| 97 | + | m.ctrl.T.Helper() | |||
| 98 | + | ret := m.ctrl.Call(m, "InjectScript", script) | |||
| 99 | + | ret0, _ := ret[0].(error) | |||
| 100 | + | return ret0 | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | // InjectScript indicates an expected call of InjectScript. | |||
| 104 | + | func (mr *MockWebViewInterfaceMockRecorder) InjectScript(script interface{}) *gomock.Call { | |||
| 105 | + | mr.mock.ctrl.T.Helper() | |||
| 106 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InjectScript", reflect.TypeOf((*MockWebViewInterface)(nil).InjectScript), script) | |||
| 107 | + | } | |||
| 108 | + | ||||
| 109 | + | // LoadURL mocks base method. | |||
| 110 | + | func (m *MockWebViewInterface) LoadURL(url string) error { | |||
| 111 | + | m.ctrl.T.Helper() | |||
| 112 | + | ret := m.ctrl.Call(m, "LoadURL", url) | |||
| 113 | + | ret0, _ := ret[0].(error) | |||
| 114 | + | return ret0 | |||
| 115 | + | } | |||
| 116 | + | ||||
| 117 | + | // LoadURL indicates an expected call of LoadURL. | |||
| 118 | + | func (mr *MockWebViewInterfaceMockRecorder) LoadURL(url interface{}) *gomock.Call { | |||
| 119 | + | mr.mock.ctrl.T.Helper() | |||
| 120 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadURL", reflect.TypeOf((*MockWebViewInterface)(nil).LoadURL), url) | |||
| 121 | + | } | |||
| 122 | + | ||||
| 123 | + | // RegisterKeyboardShortcut mocks base method. | |||
| 124 | + | func (m *MockWebViewInterface) RegisterKeyboardShortcut(key string, handler func()) error { | |||
| 125 | + | m.ctrl.T.Helper() | |||
| 126 | + | ret := m.ctrl.Call(m, "RegisterKeyboardShortcut", key, handler) | |||
| 127 | + | ret0, _ := ret[0].(error) | |||
| 128 | + | return ret0 | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | // RegisterKeyboardShortcut indicates an expected call of RegisterKeyboardShortcut. | |||
| 132 | + | func (mr *MockWebViewInterfaceMockRecorder) RegisterKeyboardShortcut(key, handler interface{}) *gomock.Call { | |||
| 133 | + | mr.mock.ctrl.T.Helper() | |||
| 134 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterKeyboardShortcut", reflect.TypeOf((*MockWebViewInterface)(nil).RegisterKeyboardShortcut), key, handler) | |||
| 135 | + | } | |||
| 136 | + | ||||
| 137 | + | // RegisterURIChangedHandler mocks base method. | |||
| 138 | + | func (m *MockWebViewInterface) RegisterURIChangedHandler(handler func(string)) { | |||
| 139 | + | m.ctrl.T.Helper() | |||
| 140 | + | m.ctrl.Call(m, "RegisterURIChangedHandler", handler) | |||
| 141 | + | } | |||
| 142 | + | ||||
| 143 | + | // RegisterURIChangedHandler indicates an expected call of RegisterURIChangedHandler. | |||
| 144 | + | func (mr *MockWebViewInterfaceMockRecorder) RegisterURIChangedHandler(handler interface{}) *gomock.Call { | |||
| 145 | + | mr.mock.ctrl.T.Helper() | |||
| 146 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterURIChangedHandler", reflect.TypeOf((*MockWebViewInterface)(nil).RegisterURIChangedHandler), handler) | |||
| 147 | + | } | |||
| 148 | + | ||||
| 149 | + | // RootWidget mocks base method. | |||
| 150 | + | func (m *MockWebViewInterface) RootWidget() uintptr { | |||
| 151 | + | m.ctrl.T.Helper() | |||
| 152 | + | ret := m.ctrl.Call(m, "RootWidget") | |||
| 153 | + | ret0, _ := ret[0].(uintptr) | |||
| 154 | + | return ret0 | |||
| 155 | + | } | |||
| 156 | + | ||||
| 157 | + | // RootWidget indicates an expected call of RootWidget. | |||
| 158 | + | func (mr *MockWebViewInterfaceMockRecorder) RootWidget() *gomock.Call { | |||
| 159 | + | mr.mock.ctrl.T.Helper() | |||
| 160 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RootWidget", reflect.TypeOf((*MockWebViewInterface)(nil).RootWidget)) | |||
| 161 | + | } | |||
| 162 | + | ||||
| 163 | + | // Show mocks base method. | |||
| 164 | + | func (m *MockWebViewInterface) Show() error { | |||
| 165 | + | m.ctrl.T.Helper() | |||
| 166 | + | ret := m.ctrl.Call(m, "Show") | |||
| 167 | + | ret0, _ := ret[0].(error) | |||
| 168 | + | return ret0 | |||
| 169 | + | } | |||
| 170 | + | ||||
| 171 | + | // Show indicates an expected call of Show. | |||
| 172 | + | func (mr *MockWebViewInterfaceMockRecorder) Show() *gomock.Call { | |||
| 173 | + | mr.mock.ctrl.T.Helper() | |||
| 174 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Show", reflect.TypeOf((*MockWebViewInterface)(nil).Show)) | |||
| 175 | + | } | |||
| 176 | + | ||||
| 177 | + | // Widget mocks base method. | |||
| 178 | + | func (m *MockWebViewInterface) Widget() uintptr { | |||
| 179 | + | m.ctrl.T.Helper() | |||
| 180 | + | ret := m.ctrl.Call(m, "Widget") | |||
| 181 | + | ret0, _ := ret[0].(uintptr) | |||
| 182 | + | return ret0 | |||
| 183 | + | } | |||
| 184 | + | ||||
| 185 | + | // Widget indicates an expected call of Widget. | |||
| 186 | + | func (mr *MockWebViewInterfaceMockRecorder) Widget() *gomock.Call { | |||
| 187 | + | mr.mock.ctrl.T.Helper() | |||
| 188 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Widget", reflect.TypeOf((*MockWebViewInterface)(nil).Widget)) | |||
| 189 | + | } | |||
| 190 | + | ||||
| 191 | + | // Window mocks base method. | |||
| 192 | + | func (m *MockWebViewInterface) Window() *webkit.Window { | |||
| 193 | + | m.ctrl.T.Helper() | |||
| 194 | + | ret := m.ctrl.Call(m, "Window") | |||
| 195 | + | ret0, _ := ret[0].(*webkit.Window) | |||
| 196 | + | return ret0 | |||
| 197 | + | } | |||
| 198 | + | ||||
| 199 | + | // Window indicates an expected call of Window. | |||
| 200 | + | func (mr *MockWebViewInterfaceMockRecorder) Window() *gomock.Call { | |||
| 201 | + | mr.mock.ctrl.T.Helper() | |||
| 202 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Window", reflect.TypeOf((*MockWebViewInterface)(nil).Window)) | |||
| 203 | + | } | |||
| 204 | + | ||||
| 205 | + | // MockPaneInterface is a mock of PaneInterface interface. | |||
| 206 | + | type MockPaneInterface struct { | |||
| 207 | + | ctrl *gomock.Controller | |||
| 208 | + | recorder *MockPaneInterfaceMockRecorder | |||
| 209 | + | } | |||
| 210 | + | ||||
| 211 | + | // MockPaneInterfaceMockRecorder is the mock recorder for MockPaneInterface. | |||
| 212 | + | type MockPaneInterfaceMockRecorder struct { | |||
| 213 | + | mock *MockPaneInterface | |||
| 214 | + | } | |||
| 215 | + | ||||
| 216 | + | // NewMockPaneInterface creates a new mock instance. | |||
| 217 | + | func NewMockPaneInterface(ctrl *gomock.Controller) *MockPaneInterface { | |||
| 218 | + | mock := &MockPaneInterface{ctrl: ctrl} | |||
| 219 | + | mock.recorder = &MockPaneInterfaceMockRecorder{mock} | |||
| 220 | + | return mock | |||
| 221 | + | } | |||
| 222 | + | ||||
| 223 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |||
| 224 | + | func (m *MockPaneInterface) EXPECT() *MockPaneInterfaceMockRecorder { | |||
| 225 | + | return m.recorder | |||
| 226 | + | } | |||
| 227 | + | ||||
| 228 | + | // Cleanup mocks base method. | |||
| 229 | + | func (m *MockPaneInterface) Cleanup() { | |||
| 230 | + | m.ctrl.T.Helper() | |||
| 231 | + | m.ctrl.Call(m, "Cleanup") | |||
| 232 | + | } | |||
| 233 | + | ||||
| 234 | + | // Cleanup indicates an expected call of Cleanup. | |||
| 235 | + | func (mr *MockPaneInterfaceMockRecorder) Cleanup() *gomock.Call { | |||
| 236 | + | mr.mock.ctrl.T.Helper() | |||
| 237 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cleanup", reflect.TypeOf((*MockPaneInterface)(nil).Cleanup)) | |||
| 238 | + | } | |||
| 239 | + | ||||
| 240 | + | // ClipboardController mocks base method. | |||
| 241 | + | func (m *MockPaneInterface) ClipboardController() browser.ClipboardControllerInterface { | |||
| 242 | + | m.ctrl.T.Helper() | |||
| 243 | + | ret := m.ctrl.Call(m, "ClipboardController") | |||
| 244 | + | ret0, _ := ret[0].(browser.ClipboardControllerInterface) | |||
| 245 | + | return ret0 | |||
| 246 | + | } | |||
| 247 | + | ||||
| 248 | + | // ClipboardController indicates an expected call of ClipboardController. | |||
| 249 | + | func (mr *MockPaneInterfaceMockRecorder) ClipboardController() *gomock.Call { | |||
| 250 | + | mr.mock.ctrl.T.Helper() | |||
| 251 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClipboardController", reflect.TypeOf((*MockPaneInterface)(nil).ClipboardController)) | |||
| 252 | + | } | |||
| 253 | + | ||||
| 254 | + | // HasGUI mocks base method. | |||
| 255 | + | func (m *MockPaneInterface) HasGUI() bool { | |||
| 256 | + | m.ctrl.T.Helper() | |||
| 257 | + | ret := m.ctrl.Call(m, "HasGUI") | |||
| 258 | + | ret0, _ := ret[0].(bool) | |||
| 259 | + | return ret0 | |||
| 260 | + | } | |||
| 261 | + | ||||
| 262 | + | // HasGUI indicates an expected call of HasGUI. | |||
| 263 | + | func (mr *MockPaneInterfaceMockRecorder) HasGUI() *gomock.Call { | |||
| 264 | + | mr.mock.ctrl.T.Helper() | |||
| 265 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasGUI", reflect.TypeOf((*MockPaneInterface)(nil).HasGUI)) | |||
| 266 | + | } | |||
| 267 | + | ||||
| 268 | + | // HasGUIComponent mocks base method. | |||
| 269 | + | func (m *MockPaneInterface) HasGUIComponent(component string) bool { | |||
| 270 | + | m.ctrl.T.Helper() | |||
| 271 | + | ret := m.ctrl.Call(m, "HasGUIComponent", component) | |||
| 272 | + | ret0, _ := ret[0].(bool) | |||
| 273 | + | return ret0 | |||
| 274 | + | } | |||
| 275 | + | ||||
| 276 | + | // HasGUIComponent indicates an expected call of HasGUIComponent. | |||
| 277 | + | func (mr *MockPaneInterfaceMockRecorder) HasGUIComponent(component interface{}) *gomock.Call { | |||
| 278 | + | mr.mock.ctrl.T.Helper() | |||
| 279 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasGUIComponent", reflect.TypeOf((*MockPaneInterface)(nil).HasGUIComponent), component) | |||
| 280 | + | } | |||
| 281 | + | ||||
| 282 | + | // ID mocks base method. | |||
| 283 | + | func (m *MockPaneInterface) ID() string { | |||
| 284 | + | m.ctrl.T.Helper() | |||
| 285 | + | ret := m.ctrl.Call(m, "ID") | |||
| 286 | + | ret0, _ := ret[0].(string) | |||
| 287 | + | return ret0 | |||
| 288 | + | } | |||
| 289 | + | ||||
| 290 | + | // ID indicates an expected call of ID. | |||
| 291 | + | func (mr *MockPaneInterfaceMockRecorder) ID() *gomock.Call { | |||
| 292 | + | mr.mock.ctrl.T.Helper() | |||
| 293 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockPaneInterface)(nil).ID)) | |||
| 294 | + | } | |||
| 295 | + | ||||
| 296 | + | // MessageHandler mocks base method. | |||
| 297 | + | func (m *MockPaneInterface) MessageHandler() browser.MessageHandlerInterface { | |||
| 298 | + | m.ctrl.T.Helper() | |||
| 299 | + | ret := m.ctrl.Call(m, "MessageHandler") | |||
| 300 | + | ret0, _ := ret[0].(browser.MessageHandlerInterface) | |||
| 301 | + | return ret0 | |||
| 302 | + | } | |||
| 303 | + | ||||
| 304 | + | // MessageHandler indicates an expected call of MessageHandler. | |||
| 305 | + | func (mr *MockPaneInterfaceMockRecorder) MessageHandler() *gomock.Call { | |||
| 306 | + | mr.mock.ctrl.T.Helper() | |||
| 307 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MessageHandler", reflect.TypeOf((*MockPaneInterface)(nil).MessageHandler)) | |||
| 308 | + | } | |||
| 309 | + | ||||
| 310 | + | // NavigationController mocks base method. | |||
| 311 | + | func (m *MockPaneInterface) NavigationController() browser.NavigationControllerInterface { | |||
| 312 | + | m.ctrl.T.Helper() | |||
| 313 | + | ret := m.ctrl.Call(m, "NavigationController") | |||
| 314 | + | ret0, _ := ret[0].(browser.NavigationControllerInterface) | |||
| 315 | + | return ret0 | |||
| 316 | + | } | |||
| 317 | + | ||||
| 318 | + | // NavigationController indicates an expected call of NavigationController. | |||
| 319 | + | func (mr *MockPaneInterfaceMockRecorder) NavigationController() *gomock.Call { | |||
| 320 | + | mr.mock.ctrl.T.Helper() | |||
| 321 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NavigationController", reflect.TypeOf((*MockPaneInterface)(nil).NavigationController)) | |||
| 322 | + | } | |||
| 323 | + | ||||
| 324 | + | // SetGUIComponent mocks base method. | |||
| 325 | + | func (m *MockPaneInterface) SetGUIComponent(component string, loaded bool) { | |||
| 326 | + | m.ctrl.T.Helper() | |||
| 327 | + | m.ctrl.Call(m, "SetGUIComponent", component, loaded) | |||
| 328 | + | } | |||
| 329 | + | ||||
| 330 | + | // SetGUIComponent indicates an expected call of SetGUIComponent. | |||
| 331 | + | func (mr *MockPaneInterfaceMockRecorder) SetGUIComponent(component, loaded interface{}) *gomock.Call { | |||
| 332 | + | mr.mock.ctrl.T.Helper() | |||
| 333 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetGUIComponent", reflect.TypeOf((*MockPaneInterface)(nil).SetGUIComponent), component, loaded) | |||
| 334 | + | } | |||
| 335 | + | ||||
| 336 | + | // SetHasGUI mocks base method. | |||
| 337 | + | func (m *MockPaneInterface) SetHasGUI(arg0 bool) { | |||
| 338 | + | m.ctrl.T.Helper() | |||
| 339 | + | m.ctrl.Call(m, "SetHasGUI", arg0) | |||
| 340 | + | } | |||
| 341 | + | ||||
| 342 | + | // SetHasGUI indicates an expected call of SetHasGUI. | |||
| 343 | + | func (mr *MockPaneInterfaceMockRecorder) SetHasGUI(arg0 interface{}) *gomock.Call { | |||
| 344 | + | mr.mock.ctrl.T.Helper() | |||
| 345 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHasGUI", reflect.TypeOf((*MockPaneInterface)(nil).SetHasGUI), arg0) | |||
| 346 | + | } | |||
| 347 | + | ||||
| 348 | + | // SetID mocks base method. | |||
| 349 | + | func (m *MockPaneInterface) SetID(arg0 string) { | |||
| 350 | + | m.ctrl.T.Helper() | |||
| 351 | + | m.ctrl.Call(m, "SetID", arg0) | |||
| 352 | + | } | |||
| 353 | + | ||||
| 354 | + | // SetID indicates an expected call of SetID. | |||
| 355 | + | func (mr *MockPaneInterfaceMockRecorder) SetID(arg0 interface{}) *gomock.Call { | |||
| 356 | + | mr.mock.ctrl.T.Helper() | |||
| 357 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetID", reflect.TypeOf((*MockPaneInterface)(nil).SetID), arg0) | |||
| 358 | + | } | |||
| 359 | + | ||||
| 360 | + | // ShortcutHandler mocks base method. | |||
| 361 | + | func (m *MockPaneInterface) ShortcutHandler() browser.ShortcutHandlerInterface { | |||
| 362 | + | m.ctrl.T.Helper() | |||
| 363 | + | ret := m.ctrl.Call(m, "ShortcutHandler") | |||
| 364 | + | ret0, _ := ret[0].(browser.ShortcutHandlerInterface) | |||
| 365 | + | return ret0 | |||
| 366 | + | } | |||
| 367 | + | ||||
| 368 | + | // ShortcutHandler indicates an expected call of ShortcutHandler. | |||
| 369 | + | func (mr *MockPaneInterfaceMockRecorder) ShortcutHandler() *gomock.Call { | |||
| 370 | + | mr.mock.ctrl.T.Helper() | |||
| 371 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShortcutHandler", reflect.TypeOf((*MockPaneInterface)(nil).ShortcutHandler)) | |||
| 372 | + | } | |||
| 373 | + | ||||
| 374 | + | // UpdateLastFocus mocks base method. | |||
| 375 | + | func (m *MockPaneInterface) UpdateLastFocus() { | |||
| 376 | + | m.ctrl.T.Helper() | |||
| 377 | + | m.ctrl.Call(m, "UpdateLastFocus") | |||
| 378 | + | } | |||
| 379 | + | ||||
| 380 | + | // UpdateLastFocus indicates an expected call of UpdateLastFocus. | |||
| 381 | + | func (mr *MockPaneInterfaceMockRecorder) UpdateLastFocus() *gomock.Call { | |||
| 382 | + | mr.mock.ctrl.T.Helper() | |||
| 383 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLastFocus", reflect.TypeOf((*MockPaneInterface)(nil).UpdateLastFocus)) | |||
| 384 | + | } | |||
| 385 | + | ||||
| 386 | + | // WebView mocks base method. | |||
| 387 | + | func (m *MockPaneInterface) WebView() browser.WebViewInterface { | |||
| 388 | + | m.ctrl.T.Helper() | |||
| 389 | + | ret := m.ctrl.Call(m, "WebView") | |||
| 390 | + | ret0, _ := ret[0].(browser.WebViewInterface) | |||
| 391 | + | return ret0 | |||
| 392 | + | } | |||
| 393 | + | ||||
| 394 | + | // WebView indicates an expected call of WebView. | |||
| 395 | + | func (mr *MockPaneInterfaceMockRecorder) WebView() *gomock.Call { | |||
| 396 | + | mr.mock.ctrl.T.Helper() | |||
| 397 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WebView", reflect.TypeOf((*MockPaneInterface)(nil).WebView)) | |||
| 398 | + | } | |||
| 399 | + | ||||
| 400 | + | // ZoomController mocks base method. | |||
| 401 | + | func (m *MockPaneInterface) ZoomController() browser.ZoomControllerInterface { | |||
| 402 | + | m.ctrl.T.Helper() | |||
| 403 | + | ret := m.ctrl.Call(m, "ZoomController") | |||
| 404 | + | ret0, _ := ret[0].(browser.ZoomControllerInterface) | |||
| 405 | + | return ret0 | |||
| 406 | + | } | |||
| 407 | + | ||||
| 408 | + | // ZoomController indicates an expected call of ZoomController. | |||
| 409 | + | func (mr *MockPaneInterfaceMockRecorder) ZoomController() *gomock.Call { | |||
| 410 | + | mr.mock.ctrl.T.Helper() | |||
| 411 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ZoomController", reflect.TypeOf((*MockPaneInterface)(nil).ZoomController)) | |||
| 412 | + | } | |||
| 413 | + | ||||
| 414 | + | // MockMessageHandlerInterface is a mock of MessageHandlerInterface interface. | |||
| 415 | + | type MockMessageHandlerInterface struct { | |||
| 416 | + | ctrl *gomock.Controller | |||
| 417 | + | recorder *MockMessageHandlerInterfaceMockRecorder | |||
| 418 | + | } | |||
| 419 | + | ||||
| 420 | + | // MockMessageHandlerInterfaceMockRecorder is the mock recorder for MockMessageHandlerInterface. | |||
| 421 | + | type MockMessageHandlerInterfaceMockRecorder struct { | |||
| 422 | + | mock *MockMessageHandlerInterface | |||
| 423 | + | } | |||
| 424 | + | ||||
| 425 | + | // NewMockMessageHandlerInterface creates a new mock instance. | |||
| 426 | + | func NewMockMessageHandlerInterface(ctrl *gomock.Controller) *MockMessageHandlerInterface { | |||
| 427 | + | mock := &MockMessageHandlerInterface{ctrl: ctrl} | |||
| 428 | + | mock.recorder = &MockMessageHandlerInterfaceMockRecorder{mock} | |||
| 429 | + | return mock | |||
| 430 | + | } | |||
| 431 | + | ||||
| 432 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |||
| 433 | + | func (m *MockMessageHandlerInterface) EXPECT() *MockMessageHandlerInterfaceMockRecorder { | |||
| 434 | + | return m.recorder | |||
| 435 | + | } | |||
| 436 | + | ||||
| 437 | + | // SetWorkspaceObserver mocks base method. | |||
| 438 | + | func (m *MockMessageHandlerInterface) SetWorkspaceObserver(observer messaging.WorkspaceObserver) { | |||
| 439 | + | m.ctrl.T.Helper() | |||
| 440 | + | m.ctrl.Call(m, "SetWorkspaceObserver", observer) | |||
| 441 | + | } | |||
| 442 | + | ||||
| 443 | + | // SetWorkspaceObserver indicates an expected call of SetWorkspaceObserver. | |||
| 444 | + | func (mr *MockMessageHandlerInterfaceMockRecorder) SetWorkspaceObserver(observer interface{}) *gomock.Call { | |||
| 445 | + | mr.mock.ctrl.T.Helper() | |||
| 446 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWorkspaceObserver", reflect.TypeOf((*MockMessageHandlerInterface)(nil).SetWorkspaceObserver), observer) | |||
| 447 | + | } | |||
| 448 | + | ||||
| 449 | + | // MockNavigationControllerInterface is a mock of NavigationControllerInterface interface. | |||
| 450 | + | type MockNavigationControllerInterface struct { | |||
| 451 | + | ctrl *gomock.Controller | |||
| 452 | + | recorder *MockNavigationControllerInterfaceMockRecorder | |||
| 453 | + | } | |||
| 454 | + | ||||
| 455 | + | // MockNavigationControllerInterfaceMockRecorder is the mock recorder for MockNavigationControllerInterface. | |||
| 456 | + | type MockNavigationControllerInterfaceMockRecorder struct { | |||
| 457 | + | mock *MockNavigationControllerInterface | |||
| 458 | + | } | |||
| 459 | + | ||||
| 460 | + | // NewMockNavigationControllerInterface creates a new mock instance. | |||
| 461 | + | func NewMockNavigationControllerInterface(ctrl *gomock.Controller) *MockNavigationControllerInterface { | |||
| 462 | + | mock := &MockNavigationControllerInterface{ctrl: ctrl} | |||
| 463 | + | mock.recorder = &MockNavigationControllerInterfaceMockRecorder{mock} | |||
| 464 | + | return mock | |||
| 465 | + | } | |||
| 466 | + | ||||
| 467 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |||
| 468 | + | func (m *MockNavigationControllerInterface) EXPECT() *MockNavigationControllerInterfaceMockRecorder { | |||
| 469 | + | return m.recorder | |||
| 470 | + | } | |||
| 471 | + | ||||
| 472 | + | // NavigateToURL mocks base method. | |||
| 473 | + | func (m *MockNavigationControllerInterface) NavigateToURL(url string) error { | |||
| 474 | + | m.ctrl.T.Helper() | |||
| 475 | + | ret := m.ctrl.Call(m, "NavigateToURL", url) | |||
| 476 | + | ret0, _ := ret[0].(error) | |||
| 477 | + | return ret0 | |||
| 478 | + | } | |||
| 479 | + | ||||
| 480 | + | // NavigateToURL indicates an expected call of NavigateToURL. | |||
| 481 | + | func (mr *MockNavigationControllerInterfaceMockRecorder) NavigateToURL(url interface{}) *gomock.Call { | |||
| 482 | + | mr.mock.ctrl.T.Helper() | |||
| 483 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NavigateToURL", reflect.TypeOf((*MockNavigationControllerInterface)(nil).NavigateToURL), url) | |||
| 484 | + | } | |||
| 485 | + | ||||
| 486 | + | // MockZoomControllerInterface is a mock of ZoomControllerInterface interface. | |||
| 487 | + | type MockZoomControllerInterface struct { | |||
| 488 | + | ctrl *gomock.Controller | |||
| 489 | + | recorder *MockZoomControllerInterfaceMockRecorder | |||
| 490 | + | } | |||
| 491 | + | ||||
| 492 | + | // MockZoomControllerInterfaceMockRecorder is the mock recorder for MockZoomControllerInterface. | |||
| 493 | + | type MockZoomControllerInterfaceMockRecorder struct { | |||
| 494 | + | mock *MockZoomControllerInterface | |||
| 495 | + | } | |||
| 496 | + | ||||
| 497 | + | // NewMockZoomControllerInterface creates a new mock instance. | |||
| 498 | + | func NewMockZoomControllerInterface(ctrl *gomock.Controller) *MockZoomControllerInterface { | |||
| 499 | + | mock := &MockZoomControllerInterface{ctrl: ctrl} | |||
| 500 | + | mock.recorder = &MockZoomControllerInterfaceMockRecorder{mock} | |||
| 501 | + | return mock | |||
| 502 | + | } | |||
| 503 | + | ||||
| 504 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |||
| 505 | + | func (m *MockZoomControllerInterface) EXPECT() *MockZoomControllerInterfaceMockRecorder { | |||
| 506 | + | return m.recorder | |||
| 507 | + | } | |||
| 508 | + | ||||
| 509 | + | // ApplyInitialZoom mocks base method. | |||
| 510 | + | func (m *MockZoomControllerInterface) ApplyInitialZoom() { | |||
| 511 | + | m.ctrl.T.Helper() | |||
| 512 | + | m.ctrl.Call(m, "ApplyInitialZoom") | |||
| 513 | + | } | |||
| 514 | + | ||||
| 515 | + | // ApplyInitialZoom indicates an expected call of ApplyInitialZoom. | |||
| 516 | + | func (mr *MockZoomControllerInterfaceMockRecorder) ApplyInitialZoom() *gomock.Call { | |||
| 517 | + | mr.mock.ctrl.T.Helper() | |||
| 518 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyInitialZoom", reflect.TypeOf((*MockZoomControllerInterface)(nil).ApplyInitialZoom)) | |||
| 519 | + | } | |||
| 520 | + | ||||
| 521 | + | // MockClipboardControllerInterface is a mock of ClipboardControllerInterface interface. | |||
| 522 | + | type MockClipboardControllerInterface struct { | |||
| 523 | + | ctrl *gomock.Controller | |||
| 524 | + | recorder *MockClipboardControllerInterfaceMockRecorder | |||
| 525 | + | } | |||
| 526 | + | ||||
| 527 | + | // MockClipboardControllerInterfaceMockRecorder is the mock recorder for MockClipboardControllerInterface. | |||
| 528 | + | type MockClipboardControllerInterfaceMockRecorder struct { | |||
| 529 | + | mock *MockClipboardControllerInterface | |||
| 530 | + | } | |||
| 531 | + | ||||
| 532 | + | // NewMockClipboardControllerInterface creates a new mock instance. | |||
| 533 | + | func NewMockClipboardControllerInterface(ctrl *gomock.Controller) *MockClipboardControllerInterface { | |||
| 534 | + | mock := &MockClipboardControllerInterface{ctrl: ctrl} | |||
| 535 | + | mock.recorder = &MockClipboardControllerInterfaceMockRecorder{mock} | |||
| 536 | + | return mock | |||
| 537 | + | } | |||
| 538 | + | ||||
| 539 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |||
| 540 | + | func (m *MockClipboardControllerInterface) EXPECT() *MockClipboardControllerInterfaceMockRecorder { | |||
| 541 | + | return m.recorder | |||
| 542 | + | } | |||
| 543 | + | ||||
| 544 | + | // MockShortcutHandlerInterface is a mock of ShortcutHandlerInterface interface. | |||
| 545 | + | type MockShortcutHandlerInterface struct { | |||
| 546 | + | ctrl *gomock.Controller | |||
| 547 | + | recorder *MockShortcutHandlerInterfaceMockRecorder | |||
| 548 | + | } | |||
| 549 | + | ||||
| 550 | + | // MockShortcutHandlerInterfaceMockRecorder is the mock recorder for MockShortcutHandlerInterface. | |||
| 551 | + | type MockShortcutHandlerInterfaceMockRecorder struct { | |||
| 552 | + | mock *MockShortcutHandlerInterface | |||
| 553 | + | } | |||
| 554 | + | ||||
| 555 | + | // NewMockShortcutHandlerInterface creates a new mock instance. | |||
| 556 | + | func NewMockShortcutHandlerInterface(ctrl *gomock.Controller) *MockShortcutHandlerInterface { | |||
| 557 | + | mock := &MockShortcutHandlerInterface{ctrl: ctrl} | |||
| 558 | + | mock.recorder = &MockShortcutHandlerInterfaceMockRecorder{mock} | |||
| 559 | + | return mock | |||
| 560 | + | } | |||
| 561 | + | ||||
| 562 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |||
| 563 | + | func (m *MockShortcutHandlerInterface) EXPECT() *MockShortcutHandlerInterfaceMockRecorder { | |||
| 564 | + | return m.recorder | |||
| 565 | + | } | |||
| 566 | + | ||||
| 567 | + | // MockBrowserAppInterface is a mock of BrowserAppInterface interface. | |||
| 568 | + | type MockBrowserAppInterface struct { | |||
| 569 | + | ctrl *gomock.Controller | |||
| 570 | + | recorder *MockBrowserAppInterfaceMockRecorder | |||
| 571 | + | } | |||
| 572 | + | ||||
| 573 | + | // MockBrowserAppInterfaceMockRecorder is the mock recorder for MockBrowserAppInterface. | |||
| 574 | + | type MockBrowserAppInterfaceMockRecorder struct { | |||
| 575 | + | mock *MockBrowserAppInterface | |||
| 576 | + | } | |||
| 577 | + | ||||
| 578 | + | // NewMockBrowserAppInterface creates a new mock instance. | |||
| 579 | + | func NewMockBrowserAppInterface(ctrl *gomock.Controller) *MockBrowserAppInterface { | |||
| 580 | + | mock := &MockBrowserAppInterface{ctrl: ctrl} | |||
| 581 | + | mock.recorder = &MockBrowserAppInterfaceMockRecorder{mock} | |||
| 582 | + | return mock | |||
| 583 | + | } | |||
| 584 | + | ||||
| 585 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |||
| 586 | + | func (m *MockBrowserAppInterface) EXPECT() *MockBrowserAppInterfaceMockRecorder { | |||
| 587 | + | return m.recorder | |||
| 588 | + | } | |||
| 589 | + | ||||
| 590 | + | // AppendPane mocks base method. | |||
| 591 | + | func (m *MockBrowserAppInterface) AppendPane(pane *browser.BrowserPane) { | |||
| 592 | + | m.ctrl.T.Helper() | |||
| 593 | + | m.ctrl.Call(m, "AppendPane", pane) | |||
| 594 | + | } | |||
| 595 | + | ||||
| 596 | + | // AppendPane indicates an expected call of AppendPane. | |||
| 597 | + | func (mr *MockBrowserAppInterfaceMockRecorder) AppendPane(pane interface{}) *gomock.Call { | |||
| 598 | + | mr.mock.ctrl.T.Helper() | |||
| 599 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppendPane", reflect.TypeOf((*MockBrowserAppInterface)(nil).AppendPane), pane) | |||
| 600 | + | } | |||
| 601 | + | ||||
| 602 | + | // BuildWebkitConfig mocks base method. | |||
| 603 | + | func (m *MockBrowserAppInterface) BuildWebkitConfig() (*webkit.Config, error) { | |||
| 604 | + | m.ctrl.T.Helper() | |||
| 605 | + | ret := m.ctrl.Call(m, "BuildWebkitConfig") | |||
| 606 | + | ret0, _ := ret[0].(*webkit.Config) | |||
| 607 | + | ret1, _ := ret[1].(error) | |||
| 608 | + | return ret0, ret1 | |||
| 609 | + | } | |||
| 610 | + | ||||
| 611 | + | // BuildWebkitConfig indicates an expected call of BuildWebkitConfig. | |||
| 612 | + | func (mr *MockBrowserAppInterfaceMockRecorder) BuildWebkitConfig() *gomock.Call { | |||
| 613 | + | mr.mock.ctrl.T.Helper() | |||
| 614 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildWebkitConfig", reflect.TypeOf((*MockBrowserAppInterface)(nil).BuildWebkitConfig)) | |||
| 615 | + | } | |||
| 616 | + | ||||
| 617 | + | // CreatePaneForView mocks base method. | |||
| 618 | + | func (m *MockBrowserAppInterface) CreatePaneForView(view *webkit.WebView) (*browser.BrowserPane, error) { | |||
| 619 | + | m.ctrl.T.Helper() | |||
| 620 | + | ret := m.ctrl.Call(m, "CreatePaneForView", view) | |||
| 621 | + | ret0, _ := ret[0].(*browser.BrowserPane) | |||
| 622 | + | ret1, _ := ret[1].(error) | |||
| 623 | + | return ret0, ret1 | |||
| 624 | + | } | |||
| 625 | + | ||||
| 626 | + | // CreatePaneForView indicates an expected call of CreatePaneForView. | |||
| 627 | + | func (mr *MockBrowserAppInterfaceMockRecorder) CreatePaneForView(view interface{}) *gomock.Call { | |||
| 628 | + | mr.mock.ctrl.T.Helper() | |||
| 629 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePaneForView", reflect.TypeOf((*MockBrowserAppInterface)(nil).CreatePaneForView), view) | |||
| 630 | + | } | |||
| 631 | + | ||||
| 632 | + | // GetActivePane mocks base method. | |||
| 633 | + | func (m *MockBrowserAppInterface) GetActivePane() *browser.BrowserPane { | |||
| 634 | + | m.ctrl.T.Helper() | |||
| 635 | + | ret := m.ctrl.Call(m, "GetActivePane") | |||
| 636 | + | ret0, _ := ret[0].(*browser.BrowserPane) | |||
| 637 | + | return ret0 | |||
| 638 | + | } | |||
| 639 | + | ||||
| 640 | + | // GetActivePane indicates an expected call of GetActivePane. | |||
| 641 | + | func (mr *MockBrowserAppInterfaceMockRecorder) GetActivePane() *gomock.Call { | |||
| 642 | + | mr.mock.ctrl.T.Helper() | |||
| 643 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetActivePane", reflect.TypeOf((*MockBrowserAppInterface)(nil).GetActivePane)) | |||
| 644 | + | } | |||
| 645 | + | ||||
| 646 | + | // GetPanes mocks base method. | |||
| 647 | + | func (m *MockBrowserAppInterface) GetPanes() []*browser.BrowserPane { | |||
| 648 | + | m.ctrl.T.Helper() | |||
| 649 | + | ret := m.ctrl.Call(m, "GetPanes") | |||
| 650 | + | ret0, _ := ret[0].([]*browser.BrowserPane) | |||
| 651 | + | return ret0 | |||
| 652 | + | } | |||
| 653 | + | ||||
| 654 | + | // GetPanes indicates an expected call of GetPanes. | |||
| 655 | + | func (mr *MockBrowserAppInterfaceMockRecorder) GetPanes() *gomock.Call { | |||
| 656 | + | mr.mock.ctrl.T.Helper() | |||
| 657 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPanes", reflect.TypeOf((*MockBrowserAppInterface)(nil).GetPanes)) | |||
| 658 | + | } | |||
| 659 | + | ||||
| 660 | + | // RemovePane mocks base method. | |||
| 661 | + | func (m *MockBrowserAppInterface) RemovePane(pane *browser.BrowserPane) { | |||
| 662 | + | m.ctrl.T.Helper() | |||
| 663 | + | m.ctrl.Call(m, "RemovePane", pane) | |||
| 664 | + | } | |||
| 665 | + | ||||
| 666 | + | // RemovePane indicates an expected call of RemovePane. | |||
| 667 | + | func (mr *MockBrowserAppInterfaceMockRecorder) RemovePane(pane interface{}) *gomock.Call { | |||
| 668 | + | mr.mock.ctrl.T.Helper() | |||
| 669 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemovePane", reflect.TypeOf((*MockBrowserAppInterface)(nil).RemovePane), pane) | |||
| 670 | + | } | |||
| 671 | + | ||||
| 672 | + | // SetActivePane mocks base method. | |||
| 673 | + | func (m *MockBrowserAppInterface) SetActivePane(pane *browser.BrowserPane) { | |||
| 674 | + | m.ctrl.T.Helper() | |||
| 675 | + | m.ctrl.Call(m, "SetActivePane", pane) | |||
| 676 | + | } | |||
| 677 | + | ||||
| 678 | + | // SetActivePane indicates an expected call of SetActivePane. | |||
| 679 | + | func (mr *MockBrowserAppInterfaceMockRecorder) SetActivePane(pane interface{}) *gomock.Call { | |||
| 680 | + | mr.mock.ctrl.T.Helper() | |||
| 681 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetActivePane", reflect.TypeOf((*MockBrowserAppInterface)(nil).SetActivePane), pane) | |||
| 682 | + | } | |||
| 683 | + | ||||
| 684 | + | // MockWindowInterface is a mock of WindowInterface interface. | |||
| 685 | + | type MockWindowInterface struct { | |||
| 686 | + | ctrl *gomock.Controller | |||
| 687 | + | recorder *MockWindowInterfaceMockRecorder | |||
| 688 | + | } | |||
| 689 | + | ||||
| 690 | + | // MockWindowInterfaceMockRecorder is the mock recorder for MockWindowInterface. | |||
| 691 | + | type MockWindowInterfaceMockRecorder struct { | |||
| 692 | + | mock *MockWindowInterface | |||
| 693 | + | } | |||
| 694 | + | ||||
| 695 | + | // NewMockWindowInterface creates a new mock instance. | |||
| 696 | + | func NewMockWindowInterface(ctrl *gomock.Controller) *MockWindowInterface { | |||
| 697 | + | mock := &MockWindowInterface{ctrl: ctrl} | |||
| 698 | + | mock.recorder = &MockWindowInterfaceMockRecorder{mock} | |||
| 699 | + | return mock | |||
| 700 | + | } | |||
| 701 | + | ||||
| 702 | + | // EXPECT returns an object that allows the caller to indicate expected use. | |||
| 703 | + | func (m *MockWindowInterface) EXPECT() *MockWindowInterfaceMockRecorder { | |||
| 704 | + | return m.recorder | |||
| 705 | + | } | |||
| 706 | + | ||||
| 707 | + | // SetChild mocks base method. | |||
| 708 | + | func (m *MockWindowInterface) SetChild(widget uintptr) { | |||
| 709 | + | m.ctrl.T.Helper() | |||
| 710 | + | m.ctrl.Call(m, "SetChild", widget) | |||
| 711 | + | } | |||
| 712 | + | ||||
| 713 | + | // SetChild indicates an expected call of SetChild. | |||
| 714 | + | func (mr *MockWindowInterfaceMockRecorder) SetChild(widget interface{}) *gomock.Call { | |||
| 715 | + | mr.mock.ctrl.T.Helper() | |||
| 716 | + | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetChild", reflect.TypeOf((*MockWindowInterface)(nil).SetChild), widget) | |||
| 717 | + | } | |||
M internal/app/browser/webview.go +1 -1
| @@ -260,7 +260,7 @@ func (app *BrowserApp) createWebView() error { | |||
| 260 | 260 | if app.windowShortcutHandler != nil { | |
| 261 | 261 | log.Printf("Window-level global shortcuts initialized") | |
| 262 | 262 | } else { | |
| 263 | - | log.Printf("Warning: Failed to initialize window-level shortcuts") | |
| 263 | + | log.Printf("Warning: failed to initialize window-level shortcuts") | |
| 264 | 264 | } | |
| 265 | 265 | } | |
| 266 | 266 | ||
| @@ -260,7 +260,7 @@ func (app *BrowserApp) createWebView() error { | |||||
| 260 | if app.windowShortcutHandler != nil { | 260 | if app.windowShortcutHandler != nil { | ||
| 261 | log.Printf("Window-level global shortcuts initialized") | 261 | log.Printf("Window-level global shortcuts initialized") | ||
| 262 | } else { | 262 | } else { | ||
| 263 | - | log.Printf("Warning: Failed to initialize window-level shortcuts") | 263 | + | log.Printf("Warning: failed to initialize window-level shortcuts") |
| 264 | } | 264 | } | ||
| 265 | } | 265 | } | ||
| 266 | 266 | ||||
M internal/app/browser/workspace_manager.go +35 -14
| @@ -599,6 +599,17 @@ func (wm *WorkspaceManager) closestLeafFromSubtree(node *paneNode, cx, cy float6 | |||
| 599 | 599 | } | |
| 600 | 600 | ||
| 601 | 601 | func (wm *WorkspaceManager) boundaryFallback(node *paneNode, direction string) *paneNode { | |
| 602 | + | return wm.boundaryFallbackWithDepth(node, direction, 0) | |
| 603 | + | } | |
| 604 | + | ||
| 605 | + | func (wm *WorkspaceManager) boundaryFallbackWithDepth(node *paneNode, direction string, depth int) *paneNode { | |
| 606 | + | // Prevent infinite recursion - max tree depth should be reasonable | |
| 607 | + | const maxDepth = 50 | |
| 608 | + | if depth > maxDepth { | |
| 609 | + | log.Printf("[workspace] boundaryFallback: max depth exceeded, possible tree corruption") | |
| 610 | + | return nil | |
| 611 | + | } | |
| 612 | + | ||
| 602 | 613 | if node == nil { | |
| 603 | 614 | return nil | |
| 604 | 615 | } | |
| @@ -607,25 +618,25 @@ func (wm *WorkspaceManager) boundaryFallback(node *paneNode, direction string) * | |||
| 607 | 618 | } | |
| 608 | 619 | switch direction { | |
| 609 | 620 | case "up": | |
| 610 | - | if leaf := wm.boundaryFallback(node.right, direction); leaf != nil { | |
| 621 | + | if leaf := wm.boundaryFallbackWithDepth(node.right, direction, depth+1); leaf != nil { | |
| 611 | 622 | return leaf | |
| 612 | 623 | } | |
| 613 | - | return wm.boundaryFallback(node.left, direction) | |
| 624 | + | return wm.boundaryFallbackWithDepth(node.left, direction, depth+1) | |
| 614 | 625 | case "down": | |
| 615 | - | if leaf := wm.boundaryFallback(node.left, direction); leaf != nil { | |
| 626 | + | if leaf := wm.boundaryFallbackWithDepth(node.left, direction, depth+1); leaf != nil { | |
| 616 | 627 | return leaf | |
| 617 | 628 | } | |
| 618 | - | return wm.boundaryFallback(node.right, direction) | |
| 629 | + | return wm.boundaryFallbackWithDepth(node.right, direction, depth+1) | |
| 619 | 630 | case "left": | |
| 620 | - | if leaf := wm.boundaryFallback(node.right, direction); leaf != nil { | |
| 631 | + | if leaf := wm.boundaryFallbackWithDepth(node.right, direction, depth+1); leaf != nil { | |
| 621 | 632 | return leaf | |
| 622 | 633 | } | |
| 623 | - | return wm.boundaryFallback(node.left, direction) | |
| 634 | + | return wm.boundaryFallbackWithDepth(node.left, direction, depth+1) | |
| 624 | 635 | case "right": | |
| 625 | - | if leaf := wm.boundaryFallback(node.left, direction); leaf != nil { | |
| 636 | + | if leaf := wm.boundaryFallbackWithDepth(node.left, direction, depth+1); leaf != nil { | |
| 626 | 637 | return leaf | |
| 627 | 638 | } | |
| 628 | - | return wm.boundaryFallback(node.right, direction) | |
| 639 | + | return wm.boundaryFallbackWithDepth(node.right, direction, depth+1) | |
| 629 | 640 | default: | |
| 630 | 641 | return nil | |
| 631 | 642 | } | |
| @@ -637,19 +648,29 @@ func (wm *WorkspaceManager) collectLeaves() []*paneNode { | |||
| 637 | 648 | ||
| 638 | 649 | func (wm *WorkspaceManager) collectLeavesFrom(node *paneNode) []*paneNode { | |
| 639 | 650 | var leaves []*paneNode | |
| 640 | - | var walk func(*paneNode) | |
| 641 | - | walk = func(n *paneNode) { | |
| 642 | - | if n == nil { | |
| 651 | + | visited := make(map[*paneNode]bool) | |
| 652 | + | ||
| 653 | + | var walk func(*paneNode, int) | |
| 654 | + | walk = func(n *paneNode, depth int) { | |
| 655 | + | // Prevent infinite recursion and cycles | |
| 656 | + | const maxDepth = 50 | |
| 657 | + | if n == nil || depth > maxDepth { | |
| 658 | + | return | |
| 659 | + | } | |
| 660 | + | if visited[n] { | |
| 661 | + | log.Printf("[workspace] collectLeavesFrom: cycle detected in tree") | |
| 643 | 662 | return | |
| 644 | 663 | } | |
| 664 | + | visited[n] = true | |
| 665 | + | ||
| 645 | 666 | if n.isLeaf { | |
| 646 | 667 | leaves = append(leaves, n) | |
| 647 | 668 | return | |
| 648 | 669 | } | |
| 649 | - | walk(n.left) | |
| 650 | - | walk(n.right) | |
| 670 | + | walk(n.left, depth+1) | |
| 671 | + | walk(n.right, depth+1) | |
| 651 | 672 | } | |
| 652 | - | walk(node) | |
| 673 | + | walk(node, 0) | |
| 653 | 674 | return leaves | |
| 654 | 675 | } | |
| 655 | 676 | ||
| @@ -599,6 +599,17 @@ func (wm *WorkspaceManager) closestLeafFromSubtree(node *paneNode, cx, cy float6 | |||||
| 599 | } | 599 | } | ||
| 600 | 600 | ||||
| 601 | func (wm *WorkspaceManager) boundaryFallback(node *paneNode, direction string) *paneNode { | 601 | func (wm *WorkspaceManager) boundaryFallback(node *paneNode, direction string) *paneNode { | ||
| 602 | + | return wm.boundaryFallbackWithDepth(node, direction, 0) | |||
| 603 | + | } | |||
| 604 | + | ||||
| 605 | + | func (wm *WorkspaceManager) boundaryFallbackWithDepth(node *paneNode, direction string, depth int) *paneNode { | |||
| 606 | + | // Prevent infinite recursion - max tree depth should be reasonable | |||
| 607 | + | const maxDepth = 50 | |||
| 608 | + | if depth > maxDepth { | |||
| 609 | + | log.Printf("[workspace] boundaryFallback: max depth exceeded, possible tree corruption") | |||
| 610 | + | return nil | |||
| 611 | + | } | |||
| 612 | + | ||||
| 602 | if node == nil { | 613 | if node == nil { | ||
| 603 | return nil | 614 | return nil | ||
| 604 | } | 615 | } | ||
| @@ -607,25 +618,25 @@ func (wm *WorkspaceManager) boundaryFallback(node *paneNode, direction string) * | |||||
| 607 | } | 618 | } | ||
| 608 | switch direction { | 619 | switch direction { | ||
| 609 | case "up": | 620 | case "up": | ||
| 610 | - | if leaf := wm.boundaryFallback(node.right, direction); leaf != nil { | 621 | + | if leaf := wm.boundaryFallbackWithDepth(node.right, direction, depth+1); leaf != nil { |
| 611 | return leaf | 622 | return leaf | ||
| 612 | } | 623 | } | ||
| 613 | - | return wm.boundaryFallback(node.left, direction) | 624 | + | return wm.boundaryFallbackWithDepth(node.left, direction, depth+1) |
| 614 | case "down": | 625 | case "down": | ||
| 615 | - | if leaf := wm.boundaryFallback(node.left, direction); leaf != nil { | 626 | + | if leaf := wm.boundaryFallbackWithDepth(node.left, direction, depth+1); leaf != nil { |
| 616 | return leaf | 627 | return leaf | ||
| 617 | } | 628 | } | ||
| 618 | - | return wm.boundaryFallback(node.right, direction) | 629 | + | return wm.boundaryFallbackWithDepth(node.right, direction, depth+1) |
| 619 | case "left": | 630 | case "left": | ||
| 620 | - | if leaf := wm.boundaryFallback(node.right, direction); leaf != nil { | 631 | + | if leaf := wm.boundaryFallbackWithDepth(node.right, direction, depth+1); leaf != nil { |
| 621 | return leaf | 632 | return leaf | ||
| 622 | } | 633 | } | ||
| 623 | - | return wm.boundaryFallback(node.left, direction) | 634 | + | return wm.boundaryFallbackWithDepth(node.left, direction, depth+1) |
| 624 | case "right": | 635 | case "right": | ||
| 625 | - | if leaf := wm.boundaryFallback(node.left, direction); leaf != nil { | 636 | + | if leaf := wm.boundaryFallbackWithDepth(node.left, direction, depth+1); leaf != nil { |
| 626 | return leaf | 637 | return leaf | ||
| 627 | } | 638 | } | ||
| 628 | - | return wm.boundaryFallback(node.right, direction) | 639 | + | return wm.boundaryFallbackWithDepth(node.right, direction, depth+1) |
| 629 | default: | 640 | default: | ||
| 630 | return nil | 641 | return nil | ||
| 631 | } | 642 | } | ||
| @@ -637,19 +648,29 @@ func (wm *WorkspaceManager) collectLeaves() []*paneNode { | |||||
| 637 | 648 | ||||
| 638 | func (wm *WorkspaceManager) collectLeavesFrom(node *paneNode) []*paneNode { | 649 | func (wm *WorkspaceManager) collectLeavesFrom(node *paneNode) []*paneNode { | ||
| 639 | var leaves []*paneNode | 650 | var leaves []*paneNode | ||
| 640 | - | var walk func(*paneNode) | 651 | + | visited := make(map[*paneNode]bool) |
| 641 | - | walk = func(n *paneNode) { | 652 | + | |
| 642 | - | if n == nil { | 653 | + | var walk func(*paneNode, int) |
| 654 | + | walk = func(n *paneNode, depth int) { | |||
| 655 | + | // Prevent infinite recursion and cycles | |||
| 656 | + | const maxDepth = 50 | |||
| 657 | + | if n == nil || depth > maxDepth { | |||
| 658 | + | return | |||
| 659 | + | } | |||
| 660 | + | if visited[n] { | |||
| 661 | + | log.Printf("[workspace] collectLeavesFrom: cycle detected in tree") | |||
| 643 | return | 662 | return | ||
| 644 | } | 663 | } | ||
| 664 | + | visited[n] = true | |||
| 665 | + | ||||
| 645 | if n.isLeaf { | 666 | if n.isLeaf { | ||
| 646 | leaves = append(leaves, n) | 667 | leaves = append(leaves, n) | ||
| 647 | return | 668 | return | ||
| 648 | } | 669 | } | ||
| 649 | - | walk(n.left) | 670 | + | walk(n.left, depth+1) |
| 650 | - | walk(n.right) | 671 | + | walk(n.right, depth+1) |
| 651 | } | 672 | } | ||
| 652 | - | walk(node) | 673 | + | walk(node, 0) |
| 653 | return leaves | 674 | return leaves | ||
| 654 | } | 675 | } | ||
| 655 | 676 | ||||
A internal/app/browser/workspace_manager_bench_test.go +344 -0
| @@ -0,0 +1,344 @@ | |||
| 1 | + | package browser | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | "time" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/dumber/pkg/webkit" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | func BenchmarkSplitNode(b *testing.B) { | |
| 11 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 12 | + | ||
| 13 | + | b.ResetTimer() | |
| 14 | + | for i := 0; i < b.N; i++ { | |
| 15 | + | b.StopTimer() | |
| 16 | + | // Reset to single node for each iteration | |
| 17 | + | wm = newTestWorkspaceManagerWithMocks(b) | |
| 18 | + | b.StartTimer() | |
| 19 | + | ||
| 20 | + | _, err := wm.splitNode(wm.root, "right") | |
| 21 | + | if err != nil { | |
| 22 | + | b.Fatalf("Split failed: %v", err) | |
| 23 | + | } | |
| 24 | + | } | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | func BenchmarkClosePane(b *testing.B) { | |
| 28 | + | for i := 0; i < b.N; i++ { | |
| 29 | + | b.StopTimer() | |
| 30 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 31 | + | node, err := wm.splitNode(wm.root, "right") | |
| 32 | + | if err != nil { | |
| 33 | + | b.Fatalf("Setup split failed: %v", err) | |
| 34 | + | } | |
| 35 | + | b.StartTimer() | |
| 36 | + | ||
| 37 | + | err = wm.closePane(node) | |
| 38 | + | if err != nil { | |
| 39 | + | b.Fatalf("Close failed: %v", err) | |
| 40 | + | } | |
| 41 | + | } | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | func BenchmarkTreeTraversal(b *testing.B) { | |
| 45 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 46 | + | ||
| 47 | + | // Build a complex tree with 15 leaves | |
| 48 | + | leaves := []*paneNode{wm.root} | |
| 49 | + | for i := 0; i < 14; i++ { | |
| 50 | + | if len(leaves) > 0 { | |
| 51 | + | // Split the first leaf | |
| 52 | + | target := leaves[0] | |
| 53 | + | leaves = leaves[1:] // Remove the target from leaves | |
| 54 | + | ||
| 55 | + | newNode, err := wm.splitNode(target, "right") | |
| 56 | + | if err != nil { | |
| 57 | + | b.Fatalf("Setup split %d failed: %v", i, err) | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | // Add both resulting leaves back | |
| 61 | + | leaves = append(leaves, target, newNode) | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | b.ResetTimer() | |
| 66 | + | for i := 0; i < b.N; i++ { | |
| 67 | + | wm.collectLeaves() | |
| 68 | + | } | |
| 69 | + | } | |
| 70 | + | ||
| 71 | + | func BenchmarkCollectLeavesFrom(b *testing.B) { | |
| 72 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 73 | + | ||
| 74 | + | // Build a balanced binary tree | |
| 75 | + | for i := 0; i < 10; i++ { | |
| 76 | + | leaves := wm.collectLeaves() | |
| 77 | + | if len(leaves) > 0 { | |
| 78 | + | _, err := wm.splitNode(leaves[0], "right") | |
| 79 | + | if err != nil { | |
| 80 | + | b.Fatalf("Setup split %d failed: %v", i, err) | |
| 81 | + | } | |
| 82 | + | } | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | b.ResetTimer() | |
| 86 | + | for i := 0; i < b.N; i++ { | |
| 87 | + | wm.collectLeavesFrom(wm.root) | |
| 88 | + | } | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | func BenchmarkLeftmostLeaf(b *testing.B) { | |
| 92 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 93 | + | ||
| 94 | + | // Create a deep left-leaning tree | |
| 95 | + | current := wm.root | |
| 96 | + | for i := 0; i < 20; i++ { | |
| 97 | + | newNode, err := wm.splitNode(current, "left") | |
| 98 | + | if err != nil { | |
| 99 | + | b.Fatalf("Setup split %d failed: %v", i, err) | |
| 100 | + | } | |
| 101 | + | current = newNode | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | b.ResetTimer() | |
| 105 | + | for i := 0; i < b.N; i++ { | |
| 106 | + | wm.leftmostLeaf(wm.root) | |
| 107 | + | } | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | func BenchmarkFindReplacementRoot(b *testing.B) { | |
| 111 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 112 | + | ||
| 113 | + | // Build a complex tree | |
| 114 | + | leaves := wm.collectLeaves() | |
| 115 | + | for i := 0; i < 15; i++ { | |
| 116 | + | if len(leaves) > 0 { | |
| 117 | + | target := leaves[i%len(leaves)] | |
| 118 | + | newNode, err := wm.splitNode(target, "right") | |
| 119 | + | if err != nil { | |
| 120 | + | continue | |
| 121 | + | } | |
| 122 | + | leaves = append(leaves, newNode) | |
| 123 | + | } | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | excludeNode := wm.root | |
| 127 | + | ||
| 128 | + | b.ResetTimer() | |
| 129 | + | for i := 0; i < b.N; i++ { | |
| 130 | + | wm.findReplacementRoot(excludeNode) | |
| 131 | + | } | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | func BenchmarkFocusAdjacent(b *testing.B) { | |
| 135 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 136 | + | ||
| 137 | + | // Create a 2x2 grid of panes with known geometry | |
| 138 | + | right, _ := wm.splitNode(wm.root, "right") | |
| 139 | + | bottom, _ := wm.splitNode(wm.root, "down") | |
| 140 | + | _, _ = wm.splitNode(right, "down") | |
| 141 | + | ||
| 142 | + | // Set up widget bounds for focus calculations | |
| 143 | + | webkit.SetWidgetBoundsForTesting(wm.root.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100}) | |
| 144 | + | webkit.SetWidgetBoundsForTesting(right.container, webkit.WidgetBounds{X: 120, Y: 0, Width: 100, Height: 100}) | |
| 145 | + | webkit.SetWidgetBoundsForTesting(bottom.container, webkit.WidgetBounds{X: 0, Y: 120, Width: 100, Height: 100}) | |
| 146 | + | ||
| 147 | + | wm.active = wm.root | |
| 148 | + | ||
| 149 | + | b.ResetTimer() | |
| 150 | + | for i := 0; i < b.N; i++ { | |
| 151 | + | wm.focusAdjacent("right") | |
| 152 | + | wm.focusAdjacent("left") | |
| 153 | + | } | |
| 154 | + | } | |
| 155 | + | ||
| 156 | + | func BenchmarkStructuralNeighbor(b *testing.B) { | |
| 157 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 158 | + | ||
| 159 | + | // Create a complex nested structure | |
| 160 | + | right, _ := wm.splitNode(wm.root, "right") | |
| 161 | + | _, _ = wm.splitNode(right, "down") | |
| 162 | + | _, _ = wm.splitNode(wm.root, "down") | |
| 163 | + | ||
| 164 | + | // Set up geometry | |
| 165 | + | webkit.SetWidgetBoundsForTesting(wm.root.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100}) | |
| 166 | + | ||
| 167 | + | b.ResetTimer() | |
| 168 | + | for i := 0; i < b.N; i++ { | |
| 169 | + | wm.structuralNeighbor(wm.root, "right") | |
| 170 | + | wm.structuralNeighbor(wm.root, "down") | |
| 171 | + | } | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | func BenchmarkClosestLeafFromSubtree(b *testing.B) { | |
| 175 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 176 | + | ||
| 177 | + | // Build a subtree with multiple leaves | |
| 178 | + | right, _ := wm.splitNode(wm.root, "right") | |
| 179 | + | _, _ = wm.splitNode(right, "down") | |
| 180 | + | _, _ = wm.splitNode(right, "up") | |
| 181 | + | ||
| 182 | + | // Set up geometry for all nodes | |
| 183 | + | leaves := wm.collectLeavesFrom(right) | |
| 184 | + | for i, leaf := range leaves { | |
| 185 | + | webkit.SetWidgetBoundsForTesting(leaf.container, webkit.WidgetBounds{ | |
| 186 | + | X: float64(i * 50), Y: 0, Width: 45, Height: 100, | |
| 187 | + | }) | |
| 188 | + | } | |
| 189 | + | ||
| 190 | + | b.ResetTimer() | |
| 191 | + | for i := 0; i < b.N; i++ { | |
| 192 | + | wm.closestLeafFromSubtree(right, 25, 50, "right") | |
| 193 | + | } | |
| 194 | + | } | |
| 195 | + | ||
| 196 | + | func BenchmarkComplexTreeOperations(b *testing.B) { | |
| 197 | + | b.Run("SplitAndClose", func(b *testing.B) { | |
| 198 | + | for i := 0; i < b.N; i++ { | |
| 199 | + | b.StopTimer() | |
| 200 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 201 | + | b.StartTimer() | |
| 202 | + | ||
| 203 | + | // Split multiple times then close in reverse order | |
| 204 | + | nodes := []*paneNode{wm.root} | |
| 205 | + | for j := 0; j < 5; j++ { | |
| 206 | + | newNode, err := wm.splitNode(nodes[len(nodes)-1], "right") | |
| 207 | + | if err != nil { | |
| 208 | + | b.Fatalf("Split failed: %v", err) | |
| 209 | + | } | |
| 210 | + | nodes = append(nodes, newNode) | |
| 211 | + | } | |
| 212 | + | ||
| 213 | + | // Close all but root | |
| 214 | + | for j := len(nodes) - 1; j > 0; j-- { | |
| 215 | + | err := wm.closePane(nodes[j]) | |
| 216 | + | if err != nil { | |
| 217 | + | b.Fatalf("Close failed: %v", err) | |
| 218 | + | } | |
| 219 | + | } | |
| 220 | + | } | |
| 221 | + | }) | |
| 222 | + | ||
| 223 | + | b.Run("BuildBalancedTree", func(b *testing.B) { | |
| 224 | + | for i := 0; i < b.N; i++ { | |
| 225 | + | b.StopTimer() | |
| 226 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 227 | + | b.StartTimer() | |
| 228 | + | ||
| 229 | + | // Build a balanced tree by alternating split directions | |
| 230 | + | leaves := []*paneNode{wm.root} | |
| 231 | + | directions := []string{"right", "down", "left", "up"} | |
| 232 | + | ||
| 233 | + | for j := 0; j < 15 && len(leaves) > 0; j++ { | |
| 234 | + | target := leaves[j%len(leaves)] | |
| 235 | + | direction := directions[j%len(directions)] | |
| 236 | + | ||
| 237 | + | newNode, err := wm.splitNode(target, direction) | |
| 238 | + | if err != nil { | |
| 239 | + | continue | |
| 240 | + | } | |
| 241 | + | leaves = append(leaves, newNode) | |
| 242 | + | } | |
| 243 | + | } | |
| 244 | + | }) | |
| 245 | + | } | |
| 246 | + | ||
| 247 | + | func BenchmarkMemoryOperations(b *testing.B) { | |
| 248 | + | b.Run("ViewToNodeMapping", func(b *testing.B) { | |
| 249 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 250 | + | ||
| 251 | + | // Create many nodes to stress the map | |
| 252 | + | for i := 0; i < 100; i++ { | |
| 253 | + | leaves := wm.collectLeaves() | |
| 254 | + | if len(leaves) > 0 { | |
| 255 | + | _, _ = wm.splitNode(leaves[0], "right") | |
| 256 | + | } | |
| 257 | + | } | |
| 258 | + | ||
| 259 | + | b.ResetTimer() | |
| 260 | + | for i := 0; i < b.N; i++ { | |
| 261 | + | // Access all mappings | |
| 262 | + | for view, node := range wm.viewToNode { | |
| 263 | + | _ = view | |
| 264 | + | _ = node | |
| 265 | + | } | |
| 266 | + | } | |
| 267 | + | }) | |
| 268 | + | ||
| 269 | + | b.Run("TreeWalkMemory", func(b *testing.B) { | |
| 270 | + | wm := newTestWorkspaceManagerWithMocks(b) | |
| 271 | + | ||
| 272 | + | // Build deep tree | |
| 273 | + | current := wm.root | |
| 274 | + | for i := 0; i < 50; i++ { | |
| 275 | + | newNode, err := wm.splitNode(current, "right") | |
| 276 | + | if err != nil { | |
| 277 | + | break | |
| 278 | + | } | |
| 279 | + | current = newNode | |
| 280 | + | } | |
| 281 | + | ||
| 282 | + | b.ResetTimer() | |
| 283 | + | for i := 0; i < b.N; i++ { | |
| 284 | + | // Multiple tree operations that allocate | |
| 285 | + | leaves := wm.collectLeaves() | |
| 286 | + | _ = leaves | |
| 287 | + | countNodes(wm.root) | |
| 288 | + | wm.leftmostLeaf(wm.root) | |
| 289 | + | } | |
| 290 | + | }) | |
| 291 | + | } | |
| 292 | + | ||
| 293 | + | // Helper function for benchmarks | |
| 294 | + | func newTestWorkspaceManagerWithMocks(tb testing.TB) *WorkspaceManager { | |
| 295 | + | tb.Helper() | |
| 296 | + | webkit.ResetWidgetStubsForTesting() | |
| 297 | + | ||
| 298 | + | rootView, err := webkit.NewWebView(&webkit.Config{}) | |
| 299 | + | if err != nil { | |
| 300 | + | tb.Fatalf("failed to create root webview: %v", err) | |
| 301 | + | } | |
| 302 | + | ||
| 303 | + | rootPane := &BrowserPane{ | |
| 304 | + | webView: rootView, | |
| 305 | + | id: "root", | |
| 306 | + | } | |
| 307 | + | ||
| 308 | + | app := &BrowserApp{ | |
| 309 | + | panes: []*BrowserPane{rootPane}, | |
| 310 | + | activePane: rootPane, | |
| 311 | + | webView: rootView, | |
| 312 | + | } | |
| 313 | + | ||
| 314 | + | wm := &WorkspaceManager{ | |
| 315 | + | app: app, | |
| 316 | + | window: rootView.Window(), | |
| 317 | + | viewToNode: make(map[*webkit.WebView]*paneNode), | |
| 318 | + | lastSplitMsg: make(map[*webkit.WebView]time.Time), | |
| 319 | + | lastExitMsg: make(map[*webkit.WebView]time.Time), | |
| 320 | + | } | |
| 321 | + | ||
| 322 | + | wm.createWebViewFn = func() (*webkit.WebView, error) { | |
| 323 | + | return webkit.NewWebView(&webkit.Config{}) | |
| 324 | + | } | |
| 325 | + | ||
| 326 | + | wm.createPaneFn = func(view *webkit.WebView) (*BrowserPane, error) { | |
| 327 | + | return &BrowserPane{ | |
| 328 | + | webView: view, | |
| 329 | + | id: "bench-pane", | |
| 330 | + | }, nil | |
| 331 | + | } | |
| 332 | + | ||
| 333 | + | root := &paneNode{ | |
| 334 | + | pane: rootPane, | |
| 335 | + | container: rootView.RootWidget(), | |
| 336 | + | isLeaf: true, | |
| 337 | + | } | |
| 338 | + | wm.root = root | |
| 339 | + | wm.active = root | |
| 340 | + | wm.mainPane = root | |
| 341 | + | wm.viewToNode[rootView] = root | |
| 342 | + | ||
| 343 | + | return wm | |
| 344 | + | } | |
| @@ -0,0 +1,344 @@ | |||||
| 1 | + | package browser | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | "time" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/dumber/pkg/webkit" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | func BenchmarkSplitNode(b *testing.B) { | |||
| 11 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 12 | + | ||||
| 13 | + | b.ResetTimer() | |||
| 14 | + | for i := 0; i < b.N; i++ { | |||
| 15 | + | b.StopTimer() | |||
| 16 | + | // Reset to single node for each iteration | |||
| 17 | + | wm = newTestWorkspaceManagerWithMocks(b) | |||
| 18 | + | b.StartTimer() | |||
| 19 | + | ||||
| 20 | + | _, err := wm.splitNode(wm.root, "right") | |||
| 21 | + | if err != nil { | |||
| 22 | + | b.Fatalf("Split failed: %v", err) | |||
| 23 | + | } | |||
| 24 | + | } | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | func BenchmarkClosePane(b *testing.B) { | |||
| 28 | + | for i := 0; i < b.N; i++ { | |||
| 29 | + | b.StopTimer() | |||
| 30 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 31 | + | node, err := wm.splitNode(wm.root, "right") | |||
| 32 | + | if err != nil { | |||
| 33 | + | b.Fatalf("Setup split failed: %v", err) | |||
| 34 | + | } | |||
| 35 | + | b.StartTimer() | |||
| 36 | + | ||||
| 37 | + | err = wm.closePane(node) | |||
| 38 | + | if err != nil { | |||
| 39 | + | b.Fatalf("Close failed: %v", err) | |||
| 40 | + | } | |||
| 41 | + | } | |||
| 42 | + | } | |||
| 43 | + | ||||
| 44 | + | func BenchmarkTreeTraversal(b *testing.B) { | |||
| 45 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 46 | + | ||||
| 47 | + | // Build a complex tree with 15 leaves | |||
| 48 | + | leaves := []*paneNode{wm.root} | |||
| 49 | + | for i := 0; i < 14; i++ { | |||
| 50 | + | if len(leaves) > 0 { | |||
| 51 | + | // Split the first leaf | |||
| 52 | + | target := leaves[0] | |||
| 53 | + | leaves = leaves[1:] // Remove the target from leaves | |||
| 54 | + | ||||
| 55 | + | newNode, err := wm.splitNode(target, "right") | |||
| 56 | + | if err != nil { | |||
| 57 | + | b.Fatalf("Setup split %d failed: %v", i, err) | |||
| 58 | + | } | |||
| 59 | + | ||||
| 60 | + | // Add both resulting leaves back | |||
| 61 | + | leaves = append(leaves, target, newNode) | |||
| 62 | + | } | |||
| 63 | + | } | |||
| 64 | + | ||||
| 65 | + | b.ResetTimer() | |||
| 66 | + | for i := 0; i < b.N; i++ { | |||
| 67 | + | wm.collectLeaves() | |||
| 68 | + | } | |||
| 69 | + | } | |||
| 70 | + | ||||
| 71 | + | func BenchmarkCollectLeavesFrom(b *testing.B) { | |||
| 72 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 73 | + | ||||
| 74 | + | // Build a balanced binary tree | |||
| 75 | + | for i := 0; i < 10; i++ { | |||
| 76 | + | leaves := wm.collectLeaves() | |||
| 77 | + | if len(leaves) > 0 { | |||
| 78 | + | _, err := wm.splitNode(leaves[0], "right") | |||
| 79 | + | if err != nil { | |||
| 80 | + | b.Fatalf("Setup split %d failed: %v", i, err) | |||
| 81 | + | } | |||
| 82 | + | } | |||
| 83 | + | } | |||
| 84 | + | ||||
| 85 | + | b.ResetTimer() | |||
| 86 | + | for i := 0; i < b.N; i++ { | |||
| 87 | + | wm.collectLeavesFrom(wm.root) | |||
| 88 | + | } | |||
| 89 | + | } | |||
| 90 | + | ||||
| 91 | + | func BenchmarkLeftmostLeaf(b *testing.B) { | |||
| 92 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 93 | + | ||||
| 94 | + | // Create a deep left-leaning tree | |||
| 95 | + | current := wm.root | |||
| 96 | + | for i := 0; i < 20; i++ { | |||
| 97 | + | newNode, err := wm.splitNode(current, "left") | |||
| 98 | + | if err != nil { | |||
| 99 | + | b.Fatalf("Setup split %d failed: %v", i, err) | |||
| 100 | + | } | |||
| 101 | + | current = newNode | |||
| 102 | + | } | |||
| 103 | + | ||||
| 104 | + | b.ResetTimer() | |||
| 105 | + | for i := 0; i < b.N; i++ { | |||
| 106 | + | wm.leftmostLeaf(wm.root) | |||
| 107 | + | } | |||
| 108 | + | } | |||
| 109 | + | ||||
| 110 | + | func BenchmarkFindReplacementRoot(b *testing.B) { | |||
| 111 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 112 | + | ||||
| 113 | + | // Build a complex tree | |||
| 114 | + | leaves := wm.collectLeaves() | |||
| 115 | + | for i := 0; i < 15; i++ { | |||
| 116 | + | if len(leaves) > 0 { | |||
| 117 | + | target := leaves[i%len(leaves)] | |||
| 118 | + | newNode, err := wm.splitNode(target, "right") | |||
| 119 | + | if err != nil { | |||
| 120 | + | continue | |||
| 121 | + | } | |||
| 122 | + | leaves = append(leaves, newNode) | |||
| 123 | + | } | |||
| 124 | + | } | |||
| 125 | + | ||||
| 126 | + | excludeNode := wm.root | |||
| 127 | + | ||||
| 128 | + | b.ResetTimer() | |||
| 129 | + | for i := 0; i < b.N; i++ { | |||
| 130 | + | wm.findReplacementRoot(excludeNode) | |||
| 131 | + | } | |||
| 132 | + | } | |||
| 133 | + | ||||
| 134 | + | func BenchmarkFocusAdjacent(b *testing.B) { | |||
| 135 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 136 | + | ||||
| 137 | + | // Create a 2x2 grid of panes with known geometry | |||
| 138 | + | right, _ := wm.splitNode(wm.root, "right") | |||
| 139 | + | bottom, _ := wm.splitNode(wm.root, "down") | |||
| 140 | + | _, _ = wm.splitNode(right, "down") | |||
| 141 | + | ||||
| 142 | + | // Set up widget bounds for focus calculations | |||
| 143 | + | webkit.SetWidgetBoundsForTesting(wm.root.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100}) | |||
| 144 | + | webkit.SetWidgetBoundsForTesting(right.container, webkit.WidgetBounds{X: 120, Y: 0, Width: 100, Height: 100}) | |||
| 145 | + | webkit.SetWidgetBoundsForTesting(bottom.container, webkit.WidgetBounds{X: 0, Y: 120, Width: 100, Height: 100}) | |||
| 146 | + | ||||
| 147 | + | wm.active = wm.root | |||
| 148 | + | ||||
| 149 | + | b.ResetTimer() | |||
| 150 | + | for i := 0; i < b.N; i++ { | |||
| 151 | + | wm.focusAdjacent("right") | |||
| 152 | + | wm.focusAdjacent("left") | |||
| 153 | + | } | |||
| 154 | + | } | |||
| 155 | + | ||||
| 156 | + | func BenchmarkStructuralNeighbor(b *testing.B) { | |||
| 157 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 158 | + | ||||
| 159 | + | // Create a complex nested structure | |||
| 160 | + | right, _ := wm.splitNode(wm.root, "right") | |||
| 161 | + | _, _ = wm.splitNode(right, "down") | |||
| 162 | + | _, _ = wm.splitNode(wm.root, "down") | |||
| 163 | + | ||||
| 164 | + | // Set up geometry | |||
| 165 | + | webkit.SetWidgetBoundsForTesting(wm.root.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100}) | |||
| 166 | + | ||||
| 167 | + | b.ResetTimer() | |||
| 168 | + | for i := 0; i < b.N; i++ { | |||
| 169 | + | wm.structuralNeighbor(wm.root, "right") | |||
| 170 | + | wm.structuralNeighbor(wm.root, "down") | |||
| 171 | + | } | |||
| 172 | + | } | |||
| 173 | + | ||||
| 174 | + | func BenchmarkClosestLeafFromSubtree(b *testing.B) { | |||
| 175 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 176 | + | ||||
| 177 | + | // Build a subtree with multiple leaves | |||
| 178 | + | right, _ := wm.splitNode(wm.root, "right") | |||
| 179 | + | _, _ = wm.splitNode(right, "down") | |||
| 180 | + | _, _ = wm.splitNode(right, "up") | |||
| 181 | + | ||||
| 182 | + | // Set up geometry for all nodes | |||
| 183 | + | leaves := wm.collectLeavesFrom(right) | |||
| 184 | + | for i, leaf := range leaves { | |||
| 185 | + | webkit.SetWidgetBoundsForTesting(leaf.container, webkit.WidgetBounds{ | |||
| 186 | + | X: float64(i * 50), Y: 0, Width: 45, Height: 100, | |||
| 187 | + | }) | |||
| 188 | + | } | |||
| 189 | + | ||||
| 190 | + | b.ResetTimer() | |||
| 191 | + | for i := 0; i < b.N; i++ { | |||
| 192 | + | wm.closestLeafFromSubtree(right, 25, 50, "right") | |||
| 193 | + | } | |||
| 194 | + | } | |||
| 195 | + | ||||
| 196 | + | func BenchmarkComplexTreeOperations(b *testing.B) { | |||
| 197 | + | b.Run("SplitAndClose", func(b *testing.B) { | |||
| 198 | + | for i := 0; i < b.N; i++ { | |||
| 199 | + | b.StopTimer() | |||
| 200 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 201 | + | b.StartTimer() | |||
| 202 | + | ||||
| 203 | + | // Split multiple times then close in reverse order | |||
| 204 | + | nodes := []*paneNode{wm.root} | |||
| 205 | + | for j := 0; j < 5; j++ { | |||
| 206 | + | newNode, err := wm.splitNode(nodes[len(nodes)-1], "right") | |||
| 207 | + | if err != nil { | |||
| 208 | + | b.Fatalf("Split failed: %v", err) | |||
| 209 | + | } | |||
| 210 | + | nodes = append(nodes, newNode) | |||
| 211 | + | } | |||
| 212 | + | ||||
| 213 | + | // Close all but root | |||
| 214 | + | for j := len(nodes) - 1; j > 0; j-- { | |||
| 215 | + | err := wm.closePane(nodes[j]) | |||
| 216 | + | if err != nil { | |||
| 217 | + | b.Fatalf("Close failed: %v", err) | |||
| 218 | + | } | |||
| 219 | + | } | |||
| 220 | + | } | |||
| 221 | + | }) | |||
| 222 | + | ||||
| 223 | + | b.Run("BuildBalancedTree", func(b *testing.B) { | |||
| 224 | + | for i := 0; i < b.N; i++ { | |||
| 225 | + | b.StopTimer() | |||
| 226 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 227 | + | b.StartTimer() | |||
| 228 | + | ||||
| 229 | + | // Build a balanced tree by alternating split directions | |||
| 230 | + | leaves := []*paneNode{wm.root} | |||
| 231 | + | directions := []string{"right", "down", "left", "up"} | |||
| 232 | + | ||||
| 233 | + | for j := 0; j < 15 && len(leaves) > 0; j++ { | |||
| 234 | + | target := leaves[j%len(leaves)] | |||
| 235 | + | direction := directions[j%len(directions)] | |||
| 236 | + | ||||
| 237 | + | newNode, err := wm.splitNode(target, direction) | |||
| 238 | + | if err != nil { | |||
| 239 | + | continue | |||
| 240 | + | } | |||
| 241 | + | leaves = append(leaves, newNode) | |||
| 242 | + | } | |||
| 243 | + | } | |||
| 244 | + | }) | |||
| 245 | + | } | |||
| 246 | + | ||||
| 247 | + | func BenchmarkMemoryOperations(b *testing.B) { | |||
| 248 | + | b.Run("ViewToNodeMapping", func(b *testing.B) { | |||
| 249 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 250 | + | ||||
| 251 | + | // Create many nodes to stress the map | |||
| 252 | + | for i := 0; i < 100; i++ { | |||
| 253 | + | leaves := wm.collectLeaves() | |||
| 254 | + | if len(leaves) > 0 { | |||
| 255 | + | _, _ = wm.splitNode(leaves[0], "right") | |||
| 256 | + | } | |||
| 257 | + | } | |||
| 258 | + | ||||
| 259 | + | b.ResetTimer() | |||
| 260 | + | for i := 0; i < b.N; i++ { | |||
| 261 | + | // Access all mappings | |||
| 262 | + | for view, node := range wm.viewToNode { | |||
| 263 | + | _ = view | |||
| 264 | + | _ = node | |||
| 265 | + | } | |||
| 266 | + | } | |||
| 267 | + | }) | |||
| 268 | + | ||||
| 269 | + | b.Run("TreeWalkMemory", func(b *testing.B) { | |||
| 270 | + | wm := newTestWorkspaceManagerWithMocks(b) | |||
| 271 | + | ||||
| 272 | + | // Build deep tree | |||
| 273 | + | current := wm.root | |||
| 274 | + | for i := 0; i < 50; i++ { | |||
| 275 | + | newNode, err := wm.splitNode(current, "right") | |||
| 276 | + | if err != nil { | |||
| 277 | + | break | |||
| 278 | + | } | |||
| 279 | + | current = newNode | |||
| 280 | + | } | |||
| 281 | + | ||||
| 282 | + | b.ResetTimer() | |||
| 283 | + | for i := 0; i < b.N; i++ { | |||
| 284 | + | // Multiple tree operations that allocate | |||
| 285 | + | leaves := wm.collectLeaves() | |||
| 286 | + | _ = leaves | |||
| 287 | + | countNodes(wm.root) | |||
| 288 | + | wm.leftmostLeaf(wm.root) | |||
| 289 | + | } | |||
| 290 | + | }) | |||
| 291 | + | } | |||
| 292 | + | ||||
| 293 | + | // Helper function for benchmarks | |||
| 294 | + | func newTestWorkspaceManagerWithMocks(tb testing.TB) *WorkspaceManager { | |||
| 295 | + | tb.Helper() | |||
| 296 | + | webkit.ResetWidgetStubsForTesting() | |||
| 297 | + | ||||
| 298 | + | rootView, err := webkit.NewWebView(&webkit.Config{}) | |||
| 299 | + | if err != nil { | |||
| 300 | + | tb.Fatalf("failed to create root webview: %v", err) | |||
| 301 | + | } | |||
| 302 | + | ||||
| 303 | + | rootPane := &BrowserPane{ | |||
| 304 | + | webView: rootView, | |||
| 305 | + | id: "root", | |||
| 306 | + | } | |||
| 307 | + | ||||
| 308 | + | app := &BrowserApp{ | |||
| 309 | + | panes: []*BrowserPane{rootPane}, | |||
| 310 | + | activePane: rootPane, | |||
| 311 | + | webView: rootView, | |||
| 312 | + | } | |||
| 313 | + | ||||
| 314 | + | wm := &WorkspaceManager{ | |||
| 315 | + | app: app, | |||
| 316 | + | window: rootView.Window(), | |||
| 317 | + | viewToNode: make(map[*webkit.WebView]*paneNode), | |||
| 318 | + | lastSplitMsg: make(map[*webkit.WebView]time.Time), | |||
| 319 | + | lastExitMsg: make(map[*webkit.WebView]time.Time), | |||
| 320 | + | } | |||
| 321 | + | ||||
| 322 | + | wm.createWebViewFn = func() (*webkit.WebView, error) { | |||
| 323 | + | return webkit.NewWebView(&webkit.Config{}) | |||
| 324 | + | } | |||
| 325 | + | ||||
| 326 | + | wm.createPaneFn = func(view *webkit.WebView) (*BrowserPane, error) { | |||
| 327 | + | return &BrowserPane{ | |||
| 328 | + | webView: view, | |||
| 329 | + | id: "bench-pane", | |||
| 330 | + | }, nil | |||
| 331 | + | } | |||
| 332 | + | ||||
| 333 | + | root := &paneNode{ | |||
| 334 | + | pane: rootPane, | |||
| 335 | + | container: rootView.RootWidget(), | |||
| 336 | + | isLeaf: true, | |||
| 337 | + | } | |||
| 338 | + | wm.root = root | |||
| 339 | + | wm.active = root | |||
| 340 | + | wm.mainPane = root | |||
| 341 | + | wm.viewToNode[rootView] = root | |||
| 342 | + | ||||
| 343 | + | return wm | |||
| 344 | + | } | |||
A internal/app/browser/workspace_manager_recursion_test.go +172 -0
| @@ -0,0 +1,172 @@ | |||
| 1 | + | package browser | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | "time" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/dumber/pkg/webkit" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | func TestInfiniteRecursionProtection(t *testing.T) { | |
| 11 | + | t.Run("BoundaryFallback with deep tree", func(t *testing.T) { | |
| 12 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 13 | + | ||
| 14 | + | // Create a pathologically deep tree | |
| 15 | + | current := wm.root | |
| 16 | + | for i := 0; i < 20; i++ { | |
| 17 | + | newNode, err := wm.splitNode(current, "right") | |
| 18 | + | if err != nil { | |
| 19 | + | t.Fatalf("Split %d failed: %v", i, err) | |
| 20 | + | } | |
| 21 | + | current = newNode | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | // Test that boundaryFallback doesn't hang even with deep trees | |
| 25 | + | done := make(chan bool, 1) | |
| 26 | + | go func() { | |
| 27 | + | // This should complete quickly without infinite recursion | |
| 28 | + | result := wm.boundaryFallback(wm.root, "right") | |
| 29 | + | _ = result // We just care that it returns | |
| 30 | + | done <- true | |
| 31 | + | }() | |
| 32 | + | ||
| 33 | + | select { | |
| 34 | + | case <-done: | |
| 35 | + | // Good - function completed | |
| 36 | + | case <-time.After(1 * time.Second): | |
| 37 | + | t.Fatal("boundaryFallback took too long - likely infinite recursion") | |
| 38 | + | } | |
| 39 | + | }) | |
| 40 | + | ||
| 41 | + | t.Run("CollectLeaves with complex tree", func(t *testing.T) { | |
| 42 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 43 | + | ||
| 44 | + | // Build a complex tree that could potentially cause issues | |
| 45 | + | leaves := []*paneNode{wm.root} | |
| 46 | + | for i := 0; i < 15; i++ { | |
| 47 | + | if len(leaves) > 0 { | |
| 48 | + | target := leaves[i%len(leaves)] | |
| 49 | + | newNode, err := wm.splitNode(target, "right") | |
| 50 | + | if err != nil { | |
| 51 | + | continue | |
| 52 | + | } | |
| 53 | + | leaves = append(leaves, newNode) | |
| 54 | + | } | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | // Test that collectLeaves doesn't hang | |
| 58 | + | done := make(chan []*paneNode, 1) | |
| 59 | + | go func() { | |
| 60 | + | result := wm.collectLeaves() | |
| 61 | + | done <- result | |
| 62 | + | }() | |
| 63 | + | ||
| 64 | + | select { | |
| 65 | + | case result := <-done: | |
| 66 | + | // Verify we got a reasonable result | |
| 67 | + | if len(result) == 0 { | |
| 68 | + | t.Error("Expected some leaves to be collected") | |
| 69 | + | } | |
| 70 | + | t.Logf("Collected %d leaves from complex tree", len(result)) | |
| 71 | + | case <-time.After(1 * time.Second): | |
| 72 | + | t.Fatal("collectLeaves took too long - likely infinite recursion") | |
| 73 | + | } | |
| 74 | + | }) | |
| 75 | + | ||
| 76 | + | t.Run("Focus calculation performance", func(t *testing.T) { | |
| 77 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 78 | + | ||
| 79 | + | // Create a tree with geometry | |
| 80 | + | right, err := wm.splitNode(wm.root, "right") | |
| 81 | + | if err != nil { | |
| 82 | + | t.Fatalf("Failed to split right: %v", err) | |
| 83 | + | } | |
| 84 | + | bottom, err := wm.splitNode(right, "down") | |
| 85 | + | if err != nil { | |
| 86 | + | t.Fatalf("Failed to split down: %v", err) | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | // Set widget bounds | |
| 90 | + | webkit.SetWidgetBoundsForTesting(wm.root.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100}) | |
| 91 | + | if right != nil { | |
| 92 | + | webkit.SetWidgetBoundsForTesting(right.container, webkit.WidgetBounds{X: 120, Y: 0, Width: 100, Height: 100}) | |
| 93 | + | } | |
| 94 | + | if bottom != nil { | |
| 95 | + | webkit.SetWidgetBoundsForTesting(bottom.container, webkit.WidgetBounds{X: 0, Y: 120, Width: 100, Height: 100}) | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | // Test that focus neighbor calculations complete quickly | |
| 99 | + | start := time.Now() | |
| 100 | + | ||
| 101 | + | // Perform multiple focus operations | |
| 102 | + | for i := 0; i < 10; i++ { | |
| 103 | + | wm.FocusNeighbor("right") | |
| 104 | + | wm.FocusNeighbor("left") | |
| 105 | + | wm.FocusNeighbor("down") | |
| 106 | + | wm.FocusNeighbor("up") | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | elapsed := time.Since(start) | |
| 110 | + | if elapsed > 100*time.Millisecond { | |
| 111 | + | t.Errorf("Focus operations took too long: %v", elapsed) | |
| 112 | + | } | |
| 113 | + | t.Logf("40 focus operations completed in %v", elapsed) | |
| 114 | + | }) | |
| 115 | + | ||
| 116 | + | t.Run("StructuralNeighbor depth protection", func(t *testing.T) { | |
| 117 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 118 | + | ||
| 119 | + | // Create nested structure | |
| 120 | + | node1, _ := wm.splitNode(wm.root, "right") | |
| 121 | + | node2, _ := wm.splitNode(node1, "down") | |
| 122 | + | node3, _ := wm.splitNode(node2, "right") | |
| 123 | + | ||
| 124 | + | // Set some bounds for the calculation | |
| 125 | + | webkit.SetWidgetBoundsForTesting(node3.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100}) | |
| 126 | + | ||
| 127 | + | // Test structural neighbor calculation doesn't hang | |
| 128 | + | done := make(chan *paneNode, 1) | |
| 129 | + | go func() { | |
| 130 | + | result := wm.structuralNeighbor(node3, "left") | |
| 131 | + | done <- result | |
| 132 | + | }() | |
| 133 | + | ||
| 134 | + | select { | |
| 135 | + | case result := <-done: | |
| 136 | + | t.Logf("Structural neighbor calculation completed, result: %v", result != nil) | |
| 137 | + | case <-time.After(500 * time.Millisecond): | |
| 138 | + | t.Fatal("structuralNeighbor took too long") | |
| 139 | + | } | |
| 140 | + | }) | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | func TestRecursionLimits(t *testing.T) { | |
| 144 | + | t.Run("MaxDepth protection", func(t *testing.T) { | |
| 145 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 146 | + | ||
| 147 | + | // Create an artificially corrupted node for testing | |
| 148 | + | // (This would never happen in normal operation) | |
| 149 | + | corruptedNode := &paneNode{ | |
| 150 | + | isLeaf: false, | |
| 151 | + | left: nil, // This creates a problematic state | |
| 152 | + | right: nil, // No children but not a leaf | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | // Test that boundaryFallback handles this gracefully | |
| 156 | + | result := wm.boundaryFallback(corruptedNode, "right") | |
| 157 | + | if result != nil { | |
| 158 | + | t.Error("Expected nil result for corrupted node") | |
| 159 | + | } | |
| 160 | + | ||
| 161 | + | // The function should return quickly, not hang | |
| 162 | + | start := time.Now() | |
| 163 | + | for i := 0; i < 10; i++ { | |
| 164 | + | wm.boundaryFallback(corruptedNode, "left") | |
| 165 | + | } | |
| 166 | + | elapsed := time.Since(start) | |
| 167 | + | ||
| 168 | + | if elapsed > 10*time.Millisecond { | |
| 169 | + | t.Errorf("Corrupted node handling took too long: %v", elapsed) | |
| 170 | + | } | |
| 171 | + | }) | |
| 172 | + | }⏎̸ | |
| @@ -0,0 +1,172 @@ | |||||
| 1 | + | package browser | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | "time" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/dumber/pkg/webkit" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | func TestInfiniteRecursionProtection(t *testing.T) { | |||
| 11 | + | t.Run("BoundaryFallback with deep tree", func(t *testing.T) { | |||
| 12 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 13 | + | ||||
| 14 | + | // Create a pathologically deep tree | |||
| 15 | + | current := wm.root | |||
| 16 | + | for i := 0; i < 20; i++ { | |||
| 17 | + | newNode, err := wm.splitNode(current, "right") | |||
| 18 | + | if err != nil { | |||
| 19 | + | t.Fatalf("Split %d failed: %v", i, err) | |||
| 20 | + | } | |||
| 21 | + | current = newNode | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | // Test that boundaryFallback doesn't hang even with deep trees | |||
| 25 | + | done := make(chan bool, 1) | |||
| 26 | + | go func() { | |||
| 27 | + | // This should complete quickly without infinite recursion | |||
| 28 | + | result := wm.boundaryFallback(wm.root, "right") | |||
| 29 | + | _ = result // We just care that it returns | |||
| 30 | + | done <- true | |||
| 31 | + | }() | |||
| 32 | + | ||||
| 33 | + | select { | |||
| 34 | + | case <-done: | |||
| 35 | + | // Good - function completed | |||
| 36 | + | case <-time.After(1 * time.Second): | |||
| 37 | + | t.Fatal("boundaryFallback took too long - likely infinite recursion") | |||
| 38 | + | } | |||
| 39 | + | }) | |||
| 40 | + | ||||
| 41 | + | t.Run("CollectLeaves with complex tree", func(t *testing.T) { | |||
| 42 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 43 | + | ||||
| 44 | + | // Build a complex tree that could potentially cause issues | |||
| 45 | + | leaves := []*paneNode{wm.root} | |||
| 46 | + | for i := 0; i < 15; i++ { | |||
| 47 | + | if len(leaves) > 0 { | |||
| 48 | + | target := leaves[i%len(leaves)] | |||
| 49 | + | newNode, err := wm.splitNode(target, "right") | |||
| 50 | + | if err != nil { | |||
| 51 | + | continue | |||
| 52 | + | } | |||
| 53 | + | leaves = append(leaves, newNode) | |||
| 54 | + | } | |||
| 55 | + | } | |||
| 56 | + | ||||
| 57 | + | // Test that collectLeaves doesn't hang | |||
| 58 | + | done := make(chan []*paneNode, 1) | |||
| 59 | + | go func() { | |||
| 60 | + | result := wm.collectLeaves() | |||
| 61 | + | done <- result | |||
| 62 | + | }() | |||
| 63 | + | ||||
| 64 | + | select { | |||
| 65 | + | case result := <-done: | |||
| 66 | + | // Verify we got a reasonable result | |||
| 67 | + | if len(result) == 0 { | |||
| 68 | + | t.Error("Expected some leaves to be collected") | |||
| 69 | + | } | |||
| 70 | + | t.Logf("Collected %d leaves from complex tree", len(result)) | |||
| 71 | + | case <-time.After(1 * time.Second): | |||
| 72 | + | t.Fatal("collectLeaves took too long - likely infinite recursion") | |||
| 73 | + | } | |||
| 74 | + | }) | |||
| 75 | + | ||||
| 76 | + | t.Run("Focus calculation performance", func(t *testing.T) { | |||
| 77 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 78 | + | ||||
| 79 | + | // Create a tree with geometry | |||
| 80 | + | right, err := wm.splitNode(wm.root, "right") | |||
| 81 | + | if err != nil { | |||
| 82 | + | t.Fatalf("Failed to split right: %v", err) | |||
| 83 | + | } | |||
| 84 | + | bottom, err := wm.splitNode(right, "down") | |||
| 85 | + | if err != nil { | |||
| 86 | + | t.Fatalf("Failed to split down: %v", err) | |||
| 87 | + | } | |||
| 88 | + | ||||
| 89 | + | // Set widget bounds | |||
| 90 | + | webkit.SetWidgetBoundsForTesting(wm.root.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100}) | |||
| 91 | + | if right != nil { | |||
| 92 | + | webkit.SetWidgetBoundsForTesting(right.container, webkit.WidgetBounds{X: 120, Y: 0, Width: 100, Height: 100}) | |||
| 93 | + | } | |||
| 94 | + | if bottom != nil { | |||
| 95 | + | webkit.SetWidgetBoundsForTesting(bottom.container, webkit.WidgetBounds{X: 0, Y: 120, Width: 100, Height: 100}) | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | // Test that focus neighbor calculations complete quickly | |||
| 99 | + | start := time.Now() | |||
| 100 | + | ||||
| 101 | + | // Perform multiple focus operations | |||
| 102 | + | for i := 0; i < 10; i++ { | |||
| 103 | + | wm.FocusNeighbor("right") | |||
| 104 | + | wm.FocusNeighbor("left") | |||
| 105 | + | wm.FocusNeighbor("down") | |||
| 106 | + | wm.FocusNeighbor("up") | |||
| 107 | + | } | |||
| 108 | + | ||||
| 109 | + | elapsed := time.Since(start) | |||
| 110 | + | if elapsed > 100*time.Millisecond { | |||
| 111 | + | t.Errorf("Focus operations took too long: %v", elapsed) | |||
| 112 | + | } | |||
| 113 | + | t.Logf("40 focus operations completed in %v", elapsed) | |||
| 114 | + | }) | |||
| 115 | + | ||||
| 116 | + | t.Run("StructuralNeighbor depth protection", func(t *testing.T) { | |||
| 117 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 118 | + | ||||
| 119 | + | // Create nested structure | |||
| 120 | + | node1, _ := wm.splitNode(wm.root, "right") | |||
| 121 | + | node2, _ := wm.splitNode(node1, "down") | |||
| 122 | + | node3, _ := wm.splitNode(node2, "right") | |||
| 123 | + | ||||
| 124 | + | // Set some bounds for the calculation | |||
| 125 | + | webkit.SetWidgetBoundsForTesting(node3.container, webkit.WidgetBounds{X: 0, Y: 0, Width: 100, Height: 100}) | |||
| 126 | + | ||||
| 127 | + | // Test structural neighbor calculation doesn't hang | |||
| 128 | + | done := make(chan *paneNode, 1) | |||
| 129 | + | go func() { | |||
| 130 | + | result := wm.structuralNeighbor(node3, "left") | |||
| 131 | + | done <- result | |||
| 132 | + | }() | |||
| 133 | + | ||||
| 134 | + | select { | |||
| 135 | + | case result := <-done: | |||
| 136 | + | t.Logf("Structural neighbor calculation completed, result: %v", result != nil) | |||
| 137 | + | case <-time.After(500 * time.Millisecond): | |||
| 138 | + | t.Fatal("structuralNeighbor took too long") | |||
| 139 | + | } | |||
| 140 | + | }) | |||
| 141 | + | } | |||
| 142 | + | ||||
| 143 | + | func TestRecursionLimits(t *testing.T) { | |||
| 144 | + | t.Run("MaxDepth protection", func(t *testing.T) { | |||
| 145 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 146 | + | ||||
| 147 | + | // Create an artificially corrupted node for testing | |||
| 148 | + | // (This would never happen in normal operation) | |||
| 149 | + | corruptedNode := &paneNode{ | |||
| 150 | + | isLeaf: false, | |||
| 151 | + | left: nil, // This creates a problematic state | |||
| 152 | + | right: nil, // No children but not a leaf | |||
| 153 | + | } | |||
| 154 | + | ||||
| 155 | + | // Test that boundaryFallback handles this gracefully | |||
| 156 | + | result := wm.boundaryFallback(corruptedNode, "right") | |||
| 157 | + | if result != nil { | |||
| 158 | + | t.Error("Expected nil result for corrupted node") | |||
| 159 | + | } | |||
| 160 | + | ||||
| 161 | + | // The function should return quickly, not hang | |||
| 162 | + | start := time.Now() | |||
| 163 | + | for i := 0; i < 10; i++ { | |||
| 164 | + | wm.boundaryFallback(corruptedNode, "left") | |||
| 165 | + | } | |||
| 166 | + | elapsed := time.Since(start) | |||
| 167 | + | ||||
| 168 | + | if elapsed > 10*time.Millisecond { | |||
| 169 | + | t.Errorf("Corrupted node handling took too long: %v", elapsed) | |||
| 170 | + | } | |||
| 171 | + | }) | |||
| 172 | + | }⏎̸ | |||
A internal/app/browser/workspace_manager_tree_test.go +594 -0
| @@ -0,0 +1,594 @@ | |||
| 1 | + | package browser | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "fmt" | |
| 5 | + | "testing" | |
| 6 | + | "time" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/dumber/pkg/webkit" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | // Test helper to create mock pane node | |
| 12 | + | func newMockPaneNode(id string) *paneNode { | |
| 13 | + | container := uintptr(100 + len(id)) | |
| 14 | + | ||
| 15 | + | return &paneNode{ | |
| 16 | + | pane: &BrowserPane{id: id}, | |
| 17 | + | container: container, | |
| 18 | + | isLeaf: true, | |
| 19 | + | } | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | // Test tree validation helper | |
| 23 | + | func validateTreeStructure(t *testing.T, node *paneNode) { | |
| 24 | + | t.Helper() | |
| 25 | + | ||
| 26 | + | if node == nil { | |
| 27 | + | return | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | if node.isLeaf { | |
| 31 | + | // Leaf nodes should not have children | |
| 32 | + | if node.left != nil || node.right != nil { | |
| 33 | + | t.Errorf("Leaf node has children: left=%v, right=%v", node.left, node.right) | |
| 34 | + | } | |
| 35 | + | // Leaf nodes must have a pane | |
| 36 | + | if node.pane == nil { | |
| 37 | + | t.Error("Leaf node missing pane") | |
| 38 | + | } | |
| 39 | + | } else { | |
| 40 | + | // Branch nodes must have both children | |
| 41 | + | if node.left == nil || node.right == nil { | |
| 42 | + | t.Errorf("Branch node missing children: left=%v, right=%v", node.left, node.right) | |
| 43 | + | } | |
| 44 | + | // Branch nodes should not have a pane | |
| 45 | + | if node.pane != nil { | |
| 46 | + | t.Error("Branch node should not have pane") | |
| 47 | + | } | |
| 48 | + | // Validate children's parent pointers | |
| 49 | + | if node.left != nil && node.left.parent != node { | |
| 50 | + | t.Error("Left child's parent pointer incorrect") | |
| 51 | + | } | |
| 52 | + | if node.right != nil && node.right.parent != node { | |
| 53 | + | t.Error("Right child's parent pointer incorrect") | |
| 54 | + | } | |
| 55 | + | // Recursively validate subtrees | |
| 56 | + | validateTreeStructure(t, node.left) | |
| 57 | + | validateTreeStructure(t, node.right) | |
| 58 | + | } | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | // Count nodes in tree | |
| 62 | + | func countNodes(node *paneNode) (leaves, branches int) { | |
| 63 | + | if node == nil { | |
| 64 | + | return 0, 0 | |
| 65 | + | } | |
| 66 | + | if node.isLeaf { | |
| 67 | + | return 1, 0 | |
| 68 | + | } | |
| 69 | + | leftLeaves, leftBranches := countNodes(node.left) | |
| 70 | + | rightLeaves, rightBranches := countNodes(node.right) | |
| 71 | + | return leftLeaves + rightLeaves, leftBranches + rightBranches + 1 | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | func TestPaneNodeTreeInvariants(t *testing.T) { | |
| 75 | + | t.Run("Initial root is leaf", func(t *testing.T) { | |
| 76 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 77 | + | ||
| 78 | + | if !wm.root.isLeaf { | |
| 79 | + | t.Error("Initial root should be a leaf") | |
| 80 | + | } | |
| 81 | + | if wm.root.parent != nil { | |
| 82 | + | t.Error("Root should have no parent") | |
| 83 | + | } | |
| 84 | + | validateTreeStructure(t, wm.root) | |
| 85 | + | }) | |
| 86 | + | ||
| 87 | + | t.Run("Tree structure after single split", func(t *testing.T) { | |
| 88 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 89 | + | original := wm.root | |
| 90 | + | ||
| 91 | + | // Split right | |
| 92 | + | newNode, err := wm.splitNode(original, "right") | |
| 93 | + | if err != nil { | |
| 94 | + | t.Fatalf("Split failed: %v", err) | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | // Validate new tree structure | |
| 98 | + | validateTreeStructure(t, wm.root) | |
| 99 | + | ||
| 100 | + | // Root should now be a branch | |
| 101 | + | if wm.root.isLeaf { | |
| 102 | + | t.Error("Root should be branch after split") | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | // Original and new nodes should be siblings | |
| 106 | + | if wm.root.left != original { | |
| 107 | + | t.Error("Original node should be left child") | |
| 108 | + | } | |
| 109 | + | if wm.root.right != newNode { | |
| 110 | + | t.Error("New node should be right child") | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | // Count nodes | |
| 114 | + | leaves, branches := countNodes(wm.root) | |
| 115 | + | if leaves != 2 { | |
| 116 | + | t.Errorf("Expected 2 leaves, got %d", leaves) | |
| 117 | + | } | |
| 118 | + | if branches != 1 { | |
| 119 | + | t.Errorf("Expected 1 branch, got %d", branches) | |
| 120 | + | } | |
| 121 | + | }) | |
| 122 | + | } | |
| 123 | + | ||
| 124 | + | func TestPaneSplitDirections(t *testing.T) { | |
| 125 | + | testCases := []struct { | |
| 126 | + | name string | |
| 127 | + | direction string | |
| 128 | + | expectedOrientation webkit.Orientation | |
| 129 | + | expectedOriginalPosition string // "left" or "right" child | |
| 130 | + | }{ | |
| 131 | + | {"SplitRight", "right", webkit.OrientationHorizontal, "left"}, | |
| 132 | + | {"SplitLeft", "left", webkit.OrientationHorizontal, "right"}, | |
| 133 | + | {"SplitDown", "down", webkit.OrientationVertical, "left"}, | |
| 134 | + | {"SplitUp", "up", webkit.OrientationVertical, "right"}, | |
| 135 | + | } | |
| 136 | + | ||
| 137 | + | for _, tc := range testCases { | |
| 138 | + | t.Run(tc.name, func(t *testing.T) { | |
| 139 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 140 | + | original := wm.root | |
| 141 | + | ||
| 142 | + | newNode, err := wm.splitNode(original, tc.direction) | |
| 143 | + | if err != nil { | |
| 144 | + | t.Fatalf("Split failed: %v", err) | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | parent := original.parent | |
| 148 | + | if parent.orientation != tc.expectedOrientation { | |
| 149 | + | t.Errorf("Expected orientation %v, got %v", | |
| 150 | + | tc.expectedOrientation, parent.orientation) | |
| 151 | + | } | |
| 152 | + | ||
| 153 | + | if tc.expectedOriginalPosition == "left" { | |
| 154 | + | if parent.left != original { | |
| 155 | + | t.Error("Original should be left child") | |
| 156 | + | } | |
| 157 | + | if parent.right != newNode { | |
| 158 | + | t.Error("New node should be right child") | |
| 159 | + | } | |
| 160 | + | } else { | |
| 161 | + | if parent.right != original { | |
| 162 | + | t.Error("Original should be right child") | |
| 163 | + | } | |
| 164 | + | if parent.left != newNode { | |
| 165 | + | t.Error("New node should be left child") | |
| 166 | + | } | |
| 167 | + | } | |
| 168 | + | ||
| 169 | + | validateTreeStructure(t, wm.root) | |
| 170 | + | }) | |
| 171 | + | } | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | func TestComplexTreeOperations(t *testing.T) { | |
| 175 | + | t.Run("Multiple nested splits", func(t *testing.T) { | |
| 176 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 177 | + | ||
| 178 | + | // Create complex tree: split right, then split the right pane down | |
| 179 | + | node1, _ := wm.splitNode(wm.root, "right") | |
| 180 | + | node2, _ := wm.splitNode(node1, "down") | |
| 181 | + | ||
| 182 | + | validateTreeStructure(t, wm.root) | |
| 183 | + | ||
| 184 | + | // Count nodes | |
| 185 | + | leaves, branches := countNodes(wm.root) | |
| 186 | + | if leaves != 3 { | |
| 187 | + | t.Errorf("Expected 3 leaves, got %d", leaves) | |
| 188 | + | } | |
| 189 | + | if branches != 2 { | |
| 190 | + | t.Errorf("Expected 2 branches, got %d", branches) | |
| 191 | + | } | |
| 192 | + | ||
| 193 | + | // Verify node2 is correctly positioned | |
| 194 | + | if node2 == nil { | |
| 195 | + | t.Error("node2 should not be nil") | |
| 196 | + | } | |
| 197 | + | }) | |
| 198 | + | ||
| 199 | + | t.Run("Deep nesting", func(t *testing.T) { | |
| 200 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 201 | + | ||
| 202 | + | // Create a chain of splits | |
| 203 | + | current := wm.root | |
| 204 | + | for i := 0; i < 5; i++ { | |
| 205 | + | newNode, err := wm.splitNode(current, "right") | |
| 206 | + | if err != nil { | |
| 207 | + | t.Fatalf("Split %d failed: %v", i, err) | |
| 208 | + | } | |
| 209 | + | current = newNode | |
| 210 | + | } | |
| 211 | + | ||
| 212 | + | validateTreeStructure(t, wm.root) | |
| 213 | + | ||
| 214 | + | leaves, branches := countNodes(wm.root) | |
| 215 | + | if leaves != 6 { | |
| 216 | + | t.Errorf("Expected 6 leaves, got %d", leaves) | |
| 217 | + | } | |
| 218 | + | if branches != 5 { | |
| 219 | + | t.Errorf("Expected 5 branches, got %d", branches) | |
| 220 | + | } | |
| 221 | + | }) | |
| 222 | + | } | |
| 223 | + | ||
| 224 | + | func TestPaneDeletion(t *testing.T) { | |
| 225 | + | t.Run("Close leaf promotes sibling", func(t *testing.T) { | |
| 226 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 227 | + | original := wm.root | |
| 228 | + | ||
| 229 | + | // Split and then close the new pane | |
| 230 | + | newNode, _ := wm.splitNode(original, "right") | |
| 231 | + | ||
| 232 | + | err := wm.closePane(newNode) | |
| 233 | + | if err != nil { | |
| 234 | + | t.Fatalf("Close failed: %v", err) | |
| 235 | + | } | |
| 236 | + | ||
| 237 | + | // Original should be root again | |
| 238 | + | if wm.root != original { | |
| 239 | + | t.Error("Original should be promoted to root") | |
| 240 | + | } | |
| 241 | + | if original.parent != nil { | |
| 242 | + | t.Error("Promoted node should have no parent") | |
| 243 | + | } | |
| 244 | + | ||
| 245 | + | validateTreeStructure(t, wm.root) | |
| 246 | + | }) | |
| 247 | + | ||
| 248 | + | t.Run("Close in complex tree", func(t *testing.T) { | |
| 249 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 250 | + | ||
| 251 | + | // Build tree: root splits right to A and B, then B splits down to C and D | |
| 252 | + | A := wm.root | |
| 253 | + | B, _ := wm.splitNode(A, "right") | |
| 254 | + | C, _ := wm.splitNode(B, "up") | |
| 255 | + | // D is the node that B became after split (positioned down) | |
| 256 | + | D := B.parent.right | |
| 257 | + | ||
| 258 | + | // Close C - D should be promoted to take the place of C's parent | |
| 259 | + | err := wm.closePane(C) | |
| 260 | + | if err != nil { | |
| 261 | + | t.Fatalf("Close failed: %v", err) | |
| 262 | + | } | |
| 263 | + | ||
| 264 | + | validateTreeStructure(t, wm.root) | |
| 265 | + | ||
| 266 | + | // Tree should now have A and D as siblings | |
| 267 | + | if wm.root.left != A && wm.root.right != A { | |
| 268 | + | t.Error("A should still be child of root") | |
| 269 | + | } | |
| 270 | + | if wm.root.left != D && wm.root.right != D { | |
| 271 | + | t.Error("D should be promoted as child of root") | |
| 272 | + | } | |
| 273 | + | ||
| 274 | + | leaves, branches := countNodes(wm.root) | |
| 275 | + | if leaves != 2 { | |
| 276 | + | t.Errorf("Expected 2 leaves after deletion, got %d", leaves) | |
| 277 | + | } | |
| 278 | + | if branches != 1 { | |
| 279 | + | t.Errorf("Expected 1 branch after deletion, got %d", branches) | |
| 280 | + | } | |
| 281 | + | }) | |
| 282 | + | } | |
| 283 | + | ||
| 284 | + | func TestRootDelegation(t *testing.T) { | |
| 285 | + | t.Run("Closing root with siblings delegates correctly", func(t *testing.T) { | |
| 286 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 287 | + | ||
| 288 | + | // Create structure where root will need delegation | |
| 289 | + | originalRoot := wm.root | |
| 290 | + | rightNode, _ := wm.splitNode(originalRoot, "right") | |
| 291 | + | ||
| 292 | + | // Now originalRoot and rightNode are siblings under new root | |
| 293 | + | // Try to close the left child (originalRoot) | |
| 294 | + | err := wm.closePane(originalRoot) | |
| 295 | + | if err != nil { | |
| 296 | + | t.Fatalf("Close failed: %v", err) | |
| 297 | + | } | |
| 298 | + | ||
| 299 | + | // rightNode should now be the root | |
| 300 | + | if wm.root != rightNode { | |
| 301 | + | t.Error("Right sibling should be promoted to root") | |
| 302 | + | } | |
| 303 | + | if rightNode.parent != nil { | |
| 304 | + | t.Error("New root should have no parent") | |
| 305 | + | } | |
| 306 | + | ||
| 307 | + | validateTreeStructure(t, wm.root) | |
| 308 | + | }) | |
| 309 | + | } | |
| 310 | + | ||
| 311 | + | func TestFindReplacementRoot(t *testing.T) { | |
| 312 | + | t.Run("Find replacement in complex tree", func(t *testing.T) { | |
| 313 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 314 | + | ||
| 315 | + | // Build a complex tree | |
| 316 | + | A := wm.root | |
| 317 | + | B, _ := wm.splitNode(A, "right") | |
| 318 | + | _, _ = wm.splitNode(B, "down") | |
| 319 | + | ||
| 320 | + | // Find replacement excluding A | |
| 321 | + | replacement := wm.findReplacementRoot(A) | |
| 322 | + | ||
| 323 | + | if replacement == nil { | |
| 324 | + | t.Error("Should find replacement root") | |
| 325 | + | } | |
| 326 | + | if replacement == A { | |
| 327 | + | t.Error("Replacement should not be excluded node") | |
| 328 | + | } | |
| 329 | + | }) | |
| 330 | + | ||
| 331 | + | t.Run("No replacement when only one pane", func(t *testing.T) { | |
| 332 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 333 | + | ||
| 334 | + | replacement := wm.findReplacementRoot(wm.root) | |
| 335 | + | if replacement != nil { | |
| 336 | + | t.Error("Should not find replacement when only one pane exists") | |
| 337 | + | } | |
| 338 | + | }) | |
| 339 | + | } | |
| 340 | + | ||
| 341 | + | func TestTreeTraversal(t *testing.T) { | |
| 342 | + | t.Run("CollectLeaves gathers all leaf nodes", func(t *testing.T) { | |
| 343 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 344 | + | ||
| 345 | + | // Create tree with 4 leaves | |
| 346 | + | node1, _ := wm.splitNode(wm.root, "right") | |
| 347 | + | _, _ = wm.splitNode(node1, "down") | |
| 348 | + | _, _ = wm.splitNode(wm.root, "down") | |
| 349 | + | ||
| 350 | + | leaves := wm.collectLeaves() | |
| 351 | + | ||
| 352 | + | if len(leaves) != 4 { | |
| 353 | + | t.Errorf("Expected 4 leaves, got %d", len(leaves)) | |
| 354 | + | } | |
| 355 | + | ||
| 356 | + | // All collected nodes should be leaves | |
| 357 | + | for _, leaf := range leaves { | |
| 358 | + | if !leaf.isLeaf { | |
| 359 | + | t.Error("Non-leaf node in collectLeaves result") | |
| 360 | + | } | |
| 361 | + | } | |
| 362 | + | }) | |
| 363 | + | ||
| 364 | + | t.Run("LeftmostLeaf finds correct node", func(t *testing.T) { | |
| 365 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 366 | + | ||
| 367 | + | original := wm.root | |
| 368 | + | right, _ := wm.splitNode(original, "right") | |
| 369 | + | _, _ = wm.splitNode(right, "right") | |
| 370 | + | ||
| 371 | + | leftmost := wm.leftmostLeaf(wm.root) | |
| 372 | + | if leftmost != original { | |
| 373 | + | t.Error("Leftmost should be original node") | |
| 374 | + | } | |
| 375 | + | }) | |
| 376 | + | ||
| 377 | + | t.Run("LeftmostLeaf handles nil", func(t *testing.T) { | |
| 378 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 379 | + | ||
| 380 | + | leftmost := wm.leftmostLeaf(nil) | |
| 381 | + | if leftmost != nil { | |
| 382 | + | t.Error("LeftmostLeaf should return nil for nil input") | |
| 383 | + | } | |
| 384 | + | }) | |
| 385 | + | } | |
| 386 | + | ||
| 387 | + | func TestErrorHandling(t *testing.T) { | |
| 388 | + | t.Run("Cannot split non-leaf", func(t *testing.T) { | |
| 389 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 390 | + | ||
| 391 | + | // Create a branch node | |
| 392 | + | wm.splitNode(wm.root, "right") | |
| 393 | + | ||
| 394 | + | // Try to split the branch (root is now a branch) | |
| 395 | + | _, err := wm.splitNode(wm.root, "down") | |
| 396 | + | ||
| 397 | + | if err == nil { | |
| 398 | + | t.Error("Should not allow splitting branch nodes") | |
| 399 | + | } | |
| 400 | + | }) | |
| 401 | + | ||
| 402 | + | t.Run("Cannot close non-leaf", func(t *testing.T) { | |
| 403 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 404 | + | ||
| 405 | + | wm.splitNode(wm.root, "right") | |
| 406 | + | ||
| 407 | + | // Try to close the branch node | |
| 408 | + | err := wm.closePane(wm.root) | |
| 409 | + | ||
| 410 | + | if err == nil { | |
| 411 | + | t.Error("Should not allow closing branch nodes") | |
| 412 | + | } | |
| 413 | + | }) | |
| 414 | + | ||
| 415 | + | t.Run("Cannot split nil node", func(t *testing.T) { | |
| 416 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 417 | + | ||
| 418 | + | _, err := wm.splitNode(nil, "right") | |
| 419 | + | ||
| 420 | + | if err == nil { | |
| 421 | + | t.Error("Should not allow splitting nil nodes") | |
| 422 | + | } | |
| 423 | + | }) | |
| 424 | + | ||
| 425 | + | t.Run("Cannot close nil node", func(t *testing.T) { | |
| 426 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 427 | + | ||
| 428 | + | err := wm.closePane(nil) | |
| 429 | + | ||
| 430 | + | if err == nil { | |
| 431 | + | t.Error("Should not allow closing nil nodes") | |
| 432 | + | } | |
| 433 | + | }) | |
| 434 | + | } | |
| 435 | + | ||
| 436 | + | func TestFocusManagement(t *testing.T) { | |
| 437 | + | t.Run("Focus transfers after closing active pane", func(t *testing.T) { | |
| 438 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 439 | + | ||
| 440 | + | original := wm.root | |
| 441 | + | newNode, _ := wm.splitNode(original, "right") | |
| 442 | + | ||
| 443 | + | // Set new node as active | |
| 444 | + | wm.active = newNode | |
| 445 | + | ||
| 446 | + | // Close the active node | |
| 447 | + | err := wm.closePane(newNode) | |
| 448 | + | if err != nil { | |
| 449 | + | t.Fatalf("Close failed: %v", err) | |
| 450 | + | } | |
| 451 | + | ||
| 452 | + | // Focus should transfer to original | |
| 453 | + | if wm.active != original { | |
| 454 | + | t.Error("Focus should transfer to remaining pane") | |
| 455 | + | } | |
| 456 | + | }) | |
| 457 | + | ||
| 458 | + | t.Run("Focus preserved when closing non-active pane", func(t *testing.T) { | |
| 459 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 460 | + | ||
| 461 | + | original := wm.root | |
| 462 | + | newNode, _ := wm.splitNode(original, "right") | |
| 463 | + | ||
| 464 | + | // Keep original as active | |
| 465 | + | wm.active = original | |
| 466 | + | ||
| 467 | + | // Close the non-active node | |
| 468 | + | err := wm.closePane(newNode) | |
| 469 | + | if err != nil { | |
| 470 | + | t.Fatalf("Close failed: %v", err) | |
| 471 | + | } | |
| 472 | + | ||
| 473 | + | // Focus should remain on original | |
| 474 | + | if wm.active != original { | |
| 475 | + | t.Error("Focus should remain on original pane") | |
| 476 | + | } | |
| 477 | + | }) | |
| 478 | + | } | |
| 479 | + | ||
| 480 | + | func TestEdgeCases(t *testing.T) { | |
| 481 | + | t.Run("Closing last pane", func(t *testing.T) { | |
| 482 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 483 | + | ||
| 484 | + | // Mock the app.panes slice to have only one pane | |
| 485 | + | wm.app.panes = []*BrowserPane{wm.root.pane} | |
| 486 | + | ||
| 487 | + | // Closing the last pane should handle gracefully | |
| 488 | + | err := wm.closePane(wm.root) | |
| 489 | + | if err != nil { | |
| 490 | + | t.Logf("Expected behavior: closing last pane returned error: %v", err) | |
| 491 | + | } | |
| 492 | + | }) | |
| 493 | + | ||
| 494 | + | t.Run("Split with invalid direction", func(t *testing.T) { | |
| 495 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 496 | + | ||
| 497 | + | // Test default behavior with invalid direction | |
| 498 | + | newNode, err := wm.splitNode(wm.root, "invalid") | |
| 499 | + | if err != nil { | |
| 500 | + | t.Fatalf("Split with invalid direction failed: %v", err) | |
| 501 | + | } | |
| 502 | + | ||
| 503 | + | // Should default to horizontal orientation | |
| 504 | + | if newNode.parent.orientation != webkit.OrientationHorizontal { | |
| 505 | + | t.Error("Invalid direction should default to horizontal") | |
| 506 | + | } | |
| 507 | + | }) | |
| 508 | + | } | |
| 509 | + | ||
| 510 | + | func TestViewToNodeMapping(t *testing.T) { | |
| 511 | + | t.Run("ViewToNode map updates correctly", func(t *testing.T) { | |
| 512 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |
| 513 | + | ||
| 514 | + | originalNode := wm.root | |
| 515 | + | originalView := wm.root.pane.webView | |
| 516 | + | ||
| 517 | + | // Split should add new mapping | |
| 518 | + | newNode, _ := wm.splitNode(wm.root, "right") | |
| 519 | + | newView := newNode.pane.webView | |
| 520 | + | ||
| 521 | + | // Check mapping exists for new view | |
| 522 | + | if mapped, ok := wm.viewToNode[newView]; !ok || mapped != newNode { | |
| 523 | + | t.Error("New webview should be mapped to new node") | |
| 524 | + | } | |
| 525 | + | ||
| 526 | + | // Original mapping should still exist - note that wm.root may have changed after split | |
| 527 | + | if mapped, ok := wm.viewToNode[originalView]; !ok || mapped != originalNode { | |
| 528 | + | t.Error("Original webview mapping should be preserved") | |
| 529 | + | } | |
| 530 | + | ||
| 531 | + | // Close should remove mapping | |
| 532 | + | wm.closePane(newNode) | |
| 533 | + | ||
| 534 | + | if _, ok := wm.viewToNode[newView]; ok { | |
| 535 | + | t.Error("Closed pane's webview should be removed from mapping") | |
| 536 | + | } | |
| 537 | + | }) | |
| 538 | + | } | |
| 539 | + | ||
| 540 | + | // Helper to create WorkspaceManager with minimal mocked dependencies | |
| 541 | + | func newTestWorkspaceManagerWithMocksForTree(t *testing.T) *WorkspaceManager { | |
| 542 | + | t.Helper() | |
| 543 | + | webkit.ResetWidgetStubsForTesting() | |
| 544 | + | ||
| 545 | + | // Create a real WebView for testing (stub implementation) | |
| 546 | + | rootView, err := webkit.NewWebView(&webkit.Config{}) | |
| 547 | + | if err != nil { | |
| 548 | + | t.Fatalf("failed to create root webview: %v", err) | |
| 549 | + | } | |
| 550 | + | ||
| 551 | + | rootPane := &BrowserPane{ | |
| 552 | + | webView: rootView, | |
| 553 | + | id: "root", | |
| 554 | + | } | |
| 555 | + | ||
| 556 | + | app := &BrowserApp{ | |
| 557 | + | panes: []*BrowserPane{rootPane}, | |
| 558 | + | activePane: rootPane, | |
| 559 | + | webView: rootView, | |
| 560 | + | } | |
| 561 | + | ||
| 562 | + | wm := &WorkspaceManager{ | |
| 563 | + | app: app, | |
| 564 | + | window: rootView.Window(), | |
| 565 | + | viewToNode: make(map[*webkit.WebView]*paneNode), | |
| 566 | + | lastSplitMsg: make(map[*webkit.WebView]time.Time), | |
| 567 | + | lastExitMsg: make(map[*webkit.WebView]time.Time), | |
| 568 | + | } | |
| 569 | + | ||
| 570 | + | // Set up mock factories | |
| 571 | + | wm.createWebViewFn = func() (*webkit.WebView, error) { | |
| 572 | + | return webkit.NewWebView(&webkit.Config{}) | |
| 573 | + | } | |
| 574 | + | ||
| 575 | + | wm.createPaneFn = func(view *webkit.WebView) (*BrowserPane, error) { | |
| 576 | + | return &BrowserPane{ | |
| 577 | + | webView: view, | |
| 578 | + | id: fmt.Sprintf("pane-%p", view), | |
| 579 | + | }, nil | |
| 580 | + | } | |
| 581 | + | ||
| 582 | + | // Initialize root | |
| 583 | + | root := &paneNode{ | |
| 584 | + | pane: rootPane, | |
| 585 | + | container: rootView.RootWidget(), | |
| 586 | + | isLeaf: true, | |
| 587 | + | } | |
| 588 | + | wm.root = root | |
| 589 | + | wm.active = root | |
| 590 | + | wm.mainPane = root | |
| 591 | + | wm.viewToNode[rootView] = root | |
| 592 | + | ||
| 593 | + | return wm | |
| 594 | + | } | |
| @@ -0,0 +1,594 @@ | |||||
| 1 | + | package browser | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "fmt" | |||
| 5 | + | "testing" | |||
| 6 | + | "time" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/dumber/pkg/webkit" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | // Test helper to create mock pane node | |||
| 12 | + | func newMockPaneNode(id string) *paneNode { | |||
| 13 | + | container := uintptr(100 + len(id)) | |||
| 14 | + | ||||
| 15 | + | return &paneNode{ | |||
| 16 | + | pane: &BrowserPane{id: id}, | |||
| 17 | + | container: container, | |||
| 18 | + | isLeaf: true, | |||
| 19 | + | } | |||
| 20 | + | } | |||
| 21 | + | ||||
| 22 | + | // Test tree validation helper | |||
| 23 | + | func validateTreeStructure(t *testing.T, node *paneNode) { | |||
| 24 | + | t.Helper() | |||
| 25 | + | ||||
| 26 | + | if node == nil { | |||
| 27 | + | return | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | if node.isLeaf { | |||
| 31 | + | // Leaf nodes should not have children | |||
| 32 | + | if node.left != nil || node.right != nil { | |||
| 33 | + | t.Errorf("Leaf node has children: left=%v, right=%v", node.left, node.right) | |||
| 34 | + | } | |||
| 35 | + | // Leaf nodes must have a pane | |||
| 36 | + | if node.pane == nil { | |||
| 37 | + | t.Error("Leaf node missing pane") | |||
| 38 | + | } | |||
| 39 | + | } else { | |||
| 40 | + | // Branch nodes must have both children | |||
| 41 | + | if node.left == nil || node.right == nil { | |||
| 42 | + | t.Errorf("Branch node missing children: left=%v, right=%v", node.left, node.right) | |||
| 43 | + | } | |||
| 44 | + | // Branch nodes should not have a pane | |||
| 45 | + | if node.pane != nil { | |||
| 46 | + | t.Error("Branch node should not have pane") | |||
| 47 | + | } | |||
| 48 | + | // Validate children's parent pointers | |||
| 49 | + | if node.left != nil && node.left.parent != node { | |||
| 50 | + | t.Error("Left child's parent pointer incorrect") | |||
| 51 | + | } | |||
| 52 | + | if node.right != nil && node.right.parent != node { | |||
| 53 | + | t.Error("Right child's parent pointer incorrect") | |||
| 54 | + | } | |||
| 55 | + | // Recursively validate subtrees | |||
| 56 | + | validateTreeStructure(t, node.left) | |||
| 57 | + | validateTreeStructure(t, node.right) | |||
| 58 | + | } | |||
| 59 | + | } | |||
| 60 | + | ||||
| 61 | + | // Count nodes in tree | |||
| 62 | + | func countNodes(node *paneNode) (leaves, branches int) { | |||
| 63 | + | if node == nil { | |||
| 64 | + | return 0, 0 | |||
| 65 | + | } | |||
| 66 | + | if node.isLeaf { | |||
| 67 | + | return 1, 0 | |||
| 68 | + | } | |||
| 69 | + | leftLeaves, leftBranches := countNodes(node.left) | |||
| 70 | + | rightLeaves, rightBranches := countNodes(node.right) | |||
| 71 | + | return leftLeaves + rightLeaves, leftBranches + rightBranches + 1 | |||
| 72 | + | } | |||
| 73 | + | ||||
| 74 | + | func TestPaneNodeTreeInvariants(t *testing.T) { | |||
| 75 | + | t.Run("Initial root is leaf", func(t *testing.T) { | |||
| 76 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 77 | + | ||||
| 78 | + | if !wm.root.isLeaf { | |||
| 79 | + | t.Error("Initial root should be a leaf") | |||
| 80 | + | } | |||
| 81 | + | if wm.root.parent != nil { | |||
| 82 | + | t.Error("Root should have no parent") | |||
| 83 | + | } | |||
| 84 | + | validateTreeStructure(t, wm.root) | |||
| 85 | + | }) | |||
| 86 | + | ||||
| 87 | + | t.Run("Tree structure after single split", func(t *testing.T) { | |||
| 88 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 89 | + | original := wm.root | |||
| 90 | + | ||||
| 91 | + | // Split right | |||
| 92 | + | newNode, err := wm.splitNode(original, "right") | |||
| 93 | + | if err != nil { | |||
| 94 | + | t.Fatalf("Split failed: %v", err) | |||
| 95 | + | } | |||
| 96 | + | ||||
| 97 | + | // Validate new tree structure | |||
| 98 | + | validateTreeStructure(t, wm.root) | |||
| 99 | + | ||||
| 100 | + | // Root should now be a branch | |||
| 101 | + | if wm.root.isLeaf { | |||
| 102 | + | t.Error("Root should be branch after split") | |||
| 103 | + | } | |||
| 104 | + | ||||
| 105 | + | // Original and new nodes should be siblings | |||
| 106 | + | if wm.root.left != original { | |||
| 107 | + | t.Error("Original node should be left child") | |||
| 108 | + | } | |||
| 109 | + | if wm.root.right != newNode { | |||
| 110 | + | t.Error("New node should be right child") | |||
| 111 | + | } | |||
| 112 | + | ||||
| 113 | + | // Count nodes | |||
| 114 | + | leaves, branches := countNodes(wm.root) | |||
| 115 | + | if leaves != 2 { | |||
| 116 | + | t.Errorf("Expected 2 leaves, got %d", leaves) | |||
| 117 | + | } | |||
| 118 | + | if branches != 1 { | |||
| 119 | + | t.Errorf("Expected 1 branch, got %d", branches) | |||
| 120 | + | } | |||
| 121 | + | }) | |||
| 122 | + | } | |||
| 123 | + | ||||
| 124 | + | func TestPaneSplitDirections(t *testing.T) { | |||
| 125 | + | testCases := []struct { | |||
| 126 | + | name string | |||
| 127 | + | direction string | |||
| 128 | + | expectedOrientation webkit.Orientation | |||
| 129 | + | expectedOriginalPosition string // "left" or "right" child | |||
| 130 | + | }{ | |||
| 131 | + | {"SplitRight", "right", webkit.OrientationHorizontal, "left"}, | |||
| 132 | + | {"SplitLeft", "left", webkit.OrientationHorizontal, "right"}, | |||
| 133 | + | {"SplitDown", "down", webkit.OrientationVertical, "left"}, | |||
| 134 | + | {"SplitUp", "up", webkit.OrientationVertical, "right"}, | |||
| 135 | + | } | |||
| 136 | + | ||||
| 137 | + | for _, tc := range testCases { | |||
| 138 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 139 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 140 | + | original := wm.root | |||
| 141 | + | ||||
| 142 | + | newNode, err := wm.splitNode(original, tc.direction) | |||
| 143 | + | if err != nil { | |||
| 144 | + | t.Fatalf("Split failed: %v", err) | |||
| 145 | + | } | |||
| 146 | + | ||||
| 147 | + | parent := original.parent | |||
| 148 | + | if parent.orientation != tc.expectedOrientation { | |||
| 149 | + | t.Errorf("Expected orientation %v, got %v", | |||
| 150 | + | tc.expectedOrientation, parent.orientation) | |||
| 151 | + | } | |||
| 152 | + | ||||
| 153 | + | if tc.expectedOriginalPosition == "left" { | |||
| 154 | + | if parent.left != original { | |||
| 155 | + | t.Error("Original should be left child") | |||
| 156 | + | } | |||
| 157 | + | if parent.right != newNode { | |||
| 158 | + | t.Error("New node should be right child") | |||
| 159 | + | } | |||
| 160 | + | } else { | |||
| 161 | + | if parent.right != original { | |||
| 162 | + | t.Error("Original should be right child") | |||
| 163 | + | } | |||
| 164 | + | if parent.left != newNode { | |||
| 165 | + | t.Error("New node should be left child") | |||
| 166 | + | } | |||
| 167 | + | } | |||
| 168 | + | ||||
| 169 | + | validateTreeStructure(t, wm.root) | |||
| 170 | + | }) | |||
| 171 | + | } | |||
| 172 | + | } | |||
| 173 | + | ||||
| 174 | + | func TestComplexTreeOperations(t *testing.T) { | |||
| 175 | + | t.Run("Multiple nested splits", func(t *testing.T) { | |||
| 176 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 177 | + | ||||
| 178 | + | // Create complex tree: split right, then split the right pane down | |||
| 179 | + | node1, _ := wm.splitNode(wm.root, "right") | |||
| 180 | + | node2, _ := wm.splitNode(node1, "down") | |||
| 181 | + | ||||
| 182 | + | validateTreeStructure(t, wm.root) | |||
| 183 | + | ||||
| 184 | + | // Count nodes | |||
| 185 | + | leaves, branches := countNodes(wm.root) | |||
| 186 | + | if leaves != 3 { | |||
| 187 | + | t.Errorf("Expected 3 leaves, got %d", leaves) | |||
| 188 | + | } | |||
| 189 | + | if branches != 2 { | |||
| 190 | + | t.Errorf("Expected 2 branches, got %d", branches) | |||
| 191 | + | } | |||
| 192 | + | ||||
| 193 | + | // Verify node2 is correctly positioned | |||
| 194 | + | if node2 == nil { | |||
| 195 | + | t.Error("node2 should not be nil") | |||
| 196 | + | } | |||
| 197 | + | }) | |||
| 198 | + | ||||
| 199 | + | t.Run("Deep nesting", func(t *testing.T) { | |||
| 200 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 201 | + | ||||
| 202 | + | // Create a chain of splits | |||
| 203 | + | current := wm.root | |||
| 204 | + | for i := 0; i < 5; i++ { | |||
| 205 | + | newNode, err := wm.splitNode(current, "right") | |||
| 206 | + | if err != nil { | |||
| 207 | + | t.Fatalf("Split %d failed: %v", i, err) | |||
| 208 | + | } | |||
| 209 | + | current = newNode | |||
| 210 | + | } | |||
| 211 | + | ||||
| 212 | + | validateTreeStructure(t, wm.root) | |||
| 213 | + | ||||
| 214 | + | leaves, branches := countNodes(wm.root) | |||
| 215 | + | if leaves != 6 { | |||
| 216 | + | t.Errorf("Expected 6 leaves, got %d", leaves) | |||
| 217 | + | } | |||
| 218 | + | if branches != 5 { | |||
| 219 | + | t.Errorf("Expected 5 branches, got %d", branches) | |||
| 220 | + | } | |||
| 221 | + | }) | |||
| 222 | + | } | |||
| 223 | + | ||||
| 224 | + | func TestPaneDeletion(t *testing.T) { | |||
| 225 | + | t.Run("Close leaf promotes sibling", func(t *testing.T) { | |||
| 226 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 227 | + | original := wm.root | |||
| 228 | + | ||||
| 229 | + | // Split and then close the new pane | |||
| 230 | + | newNode, _ := wm.splitNode(original, "right") | |||
| 231 | + | ||||
| 232 | + | err := wm.closePane(newNode) | |||
| 233 | + | if err != nil { | |||
| 234 | + | t.Fatalf("Close failed: %v", err) | |||
| 235 | + | } | |||
| 236 | + | ||||
| 237 | + | // Original should be root again | |||
| 238 | + | if wm.root != original { | |||
| 239 | + | t.Error("Original should be promoted to root") | |||
| 240 | + | } | |||
| 241 | + | if original.parent != nil { | |||
| 242 | + | t.Error("Promoted node should have no parent") | |||
| 243 | + | } | |||
| 244 | + | ||||
| 245 | + | validateTreeStructure(t, wm.root) | |||
| 246 | + | }) | |||
| 247 | + | ||||
| 248 | + | t.Run("Close in complex tree", func(t *testing.T) { | |||
| 249 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 250 | + | ||||
| 251 | + | // Build tree: root splits right to A and B, then B splits down to C and D | |||
| 252 | + | A := wm.root | |||
| 253 | + | B, _ := wm.splitNode(A, "right") | |||
| 254 | + | C, _ := wm.splitNode(B, "up") | |||
| 255 | + | // D is the node that B became after split (positioned down) | |||
| 256 | + | D := B.parent.right | |||
| 257 | + | ||||
| 258 | + | // Close C - D should be promoted to take the place of C's parent | |||
| 259 | + | err := wm.closePane(C) | |||
| 260 | + | if err != nil { | |||
| 261 | + | t.Fatalf("Close failed: %v", err) | |||
| 262 | + | } | |||
| 263 | + | ||||
| 264 | + | validateTreeStructure(t, wm.root) | |||
| 265 | + | ||||
| 266 | + | // Tree should now have A and D as siblings | |||
| 267 | + | if wm.root.left != A && wm.root.right != A { | |||
| 268 | + | t.Error("A should still be child of root") | |||
| 269 | + | } | |||
| 270 | + | if wm.root.left != D && wm.root.right != D { | |||
| 271 | + | t.Error("D should be promoted as child of root") | |||
| 272 | + | } | |||
| 273 | + | ||||
| 274 | + | leaves, branches := countNodes(wm.root) | |||
| 275 | + | if leaves != 2 { | |||
| 276 | + | t.Errorf("Expected 2 leaves after deletion, got %d", leaves) | |||
| 277 | + | } | |||
| 278 | + | if branches != 1 { | |||
| 279 | + | t.Errorf("Expected 1 branch after deletion, got %d", branches) | |||
| 280 | + | } | |||
| 281 | + | }) | |||
| 282 | + | } | |||
| 283 | + | ||||
| 284 | + | func TestRootDelegation(t *testing.T) { | |||
| 285 | + | t.Run("Closing root with siblings delegates correctly", func(t *testing.T) { | |||
| 286 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 287 | + | ||||
| 288 | + | // Create structure where root will need delegation | |||
| 289 | + | originalRoot := wm.root | |||
| 290 | + | rightNode, _ := wm.splitNode(originalRoot, "right") | |||
| 291 | + | ||||
| 292 | + | // Now originalRoot and rightNode are siblings under new root | |||
| 293 | + | // Try to close the left child (originalRoot) | |||
| 294 | + | err := wm.closePane(originalRoot) | |||
| 295 | + | if err != nil { | |||
| 296 | + | t.Fatalf("Close failed: %v", err) | |||
| 297 | + | } | |||
| 298 | + | ||||
| 299 | + | // rightNode should now be the root | |||
| 300 | + | if wm.root != rightNode { | |||
| 301 | + | t.Error("Right sibling should be promoted to root") | |||
| 302 | + | } | |||
| 303 | + | if rightNode.parent != nil { | |||
| 304 | + | t.Error("New root should have no parent") | |||
| 305 | + | } | |||
| 306 | + | ||||
| 307 | + | validateTreeStructure(t, wm.root) | |||
| 308 | + | }) | |||
| 309 | + | } | |||
| 310 | + | ||||
| 311 | + | func TestFindReplacementRoot(t *testing.T) { | |||
| 312 | + | t.Run("Find replacement in complex tree", func(t *testing.T) { | |||
| 313 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 314 | + | ||||
| 315 | + | // Build a complex tree | |||
| 316 | + | A := wm.root | |||
| 317 | + | B, _ := wm.splitNode(A, "right") | |||
| 318 | + | _, _ = wm.splitNode(B, "down") | |||
| 319 | + | ||||
| 320 | + | // Find replacement excluding A | |||
| 321 | + | replacement := wm.findReplacementRoot(A) | |||
| 322 | + | ||||
| 323 | + | if replacement == nil { | |||
| 324 | + | t.Error("Should find replacement root") | |||
| 325 | + | } | |||
| 326 | + | if replacement == A { | |||
| 327 | + | t.Error("Replacement should not be excluded node") | |||
| 328 | + | } | |||
| 329 | + | }) | |||
| 330 | + | ||||
| 331 | + | t.Run("No replacement when only one pane", func(t *testing.T) { | |||
| 332 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 333 | + | ||||
| 334 | + | replacement := wm.findReplacementRoot(wm.root) | |||
| 335 | + | if replacement != nil { | |||
| 336 | + | t.Error("Should not find replacement when only one pane exists") | |||
| 337 | + | } | |||
| 338 | + | }) | |||
| 339 | + | } | |||
| 340 | + | ||||
| 341 | + | func TestTreeTraversal(t *testing.T) { | |||
| 342 | + | t.Run("CollectLeaves gathers all leaf nodes", func(t *testing.T) { | |||
| 343 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 344 | + | ||||
| 345 | + | // Create tree with 4 leaves | |||
| 346 | + | node1, _ := wm.splitNode(wm.root, "right") | |||
| 347 | + | _, _ = wm.splitNode(node1, "down") | |||
| 348 | + | _, _ = wm.splitNode(wm.root, "down") | |||
| 349 | + | ||||
| 350 | + | leaves := wm.collectLeaves() | |||
| 351 | + | ||||
| 352 | + | if len(leaves) != 4 { | |||
| 353 | + | t.Errorf("Expected 4 leaves, got %d", len(leaves)) | |||
| 354 | + | } | |||
| 355 | + | ||||
| 356 | + | // All collected nodes should be leaves | |||
| 357 | + | for _, leaf := range leaves { | |||
| 358 | + | if !leaf.isLeaf { | |||
| 359 | + | t.Error("Non-leaf node in collectLeaves result") | |||
| 360 | + | } | |||
| 361 | + | } | |||
| 362 | + | }) | |||
| 363 | + | ||||
| 364 | + | t.Run("LeftmostLeaf finds correct node", func(t *testing.T) { | |||
| 365 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 366 | + | ||||
| 367 | + | original := wm.root | |||
| 368 | + | right, _ := wm.splitNode(original, "right") | |||
| 369 | + | _, _ = wm.splitNode(right, "right") | |||
| 370 | + | ||||
| 371 | + | leftmost := wm.leftmostLeaf(wm.root) | |||
| 372 | + | if leftmost != original { | |||
| 373 | + | t.Error("Leftmost should be original node") | |||
| 374 | + | } | |||
| 375 | + | }) | |||
| 376 | + | ||||
| 377 | + | t.Run("LeftmostLeaf handles nil", func(t *testing.T) { | |||
| 378 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 379 | + | ||||
| 380 | + | leftmost := wm.leftmostLeaf(nil) | |||
| 381 | + | if leftmost != nil { | |||
| 382 | + | t.Error("LeftmostLeaf should return nil for nil input") | |||
| 383 | + | } | |||
| 384 | + | }) | |||
| 385 | + | } | |||
| 386 | + | ||||
| 387 | + | func TestErrorHandling(t *testing.T) { | |||
| 388 | + | t.Run("Cannot split non-leaf", func(t *testing.T) { | |||
| 389 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 390 | + | ||||
| 391 | + | // Create a branch node | |||
| 392 | + | wm.splitNode(wm.root, "right") | |||
| 393 | + | ||||
| 394 | + | // Try to split the branch (root is now a branch) | |||
| 395 | + | _, err := wm.splitNode(wm.root, "down") | |||
| 396 | + | ||||
| 397 | + | if err == nil { | |||
| 398 | + | t.Error("Should not allow splitting branch nodes") | |||
| 399 | + | } | |||
| 400 | + | }) | |||
| 401 | + | ||||
| 402 | + | t.Run("Cannot close non-leaf", func(t *testing.T) { | |||
| 403 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 404 | + | ||||
| 405 | + | wm.splitNode(wm.root, "right") | |||
| 406 | + | ||||
| 407 | + | // Try to close the branch node | |||
| 408 | + | err := wm.closePane(wm.root) | |||
| 409 | + | ||||
| 410 | + | if err == nil { | |||
| 411 | + | t.Error("Should not allow closing branch nodes") | |||
| 412 | + | } | |||
| 413 | + | }) | |||
| 414 | + | ||||
| 415 | + | t.Run("Cannot split nil node", func(t *testing.T) { | |||
| 416 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 417 | + | ||||
| 418 | + | _, err := wm.splitNode(nil, "right") | |||
| 419 | + | ||||
| 420 | + | if err == nil { | |||
| 421 | + | t.Error("Should not allow splitting nil nodes") | |||
| 422 | + | } | |||
| 423 | + | }) | |||
| 424 | + | ||||
| 425 | + | t.Run("Cannot close nil node", func(t *testing.T) { | |||
| 426 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 427 | + | ||||
| 428 | + | err := wm.closePane(nil) | |||
| 429 | + | ||||
| 430 | + | if err == nil { | |||
| 431 | + | t.Error("Should not allow closing nil nodes") | |||
| 432 | + | } | |||
| 433 | + | }) | |||
| 434 | + | } | |||
| 435 | + | ||||
| 436 | + | func TestFocusManagement(t *testing.T) { | |||
| 437 | + | t.Run("Focus transfers after closing active pane", func(t *testing.T) { | |||
| 438 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 439 | + | ||||
| 440 | + | original := wm.root | |||
| 441 | + | newNode, _ := wm.splitNode(original, "right") | |||
| 442 | + | ||||
| 443 | + | // Set new node as active | |||
| 444 | + | wm.active = newNode | |||
| 445 | + | ||||
| 446 | + | // Close the active node | |||
| 447 | + | err := wm.closePane(newNode) | |||
| 448 | + | if err != nil { | |||
| 449 | + | t.Fatalf("Close failed: %v", err) | |||
| 450 | + | } | |||
| 451 | + | ||||
| 452 | + | // Focus should transfer to original | |||
| 453 | + | if wm.active != original { | |||
| 454 | + | t.Error("Focus should transfer to remaining pane") | |||
| 455 | + | } | |||
| 456 | + | }) | |||
| 457 | + | ||||
| 458 | + | t.Run("Focus preserved when closing non-active pane", func(t *testing.T) { | |||
| 459 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 460 | + | ||||
| 461 | + | original := wm.root | |||
| 462 | + | newNode, _ := wm.splitNode(original, "right") | |||
| 463 | + | ||||
| 464 | + | // Keep original as active | |||
| 465 | + | wm.active = original | |||
| 466 | + | ||||
| 467 | + | // Close the non-active node | |||
| 468 | + | err := wm.closePane(newNode) | |||
| 469 | + | if err != nil { | |||
| 470 | + | t.Fatalf("Close failed: %v", err) | |||
| 471 | + | } | |||
| 472 | + | ||||
| 473 | + | // Focus should remain on original | |||
| 474 | + | if wm.active != original { | |||
| 475 | + | t.Error("Focus should remain on original pane") | |||
| 476 | + | } | |||
| 477 | + | }) | |||
| 478 | + | } | |||
| 479 | + | ||||
| 480 | + | func TestEdgeCases(t *testing.T) { | |||
| 481 | + | t.Run("Closing last pane", func(t *testing.T) { | |||
| 482 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 483 | + | ||||
| 484 | + | // Mock the app.panes slice to have only one pane | |||
| 485 | + | wm.app.panes = []*BrowserPane{wm.root.pane} | |||
| 486 | + | ||||
| 487 | + | // Closing the last pane should handle gracefully | |||
| 488 | + | err := wm.closePane(wm.root) | |||
| 489 | + | if err != nil { | |||
| 490 | + | t.Logf("Expected behavior: closing last pane returned error: %v", err) | |||
| 491 | + | } | |||
| 492 | + | }) | |||
| 493 | + | ||||
| 494 | + | t.Run("Split with invalid direction", func(t *testing.T) { | |||
| 495 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 496 | + | ||||
| 497 | + | // Test default behavior with invalid direction | |||
| 498 | + | newNode, err := wm.splitNode(wm.root, "invalid") | |||
| 499 | + | if err != nil { | |||
| 500 | + | t.Fatalf("Split with invalid direction failed: %v", err) | |||
| 501 | + | } | |||
| 502 | + | ||||
| 503 | + | // Should default to horizontal orientation | |||
| 504 | + | if newNode.parent.orientation != webkit.OrientationHorizontal { | |||
| 505 | + | t.Error("Invalid direction should default to horizontal") | |||
| 506 | + | } | |||
| 507 | + | }) | |||
| 508 | + | } | |||
| 509 | + | ||||
| 510 | + | func TestViewToNodeMapping(t *testing.T) { | |||
| 511 | + | t.Run("ViewToNode map updates correctly", func(t *testing.T) { | |||
| 512 | + | wm := newTestWorkspaceManagerWithMocksForTree(t) | |||
| 513 | + | ||||
| 514 | + | originalNode := wm.root | |||
| 515 | + | originalView := wm.root.pane.webView | |||
| 516 | + | ||||
| 517 | + | // Split should add new mapping | |||
| 518 | + | newNode, _ := wm.splitNode(wm.root, "right") | |||
| 519 | + | newView := newNode.pane.webView | |||
| 520 | + | ||||
| 521 | + | // Check mapping exists for new view | |||
| 522 | + | if mapped, ok := wm.viewToNode[newView]; !ok || mapped != newNode { | |||
| 523 | + | t.Error("New webview should be mapped to new node") | |||
| 524 | + | } | |||
| 525 | + | ||||
| 526 | + | // Original mapping should still exist - note that wm.root may have changed after split | |||
| 527 | + | if mapped, ok := wm.viewToNode[originalView]; !ok || mapped != originalNode { | |||
| 528 | + | t.Error("Original webview mapping should be preserved") | |||
| 529 | + | } | |||
| 530 | + | ||||
| 531 | + | // Close should remove mapping | |||
| 532 | + | wm.closePane(newNode) | |||
| 533 | + | ||||
| 534 | + | if _, ok := wm.viewToNode[newView]; ok { | |||
| 535 | + | t.Error("Closed pane's webview should be removed from mapping") | |||
| 536 | + | } | |||
| 537 | + | }) | |||
| 538 | + | } | |||
| 539 | + | ||||
| 540 | + | // Helper to create WorkspaceManager with minimal mocked dependencies | |||
| 541 | + | func newTestWorkspaceManagerWithMocksForTree(t *testing.T) *WorkspaceManager { | |||
| 542 | + | t.Helper() | |||
| 543 | + | webkit.ResetWidgetStubsForTesting() | |||
| 544 | + | ||||
| 545 | + | // Create a real WebView for testing (stub implementation) | |||
| 546 | + | rootView, err := webkit.NewWebView(&webkit.Config{}) | |||
| 547 | + | if err != nil { | |||
| 548 | + | t.Fatalf("failed to create root webview: %v", err) | |||
| 549 | + | } | |||
| 550 | + | ||||
| 551 | + | rootPane := &BrowserPane{ | |||
| 552 | + | webView: rootView, | |||
| 553 | + | id: "root", | |||
| 554 | + | } | |||
| 555 | + | ||||
| 556 | + | app := &BrowserApp{ | |||
| 557 | + | panes: []*BrowserPane{rootPane}, | |||
| 558 | + | activePane: rootPane, | |||
| 559 | + | webView: rootView, | |||
| 560 | + | } | |||
| 561 | + | ||||
| 562 | + | wm := &WorkspaceManager{ | |||
| 563 | + | app: app, | |||
| 564 | + | window: rootView.Window(), | |||
| 565 | + | viewToNode: make(map[*webkit.WebView]*paneNode), | |||
| 566 | + | lastSplitMsg: make(map[*webkit.WebView]time.Time), | |||
| 567 | + | lastExitMsg: make(map[*webkit.WebView]time.Time), | |||
| 568 | + | } | |||
| 569 | + | ||||
| 570 | + | // Set up mock factories | |||
| 571 | + | wm.createWebViewFn = func() (*webkit.WebView, error) { | |||
| 572 | + | return webkit.NewWebView(&webkit.Config{}) | |||
| 573 | + | } | |||
| 574 | + | ||||
| 575 | + | wm.createPaneFn = func(view *webkit.WebView) (*BrowserPane, error) { | |||
| 576 | + | return &BrowserPane{ | |||
| 577 | + | webView: view, | |||
| 578 | + | id: fmt.Sprintf("pane-%p", view), | |||
| 579 | + | }, nil | |||
| 580 | + | } | |||
| 581 | + | ||||
| 582 | + | // Initialize root | |||
| 583 | + | root := &paneNode{ | |||
| 584 | + | pane: rootPane, | |||
| 585 | + | container: rootView.RootWidget(), | |||
| 586 | + | isLeaf: true, | |||
| 587 | + | } | |||
| 588 | + | wm.root = root | |||
| 589 | + | wm.active = root | |||
| 590 | + | wm.mainPane = root | |||
| 591 | + | wm.viewToNode[rootView] = root | |||
| 592 | + | ||||
| 593 | + | return wm | |||
| 594 | + | } | |||
A pkg/webkit/window_shortcuts_stub.go +45 -0
| @@ -0,0 +1,45 @@ | |||
| 1 | + | //go:build !webkit_cgo | |
| 2 | + | ||
| 3 | + | package webkit | |
| 4 | + | ||
| 5 | + | import "log" | |
| 6 | + | ||
| 7 | + | // WindowShortcuts stub implementation for non-CGO builds | |
| 8 | + | type WindowShortcuts struct { | |
| 9 | + | window *Window | |
| 10 | + | } | |
| 11 | + | ||
| 12 | + | // InitializeGlobalShortcuts returns a stub implementation for non-CGO builds | |
| 13 | + | func (w *Window) InitializeGlobalShortcuts() *WindowShortcuts { | |
| 14 | + | log.Printf("[window-shortcuts] Stub implementation - global shortcuts not available without webkit_cgo") | |
| 15 | + | return &WindowShortcuts{ | |
| 16 | + | window: w, | |
| 17 | + | } | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | // RegisterGlobalShortcut stub - always returns nil for non-CGO builds | |
| 21 | + | func (ws *WindowShortcuts) RegisterGlobalShortcut(key string, callback func()) error { | |
| 22 | + | log.Printf("[window-shortcuts] Stub: would register %s (webkit_cgo required for actual functionality)", key) | |
| 23 | + | return nil | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | // Cleanup stub implementation | |
| 27 | + | func (ws *WindowShortcuts) Cleanup() { | |
| 28 | + | log.Printf("[window-shortcuts] Stub cleanup") | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | // Stub implementations for Go wrapper functions | |
| 32 | + | func CreateGlobalShortcutController(window uintptr) uintptr { | |
| 33 | + | return 0 | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | func AddShortcutToController(controller uintptr, key string, handle uintptr) error { | |
| 37 | + | return nil | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | func registerWindowShortcutCallback(cb func()) uintptr { | |
| 41 | + | return 0 | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | func unregisterWindowShortcutCallback(id uintptr) { | |
| 45 | + | }⏎̸ | |
| @@ -0,0 +1,45 @@ | |||||
| 1 | + | //go:build !webkit_cgo | |||
| 2 | + | ||||
| 3 | + | package webkit | |||
| 4 | + | ||||
| 5 | + | import "log" | |||
| 6 | + | ||||
| 7 | + | // WindowShortcuts stub implementation for non-CGO builds | |||
| 8 | + | type WindowShortcuts struct { | |||
| 9 | + | window *Window | |||
| 10 | + | } | |||
| 11 | + | ||||
| 12 | + | // InitializeGlobalShortcuts returns a stub implementation for non-CGO builds | |||
| 13 | + | func (w *Window) InitializeGlobalShortcuts() *WindowShortcuts { | |||
| 14 | + | log.Printf("[window-shortcuts] Stub implementation - global shortcuts not available without webkit_cgo") | |||
| 15 | + | return &WindowShortcuts{ | |||
| 16 | + | window: w, | |||
| 17 | + | } | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | // RegisterGlobalShortcut stub - always returns nil for non-CGO builds | |||
| 21 | + | func (ws *WindowShortcuts) RegisterGlobalShortcut(key string, callback func()) error { | |||
| 22 | + | log.Printf("[window-shortcuts] Stub: would register %s (webkit_cgo required for actual functionality)", key) | |||
| 23 | + | return nil | |||
| 24 | + | } | |||
| 25 | + | ||||
| 26 | + | // Cleanup stub implementation | |||
| 27 | + | func (ws *WindowShortcuts) Cleanup() { | |||
| 28 | + | log.Printf("[window-shortcuts] Stub cleanup") | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | // Stub implementations for Go wrapper functions | |||
| 32 | + | func CreateGlobalShortcutController(window uintptr) uintptr { | |||
| 33 | + | return 0 | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | func AddShortcutToController(controller uintptr, key string, handle uintptr) error { | |||
| 37 | + | return nil | |||
| 38 | + | } | |||
| 39 | + | ||||
| 40 | + | func registerWindowShortcutCallback(cb func()) uintptr { | |||
| 41 | + | return 0 | |||
| 42 | + | } | |||
| 43 | + | ||||
| 44 | + | func unregisterWindowShortcutCallback(id uintptr) { | |||
| 45 | + | }⏎̸ | |||