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) } }