detached 25599f9

test(ui): cancel transient toaster timers

commit
25599f98dbb20a2df180f2fdd26dea878d94b25f
author
brice · · 2026-07-29 07:46 UTC
committer
bnema · · 2026-08-14 02:42 UTC
parent
7a23056
signature
unsigned
1 file · +12 -0
M internal/ui/app_browser_launch_test.go +12 -0
@@ -959,6 +959,10 @@ func stackedViewOnActivateIsNil(t *testing.T, sv *layout.StackedView) bool {
959959 return reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem().IsNil()
960960 }
961961
962+// newTestShellToaster builds a Toaster backed by testify mocks.
963+// Callers that Show() must Hide() (or otherwise cancel the dismiss timer)
964+// before the test returns — default Show arms a GLib TimeoutAdd that can
965+// fire during a later test's MainContext.Iteration and panic on dead mocks.
962966 func newTestShellToaster(t *testing.T) (*component.Toaster, *layoutmocks.MockBoxWidget, *layoutmocks.MockLabelWidget) {
963967 t.Helper()
964968
@@ -991,6 +995,9 @@ func TestApp_ShowFilterStatusUsesLastFocusedBrowserWindowToaster(t *testing.T) {
991995 toaster, box, label := newTestShellToaster(t)
992996 box.EXPECT().SetVisible(true).Once()
993997 label.EXPECT().SetText("Ad blocker loading").Once()
998+ // Cancel the auto-dismiss GLib timer before mock teardown so a later test
999+ // iterating the main context cannot fire hide() on a completed test's mocks.
1000+ box.EXPECT().SetVisible(false).Once()
9941001
9951002 bw := &browserWindow{id: "window-1", appToaster: toaster}
9961003 app := &App{
@@ -999,6 +1006,7 @@ func TestApp_ShowFilterStatusUsesLastFocusedBrowserWindowToaster(t *testing.T) {
9991006 }
10001007
10011008 app.showFilterStatus(ctx, port.FilterStatus{State: port.FilterStateLoading, Message: "Ad blocker loading"})
1009+ toaster.Hide()
10021010 }
10031011
10041012 func TestTestShellToasterCleanupCancelsAutoDismiss(t *testing.T) {
@@ -1024,6 +1032,9 @@ func TestApp_CheckConfigMigrationUsesLastFocusedBrowserWindowToaster(t *testing.
10241032 toaster, box, label := newTestShellToaster(t)
10251033 box.EXPECT().SetVisible(true).Once()
10261034 label.EXPECT().SetText("Config has 1 new settings. Run 'dumber config migrate'").Once()
1035+ // Cancel the auto-dismiss GLib timer before mock teardown so a later test
1036+ // iterating the main context cannot fire hide() on a completed test's mocks.
1037+ box.EXPECT().SetVisible(false).Once()
10271038
10281039 migrator := portmocks.NewMockConfigMigrator(t)
10291040 migrator.EXPECT().CheckMigration().Return(&port.MigrationResult{MissingKeys: []string{"update.notify_on_new_settings"}}, nil).Once()
@@ -1042,6 +1053,7 @@ func TestApp_CheckConfigMigrationUsesLastFocusedBrowserWindowToaster(t *testing.
10421053 }
10431054
10441055 app.checkConfigMigration(ctx)
1056+ toaster.Hide()
10451057 }
10461058
10471059 func TestApp_FinalizeActivationStartsBrowserLaunchRelayOnceAndClosesOnShutdown(t *testing.T) {
@@ -959,6 +959,10 @@ func stackedViewOnActivateIsNil(t *testing.T, sv *layout.StackedView) bool {
959 return reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem().IsNil()959 return reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem().IsNil()
960 }960 }
961 961
962+// newTestShellToaster builds a Toaster backed by testify mocks.
963+// Callers that Show() must Hide() (or otherwise cancel the dismiss timer)
964+// before the test returns — default Show arms a GLib TimeoutAdd that can
965+// fire during a later test's MainContext.Iteration and panic on dead mocks.
962 func newTestShellToaster(t *testing.T) (*component.Toaster, *layoutmocks.MockBoxWidget, *layoutmocks.MockLabelWidget) {966 func newTestShellToaster(t *testing.T) (*component.Toaster, *layoutmocks.MockBoxWidget, *layoutmocks.MockLabelWidget) {
963 t.Helper()967 t.Helper()
964 968
@@ -991,6 +995,9 @@ func TestApp_ShowFilterStatusUsesLastFocusedBrowserWindowToaster(t *testing.T) {
991 toaster, box, label := newTestShellToaster(t)995 toaster, box, label := newTestShellToaster(t)
992 box.EXPECT().SetVisible(true).Once()996 box.EXPECT().SetVisible(true).Once()
993 label.EXPECT().SetText("Ad blocker loading").Once()997 label.EXPECT().SetText("Ad blocker loading").Once()
998+ // Cancel the auto-dismiss GLib timer before mock teardown so a later test
999+ // iterating the main context cannot fire hide() on a completed test's mocks.
1000+ box.EXPECT().SetVisible(false).Once()
994 1001
995 bw := &browserWindow{id: "window-1", appToaster: toaster}1002 bw := &browserWindow{id: "window-1", appToaster: toaster}
996 app := &App{1003 app := &App{
@@ -999,6 +1006,7 @@ func TestApp_ShowFilterStatusUsesLastFocusedBrowserWindowToaster(t *testing.T) {
999 }1006 }
1000 1007
1001 app.showFilterStatus(ctx, port.FilterStatus{State: port.FilterStateLoading, Message: "Ad blocker loading"})1008 app.showFilterStatus(ctx, port.FilterStatus{State: port.FilterStateLoading, Message: "Ad blocker loading"})
1009+ toaster.Hide()
1002 }1010 }
1003 1011
1004 func TestTestShellToasterCleanupCancelsAutoDismiss(t *testing.T) {1012 func TestTestShellToasterCleanupCancelsAutoDismiss(t *testing.T) {
@@ -1024,6 +1032,9 @@ func TestApp_CheckConfigMigrationUsesLastFocusedBrowserWindowToaster(t *testing.
1024 toaster, box, label := newTestShellToaster(t)1032 toaster, box, label := newTestShellToaster(t)
1025 box.EXPECT().SetVisible(true).Once()1033 box.EXPECT().SetVisible(true).Once()
1026 label.EXPECT().SetText("Config has 1 new settings. Run 'dumber config migrate'").Once()1034 label.EXPECT().SetText("Config has 1 new settings. Run 'dumber config migrate'").Once()
1035+ // Cancel the auto-dismiss GLib timer before mock teardown so a later test
1036+ // iterating the main context cannot fire hide() on a completed test's mocks.
1037+ box.EXPECT().SetVisible(false).Once()
1027 1038
1028 migrator := portmocks.NewMockConfigMigrator(t)1039 migrator := portmocks.NewMockConfigMigrator(t)
1029 migrator.EXPECT().CheckMigration().Return(&port.MigrationResult{MissingKeys: []string{"update.notify_on_new_settings"}}, nil).Once()1040 migrator.EXPECT().CheckMigration().Return(&port.MigrationResult{MissingKeys: []string{"update.notify_on_new_settings"}}, nil).Once()
@@ -1042,6 +1053,7 @@ func TestApp_CheckConfigMigrationUsesLastFocusedBrowserWindowToaster(t *testing.
1042 }1053 }
1043 1054
1044 app.checkConfigMigration(ctx)1055 app.checkConfigMigration(ctx)
1056+ toaster.Hide()
1045 }1057 }
1046 1058
1047 func TestApp_FinalizeActivationStartsBrowserLaunchRelayOnceAndClosesOnShutdown(t *testing.T) {1059 func TestApp_FinalizeActivationStartsBrowserLaunchRelayOnceAndClosesOnShutdown(t *testing.T) {

Keyboard shortcuts

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

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

Branches 8

Tags 42

↑↓ moveenter openesc close