Skip to content

Commit 2cde41b

Browse files
authored
Merge pull request #43 from robzolkos/add-card-move-command
Add card move command
2 parents 900f94d + f7b420a commit 2cde41b

3 files changed

Lines changed: 137 additions & 0 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,10 @@ fizzy card delete 42
210210
fizzy card close 42
211211
fizzy card reopen 42
212212

213+
# Move to a different board
214+
fizzy card move 42 --to BOARD_ID
215+
fizzy card move 42 -t BOARD_ID
216+
213217
# Move to "Not Now"
214218
fizzy card postpone 42
215219

internal/commands/card.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,51 @@ var cardPostponeCmd = &cobra.Command{
442442
},
443443
}
444444

445+
// Card move flags
446+
var cardMoveBoard string
447+
448+
var cardMoveCmd = &cobra.Command{
449+
Use: "move CARD_NUMBER",
450+
Short: "Move card to a different board",
451+
Long: "Moves a card to a different board.",
452+
Args: cobra.ExactArgs(1),
453+
Run: func(cmd *cobra.Command, args []string) {
454+
if err := requireAuthAndAccount(); err != nil {
455+
exitWithError(err)
456+
}
457+
458+
if cardMoveBoard == "" {
459+
exitWithError(newRequiredFlagError("to"))
460+
}
461+
462+
body := map[string]interface{}{
463+
"board_id": cardMoveBoard,
464+
}
465+
466+
client := getClient()
467+
_, err := client.Patch("/cards/"+args[0]+"/board.json", body)
468+
if err != nil {
469+
exitWithError(err)
470+
}
471+
472+
// Fetch the updated card to show confirmation with title
473+
resp, err := client.Get("/cards/" + args[0] + ".json")
474+
if err != nil {
475+
exitWithError(err)
476+
}
477+
478+
// Build summary with card title if available
479+
summary := fmt.Sprintf("Card #%s moved to board %s", args[0], cardMoveBoard)
480+
if card, ok := resp.Data.(map[string]interface{}); ok {
481+
if title, ok := card["title"].(string); ok {
482+
summary = fmt.Sprintf("Card #%s \"%s\" moved to board %s", args[0], title, cardMoveBoard)
483+
}
484+
}
485+
486+
printSuccessWithSummary(resp.Data, summary)
487+
},
488+
}
489+
445490
// Card column flags
446491
var cardColumnColumn string
447492

@@ -782,6 +827,10 @@ func init() {
782827
cardCmd.AddCommand(cardReopenCmd)
783828
cardCmd.AddCommand(cardPostponeCmd)
784829

830+
// Move to different board
831+
cardMoveCmd.Flags().StringVarP(&cardMoveBoard, "to", "t", "", "Target board ID (required)")
832+
cardCmd.AddCommand(cardMoveCmd)
833+
785834
// Column
786835
cardColumnCmd.Flags().StringVar(&cardColumnColumn, "column", "", "Column ID (required)")
787836
cardCmd.AddCommand(cardColumnCmd)

internal/commands/card_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,3 +1184,87 @@ func TestCardUngolden(t *testing.T) {
11841184
}
11851185
})
11861186
}
1187+
1188+
func TestCardMove(t *testing.T) {
1189+
t.Run("moves card to different board", func(t *testing.T) {
1190+
mock := NewMockClient()
1191+
mock.PatchResponse = &client.APIResponse{
1192+
StatusCode: 204,
1193+
Data: nil,
1194+
}
1195+
mock.GetResponse = &client.APIResponse{
1196+
StatusCode: 200,
1197+
Data: map[string]interface{}{
1198+
"id": "abc",
1199+
"number": float64(42),
1200+
"title": "Test Card",
1201+
"board_id": "board-456",
1202+
},
1203+
}
1204+
1205+
result := SetTestMode(mock)
1206+
SetTestConfig("token", "account", "https://api.example.com")
1207+
defer ResetTestMode()
1208+
1209+
cardMoveBoard = "board-456"
1210+
RunTestCommand(func() {
1211+
cardMoveCmd.Run(cardMoveCmd, []string{"42"})
1212+
})
1213+
cardMoveBoard = ""
1214+
1215+
if result.ExitCode != 0 {
1216+
t.Errorf("expected exit code 0, got %d", result.ExitCode)
1217+
}
1218+
if len(mock.PatchCalls) != 1 {
1219+
t.Errorf("expected 1 patch call, got %d", len(mock.PatchCalls))
1220+
}
1221+
if mock.PatchCalls[0].Path != "/cards/42/board.json" {
1222+
t.Errorf("expected path '/cards/42/board.json', got '%s'", mock.PatchCalls[0].Path)
1223+
}
1224+
1225+
body := mock.PatchCalls[0].Body.(map[string]interface{})
1226+
if body["board_id"] != "board-456" {
1227+
t.Errorf("expected board_id 'board-456', got '%v'", body["board_id"])
1228+
}
1229+
1230+
// Verify it fetched the card after moving
1231+
if len(mock.GetCalls) != 1 || mock.GetCalls[0].Path != "/cards/42.json" {
1232+
t.Errorf("expected get call to '/cards/42.json', got %+v", mock.GetCalls)
1233+
}
1234+
})
1235+
1236+
t.Run("requires --to flag", func(t *testing.T) {
1237+
mock := NewMockClient()
1238+
result := SetTestMode(mock)
1239+
SetTestConfig("token", "account", "https://api.example.com")
1240+
defer ResetTestMode()
1241+
1242+
cardMoveBoard = ""
1243+
RunTestCommand(func() {
1244+
cardMoveCmd.Run(cardMoveCmd, []string{"42"})
1245+
})
1246+
1247+
if result.ExitCode != errors.ExitInvalidArgs {
1248+
t.Errorf("expected exit code %d, got %d", errors.ExitInvalidArgs, result.ExitCode)
1249+
}
1250+
})
1251+
1252+
t.Run("handles not found error", func(t *testing.T) {
1253+
mock := NewMockClient()
1254+
mock.PatchError = errors.NewNotFoundError("Card not found")
1255+
1256+
result := SetTestMode(mock)
1257+
SetTestConfig("token", "account", "https://api.example.com")
1258+
defer ResetTestMode()
1259+
1260+
cardMoveBoard = "board-456"
1261+
RunTestCommand(func() {
1262+
cardMoveCmd.Run(cardMoveCmd, []string{"999"})
1263+
})
1264+
cardMoveBoard = ""
1265+
1266+
if result.ExitCode != errors.ExitNotFound {
1267+
t.Errorf("expected exit code %d, got %d", errors.ExitNotFound, result.ExitCode)
1268+
}
1269+
})
1270+
}

0 commit comments

Comments
 (0)