Move pkg/cli from core/go to core/cli. Includes Frame AppShell, Stream, TaskTracker, Tree, Rich Table. Update imports to v0.0.1 tagged deps and fix openpgp import path for go-crypt split. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
188 lines
3.9 KiB
Go
188 lines
3.9 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestTaskTracker_Good(t *testing.T) {
|
|
t.Run("add and complete tasks", func(t *testing.T) {
|
|
tr := NewTaskTracker()
|
|
tr.out = &bytes.Buffer{} // non-TTY
|
|
|
|
t1 := tr.Add("repo-a")
|
|
t2 := tr.Add("repo-b")
|
|
|
|
t1.Update("pulling...")
|
|
t2.Update("pulling...")
|
|
|
|
t1.Done("up to date")
|
|
t2.Done("3 commits behind")
|
|
|
|
out := tr.String()
|
|
assert.Contains(t, out, "repo-a")
|
|
assert.Contains(t, out, "repo-b")
|
|
assert.Contains(t, out, "up to date")
|
|
assert.Contains(t, out, "3 commits behind")
|
|
})
|
|
|
|
t.Run("task states", func(t *testing.T) {
|
|
tr := NewTaskTracker()
|
|
tr.out = &bytes.Buffer{}
|
|
|
|
task := tr.Add("test")
|
|
|
|
// Pending
|
|
_, _, state := task.snapshot()
|
|
assert.Equal(t, taskPending, state)
|
|
|
|
// Running
|
|
task.Update("working")
|
|
_, status, state := task.snapshot()
|
|
assert.Equal(t, taskRunning, state)
|
|
assert.Equal(t, "working", status)
|
|
|
|
// Done
|
|
task.Done("finished")
|
|
_, status, state = task.snapshot()
|
|
assert.Equal(t, taskDone, state)
|
|
assert.Equal(t, "finished", status)
|
|
})
|
|
|
|
t.Run("task fail", func(t *testing.T) {
|
|
tr := NewTaskTracker()
|
|
tr.out = &bytes.Buffer{}
|
|
|
|
task := tr.Add("bad-repo")
|
|
task.Fail("connection refused")
|
|
|
|
_, status, state := task.snapshot()
|
|
assert.Equal(t, taskFailed, state)
|
|
assert.Equal(t, "connection refused", status)
|
|
})
|
|
|
|
t.Run("concurrent updates", func(t *testing.T) {
|
|
tr := NewTaskTracker()
|
|
tr.out = &bytes.Buffer{}
|
|
|
|
var wg sync.WaitGroup
|
|
for i := range 10 {
|
|
task := tr.Add("task-" + string(rune('a'+i)))
|
|
wg.Add(1)
|
|
go func(t *TrackedTask) {
|
|
defer wg.Done()
|
|
t.Update("running")
|
|
time.Sleep(5 * time.Millisecond)
|
|
t.Done("ok")
|
|
}(task)
|
|
}
|
|
wg.Wait()
|
|
|
|
assert.True(t, tr.allDone())
|
|
})
|
|
|
|
t.Run("summary all passed", func(t *testing.T) {
|
|
tr := NewTaskTracker()
|
|
tr.out = &bytes.Buffer{}
|
|
|
|
tr.Add("a").Done("ok")
|
|
tr.Add("b").Done("ok")
|
|
tr.Add("c").Done("ok")
|
|
|
|
assert.Equal(t, "3/3 passed", tr.Summary())
|
|
})
|
|
|
|
t.Run("summary with failures", func(t *testing.T) {
|
|
tr := NewTaskTracker()
|
|
tr.out = &bytes.Buffer{}
|
|
|
|
tr.Add("a").Done("ok")
|
|
tr.Add("b").Fail("error")
|
|
tr.Add("c").Done("ok")
|
|
|
|
assert.Equal(t, "2/3 passed, 1 failed", tr.Summary())
|
|
})
|
|
|
|
t.Run("wait completes for non-TTY", func(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
tr := NewTaskTracker()
|
|
tr.out = &buf
|
|
|
|
task := tr.Add("quick")
|
|
go func() {
|
|
time.Sleep(10 * time.Millisecond)
|
|
task.Done("done")
|
|
}()
|
|
|
|
tr.Wait()
|
|
assert.Contains(t, buf.String(), "quick")
|
|
assert.Contains(t, buf.String(), "done")
|
|
})
|
|
|
|
t.Run("name width alignment", func(t *testing.T) {
|
|
tr := NewTaskTracker()
|
|
tr.out = &bytes.Buffer{}
|
|
|
|
tr.Add("short")
|
|
tr.Add("very-long-repo-name")
|
|
|
|
w := tr.nameWidth()
|
|
assert.Equal(t, 19, w)
|
|
})
|
|
|
|
t.Run("String output format", func(t *testing.T) {
|
|
tr := NewTaskTracker()
|
|
tr.out = &bytes.Buffer{}
|
|
|
|
tr.Add("repo-a").Done("clean")
|
|
tr.Add("repo-b").Fail("dirty")
|
|
tr.Add("repo-c").Update("pulling")
|
|
|
|
out := tr.String()
|
|
assert.Contains(t, out, "✓")
|
|
assert.Contains(t, out, "✗")
|
|
assert.Contains(t, out, "⠋")
|
|
})
|
|
}
|
|
|
|
func TestTaskTracker_Bad(t *testing.T) {
|
|
t.Run("allDone with no tasks", func(t *testing.T) {
|
|
tr := NewTaskTracker()
|
|
tr.out = &bytes.Buffer{}
|
|
assert.True(t, tr.allDone())
|
|
})
|
|
|
|
t.Run("allDone incomplete", func(t *testing.T) {
|
|
tr := NewTaskTracker()
|
|
tr.out = &bytes.Buffer{}
|
|
tr.Add("pending")
|
|
assert.False(t, tr.allDone())
|
|
})
|
|
}
|
|
|
|
func TestTrackedTask_Good(t *testing.T) {
|
|
t.Run("thread safety", func(t *testing.T) {
|
|
tr := NewTaskTracker()
|
|
tr.out = &bytes.Buffer{}
|
|
task := tr.Add("concurrent")
|
|
|
|
var wg sync.WaitGroup
|
|
for range 100 {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
task.Update("running")
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
|
|
_, status, state := task.snapshot()
|
|
require.Equal(t, taskRunning, state)
|
|
require.Equal(t, "running", status)
|
|
})
|
|
}
|