Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions pkg/term/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 51 additions & 0 deletions pkg/term/env_test.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down
65 changes: 65 additions & 0 deletions pkg/term/test.go
Original file line number Diff line number Diff line change
@@ -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
}