Files

114 lines
3.5 KiB
Go

// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package prompt
import (
"testing"
"git.stormux.org/storm/fenrir/internal/screen"
)
func snapshot(lines ...string) screen.Snapshot {
result := screen.Snapshot{Terminal: 1, Active: true, Rows: len(lines), Cols: 100, Y: len(lines) - 1}
for _, line := range lines {
cells := make([]rune, result.Cols)
for index := range cells {
cells[index] = ' '
}
copy(cells, []rune(line))
result.Cells = append(result.Cells, cells...)
}
return result
}
func TestRecognizesFreshPromptsAndCompletion(t *testing.T) {
for _, line := range []string{
"$", "#", ">", "bash-5.3$", "[Username@Host ~/code] $", "Username@Host:~$",
"Proceed? [Y/n]", "Continue? (Yes/No)", "Is this ok [y/N]:", ":: Proceed? [Y/n]",
"Password:", "[sudo] password for Username:", "Username's password:",
"Please enter passphrase for key:", "Press any key to continue...", "Are you sure? [yes/no]",
"Please confirm:", "CUSTOM READY", "Device 42 ready:",
"Worked for 3m 35s • 8:05 PM", "Worked for 19s • 7:46 PM", "Worked for 1h 2m 3s • 12:00 AM",
} {
t.Run(line, func(t *testing.T) {
detector, err := New([]string{"CUSTOM READY"}, []string{`^Device [0-9]+ ready:$`})
if err != nil {
t.Fatal(err)
}
if got := detector.Observe(snapshot("working")); got != "" {
t.Fatal(got)
}
if got := detector.Observe(snapshot(" " + line)); got != line {
t.Fatal(got)
}
if got := detector.Observe(snapshot(" " + line)); got != "" {
t.Fatal("stale line restored", got)
}
})
}
}
func TestRejectsFalseAndIncompletePrompts(t *testing.T) {
for _, line := range []string{
"Price is $", "C# language", "Downloading 20%", "$ echo hello", "Password: secret",
"Please enter passphrase: secret", "Worked for 19s", "Worked for 19s • 7:4",
"Worked for 19s • 17:46 PM", "Worked for 19s • 7:99 PM", "Worked for 19s • 7:46 PM more",
"quoted Worked for 19s • 7:46 PM", "Worked for 3m nonsense • 8:05 PM",
} {
t.Run(line, func(t *testing.T) {
detector, _ := New(nil, nil)
detector.Observe(snapshot("working"))
if got := detector.Observe(snapshot(line)); got != "" {
t.Fatal(got)
}
})
}
}
func TestSplitCompletionAboveCursorAndOldScroll(t *testing.T) {
detector, _ := New(nil, nil)
detector.Observe(snapshot("old", "working", "input"))
if got := detector.Observe(snapshot("old", "Worked for 19s • 7:4", "input")); got != "" {
t.Fatal(got)
}
line := "Worked for 19s • 7:46 PM"
if got := detector.Observe(snapshot("old", line, "input")); got != line {
t.Fatal(got)
}
if got := detector.Observe(snapshot(line, "input", "still working")); got != "" {
t.Fatal("scrolled history restored", got)
}
// A shell prompt elsewhere on the screen is not the active prompt.
if got := detector.Observe(snapshot(line, "$", "still working")); got != "" {
t.Fatal(got)
}
}
func TestSessionChangesOnlyEstablishBaseline(t *testing.T) {
for _, mode := range []string{"terminal", "generation", "resize", "inactive"} {
t.Run(mode, func(t *testing.T) {
detector, _ := New(nil, nil)
detector.Observe(snapshot("working"))
next := snapshot("$")
switch mode {
case "terminal":
next.Terminal++
case "generation":
next.Generation++
case "resize":
next = snapshot("", "$")
case "inactive":
next.Active = false
}
if got := detector.Observe(next); got != "" {
t.Fatal(got)
}
next.Active = true
if got := detector.Observe(next); got != "" {
t.Fatal(got)
}
})
}
}