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