Skip to content

Commit ef32358

Browse files
authored
Merge pull request #61 from robzolkos/add-notification-tray
Add notification tray command
2 parents 4b12d6f + 1d6d8e8 commit ef32358

5 files changed

Lines changed: 159 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,8 @@ fizzy search "bug" --sort newest # Sort by created_at desc
453453

454454
```bash
455455
fizzy notification list
456+
fizzy notification tray # Unread notifications (up to 100)
457+
fizzy notification tray --include-read # Include read notifications
456458
fizzy notification read NOTIFICATION_ID
457459
fizzy notification unread NOTIFICATION_ID
458460
fizzy notification read-all

e2e/tests/notification_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,52 @@ func TestNotificationReadUnread(t *testing.T) {
108108
})
109109
}
110110

111+
func TestNotificationTray(t *testing.T) {
112+
h := harness.New(t)
113+
114+
t.Run("returns notification tray", func(t *testing.T) {
115+
result := h.Run("notification", "tray")
116+
117+
if result.ExitCode != harness.ExitSuccess {
118+
t.Errorf("expected exit code %d, got %d\nstderr: %s", harness.ExitSuccess, result.ExitCode, result.Stderr)
119+
}
120+
121+
if result.Response == nil {
122+
t.Fatalf("expected JSON response, got nil\nstdout: %s", result.Stdout)
123+
}
124+
125+
if !result.Response.Success {
126+
t.Error("expected success=true")
127+
}
128+
129+
arr := result.GetDataArray()
130+
if arr == nil {
131+
t.Error("expected data to be an array")
132+
}
133+
})
134+
135+
t.Run("supports --include-read flag", func(t *testing.T) {
136+
result := h.Run("notification", "tray", "--include-read")
137+
138+
if result.ExitCode != harness.ExitSuccess {
139+
t.Errorf("expected exit code %d, got %d\nstderr: %s", harness.ExitSuccess, result.ExitCode, result.Stderr)
140+
}
141+
142+
if result.Response == nil {
143+
t.Fatalf("expected JSON response, got nil\nstdout: %s", result.Stdout)
144+
}
145+
146+
if !result.Response.Success {
147+
t.Error("expected success=true")
148+
}
149+
150+
arr := result.GetDataArray()
151+
if arr == nil {
152+
t.Error("expected data to be an array")
153+
}
154+
})
155+
}
156+
111157
func TestNotificationReadAll(t *testing.T) {
112158
h := harness.New(t)
113159

internal/commands/notification.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,55 @@ var notificationReadAllCmd = &cobra.Command{
160160
},
161161
}
162162

163+
// Notification tray flags
164+
var notificationTrayIncludeRead bool
165+
166+
var notificationTrayCmd = &cobra.Command{
167+
Use: "tray",
168+
Short: "Show notification tray",
169+
Long: "Shows your notification tray (up to 100 unread notifications). Use --include-read to also include read notifications.",
170+
Run: func(cmd *cobra.Command, args []string) {
171+
if err := requireAuthAndAccount(); err != nil {
172+
exitWithError(err)
173+
}
174+
175+
client := getClient()
176+
path := "/notifications/tray.json"
177+
if notificationTrayIncludeRead {
178+
path += "?include_read=true"
179+
}
180+
181+
resp, err := client.Get(path)
182+
if err != nil {
183+
exitWithError(err)
184+
}
185+
186+
// Build summary
187+
count := 0
188+
unreadCount := 0
189+
if arr, ok := resp.Data.([]interface{}); ok {
190+
count = len(arr)
191+
for _, item := range arr {
192+
if notif, ok := item.(map[string]interface{}); ok {
193+
if read, ok := notif["read"].(bool); ok && !read {
194+
unreadCount++
195+
}
196+
}
197+
}
198+
}
199+
summary := fmt.Sprintf("%d notifications (%d unread)", count, unreadCount)
200+
201+
// Build breadcrumbs
202+
breadcrumbs := []response.Breadcrumb{
203+
breadcrumb("read", "fizzy notification read <id>", "Mark as read"),
204+
breadcrumb("read-all", "fizzy notification read-all", "Mark all as read"),
205+
breadcrumb("list", "fizzy notification list", "List all notifications"),
206+
}
207+
208+
printSuccessWithBreadcrumbs(resp.Data, summary, breadcrumbs)
209+
},
210+
}
211+
163212
func init() {
164213
rootCmd.AddCommand(notificationCmd)
165214

@@ -168,6 +217,10 @@ func init() {
168217
notificationListCmd.Flags().BoolVar(&notificationListAll, "all", false, "Fetch all pages")
169218
notificationCmd.AddCommand(notificationListCmd)
170219

220+
// Tray
221+
notificationTrayCmd.Flags().BoolVar(&notificationTrayIncludeRead, "include-read", false, "Include read notifications")
222+
notificationCmd.AddCommand(notificationTrayCmd)
223+
171224
// Read/Unread
172225
notificationCmd.AddCommand(notificationReadCmd)
173226
notificationCmd.AddCommand(notificationUnreadCmd)

internal/commands/notification_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,62 @@ func TestNotificationUnread(t *testing.T) {
8383
})
8484
}
8585

86+
func TestNotificationTray(t *testing.T) {
87+
t.Run("returns notification tray", func(t *testing.T) {
88+
mock := NewMockClient()
89+
mock.GetResponse = &client.APIResponse{
90+
StatusCode: 200,
91+
Data: []interface{}{
92+
map[string]interface{}{"id": "1", "read": false},
93+
map[string]interface{}{"id": "2", "read": false},
94+
},
95+
}
96+
97+
result := SetTestMode(mock)
98+
SetTestConfig("token", "account", "https://api.example.com")
99+
defer ResetTestMode()
100+
101+
RunTestCommand(func() {
102+
notificationTrayCmd.Run(notificationTrayCmd, []string{})
103+
})
104+
105+
if result.ExitCode != 0 {
106+
t.Errorf("expected exit code 0, got %d", result.ExitCode)
107+
}
108+
if mock.GetCalls[0].Path != "/notifications/tray.json" {
109+
t.Errorf("expected path '/notifications/tray.json', got '%s'", mock.GetCalls[0].Path)
110+
}
111+
})
112+
113+
t.Run("includes read notifications with flag", func(t *testing.T) {
114+
mock := NewMockClient()
115+
mock.GetResponse = &client.APIResponse{
116+
StatusCode: 200,
117+
Data: []interface{}{
118+
map[string]interface{}{"id": "1", "read": false},
119+
map[string]interface{}{"id": "2", "read": true},
120+
},
121+
}
122+
123+
result := SetTestMode(mock)
124+
SetTestConfig("token", "account", "https://api.example.com")
125+
defer ResetTestMode()
126+
127+
notificationTrayIncludeRead = true
128+
RunTestCommand(func() {
129+
notificationTrayCmd.Run(notificationTrayCmd, []string{})
130+
})
131+
notificationTrayIncludeRead = false
132+
133+
if result.ExitCode != 0 {
134+
t.Errorf("expected exit code 0, got %d", result.ExitCode)
135+
}
136+
if mock.GetCalls[0].Path != "/notifications/tray.json?include_read=true" {
137+
t.Errorf("expected path '/notifications/tray.json?include_read=true', got '%s'", mock.GetCalls[0].Path)
138+
}
139+
})
140+
}
141+
86142
func TestNotificationReadAll(t *testing.T) {
87143
t.Run("marks all notifications as read", func(t *testing.T) {
88144
mock := NewMockClient()

skills/fizzy/SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,8 @@ fizzy pin list # List your pinned cards (up to 1
644644

645645
```bash
646646
fizzy notification list [--page N] [--all]
647+
fizzy notification tray # Unread notifications (up to 100)
648+
fizzy notification tray --include-read # Include read notifications
647649
fizzy notification read NOTIFICATION_ID
648650
fizzy notification read-all
649651
fizzy notification unread NOTIFICATION_ID

0 commit comments

Comments
 (0)