diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-22 22:36:55 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-22 22:36:55 +0300 |
| commit | bac4c614296501559bd3a31aaf30efc1db974426 (patch) | |
| tree | c55bb43886736a51cc818ab67f31b75f37cb0ef3 | |
| parent | 9fc53c1970f61360e2d8169fad4cbe11e23edac6 (diff) | |
Fix recurrence detail blink target (qq0)
| -rw-r--r-- | internal/ui/handlers.go | 5 | ||||
| -rw-r--r-- | internal/ui/table_test.go | 137 |
2 files changed, 138 insertions, 4 deletions
diff --git a/internal/ui/handlers.go b/internal/ui/handlers.go index 05309b2..e556570 100644 --- a/internal/ui/handlers.go +++ b/internal/ui/handlers.go @@ -236,11 +236,8 @@ func (m *Model) handleRecurrenceMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { model, cmd := m.handleTextInput(msg, &m.recurInput, onEnter, onExit) if msg.String() == "enter" { if m.showTaskDetail { - // In detail view, blink the recurrence field (dynamic index) - // Need to calculate the index based on whether recurrence field exists - fieldIndex := 8 // Base index for recurrence if m.currentTaskDetail != nil && m.currentTaskDetail.Recur != "" { - return model, m.startDetailBlink(fieldIndex) + return model, m.startDetailBlink(fieldRecur) } } return model, m.startBlink(m.recurID, false) diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go index 1120d27..619a71a 100644 --- a/internal/ui/table_test.go +++ b/internal/ui/table_test.go @@ -1084,6 +1084,143 @@ func TestRecurrenceHotkey(t *testing.T) { } } +func TestHandleRecurrenceModeDetailBlinkTargetsRecurField(t *testing.T) { + m := newRecurrenceDetailModel(t, "") + + m.showTaskDetail = true + m.currentTaskDetail = &m.tasks[0] + m.activateRecurEdit(m.currentTaskDetail.ID, m.currentTaskDetail.Recur) + m.recurInput.SetValue("daily") + + mv, cmd := (&m).handleRecurrenceMode(tea.KeyPressMsg{Code: tea.KeyEnter}) + m = *mv.(*Model) + + if cmd == nil { + t.Fatalf("recurrence edit did not start a blink command") + } + if m.currentTaskDetail == nil { + t.Fatalf("current task detail was cleared") + } + if m.currentTaskDetail.Recur != "daily" { + t.Fatalf("current detail recurrence = %q, want daily", m.currentTaskDetail.Recur) + } + if m.detailBlinkField != fieldRecur { + t.Fatalf("detail blink field = %d, want recurrence field %d", m.detailBlinkField, fieldRecur) + } + if m.detailBlinkField == fieldEntry { + t.Fatalf("detail blink targeted entry field %d, want recurrence field %d", fieldEntry, fieldRecur) + } + if m.blinkID != 0 { + t.Fatalf("row blink started unexpectedly for task ID %d", m.blinkID) + } +} + +func TestHandleRecurrenceModeDetailFallsBackWhenRecurrenceRemoved(t *testing.T) { + m := newRecurrenceDetailModel(t, "daily") + + m.showTaskDetail = true + m.currentTaskDetail = &m.tasks[0] + m.detailBlinkField = -1 + m.activateRecurEdit(m.currentTaskDetail.ID, m.currentTaskDetail.Recur) + m.recurInput.SetValue("") + + mv, cmd := (&m).handleRecurrenceMode(tea.KeyPressMsg{Code: tea.KeyEnter}) + m = *mv.(*Model) + + if cmd == nil { + t.Fatalf("recurrence removal did not start a fallback blink command") + } + if m.currentTaskDetail == nil { + t.Fatalf("current task detail was cleared") + } + if m.currentTaskDetail.Recur != "" { + t.Fatalf("current detail recurrence = %q, want empty", m.currentTaskDetail.Recur) + } + if m.detailBlinkField == fieldRecur { + t.Fatalf("detail blink targeted recurrence field after recurrence row was removed") + } + if m.blinkID != 1 { + t.Fatalf("row blink ID = %d, want fallback task ID 1", m.blinkID) + } +} + +func newRecurrenceDetailModel(t *testing.T, initialRecur string) Model { + t.Helper() + + tmp := t.TempDir() + taskPath := filepath.Join(tmp, "task") + stateFile := filepath.Join(tmp, "recur-state.txt") + + if initialRecur != "" { + if err := os.WriteFile(stateFile, []byte(initialRecur), 0o644); err != nil { + t.Fatal(err) + } + } + + script := fmt.Sprintf(`#!/bin/sh +state=%q +if echo "$@" | grep -q export; then + recur="" + if [ -f "$state" ]; then + recur=$(cat "$state") + fi + if [ -n "$recur" ]; then + printf '{"id":1,"uuid":"x","description":"d","status":"pending","entry":"","priority":"","urgency":0,"recur":"%%s"}\n' "$recur" + else + printf '{"id":1,"uuid":"x","description":"d","status":"pending","entry":"","priority":"","urgency":0}\n' + fi + exit 0 +fi +for arg in "$@"; do + case "$arg" in + recur:*) + printf '%%s' "${arg#recur:}" > "$state" + exit 0 + ;; + esac +done +exit 0 +`, stateFile) + + if err := os.WriteFile(taskPath, []byte(script), 0o755); err != nil { + t.Fatal(err) + } + + origPath := os.Getenv("PATH") + if err := os.Setenv("PATH", tmp+":"+origPath); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := os.Setenv("PATH", origPath); err != nil { + t.Errorf("restore PATH: %v", err) + } + }) + + if err := os.Setenv("TASKDATA", tmp); err != nil { + t.Fatal(err) + } + if err := os.Setenv("TASKRC", "/dev/null"); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := os.Unsetenv("TASKDATA"); err != nil { + t.Errorf("unset TASKDATA: %v", err) + } + if err := os.Unsetenv("TASKRC"); err != nil { + t.Errorf("unset TASKRC: %v", err) + } + }) + + m, err := New(nil, "firefox") + if err != nil { + t.Fatalf("New: %v", err) + } + if len(m.tasks) != 1 { + t.Fatalf("loaded %d tasks, want 1", len(m.tasks)) + } + return m +} + func TestPriorityHotkey(t *testing.T) { tmp := t.TempDir() taskPath := filepath.Join(tmp, "task") |
