Vectrum
Sign In
108 lines · 3.8 KB · Go
package tools

import (
	"strings"
	"testing"
)

func TestTodoWriteRendersAndStores(t *testing.T) {
	todos := NewTodos()
	got, err := run(t, NewTodoWrite(todos), TodoWriteArgs{Todos: []Todo{
		{Content: "Read the failing test", Status: TodoDone},
		{Content: "Fix the parser", Status: TodoInProgress},
		{Content: "Run the suite", Status: TodoPending},
	}})
	if err != nil {
		t.Fatalf("todo_write: %v", err)
	}
	for _, want := range []string{"[x] Read the failing test", "[»] Fix the parser", "[ ] Run the suite", "1 of 3 done"} {
		if !strings.Contains(got, want) {
			t.Errorf("rendering lacks %q:\n%s", want, got)
		}
	}
	if snap := todos.Snapshot(); len(snap) != 3 || snap[1].Status != TodoInProgress {
		t.Errorf("the list was not stored for a front end to read: %+v", snap)
	}
}

// The list is replaced wholesale, not merged: a model sending three items
// after four must end up with three.
func TestTodoWriteReplacesTheWholeList(t *testing.T) {
	todos := NewTodos()
	tool := NewTodoWrite(todos)
	run(t, tool, TodoWriteArgs{Todos: []Todo{
		{Content: "a", Status: TodoPending}, {Content: "b", Status: TodoPending},
		{Content: "c", Status: TodoPending}, {Content: "d", Status: TodoPending},
	}})
	run(t, tool, TodoWriteArgs{Todos: []Todo{{Content: "only", Status: TodoInProgress}}})
	if snap := todos.Snapshot(); len(snap) != 1 || snap[0].Content != "only" {
		t.Errorf("list = %+v, want it replaced", snap)
	}
}

// Snapshot must not hand out the slice the tool keeps writing to.
func TestTodoSnapshotIsACopy(t *testing.T) {
	todos := NewTodos()
	run(t, NewTodoWrite(todos), TodoWriteArgs{Todos: []Todo{{Content: "a", Status: TodoPending}}})
	snap := todos.Snapshot()
	snap[0].Content = "mutated"
	if todos.Snapshot()[0].Content != "a" {
		t.Error("Snapshot handed out the live slice")
	}
}

func TestTodoWriteRejectsBadInput(t *testing.T) {
	tool := NewTodoWrite(NewTodos())
	if _, err := run(t, tool, TodoWriteArgs{}); err == nil {
		t.Error("an empty list must be refused")
	}
	_, err := run(t, tool, TodoWriteArgs{Todos: []Todo{{Content: "x", Status: "doing"}}})
	if err == nil || !strings.Contains(err.Error(), TodoInProgress) {
		t.Errorf("err = %v, want it to list the statuses that exist", err)
	}
	if _, err := run(t, tool, TodoWriteArgs{Todos: []Todo{{Content: "  ", Status: TodoPending}}}); err == nil {
		t.Error("an item with no content must be refused")
	}
}

// Two in_progress items are a nudge, not a failure: refusing would cost a
// whole turn to fix something that changes nothing about the work.
func TestTodoWriteNudgesRatherThanFailingOnTwoInProgress(t *testing.T) {
	got, err := run(t, NewTodoWrite(NewTodos()), TodoWriteArgs{Todos: []Todo{
		{Content: "a", Status: TodoInProgress}, {Content: "b", Status: TodoInProgress},
	}})
	if err != nil {
		t.Fatalf("this should be accepted: %v", err)
	}
	if !strings.Contains(got, "2 items are in_progress") {
		t.Errorf("no nudge about the second in_progress item:\n%s", got)
	}
}

func TestTodoWriteNotesWhenNothingIsStarted(t *testing.T) {
	got, _ := run(t, NewTodoWrite(NewTodos()), TodoWriteArgs{Todos: []Todo{
		{Content: "a", Status: TodoPending}, {Content: "b", Status: TodoDone},
	}})
	if !strings.Contains(got, "Nothing is in_progress") {
		t.Errorf("a stalled list should say so:\n%s", got)
	}
}

// A finished list is not nagged at.
func TestTodoWriteSaysNothingWhenAllDone(t *testing.T) {
	got, _ := run(t, NewTodoWrite(NewTodos()), TodoWriteArgs{Todos: []Todo{
		{Content: "a", Status: TodoDone},
	}})
	if strings.Contains(got, "Nothing is in_progress") {
		t.Errorf("a completed list needs no nudge:\n%s", got)
	}
	if !strings.Contains(got, "1 of 1 done") {
		t.Errorf("missing the count:\n%s", got)
	}
}

func TestTodoWriteIsNotRegisteredWithoutAStore(t *testing.T) {
	if NewTodoWrite(nil) != nil {
		t.Error("with nowhere to keep the list the tool must not exist")
	}
}