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 17ba836c74ebd128fed81eb3a2b59aadc0ec887e 233 lines 6.8 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) 10 11func 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 183func 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}