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  }