Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

198 lines
5.8 KiB
Go

package progress
import (
"bytes"
"errors"
"strings"
"testing"
)
func TestSpinnerDisabledModePrintsPlainText(t *testing.T) {
var buf bytes.Buffer
sp := NewSpinner(&buf)
sp.Disable()
sp.Start("Indexing repository")
sp.Report("walking files", 0, 0)
sp.Report("walking files", 50, 100) // same stage, must not re-emit
sp.Report("parsing", 0, 0) // new stage: finishes the previous, starts its own
sp.Done()
out := buf.String()
wants := []string{
"Indexing repository",
"walking files ...", // stage start
"✓ walking files · 50", // stage finish with final counter
"parsing ...", // next stage start
"✓ parsing", // finished by Done
"✓ Indexing repository", // overall summary
}
for _, w := range wants {
if !strings.Contains(out, w) {
t.Errorf("output missing %q\n--- got ---\n%s", w, out)
}
}
// Each stage prints exactly twice — one start line, one finish line —
// regardless of how many progress ticks arrive in between.
if got := strings.Count(out, "walking files"); got != 2 {
t.Errorf("expected stage 'walking files' to print exactly twice (start+finish), got %d\n--- got ---\n%s", got, out)
}
if idx := strings.Index(out, "parsing ..."); idx < strings.Index(out, "✓ walking files") {
t.Errorf("stage finish line must precede the next stage start\n--- got ---\n%s", out)
}
}
func TestSpinnerDisabledFailPrintsErrorLine(t *testing.T) {
var buf bytes.Buffer
sp := NewSpinner(&buf)
sp.Disable()
sp.Start("Stamping blame")
sp.Fail(errors.New("boom"))
out := buf.String()
if !strings.Contains(out, "✗ Stamping blame") {
t.Errorf("expected ✗ summary, got: %q", out)
}
if !strings.Contains(out, "boom") {
t.Errorf("expected error message in output, got: %q", out)
}
}
func TestSpinnerDoneIsIdempotent(t *testing.T) {
var buf bytes.Buffer
sp := NewSpinner(&buf)
sp.Disable()
sp.Start("Indexing")
sp.Done()
sp.Done() // must not double-print or panic
sp.Fail(errors.New("late")) // must be a no-op after Done
if got := strings.Count(buf.String(), "✓"); got != 1 {
t.Errorf("expected exactly one ✓, got %d in:\n%s", got, buf.String())
}
if strings.Contains(buf.String(), "✗") {
t.Errorf("expected no ✗ after successful Done, got: %s", buf.String())
}
}
func TestMultiFansOutAndSkipsNil(t *testing.T) {
a := &countingReporter{}
b := &countingReporter{}
r := Multi(a, nil, b)
r.Report("walk", 1, 10)
r.Report("parse", 0, 0)
if a.calls != 2 || b.calls != 2 {
t.Errorf("expected 2 calls each, got a=%d b=%d", a.calls, b.calls)
}
}
func TestMultiCollapsesToSingle(t *testing.T) {
a := &countingReporter{}
r := Multi(nil, a, nil)
if r != a {
t.Errorf("expected Multi with one non-nil to return that reporter directly")
}
}
func TestMultiAllNilReturnsNop(t *testing.T) {
r := Multi(nil, nil)
if _, ok := r.(Nop); !ok {
t.Errorf("expected Nop when all inputs nil, got %T", r)
}
}
type countingReporter struct{ calls int }
func (c *countingReporter) Report(string, int, int) { c.calls++ }
// TestASCIIGlyphFallbackOnOEMCodepage proves the F8 contract: a terminal that
// cannot render UTF-8 (a legacy OEM codepage, a linux virtual console, or an
// explicit GORTEX_ASCII) gets an ASCII glyph set for the spinner finish
// markers AND the box-drawing border — not just the check/cross glyphs.
func TestASCIIGlyphFallbackOnOEMCodepage(t *testing.T) {
t.Run("ascii_override_selects_ascii_set", func(t *testing.T) {
t.Setenv("GORTEX_UNICODE", "")
t.Setenv("GORTEX_ASCII", "1")
if supportsUnicode() {
t.Fatal("GORTEX_ASCII=1 must disable unicode")
}
g := activeGlyphs()
if g.OK != "+" || g.Fail != "x" {
t.Errorf("ascii markers = %q/%q, want +/x", g.OK, g.Fail)
}
// The border charset is ASCII too — the beat-codegraph axis.
if g.Border.TopLeft != "+" {
t.Errorf("ascii border TopLeft = %q, want +", g.Border.TopLeft)
}
})
t.Run("unicode_override_wins_over_term_linux", func(t *testing.T) {
t.Setenv("GORTEX_ASCII", "")
t.Setenv("TERM", "linux")
t.Setenv("GORTEX_UNICODE", "1")
if !supportsUnicode() {
t.Fatal("GORTEX_UNICODE=1 must enable unicode even on TERM=linux")
}
if activeGlyphs().OK != "✓" {
t.Error("unicode set must use ✓")
}
})
t.Run("term_linux_falls_back_to_ascii", func(t *testing.T) {
t.Setenv("GORTEX_ASCII", "")
t.Setenv("GORTEX_UNICODE", "")
t.Setenv("TERM", "linux")
if supportsUnicode() {
t.Fatal("TERM=linux must fall back to ASCII")
}
})
t.Run("spinner_done_uses_ascii_marker", func(t *testing.T) {
t.Setenv("GORTEX_UNICODE", "")
t.Setenv("GORTEX_ASCII", "1")
var buf bytes.Buffer
sp := NewSpinner(&buf) // not a TTY → disabled → direct Fprintf path
sp.Start("Indexing")
sp.Done()
out := buf.String()
if !strings.Contains(out, "+ Indexing") {
t.Errorf("done line should use the ASCII marker; got %q", out)
}
if strings.Contains(out, "✓") {
t.Errorf("done line must not contain the unicode check; got %q", out)
}
})
t.Run("spinner_fail_uses_ascii_cross", func(t *testing.T) {
t.Setenv("GORTEX_UNICODE", "")
t.Setenv("GORTEX_ASCII", "1")
var buf bytes.Buffer
sp := NewSpinner(&buf)
sp.Start("Indexing")
sp.Fail(errors.New("boom"))
out := buf.String()
if !strings.Contains(out, "x Indexing") {
t.Errorf("fail line should use the ASCII cross; got %q", out)
}
if strings.Contains(out, "✗") {
t.Errorf("fail line must not contain the unicode cross; got %q", out)
}
})
t.Run("card_border_is_ascii", func(t *testing.T) {
t.Setenv("GORTEX_UNICODE", "")
t.Setenv("GORTEX_ASCII", "1")
card := Card("", "hello")
if !strings.Contains(card, "+") {
t.Errorf("ascii card must use + corners; got %q", card)
}
if strings.ContainsAny(card, "╭╮╰╯─│") {
t.Errorf("ascii card must not use rounded box-drawing; got %q", card)
}
})
}