summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-22 22:36:55 +0300
committerPaul Buetow <paul@buetow.org>2026-06-22 22:36:55 +0300
commitbac4c614296501559bd3a31aaf30efc1db974426 (patch)
treec55bb43886736a51cc818ab67f31b75f37cb0ef3
parent9fc53c1970f61360e2d8169fad4cbe11e23edac6 (diff)
Fix recurrence detail blink target (qq0)
-rw-r--r--internal/ui/handlers.go5
-rw-r--r--internal/ui/table_test.go137
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")