src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/edit/testutils_test.go (about)

     1  package edit
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"src.elv.sh/pkg/cli"
     8  	"src.elv.sh/pkg/cli/clitest"
     9  	"src.elv.sh/pkg/cli/term"
    10  	"src.elv.sh/pkg/cli/tk"
    11  	"src.elv.sh/pkg/eval"
    12  	"src.elv.sh/pkg/eval/vals"
    13  	"src.elv.sh/pkg/eval/vars"
    14  	"src.elv.sh/pkg/mods/file"
    15  	"src.elv.sh/pkg/parse"
    16  	"src.elv.sh/pkg/store"
    17  	"src.elv.sh/pkg/store/storedefs"
    18  	"src.elv.sh/pkg/testutil"
    19  	"src.elv.sh/pkg/tt"
    20  )
    21  
    22  // Aliases.
    23  var (
    24  	Args   = tt.Args
    25  	Styles = clitest.Styles
    26  )
    27  
    28  type fixture struct {
    29  	Editor  *Editor
    30  	TTYCtrl clitest.TTYCtrl
    31  	Evaler  *eval.Evaler
    32  	Store   storedefs.Store
    33  	Home    string
    34  
    35  	width  int
    36  	codeCh <-chan string
    37  	errCh  <-chan error
    38  }
    39  
    40  func rc(codes ...string) func(*fixture) {
    41  	return func(f *fixture) { evals(f.Evaler, codes...) }
    42  }
    43  
    44  func assign(name string, val any) func(*fixture) {
    45  	return func(f *fixture) {
    46  		f.Evaler.ExtendGlobal(eval.BuildNs().AddVar("temp", vars.NewReadOnly(val)))
    47  		evals(f.Evaler, "set "+name+" = $temp")
    48  	}
    49  }
    50  
    51  func storeOp(storeFn func(storedefs.Store)) func(*fixture) {
    52  	return func(f *fixture) {
    53  		storeFn(f.Store)
    54  		// TODO(xiaq): Don't depend on this Elvish API.
    55  		evals(f.Evaler, "edit:history:fast-forward")
    56  	}
    57  }
    58  
    59  func setup(c testutil.Cleanuper, fns ...func(*fixture)) *fixture {
    60  	st := store.MustTempStore(c)
    61  	home := testutil.InTempHome(c)
    62  	testutil.Setenv(c, "PATH", "")
    63  
    64  	tty, ttyCtrl := clitest.NewFakeTTY()
    65  	ev := eval.NewEvaler()
    66  	ev.ExtendGlobal(eval.BuildNs().AddNs("file", file.Ns))
    67  	ed := NewEditor(tty, ev, st)
    68  	ev.ExtendBuiltin(eval.BuildNs().AddNs("edit", ed))
    69  	evals(ev,
    70  		// This is the same as the default prompt for non-root users. This makes
    71  		// sure that the tests will work when run as root.
    72  		"set edit:prompt = { tilde-abbr $pwd; put '> ' }",
    73  		// This will simplify most tests against the terminal.
    74  		"set edit:rprompt = { }")
    75  	f := &fixture{Editor: ed, TTYCtrl: ttyCtrl, Evaler: ev, Store: st, Home: home}
    76  	for _, fn := range fns {
    77  		fn(f)
    78  	}
    79  	_, f.width = tty.Size()
    80  	f.codeCh, f.errCh = clitest.StartReadCode(f.Editor.ReadCode)
    81  	c.Cleanup(func() {
    82  		f.Editor.app.CommitEOF()
    83  		f.Wait()
    84  	})
    85  	return f
    86  }
    87  
    88  func (f *fixture) Wait() (string, error) {
    89  	return <-f.codeCh, <-f.errCh
    90  }
    91  
    92  func (f *fixture) MakeBuffer(args ...any) *term.Buffer {
    93  	return term.NewBufferBuilder(f.width).MarkLines(args...).Buffer()
    94  }
    95  
    96  func (f *fixture) TestTTY(t *testing.T, args ...any) {
    97  	t.Helper()
    98  	f.TTYCtrl.TestBuffer(t, f.MakeBuffer(args...))
    99  }
   100  
   101  func (f *fixture) TestTTYNotes(t *testing.T, args ...any) {
   102  	t.Helper()
   103  	f.TTYCtrl.TestNotesBuffer(t, f.MakeBuffer(args...))
   104  }
   105  
   106  func (f *fixture) SetCodeBuffer(b tk.CodeBuffer) {
   107  	codeArea(f.Editor.app).MutateState(func(s *tk.CodeAreaState) {
   108  		s.Buffer = b
   109  	})
   110  }
   111  
   112  func feedInput(ttyCtrl clitest.TTYCtrl, s string) {
   113  	for _, r := range s {
   114  		ttyCtrl.Inject(term.K(r))
   115  	}
   116  }
   117  
   118  func evals(ev *eval.Evaler, codes ...string) {
   119  	for _, code := range codes {
   120  		err := ev.Eval(parse.Source{Name: "[test]", Code: code}, eval.EvalCfg{})
   121  		if err != nil {
   122  			panic(fmt.Errorf("eval %q: %s", code, err))
   123  		}
   124  	}
   125  }
   126  
   127  func getGlobal(ev *eval.Evaler, name string) any {
   128  	v, _ := ev.Global().Index(name)
   129  	return v
   130  }
   131  
   132  func testGlobals(t *testing.T, ev *eval.Evaler, wantVals map[string]any) {
   133  	t.Helper()
   134  	for name, wantVal := range wantVals {
   135  		testGlobal(t, ev, name, wantVal)
   136  	}
   137  }
   138  
   139  func testGlobal(t *testing.T, ev *eval.Evaler, name string, wantVal any) {
   140  	t.Helper()
   141  	if val := getGlobal(ev, name); !vals.Equal(val, wantVal) {
   142  		t.Errorf("$%s = %s, want %s",
   143  			name, vals.ReprPlain(val), vals.ReprPlain(wantVal))
   144  	}
   145  }
   146  
   147  func testThatOutputErrorIsBubbled(t *testing.T, f *fixture, code string) {
   148  	t.Helper()
   149  	evals(f.Evaler, "var ret = (bool ?("+code+" >&-))")
   150  	// Exceptions are booleanly false
   151  	testGlobal(t, f.Evaler, "ret", false)
   152  }
   153  
   154  func codeArea(app cli.App) tk.CodeArea { return app.ActiveWidget().(tk.CodeArea) }