Vectrum
Sign In
113 lines · 3.3 KB · Go
package tui

import (
	"errors"
	"strings"
	"testing"

	"github.com/johannSo/codey/internal/agent"
)

// /compact rewrites the history the in-flight request is built from, so it has
// to wait for the turn to end — the same reasoning that makes /setup refuse.
func TestCompactRefusesMidTurn(t *testing.T) {
	m := newTestModel(t)
	m.phase = phaseRunning
	m.submit("/compact")

	if m.compacting {
		t.Error("/compact should not have started while a turn is running")
	}
	if out := m.tr.render(); !strings.Contains(out, "interrupt first") {
		t.Errorf("expected a refusal naming the way out, got %q", out)
	}
}

func TestCompactStartsWhenIdle(t *testing.T) {
	m := newTestModel(t)
	m.submit("/compact")

	if !m.compacting {
		t.Fatal("/compact should put the model into its compacting state")
	}
	if !strings.Contains(plain(m.activity()), "Compacting") {
		t.Errorf("the activity line should say what is happening, got %q", plain(m.activity()))
	}
}

// The context pill reports the last turn's input size. After a manual
// compaction no request follows to correct it, so leaving it alone would park
// a pre-compaction number on screen indefinitely.
func TestCompactClearsTheContextPill(t *testing.T) {
	m := newTestModel(t)
	m.compacting = true
	m.ctxTokens = 970_000

	m.Update(compactDone{did: true})

	if m.compacting {
		t.Error("compactDone should end the compacting state")
	}
	if m.ctxTokens != 0 {
		t.Errorf("ctxTokens = %d, want 0: the pre-compaction figure is no longer true", m.ctxTokens)
	}
	if _, ok := m.contextPill(); ok {
		t.Error("the pill should be hidden until the next turn measures the window again")
	}
}

func TestCompactReportsNothingToFold(t *testing.T) {
	m := newTestModel(t)
	m.compacting = true
	m.ctxTokens = 1234

	m.Update(compactDone{did: false})

	if out := m.tr.render(); !strings.Contains(out, "nothing to compact") {
		t.Errorf("expected a notice that nothing was folded, got %q", out)
	}
	if m.ctxTokens != 1234 {
		t.Error("nothing was folded, so the context pill still reports the truth")
	}
}

func TestCompactReportsFailure(t *testing.T) {
	m := newTestModel(t)
	m.compacting = true

	m.Update(compactDone{err: errors.New("the endpoint said no")})

	out := m.tr.render()
	if !strings.Contains(out, "the endpoint said no") {
		t.Errorf("a failed compaction should say why, got %q", out)
	}
	if m.compacting {
		t.Error("a failed compaction still ends the compacting state")
	}
}

// A notice from the agent loop — the window is full and nothing can be folded
// — has to reach the transcript, or the next request fails with no explanation.
func TestAgentNoticeReachesTheTranscript(t *testing.T) {
	m := newTestModel(t)
	m.Update(evNotice{text: "the context window is 97% full"})

	if out := m.tr.render(); !strings.Contains(out, "97% full") {
		t.Errorf("expected the notice in the transcript, got %q", out)
	}
}

// A turn stopped for want of room has two ways out, and neither is obvious
// from the agent's message on its own — the agent package does not know what a
// slash command is.
func TestContextFullErrorOffersTheWayOut(t *testing.T) {
	m := newTestModel(t)
	m.Update(turnDone{err: &agent.ContextFullError{Used: 970_000, Window: 1_000_000, Floor: 0.97}})

	out := m.tr.render()
	for _, want := range []string{"97% full", "/compact", "/clear"} {
		if !strings.Contains(out, want) {
			t.Errorf("the transcript should mention %q:\n%s", want, out)
		}
	}
}