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 }