github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/cli/clitest/apptest.go (about)

     1  // Package clitest provides utilities for testing cli.App.
     2  package clitest
     3  
     4  import (
     5  	"testing"
     6  
     7  	"github.com/markusbkk/elvish/pkg/cli"
     8  	"github.com/markusbkk/elvish/pkg/cli/term"
     9  	"github.com/markusbkk/elvish/pkg/ui"
    10  )
    11  
    12  // Styles defines a common stylesheet for unit tests.
    13  var Styles = ui.RuneStylesheet{
    14  	'_': ui.Underlined,
    15  	'b': ui.Bold,
    16  	'*': ui.Stylings(ui.Bold, ui.FgWhite, ui.BgMagenta),
    17  	'+': ui.Inverse,
    18  	'/': ui.FgBlue,
    19  	'#': ui.Stylings(ui.Inverse, ui.FgBlue),
    20  	'!': ui.FgRed,
    21  	'?': ui.Stylings(ui.FgBrightWhite, ui.BgRed),
    22  	'-': ui.FgMagenta,
    23  	'X': ui.Stylings(ui.Inverse, ui.FgMagenta),
    24  	'v': ui.FgGreen,
    25  	'V': ui.Stylings(ui.Underlined, ui.FgGreen),
    26  	'$': ui.FgMagenta,
    27  	'c': ui.FgCyan, // mnemonic "Comment"
    28  }
    29  
    30  // Fixture is a test fixture.
    31  type Fixture struct {
    32  	App    cli.App
    33  	TTY    TTYCtrl
    34  	width  int
    35  	codeCh <-chan string
    36  	errCh  <-chan error
    37  }
    38  
    39  // Setup sets up a test fixture. It contains an App whose ReadCode method has
    40  // been started asynchronously.
    41  func Setup(fns ...func(*cli.AppSpec, TTYCtrl)) *Fixture {
    42  	tty, ttyCtrl := NewFakeTTY()
    43  	spec := cli.AppSpec{TTY: tty}
    44  	for _, fn := range fns {
    45  		fn(&spec, ttyCtrl)
    46  	}
    47  	app := cli.NewApp(spec)
    48  	codeCh, errCh := StartReadCode(app.ReadCode)
    49  	_, width := tty.Size()
    50  	return &Fixture{app, ttyCtrl, width, codeCh, errCh}
    51  }
    52  
    53  // WithSpec takes a function that operates on *cli.AppSpec, and wraps it into a
    54  // form suitable for passing to Setup.
    55  func WithSpec(f func(*cli.AppSpec)) func(*cli.AppSpec, TTYCtrl) {
    56  	return func(spec *cli.AppSpec, _ TTYCtrl) { f(spec) }
    57  }
    58  
    59  // WithTTY takes a function that operates on TTYCtrl, and wraps it to a form
    60  // suitable for passing to Setup.
    61  func WithTTY(f func(TTYCtrl)) func(*cli.AppSpec, TTYCtrl) {
    62  	return func(_ *cli.AppSpec, tty TTYCtrl) { f(tty) }
    63  }
    64  
    65  // Wait waits for ReaCode to finish, and returns its return values.
    66  func (f *Fixture) Wait() (string, error) {
    67  	return <-f.codeCh, <-f.errCh
    68  }
    69  
    70  // Stop stops ReadCode and waits for it to finish. If ReadCode has already been
    71  // stopped, it is a no-op.
    72  func (f *Fixture) Stop() {
    73  	f.App.CommitEOF()
    74  	f.Wait()
    75  }
    76  
    77  // MakeBuffer is a helper for building a buffer. It is equivalent to
    78  // term.NewBufferBuilder(width of terminal).MarkLines(args...).Buffer().
    79  func (f *Fixture) MakeBuffer(args ...interface{}) *term.Buffer {
    80  	return term.NewBufferBuilder(f.width).MarkLines(args...).Buffer()
    81  }
    82  
    83  // TestTTY is equivalent to f.TTY.TestBuffer(f.MakeBuffer(args...)).
    84  func (f *Fixture) TestTTY(t *testing.T, args ...interface{}) {
    85  	t.Helper()
    86  	f.TTY.TestBuffer(t, f.MakeBuffer(args...))
    87  }
    88  
    89  // TestTTYNotes is equivalent to f.TTY.TestNotesBuffer(f.MakeBuffer(args...)).
    90  func (f *Fixture) TestTTYNotes(t *testing.T, args ...interface{}) {
    91  	t.Helper()
    92  	f.TTY.TestNotesBuffer(t, f.MakeBuffer(args...))
    93  }
    94  
    95  // StartReadCode starts the readCode function asynchronously, and returns two
    96  // channels that deliver its return values. The two channels are closed after
    97  // return values are delivered, so that subsequent reads will return zero values
    98  // and not block.
    99  func StartReadCode(readCode func() (string, error)) (<-chan string, <-chan error) {
   100  	codeCh := make(chan string, 1)
   101  	errCh := make(chan error, 1)
   102  	go func() {
   103  		code, err := readCode()
   104  		codeCh <- code
   105  		errCh <- err
   106  		close(codeCh)
   107  		close(errCh)
   108  	}()
   109  	return codeCh, errCh
   110  }