package tui
import (
"context"
"strings"
"testing"
"time"
"github.com/charmbracelet/x/ansi"
)
func TestElapsed(t *testing.T) {
cases := []struct {
d time.Duration
want string
}{
{0, "0.0s"},
{-time.Second, "0.0s"},
{1500 * time.Millisecond, "1.5s"},
{59900 * time.Millisecond, "59.9s"},
{time.Minute, "1m 0s"},
{72 * time.Second, "1m 12s"},
{59*time.Minute + 59*time.Second, "59m 59s"},
{time.Hour, "1h 0m"},
{2*time.Hour + 5*time.Minute, "2h 5m"},
}
for _, tc := range cases {
if got := elapsed(tc.d); got != tc.want {
t.Errorf("elapsed(%v) = %q, want %q", tc.d, got, tc.want)
}
}
}
// The turn's own timer is what the status line counted while it ran; when the
// turn ends that line is gone, so the number has to land in the scrollback.
func TestTurnDoneReportsHowLongItTook(t *testing.T) {
m := newTestModel(t)
m.opts.SessionDir = t.TempDir()
m.phase = phaseRunning
m.started = time.Now().Add(-90 * time.Second)
m.turn = 3
m.Update(turnDone{})
got := ansi.Strip(m.tr.render())
if !strings.Contains(got, "took 1m 30s") {
t.Errorf("transcript does not report the duration:\n%s", got)
}
if !strings.Contains(got, "3 turns") {
t.Errorf("transcript does not report the turn count:\n%s", got)
}
}
// One turn of the tool loop is the ordinary case and says nothing worth a
// second field.
func TestTurnDoneOmitsASingleTurnCount(t *testing.T) {
m := newTestModel(t)
m.opts.SessionDir = t.TempDir()
m.phase = phaseRunning
m.started = time.Now().Add(-2 * time.Second)
m.turn = 1
m.Update(turnDone{})
got := ansi.Strip(m.tr.render())
if !strings.Contains(got, "took 2.0s") {
t.Errorf("transcript does not report the duration:\n%s", got)
}
if strings.Contains(got, "turns") {
t.Errorf("a one-turn answer reported a turn count:\n%s", got)
}
}
// An interrupted turn still took the time it took, and the report lands under
// the notice that says it was cut short rather than replacing it.
func TestInterruptedTurnStillReportsDuration(t *testing.T) {
m := newTestModel(t)
m.opts.SessionDir = t.TempDir()
m.phase = phaseRunning
m.started = time.Now().Add(-5 * time.Second)
m.Update(turnDone{err: context.Canceled})
got := ansi.Strip(m.tr.render())
if !strings.Contains(got, "interrupted") {
t.Fatalf("the interruption went unreported:\n%s", got)
}
if i, j := strings.Index(got, "interrupted"), strings.Index(got, "took 5.0s"); j < i {
t.Errorf("the duration is not under the answer it belongs to:\n%s", got)
}
}
// A turnDone with no run behind it has nothing to time, and a duration
// measured from the zero time would be nonsense.
func TestTurnDoneWithoutATimerSaysNothing(t *testing.T) {
m := newTestModel(t)
m.opts.SessionDir = t.TempDir()
m.Update(turnDone{})
if got := ansi.Strip(m.tr.render()); strings.Contains(got, "took ") {
t.Errorf("an untimed turn reported a duration:\n%s", got)
}
}