fix(favorites): address sidebar review findings
- commit
- 91c19f7fcac581fe5fa80d5c4f7662efd258e5fe
- author
- brice · · 2026-06-28 16:39 UTC
- parent
- a2631c0
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +125 -25
M internal/ui/component/favorites_sidebar.go +12 -0
| @@ -260,8 +260,20 @@ func (fs *FavoritesSidebar) startLoad() { | |||
| 260 | 260 | gen := fs.loadGen | |
| 261 | 261 | uc := fs.favoritesUC | |
| 262 | 262 | ctx := fs.ctx | |
| 263 | + | visible := fs.visible | |
| 263 | 264 | fs.notice = "Loading favorites..." | |
| 264 | 265 | fs.mu.Unlock() | |
| 266 | + | if visible { | |
| 267 | + | fs.scheduleIdle(glib.SourceFunc(func(uintptr) bool { | |
| 268 | + | fs.mu.RLock() | |
| 269 | + | destroyed := fs.destroyed | |
| 270 | + | fs.mu.RUnlock() | |
| 271 | + | if !destroyed { | |
| 272 | + | fs.rebuildList() | |
| 273 | + | } | |
| 274 | + | return false | |
| 275 | + | })) | |
| 276 | + | } | |
| 265 | 277 | ||
| 266 | 278 | if uc == nil || ctx == nil { | |
| 267 | 279 | fs.applyLoadedData(nil, nil, gen, nil) | |
| @@ -260,8 +260,20 @@ func (fs *FavoritesSidebar) startLoad() { | |||||
| 260 | gen := fs.loadGen | 260 | gen := fs.loadGen | ||
| 261 | uc := fs.favoritesUC | 261 | uc := fs.favoritesUC | ||
| 262 | ctx := fs.ctx | 262 | ctx := fs.ctx | ||
| 263 | + | visible := fs.visible | |||
| 263 | fs.notice = "Loading favorites..." | 264 | fs.notice = "Loading favorites..." | ||
| 264 | fs.mu.Unlock() | 265 | fs.mu.Unlock() | ||
| 266 | + | if visible { | |||
| 267 | + | fs.scheduleIdle(glib.SourceFunc(func(uintptr) bool { | |||
| 268 | + | fs.mu.RLock() | |||
| 269 | + | destroyed := fs.destroyed | |||
| 270 | + | fs.mu.RUnlock() | |||
| 271 | + | if !destroyed { | |||
| 272 | + | fs.rebuildList() | |||
| 273 | + | } | |||
| 274 | + | return false | |||
| 275 | + | })) | |||
| 276 | + | } | |||
| 265 | 277 | ||||
| 266 | if uc == nil || ctx == nil { | 278 | if uc == nil || ctx == nil { | ||
| 267 | fs.applyLoadedData(nil, nil, gen, nil) | 279 | fs.applyLoadedData(nil, nil, gen, nil) | ||
M internal/ui/component/favorites_sidebar_forms.go +22 -2
| @@ -135,7 +135,21 @@ func (fs *FavoritesSidebar) renderForm(fav *entity.Favorite) { | |||
| 135 | 135 | ||
| 136 | 136 | func (fs *FavoritesSidebar) focusForm() { | |
| 137 | 137 | fs.scheduleIdle(glib.SourceFunc(func(uintptr) bool { | |
| 138 | - | if fs != nil && fs.formURLEntry != nil { | |
| 138 | + | if fs == nil { | |
| 139 | + | return false | |
| 140 | + | } | |
| 141 | + | fs.mu.RLock() | |
| 142 | + | mode := fs.mode | |
| 143 | + | destroyed := fs.destroyed | |
| 144 | + | fs.mu.RUnlock() | |
| 145 | + | if destroyed { | |
| 146 | + | return false | |
| 147 | + | } | |
| 148 | + | if mode == favoritesSidebarModeEdit && fs.formTitleEntry != nil { | |
| 149 | + | fs.formTitleEntry.GrabFocus() | |
| 150 | + | return false | |
| 151 | + | } | |
| 152 | + | if fs.formURLEntry != nil { | |
| 139 | 153 | fs.formURLEntry.GrabFocus() | |
| 140 | 154 | } | |
| 141 | 155 | return false | |
| @@ -197,7 +211,9 @@ func (fs *FavoritesSidebar) submitForm() bool { | |||
| 197 | 211 | } | |
| 198 | 212 | if key != nil && fav != nil { | |
| 199 | 213 | if err := uc.SetShortcut(ctx, fav.ID, key); err != nil { | |
| 200 | - | fs.setNotice(err.Error()) | |
| 214 | + | fs.cancelManagement() | |
| 215 | + | fs.startLoad() | |
| 216 | + | fs.setNotice("Added favorite, but failed to set shortcut: " + err.Error()) | |
| 201 | 217 | return true | |
| 202 | 218 | } | |
| 203 | 219 | } | |
| @@ -333,9 +349,13 @@ func (fs *FavoritesSidebar) confirmDeleteFavorite() bool { | |||
| 333 | 349 | return fs.requestDeleteConfirmation() | |
| 334 | 350 | } | |
| 335 | 351 | fs.mu.RLock() | |
| 352 | + | confirmed := fs.confirmDelete | |
| 336 | 353 | uc := fs.favoritesUC | |
| 337 | 354 | ctx := fs.ctx | |
| 338 | 355 | fs.mu.RUnlock() | |
| 356 | + | if !confirmed { | |
| 357 | + | return fs.requestDeleteConfirmation() | |
| 358 | + | } | |
| 339 | 359 | if uc == nil { | |
| 340 | 360 | return true | |
| 341 | 361 | } | |
| @@ -135,7 +135,21 @@ func (fs *FavoritesSidebar) renderForm(fav *entity.Favorite) { | |||||
| 135 | 135 | ||||
| 136 | func (fs *FavoritesSidebar) focusForm() { | 136 | func (fs *FavoritesSidebar) focusForm() { | ||
| 137 | fs.scheduleIdle(glib.SourceFunc(func(uintptr) bool { | 137 | fs.scheduleIdle(glib.SourceFunc(func(uintptr) bool { | ||
| 138 | - | if fs != nil && fs.formURLEntry != nil { | 138 | + | if fs == nil { |
| 139 | + | return false | |||
| 140 | + | } | |||
| 141 | + | fs.mu.RLock() | |||
| 142 | + | mode := fs.mode | |||
| 143 | + | destroyed := fs.destroyed | |||
| 144 | + | fs.mu.RUnlock() | |||
| 145 | + | if destroyed { | |||
| 146 | + | return false | |||
| 147 | + | } | |||
| 148 | + | if mode == favoritesSidebarModeEdit && fs.formTitleEntry != nil { | |||
| 149 | + | fs.formTitleEntry.GrabFocus() | |||
| 150 | + | return false | |||
| 151 | + | } | |||
| 152 | + | if fs.formURLEntry != nil { | |||
| 139 | fs.formURLEntry.GrabFocus() | 153 | fs.formURLEntry.GrabFocus() | ||
| 140 | } | 154 | } | ||
| 141 | return false | 155 | return false | ||
| @@ -197,7 +211,9 @@ func (fs *FavoritesSidebar) submitForm() bool { | |||||
| 197 | } | 211 | } | ||
| 198 | if key != nil && fav != nil { | 212 | if key != nil && fav != nil { | ||
| 199 | if err := uc.SetShortcut(ctx, fav.ID, key); err != nil { | 213 | if err := uc.SetShortcut(ctx, fav.ID, key); err != nil { | ||
| 200 | - | fs.setNotice(err.Error()) | 214 | + | fs.cancelManagement() |
| 215 | + | fs.startLoad() | |||
| 216 | + | fs.setNotice("Added favorite, but failed to set shortcut: " + err.Error()) | |||
| 201 | return true | 217 | return true | ||
| 202 | } | 218 | } | ||
| 203 | } | 219 | } | ||
| @@ -333,9 +349,13 @@ func (fs *FavoritesSidebar) confirmDeleteFavorite() bool { | |||||
| 333 | return fs.requestDeleteConfirmation() | 349 | return fs.requestDeleteConfirmation() | ||
| 334 | } | 350 | } | ||
| 335 | fs.mu.RLock() | 351 | fs.mu.RLock() | ||
| 352 | + | confirmed := fs.confirmDelete | |||
| 336 | uc := fs.favoritesUC | 353 | uc := fs.favoritesUC | ||
| 337 | ctx := fs.ctx | 354 | ctx := fs.ctx | ||
| 338 | fs.mu.RUnlock() | 355 | fs.mu.RUnlock() | ||
| 356 | + | if !confirmed { | |||
| 357 | + | return fs.requestDeleteConfirmation() | |||
| 358 | + | } | |||
| 339 | if uc == nil { | 359 | if uc == nil { | ||
| 340 | return true | 360 | return true | ||
| 341 | } | 361 | } | ||
M internal/ui/component/favorites_sidebar_keyboard.go +24 -4
| @@ -28,6 +28,7 @@ func (fs *FavoritesSidebar) setupKeyboardNavigation() { | |||
| 28 | 28 | onClose := fs.onClose | |
| 29 | 29 | fs.mu.RUnlock() | |
| 30 | 30 | ||
| 31 | + | textEditing := fs.inTextEditContext() | |
| 31 | 32 | switch keyval { | |
| 32 | 33 | case uint(gdk.KEY_Escape): | |
| 33 | 34 | if fs.cancelManagement() { | |
| @@ -47,31 +48,49 @@ func (fs *FavoritesSidebar) setupKeyboardNavigation() { | |||
| 47 | 48 | fs.cycleFocusZone(state&gdk.ShiftMaskValue != 0) | |
| 48 | 49 | return true | |
| 49 | 50 | case uint(gdk.KEY_Page_Up): | |
| 51 | + | if textEditing { | |
| 52 | + | return false | |
| 53 | + | } | |
| 50 | 54 | fs.selectAdjacentRow(-5) | |
| 51 | 55 | return true | |
| 52 | 56 | case uint(gdk.KEY_Page_Down): | |
| 57 | + | if textEditing { | |
| 58 | + | return false | |
| 59 | + | } | |
| 53 | 60 | fs.selectAdjacentRow(5) | |
| 54 | 61 | return true | |
| 55 | 62 | case uint(gdk.KEY_Home): | |
| 63 | + | if textEditing { | |
| 64 | + | return false | |
| 65 | + | } | |
| 56 | 66 | fs.mu.RLock() | |
| 57 | 67 | index := firstSelectableIndex(fs.displayRows) | |
| 58 | 68 | fs.mu.RUnlock() | |
| 59 | 69 | fs.selectIndex(index) | |
| 60 | 70 | return true | |
| 61 | 71 | case uint(gdk.KEY_End): | |
| 72 | + | if textEditing { | |
| 73 | + | return false | |
| 74 | + | } | |
| 62 | 75 | fs.mu.RLock() | |
| 63 | 76 | index := lastSelectableIndex(fs.displayRows) | |
| 64 | 77 | fs.mu.RUnlock() | |
| 65 | 78 | fs.selectIndex(index) | |
| 66 | 79 | return true | |
| 67 | 80 | case uint(gdk.KEY_Up): | |
| 81 | + | if textEditing { | |
| 82 | + | return false | |
| 83 | + | } | |
| 68 | 84 | fs.selectAdjacentRow(-1) | |
| 69 | 85 | return true | |
| 70 | 86 | case uint(gdk.KEY_Down): | |
| 87 | + | if textEditing { | |
| 88 | + | return false | |
| 89 | + | } | |
| 71 | 90 | fs.selectAdjacentRow(1) | |
| 72 | 91 | return true | |
| 73 | 92 | case uint(gdk.KEY_slash): | |
| 74 | - | if fs.inTextEditContext() { | |
| 93 | + | if textEditing { | |
| 75 | 94 | return false | |
| 76 | 95 | } | |
| 77 | 96 | searchFocused := searchEntry != nil && searchEntry.HasFocus() | |
| @@ -81,7 +100,7 @@ func (fs *FavoritesSidebar) setupKeyboardNavigation() { | |||
| 81 | 100 | fs.focusSearch() | |
| 82 | 101 | return true | |
| 83 | 102 | } | |
| 84 | - | return fs.handleSingleKeyCommand(keyval) | |
| 103 | + | return fs.handleSingleKeyCommand(keyval, state) | |
| 85 | 104 | } | |
| 86 | 105 | fs.retainedCallbacks = append(fs.retainedCallbacks, keyPressedCb) | |
| 87 | 106 | keyController.ConnectKeyPressed(&keyPressedCb) | |
| @@ -275,8 +294,9 @@ func (fs *FavoritesSidebar) inTextEditContext() bool { | |||
| 275 | 294 | return false | |
| 276 | 295 | } | |
| 277 | 296 | ||
| 278 | - | func (fs *FavoritesSidebar) handleSingleKeyCommand(keyval uint) bool { | |
| 279 | - | if fs == nil || fs.inTextEditContext() { | |
| 297 | + | func (fs *FavoritesSidebar) handleSingleKeyCommand(keyval uint, state gdk.ModifierType) bool { | |
| 298 | + | shortcutModifiers := gdk.ControlMaskValue | gdk.ShiftMaskValue | gdk.AltMaskValue | |
| 299 | + | if fs == nil || fs.inTextEditContext() || state&shortcutModifiers != 0 { | |
| 280 | 300 | return false | |
| 281 | 301 | } | |
| 282 | 302 | fs.mu.RLock() | |
| @@ -28,6 +28,7 @@ func (fs *FavoritesSidebar) setupKeyboardNavigation() { | |||||
| 28 | onClose := fs.onClose | 28 | onClose := fs.onClose | ||
| 29 | fs.mu.RUnlock() | 29 | fs.mu.RUnlock() | ||
| 30 | 30 | ||||
| 31 | + | textEditing := fs.inTextEditContext() | |||
| 31 | switch keyval { | 32 | switch keyval { | ||
| 32 | case uint(gdk.KEY_Escape): | 33 | case uint(gdk.KEY_Escape): | ||
| 33 | if fs.cancelManagement() { | 34 | if fs.cancelManagement() { | ||
| @@ -47,31 +48,49 @@ func (fs *FavoritesSidebar) setupKeyboardNavigation() { | |||||
| 47 | fs.cycleFocusZone(state&gdk.ShiftMaskValue != 0) | 48 | fs.cycleFocusZone(state&gdk.ShiftMaskValue != 0) | ||
| 48 | return true | 49 | return true | ||
| 49 | case uint(gdk.KEY_Page_Up): | 50 | case uint(gdk.KEY_Page_Up): | ||
| 51 | + | if textEditing { | |||
| 52 | + | return false | |||
| 53 | + | } | |||
| 50 | fs.selectAdjacentRow(-5) | 54 | fs.selectAdjacentRow(-5) | ||
| 51 | return true | 55 | return true | ||
| 52 | case uint(gdk.KEY_Page_Down): | 56 | case uint(gdk.KEY_Page_Down): | ||
| 57 | + | if textEditing { | |||
| 58 | + | return false | |||
| 59 | + | } | |||
| 53 | fs.selectAdjacentRow(5) | 60 | fs.selectAdjacentRow(5) | ||
| 54 | return true | 61 | return true | ||
| 55 | case uint(gdk.KEY_Home): | 62 | case uint(gdk.KEY_Home): | ||
| 63 | + | if textEditing { | |||
| 64 | + | return false | |||
| 65 | + | } | |||
| 56 | fs.mu.RLock() | 66 | fs.mu.RLock() | ||
| 57 | index := firstSelectableIndex(fs.displayRows) | 67 | index := firstSelectableIndex(fs.displayRows) | ||
| 58 | fs.mu.RUnlock() | 68 | fs.mu.RUnlock() | ||
| 59 | fs.selectIndex(index) | 69 | fs.selectIndex(index) | ||
| 60 | return true | 70 | return true | ||
| 61 | case uint(gdk.KEY_End): | 71 | case uint(gdk.KEY_End): | ||
| 72 | + | if textEditing { | |||
| 73 | + | return false | |||
| 74 | + | } | |||
| 62 | fs.mu.RLock() | 75 | fs.mu.RLock() | ||
| 63 | index := lastSelectableIndex(fs.displayRows) | 76 | index := lastSelectableIndex(fs.displayRows) | ||
| 64 | fs.mu.RUnlock() | 77 | fs.mu.RUnlock() | ||
| 65 | fs.selectIndex(index) | 78 | fs.selectIndex(index) | ||
| 66 | return true | 79 | return true | ||
| 67 | case uint(gdk.KEY_Up): | 80 | case uint(gdk.KEY_Up): | ||
| 81 | + | if textEditing { | |||
| 82 | + | return false | |||
| 83 | + | } | |||
| 68 | fs.selectAdjacentRow(-1) | 84 | fs.selectAdjacentRow(-1) | ||
| 69 | return true | 85 | return true | ||
| 70 | case uint(gdk.KEY_Down): | 86 | case uint(gdk.KEY_Down): | ||
| 87 | + | if textEditing { | |||
| 88 | + | return false | |||
| 89 | + | } | |||
| 71 | fs.selectAdjacentRow(1) | 90 | fs.selectAdjacentRow(1) | ||
| 72 | return true | 91 | return true | ||
| 73 | case uint(gdk.KEY_slash): | 92 | case uint(gdk.KEY_slash): | ||
| 74 | - | if fs.inTextEditContext() { | 93 | + | if textEditing { |
| 75 | return false | 94 | return false | ||
| 76 | } | 95 | } | ||
| 77 | searchFocused := searchEntry != nil && searchEntry.HasFocus() | 96 | searchFocused := searchEntry != nil && searchEntry.HasFocus() | ||
| @@ -81,7 +100,7 @@ func (fs *FavoritesSidebar) setupKeyboardNavigation() { | |||||
| 81 | fs.focusSearch() | 100 | fs.focusSearch() | ||
| 82 | return true | 101 | return true | ||
| 83 | } | 102 | } | ||
| 84 | - | return fs.handleSingleKeyCommand(keyval) | 103 | + | return fs.handleSingleKeyCommand(keyval, state) |
| 85 | } | 104 | } | ||
| 86 | fs.retainedCallbacks = append(fs.retainedCallbacks, keyPressedCb) | 105 | fs.retainedCallbacks = append(fs.retainedCallbacks, keyPressedCb) | ||
| 87 | keyController.ConnectKeyPressed(&keyPressedCb) | 106 | keyController.ConnectKeyPressed(&keyPressedCb) | ||
| @@ -275,8 +294,9 @@ func (fs *FavoritesSidebar) inTextEditContext() bool { | |||||
| 275 | return false | 294 | return false | ||
| 276 | } | 295 | } | ||
| 277 | 296 | ||||
| 278 | - | func (fs *FavoritesSidebar) handleSingleKeyCommand(keyval uint) bool { | 297 | + | func (fs *FavoritesSidebar) handleSingleKeyCommand(keyval uint, state gdk.ModifierType) bool { |
| 279 | - | if fs == nil || fs.inTextEditContext() { | 298 | + | shortcutModifiers := gdk.ControlMaskValue | gdk.ShiftMaskValue | gdk.AltMaskValue |
| 299 | + | if fs == nil || fs.inTextEditContext() || state&shortcutModifiers != 0 { | |||
| 280 | return false | 300 | return false | ||
| 281 | } | 301 | } | ||
| 282 | fs.mu.RLock() | 302 | fs.mu.RLock() | ||
M internal/ui/component/favorites_sidebar_rows.go +7 -6
| @@ -186,7 +186,7 @@ func (fs *FavoritesSidebar) rebuildList() { | |||
| 186 | 186 | return | |
| 187 | 187 | } | |
| 188 | 188 | for _, row := range rows { | |
| 189 | - | fs.appendFavoriteRow(listBox, row.Favorite) | |
| 189 | + | fs.appendFavoriteRow(listBox, row) | |
| 190 | 190 | } | |
| 191 | 191 | for _, text := range favoriteSidebarNoticeRows(notice, query, true) { | |
| 192 | 192 | fs.appendNoticeRow(listBox, text) | |
| @@ -225,7 +225,8 @@ func (fs *FavoritesSidebar) appendNoticeRow(listBox *gtk.ListBox, text string) { | |||
| 225 | 225 | listBox.Append(&row.Widget) | |
| 226 | 226 | } | |
| 227 | 227 | ||
| 228 | - | func (fs *FavoritesSidebar) appendFavoriteRow(listBox *gtk.ListBox, fav *entity.Favorite) { | |
| 228 | + | func (fs *FavoritesSidebar) appendFavoriteRow(listBox *gtk.ListBox, displayRow favoriteSidebarDisplayRow) { | |
| 229 | + | fav := displayRow.Favorite | |
| 229 | 230 | if fav == nil { | |
| 230 | 231 | return | |
| 231 | 232 | } | |
| @@ -243,10 +244,10 @@ func (fs *FavoritesSidebar) appendFavoriteRow(listBox *gtk.ListBox, fav *entity. | |||
| 243 | 244 | return | |
| 244 | 245 | } | |
| 245 | 246 | row.AddCssClass("favorites-sidebar-row") | |
| 246 | - | row.SetSelectable(true) | |
| 247 | - | row.SetActivatable(true) | |
| 248 | - | row.SetCanFocus(true) | |
| 249 | - | row.SetFocusOnClick(true) | |
| 247 | + | row.SetSelectable(displayRow.Selectable) | |
| 248 | + | row.SetActivatable(displayRow.Selectable) | |
| 249 | + | row.SetCanFocus(displayRow.Selectable) | |
| 250 | + | row.SetFocusOnClick(displayRow.Selectable) | |
| 250 | 251 | row.SetChild(&rowBox.Widget) | |
| 251 | 252 | listBox.Append(&row.Widget) | |
| 252 | 253 | } | |
| @@ -186,7 +186,7 @@ func (fs *FavoritesSidebar) rebuildList() { | |||||
| 186 | return | 186 | return | ||
| 187 | } | 187 | } | ||
| 188 | for _, row := range rows { | 188 | for _, row := range rows { | ||
| 189 | - | fs.appendFavoriteRow(listBox, row.Favorite) | 189 | + | fs.appendFavoriteRow(listBox, row) |
| 190 | } | 190 | } | ||
| 191 | for _, text := range favoriteSidebarNoticeRows(notice, query, true) { | 191 | for _, text := range favoriteSidebarNoticeRows(notice, query, true) { | ||
| 192 | fs.appendNoticeRow(listBox, text) | 192 | fs.appendNoticeRow(listBox, text) | ||
| @@ -225,7 +225,8 @@ func (fs *FavoritesSidebar) appendNoticeRow(listBox *gtk.ListBox, text string) { | |||||
| 225 | listBox.Append(&row.Widget) | 225 | listBox.Append(&row.Widget) | ||
| 226 | } | 226 | } | ||
| 227 | 227 | ||||
| 228 | - | func (fs *FavoritesSidebar) appendFavoriteRow(listBox *gtk.ListBox, fav *entity.Favorite) { | 228 | + | func (fs *FavoritesSidebar) appendFavoriteRow(listBox *gtk.ListBox, displayRow favoriteSidebarDisplayRow) { |
| 229 | + | fav := displayRow.Favorite | |||
| 229 | if fav == nil { | 230 | if fav == nil { | ||
| 230 | return | 231 | return | ||
| 231 | } | 232 | } | ||
| @@ -243,10 +244,10 @@ func (fs *FavoritesSidebar) appendFavoriteRow(listBox *gtk.ListBox, fav *entity. | |||||
| 243 | return | 244 | return | ||
| 244 | } | 245 | } | ||
| 245 | row.AddCssClass("favorites-sidebar-row") | 246 | row.AddCssClass("favorites-sidebar-row") | ||
| 246 | - | row.SetSelectable(true) | 247 | + | row.SetSelectable(displayRow.Selectable) |
| 247 | - | row.SetActivatable(true) | 248 | + | row.SetActivatable(displayRow.Selectable) |
| 248 | - | row.SetCanFocus(true) | 249 | + | row.SetCanFocus(displayRow.Selectable) |
| 249 | - | row.SetFocusOnClick(true) | 250 | + | row.SetFocusOnClick(displayRow.Selectable) |
| 250 | row.SetChild(&rowBox.Widget) | 251 | row.SetChild(&rowBox.Widget) | ||
| 251 | listBox.Append(&row.Widget) | 252 | listBox.Append(&row.Widget) | ||
| 252 | } | 253 | } | ||
M internal/ui/component/favorites_sidebar_test.go +60 -13
| @@ -34,8 +34,9 @@ type fakeFavoritesSidebarUC struct { | |||
| 34 | 34 | favID entity.FavoriteID | |
| 35 | 35 | tagID entity.TagID | |
| 36 | 36 | } | |
| 37 | - | err error | |
| 38 | - | getAllCalled chan struct{} | |
| 37 | + | err error | |
| 38 | + | errOnSetShortcut error | |
| 39 | + | getAllCalled chan struct{} | |
| 39 | 40 | } | |
| 40 | 41 | ||
| 41 | 42 | func (f *fakeFavoritesSidebarUC) GetAll(context.Context) ([]*entity.Favorite, error) { | |
| @@ -45,10 +46,10 @@ func (f *fakeFavoritesSidebarUC) GetAll(context.Context) ([]*entity.Favorite, er | |||
| 45 | 46 | default: | |
| 46 | 47 | } | |
| 47 | 48 | } | |
| 48 | - | return f.favorites, nil | |
| 49 | + | return f.favorites, f.err | |
| 49 | 50 | } | |
| 50 | 51 | func (f *fakeFavoritesSidebarUC) GetAllTags(context.Context) ([]*entity.Tag, error) { | |
| 51 | - | return f.tags, nil | |
| 52 | + | return f.tags, f.err | |
| 52 | 53 | } | |
| 53 | 54 | func (f *fakeFavoritesSidebarUC) AddFavorite(_ context.Context, input dto.FavoriteCreateInput) (*entity.Favorite, error) { | |
| 54 | 55 | f.addInputs = append(f.addInputs, input) | |
| @@ -73,6 +74,9 @@ func (f *fakeFavoritesSidebarUC) SetShortcut(_ context.Context, id entity.Favori | |||
| 73 | 74 | id entity.FavoriteID | |
| 74 | 75 | key *int | |
| 75 | 76 | }{id: id, key: key}) | |
| 77 | + | if f.errOnSetShortcut != nil { | |
| 78 | + | return f.errOnSetShortcut | |
| 79 | + | } | |
| 76 | 80 | return f.err | |
| 77 | 81 | } | |
| 78 | 82 | func (f *fakeFavoritesSidebarUC) TagFavorite(_ context.Context, favID entity.FavoriteID, tagID entity.TagID) error { | |
| @@ -193,7 +197,11 @@ func TestFavoritesSidebarActivationCallbacks(t *testing.T) { | |||
| 193 | 197 | ||
| 194 | 198 | func TestFavoritesSidebarRequestReloadIfVisibleReloadsData(t *testing.T) { | |
| 195 | 199 | called := make(chan struct{}, 1) | |
| 196 | - | uc := &fakeFavoritesSidebarUC{getAllCalled: called} | |
| 200 | + | uc := &fakeFavoritesSidebarUC{ | |
| 201 | + | favorites: []*entity.Favorite{{ID: 1, URL: "https://go.dev", Title: "Go"}}, | |
| 202 | + | tags: []*entity.Tag{{ID: 10, Name: "dev"}}, | |
| 203 | + | getAllCalled: called, | |
| 204 | + | } | |
| 197 | 205 | fs := newFavoritesSidebarHarness(nil, nil) | |
| 198 | 206 | fs.favoritesUC = uc | |
| 199 | 207 | fs.visible = true | |
| @@ -208,6 +216,11 @@ func TestFavoritesSidebarRequestReloadIfVisibleReloadsData(t *testing.T) { | |||
| 208 | 216 | return false | |
| 209 | 217 | } | |
| 210 | 218 | }, time.Second, 10*time.Millisecond) | |
| 219 | + | require.Eventually(t, func() bool { | |
| 220 | + | fs.mu.RLock() | |
| 221 | + | defer fs.mu.RUnlock() | |
| 222 | + | return len(fs.allFavorites) == 1 && len(fs.allTags) == 1 | |
| 223 | + | }, time.Second, 10*time.Millisecond) | |
| 211 | 224 | } | |
| 212 | 225 | ||
| 213 | 226 | func TestFavoritesSidebarStaleLoadRejected(t *testing.T) { | |
| @@ -313,11 +326,11 @@ func TestFavoritesSidebarSingleKeyCommandsStartManagementOutsideTextEditContext( | |||
| 313 | 326 | fs.searchEntry = nil | |
| 314 | 327 | fs.displayRows = []favoriteSidebarDisplayRow{{FavoriteID: 1, Favorite: &entity.Favorite{ID: 1}, Selectable: true}} | |
| 315 | 328 | ||
| 316 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_a))) | |
| 329 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_a), 0)) | |
| 317 | 330 | assert.Equal(t, favoritesSidebarModeAdd, fs.mode) | |
| 318 | 331 | ||
| 319 | 332 | fs.mode = favoritesSidebarModeNone | |
| 320 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_e))) | |
| 333 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_e), 0)) | |
| 321 | 334 | assert.Equal(t, favoritesSidebarModeEdit, fs.mode) | |
| 322 | 335 | } | |
| 323 | 336 | ||
| @@ -431,6 +444,40 @@ func TestFavoritesSidebarEditSubmitIgnoresTagsByDesign(t *testing.T) { | |||
| 431 | 444 | assert.Empty(t, uc.untagged) | |
| 432 | 445 | } | |
| 433 | 446 | ||
| 447 | + | func TestFavoritesSidebarSingleKeyCommandsIgnoreModifiedKeys(t *testing.T) { | |
| 448 | + | fs := newFavoritesSidebarHarness(nil, nil) | |
| 449 | + | fs.searchEntry = nil | |
| 450 | + | ||
| 451 | + | assert.False(t, fs.handleSingleKeyCommand(uint(gdk.KEY_a), gdk.ControlMaskValue)) | |
| 452 | + | assert.Equal(t, favoritesSidebarModeNone, fs.mode) | |
| 453 | + | } | |
| 454 | + | ||
| 455 | + | func TestFavoritesSidebarConfirmDeleteRequiresConfirmationState(t *testing.T) { | |
| 456 | + | fav := &entity.Favorite{ID: 5, URL: "https://go.dev"} | |
| 457 | + | fs := newFavoritesSidebarHarness([]*entity.Favorite{fav}, nil) | |
| 458 | + | uc := fs.favoritesUC.(*fakeFavoritesSidebarUC) | |
| 459 | + | fs.displayRows = []favoriteSidebarDisplayRow{{FavoriteID: fav.ID, Favorite: fav, URL: fav.URL, Selectable: true}} | |
| 460 | + | ||
| 461 | + | assert.True(t, fs.confirmDeleteFavorite()) | |
| 462 | + | assert.Empty(t, uc.deletedIDs) | |
| 463 | + | assert.True(t, fs.confirmDelete) | |
| 464 | + | } | |
| 465 | + | ||
| 466 | + | func TestFavoritesSidebarAddShortcutFailureClosesFormAfterCreate(t *testing.T) { | |
| 467 | + | uc := &fakeFavoritesSidebarUC{errOnSetShortcut: errors.New("shortcut failed")} | |
| 468 | + | fs := newFavoritesSidebarHarness(nil, nil) | |
| 469 | + | fs.favoritesUC = uc | |
| 470 | + | fs.formURL = "https://add.test" | |
| 471 | + | fs.formTitle = "Added" | |
| 472 | + | fs.formShortcut = "3" | |
| 473 | + | fs.mode = favoritesSidebarModeAdd | |
| 474 | + | ||
| 475 | + | assert.True(t, fs.submitForm()) | |
| 476 | + | require.Len(t, uc.addInputs, 1) | |
| 477 | + | assert.Equal(t, favoritesSidebarModeNone, fs.mode) | |
| 478 | + | assert.Contains(t, fs.notice, "failed to set shortcut") | |
| 479 | + | } | |
| 480 | + | ||
| 434 | 481 | func TestFavoritesSidebarTagShortcutAndDeleteFlows(t *testing.T) { | |
| 435 | 482 | dev := entity.Tag{ID: 10, Name: "dev"} | |
| 436 | 483 | news := entity.Tag{ID: 11, Name: "news"} | |
| @@ -441,24 +488,24 @@ func TestFavoritesSidebarTagShortcutAndDeleteFlows(t *testing.T) { | |||
| 441 | 488 | fs.displayRows = []favoriteSidebarDisplayRow{{FavoriteID: fav.ID, Favorite: fav, URL: fav.URL, Selectable: true}} | |
| 442 | 489 | ||
| 443 | 490 | fs.mode = favoritesSidebarModeTag | |
| 444 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_1))) | |
| 491 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_1), 0)) | |
| 445 | 492 | require.Len(t, uc.untagged, 1) | |
| 446 | 493 | assert.Equal(t, entity.TagID(10), uc.untagged[0].tagID) | |
| 447 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_2))) | |
| 494 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_2), 0)) | |
| 448 | 495 | require.Len(t, uc.tagged, 1) | |
| 449 | 496 | assert.Equal(t, entity.TagID(11), uc.tagged[0].tagID) | |
| 450 | 497 | ||
| 451 | 498 | fs.mode = favoritesSidebarModeShortcut | |
| 452 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_9))) | |
| 453 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_BackSpace))) | |
| 499 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_9), 0)) | |
| 500 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_BackSpace), 0)) | |
| 454 | 501 | require.GreaterOrEqual(t, len(uc.shortcuts), 2) | |
| 455 | 502 | assert.Equal(t, 9, *uc.shortcuts[len(uc.shortcuts)-2].key) | |
| 456 | 503 | assert.Nil(t, uc.shortcuts[len(uc.shortcuts)-1].key) | |
| 457 | 504 | ||
| 458 | 505 | fs.mode = favoritesSidebarModeNone | |
| 459 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_Delete))) | |
| 506 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_Delete), 0)) | |
| 460 | 507 | assert.True(t, fs.confirmDelete) | |
| 461 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_Delete))) | |
| 508 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_Delete), 0)) | |
| 462 | 509 | assert.Equal(t, []entity.FavoriteID{5}, uc.deletedIDs) | |
| 463 | 510 | } | |
| 464 | 511 | ||
| @@ -34,8 +34,9 @@ type fakeFavoritesSidebarUC struct { | |||||
| 34 | favID entity.FavoriteID | 34 | favID entity.FavoriteID | ||
| 35 | tagID entity.TagID | 35 | tagID entity.TagID | ||
| 36 | } | 36 | } | ||
| 37 | - | err error | 37 | + | err error |
| 38 | - | getAllCalled chan struct{} | 38 | + | errOnSetShortcut error |
| 39 | + | getAllCalled chan struct{} | |||
| 39 | } | 40 | } | ||
| 40 | 41 | ||||
| 41 | func (f *fakeFavoritesSidebarUC) GetAll(context.Context) ([]*entity.Favorite, error) { | 42 | func (f *fakeFavoritesSidebarUC) GetAll(context.Context) ([]*entity.Favorite, error) { | ||
| @@ -45,10 +46,10 @@ func (f *fakeFavoritesSidebarUC) GetAll(context.Context) ([]*entity.Favorite, er | |||||
| 45 | default: | 46 | default: | ||
| 46 | } | 47 | } | ||
| 47 | } | 48 | } | ||
| 48 | - | return f.favorites, nil | 49 | + | return f.favorites, f.err |
| 49 | } | 50 | } | ||
| 50 | func (f *fakeFavoritesSidebarUC) GetAllTags(context.Context) ([]*entity.Tag, error) { | 51 | func (f *fakeFavoritesSidebarUC) GetAllTags(context.Context) ([]*entity.Tag, error) { | ||
| 51 | - | return f.tags, nil | 52 | + | return f.tags, f.err |
| 52 | } | 53 | } | ||
| 53 | func (f *fakeFavoritesSidebarUC) AddFavorite(_ context.Context, input dto.FavoriteCreateInput) (*entity.Favorite, error) { | 54 | func (f *fakeFavoritesSidebarUC) AddFavorite(_ context.Context, input dto.FavoriteCreateInput) (*entity.Favorite, error) { | ||
| 54 | f.addInputs = append(f.addInputs, input) | 55 | f.addInputs = append(f.addInputs, input) | ||
| @@ -73,6 +74,9 @@ func (f *fakeFavoritesSidebarUC) SetShortcut(_ context.Context, id entity.Favori | |||||
| 73 | id entity.FavoriteID | 74 | id entity.FavoriteID | ||
| 74 | key *int | 75 | key *int | ||
| 75 | }{id: id, key: key}) | 76 | }{id: id, key: key}) | ||
| 77 | + | if f.errOnSetShortcut != nil { | |||
| 78 | + | return f.errOnSetShortcut | |||
| 79 | + | } | |||
| 76 | return f.err | 80 | return f.err | ||
| 77 | } | 81 | } | ||
| 78 | func (f *fakeFavoritesSidebarUC) TagFavorite(_ context.Context, favID entity.FavoriteID, tagID entity.TagID) error { | 82 | func (f *fakeFavoritesSidebarUC) TagFavorite(_ context.Context, favID entity.FavoriteID, tagID entity.TagID) error { | ||
| @@ -193,7 +197,11 @@ func TestFavoritesSidebarActivationCallbacks(t *testing.T) { | |||||
| 193 | 197 | ||||
| 194 | func TestFavoritesSidebarRequestReloadIfVisibleReloadsData(t *testing.T) { | 198 | func TestFavoritesSidebarRequestReloadIfVisibleReloadsData(t *testing.T) { | ||
| 195 | called := make(chan struct{}, 1) | 199 | called := make(chan struct{}, 1) | ||
| 196 | - | uc := &fakeFavoritesSidebarUC{getAllCalled: called} | 200 | + | uc := &fakeFavoritesSidebarUC{ |
| 201 | + | favorites: []*entity.Favorite{{ID: 1, URL: "https://go.dev", Title: "Go"}}, | |||
| 202 | + | tags: []*entity.Tag{{ID: 10, Name: "dev"}}, | |||
| 203 | + | getAllCalled: called, | |||
| 204 | + | } | |||
| 197 | fs := newFavoritesSidebarHarness(nil, nil) | 205 | fs := newFavoritesSidebarHarness(nil, nil) | ||
| 198 | fs.favoritesUC = uc | 206 | fs.favoritesUC = uc | ||
| 199 | fs.visible = true | 207 | fs.visible = true | ||
| @@ -208,6 +216,11 @@ func TestFavoritesSidebarRequestReloadIfVisibleReloadsData(t *testing.T) { | |||||
| 208 | return false | 216 | return false | ||
| 209 | } | 217 | } | ||
| 210 | }, time.Second, 10*time.Millisecond) | 218 | }, time.Second, 10*time.Millisecond) | ||
| 219 | + | require.Eventually(t, func() bool { | |||
| 220 | + | fs.mu.RLock() | |||
| 221 | + | defer fs.mu.RUnlock() | |||
| 222 | + | return len(fs.allFavorites) == 1 && len(fs.allTags) == 1 | |||
| 223 | + | }, time.Second, 10*time.Millisecond) | |||
| 211 | } | 224 | } | ||
| 212 | 225 | ||||
| 213 | func TestFavoritesSidebarStaleLoadRejected(t *testing.T) { | 226 | func TestFavoritesSidebarStaleLoadRejected(t *testing.T) { | ||
| @@ -313,11 +326,11 @@ func TestFavoritesSidebarSingleKeyCommandsStartManagementOutsideTextEditContext( | |||||
| 313 | fs.searchEntry = nil | 326 | fs.searchEntry = nil | ||
| 314 | fs.displayRows = []favoriteSidebarDisplayRow{{FavoriteID: 1, Favorite: &entity.Favorite{ID: 1}, Selectable: true}} | 327 | fs.displayRows = []favoriteSidebarDisplayRow{{FavoriteID: 1, Favorite: &entity.Favorite{ID: 1}, Selectable: true}} | ||
| 315 | 328 | ||||
| 316 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_a))) | 329 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_a), 0)) |
| 317 | assert.Equal(t, favoritesSidebarModeAdd, fs.mode) | 330 | assert.Equal(t, favoritesSidebarModeAdd, fs.mode) | ||
| 318 | 331 | ||||
| 319 | fs.mode = favoritesSidebarModeNone | 332 | fs.mode = favoritesSidebarModeNone | ||
| 320 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_e))) | 333 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_e), 0)) |
| 321 | assert.Equal(t, favoritesSidebarModeEdit, fs.mode) | 334 | assert.Equal(t, favoritesSidebarModeEdit, fs.mode) | ||
| 322 | } | 335 | } | ||
| 323 | 336 | ||||
| @@ -431,6 +444,40 @@ func TestFavoritesSidebarEditSubmitIgnoresTagsByDesign(t *testing.T) { | |||||
| 431 | assert.Empty(t, uc.untagged) | 444 | assert.Empty(t, uc.untagged) | ||
| 432 | } | 445 | } | ||
| 433 | 446 | ||||
| 447 | + | func TestFavoritesSidebarSingleKeyCommandsIgnoreModifiedKeys(t *testing.T) { | |||
| 448 | + | fs := newFavoritesSidebarHarness(nil, nil) | |||
| 449 | + | fs.searchEntry = nil | |||
| 450 | + | ||||
| 451 | + | assert.False(t, fs.handleSingleKeyCommand(uint(gdk.KEY_a), gdk.ControlMaskValue)) | |||
| 452 | + | assert.Equal(t, favoritesSidebarModeNone, fs.mode) | |||
| 453 | + | } | |||
| 454 | + | ||||
| 455 | + | func TestFavoritesSidebarConfirmDeleteRequiresConfirmationState(t *testing.T) { | |||
| 456 | + | fav := &entity.Favorite{ID: 5, URL: "https://go.dev"} | |||
| 457 | + | fs := newFavoritesSidebarHarness([]*entity.Favorite{fav}, nil) | |||
| 458 | + | uc := fs.favoritesUC.(*fakeFavoritesSidebarUC) | |||
| 459 | + | fs.displayRows = []favoriteSidebarDisplayRow{{FavoriteID: fav.ID, Favorite: fav, URL: fav.URL, Selectable: true}} | |||
| 460 | + | ||||
| 461 | + | assert.True(t, fs.confirmDeleteFavorite()) | |||
| 462 | + | assert.Empty(t, uc.deletedIDs) | |||
| 463 | + | assert.True(t, fs.confirmDelete) | |||
| 464 | + | } | |||
| 465 | + | ||||
| 466 | + | func TestFavoritesSidebarAddShortcutFailureClosesFormAfterCreate(t *testing.T) { | |||
| 467 | + | uc := &fakeFavoritesSidebarUC{errOnSetShortcut: errors.New("shortcut failed")} | |||
| 468 | + | fs := newFavoritesSidebarHarness(nil, nil) | |||
| 469 | + | fs.favoritesUC = uc | |||
| 470 | + | fs.formURL = "https://add.test" | |||
| 471 | + | fs.formTitle = "Added" | |||
| 472 | + | fs.formShortcut = "3" | |||
| 473 | + | fs.mode = favoritesSidebarModeAdd | |||
| 474 | + | ||||
| 475 | + | assert.True(t, fs.submitForm()) | |||
| 476 | + | require.Len(t, uc.addInputs, 1) | |||
| 477 | + | assert.Equal(t, favoritesSidebarModeNone, fs.mode) | |||
| 478 | + | assert.Contains(t, fs.notice, "failed to set shortcut") | |||
| 479 | + | } | |||
| 480 | + | ||||
| 434 | func TestFavoritesSidebarTagShortcutAndDeleteFlows(t *testing.T) { | 481 | func TestFavoritesSidebarTagShortcutAndDeleteFlows(t *testing.T) { | ||
| 435 | dev := entity.Tag{ID: 10, Name: "dev"} | 482 | dev := entity.Tag{ID: 10, Name: "dev"} | ||
| 436 | news := entity.Tag{ID: 11, Name: "news"} | 483 | news := entity.Tag{ID: 11, Name: "news"} | ||
| @@ -441,24 +488,24 @@ func TestFavoritesSidebarTagShortcutAndDeleteFlows(t *testing.T) { | |||||
| 441 | fs.displayRows = []favoriteSidebarDisplayRow{{FavoriteID: fav.ID, Favorite: fav, URL: fav.URL, Selectable: true}} | 488 | fs.displayRows = []favoriteSidebarDisplayRow{{FavoriteID: fav.ID, Favorite: fav, URL: fav.URL, Selectable: true}} | ||
| 442 | 489 | ||||
| 443 | fs.mode = favoritesSidebarModeTag | 490 | fs.mode = favoritesSidebarModeTag | ||
| 444 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_1))) | 491 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_1), 0)) |
| 445 | require.Len(t, uc.untagged, 1) | 492 | require.Len(t, uc.untagged, 1) | ||
| 446 | assert.Equal(t, entity.TagID(10), uc.untagged[0].tagID) | 493 | assert.Equal(t, entity.TagID(10), uc.untagged[0].tagID) | ||
| 447 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_2))) | 494 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_2), 0)) |
| 448 | require.Len(t, uc.tagged, 1) | 495 | require.Len(t, uc.tagged, 1) | ||
| 449 | assert.Equal(t, entity.TagID(11), uc.tagged[0].tagID) | 496 | assert.Equal(t, entity.TagID(11), uc.tagged[0].tagID) | ||
| 450 | 497 | ||||
| 451 | fs.mode = favoritesSidebarModeShortcut | 498 | fs.mode = favoritesSidebarModeShortcut | ||
| 452 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_9))) | 499 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_9), 0)) |
| 453 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_BackSpace))) | 500 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_BackSpace), 0)) |
| 454 | require.GreaterOrEqual(t, len(uc.shortcuts), 2) | 501 | require.GreaterOrEqual(t, len(uc.shortcuts), 2) | ||
| 455 | assert.Equal(t, 9, *uc.shortcuts[len(uc.shortcuts)-2].key) | 502 | assert.Equal(t, 9, *uc.shortcuts[len(uc.shortcuts)-2].key) | ||
| 456 | assert.Nil(t, uc.shortcuts[len(uc.shortcuts)-1].key) | 503 | assert.Nil(t, uc.shortcuts[len(uc.shortcuts)-1].key) | ||
| 457 | 504 | ||||
| 458 | fs.mode = favoritesSidebarModeNone | 505 | fs.mode = favoritesSidebarModeNone | ||
| 459 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_Delete))) | 506 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_Delete), 0)) |
| 460 | assert.True(t, fs.confirmDelete) | 507 | assert.True(t, fs.confirmDelete) | ||
| 461 | - | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_Delete))) | 508 | + | assert.True(t, fs.handleSingleKeyCommand(uint(gdk.KEY_Delete), 0)) |
| 462 | assert.Equal(t, []entity.FavoriteID{5}, uc.deletedIDs) | 509 | assert.Equal(t, []entity.FavoriteID{5}, uc.deletedIDs) | ||
| 463 | } | 510 | } | ||
| 464 | 511 | ||||