fix(favorites): preserve shortcuts on metadata updates
- commit
- 7be50169f1cb038f45e1a5e42a5d92996c6e4c1c
- author
- brice · · 2026-06-28 10:03 UTC
- parent
- 87325b3
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +114 -30
M internal/application/dto/favorites.go +5 -4
| @@ -12,8 +12,9 @@ type FavoriteCreateInput struct { | |||
| 12 | 12 | ||
| 13 | 13 | // FavoriteUpdateInput contains editable favorite metadata. | |
| 14 | 14 | type FavoriteUpdateInput struct { | |
| 15 | - | ID entity.FavoriteID `json:"id"` | |
| 16 | - | Title string `json:"title"` | |
| 17 | - | FaviconURL string `json:"favicon_url"` | |
| 18 | - | ShortcutKey *int `json:"shortcut_key"` | |
| 15 | + | ID entity.FavoriteID `json:"id"` | |
| 16 | + | Title string `json:"title"` | |
| 17 | + | FaviconURL string `json:"favicon_url"` | |
| 18 | + | ShortcutKey *int `json:"shortcut_key"` | |
| 19 | + | ShortcutKeySet bool `json:"-"` | |
| 19 | 20 | } | |
| @@ -12,8 +12,9 @@ type FavoriteCreateInput struct { | |||||
| 12 | 12 | ||||
| 13 | // FavoriteUpdateInput contains editable favorite metadata. | 13 | // FavoriteUpdateInput contains editable favorite metadata. | ||
| 14 | type FavoriteUpdateInput struct { | 14 | type FavoriteUpdateInput struct { | ||
| 15 | - | ID entity.FavoriteID `json:"id"` | 15 | + | ID entity.FavoriteID `json:"id"` |
| 16 | - | Title string `json:"title"` | 16 | + | Title string `json:"title"` |
| 17 | - | FaviconURL string `json:"favicon_url"` | 17 | + | FaviconURL string `json:"favicon_url"` |
| 18 | - | ShortcutKey *int `json:"shortcut_key"` | 18 | + | ShortcutKey *int `json:"shortcut_key"` |
| 19 | + | ShortcutKeySet bool `json:"-"` | |||
| 19 | } | 20 | } | ||
M internal/application/usecase/manage_favorites.go +4 -2
| @@ -180,7 +180,7 @@ func (uc *ManageFavoritesUseCase) UpdateFavorite(ctx context.Context, input dto. | |||
| 180 | 180 | if input.ID <= 0 { | |
| 181 | 181 | return nil, fmt.Errorf("favorite ID is required") | |
| 182 | 182 | } | |
| 183 | - | if input.ShortcutKey != nil && (*input.ShortcutKey < 1 || *input.ShortcutKey > 9) { | |
| 183 | + | if input.ShortcutKeySet && input.ShortcutKey != nil && (*input.ShortcutKey < 1 || *input.ShortcutKey > 9) { | |
| 184 | 184 | return nil, fmt.Errorf("shortcut key must be 1-9, got %d", *input.ShortcutKey) | |
| 185 | 185 | } | |
| 186 | 186 | ||
| @@ -198,7 +198,9 @@ func (uc *ManageFavoritesUseCase) UpdateFavorite(ctx context.Context, input dto. | |||
| 198 | 198 | } | |
| 199 | 199 | fav.Title = title | |
| 200 | 200 | fav.FaviconURL = strings.TrimSpace(input.FaviconURL) | |
| 201 | - | fav.ShortcutKey = input.ShortcutKey | |
| 201 | + | if input.ShortcutKeySet { | |
| 202 | + | fav.ShortcutKey = input.ShortcutKey | |
| 203 | + | } | |
| 202 | 204 | ||
| 203 | 205 | if err := uc.Update(ctx, fav); err != nil { | |
| 204 | 206 | return nil, err | |
| @@ -180,7 +180,7 @@ func (uc *ManageFavoritesUseCase) UpdateFavorite(ctx context.Context, input dto. | |||||
| 180 | if input.ID <= 0 { | 180 | if input.ID <= 0 { | ||
| 181 | return nil, fmt.Errorf("favorite ID is required") | 181 | return nil, fmt.Errorf("favorite ID is required") | ||
| 182 | } | 182 | } | ||
| 183 | - | if input.ShortcutKey != nil && (*input.ShortcutKey < 1 || *input.ShortcutKey > 9) { | 183 | + | if input.ShortcutKeySet && input.ShortcutKey != nil && (*input.ShortcutKey < 1 || *input.ShortcutKey > 9) { |
| 184 | return nil, fmt.Errorf("shortcut key must be 1-9, got %d", *input.ShortcutKey) | 184 | return nil, fmt.Errorf("shortcut key must be 1-9, got %d", *input.ShortcutKey) | ||
| 185 | } | 185 | } | ||
| 186 | 186 | ||||
| @@ -198,7 +198,9 @@ func (uc *ManageFavoritesUseCase) UpdateFavorite(ctx context.Context, input dto. | |||||
| 198 | } | 198 | } | ||
| 199 | fav.Title = title | 199 | fav.Title = title | ||
| 200 | fav.FaviconURL = strings.TrimSpace(input.FaviconURL) | 200 | fav.FaviconURL = strings.TrimSpace(input.FaviconURL) | ||
| 201 | - | fav.ShortcutKey = input.ShortcutKey | 201 | + | if input.ShortcutKeySet { |
| 202 | + | fav.ShortcutKey = input.ShortcutKey | |||
| 203 | + | } | |||
| 202 | 204 | ||||
| 203 | if err := uc.Update(ctx, fav); err != nil { | 205 | if err := uc.Update(ctx, fav); err != nil { | ||
| 204 | return nil, err | 206 | return nil, err | ||
M internal/application/usecase/manage_favorites_test.go +54 -0
| @@ -214,6 +214,60 @@ func TestManageFavoritesUseCase_Toggle_DeleteErrorReturnsError(t *testing.T) { | |||
| 214 | 214 | assert.Contains(t, err.Error(), "failed to remove favorite") | |
| 215 | 215 | } | |
| 216 | 216 | ||
| 217 | + | func TestManageFavoritesUseCase_UpdateFavoritePreservesShortcutWhenOmitted(t *testing.T) { | |
| 218 | + | ctx := testContext() | |
| 219 | + | shortcut := 3 | |
| 220 | + | existing := &entity.Favorite{ | |
| 221 | + | ID: 42, | |
| 222 | + | URL: "https://example.com", | |
| 223 | + | Title: "Example", | |
| 224 | + | ShortcutKey: &shortcut, | |
| 225 | + | } | |
| 226 | + | ||
| 227 | + | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 228 | + | tagRepo := repomocks.NewMockTagRepository(t) | |
| 229 | + | favoriteRepo.EXPECT().FindByID(mock.Anything, entity.FavoriteID(42)).Return(existing, nil) | |
| 230 | + | favoriteRepo.EXPECT().Save(mock.Anything, mock.MatchedBy(func(fav *entity.Favorite) bool { | |
| 231 | + | return fav.ID == 42 && fav.Title == "Updated" && fav.ShortcutKey != nil && *fav.ShortcutKey == 3 | |
| 232 | + | })).Return(nil) | |
| 233 | + | ||
| 234 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 235 | + | updated, err := uc.UpdateFavorite(ctx, dto.FavoriteUpdateInput{ | |
| 236 | + | ID: 42, | |
| 237 | + | Title: "Updated", | |
| 238 | + | }) | |
| 239 | + | require.NoError(t, err) | |
| 240 | + | require.NotNil(t, updated.ShortcutKey) | |
| 241 | + | assert.Equal(t, 3, *updated.ShortcutKey) | |
| 242 | + | } | |
| 243 | + | ||
| 244 | + | func TestManageFavoritesUseCase_UpdateFavoriteClearsShortcutWhenExplicit(t *testing.T) { | |
| 245 | + | ctx := testContext() | |
| 246 | + | shortcut := 3 | |
| 247 | + | existing := &entity.Favorite{ | |
| 248 | + | ID: 42, | |
| 249 | + | URL: "https://example.com", | |
| 250 | + | Title: "Example", | |
| 251 | + | ShortcutKey: &shortcut, | |
| 252 | + | } | |
| 253 | + | ||
| 254 | + | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |
| 255 | + | tagRepo := repomocks.NewMockTagRepository(t) | |
| 256 | + | favoriteRepo.EXPECT().FindByID(mock.Anything, entity.FavoriteID(42)).Return(existing, nil) | |
| 257 | + | favoriteRepo.EXPECT().Save(mock.Anything, mock.MatchedBy(func(fav *entity.Favorite) bool { | |
| 258 | + | return fav.ID == 42 && fav.Title == "Updated" && fav.ShortcutKey == nil | |
| 259 | + | })).Return(nil) | |
| 260 | + | ||
| 261 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |
| 262 | + | updated, err := uc.UpdateFavorite(ctx, dto.FavoriteUpdateInput{ | |
| 263 | + | ID: 42, | |
| 264 | + | Title: "Updated", | |
| 265 | + | ShortcutKeySet: true, | |
| 266 | + | }) | |
| 267 | + | require.NoError(t, err) | |
| 268 | + | assert.Nil(t, updated.ShortcutKey) | |
| 269 | + | } | |
| 270 | + | ||
| 217 | 271 | func TestManageFavoritesUseCase_FilterForOmnibox_RanksPrefixBeforeContains(t *testing.T) { | |
| 218 | 272 | ctx := testContext() | |
| 219 | 273 | ||
| @@ -214,6 +214,60 @@ func TestManageFavoritesUseCase_Toggle_DeleteErrorReturnsError(t *testing.T) { | |||||
| 214 | assert.Contains(t, err.Error(), "failed to remove favorite") | 214 | assert.Contains(t, err.Error(), "failed to remove favorite") | ||
| 215 | } | 215 | } | ||
| 216 | 216 | ||||
| 217 | + | func TestManageFavoritesUseCase_UpdateFavoritePreservesShortcutWhenOmitted(t *testing.T) { | |||
| 218 | + | ctx := testContext() | |||
| 219 | + | shortcut := 3 | |||
| 220 | + | existing := &entity.Favorite{ | |||
| 221 | + | ID: 42, | |||
| 222 | + | URL: "https://example.com", | |||
| 223 | + | Title: "Example", | |||
| 224 | + | ShortcutKey: &shortcut, | |||
| 225 | + | } | |||
| 226 | + | ||||
| 227 | + | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |||
| 228 | + | tagRepo := repomocks.NewMockTagRepository(t) | |||
| 229 | + | favoriteRepo.EXPECT().FindByID(mock.Anything, entity.FavoriteID(42)).Return(existing, nil) | |||
| 230 | + | favoriteRepo.EXPECT().Save(mock.Anything, mock.MatchedBy(func(fav *entity.Favorite) bool { | |||
| 231 | + | return fav.ID == 42 && fav.Title == "Updated" && fav.ShortcutKey != nil && *fav.ShortcutKey == 3 | |||
| 232 | + | })).Return(nil) | |||
| 233 | + | ||||
| 234 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |||
| 235 | + | updated, err := uc.UpdateFavorite(ctx, dto.FavoriteUpdateInput{ | |||
| 236 | + | ID: 42, | |||
| 237 | + | Title: "Updated", | |||
| 238 | + | }) | |||
| 239 | + | require.NoError(t, err) | |||
| 240 | + | require.NotNil(t, updated.ShortcutKey) | |||
| 241 | + | assert.Equal(t, 3, *updated.ShortcutKey) | |||
| 242 | + | } | |||
| 243 | + | ||||
| 244 | + | func TestManageFavoritesUseCase_UpdateFavoriteClearsShortcutWhenExplicit(t *testing.T) { | |||
| 245 | + | ctx := testContext() | |||
| 246 | + | shortcut := 3 | |||
| 247 | + | existing := &entity.Favorite{ | |||
| 248 | + | ID: 42, | |||
| 249 | + | URL: "https://example.com", | |||
| 250 | + | Title: "Example", | |||
| 251 | + | ShortcutKey: &shortcut, | |||
| 252 | + | } | |||
| 253 | + | ||||
| 254 | + | favoriteRepo := repomocks.NewMockFavoriteRepository(t) | |||
| 255 | + | tagRepo := repomocks.NewMockTagRepository(t) | |||
| 256 | + | favoriteRepo.EXPECT().FindByID(mock.Anything, entity.FavoriteID(42)).Return(existing, nil) | |||
| 257 | + | favoriteRepo.EXPECT().Save(mock.Anything, mock.MatchedBy(func(fav *entity.Favorite) bool { | |||
| 258 | + | return fav.ID == 42 && fav.Title == "Updated" && fav.ShortcutKey == nil | |||
| 259 | + | })).Return(nil) | |||
| 260 | + | ||||
| 261 | + | uc := usecase.NewManageFavoritesUseCase(favoriteRepo, tagRepo) | |||
| 262 | + | updated, err := uc.UpdateFavorite(ctx, dto.FavoriteUpdateInput{ | |||
| 263 | + | ID: 42, | |||
| 264 | + | Title: "Updated", | |||
| 265 | + | ShortcutKeySet: true, | |||
| 266 | + | }) | |||
| 267 | + | require.NoError(t, err) | |||
| 268 | + | assert.Nil(t, updated.ShortcutKey) | |||
| 269 | + | } | |||
| 270 | + | ||||
| 217 | func TestManageFavoritesUseCase_FilterForOmnibox_RanksPrefixBeforeContains(t *testing.T) { | 271 | func TestManageFavoritesUseCase_FilterForOmnibox_RanksPrefixBeforeContains(t *testing.T) { | ||
| 218 | ctx := testContext() | 272 | ctx := testContext() | ||
| 219 | 273 | ||||
M internal/infrastructure/handlers/homepage/favorites.go +10 -4
| @@ -116,6 +116,11 @@ func (h *FavoritesHandlers) HandleUpdate() port.WebUIMessageHandler { | |||
| 116 | 116 | if err := json.Unmarshal(payload, &req); err != nil { | |
| 117 | 117 | return NewErrorResponse("", err), nil | |
| 118 | 118 | } | |
| 119 | + | var fields map[string]json.RawMessage | |
| 120 | + | if err := json.Unmarshal(payload, &fields); err != nil { | |
| 121 | + | return NewErrorResponse("", err), nil | |
| 122 | + | } | |
| 123 | + | _, shortcutKeySet := fields["shortcut_key"] | |
| 119 | 124 | ||
| 120 | 125 | log.Debug(). | |
| 121 | 126 | Str("request_id", req.RequestID). | |
| @@ -127,10 +132,11 @@ func (h *FavoritesHandlers) HandleUpdate() port.WebUIMessageHandler { | |||
| 127 | 132 | } | |
| 128 | 133 | ||
| 129 | 134 | favorite, err := h.favoritesUC.UpdateFavorite(ctx, dto.FavoriteUpdateInput{ | |
| 130 | - | ID: entity.FavoriteID(req.ID), | |
| 131 | - | Title: req.Title, | |
| 132 | - | FaviconURL: req.FaviconURL, | |
| 133 | - | ShortcutKey: req.ShortcutKey, | |
| 135 | + | ID: entity.FavoriteID(req.ID), | |
| 136 | + | Title: req.Title, | |
| 137 | + | FaviconURL: req.FaviconURL, | |
| 138 | + | ShortcutKey: req.ShortcutKey, | |
| 139 | + | ShortcutKeySet: shortcutKeySet, | |
| 134 | 140 | }) | |
| 135 | 141 | if err != nil { | |
| 136 | 142 | return NewErrorResponse(req.RequestID, err), nil | |
| @@ -116,6 +116,11 @@ func (h *FavoritesHandlers) HandleUpdate() port.WebUIMessageHandler { | |||||
| 116 | if err := json.Unmarshal(payload, &req); err != nil { | 116 | if err := json.Unmarshal(payload, &req); err != nil { | ||
| 117 | return NewErrorResponse("", err), nil | 117 | return NewErrorResponse("", err), nil | ||
| 118 | } | 118 | } | ||
| 119 | + | var fields map[string]json.RawMessage | |||
| 120 | + | if err := json.Unmarshal(payload, &fields); err != nil { | |||
| 121 | + | return NewErrorResponse("", err), nil | |||
| 122 | + | } | |||
| 123 | + | _, shortcutKeySet := fields["shortcut_key"] | |||
| 119 | 124 | ||||
| 120 | log.Debug(). | 125 | log.Debug(). | ||
| 121 | Str("request_id", req.RequestID). | 126 | Str("request_id", req.RequestID). | ||
| @@ -127,10 +132,11 @@ func (h *FavoritesHandlers) HandleUpdate() port.WebUIMessageHandler { | |||||
| 127 | } | 132 | } | ||
| 128 | 133 | ||||
| 129 | favorite, err := h.favoritesUC.UpdateFavorite(ctx, dto.FavoriteUpdateInput{ | 134 | favorite, err := h.favoritesUC.UpdateFavorite(ctx, dto.FavoriteUpdateInput{ | ||
| 130 | - | ID: entity.FavoriteID(req.ID), | 135 | + | ID: entity.FavoriteID(req.ID), |
| 131 | - | Title: req.Title, | 136 | + | Title: req.Title, |
| 132 | - | FaviconURL: req.FaviconURL, | 137 | + | FaviconURL: req.FaviconURL, |
| 133 | - | ShortcutKey: req.ShortcutKey, | 138 | + | ShortcutKey: req.ShortcutKey, |
| 139 | + | ShortcutKeySet: shortcutKeySet, | |||
| 134 | }) | 140 | }) | ||
| 135 | if err != nil { | 141 | if err != nil { | ||
| 136 | return NewErrorResponse(req.RequestID, err), nil | 142 | return NewErrorResponse(req.RequestID, err), nil | ||
M internal/infrastructure/handlers/homepage/favorites_test.go +26 -4
| @@ -55,10 +55,11 @@ func TestHandleFavoriteUpdatePassesTagsOnlyPayload(t *testing.T) { | |||
| 55 | 55 | favorites := portmocks.NewMockHomepageFavorites(t) | |
| 56 | 56 | favorites.EXPECT(). | |
| 57 | 57 | UpdateFavorite(mock.Anything, dto.FavoriteUpdateInput{ | |
| 58 | - | ID: entity.FavoriteID(42), | |
| 59 | - | Title: "Updated", | |
| 60 | - | FaviconURL: "https://example.com/favicon.ico", | |
| 61 | - | ShortcutKey: &shortcut, | |
| 58 | + | ID: entity.FavoriteID(42), | |
| 59 | + | Title: "Updated", | |
| 60 | + | FaviconURL: "https://example.com/favicon.ico", | |
| 61 | + | ShortcutKey: &shortcut, | |
| 62 | + | ShortcutKeySet: true, | |
| 62 | 63 | }). | |
| 63 | 64 | Return(&entity.Favorite{ID: 42, URL: "https://example.com", Title: "Updated", ShortcutKey: &shortcut}, nil). | |
| 64 | 65 | Once() | |
| @@ -71,3 +72,24 @@ func TestHandleFavoriteUpdatePassesTagsOnlyPayload(t *testing.T) { | |||
| 71 | 72 | require.True(t, ok) | |
| 72 | 73 | require.True(t, resp.Success) | |
| 73 | 74 | } | |
| 75 | + | func TestHandleFavoriteUpdatePreservesShortcutWhenOmitted(t *testing.T) { | |
| 76 | + | t.Parallel() | |
| 77 | + | ||
| 78 | + | favorites := portmocks.NewMockHomepageFavorites(t) | |
| 79 | + | favorites.EXPECT(). | |
| 80 | + | UpdateFavorite(mock.Anything, dto.FavoriteUpdateInput{ | |
| 81 | + | ID: entity.FavoriteID(42), | |
| 82 | + | Title: "Updated", | |
| 83 | + | FaviconURL: "https://example.com/favicon.ico", | |
| 84 | + | }). | |
| 85 | + | Return(&entity.Favorite{ID: 42, URL: "https://example.com", Title: "Updated"}, nil). | |
| 86 | + | Once() | |
| 87 | + | ||
| 88 | + | handler := NewFavoritesHandlers(favorites).HandleUpdate() | |
| 89 | + | got, err := handler.Handle(context.Background(), port.WebViewID(0), json.RawMessage(`{"requestId":"req-4","id":42,"title":"Updated","favicon_url":"https://example.com/favicon.ico"}`)) | |
| 90 | + | require.NoError(t, err) | |
| 91 | + | ||
| 92 | + | resp, ok := got.(Response) | |
| 93 | + | require.True(t, ok) | |
| 94 | + | require.True(t, resp.Success) | |
| 95 | + | } | |
| @@ -55,10 +55,11 @@ func TestHandleFavoriteUpdatePassesTagsOnlyPayload(t *testing.T) { | |||||
| 55 | favorites := portmocks.NewMockHomepageFavorites(t) | 55 | favorites := portmocks.NewMockHomepageFavorites(t) | ||
| 56 | favorites.EXPECT(). | 56 | favorites.EXPECT(). | ||
| 57 | UpdateFavorite(mock.Anything, dto.FavoriteUpdateInput{ | 57 | UpdateFavorite(mock.Anything, dto.FavoriteUpdateInput{ | ||
| 58 | - | ID: entity.FavoriteID(42), | 58 | + | ID: entity.FavoriteID(42), |
| 59 | - | Title: "Updated", | 59 | + | Title: "Updated", |
| 60 | - | FaviconURL: "https://example.com/favicon.ico", | 60 | + | FaviconURL: "https://example.com/favicon.ico", |
| 61 | - | ShortcutKey: &shortcut, | 61 | + | ShortcutKey: &shortcut, |
| 62 | + | ShortcutKeySet: true, | |||
| 62 | }). | 63 | }). | ||
| 63 | Return(&entity.Favorite{ID: 42, URL: "https://example.com", Title: "Updated", ShortcutKey: &shortcut}, nil). | 64 | Return(&entity.Favorite{ID: 42, URL: "https://example.com", Title: "Updated", ShortcutKey: &shortcut}, nil). | ||
| 64 | Once() | 65 | Once() | ||
| @@ -71,3 +72,24 @@ func TestHandleFavoriteUpdatePassesTagsOnlyPayload(t *testing.T) { | |||||
| 71 | require.True(t, ok) | 72 | require.True(t, ok) | ||
| 72 | require.True(t, resp.Success) | 73 | require.True(t, resp.Success) | ||
| 73 | } | 74 | } | ||
| 75 | + | func TestHandleFavoriteUpdatePreservesShortcutWhenOmitted(t *testing.T) { | |||
| 76 | + | t.Parallel() | |||
| 77 | + | ||||
| 78 | + | favorites := portmocks.NewMockHomepageFavorites(t) | |||
| 79 | + | favorites.EXPECT(). | |||
| 80 | + | UpdateFavorite(mock.Anything, dto.FavoriteUpdateInput{ | |||
| 81 | + | ID: entity.FavoriteID(42), | |||
| 82 | + | Title: "Updated", | |||
| 83 | + | FaviconURL: "https://example.com/favicon.ico", | |||
| 84 | + | }). | |||
| 85 | + | Return(&entity.Favorite{ID: 42, URL: "https://example.com", Title: "Updated"}, nil). | |||
| 86 | + | Once() | |||
| 87 | + | ||||
| 88 | + | handler := NewFavoritesHandlers(favorites).HandleUpdate() | |||
| 89 | + | got, err := handler.Handle(context.Background(), port.WebViewID(0), json.RawMessage(`{"requestId":"req-4","id":42,"title":"Updated","favicon_url":"https://example.com/favicon.ico"}`)) | |||
| 90 | + | require.NoError(t, err) | |||
| 91 | + | ||||
| 92 | + | resp, ok := got.(Response) | |||
| 93 | + | require.True(t, ok) | |||
| 94 | + | require.True(t, resp.Success) | |||
| 95 | + | } | |||
M internal/infrastructure/systemviewsbridge/client.go +9 -12
| @@ -283,19 +283,16 @@ func favoriteCreatePayload(input dto.FavoriteCreateInput) any { | |||
| 283 | 283 | } | |
| 284 | 284 | ||
| 285 | 285 | func favoriteUpdatePayload(input dto.FavoriteUpdateInput) any { | |
| 286 | - | return struct { | |
| 287 | - | RequestID string `json:"requestId"` | |
| 288 | - | ID int64 `json:"id"` | |
| 289 | - | Title string `json:"title"` | |
| 290 | - | FaviconURL string `json:"favicon_url"` | |
| 291 | - | ShortcutKey *int `json:"shortcut_key"` | |
| 292 | - | }{ | |
| 293 | - | RequestID: nextRequestID(), | |
| 294 | - | ID: int64(input.ID), | |
| 295 | - | Title: input.Title, | |
| 296 | - | FaviconURL: input.FaviconURL, | |
| 297 | - | ShortcutKey: input.ShortcutKey, | |
| 286 | + | payload := map[string]any{ | |
| 287 | + | "requestId": nextRequestID(), | |
| 288 | + | "id": int64(input.ID), | |
| 289 | + | "title": input.Title, | |
| 290 | + | "favicon_url": input.FaviconURL, | |
| 291 | + | } | |
| 292 | + | if input.ShortcutKeySet { | |
| 293 | + | payload["shortcut_key"] = input.ShortcutKey | |
| 298 | 294 | } | |
| 295 | + | return payload | |
| 299 | 296 | } | |
| 300 | 297 | ||
| 301 | 298 | func tagIDPayloads(ids []entity.TagID) []int64 { | |
| @@ -283,19 +283,16 @@ func favoriteCreatePayload(input dto.FavoriteCreateInput) any { | |||||
| 283 | } | 283 | } | ||
| 284 | 284 | ||||
| 285 | func favoriteUpdatePayload(input dto.FavoriteUpdateInput) any { | 285 | func favoriteUpdatePayload(input dto.FavoriteUpdateInput) any { | ||
| 286 | - | return struct { | 286 | + | payload := map[string]any{ |
| 287 | - | RequestID string `json:"requestId"` | 287 | + | "requestId": nextRequestID(), |
| 288 | - | ID int64 `json:"id"` | 288 | + | "id": int64(input.ID), |
| 289 | - | Title string `json:"title"` | 289 | + | "title": input.Title, |
| 290 | - | FaviconURL string `json:"favicon_url"` | 290 | + | "favicon_url": input.FaviconURL, |
| 291 | - | ShortcutKey *int `json:"shortcut_key"` | 291 | + | } |
| 292 | - | }{ | 292 | + | if input.ShortcutKeySet { |
| 293 | - | RequestID: nextRequestID(), | 293 | + | payload["shortcut_key"] = input.ShortcutKey |
| 294 | - | ID: int64(input.ID), | |||
| 295 | - | Title: input.Title, | |||
| 296 | - | FaviconURL: input.FaviconURL, | |||
| 297 | - | ShortcutKey: input.ShortcutKey, | |||
| 298 | } | 294 | } | ||
| 295 | + | return payload | |||
| 299 | } | 296 | } | ||
| 300 | 297 | ||||
| 301 | func tagIDPayloads(ids []entity.TagID) []int64 { | 298 | func tagIDPayloads(ids []entity.TagID) []int64 { | ||
M internal/ui/systemviews/app_test.go +1 -0
| @@ -597,6 +597,7 @@ func TestAppHandleFavoriteActionsRefreshesDOM(t *testing.T) { | |||
| 597 | 597 | })) | |
| 598 | 598 | assert.Equal(t, entity.FavoriteID(42), favorites.updatedFavorite.ID) | |
| 599 | 599 | assert.Equal(t, "Updated", favorites.updatedFavorite.Title) | |
| 600 | + | assert.True(t, favorites.updatedFavorite.ShortcutKeySet) | |
| 600 | 601 | require.NotNil(t, favorites.updatedFavorite.ShortcutKey) | |
| 601 | 602 | assert.Equal(t, 4, *favorites.updatedFavorite.ShortcutKey) | |
| 602 | 603 | assert.Contains(t, dom.HTML(), "Saved favorite Updated") | |
| @@ -597,6 +597,7 @@ func TestAppHandleFavoriteActionsRefreshesDOM(t *testing.T) { | |||||
| 597 | })) | 597 | })) | ||
| 598 | assert.Equal(t, entity.FavoriteID(42), favorites.updatedFavorite.ID) | 598 | assert.Equal(t, entity.FavoriteID(42), favorites.updatedFavorite.ID) | ||
| 599 | assert.Equal(t, "Updated", favorites.updatedFavorite.Title) | 599 | assert.Equal(t, "Updated", favorites.updatedFavorite.Title) | ||
| 600 | + | assert.True(t, favorites.updatedFavorite.ShortcutKeySet) | |||
| 600 | require.NotNil(t, favorites.updatedFavorite.ShortcutKey) | 601 | require.NotNil(t, favorites.updatedFavorite.ShortcutKey) | ||
| 601 | assert.Equal(t, 4, *favorites.updatedFavorite.ShortcutKey) | 602 | assert.Equal(t, 4, *favorites.updatedFavorite.ShortcutKey) | ||
| 602 | assert.Contains(t, dom.HTML(), "Saved favorite Updated") | 603 | assert.Contains(t, dom.HTML(), "Saved favorite Updated") | ||
M internal/ui/systemviews/favorite_actions.go +5 -4
| @@ -60,10 +60,11 @@ func (a *App) handleFavoriteAction(ctx context.Context, event DOMAction) error { | |||
| 60 | 60 | return err | |
| 61 | 61 | } | |
| 62 | 62 | favorite, err := a.deps.Favorites.UpdateFavorite(ctx, dto.FavoriteUpdateInput{ | |
| 63 | - | ID: entity.FavoriteID(id), | |
| 64 | - | Title: strings.TrimSpace(data["title"]), | |
| 65 | - | FaviconURL: strings.TrimSpace(data["favicon_url"]), | |
| 66 | - | ShortcutKey: shortcut, | |
| 63 | + | ID: entity.FavoriteID(id), | |
| 64 | + | Title: strings.TrimSpace(data["title"]), | |
| 65 | + | FaviconURL: strings.TrimSpace(data["favicon_url"]), | |
| 66 | + | ShortcutKey: shortcut, | |
| 67 | + | ShortcutKeySet: true, | |
| 67 | 68 | }) | |
| 68 | 69 | if err != nil { | |
| 69 | 70 | return err | |
| @@ -60,10 +60,11 @@ func (a *App) handleFavoriteAction(ctx context.Context, event DOMAction) error { | |||||
| 60 | return err | 60 | return err | ||
| 61 | } | 61 | } | ||
| 62 | favorite, err := a.deps.Favorites.UpdateFavorite(ctx, dto.FavoriteUpdateInput{ | 62 | favorite, err := a.deps.Favorites.UpdateFavorite(ctx, dto.FavoriteUpdateInput{ | ||
| 63 | - | ID: entity.FavoriteID(id), | 63 | + | ID: entity.FavoriteID(id), |
| 64 | - | Title: strings.TrimSpace(data["title"]), | 64 | + | Title: strings.TrimSpace(data["title"]), |
| 65 | - | FaviconURL: strings.TrimSpace(data["favicon_url"]), | 65 | + | FaviconURL: strings.TrimSpace(data["favicon_url"]), |
| 66 | - | ShortcutKey: shortcut, | 66 | + | ShortcutKey: shortcut, |
| 67 | + | ShortcutKeySet: true, | |||
| 67 | }) | 68 | }) | ||
| 68 | if err != nil { | 69 | if err != nil { | ||
| 69 | return err | 70 | return err | ||