···181 var series TVSeries
182 found := false
183 doc.Find("script[type='application/ld+json']").Each(func(i int, s *goquery.Selection) {
184- var tmp map[string]interface{}
185 if err := json.Unmarshal([]byte(s.Text()), &tmp); err == nil {
186 if t, ok := tmp["@type"].(string); ok && t == "TVSeries" {
187 if err := json.Unmarshal([]byte(s.Text()), &series); err == nil {
···181 var series TVSeries
182 found := false
183 doc.Find("script[type='application/ld+json']").Each(func(i int, s *goquery.Selection) {
184+ var tmp map[string]any
185 if err := json.Unmarshal([]byte(s.Text()), &tmp); err == nil {
186 if t, ok := tmp["@type"].(string); ok && t == "TVSeries" {
187 if err := json.Unmarshal([]byte(s.Text()), &series); err == nil {
···1+package ui
2+3+import (
4+ "testing"
5+ "time"
6+7+ tea "github.com/charmbracelet/bubbletea"
8+ "github.com/stormlightlabs/noteleaf/internal/models"
9+)
10+11+func TestInteractiveTUIBehavior(t *testing.T) {
12+ t.Run("Priority Mode Switching with TUI Framework", func(t *testing.T) {
13+ task := &models.Task{ID: 1, Priority: models.PriorityHigh}
14+ model := createTestTaskEditModel(task)
15+16+ suite := NewTUITestSuite(t, model, WithInitialSize(80, 24))
17+ suite.Start()
18+19+ if err := suite.SendKeyString("p"); err != nil {
20+ t.Fatalf("Failed to send 'p' key: %v", err)
21+ }
22+23+ if err := suite.WaitFor(func(m tea.Model) bool {
24+ if taskModel, ok := m.(taskEditModel); ok {
25+ return taskModel.mode == priorityPicker
26+ }
27+ return false
28+ }, 1*time.Second); err != nil {
29+ t.Fatalf("Failed to enter priority picker mode: %v", err)
30+ }
31+32+ if err := suite.SendKeyString("m"); err != nil {
33+ t.Fatalf("Failed to send 'm' key: %v", err)
34+ }
35+36+ if err := suite.WaitFor(func(m tea.Model) bool {
37+ if taskModel, ok := m.(taskEditModel); ok {
38+ return taskModel.priorityMode == priorityModeNumeric
39+ }
40+ return false
41+ }, 1*time.Second); err != nil {
42+ t.Fatalf("Failed to switch to numeric priority mode: %v", err)
43+ }
44+45+ if err := suite.WaitForView("Numeric", 1*time.Second); err != nil {
46+ t.Errorf("Expected view to contain 'Numeric': %v", err)
47+ }
48+49+ if err := suite.SendKeyString("m"); err != nil {
50+ t.Fatalf("Failed to send second 'm' key: %v", err)
51+ }
52+53+ if err := suite.WaitFor(func(m tea.Model) bool {
54+ if taskModel, ok := m.(taskEditModel); ok {
55+ return taskModel.priorityMode == priorityModeLegacy
56+ }
57+ return false
58+ }, 1*time.Second); err != nil {
59+ t.Fatalf("Failed to switch to legacy priority mode: %v", err)
60+ }
61+62+ if err := suite.WaitForView("Legacy", 1*time.Second); err != nil {
63+ t.Errorf("Expected view to contain 'Legacy': %v", err)
64+ }
65+ })
66+67+ t.Run("Keyboard Navigation with TUI Framework", func(t *testing.T) {
68+ task := &models.Task{ID: 1, Status: models.StatusTodo}
69+ model := createTestTaskEditModel(task)
70+71+ suite := NewTUITestSuite(t, model)
72+ suite.Start()
73+74+ if err := suite.SendKeyString("s"); err != nil {
75+ t.Fatalf("Failed to send 's' key: %v", err)
76+ }
77+78+ if err := suite.WaitFor(func(m tea.Model) bool {
79+ if taskModel, ok := m.(taskEditModel); ok {
80+ return taskModel.mode == statusPicker
81+ }
82+ return false
83+ }, 1*time.Second); err != nil {
84+ t.Fatalf("Failed to enter status picker mode: %v", err)
85+ }
86+87+ if err := suite.SendKey(tea.KeyDown); err != nil {
88+ t.Fatalf("Failed to send down arrow: %v", err)
89+ }
90+91+ if err := suite.WaitFor(func(m tea.Model) bool {
92+ if taskModel, ok := m.(taskEditModel); ok {
93+ return taskModel.statusIndex == 1
94+ }
95+ return false
96+ }, 1*time.Second); err != nil {
97+ t.Fatalf("Status index should have changed to 1: %v", err)
98+ }
99+100+ if err := suite.SendKey(tea.KeyEsc); err != nil {
101+ t.Fatalf("Failed to send escape key: %v", err)
102+ }
103+104+ if err := suite.WaitFor(func(m tea.Model) bool {
105+ if taskModel, ok := m.(taskEditModel); ok {
106+ return taskModel.mode == fieldNavigation
107+ }
108+ return false
109+ }, 1*time.Second); err != nil {
110+ t.Fatalf("Should have returned to field navigation mode: %v", err)
111+ }
112+ })
113+114+ t.Run("Window Resize Handling", func(t *testing.T) {
115+ task := &models.Task{ID: 1}
116+ model := createTestTaskEditModel(task)
117+118+ suite := NewTUITestSuite(t, model)
119+ suite.Start()
120+121+ resizeMsg := tea.WindowSizeMsg{Width: 120, Height: 40}
122+ if err := suite.SendMessage(resizeMsg); err != nil {
123+ t.Fatalf("Failed to send window resize message: %v", err)
124+ }
125+126+ if err := suite.WaitFor(func(m tea.Model) bool {
127+ if taskModel, ok := m.(taskEditModel); ok {
128+ return taskModel.opts.Width == 120
129+ }
130+ return false
131+ }, 1*time.Second); err != nil {
132+ t.Fatalf("Window width should have been updated to 120: %v", err)
133+ }
134+ })
135+136+ t.Run("Complex Key Sequence with TUI Framework", func(t *testing.T) {
137+ task := &models.Task{ID: 1, Description: "Test", Project: "TestProject"}
138+ model := createTestTaskEditModel(task)
139+140+ suite := NewTUITestSuite(t, model)
141+ suite.Start()
142+143+ keySequence := []KeyWithTiming{
144+ {KeyType: tea.KeyDown, Delay: 50 * time.Millisecond},
145+ {KeyType: tea.KeyDown, Delay: 50 * time.Millisecond},
146+ {KeyType: tea.KeyDown, Delay: 50 * time.Millisecond},
147+ {KeyType: tea.KeyEnter, Delay: 100 * time.Millisecond},
148+ }
149+150+ if err := suite.SimulateKeySequence(keySequence); err != nil {
151+ t.Fatalf("Failed to simulate key sequence: %v", err)
152+ }
153+154+ if err := suite.WaitFor(func(m tea.Model) bool {
155+ if taskModel, ok := m.(taskEditModel); ok {
156+ return taskModel.mode == textInput && taskModel.currentField == 3 // Project field
157+ }
158+ return false
159+ }, 2*time.Second); err != nil {
160+ t.Fatalf("Should have entered text input mode for project field: %v", err)
161+ }
162+163+ if err := suite.SendKeyString(" Updated"); err != nil {
164+ t.Fatalf("Failed to send text: %v", err)
165+ }
166+167+ if err := suite.SendKey(tea.KeyEnter); err != nil {
168+ t.Fatalf("Failed to send enter key: %v", err)
169+ }
170+171+ if err := suite.WaitFor(func(m tea.Model) bool {
172+ if taskModel, ok := m.(taskEditModel); ok {
173+ return taskModel.mode == fieldNavigation &&
174+ taskModel.task.Project == "TestProject Updated"
175+ }
176+ return false
177+ }, 1*time.Second); err != nil {
178+ t.Fatalf("Project should have been updated: %v", err)
179+ }
180+ })
181+}
182+183+func TestTUIFrameworkFeatures(t *testing.T) {
184+ t.Run("Output Capture", func(t *testing.T) {
185+ task := &models.Task{ID: 1, Description: "Test Output"}
186+ model := createTestTaskEditModel(task)
187+188+ suite := NewTUITestSuite(t, model)
189+ suite.Start()
190+191+ time.Sleep(100 * time.Millisecond)
192+193+ view := suite.GetCurrentView()
194+ if len(view) == 0 {
195+ t.Error("View should not be empty")
196+ }
197+198+ if !containsString(view, "Test Output") {
199+ t.Error("View should contain task description")
200+ }
201+ })
202+203+ t.Run("Timeout Handling", func(t *testing.T) {
204+ task := &models.Task{ID: 1}
205+ model := createTestTaskEditModel(task)
206+207+ suite := NewTUITestSuite(t, model, WithTimeout(100*time.Millisecond))
208+ suite.Start()
209+210+ if err := suite.WaitFor(func(m tea.Model) bool {
211+ return false
212+ }, 50*time.Millisecond); err == nil {
213+ t.Error("Expected timeout error")
214+ }
215+ })
216+217+ t.Run("Multiple Assertions", func(t *testing.T) {
218+ task := &models.Task{ID: 1, Description: "Test Task", Status: models.StatusTodo}
219+ model := createTestTaskEditModel(task)
220+221+ suite := NewTUITestSuite(t, model)
222+ suite.Start()
223+224+ Expect.AssertViewContains(t, suite, "Test Task", "View should contain task description")
225+ Expect.AssertViewContains(t, suite, models.StatusTodo, "View should contain status")
226+ Expect.AssertModelState(t, suite, func(m tea.Model) bool {
227+ if taskModel, ok := m.(taskEditModel); ok {
228+ return taskModel.mode == fieldNavigation
229+ }
230+ return false
231+ }, "Model should be in field navigation mode")
232+ })
233+}
+259
internal/ui/task_edit_test.go
···484 t.Error("Priority picker should show current mode")
485 }
486}
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
···484 t.Error("Priority picker should show current mode")
485 }
486}
487+488+// TestUncoveredPriorityModes tests all priority mode switch cases
489+func TestUncoveredPriorityModes(t *testing.T) {
490+ t.Run("Priority Mode Display Strings", func(t *testing.T) {
491+ task := &models.Task{ID: 1, Priority: models.PriorityHigh}
492+ model := createTestTaskEditModel(task)
493+ model.mode = priorityPicker
494+495+ // Test numeric mode
496+ model.priorityMode = priorityModeNumeric
497+ result := model.renderPriorityPicker()
498+ if !strings.Contains(result, "Numeric") {
499+ t.Error("Priority picker should show Numeric mode")
500+ }
501+502+ // Test legacy mode
503+ model.priorityMode = priorityModeLegacy
504+ result = model.renderPriorityPicker()
505+ if !strings.Contains(result, "Legacy") {
506+ t.Error("Priority picker should show Legacy mode")
507+ }
508+ })
509+510+ t.Run("Priority Display Type Switches", func(t *testing.T) {
511+ tests := []struct {
512+ name string
513+ priority string
514+ expectedType string
515+ }{
516+ {"Numeric Priority", "3", "numeric"},
517+ {"Legacy Priority A", "A", "legacy"},
518+ {"Legacy Priority E", "E", "legacy"},
519+ }
520+521+ for _, tt := range tests {
522+ t.Run(tt.name, func(t *testing.T) {
523+ task := &models.Task{ID: 1, Priority: tt.priority}
524+525+ displayType := GetPriorityDisplayType(task.Priority)
526+ if displayType != tt.expectedType {
527+ t.Errorf("Expected display type %s for priority %s, got %s (task:%v)", tt.expectedType, tt.priority, displayType, task)
528+ }
529+ })
530+ }
531+ })
532+}
533+534+func TestUncoveredKeyboardNavigation(t *testing.T) {
535+ t.Run("Status Edit Key Binding", func(t *testing.T) {
536+ task := &models.Task{ID: 1, Status: models.StatusTodo}
537+ model := createTestTaskEditModel(task)
538+539+ msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'s'}}
540+ updatedModel, _ := model.Update(msg)
541+ model = updatedModel.(taskEditModel)
542+543+ if model.mode != statusPicker {
544+ t.Error("Expected status picker mode when 's' key is pressed")
545+ }
546+ })
547+548+ t.Run("Priority Edit Key Binding", func(t *testing.T) {
549+ task := &models.Task{ID: 1}
550+ model := createTestTaskEditModel(task)
551+552+ msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'p'}}
553+ updatedModel, _ := model.Update(msg)
554+ model = updatedModel.(taskEditModel)
555+556+ if model.mode != priorityPicker {
557+ t.Error("Expected priority picker mode when 'p' key is pressed")
558+ }
559+ })
560+561+ t.Run("Window Resize Handling", func(t *testing.T) {
562+ task := &models.Task{ID: 1}
563+ model := createTestTaskEditModel(task)
564+565+ msg := tea.WindowSizeMsg{Width: 120, Height: 40}
566+ updatedModel, _ := model.Update(msg)
567+ model = updatedModel.(taskEditModel)
568+569+ if model.opts.Width != 120 {
570+ t.Errorf("Expected width to be updated to 120, got %d", model.opts.Width)
571+ }
572+ })
573+574+ t.Run("Escape Key in Status Picker", func(t *testing.T) {
575+ task := &models.Task{ID: 1}
576+ model := createTestTaskEditModel(task)
577+ model.mode = statusPicker
578+579+ msg := tea.KeyMsg{Type: tea.KeyEsc}
580+ updatedModel, _ := model.Update(msg)
581+ model = updatedModel.(taskEditModel)
582+583+ if model.mode != fieldNavigation {
584+ t.Error("Expected to return to field navigation when escape is pressed in status picker")
585+ }
586+ })
587+588+ t.Run("Escape Key in Priority Picker", func(t *testing.T) {
589+ task := &models.Task{ID: 1}
590+ model := createTestTaskEditModel(task)
591+ model.mode = priorityPicker
592+593+ msg := tea.KeyMsg{Type: tea.KeyEsc}
594+ updatedModel, _ := model.Update(msg)
595+ model = updatedModel.(taskEditModel)
596+597+ if model.mode != fieldNavigation {
598+ t.Error("Expected to return to field navigation when escape is pressed in priority picker")
599+ }
600+ })
601+602+ t.Run("Navigation Keys in Status Picker", func(t *testing.T) {
603+ task := &models.Task{ID: 1}
604+ model := createTestTaskEditModel(task)
605+ model.mode = statusPicker
606+ model.statusIndex = 0
607+608+ // Test down/right navigation
609+ msg := tea.KeyMsg{Type: tea.KeyDown}
610+ updatedModel, _ := model.Update(msg)
611+ model = updatedModel.(taskEditModel)
612+613+ if model.statusIndex != 1 {
614+ t.Errorf("Expected status index to be 1, got %d", model.statusIndex)
615+ }
616+617+ // Test up/left navigation
618+ msg = tea.KeyMsg{Type: tea.KeyUp}
619+ updatedModel, _ = model.Update(msg)
620+ model = updatedModel.(taskEditModel)
621+622+ if model.statusIndex != 0 {
623+ t.Errorf("Expected status index to be 0, got %d", model.statusIndex)
624+ }
625+ })
626+627+ t.Run("Navigation Keys in Priority Picker", func(t *testing.T) {
628+ task := &models.Task{ID: 1}
629+ model := createTestTaskEditModel(task)
630+ model.mode = priorityPicker
631+ model.priorityIndex = 0
632+633+ // Test down/right navigation
634+ msg := tea.KeyMsg{Type: tea.KeyDown}
635+ updatedModel, _ := model.Update(msg)
636+ model = updatedModel.(taskEditModel)
637+638+ if model.priorityIndex != 1 {
639+ t.Errorf("Expected priority index to be 1, got %d", model.priorityIndex)
640+ }
641+642+ // Test up/left navigation
643+ msg = tea.KeyMsg{Type: tea.KeyUp}
644+ updatedModel, _ = model.Update(msg)
645+ model = updatedModel.(taskEditModel)
646+647+ if model.priorityIndex != 0 {
648+ t.Errorf("Expected priority index to be 0, got %d", model.priorityIndex)
649+ }
650+ })
651+}
652+653+func TestUncoveredFieldSwitches(t *testing.T) {
654+ t.Run("Field Entry Switch Cases", func(t *testing.T) {
655+ task := &models.Task{ID: 1, Description: "Test", Project: "TestProject"}
656+ model := createTestTaskEditModel(task)
657+658+ model.currentField = 3
659+ model.mode = textInput
660+661+ msg := tea.KeyMsg{Type: tea.KeyEnter}
662+ updatedModel, _ := model.Update(msg)
663+ model = updatedModel.(taskEditModel)
664+665+ if model.mode != fieldNavigation {
666+ t.Error("Expected to return to field navigation after entering project field")
667+ }
668+ })
669+670+ t.Run("Field Update Switch Cases", func(t *testing.T) {
671+ task := &models.Task{ID: 1, Description: "Test", Project: "TestProject"}
672+ model := createTestTaskEditModel(task)
673+ model.currentField = 3
674+ model.mode = textInput
675+676+ model.projectInput.SetValue("Updated Project")
677+678+ msg := tea.KeyMsg{Type: tea.KeyEnter}
679+ updatedModel, _ := model.Update(msg)
680+ model = updatedModel.(taskEditModel)
681+682+ if model.task.Project != "Updated Project" {
683+ t.Errorf("Expected project to be updated to 'Updated Project', got %s", model.task.Project)
684+ }
685+ })
686+}
687+688+func TestTaskFieldAccessors(t *testing.T) {
689+ t.Run("Task Field Value Extraction", func(t *testing.T) {
690+ now := time.Now()
691+ task := &models.Task{
692+ ID: 1,
693+ Description: "Test",
694+ Tags: []string{"tag1", "tag2"},
695+ Due: &now,
696+ Entry: now,
697+ Start: &now,
698+ End: &now,
699+ }
700+701+ tests := []struct {
702+ field string
703+ expected any
704+ }{
705+ {"due", task.Due},
706+ {"entry", task.Entry},
707+ {"start", task.Start},
708+ {"end", task.End},
709+ }
710+711+ for _, tt := range tests {
712+ t.Run(tt.field, func(t *testing.T) {
713+ result := getTaskFieldValue(task, tt.field)
714+ if result != tt.expected {
715+ t.Errorf("Expected %v for field %s, got %v", tt.expected, tt.field, result)
716+ }
717+ })
718+ }
719+ })
720+}
721+722+func getTaskFieldValue(task *models.Task, field string) any {
723+ switch field {
724+ case "description":
725+ return task.Description
726+ case "status":
727+ return task.Status
728+ case "priority":
729+ return task.Priority
730+ case "project":
731+ return task.Project
732+ case "tags":
733+ return task.Tags
734+ case "due":
735+ return task.Due
736+ case "entry":
737+ return task.Entry
738+ case "start":
739+ return task.Start
740+ case "end":
741+ return task.End
742+ default:
743+ return nil
744+ }
745+}