From e337ad39870d393be6e3207355b1ab19b52e7892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Wed, 23 Sep 2026 08:54:20 +0300 Subject: [PATCH] feat(term): add configurable test terminal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Efe Gökdemir --- pkg/term/env.go | 16 +++++++++++ pkg/term/env_test.go | 51 ++++++++++++++++++++++++++++++++++ pkg/term/test.go | 65 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 pkg/term/test.go diff --git a/pkg/term/env.go b/pkg/term/env.go index c650cc22..2c4a7845 100644 --- a/pkg/term/env.go +++ b/pkg/term/env.go @@ -12,6 +12,22 @@ import ( "golang.org/x/term" ) +// Terminal describes the terminal capabilities and streams used by a caller. +// +// Term and TestTerminal implement Terminal. The interface is useful when +// production code needs a deterministic terminal implementation in tests. +type Terminal interface { + In() io.Reader + Out() io.Writer + ErrOut() io.Writer + IsTerminalOutput() bool + IsColorEnabled() bool + Is256ColorSupported() bool + IsTrueColorSupported() bool + Size() (int, int, error) + Theme() string +} + // Term represents information about the terminal that a process is connected to. type Term struct { in *os.File diff --git a/pkg/term/env_test.go b/pkg/term/env_test.go index 1e7346b3..e8ccc428 100644 --- a/pkg/term/env_test.go +++ b/pkg/term/env_test.go @@ -1,9 +1,60 @@ package term import ( + "bytes" + "errors" "testing" ) +func TestTestTerminal(t *testing.T) { + input := bytes.NewBufferString("input") + output := &bytes.Buffer{} + errOutput := &bytes.Buffer{} + sizeErr := errors.New("size unavailable") + terminal := TestTerminal{ + Input: input, + Output: output, + ErrorOutput: errOutput, + TTY: true, + Color: true, + Color256: true, + TrueColor: true, + Width: 120, + Height: 40, + SizeError: sizeErr, + TerminalTheme: "dark", + } + + if got := terminal.In(); got != input { + t.Fatalf("expected input reader to be preserved") + } + if got := terminal.Out(); got != output { + t.Fatalf("expected output writer to be preserved") + } + if got := terminal.ErrOut(); got != errOutput { + t.Fatalf("expected error writer to be preserved") + } + if !terminal.IsTerminalOutput() || !terminal.IsColorEnabled() || !terminal.Is256ColorSupported() || !terminal.IsTrueColorSupported() { + t.Fatalf("expected configured terminal capabilities") + } + if width, height, err := terminal.Size(); width != 120 || height != 40 || !errors.Is(err, sizeErr) { + t.Fatalf("expected configured terminal size and error, got %d x %d, %v", width, height, err) + } + if got := terminal.Theme(); got != "dark" { + t.Fatalf("expected configured terminal theme, got %q", got) + } +} + +func TestTestTerminalZeroValue(t *testing.T) { + var terminal TestTerminal + if got := terminal.In(); got == nil { + t.Fatal("expected zero-value terminal to provide an input reader") + } + if width, height, err := terminal.Size(); width != 0 || height != 0 || err != nil { + t.Fatalf("expected zero-value size, got %d x %d, %v", width, height, err) + } +} + func TestFromEnv(t *testing.T) { tests := []struct { name string diff --git a/pkg/term/test.go b/pkg/term/test.go new file mode 100644 index 00000000..86043fe3 --- /dev/null +++ b/pkg/term/test.go @@ -0,0 +1,65 @@ +package term + +import ( + "io" + "strings" +) + +// TestTerminal is a configurable terminal implementation for tests. +// +// Its zero value is useful for non-interactive tests. Set the exported fields +// when a test needs to model terminal capabilities or a particular size. +type TestTerminal struct { + Input io.Reader + Output io.Writer + ErrorOutput io.Writer + TTY bool + Color bool + Color256 bool + TrueColor bool + Width int + Height int + SizeError error + TerminalTheme string +} + +var _ Terminal = TestTerminal{} + +func (t TestTerminal) In() io.Reader { + if t.Input == nil { + return strings.NewReader("") + } + return t.Input +} + +func (t TestTerminal) Out() io.Writer { + return t.Output +} + +func (t TestTerminal) ErrOut() io.Writer { + return t.ErrorOutput +} + +func (t TestTerminal) IsTerminalOutput() bool { + return t.TTY +} + +func (t TestTerminal) IsColorEnabled() bool { + return t.Color +} + +func (t TestTerminal) Is256ColorSupported() bool { + return t.Color256 +} + +func (t TestTerminal) IsTrueColorSupported() bool { + return t.TrueColor +} + +func (t TestTerminal) Size() (int, int, error) { + return t.Width, t.Height, t.SizeError +} + +func (t TestTerminal) Theme() string { + return t.TerminalTheme +}