diff --git a/main.go b/main.go index fe4e6df..b14d04c 100644 --- a/main.go +++ b/main.go @@ -82,6 +82,10 @@ func initialModel(fullscreen bool, colorLeft string, colorRight string) model { inputs[2].Placeholder = "Number of intervals" inputs[2].Validate = validateInt + if colorLeft == "" || colorRight == "" { + panic("Color values must be provided") + } + return model{ inputs: inputs, progressBar: progress.New(progress.WithScaledGradient(colorLeft, colorRight)), @@ -113,18 +117,24 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { - case "ctrl+c": + case "ctrl+c", "esc": // Gracefully exit return m, tea.Quit - case "tab": + case "q": + // Quit the program if on the timer screen + if m.currentScreen == timerScreen { + return m, tea.Quit + } + + case "tab", "ctrl+n", "j", "down": // Move to the next input field m.focusIndex = (m.focusIndex + 1) % len(m.inputs) m.updateFocus() return m, nil - case "shift+tab": + case "shift+tab", "ctrl+p", "k", "up": // Move to the previous input field m.focusIndex = (m.focusIndex - 1 + len(m.inputs)) % len(m.inputs) m.updateFocus() @@ -175,6 +185,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case timeMsg: // Handle timer update for each second + // TODO: Use absolute times to tick down remaining time rather than incrementing seconds m.remaining -= time.Second if m.remaining < 0 { if m.isFocus { @@ -389,6 +400,9 @@ func main() { return nil } + fmt.Printf("color-left: %s\n", c.String("color-left")) + fmt.Printf("color-right: %s\n", c.String("color-right")) + m := initialModel(c.Bool("fullscreen"), c.String("color-left"), c.String("color-right")) // Collect command flags diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..ebaea3d --- /dev/null +++ b/main_test.go @@ -0,0 +1,156 @@ +package main + +import ( + "bytes" + "io" + "os" + "strings" + "testing" + "time" + + tea "github.com/charmbracelet/bubbletea" +) + +// assertEqual checks if two values are equal and reports an error if they are not. +func assertEqual(t *testing.T, actual, expected interface{}, msg string) { + t.Helper() + + if actual != expected { + t.Errorf("%s: expected %v, got %v", msg, expected, actual) + } +} + +// assertNotEqual checks if two values are not equal and reports an error if they are. +func assertNotEqual(t *testing.T, actual, expected interface{}, msg string) { + t.Helper() + + if actual == expected { + t.Errorf("%s: expected %v to be different from %v", msg, actual, expected) + } +} + +// TestParseDuration tests the parseDuration function +func TestParseDuration(t *testing.T) { + tests := []struct { + input string + expected time.Duration + hasError bool + }{ + {"10", 10 * time.Minute, false}, + {"1h", 1 * time.Hour, false}, + {"invalid", 0, true}, + } + + for _, test := range tests { + result, err := parseDuration(test.input) + if test.hasError { + assertNotEqual(t, err, nil, "Expected an error for input "+test.input) + } else { + assertEqual(t, err, nil, "Did not expect an error for input "+test.input) + assertEqual(t, result, test.expected, "Expected duration for input "+test.input) + } + } +} + +// TestRunCommands tests the runCommands function +func TestRunCommands(t *testing.T) { + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + + // Redirect stdout and stderr to the pipe + oldStdout := os.Stdout + oldStderr := os.Stderr + os.Stdout = w + os.Stderr = w + + t.Cleanup(func() { + os.Stdout = oldStdout + os.Stderr = oldStderr + + w.Close() + }) + + m := model{} + m.runCommands([]string{"echo Hello, World!"}) + + w.Close() + + var buf bytes.Buffer + + if _, err = io.Copy(&buf, r); err != nil { + t.Fatal(err) + } + + output := buf.String() + assertEqual(t, strings.Contains(output, "Hello, World!"), true, "Expected output to contain 'Hello, World!'") +} + +// TestInputView tests the Update method of the model for the input view +func TestInputView(t *testing.T) { + m := initialModel(false, "#ffdd57", "#57ddff") + m.View() + + assertEqual(t, m.currentScreen, inputScreen, "Expected currentScreen to be inputScreen") + assertEqual(t, m.focusIndex, 0, "Expected focusIndex to be 0") + + m.inputs[m.focusIndex].SetValue("10") + + var msg tea.Msg + + // Test tab key press + msg = tea.KeyMsg{Type: tea.KeyTab} + updatedModel, _ := m.Update(msg) + m = updatedModel.(model) + assertEqual(t, m.focusIndex, 1, "Expected focusIndex to be 1") + + // Test shift+tab key press + msg = tea.KeyMsg{Type: tea.KeyShiftTab} + updatedModel, _ = m.Update(msg) + m = updatedModel.(model) + assertEqual(t, m.focusIndex, 0, "Expected focusIndex to be 0") + + // Enter last value and test enter key press + for i := m.focusIndex; i < 2; i++ { + msg = tea.KeyMsg{Type: tea.KeyTab} + updatedModel, _ = m.Update(msg) + m = updatedModel.(model) + m.inputs[m.focusIndex].SetValue("10") + } + + msg = tea.KeyMsg{Type: tea.KeyEnter} + updatedModel, resultCmd := m.Update(msg) + m = updatedModel.(model) + + assertEqual(t, m.err, nil, "Expected no error") + assertNotEqual(t, resultCmd, nil, "Expected resultCmd to be not nil") + assertEqual(t, m.currentScreen, timerScreen, "Expected currentScreen to be timerScreen") + assertEqual(t, m.remaining, 10*time.Minute, "Expected remaining to be 10 minutes") + + // Test timer view + m.View() + + oneSec := timeMsg(time.Now().Add(1 * time.Second)) + updatedModel, _ = m.Update(oneSec) + m = updatedModel.(model) + + assertEqual(t, m.state, "Focus", "Expected state to be 'Focus'") + assertEqual(t, m.remaining, 9*time.Minute+59*time.Second, "Expected remaining to be 9 minutes 59 seconds") + + // Test switch to break time + m.remaining = 0 + updatedModel, _ = m.Update(oneSec) + m = updatedModel.(model) + + assertEqual(t, m.state, "Break", "Expected state to be 'Break'") + assertEqual(t, m.remaining, 9*time.Minute+59*time.Second, "Expected remaining to be 9 minutes 59 seconds") + + // Switch back to focus time + m.remaining = 0 + updatedModel, _ = m.Update(oneSec) + m = updatedModel.(model) + + assertEqual(t, m.state, "Focus", "Expected state to be 'Focus'") + assertEqual(t, m.remaining, 9*time.Minute+59*time.Second, "Expected remaining to be 9 minutes 59 seconds") +}