package tui
import (
"errors"
"strings"
"testing"
"harness/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)
}
}
}