gitlink-cli/cmd/interactive/output_viewport_test.go

121 lines
3.4 KiB
Go

package interactive
import (
"strings"
"testing"
tea "github.com/charmbracelet/bubbletea"
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)
// makeShortcutForViewport builds a minimal shortcut so formatCommandDisplay has
// something to render in the display header line of the output viewer.
func makeShortcutForViewport() *common.Shortcut {
return &common.Shortcut{
Name: "list",
}
}
// TestLongOutputEntersViewport verifies that when command output exceeds the
// available terminal height, the REPL switches into the scrollable output
// viewer (stateOutput) instead of falling back to the inline input state.
func TestLongOutputEntersViewport(t *testing.T) {
m := newReplModel("owner", "repo")
// Small terminal: m.height-4 == 1 line of room, so any multi-line output
// must overflow into the viewport.
m.height = 5
m.width = 80
m.state = stateExecuting
longOutput := strings.Repeat("line of output\n", 20)
msg := execResultMsg{
group: "issue",
shortcut: makeShortcutForViewport(),
args: map[string]string{},
output: longOutput,
err: nil,
}
newModel, _ := m.Update(msg)
repl, ok := newModel.(replModel)
if !ok {
t.Fatalf("expected replModel, got %T", newModel)
}
if repl.state != stateOutput {
t.Fatalf("expected stateOutput for long output, got %v", repl.state)
}
if repl.viewport.Height != m.height-2 {
t.Fatalf("expected viewport height %d, got %d", m.height-2, repl.viewport.Height)
}
}
// TestShortOutputStaysInput verifies that short command output keeps the
// existing behaviour: store lastOutput and return to stateInput.
func TestShortOutputStaysInput(t *testing.T) {
m := newReplModel("owner", "repo")
m.height = 40 // plenty of room
m.width = 80
m.state = stateExecuting
shortOutput := "only one line"
msg := execResultMsg{
group: "issue",
shortcut: makeShortcutForViewport(),
args: map[string]string{},
output: shortOutput,
err: nil,
}
newModel, _ := m.Update(msg)
repl, ok := newModel.(replModel)
if !ok {
t.Fatalf("expected replModel, got %T", newModel)
}
if repl.state != stateInput {
t.Fatalf("expected stateInput for short output, got %v", repl.state)
}
if repl.lastOutput == "" {
t.Fatalf("expected lastOutput to be set for short output")
}
if !strings.Contains(repl.lastOutput, shortOutput) {
t.Fatalf("expected lastOutput to contain %q, got %q", shortOutput, repl.lastOutput)
}
}
// TestViewportQuitReturnsToInput verifies that pressing 'q' in the output
// viewer returns the REPL to the input state.
func TestViewportQuitReturnsToInput(t *testing.T) {
m := newReplModel("owner", "repo")
m.state = stateOutput
keyQ := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'q'}}
newModel, _ := m.Update(keyQ)
repl, ok := newModel.(replModel)
if !ok {
t.Fatalf("expected replModel, got %T", newModel)
}
if repl.state != stateInput {
t.Fatalf("expected stateInput after pressing 'q', got %v", repl.state)
}
}
// TestViewportEscReturnsToInput verifies that Esc also exits the output viewer.
func TestViewportEscReturnsToInput(t *testing.T) {
m := newReplModel("owner", "repo")
m.state = stateOutput
keyEsc := tea.KeyMsg{Type: tea.KeyEsc}
newModel, _ := m.Update(keyEsc)
repl, ok := newModel.(replModel)
if !ok {
t.Fatalf("expected replModel, got %T", newModel)
}
if repl.state != stateInput {
t.Fatalf("expected stateInput after pressing Esc, got %v", repl.state)
}
}