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

     1  package edit
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  	"time"
     8  
     9  	"src.elv.sh/pkg/cli/clitest"
    10  	"src.elv.sh/pkg/cli/term"
    11  	"src.elv.sh/pkg/testutil"
    12  	"src.elv.sh/pkg/ui"
    13  )
    14  
    15  func TestPrompt_ValueOutput(t *testing.T) {
    16  	f := setup(t, rc(`set edit:prompt = { put '#'; num 13; styled '> ' red }`))
    17  
    18  	f.TestTTY(t,
    19  		"#13> ", Styles,
    20  		"   !!", term.DotHere)
    21  }
    22  
    23  func TestPrompt_ByteOutput(t *testing.T) {
    24  	f := setup(t, rc(`set edit:prompt = { print 'bytes> ' }`))
    25  
    26  	f.TestTTY(t, "bytes> ", term.DotHere)
    27  }
    28  
    29  func TestPrompt_ParsesSGRInByteOutput(t *testing.T) {
    30  	f := setup(t, rc(`set edit:prompt = { print "\033[31mred\033[m> " }`))
    31  
    32  	f.TestTTY(t,
    33  		"red> ", Styles,
    34  		"!!!  ", term.DotHere)
    35  }
    36  
    37  func TestPrompt_NotifiesInvalidValueOutput(t *testing.T) {
    38  	f := setup(t, rc(`set edit:prompt = { put good [bad] good2 }`))
    39  
    40  	f.TestTTY(t, "goodgood2", term.DotHere)
    41  	f.TestTTYNotes(t, "invalid output type from prompt: list")
    42  }
    43  
    44  func TestPrompt_NotifiesException(t *testing.T) {
    45  	f := setup(t, rc(`set edit:prompt = { fail ERROR }`))
    46  
    47  	f.TestTTYNotes(t,
    48  		"[prompt error] ERROR\n",
    49  		`see stack trace with "show $edit:exceptions[0]"`)
    50  	evals(f.Evaler, `var excs = (count $edit:exceptions)`)
    51  	testGlobal(t, f.Evaler, "excs", 1)
    52  }
    53  
    54  func TestRPrompt(t *testing.T) {
    55  	f := setup(t, rc(`set edit:rprompt = { put 'RRR' }`))
    56  
    57  	f.TestTTY(t, "~> ", term.DotHere,
    58  		strings.Repeat(" ", clitest.FakeTTYWidth-6)+"RRR")
    59  }
    60  
    61  func TestPromptEagerness(t *testing.T) {
    62  	f := setup(t, rc(
    63  		`var i = 0`,
    64  		`set edit:prompt = { set i = (+ $i 1); put $i'> ' }`,
    65  		`set edit:-prompt-eagerness = 10`))
    66  
    67  	f.TestTTY(t, "1> ", term.DotHere)
    68  	// With eagerness = 10, any key press will cause the prompt to be
    69  	// recomputed.
    70  	f.TTYCtrl.Inject(term.K(ui.Backspace))
    71  	f.TestTTY(t, "2> ", term.DotHere)
    72  }
    73  
    74  func TestPromptStaleThreshold(t *testing.T) {
    75  	f := setup(t, rc(
    76  		`var pipe = (file:pipe)`,
    77  		`set edit:prompt = { nop (slurp < $pipe); put '> ' }`,
    78  		`set edit:prompt-stale-threshold = `+scaledMsAsSec(50)))
    79  
    80  	f.TestTTY(t,
    81  		"???> ", Styles,
    82  		"+++++", term.DotHere)
    83  
    84  	evals(f.Evaler, `file:close $pipe[w]`)
    85  	f.TestTTY(t, "> ", term.DotHere)
    86  	evals(f.Evaler, `file:close $pipe[r]`)
    87  }
    88  
    89  func TestPromptStaleTransform(t *testing.T) {
    90  	f := setup(t, rc(
    91  		`var pipe = (file:pipe)`,
    92  		`set edit:prompt = { nop (slurp < $pipe); put '> ' }`,
    93  		`set edit:prompt-stale-threshold = `+scaledMsAsSec(50),
    94  		`set edit:prompt-stale-transform = {|a| put S; put $a; put S }`))
    95  
    96  	f.TestTTY(t, "S???> S", term.DotHere)
    97  	evals(f.Evaler, `file:close $pipe[w]`)
    98  	evals(f.Evaler, `file:close $pipe[r]`)
    99  }
   100  
   101  func TestPromptStaleTransform_Exception(t *testing.T) {
   102  	f := setup(t, rc(
   103  		`var pipe = (file:pipe)`,
   104  		`set edit:prompt = { nop (slurp < $pipe); put '> ' }`,
   105  		`set edit:prompt-stale-threshold = `+scaledMsAsSec(50),
   106  		`set edit:prompt-stale-transform = {|_| fail ERROR }`))
   107  
   108  	f.TestTTYNotes(t,
   109  		"[prompt stale transform error] ERROR\n",
   110  		`see stack trace with "show $edit:exceptions[0]"`)
   111  	evals(f.Evaler, `var excs = (count $edit:exceptions)`)
   112  	testGlobal(t, f.Evaler, "excs", 1)
   113  }
   114  
   115  func TestRPromptPersistent_True(t *testing.T) {
   116  	testRPromptPersistent(t, `set edit:rprompt-persistent = $true`,
   117  		"~> "+strings.Repeat(" ", clitest.FakeTTYWidth-6)+"RRR",
   118  		"\n", term.DotHere,
   119  	)
   120  }
   121  
   122  func TestRPromptPersistent_False(t *testing.T) {
   123  	testRPromptPersistent(t, `set edit:rprompt-persistent = $false`,
   124  		"~> ", // no rprompt
   125  		"\n", term.DotHere,
   126  	)
   127  }
   128  
   129  func testRPromptPersistent(t *testing.T, code string, finalBuf ...any) {
   130  	f := setup(t, rc(`set edit:rprompt = { put RRR }`, code))
   131  
   132  	// Make sure that the UI has stabilized before hitting Enter.
   133  	f.TestTTY(t,
   134  		"~> ", term.DotHere,
   135  		strings.Repeat(" ", clitest.FakeTTYWidth-6), "RRR",
   136  	)
   137  
   138  	f.TTYCtrl.Inject(term.K('\n'))
   139  	f.TestTTY(t, finalBuf...)
   140  }
   141  
   142  func TestDefaultPromptForNonRoot(t *testing.T) {
   143  	f := setup(t, assign("edit:prompt", getDefaultPrompt(false)))
   144  
   145  	f.TestTTY(t, "~> ", term.DotHere)
   146  }
   147  
   148  func TestDefaultPromptForRoot(t *testing.T) {
   149  	f := setup(t, assign("edit:prompt", getDefaultPrompt(true)))
   150  
   151  	f.TestTTY(t,
   152  		"~# ", Styles,
   153  		" !!", term.DotHere)
   154  }
   155  
   156  func TestDefaultRPrompt(t *testing.T) {
   157  	f := setup(t, assign("edit:rprompt", getDefaultRPrompt("elf", "host")))
   158  
   159  	f.TestTTY(t,
   160  		"~> ", term.DotHere, strings.Repeat(" ", 39),
   161  		"elf@host", Styles,
   162  		"++++++++")
   163  }
   164  
   165  func scaledMsAsSec(ms int) string {
   166  	return fmt.Sprint(testutil.Scaled(time.Duration(ms) * time.Millisecond).Seconds())
   167  }