fix(history): tighten sidebar regression coverage
- commit
- 5caec5d7b78cb0dd5e45badc2dc9e9d3cd43c216
- author
- brice · · 2026-06-13 07:02 UTC
- parent
- b5b4cef
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +55 -18
M internal/ui/browser_window_test.go +11 -3
| @@ -1225,6 +1225,7 @@ func TestApp_HistorySidebarToggleHandler_NilFocusedWindowIsNoOp(t *testing.T) { | |||
| 1225 | 1225 | // TestHistorySidebarConfig_OnCloseHidesSidebar verifies that the OnClose | |
| 1226 | 1226 | // callback hides the sidebar for the owning browser window. | |
| 1227 | 1227 | func TestHistorySidebarConfig_OnCloseHidesSidebar(t *testing.T) { | |
| 1228 | + | ctx := context.Background() | |
| 1228 | 1229 | tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(entity.PaneID("pane-1"))) | |
| 1229 | 1230 | bwTabs := entity.NewTabList() | |
| 1230 | 1231 | bwTabs.Add(tab) | |
| @@ -1238,10 +1239,17 @@ func TestHistorySidebarConfig_OnCloseHidesSidebar(t *testing.T) { | |||
| 1238 | 1239 | sidebarVisible: true, | |
| 1239 | 1240 | } | |
| 1240 | 1241 | ||
| 1241 | - | // Simulate the hide step used by OnClose's hideAndRestoreFocus closure. | |
| 1242 | - | bw.hideHistorySidebar() | |
| 1243 | - | assert.False(t, bw.sidebarVisible, "sidebar must be hidden by hideAndRestoreFocus") | |
| 1242 | + | app := &App{ | |
| 1243 | + | tabs: entity.NewTabList(), | |
| 1244 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, | |
| 1245 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{tab.ID: {}}, | |
| 1246 | + | } | |
| 1247 | + | app.tabs.Add(tab) | |
| 1248 | + | ||
| 1249 | + | cfg := app.buildHistorySidebarConfig(ctx, bw) | |
| 1250 | + | cfg.OnClose() | |
| 1244 | 1251 | ||
| 1252 | + | assert.False(t, bw.sidebarVisible, "sidebar must be hidden by OnClose") | |
| 1245 | 1253 | } | |
| 1246 | 1254 | ||
| 1247 | 1255 | // ============================================================================= | |
| @@ -1225,6 +1225,7 @@ func TestApp_HistorySidebarToggleHandler_NilFocusedWindowIsNoOp(t *testing.T) { | |||||
| 1225 | // TestHistorySidebarConfig_OnCloseHidesSidebar verifies that the OnClose | 1225 | // TestHistorySidebarConfig_OnCloseHidesSidebar verifies that the OnClose | ||
| 1226 | // callback hides the sidebar for the owning browser window. | 1226 | // callback hides the sidebar for the owning browser window. | ||
| 1227 | func TestHistorySidebarConfig_OnCloseHidesSidebar(t *testing.T) { | 1227 | func TestHistorySidebarConfig_OnCloseHidesSidebar(t *testing.T) { | ||
| 1228 | + | ctx := context.Background() | |||
| 1228 | tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(entity.PaneID("pane-1"))) | 1229 | tab := entity.NewTab(entity.TabID("tab-1"), entity.WorkspaceID("ws-1"), entity.NewPane(entity.PaneID("pane-1"))) | ||
| 1229 | bwTabs := entity.NewTabList() | 1230 | bwTabs := entity.NewTabList() | ||
| 1230 | bwTabs.Add(tab) | 1231 | bwTabs.Add(tab) | ||
| @@ -1238,10 +1239,17 @@ func TestHistorySidebarConfig_OnCloseHidesSidebar(t *testing.T) { | |||||
| 1238 | sidebarVisible: true, | 1239 | sidebarVisible: true, | ||
| 1239 | } | 1240 | } | ||
| 1240 | 1241 | ||||
| 1241 | - | // Simulate the hide step used by OnClose's hideAndRestoreFocus closure. | 1242 | + | app := &App{ |
| 1242 | - | bw.hideHistorySidebar() | 1243 | + | tabs: entity.NewTabList(), |
| 1243 | - | assert.False(t, bw.sidebarVisible, "sidebar must be hidden by hideAndRestoreFocus") | 1244 | + | browserWindows: map[string]*browserWindow{bw.id: bw}, |
| 1245 | + | workspaceViews: map[entity.TabID]*component.WorkspaceView{tab.ID: {}}, | |||
| 1246 | + | } | |||
| 1247 | + | app.tabs.Add(tab) | |||
| 1248 | + | ||||
| 1249 | + | cfg := app.buildHistorySidebarConfig(ctx, bw) | |||
| 1250 | + | cfg.OnClose() | |||
| 1244 | 1251 | ||||
| 1252 | + | assert.False(t, bw.sidebarVisible, "sidebar must be hidden by OnClose") | |||
| 1245 | } | 1253 | } | ||
| 1246 | 1254 | ||||
| 1247 | // ============================================================================= | 1255 | // ============================================================================= | ||
M internal/ui/component/history_sidebar.go +16 -3
| @@ -95,6 +95,10 @@ type HistorySidebar struct { | |||
| 95 | 95 | // Retained callbacks | |
| 96 | 96 | retainedCallbacks []interface{} | |
| 97 | 97 | ||
| 98 | + | // idleScheduler dispatches work onto the GTK main thread. | |
| 99 | + | // Tests may override it to exercise scheduled callbacks deterministically. | |
| 100 | + | idleScheduler func(glib.SourceFunc) | |
| 101 | + | ||
| 98 | 102 | // Context | |
| 99 | 103 | ctx context.Context | |
| 100 | 104 | cancel context.CancelFunc | |
| @@ -804,7 +808,7 @@ func (hs *HistorySidebar) doFTSearch(query string, gen uint64) { | |||
| 804 | 808 | } | |
| 805 | 809 | return false | |
| 806 | 810 | }) | |
| 807 | - | glib.IdleAdd(&cb, 0) | |
| 811 | + | hs.scheduleIdle(cb) | |
| 808 | 812 | }() | |
| 809 | 813 | } | |
| 810 | 814 | ||
| @@ -826,6 +830,14 @@ func (hs *HistorySidebar) applySearchResults(entries []*entity.HistoryEntry, gen | |||
| 826 | 830 | return true | |
| 827 | 831 | } | |
| 828 | 832 | ||
| 833 | + | func (hs *HistorySidebar) scheduleIdle(cb glib.SourceFunc) { | |
| 834 | + | if hs != nil && hs.idleScheduler != nil { | |
| 835 | + | hs.idleScheduler(cb) | |
| 836 | + | return | |
| 837 | + | } | |
| 838 | + | glib.IdleAdd(&cb, 0) | |
| 839 | + | } | |
| 840 | + | ||
| 829 | 841 | // scheduleClearList clears the list box on the GTK main thread. | |
| 830 | 842 | func (hs *HistorySidebar) scheduleClearList() { | |
| 831 | 843 | cb := glib.SourceFunc(func(uintptr) bool { | |
| @@ -839,7 +851,7 @@ func (hs *HistorySidebar) scheduleClearList() { | |||
| 839 | 851 | listBox.RemoveAll() | |
| 840 | 852 | return false | |
| 841 | 853 | }) | |
| 842 | - | glib.IdleAdd(&cb, 0) | |
| 854 | + | hs.scheduleIdle(cb) | |
| 843 | 855 | } | |
| 844 | 856 | ||
| 845 | 857 | // scheduleRebuild schedules a list rebuild on the GTK main thread. | |
| @@ -848,7 +860,7 @@ func (hs *HistorySidebar) scheduleRebuild() { | |||
| 848 | 860 | hs.rebuildList() | |
| 849 | 861 | return false | |
| 850 | 862 | }) | |
| 851 | - | glib.IdleAdd(&cb, 0) | |
| 863 | + | hs.scheduleIdle(cb) | |
| 852 | 864 | } | |
| 853 | 865 | ||
| 854 | 866 | // ============================================================================= | |
| @@ -1240,6 +1252,7 @@ func (hs *HistorySidebar) handleDeleteKey() bool { | |||
| 1240 | 1252 | hs.prevSelectedURL = nextSelectedURL | |
| 1241 | 1253 | hs.searchGen++ | |
| 1242 | 1254 | hs.removeFromAllEntries(url, entryID) | |
| 1255 | + | hs.totalLoaded = len(hs.allEntries) | |
| 1243 | 1256 | hs.removeFromSearchResults(entryID) | |
| 1244 | 1257 | hs.rebuildLocalGroups() | |
| 1245 | 1258 | hs.mu.Unlock() | |
| @@ -95,6 +95,10 @@ type HistorySidebar struct { | |||||
| 95 | // Retained callbacks | 95 | // Retained callbacks | ||
| 96 | retainedCallbacks []interface{} | 96 | retainedCallbacks []interface{} | ||
| 97 | 97 | ||||
| 98 | + | // idleScheduler dispatches work onto the GTK main thread. | |||
| 99 | + | // Tests may override it to exercise scheduled callbacks deterministically. | |||
| 100 | + | idleScheduler func(glib.SourceFunc) | |||
| 101 | + | ||||
| 98 | // Context | 102 | // Context | ||
| 99 | ctx context.Context | 103 | ctx context.Context | ||
| 100 | cancel context.CancelFunc | 104 | cancel context.CancelFunc | ||
| @@ -804,7 +808,7 @@ func (hs *HistorySidebar) doFTSearch(query string, gen uint64) { | |||||
| 804 | } | 808 | } | ||
| 805 | return false | 809 | return false | ||
| 806 | }) | 810 | }) | ||
| 807 | - | glib.IdleAdd(&cb, 0) | 811 | + | hs.scheduleIdle(cb) |
| 808 | }() | 812 | }() | ||
| 809 | } | 813 | } | ||
| 810 | 814 | ||||
| @@ -826,6 +830,14 @@ func (hs *HistorySidebar) applySearchResults(entries []*entity.HistoryEntry, gen | |||||
| 826 | return true | 830 | return true | ||
| 827 | } | 831 | } | ||
| 828 | 832 | ||||
| 833 | + | func (hs *HistorySidebar) scheduleIdle(cb glib.SourceFunc) { | |||
| 834 | + | if hs != nil && hs.idleScheduler != nil { | |||
| 835 | + | hs.idleScheduler(cb) | |||
| 836 | + | return | |||
| 837 | + | } | |||
| 838 | + | glib.IdleAdd(&cb, 0) | |||
| 839 | + | } | |||
| 840 | + | ||||
| 829 | // scheduleClearList clears the list box on the GTK main thread. | 841 | // scheduleClearList clears the list box on the GTK main thread. | ||
| 830 | func (hs *HistorySidebar) scheduleClearList() { | 842 | func (hs *HistorySidebar) scheduleClearList() { | ||
| 831 | cb := glib.SourceFunc(func(uintptr) bool { | 843 | cb := glib.SourceFunc(func(uintptr) bool { | ||
| @@ -839,7 +851,7 @@ func (hs *HistorySidebar) scheduleClearList() { | |||||
| 839 | listBox.RemoveAll() | 851 | listBox.RemoveAll() | ||
| 840 | return false | 852 | return false | ||
| 841 | }) | 853 | }) | ||
| 842 | - | glib.IdleAdd(&cb, 0) | 854 | + | hs.scheduleIdle(cb) |
| 843 | } | 855 | } | ||
| 844 | 856 | ||||
| 845 | // scheduleRebuild schedules a list rebuild on the GTK main thread. | 857 | // scheduleRebuild schedules a list rebuild on the GTK main thread. | ||
| @@ -848,7 +860,7 @@ func (hs *HistorySidebar) scheduleRebuild() { | |||||
| 848 | hs.rebuildList() | 860 | hs.rebuildList() | ||
| 849 | return false | 861 | return false | ||
| 850 | }) | 862 | }) | ||
| 851 | - | glib.IdleAdd(&cb, 0) | 863 | + | hs.scheduleIdle(cb) |
| 852 | } | 864 | } | ||
| 853 | 865 | ||||
| 854 | // ============================================================================= | 866 | // ============================================================================= | ||
| @@ -1240,6 +1252,7 @@ func (hs *HistorySidebar) handleDeleteKey() bool { | |||||
| 1240 | hs.prevSelectedURL = nextSelectedURL | 1252 | hs.prevSelectedURL = nextSelectedURL | ||
| 1241 | hs.searchGen++ | 1253 | hs.searchGen++ | ||
| 1242 | hs.removeFromAllEntries(url, entryID) | 1254 | hs.removeFromAllEntries(url, entryID) | ||
| 1255 | + | hs.totalLoaded = len(hs.allEntries) | |||
| 1243 | hs.removeFromSearchResults(entryID) | 1256 | hs.removeFromSearchResults(entryID) | ||
| 1244 | hs.rebuildLocalGroups() | 1257 | hs.rebuildLocalGroups() | ||
| 1245 | hs.mu.Unlock() | 1258 | hs.mu.Unlock() | ||
M internal/ui/component/history_sidebar_search_test.go +28 -12
| @@ -9,6 +9,7 @@ import ( | |||
| 9 | 9 | ||
| 10 | 10 | "github.com/bnema/dumber/internal/application/dto" | |
| 11 | 11 | "github.com/bnema/dumber/internal/domain/entity" | |
| 12 | + | "github.com/bnema/puregotk/v4/glib" | |
| 12 | 13 | "github.com/rs/zerolog" | |
| 13 | 14 | "github.com/stretchr/testify/assert" | |
| 14 | 15 | "github.com/stretchr/testify/require" | |
| @@ -151,6 +152,7 @@ func (f *fakeHistorySidebarHistory) Delete(ctx context.Context, id int64) error | |||
| 151 | 152 | ||
| 152 | 153 | func TestApplySearchResults_StaleGenerationDropsResultsAfterSearch(t *testing.T) { | |
| 153 | 154 | searchCalled := make(chan struct{}, 1) | |
| 155 | + | idleCalled := make(chan glib.SourceFunc, 1) | |
| 154 | 156 | ||
| 155 | 157 | history := &fakeHistorySidebarHistory{ | |
| 156 | 158 | searchFn: func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |
| @@ -165,20 +167,29 @@ func TestApplySearchResults_StaleGenerationDropsResultsAfterSearch(t *testing.T) | |||
| 165 | 167 | hs.ctx = context.Background() | |
| 166 | 168 | hs.historyUC = history | |
| 167 | 169 | hs.searchGen = 1 | |
| 170 | + | hs.idleScheduler = func(cb glib.SourceFunc) { | |
| 171 | + | idleCalled <- cb | |
| 172 | + | } | |
| 168 | 173 | ||
| 169 | - | // Start search with gen=1 | |
| 174 | + | // Start search with gen=1. | |
| 170 | 175 | hs.doFTSearch("test", 1) | |
| 171 | - | <-searchCalled // Wait for the goroutine to pick up the search | |
| 176 | + | select { | |
| 177 | + | case <-searchCalled: | |
| 178 | + | case <-time.After(time.Second): | |
| 179 | + | t.Fatal("timed out waiting for search use case to be invoked") | |
| 180 | + | } | |
| 172 | 181 | ||
| 173 | - | // Advance gen before the UI callback would apply results. | |
| 182 | + | // Advance gen before the queued UI callback applies results. | |
| 174 | 183 | hs.mu.Lock() | |
| 175 | 184 | hs.searchGen = 2 | |
| 176 | 185 | hs.mu.Unlock() | |
| 177 | 186 | ||
| 178 | - | applied := hs.applySearchResults([]*entity.HistoryEntry{{ | |
| 179 | - | ID: 1, URL: "https://result.com", Title: "Result", LastVisited: time.Now(), | |
| 180 | - | }}, 1, nil) | |
| 181 | - | assert.False(t, applied, "stale search results must be dropped") | |
| 187 | + | select { | |
| 188 | + | case cb := <-idleCalled: | |
| 189 | + | cb(0) | |
| 190 | + | case <-time.After(time.Second): | |
| 191 | + | t.Fatal("timed out waiting for scheduled idle callback") | |
| 192 | + | } | |
| 182 | 193 | ||
| 183 | 194 | hs.mu.RLock() | |
| 184 | 195 | defer hs.mu.RUnlock() | |
| @@ -189,6 +200,7 @@ func TestApplySearchResults_StaleGenerationDropsResultsAfterSearch(t *testing.T) | |||
| 189 | 200 | ||
| 190 | 201 | func TestApplySearchResults_CurrentGenerationAppliedAfterSearch(t *testing.T) { | |
| 191 | 202 | searchCalled := make(chan struct{}, 1) | |
| 203 | + | idleCalled := make(chan glib.SourceFunc, 1) | |
| 192 | 204 | ||
| 193 | 205 | history := &fakeHistorySidebarHistory{ | |
| 194 | 206 | searchFn: func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | |
| @@ -203,6 +215,9 @@ func TestApplySearchResults_CurrentGenerationAppliedAfterSearch(t *testing.T) { | |||
| 203 | 215 | hs.ctx = context.Background() | |
| 204 | 216 | hs.historyUC = history | |
| 205 | 217 | hs.searchGen = 1 | |
| 218 | + | hs.idleScheduler = func(cb glib.SourceFunc) { | |
| 219 | + | idleCalled <- cb | |
| 220 | + | } | |
| 206 | 221 | ||
| 207 | 222 | // Start the search and wait for the use case to be invoked. | |
| 208 | 223 | hs.doFTSearch("live", 1) | |
| @@ -212,11 +227,12 @@ func TestApplySearchResults_CurrentGenerationAppliedAfterSearch(t *testing.T) { | |||
| 212 | 227 | t.Fatal("timed out waiting for search use case to be invoked") | |
| 213 | 228 | } | |
| 214 | 229 | ||
| 215 | - | // glib.IdleAdd is a no-op without GTK, so apply the callback effect directly. | |
| 216 | - | applied := hs.applySearchResults([]*entity.HistoryEntry{ | |
| 217 | - | {ID: 1, URL: "https://live.com", Title: "Live", LastVisited: time.Now()}, | |
| 218 | - | }, 1, nil) | |
| 219 | - | require.True(t, applied) | |
| 230 | + | select { | |
| 231 | + | case cb := <-idleCalled: | |
| 232 | + | cb(0) | |
| 233 | + | case <-time.After(time.Second): | |
| 234 | + | t.Fatal("timed out waiting for scheduled idle callback") | |
| 235 | + | } | |
| 220 | 236 | ||
| 221 | 237 | hs.mu.RLock() | |
| 222 | 238 | assert.NotNil(t, hs.searchResults) | |
| @@ -9,6 +9,7 @@ import ( | |||||
| 9 | 9 | ||||
| 10 | "github.com/bnema/dumber/internal/application/dto" | 10 | "github.com/bnema/dumber/internal/application/dto" | ||
| 11 | "github.com/bnema/dumber/internal/domain/entity" | 11 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 12 | + | "github.com/bnema/puregotk/v4/glib" | |||
| 12 | "github.com/rs/zerolog" | 13 | "github.com/rs/zerolog" | ||
| 13 | "github.com/stretchr/testify/assert" | 14 | "github.com/stretchr/testify/assert" | ||
| 14 | "github.com/stretchr/testify/require" | 15 | "github.com/stretchr/testify/require" | ||
| @@ -151,6 +152,7 @@ func (f *fakeHistorySidebarHistory) Delete(ctx context.Context, id int64) error | |||||
| 151 | 152 | ||||
| 152 | func TestApplySearchResults_StaleGenerationDropsResultsAfterSearch(t *testing.T) { | 153 | func TestApplySearchResults_StaleGenerationDropsResultsAfterSearch(t *testing.T) { | ||
| 153 | searchCalled := make(chan struct{}, 1) | 154 | searchCalled := make(chan struct{}, 1) | ||
| 155 | + | idleCalled := make(chan glib.SourceFunc, 1) | |||
| 154 | 156 | ||||
| 155 | history := &fakeHistorySidebarHistory{ | 157 | history := &fakeHistorySidebarHistory{ | ||
| 156 | searchFn: func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | 158 | searchFn: func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | ||
| @@ -165,20 +167,29 @@ func TestApplySearchResults_StaleGenerationDropsResultsAfterSearch(t *testing.T) | |||||
| 165 | hs.ctx = context.Background() | 167 | hs.ctx = context.Background() | ||
| 166 | hs.historyUC = history | 168 | hs.historyUC = history | ||
| 167 | hs.searchGen = 1 | 169 | hs.searchGen = 1 | ||
| 170 | + | hs.idleScheduler = func(cb glib.SourceFunc) { | |||
| 171 | + | idleCalled <- cb | |||
| 172 | + | } | |||
| 168 | 173 | ||||
| 169 | - | // Start search with gen=1 | 174 | + | // Start search with gen=1. |
| 170 | hs.doFTSearch("test", 1) | 175 | hs.doFTSearch("test", 1) | ||
| 171 | - | <-searchCalled // Wait for the goroutine to pick up the search | 176 | + | select { |
| 177 | + | case <-searchCalled: | |||
| 178 | + | case <-time.After(time.Second): | |||
| 179 | + | t.Fatal("timed out waiting for search use case to be invoked") | |||
| 180 | + | } | |||
| 172 | 181 | ||||
| 173 | - | // Advance gen before the UI callback would apply results. | 182 | + | // Advance gen before the queued UI callback applies results. |
| 174 | hs.mu.Lock() | 183 | hs.mu.Lock() | ||
| 175 | hs.searchGen = 2 | 184 | hs.searchGen = 2 | ||
| 176 | hs.mu.Unlock() | 185 | hs.mu.Unlock() | ||
| 177 | 186 | ||||
| 178 | - | applied := hs.applySearchResults([]*entity.HistoryEntry{{ | 187 | + | select { |
| 179 | - | ID: 1, URL: "https://result.com", Title: "Result", LastVisited: time.Now(), | 188 | + | case cb := <-idleCalled: |
| 180 | - | }}, 1, nil) | 189 | + | cb(0) |
| 181 | - | assert.False(t, applied, "stale search results must be dropped") | 190 | + | case <-time.After(time.Second): |
| 191 | + | t.Fatal("timed out waiting for scheduled idle callback") | |||
| 192 | + | } | |||
| 182 | 193 | ||||
| 183 | hs.mu.RLock() | 194 | hs.mu.RLock() | ||
| 184 | defer hs.mu.RUnlock() | 195 | defer hs.mu.RUnlock() | ||
| @@ -189,6 +200,7 @@ func TestApplySearchResults_StaleGenerationDropsResultsAfterSearch(t *testing.T) | |||||
| 189 | 200 | ||||
| 190 | func TestApplySearchResults_CurrentGenerationAppliedAfterSearch(t *testing.T) { | 201 | func TestApplySearchResults_CurrentGenerationAppliedAfterSearch(t *testing.T) { | ||
| 191 | searchCalled := make(chan struct{}, 1) | 202 | searchCalled := make(chan struct{}, 1) | ||
| 203 | + | idleCalled := make(chan glib.SourceFunc, 1) | |||
| 192 | 204 | ||||
| 193 | history := &fakeHistorySidebarHistory{ | 205 | history := &fakeHistorySidebarHistory{ | ||
| 194 | searchFn: func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | 206 | searchFn: func(context.Context, dto.HistorySearchInput) (*dto.HistorySearchOutput, error) { | ||
| @@ -203,6 +215,9 @@ func TestApplySearchResults_CurrentGenerationAppliedAfterSearch(t *testing.T) { | |||||
| 203 | hs.ctx = context.Background() | 215 | hs.ctx = context.Background() | ||
| 204 | hs.historyUC = history | 216 | hs.historyUC = history | ||
| 205 | hs.searchGen = 1 | 217 | hs.searchGen = 1 | ||
| 218 | + | hs.idleScheduler = func(cb glib.SourceFunc) { | |||
| 219 | + | idleCalled <- cb | |||
| 220 | + | } | |||
| 206 | 221 | ||||
| 207 | // Start the search and wait for the use case to be invoked. | 222 | // Start the search and wait for the use case to be invoked. | ||
| 208 | hs.doFTSearch("live", 1) | 223 | hs.doFTSearch("live", 1) | ||
| @@ -212,11 +227,12 @@ func TestApplySearchResults_CurrentGenerationAppliedAfterSearch(t *testing.T) { | |||||
| 212 | t.Fatal("timed out waiting for search use case to be invoked") | 227 | t.Fatal("timed out waiting for search use case to be invoked") | ||
| 213 | } | 228 | } | ||
| 214 | 229 | ||||
| 215 | - | // glib.IdleAdd is a no-op without GTK, so apply the callback effect directly. | 230 | + | select { |
| 216 | - | applied := hs.applySearchResults([]*entity.HistoryEntry{ | 231 | + | case cb := <-idleCalled: |
| 217 | - | {ID: 1, URL: "https://live.com", Title: "Live", LastVisited: time.Now()}, | 232 | + | cb(0) |
| 218 | - | }, 1, nil) | 233 | + | case <-time.After(time.Second): |
| 219 | - | require.True(t, applied) | 234 | + | t.Fatal("timed out waiting for scheduled idle callback") |
| 235 | + | } | |||
| 220 | 236 | ||||
| 221 | hs.mu.RLock() | 237 | hs.mu.RLock() | ||
| 222 | assert.NotNil(t, hs.searchResults) | 238 | assert.NotNil(t, hs.searchResults) | ||