github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/cli/prompt/prompt_test.go (about) 1 package prompt 2 3 import ( 4 "fmt" 5 "reflect" 6 "testing" 7 "time" 8 9 "src.elv.sh/pkg/testutil" 10 "src.elv.sh/pkg/ui" 11 ) 12 13 func TestPrompt_DefaultCompute(t *testing.T) { 14 prompt := New(Config{}) 15 16 prompt.Trigger(false) 17 testUpdate(t, prompt, ui.T("???> ")) 18 } 19 20 func TestPrompt_ShowsComputedPrompt(t *testing.T) { 21 prompt := New(Config{ 22 Compute: func() ui.Text { return ui.T(">>> ") }}) 23 24 prompt.Trigger(false) 25 testUpdate(t, prompt, ui.T(">>> ")) 26 } 27 28 func TestPrompt_StalePrompt(t *testing.T) { 29 compute, unblock := blockedAutoIncPrompt() 30 prompt := New(Config{ 31 Compute: compute, 32 StaleThreshold: func() time.Duration { 33 return testutil.ScaledMs(10) 34 }, 35 }) 36 37 prompt.Trigger(true) 38 // The compute function is blocked, so a stale version of the initial 39 // "unknown" prompt will be shown. 40 testUpdate(t, prompt, ui.T("???> ", ui.Inverse)) 41 42 // The compute function will now return. 43 unblock() 44 // The returned prompt will now be used. 45 testUpdate(t, prompt, ui.T("1> ")) 46 47 // Force a refresh. 48 prompt.Trigger(true) 49 // The compute function will now be blocked again, so after a while a stale 50 // version of the previous prompt will be shown. 51 testUpdate(t, prompt, ui.T("1> ", ui.Inverse)) 52 53 // Unblock the compute function. 54 unblock() 55 // The new prompt will now be shown. 56 testUpdate(t, prompt, ui.T("2> ")) 57 58 // Force a refresh. 59 prompt.Trigger(true) 60 // Make sure that the compute function is run and stuck. 61 testUpdate(t, prompt, ui.T("2> ", ui.Inverse)) 62 // Queue another two refreshes before the compute function can return. 63 prompt.Trigger(true) 64 prompt.Trigger(true) 65 unblock() 66 // Now the new prompt should be marked stale immediately. 67 testUpdate(t, prompt, ui.T("3> ", ui.Inverse)) 68 unblock() 69 // However, the the two refreshes we requested early only trigger one 70 // re-computation, because they are requested while the compute function is 71 // stuck, so they can be safely merged. 72 testUpdate(t, prompt, ui.T("4> ")) 73 } 74 75 func TestPrompt_Eagerness0(t *testing.T) { 76 prompt := New(Config{ 77 Compute: autoIncPrompt(), 78 Eagerness: func() int { return 0 }, 79 }) 80 81 // A forced refresh is always respected. 82 prompt.Trigger(true) 83 testUpdate(t, prompt, ui.T("1> ")) 84 85 // A unforced refresh is not respected. 86 prompt.Trigger(false) 87 testNoUpdate(t, prompt) 88 89 // No update even if pwd has changed. 90 _, cleanup := testutil.InTestDir() 91 defer cleanup() 92 prompt.Trigger(false) 93 testNoUpdate(t, prompt) 94 95 // Only force updates are respected. 96 prompt.Trigger(true) 97 testUpdate(t, prompt, ui.T("2> ")) 98 } 99 100 func TestPrompt_Eagerness5(t *testing.T) { 101 prompt := New(Config{ 102 Compute: autoIncPrompt(), 103 Eagerness: func() int { return 5 }, 104 }) 105 106 // The initial trigger is respected because there was no previous pwd. 107 prompt.Trigger(false) 108 testUpdate(t, prompt, ui.T("1> ")) 109 110 // No update because the pwd has not changed. 111 prompt.Trigger(false) 112 testNoUpdate(t, prompt) 113 114 // Update because the pwd has changed. 115 _, cleanup := testutil.InTestDir() 116 defer cleanup() 117 prompt.Trigger(false) 118 testUpdate(t, prompt, ui.T("2> ")) 119 } 120 121 func TestPrompt_Eagerness10(t *testing.T) { 122 prompt := New(Config{ 123 Compute: autoIncPrompt(), 124 Eagerness: func() int { return 10 }, 125 }) 126 127 // The initial trigger is respected. 128 prompt.Trigger(false) 129 testUpdate(t, prompt, ui.T("1> ")) 130 131 // Subsequent triggers, force or not, are also respected. 132 prompt.Trigger(false) 133 testUpdate(t, prompt, ui.T("2> ")) 134 prompt.Trigger(true) 135 testUpdate(t, prompt, ui.T("3> ")) 136 prompt.Trigger(false) 137 testUpdate(t, prompt, ui.T("4> ")) 138 } 139 140 func blockedAutoIncPrompt() (func() ui.Text, func()) { 141 unblockChan := make(chan struct{}) 142 i := 0 143 compute := func() ui.Text { 144 <-unblockChan 145 i++ 146 return ui.T(fmt.Sprintf("%d> ", i)) 147 } 148 unblock := func() { 149 unblockChan <- struct{}{} 150 } 151 return compute, unblock 152 } 153 154 func autoIncPrompt() func() ui.Text { 155 i := 0 156 return func() ui.Text { 157 i++ 158 return ui.T(fmt.Sprintf("%d> ", i)) 159 } 160 } 161 162 func testUpdate(t *testing.T, p *Prompt, wantUpdate ui.Text) { 163 t.Helper() 164 select { 165 case <-p.LateUpdates(): 166 update := p.Get() 167 if !reflect.DeepEqual(update, wantUpdate) { 168 t.Errorf("got updated %v, want %v", update, wantUpdate) 169 } 170 case <-time.After(time.Second): 171 t.Errorf("no late update after 1 second") 172 } 173 } 174 175 func testNoUpdate(t *testing.T, p *Prompt) { 176 t.Helper() 177 select { 178 case update := <-p.LateUpdates(): 179 t.Errorf("unexpected update %v", update) 180 case <-time.After(testutil.ScaledMs(10)): 181 // OK 182 } 183 }