cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists ๐Ÿƒ
charm leaflet readability golang

fix: hanging tests

+96 -19
+12 -6
cmd/commands.go
··· 108 108 }, 109 109 }) 110 110 111 - root.AddCommand(&cobra.Command{ 111 + projectsCmd := &cobra.Command{ 112 112 Use: "projects", 113 113 Short: "List projects", 114 114 Aliases: []string{"proj"}, 115 115 RunE: func(cmd *cobra.Command, args []string) error { 116 - return handlers.ListProjects(cmd.Context(), args) 116 + static, _ := cmd.Flags().GetBool("static") 117 + return handlers.ListProjects(cmd.Context(), static) 117 118 }, 118 - }) 119 + } 120 + projectsCmd.Flags().Bool("static", false, "Use static text output instead of interactive") 121 + root.AddCommand(projectsCmd) 119 122 120 - root.AddCommand(&cobra.Command{ 123 + tagsCmd := &cobra.Command{ 121 124 Use: "tags", 122 125 Short: "List tags", 123 126 Aliases: []string{"t"}, 124 127 RunE: func(cmd *cobra.Command, args []string) error { 125 - return handlers.ListTags(cmd.Context(), args) 128 + static, _ := cmd.Flags().GetBool("static") 129 + return handlers.ListTags(cmd.Context(), static) 126 130 }, 127 - }) 131 + } 132 + tagsCmd.Flags().Bool("static", false, "Use static text output instead of interactive") 133 + root.AddCommand(tagsCmd) 128 134 129 135 root.AddCommand(&cobra.Command{ 130 136 Use: "contexts",
+80 -7
internal/handlers/tasks.go
··· 103 103 Project: project, 104 104 } 105 105 106 - // Default to showing pending tasks only unless --all is specified 107 106 if !showAll && opts.Status == "" { 108 107 opts.Status = "pending" 109 108 } ··· 335 334 } 336 335 337 336 // ListProjects lists all projects with their task counts 338 - func ListProjects(ctx context.Context, args []string) error { 337 + func ListProjects(ctx context.Context, static bool) error { 339 338 handler, err := NewTaskHandler() 340 339 if err != nil { 341 340 return fmt.Errorf("failed to initialize task handler: %w", err) 342 341 } 343 342 defer handler.Close() 344 343 345 - return handler.listProjects(ctx) 344 + if static { 345 + return handler.listProjectsStatic(ctx) 346 + } 347 + 348 + return handler.listProjectsInteractive(ctx) 349 + } 350 + 351 + func (h *TaskHandler) listProjectsStatic(ctx context.Context) error { 352 + tasks, err := h.repos.Tasks.List(ctx, repo.TaskListOptions{}) 353 + if err != nil { 354 + return fmt.Errorf("failed to list tasks for projects: %w", err) 355 + } 356 + 357 + projectCounts := make(map[string]int) 358 + for _, task := range tasks { 359 + if task.Project != "" { 360 + projectCounts[task.Project]++ 361 + } 362 + } 363 + 364 + if len(projectCounts) == 0 { 365 + fmt.Printf("No projects found\n") 366 + return nil 367 + } 368 + 369 + projects := make([]string, 0, len(projectCounts)) 370 + for project := range projectCounts { 371 + projects = append(projects, project) 372 + } 373 + slices.Sort(projects) 374 + 375 + fmt.Printf("Found %d project(s):\n\n", len(projects)) 376 + for _, project := range projects { 377 + count := projectCounts[project] 378 + fmt.Printf("%s (%d task%s)\n", project, count, pluralize(count)) 379 + } 380 + 381 + return nil 346 382 } 347 383 348 - func (h *TaskHandler) listProjects(ctx context.Context) error { 384 + func (h *TaskHandler) listProjectsInteractive(ctx context.Context) error { 349 385 projectList := ui.NewProjectList(h.repos.Tasks, ui.ProjectListOptions{}) 350 386 return projectList.Browse(ctx) 351 387 } 352 388 353 389 // ListTags lists all tags with their task counts 354 - func ListTags(ctx context.Context, args []string) error { 390 + func ListTags(ctx context.Context, static bool) error { 355 391 handler, err := NewTaskHandler() 356 392 if err != nil { 357 393 return fmt.Errorf("failed to initialize task handler: %w", err) 358 394 } 359 395 defer handler.Close() 360 396 361 - return handler.listTags(ctx) 397 + if static { 398 + return handler.listTagsStatic(ctx) 399 + } 400 + 401 + return handler.listTagsInteractive(ctx) 402 + } 403 + 404 + func (h *TaskHandler) listTagsStatic(ctx context.Context) error { 405 + tasks, err := h.repos.Tasks.List(ctx, repo.TaskListOptions{}) 406 + if err != nil { 407 + return fmt.Errorf("failed to list tasks for tags: %w", err) 408 + } 409 + 410 + tagCounts := make(map[string]int) 411 + for _, task := range tasks { 412 + for _, tag := range task.Tags { 413 + tagCounts[tag]++ 414 + } 415 + } 416 + 417 + if len(tagCounts) == 0 { 418 + fmt.Printf("No tags found\n") 419 + return nil 420 + } 421 + 422 + tags := make([]string, 0, len(tagCounts)) 423 + for tag := range tagCounts { 424 + tags = append(tags, tag) 425 + } 426 + slices.Sort(tags) 427 + 428 + fmt.Printf("Found %d tag(s):\n\n", len(tags)) 429 + for _, tag := range tags { 430 + count := tagCounts[tag] 431 + fmt.Printf("%s (%d task%s)\n", tag, count, pluralize(count)) 432 + } 433 + 434 + return nil 362 435 } 363 436 364 - func (h *TaskHandler) listTags(ctx context.Context) error { 437 + func (h *TaskHandler) listTagsInteractive(ctx context.Context) error { 365 438 tagList := ui.NewTagList(h.repos.Tasks, ui.TagListOptions{}) 366 439 return tagList.Browse(ctx) 367 440 }
+4 -4
internal/handlers/tasks_test.go
··· 757 757 } 758 758 759 759 t.Run("lists projects successfully", func(t *testing.T) { 760 - err := ListProjects(ctx, []string{}) 760 + err := ListProjects(ctx, true) 761 761 if err != nil { 762 762 t.Errorf("ListProjects failed: %v", err) 763 763 } ··· 767 767 _, cleanup2 := setupTaskTest(t) 768 768 defer cleanup2() 769 769 770 - err := ListProjects(ctx, []string{}) 770 + err := ListProjects(ctx, true) 771 771 if err != nil { 772 772 t.Errorf("ListProjects with no projects failed: %v", err) 773 773 } ··· 801 801 } 802 802 803 803 t.Run("lists tags successfully", func(t *testing.T) { 804 - err := ListTags(ctx, []string{}) 804 + err := ListTags(ctx, true) 805 805 if err != nil { 806 806 t.Errorf("ListTags failed: %v", err) 807 807 } ··· 811 811 _, cleanup2 := setupTaskTest(t) 812 812 defer cleanup2() 813 813 814 - err := ListTags(ctx, []string{}) 814 + err := ListTags(ctx, true) 815 815 if err != nil { 816 816 t.Errorf("ListTags with no tags failed: %v", err) 817 817 }
-1
internal/ui/task_list_test.go
··· 227 227 if !strings.Contains(output, "Plan vacation itinerary") { 228 228 t.Error("Second task not displayed") 229 229 } 230 - // Should not show completed task by default 231 230 if strings.Contains(output, "Fix authentication bug") { 232 231 t.Error("Completed task should not be shown by default") 233 232 }
-1
internal/ui/ui_test.go
··· 310 310 withColor(t, func(r *lipgloss.Renderer) { 311 311 logo := Colossal 312 312 viewport := logo.ColoredInViewport(r) 313 - t.Logf("viewport output:\n%s", viewport) 314 313 315 314 if viewport == "" { 316 315 t.Fatal("viewport is empty")