cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
29
fork

Configure Feed

Select the types of activity you want to include in your feed.

at b5d8851190fc9c722d4e5dcc977e58042fbdec81 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}