cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
at main 234 lines 6.9 kB view raw
1package ui 2 3import ( 4 "testing" 5 "time" 6 7 tea "github.com/charmbracelet/bubbletea" 8 "github.com/stormlightlabs/noteleaf/internal/models" 9 "github.com/stormlightlabs/noteleaf/internal/shared" 10) 11 12func TestInteractiveTUIBehavior(t *testing.T) { 13 t.Run("Priority Mode Switching with TUI Framework", func(t *testing.T) { 14 task := &models.Task{ID: 1, Priority: models.PriorityHigh} 15 model := createTestTaskEditModel(task) 16 17 suite := NewTUITestSuite(t, model, WithInitialSize(80, 24)) 18 suite.Start() 19 20 if err := suite.SendKeyString("p"); err != nil { 21 t.Fatalf("Failed to send 'p' key: %v", err) 22 } 23 24 if err := suite.WaitFor(func(m tea.Model) bool { 25 if taskModel, ok := m.(taskEditModel); ok { 26 return taskModel.mode == priorityPicker 27 } 28 return false 29 }, 1*time.Second); err != nil { 30 t.Fatalf("Failed to enter priority picker mode: %v", err) 31 } 32 33 if err := suite.SendKeyString("m"); err != nil { 34 t.Fatalf("Failed to send 'm' key: %v", err) 35 } 36 37 if err := suite.WaitFor(func(m tea.Model) bool { 38 if taskModel, ok := m.(taskEditModel); ok { 39 return taskModel.priorityMode == priorityModeNumeric 40 } 41 return false 42 }, 1*time.Second); err != nil { 43 t.Fatalf("Failed to switch to numeric priority mode: %v", err) 44 } 45 46 if err := suite.WaitForView("Numeric", 1*time.Second); err != nil { 47 t.Errorf("Expected view to contain 'Numeric': %v", err) 48 } 49 50 if err := suite.SendKeyString("m"); err != nil { 51 t.Fatalf("Failed to send second 'm' key: %v", err) 52 } 53 54 if err := suite.WaitFor(func(m tea.Model) bool { 55 if taskModel, ok := m.(taskEditModel); ok { 56 return taskModel.priorityMode == priorityModeLegacy 57 } 58 return false 59 }, 1*time.Second); err != nil { 60 t.Fatalf("Failed to switch to legacy priority mode: %v", err) 61 } 62 63 if err := suite.WaitForView("Legacy", 1*time.Second); err != nil { 64 t.Errorf("Expected view to contain 'Legacy': %v", err) 65 } 66 }) 67 68 t.Run("Keyboard Navigation with TUI Framework", func(t *testing.T) { 69 task := &models.Task{ID: 1, Status: models.StatusTodo} 70 model := createTestTaskEditModel(task) 71 72 suite := NewTUITestSuite(t, model) 73 suite.Start() 74 75 if err := suite.SendKeyString("s"); err != nil { 76 t.Fatalf("Failed to send 's' key: %v", err) 77 } 78 79 if err := suite.WaitFor(func(m tea.Model) bool { 80 if taskModel, ok := m.(taskEditModel); ok { 81 return taskModel.mode == statusPicker 82 } 83 return false 84 }, 1*time.Second); err != nil { 85 t.Fatalf("Failed to enter status picker mode: %v", err) 86 } 87 88 if err := suite.SendKey(tea.KeyDown); err != nil { 89 t.Fatalf("Failed to send down arrow: %v", err) 90 } 91 92 if err := suite.WaitFor(func(m tea.Model) bool { 93 if taskModel, ok := m.(taskEditModel); ok { 94 return taskModel.statusIndex == 1 95 } 96 return false 97 }, 1*time.Second); err != nil { 98 t.Fatalf("Status index should have changed to 1: %v", err) 99 } 100 101 if err := suite.SendKey(tea.KeyEsc); err != nil { 102 t.Fatalf("Failed to send escape key: %v", err) 103 } 104 105 if err := suite.WaitFor(func(m tea.Model) bool { 106 if taskModel, ok := m.(taskEditModel); ok { 107 return taskModel.mode == fieldNavigation 108 } 109 return false 110 }, 1*time.Second); err != nil { 111 t.Fatalf("Should have returned to field navigation mode: %v", err) 112 } 113 }) 114 115 t.Run("Window Resize Handling", func(t *testing.T) { 116 task := &models.Task{ID: 1} 117 model := createTestTaskEditModel(task) 118 119 suite := NewTUITestSuite(t, model) 120 suite.Start() 121 122 resizeMsg := tea.WindowSizeMsg{Width: 120, Height: 40} 123 if err := suite.SendMessage(resizeMsg); err != nil { 124 t.Fatalf("Failed to send window resize message: %v", err) 125 } 126 127 if err := suite.WaitFor(func(m tea.Model) bool { 128 if taskModel, ok := m.(taskEditModel); ok { 129 return taskModel.opts.Width == 120 130 } 131 return false 132 }, 1*time.Second); err != nil { 133 t.Fatalf("Window width should have been updated to 120: %v", err) 134 } 135 }) 136 137 t.Run("Complex Key Sequence with TUI Framework", func(t *testing.T) { 138 task := &models.Task{ID: 1, Description: "Test", Project: "TestProject"} 139 model := createTestTaskEditModel(task) 140 141 suite := NewTUITestSuite(t, model) 142 suite.Start() 143 144 keySequence := []KeyWithTiming{ 145 {KeyType: tea.KeyDown, Delay: 50 * time.Millisecond}, 146 {KeyType: tea.KeyDown, Delay: 50 * time.Millisecond}, 147 {KeyType: tea.KeyDown, Delay: 50 * time.Millisecond}, 148 {KeyType: tea.KeyEnter, Delay: 100 * time.Millisecond}, 149 } 150 151 if err := suite.SimulateKeySequence(keySequence); err != nil { 152 t.Fatalf("Failed to simulate key sequence: %v", err) 153 } 154 155 if err := suite.WaitFor(func(m tea.Model) bool { 156 if taskModel, ok := m.(taskEditModel); ok { 157 return taskModel.mode == textInput && taskModel.currentField == 3 // Project field 158 } 159 return false 160 }, 2*time.Second); err != nil { 161 t.Fatalf("Should have entered text input mode for project field: %v", err) 162 } 163 164 if err := suite.SendKeyString(" Updated"); err != nil { 165 t.Fatalf("Failed to send text: %v", err) 166 } 167 168 if err := suite.SendKey(tea.KeyEnter); err != nil { 169 t.Fatalf("Failed to send enter key: %v", err) 170 } 171 172 if err := suite.WaitFor(func(m tea.Model) bool { 173 if taskModel, ok := m.(taskEditModel); ok { 174 return taskModel.mode == fieldNavigation && 175 taskModel.task.Project == "TestProject Updated" 176 } 177 return false 178 }, 1*time.Second); err != nil { 179 t.Fatalf("Project should have been updated: %v", err) 180 } 181 }) 182} 183 184func TestTUIFrameworkFeatures(t *testing.T) { 185 t.Run("Output Capture", func(t *testing.T) { 186 task := &models.Task{ID: 1, Description: "Test Output"} 187 model := createTestTaskEditModel(task) 188 189 suite := NewTUITestSuite(t, model) 190 suite.Start() 191 192 time.Sleep(100 * time.Millisecond) 193 194 view := suite.GetCurrentView() 195 if len(view) == 0 { 196 t.Error("View should not be empty") 197 } 198 199 if !shared.ContainsString(view, "Test Output") { 200 t.Error("View should contain task description") 201 } 202 }) 203 204 t.Run("Timeout Handling", func(t *testing.T) { 205 task := &models.Task{ID: 1} 206 model := createTestTaskEditModel(task) 207 208 suite := NewTUITestSuite(t, model, WithTimeout(100*time.Millisecond)) 209 suite.Start() 210 211 if err := suite.WaitFor(func(m tea.Model) bool { 212 return false 213 }, 50*time.Millisecond); err == nil { 214 t.Error("Expected timeout error") 215 } 216 }) 217 218 t.Run("Multiple Assertions", func(t *testing.T) { 219 task := &models.Task{ID: 1, Description: "Test Task", Status: models.StatusTodo} 220 model := createTestTaskEditModel(task) 221 222 suite := NewTUITestSuite(t, model) 223 suite.Start() 224 225 Expect.AssertViewContains(t, suite, "Test Task", "View should contain task description") 226 Expect.AssertViewContains(t, suite, models.StatusTodo, "View should contain status") 227 Expect.AssertModelState(t, suite, func(m tea.Model) bool { 228 if taskModel, ok := m.(taskEditModel); ok { 229 return taskModel.mode == fieldNavigation 230 } 231 return false 232 }, "Model should be in field navigation mode") 233 }) 234}