fix: update tests for signal disconnection in stacked panes
- commit
- a9874757a79ae249fe42258a30e1f8693f57f87c
- author
- brice · · 2026-01-01 14:19 UTC
- parent
- f9984f3
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +27 -13
M internal/ui/layout/stacked.go +23 -13
| @@ -216,28 +216,38 @@ func (sv *StackedView) connectTitleBarHandlers(tb titleBarComponents, paneID str | |||
| 216 | 216 | ||
| 217 | 217 | // disconnectPaneSignals disconnects signal handlers from a pane's buttons. | |
| 218 | 218 | // This prevents memory leaks when panes are removed from the stack. | |
| 219 | + | // Note: This is a no-op when using mock widgets in tests (GtkWidget returns nil). | |
| 219 | 220 | func (sv *StackedView) disconnectPaneSignals(pane *stackedPane) { | |
| 220 | 221 | if pane == nil { | |
| 221 | 222 | return | |
| 222 | 223 | } | |
| 223 | 224 | ||
| 224 | 225 | // Disconnect title button click signal | |
| 225 | - | if pane.titleButton != nil && pane.titleClickSignalID != 0 { | |
| 226 | - | gtkWidget := pane.titleButton.GtkWidget() | |
| 227 | - | if gtkWidget != nil { | |
| 228 | - | obj := gobject.ObjectNewFromInternalPtr(gtkWidget.GoPointer()) | |
| 229 | - | gobject.SignalHandlerDisconnect(obj, pane.titleClickSignalID) | |
| 230 | - | } | |
| 231 | - | } | |
| 226 | + | disconnectButtonSignal(pane.titleButton, pane.titleClickSignalID) | |
| 232 | 227 | ||
| 233 | 228 | // Disconnect close button click signal | |
| 234 | - | if pane.closeButton != nil && pane.closeClickSignalID != 0 { | |
| 235 | - | gtkWidget := pane.closeButton.GtkWidget() | |
| 236 | - | if gtkWidget != nil { | |
| 237 | - | obj := gobject.ObjectNewFromInternalPtr(gtkWidget.GoPointer()) | |
| 238 | - | gobject.SignalHandlerDisconnect(obj, pane.closeClickSignalID) | |
| 239 | - | } | |
| 229 | + | disconnectButtonSignal(pane.closeButton, pane.closeClickSignalID) | |
| 230 | + | } | |
| 231 | + | ||
| 232 | + | // disconnectButtonSignal safely disconnects a signal from a button widget. | |
| 233 | + | // Returns silently if button is nil, signal ID is 0, or widget doesn't support GTK operations. | |
| 234 | + | func disconnectButtonSignal(btn ButtonWidget, signalID uint32) { | |
| 235 | + | if btn == nil || signalID == 0 { | |
| 236 | + | return | |
| 240 | 237 | } | |
| 238 | + | ||
| 239 | + | gtkWidget := btn.GtkWidget() | |
| 240 | + | if gtkWidget == nil { | |
| 241 | + | return | |
| 242 | + | } | |
| 243 | + | ||
| 244 | + | ptr := gtkWidget.GoPointer() | |
| 245 | + | if ptr == 0 { | |
| 246 | + | return | |
| 247 | + | } | |
| 248 | + | ||
| 249 | + | obj := gobject.ObjectNewFromInternalPtr(ptr) | |
| 250 | + | gobject.SignalHandlerDisconnect(obj, signalID) | |
| 241 | 251 | } | |
| 242 | 252 | ||
| 243 | 253 | // InsertPaneAfter inserts a new pane after the specified index position. | |
| @@ -216,28 +216,38 @@ func (sv *StackedView) connectTitleBarHandlers(tb titleBarComponents, paneID str | |||||
| 216 | 216 | ||||
| 217 | // disconnectPaneSignals disconnects signal handlers from a pane's buttons. | 217 | // disconnectPaneSignals disconnects signal handlers from a pane's buttons. | ||
| 218 | // This prevents memory leaks when panes are removed from the stack. | 218 | // This prevents memory leaks when panes are removed from the stack. | ||
| 219 | + | // Note: This is a no-op when using mock widgets in tests (GtkWidget returns nil). | |||
| 219 | func (sv *StackedView) disconnectPaneSignals(pane *stackedPane) { | 220 | func (sv *StackedView) disconnectPaneSignals(pane *stackedPane) { | ||
| 220 | if pane == nil { | 221 | if pane == nil { | ||
| 221 | return | 222 | return | ||
| 222 | } | 223 | } | ||
| 223 | 224 | ||||
| 224 | // Disconnect title button click signal | 225 | // Disconnect title button click signal | ||
| 225 | - | if pane.titleButton != nil && pane.titleClickSignalID != 0 { | 226 | + | disconnectButtonSignal(pane.titleButton, pane.titleClickSignalID) |
| 226 | - | gtkWidget := pane.titleButton.GtkWidget() | |||
| 227 | - | if gtkWidget != nil { | |||
| 228 | - | obj := gobject.ObjectNewFromInternalPtr(gtkWidget.GoPointer()) | |||
| 229 | - | gobject.SignalHandlerDisconnect(obj, pane.titleClickSignalID) | |||
| 230 | - | } | |||
| 231 | - | } | |||
| 232 | 227 | ||||
| 233 | // Disconnect close button click signal | 228 | // Disconnect close button click signal | ||
| 234 | - | if pane.closeButton != nil && pane.closeClickSignalID != 0 { | 229 | + | disconnectButtonSignal(pane.closeButton, pane.closeClickSignalID) |
| 235 | - | gtkWidget := pane.closeButton.GtkWidget() | 230 | + | } |
| 236 | - | if gtkWidget != nil { | 231 | + | |
| 237 | - | obj := gobject.ObjectNewFromInternalPtr(gtkWidget.GoPointer()) | 232 | + | // disconnectButtonSignal safely disconnects a signal from a button widget. |
| 238 | - | gobject.SignalHandlerDisconnect(obj, pane.closeClickSignalID) | 233 | + | // Returns silently if button is nil, signal ID is 0, or widget doesn't support GTK operations. |
| 239 | - | } | 234 | + | func disconnectButtonSignal(btn ButtonWidget, signalID uint32) { |
| 235 | + | if btn == nil || signalID == 0 { | |||
| 236 | + | return | |||
| 240 | } | 237 | } | ||
| 238 | + | ||||
| 239 | + | gtkWidget := btn.GtkWidget() | |||
| 240 | + | if gtkWidget == nil { | |||
| 241 | + | return | |||
| 242 | + | } | |||
| 243 | + | ||||
| 244 | + | ptr := gtkWidget.GoPointer() | |||
| 245 | + | if ptr == 0 { | |||
| 246 | + | return | |||
| 247 | + | } | |||
| 248 | + | ||||
| 249 | + | obj := gobject.ObjectNewFromInternalPtr(ptr) | |||
| 250 | + | gobject.SignalHandlerDisconnect(obj, signalID) | |||
| 241 | } | 251 | } | ||
| 242 | 252 | ||||
| 243 | // InsertPaneAfter inserts a new pane after the specified index position. | 253 | // InsertPaneAfter inserts a new pane after the specified index position. | ||
M internal/ui/layout/stacked_test.go +4 -0
| @@ -75,6 +75,10 @@ func setupPaneMocks(t *testing.T, mockFactory *mocks.MockWidgetFactory, mockBox | |||
| 75 | 75 | mockButton.EXPECT().ConnectClicked(mock.Anything).Return(uint32(1)).Once() | |
| 76 | 76 | mockCloseButton.EXPECT().ConnectClicked(mock.Anything).Return(uint32(2)).Once() | |
| 77 | 77 | ||
| 78 | + | // Signal disconnection calls GtkWidget() - return nil to skip actual GTK operations in tests | |
| 79 | + | mockButton.EXPECT().GtkWidget().Return(nil).Maybe() | |
| 80 | + | mockCloseButton.EXPECT().GtkWidget().Return(nil).Maybe() | |
| 81 | + | ||
| 78 | 82 | // Adding to main box | |
| 79 | 83 | mockBox.EXPECT().Append(mockButton).Once() | |
| 80 | 84 | mockBox.EXPECT().Append(mockContainer).Once() | |
| @@ -75,6 +75,10 @@ func setupPaneMocks(t *testing.T, mockFactory *mocks.MockWidgetFactory, mockBox | |||||
| 75 | mockButton.EXPECT().ConnectClicked(mock.Anything).Return(uint32(1)).Once() | 75 | mockButton.EXPECT().ConnectClicked(mock.Anything).Return(uint32(1)).Once() | ||
| 76 | mockCloseButton.EXPECT().ConnectClicked(mock.Anything).Return(uint32(2)).Once() | 76 | mockCloseButton.EXPECT().ConnectClicked(mock.Anything).Return(uint32(2)).Once() | ||
| 77 | 77 | ||||
| 78 | + | // Signal disconnection calls GtkWidget() - return nil to skip actual GTK operations in tests | |||
| 79 | + | mockButton.EXPECT().GtkWidget().Return(nil).Maybe() | |||
| 80 | + | mockCloseButton.EXPECT().GtkWidget().Return(nil).Maybe() | |||
| 81 | + | ||||
| 78 | // Adding to main box | 82 | // Adding to main box | ||
| 79 | mockBox.EXPECT().Append(mockButton).Once() | 83 | mockBox.EXPECT().Append(mockButton).Once() | ||
| 80 | mockBox.EXPECT().Append(mockContainer).Once() | 84 | mockBox.EXPECT().Append(mockContainer).Once() | ||