Vectrum
Sign In
104 lines · 2.8 KB · Go
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)
	}
}