diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-23 06:25:28 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-23 06:25:28 +0300 |
| commit | 03cff75ae50399c9f43abde178f43e281db6caad (patch) | |
| tree | 6837143d6e61303b4603396e1073b545e80d4926 | |
| parent | 5c591b4b3a070057b31a8b0ff2664e651b8d5808 (diff) | |
Refactor UI Taskwarrior dependency for vq0
| -rw-r--r-- | internal/task/taskwarrior.go | 159 | ||||
| -rw-r--r-- | internal/ui/editor_handlers.go | 4 | ||||
| -rw-r--r-- | internal/ui/handlers.go | 22 | ||||
| -rw-r--r-- | internal/ui/keyactions.go | 28 | ||||
| -rw-r--r-- | internal/ui/shell.go | 8 | ||||
| -rw-r--r-- | internal/ui/table.go | 36 | ||||
| -rw-r--r-- | internal/ui/table_test.go | 64 | ||||
| -rw-r--r-- | internal/ui/ultra.go | 2 |
8 files changed, 279 insertions, 44 deletions
diff --git a/internal/task/taskwarrior.go b/internal/task/taskwarrior.go new file mode 100644 index 0000000..10b31b6 --- /dev/null +++ b/internal/task/taskwarrior.go @@ -0,0 +1,159 @@ +package task + +import ( + "context" + "os/exec" + "time" +) + +// Taskwarrior describes the Taskwarrior operations required by the UI. +type Taskwarrior interface { + Export(ctx context.Context, filters ...string) ([]Task, error) + SortTasks(tasks []Task) + TotalTasks(tasks []Task) int + InProgressTasks(tasks []Task) int + DueTasks(tasks []Task, now time.Time) int + EditCmd(id int) *exec.Cmd + RunShellLine(ctx context.Context, line string) (RunResult, error) + LoadCompletionSources(ctx context.Context) CompletionSources + AddLineContext(ctx context.Context, line string) error + AnnotateContext(ctx context.Context, id int, text string) error + ReplaceAnnotations(ctx context.Context, id int, text string) error + SetDescriptionContext(ctx context.Context, id int, desc string) error + AddTagsContext(ctx context.Context, id int, tags []string) error + RemoveTagsContext(ctx context.Context, id int, tags []string) error + SetDueDateContext(ctx context.Context, id int, due string) error + SetRecurrenceContext(ctx context.Context, id int, rec string) error + SetProjectContext(ctx context.Context, id int, project string) error + SetPriorityContext(ctx context.Context, id int, priority string) error + StartContext(ctx context.Context, id int) error + StopContext(ctx context.Context, id int) error + DoneContext(ctx context.Context, id int) error + SetStatusUUIDContext(ctx context.Context, uuid, status string) error + RecurringSeries(ctx context.Context, rootUUID string) ([]Task, error) +} + +// Client is the production Taskwarrior implementation backed by the task CLI. +type Client struct{} + +var _ Taskwarrior = Client{} + +// NewTaskwarrior returns a production Taskwarrior Client. +func NewTaskwarrior() Client { + return Client{} +} + +// Export retrieves tasks using Taskwarrior export. +func (Client) Export(ctx context.Context, filters ...string) ([]Task, error) { + return Export(ctx, filters...) +} + +// SortTasks orders tasks using TaskSamurai's default task ordering. +func (Client) SortTasks(tasks []Task) { + SortTasks(tasks) +} + +// TotalTasks returns the number of tasks provided. +func (Client) TotalTasks(tasks []Task) int { + return TotalTasks(tasks) +} + +// InProgressTasks returns the number of started, incomplete tasks. +func (Client) InProgressTasks(tasks []Task) int { + return InProgressTasks(tasks) +} + +// DueTasks returns the number of due tasks. +func (Client) DueTasks(tasks []Task, now time.Time) int { + return DueTasks(tasks, now) +} + +// EditCmd returns an editor command for a task. +func (Client) EditCmd(id int) *exec.Cmd { + return EditCmd(id) +} + +// RunShellLine runs a user-entered Taskwarrior shell command. +func (Client) RunShellLine(ctx context.Context, line string) (RunResult, error) { + return RunShellLine(ctx, line) +} + +// LoadCompletionSources returns Taskwarrior-provided completion candidates. +func (Client) LoadCompletionSources(ctx context.Context) CompletionSources { + return LoadCompletionSources(ctx) +} + +// AddLineContext adds a task from a shell-style input line. +func (Client) AddLineContext(ctx context.Context, line string) error { + return AddLineContext(ctx, line) +} + +// AnnotateContext adds an annotation to a task. +func (Client) AnnotateContext(ctx context.Context, id int, text string) error { + return AnnotateContext(ctx, id, text) +} + +// ReplaceAnnotations replaces all annotations on a task. +func (Client) ReplaceAnnotations(ctx context.Context, id int, text string) error { + return ReplaceAnnotations(ctx, id, text) +} + +// SetDescriptionContext changes a task description. +func (Client) SetDescriptionContext(ctx context.Context, id int, desc string) error { + return SetDescriptionContext(ctx, id, desc) +} + +// AddTagsContext adds tags to a task. +func (Client) AddTagsContext(ctx context.Context, id int, tags []string) error { + return AddTagsContext(ctx, id, tags) +} + +// RemoveTagsContext removes tags from a task. +func (Client) RemoveTagsContext(ctx context.Context, id int, tags []string) error { + return RemoveTagsContext(ctx, id, tags) +} + +// SetDueDateContext changes a task due date. +func (Client) SetDueDateContext(ctx context.Context, id int, due string) error { + return SetDueDateContext(ctx, id, due) +} + +// SetRecurrenceContext changes a task recurrence value. +func (Client) SetRecurrenceContext(ctx context.Context, id int, rec string) error { + return SetRecurrenceContext(ctx, id, rec) +} + +// SetProjectContext changes a task project. +func (Client) SetProjectContext(ctx context.Context, id int, project string) error { + return SetProjectContext(ctx, id, project) +} + +// SetPriorityContext changes a task priority. +func (Client) SetPriorityContext(ctx context.Context, id int, priority string) error { + return SetPriorityContext(ctx, id, priority) +} + +// StartContext starts a task. +func (Client) StartContext(ctx context.Context, id int) error { + return StartContext(ctx, id) +} + +// StopContext stops a task. +func (Client) StopContext(ctx context.Context, id int) error { + return StopContext(ctx, id) +} + +// DoneContext completes a task. +func (Client) DoneContext(ctx context.Context, id int) error { + return DoneContext(ctx, id) +} + +// SetStatusUUIDContext changes a task status by UUID. +func (Client) SetStatusUUIDContext(ctx context.Context, uuid, status string) error { + return SetStatusUUIDContext(ctx, uuid, status) +} + +// RecurringSeries returns a recurring task series. +func (Client) RecurringSeries(ctx context.Context, rootUUID string) ([]Task, error) { + return RecurringSeries(ctx, rootUUID) +} diff --git a/internal/ui/editor_handlers.go b/internal/ui/editor_handlers.go index 2db20d0..857e3c7 100644 --- a/internal/ui/editor_handlers.go +++ b/internal/ui/editor_handlers.go @@ -7,8 +7,6 @@ import ( "time" tea "charm.land/bubbletea/v2" - - "codeberg.org/snonux/tasksamurai/internal/task" ) // handleEditDone handles completion of external editor @@ -57,7 +55,7 @@ func (m *Model) handleDescEditDone(msg descEditDoneMsg) (tea.Model, tea.Cmd) { newDesc := strings.TrimSpace(string(content)) if m.currentTaskDetail != nil { ctx, cancel := m.taskOperationContext() - err = task.SetDescriptionContext(ctx, m.currentTaskDetail.ID, newDesc) + err = m.taskwarriorClient().SetDescriptionContext(ctx, m.currentTaskDetail.ID, newDesc) cancel() if err != nil { m.statusMsg = fmt.Sprintf("Error updating description: %v", err) diff --git a/internal/ui/handlers.go b/internal/ui/handlers.go index e556570..cc11f89 100644 --- a/internal/ui/handlers.go +++ b/internal/ui/handlers.go @@ -7,8 +7,6 @@ import ( "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" - - "codeberg.org/snonux/tasksamurai/internal/task" ) // handleTextInput provides generic text input handling for all input modes @@ -49,14 +47,14 @@ func (m *Model) handleAnnotationMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { if m.replaceAnnotations { ctx, cancel := m.taskOperationContext() defer cancel() - if err := task.ReplaceAnnotations(ctx, m.annotateID, value); err != nil { + if err := m.taskwarriorClient().ReplaceAnnotations(ctx, m.annotateID, value); err != nil { return err } m.replaceAnnotations = false } else { ctx, cancel := m.taskOperationContext() defer cancel() - if err := task.AnnotateContext(ctx, m.annotateID, value); err != nil { + if err := m.taskwarriorClient().AnnotateContext(ctx, m.annotateID, value); err != nil { return err } } @@ -87,7 +85,7 @@ func (m *Model) handleDescriptionMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) } ctx, cancel := m.taskOperationContext() defer cancel() - if err := task.SetDescriptionContext(ctx, m.descID, value); err != nil { + if err := m.taskwarriorClient().SetDescriptionContext(ctx, m.descID, value); err != nil { return err } if err := m.reload(); err != nil { @@ -135,12 +133,12 @@ func (m *Model) handleTagsMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { ctx, cancel := m.taskOperationContext() defer cancel() if len(adds) > 0 { - if err := task.AddTagsContext(ctx, m.tagsID, adds); err != nil { + if err := m.taskwarriorClient().AddTagsContext(ctx, m.tagsID, adds); err != nil { return err } } if len(removes) > 0 { - if err := task.RemoveTagsContext(ctx, m.tagsID, removes); err != nil { + if err := m.taskwarriorClient().RemoveTagsContext(ctx, m.tagsID, removes); err != nil { return err } } @@ -171,7 +169,7 @@ func (m *Model) handleDueEditMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { switch msg.String() { case "enter": ctx, cancel := m.taskOperationContext() - err := task.SetDueDateContext(ctx, m.dueID, m.dueDate.Format("2006-01-02")) + err := m.taskwarriorClient().SetDueDateContext(ctx, m.dueID, m.dueDate.Format("2006-01-02")) cancel() if err != nil { m.statusMsg = fmt.Sprintf("Error: %v", err) @@ -220,7 +218,7 @@ func (m *Model) handleRecurrenceMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { } ctx, cancel := m.taskOperationContext() defer cancel() - if err := task.SetRecurrenceContext(ctx, m.recurID, value); err != nil { + if err := m.taskwarriorClient().SetRecurrenceContext(ctx, m.recurID, value); err != nil { return err } if err := m.reload(); err != nil { @@ -250,7 +248,7 @@ func (m *Model) handleProjectMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { onEnter := func(value string) error { ctx, cancel := m.taskOperationContext() defer cancel() - return task.SetProjectContext(ctx, m.projID, value) + return m.taskwarriorClient().SetProjectContext(ctx, m.projID, value) } onExit := func() { @@ -282,7 +280,7 @@ func (m *Model) handlePriorityMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { return m, cmd } ctx, cancel := m.taskOperationContext() - err := task.SetPriorityContext(ctx, m.priorityID, priority) + err := m.taskwarriorClient().SetPriorityContext(ctx, m.priorityID, priority) cancel() if err != nil { m.statusMsg = fmt.Sprintf("Error: %v", err) @@ -361,7 +359,7 @@ func (m *Model) handleAddTaskMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { } ctx, cancel := m.taskOperationContext() - err := task.AddLineContext(ctx, m.addInput.Value()) + err := m.taskwarriorClient().AddLineContext(ctx, m.addInput.Value()) cancel() if err != nil { m.statusMsg = fmt.Sprintf("Error: %v", err) diff --git a/internal/ui/keyactions.go b/internal/ui/keyactions.go index ef0189a..9fede46 100644 --- a/internal/ui/keyactions.go +++ b/internal/ui/keyactions.go @@ -21,7 +21,7 @@ func (m *Model) handleEditTask() (tea.Model, tea.Cmd) { return m, nil } m.editID = id - return m, editCmd(id) + return m, m.editCmd(id) } func (m *Model) handleToggleStart() (tea.Model, tea.Cmd) { @@ -41,7 +41,7 @@ func (m *Model) handleToggleStart() (tea.Model, tea.Cmd) { if started { ctx, cancel := m.taskOperationContext() - err := task.StopContext(ctx, id) + err := m.taskwarriorClient().StopContext(ctx, id) cancel() if err != nil { m.showError(err) @@ -49,7 +49,7 @@ func (m *Model) handleToggleStart() (tea.Model, tea.Cmd) { } } else { ctx, cancel := m.taskOperationContext() - err := task.StartContext(ctx, id) + err := m.taskwarriorClient().StartContext(ctx, id) cancel() if err != nil { m.showError(err) @@ -145,7 +145,7 @@ func (m *Model) handleUndo() (tea.Model, tea.Cmd) { action := m.undoStack[len(m.undoStack)-1] ctx, cancel := m.taskOperationContext() for _, restore := range action.restores { - if err := task.SetStatusUUIDContext(ctx, restore.uuid, restore.status); err != nil { + if err := m.taskwarriorClient().SetStatusUUIDContext(ctx, restore.uuid, restore.status); err != nil { cancel() m.showError(err) return m, nil @@ -187,7 +187,7 @@ func (m *Model) handleUndo() (tea.Model, tea.Cmd) { filters = append(filters, "status:"+restore.status) ctx, cancel := m.taskOperationContext() - tasks, err := task.Export(ctx, filters...) + tasks, err := m.taskwarriorClient().Export(ctx, filters...) cancel() if err == nil && len(tasks) > 0 { id = tasks[0].ID @@ -229,7 +229,7 @@ func (m *Model) deleteTaskWithUndo(tsk task.Task) (int, bool, error) { ctx, cancel := m.taskOperationContext() defer cancel() if recurring { - series, err := task.RecurringSeries(ctx, recurringRootUUID(tsk)) + series, err := m.taskwarriorClient().RecurringSeries(ctx, recurringRootUUID(tsk)) if err != nil { return 0, true, fmt.Errorf("loading recurring series: %w", err) } @@ -250,8 +250,8 @@ func (m *Model) deleteTaskWithUndo(tsk task.Task) (int, bool, error) { completed := make([]undoRestore, 0, len(restores)) for _, restore := range restores { - if err := task.SetStatusUUIDContext(ctx, restore.uuid, "deleted"); err != nil { - if rollbackErr := rollbackUndoRestores(completed); rollbackErr != nil { + if err := m.taskwarriorClient().SetStatusUUIDContext(ctx, restore.uuid, "deleted"); err != nil { + if rollbackErr := m.rollbackUndoRestores(completed); rollbackErr != nil { return 0, recurring, fmt.Errorf("deleting task %s: %w; rollback failed: %w", restore.uuid, err, rollbackErr) } return 0, recurring, fmt.Errorf("deleting task %s: %w", restore.uuid, err) @@ -323,13 +323,13 @@ func undoStatusForTask(tsk task.Task) string { return tsk.Status } -func rollbackUndoRestores(restores []undoRestore) error { +func (m *Model) rollbackUndoRestores(restores []undoRestore) error { ctx, cancel := context.WithTimeout(context.Background(), taskOperationTimeout) defer cancel() var errs []error for i := len(restores) - 1; i >= 0; i-- { - if err := task.SetStatusUUIDContext(ctx, restores[i].uuid, restores[i].status); err != nil { + if err := m.taskwarriorClient().SetStatusUUIDContext(ctx, restores[i].uuid, restores[i].status); err != nil { errs = append(errs, fmt.Errorf("restoring task %s to %s: %w", restores[i].uuid, restores[i].status, err)) } } @@ -365,7 +365,7 @@ func (m *Model) handleRemoveDueDate() (tea.Model, tea.Cmd) { // In Taskwarrior, passing an empty value to due: removes the due date ctx, cancel := m.taskOperationContext() - err = task.SetDueDateContext(ctx, id, "") + err = m.taskwarriorClient().SetDueDateContext(ctx, id, "") cancel() if err != nil { m.showError(err) @@ -388,7 +388,7 @@ func (m *Model) handleRandomDueDate() (tea.Model, tea.Cmd) { due := time.Now().AddDate(0, 0, days).Format("2006-01-02") ctx, cancel := m.taskOperationContext() - err = task.SetDueDateContext(ctx, id, due) + err = m.taskwarriorClient().SetDueDateContext(ctx, id, due) cancel() if err != nil { m.showError(err) @@ -532,7 +532,7 @@ func (m *Model) handleTagToProject() (tea.Model, tea.Cmd) { // Set the tag as project ctx, cancel := m.taskOperationContext() - err = task.SetProjectContext(ctx, id, firstTag) + err = m.taskwarriorClient().SetProjectContext(ctx, id, firstTag) if err != nil { cancel() m.showError(err) @@ -540,7 +540,7 @@ func (m *Model) handleTagToProject() (tea.Model, tea.Cmd) { } // Remove the tag from the task - if err := task.RemoveTagsContext(ctx, id, []string{firstTag}); err != nil { + if err := m.taskwarriorClient().RemoveTagsContext(ctx, id, []string{firstTag}); err != nil { cancel() m.showError(err) return m, nil diff --git a/internal/ui/shell.go b/internal/ui/shell.go index b3b6285..d35a27c 100644 --- a/internal/ui/shell.go +++ b/internal/ui/shell.go @@ -16,12 +16,12 @@ import ( const shellCommandTimeout = 2 * time.Minute -func shellRunCmd(parent context.Context, line string, selectedID int) tea.Cmd { +func shellRunCmd(parent context.Context, tw task.Taskwarrior, line string, selectedID int) tea.Cmd { return func() tea.Msg { ctx, cancel := context.WithTimeout(parent, shellCommandTimeout) defer cancel() - result, err := task.RunShellLine(ctx, line) + result, err := tw.RunShellLine(ctx, line) return shellDoneMsg{result: result, err: err, selectedID: selectedID} } } @@ -70,7 +70,7 @@ func (m *Model) handleShellMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { m.shellActive = false m.shellInput.Blur() m.updateTableHeight() - return m, shellRunCmd(m.shellCommandContext(), line, selectedID) + return m, shellRunCmd(m.shellCommandContext(), m.taskwarriorClient(), line, selectedID) case "esc": m.shellActive = false m.shellInput.Blur() @@ -227,7 +227,7 @@ func (m *Model) loadShellCompletionsCmd() tea.Cmd { return func() tea.Msg { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() - return shellCompletionMsg{sources: task.LoadCompletionSources(ctx)} + return shellCompletionMsg{sources: m.taskwarriorClient().LoadCompletionSources(ctx)} } } diff --git a/internal/ui/table.go b/internal/ui/table.go index 949abbb..1c526c0 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -217,6 +217,7 @@ type Model struct { undoStack []undoAction browserCmd string agentFilterHotkey string + taskwarrior task.Taskwarrior theme Theme defaultTheme Theme @@ -321,8 +322,8 @@ func prepareDescriptionTempFile(description string, newTempFile func() (descript // editCmd returns a command that edits the task and sends an // editDoneMsg once the process is complete. -func editCmd(id int) tea.Cmd { - c := task.EditCmd(id) +func (m *Model) editCmd(id int) tea.Cmd { + c := m.taskwarriorClient().EditCmd(id) return tea.ExecProcess(c, func(err error) tea.Msg { return editDoneMsg{err: err} }) } @@ -360,6 +361,13 @@ func blinkCmd() tea.Cmd { return tea.Tick(blinkInterval, func(time.Time) tea.Msg { return blinkMsg{} }) } +func (m *Model) taskwarriorClient() task.Taskwarrior { + if m.taskwarrior == nil { + m.taskwarrior = task.NewTaskwarrior() + } + return m.taskwarrior +} + // clearEditingModes ensures only one editing mode is active at a time func (m *Model) clearEditingModes() { m.annotating = false @@ -405,7 +413,7 @@ func (m *Model) startBlink(id int, markDone bool) tea.Cmd { } } ctx, cancel := m.taskOperationContext() - err := task.DoneContext(ctx, id) + err := m.taskwarriorClient().DoneContext(ctx, id) cancel() if err != nil { m.showError(err) @@ -439,7 +447,15 @@ func (m *Model) startBlink(id int, markDone bool) tea.Cmd { // New creates a new UI model with the provided rows. func New(filters []string, browserCmd string) (Model, error) { - m := Model{filters: filters, browserCmd: browserCmd, agentFilterHotkey: "3", blinkState: blinkState{blinkEnabled: true}} + return NewWithTaskwarrior(filters, browserCmd, task.NewTaskwarrior()) +} + +// NewWithTaskwarrior creates a UI model using the provided Taskwarrior client. +func NewWithTaskwarrior(filters []string, browserCmd string, tw task.Taskwarrior) (Model, error) { + if tw == nil { + tw = task.NewTaskwarrior() + } + m := Model{filters: filters, browserCmd: browserCmd, agentFilterHotkey: "3", taskwarrior: tw, blinkState: blinkState{blinkEnabled: true}} m.initTaskContext() m.annotateInput = textinput.New() m.annotateInput.Prompt = "annotation: " @@ -523,12 +539,12 @@ func (m *Model) fetchTasks() (reloadData, error) { ctx, cancel := m.taskOperationContext() defer cancel() - tasks, err := task.Export(ctx, filters...) + tasks, err := m.taskwarriorClient().Export(ctx, filters...) if err != nil { return reloadData{}, err } - task.SortTasks(tasks) + m.taskwarriorClient().SortTasks(tasks) return reloadData{ tasks: tasks, ultraFilterIDs: m.ultraFilteredTaskIDs(), @@ -537,9 +553,9 @@ func (m *Model) fetchTasks() (reloadData, error) { func (m *Model) processTasks(data *reloadData) { m.tasks = data.tasks - m.total = task.TotalTasks(data.tasks) - m.inProgress = task.InProgressTasks(data.tasks) - m.due = task.DueTasks(data.tasks, time.Now()) + m.total = m.taskwarriorClient().TotalTasks(data.tasks) + m.inProgress = m.taskwarriorClient().InProgressTasks(data.tasks) + m.due = m.taskwarriorClient().DueTasks(data.tasks, time.Now()) if m.showTaskDetail { m.refreshCurrentTaskDetail() @@ -767,7 +783,7 @@ func (m *Model) handleBlinkMsg() (tea.Model, tea.Cmd) { } } ctx, cancel := m.taskOperationContext() - err := task.DoneContext(ctx, id) + err := m.taskwarriorClient().DoneContext(ctx, id) cancel() if err != nil { m.showError(err) diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go index 0920deb..fdd2e9b 100644 --- a/internal/ui/table_test.go +++ b/internal/ui/table_test.go @@ -19,6 +19,70 @@ import ( "codeberg.org/snonux/tasksamurai/internal/task" ) +type fakeTaskwarrior struct { + task.Client + + tasks []task.Task + exportFilters [][]string + addLines []string +} + +var _ task.Taskwarrior = (*fakeTaskwarrior)(nil) + +func (f *fakeTaskwarrior) Export(_ context.Context, filters ...string) ([]task.Task, error) { + f.exportFilters = append(f.exportFilters, append([]string(nil), filters...)) + return append([]task.Task(nil), f.tasks...), nil +} + +func (f *fakeTaskwarrior) AddLineContext(_ context.Context, line string) error { + f.addLines = append(f.addLines, line) + f.tasks = append(f.tasks, task.Task{ + ID: len(f.tasks) + 1, + UUID: fmt.Sprintf("fake-%d", len(f.tasks)+1), + Description: line, + Status: "pending", + }) + return nil +} + +func TestNewWithTaskwarriorUsesFakeForAddTask(t *testing.T) { + fake := &fakeTaskwarrior{ + tasks: []task.Task{ + {ID: 1, UUID: "fake-1", Description: "existing", Status: "pending"}, + }, + } + + m, err := NewWithTaskwarrior([]string{"project:home"}, "firefox", fake) + if err != nil { + t.Fatalf("NewWithTaskwarrior: %v", err) + } + + if len(fake.exportFilters) != 1 { + t.Fatalf("export calls = %d, want 1", len(fake.exportFilters)) + } + if !reflect.DeepEqual(fake.exportFilters[0], []string{"project:home", "status:pending"}) { + t.Fatalf("export filters = %v", fake.exportFilters[0]) + } + + m.addingTask = true + m.addInput.SetValue("new task +agent") + mv, _ := (&m).Update(tea.KeyPressMsg{Code: tea.KeyEnter}) + m = *mv.(*Model) + + if !reflect.DeepEqual(fake.addLines, []string{"new task +agent"}) { + t.Fatalf("add lines = %v", fake.addLines) + } + if len(fake.exportFilters) != 2 { + t.Fatalf("export calls after add = %d, want 2", len(fake.exportFilters)) + } + if len(m.tasks) != 2 { + t.Fatalf("model tasks = %d, want 2", len(m.tasks)) + } + if m.tasks[1].Description != "new task +agent" { + t.Fatalf("added task description = %q", m.tasks[1].Description) + } +} + func TestAnnotateHotkey(t *testing.T) { tmp := t.TempDir() taskPath := filepath.Join(tmp, "task") diff --git a/internal/ui/ultra.go b/internal/ui/ultra.go index 656ffb7..999ad73 100644 --- a/internal/ui/ultra.go +++ b/internal/ui/ultra.go @@ -1144,7 +1144,7 @@ func (m *Model) handleUltraEditTask() (tea.Model, tea.Cmd) { } m.editID = id - return m, editCmd(id) + return m, m.editCmd(id) } func (m *Model) handleUltraToggleStart() (tea.Model, tea.Cmd) { |
