src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/cli/tk/codearea_test.go (about)

     1  package tk
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  
     7  	"src.elv.sh/pkg/cli/term"
     8  	"src.elv.sh/pkg/tt"
     9  	"src.elv.sh/pkg/ui"
    10  )
    11  
    12  var Args = tt.Args
    13  
    14  var bb = term.NewBufferBuilder
    15  
    16  func p(t ui.Text) func() ui.Text { return func() ui.Text { return t } }
    17  
    18  var codeAreaRenderTests = []renderTest{
    19  	{
    20  		Name: "prompt only",
    21  		Given: NewCodeArea(CodeAreaSpec{
    22  			Prompt: p(ui.T("~>", ui.Bold))}),
    23  		Width: 10, Height: 24,
    24  		Want: bb(10).WriteStringSGR("~>", "1").SetDotHere(),
    25  	},
    26  	{
    27  		Name: "rprompt only",
    28  		Given: NewCodeArea(CodeAreaSpec{
    29  			RPrompt: p(ui.T("RP", ui.Inverse))}),
    30  		Width: 10, Height: 24,
    31  		Want: bb(10).SetDotHere().WriteSpaces(8).WriteStringSGR("RP", "7"),
    32  	},
    33  	{
    34  		Name: "code only with dot at beginning",
    35  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
    36  			Buffer: CodeBuffer{Content: "code", Dot: 0}}}),
    37  		Width: 10, Height: 24,
    38  		Want: bb(10).SetDotHere().Write("code"),
    39  	},
    40  	{
    41  		Name: "code only with dot at middle",
    42  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
    43  			Buffer: CodeBuffer{Content: "code", Dot: 2}}}),
    44  		Width: 10, Height: 24,
    45  		Want: bb(10).Write("co").SetDotHere().Write("de"),
    46  	},
    47  	{
    48  		Name: "code only with dot at end",
    49  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
    50  			Buffer: CodeBuffer{Content: "code", Dot: 4}}}),
    51  		Width: 10, Height: 24,
    52  		Want: bb(10).Write("code").SetDotHere(),
    53  	},
    54  	{
    55  		Name: "prompt, code and rprompt",
    56  		Given: NewCodeArea(CodeAreaSpec{
    57  			Prompt:  p(ui.T("~>")),
    58  			RPrompt: p(ui.T("RP")),
    59  			State:   CodeAreaState{Buffer: CodeBuffer{Content: "code", Dot: 4}}}),
    60  		Width: 10, Height: 24,
    61  		Want: bb(10).Write("~>code").SetDotHere().Write("  RP"),
    62  	},
    63  
    64  	{
    65  		Name: "prompt explicitly hidden ",
    66  		Given: NewCodeArea(CodeAreaSpec{
    67  			Prompt:  p(ui.T("~>")),
    68  			RPrompt: p(ui.T("RP")),
    69  			State:   CodeAreaState{Buffer: CodeBuffer{Content: "code", Dot: 4}, HideRPrompt: true}}),
    70  		Width: 10, Height: 24,
    71  		Want: bb(10).Write("~>code").SetDotHere(),
    72  	},
    73  	{
    74  		Name: "rprompt too long",
    75  		Given: NewCodeArea(CodeAreaSpec{
    76  			Prompt:  p(ui.T("~>")),
    77  			RPrompt: p(ui.T("1234")),
    78  			State:   CodeAreaState{Buffer: CodeBuffer{Content: "code", Dot: 4}}}),
    79  		Width: 10, Height: 24,
    80  		Want: bb(10).Write("~>code").SetDotHere(),
    81  	},
    82  	{
    83  		Name: "highlighted code",
    84  		Given: NewCodeArea(CodeAreaSpec{
    85  			Highlighter: func(code string) (ui.Text, []ui.Text) {
    86  				return ui.T(code, ui.Bold), nil
    87  			},
    88  			State: CodeAreaState{Buffer: CodeBuffer{Content: "code", Dot: 4}}}),
    89  		Width: 10, Height: 24,
    90  		Want: bb(10).WriteStringSGR("code", "1").SetDotHere(),
    91  	},
    92  	{
    93  		Name: "tips",
    94  		Given: NewCodeArea(CodeAreaSpec{
    95  			Prompt: p(ui.T("> ")),
    96  			Highlighter: func(code string) (ui.Text, []ui.Text) {
    97  				return ui.T(code), []ui.Text{ui.T("static error")}
    98  			},
    99  			State: CodeAreaState{Buffer: CodeBuffer{Content: "code", Dot: 4}}}),
   100  		Width: 10, Height: 24,
   101  		Want: bb(10).Write("> code").SetDotHere().
   102  			Newline().Write("static error"),
   103  	},
   104  	{
   105  		Name: "hiding tips",
   106  		Given: NewCodeArea(CodeAreaSpec{
   107  			Prompt: p(ui.T("> ")),
   108  			Highlighter: func(code string) (ui.Text, []ui.Text) {
   109  				return ui.T(code), []ui.Text{ui.T("static error")}
   110  			},
   111  			State: CodeAreaState{
   112  				Buffer: CodeBuffer{Content: "code", Dot: 4}, HideTips: true}}),
   113  		Width: 10, Height: 24,
   114  		Want: bb(10).Write("> code").SetDotHere(),
   115  	},
   116  	{
   117  		Name: "pending code inserting at the dot",
   118  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
   119  			Buffer:  CodeBuffer{Content: "code", Dot: 4},
   120  			Pending: PendingCode{From: 4, To: 4, Content: "x"},
   121  		}}),
   122  		Width: 10, Height: 24,
   123  		Want: bb(10).Write("code").WriteStringSGR("x", "4").SetDotHere(),
   124  	},
   125  	{
   126  		Name: "pending code replacing at the dot",
   127  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
   128  			Buffer:  CodeBuffer{Content: "code", Dot: 2},
   129  			Pending: PendingCode{From: 2, To: 4, Content: "x"},
   130  		}}),
   131  		Width: 10, Height: 24,
   132  		Want: bb(10).Write("co").WriteStringSGR("x", "4").SetDotHere(),
   133  	},
   134  	{
   135  		Name: "pending code to the left of the dot",
   136  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
   137  			Buffer:  CodeBuffer{Content: "code", Dot: 4},
   138  			Pending: PendingCode{From: 1, To: 3, Content: "x"},
   139  		}}),
   140  		Width: 10, Height: 24,
   141  		Want: bb(10).Write("c").WriteStringSGR("x", "4").Write("e").SetDotHere(),
   142  	},
   143  	{
   144  		Name: "pending code to the right of the cursor",
   145  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
   146  			Buffer:  CodeBuffer{Content: "code", Dot: 1},
   147  			Pending: PendingCode{From: 2, To: 3, Content: "x"},
   148  		}}),
   149  		Width: 10, Height: 24,
   150  		Want: bb(10).Write("c").SetDotHere().Write("o").
   151  			WriteStringSGR("x", "4").Write("e"),
   152  	},
   153  	{
   154  		Name: "ignore invalid pending code 1",
   155  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
   156  			Buffer:  CodeBuffer{Content: "code", Dot: 4},
   157  			Pending: PendingCode{From: 2, To: 1, Content: "x"},
   158  		}}),
   159  		Width: 10, Height: 24,
   160  		Want: bb(10).Write("code").SetDotHere(),
   161  	},
   162  	{
   163  		Name: "ignore invalid pending code 2",
   164  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
   165  			Buffer:  CodeBuffer{Content: "code", Dot: 4},
   166  			Pending: PendingCode{From: 5, To: 6, Content: "x"},
   167  		}}),
   168  		Width: 10, Height: 24,
   169  		Want: bb(10).Write("code").SetDotHere(),
   170  	},
   171  	{
   172  		Name: "prioritize lines before the cursor with small height",
   173  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
   174  			Buffer: CodeBuffer{Content: "a\nb\nc\nd", Dot: 3},
   175  		}}),
   176  		Width: 10, Height: 2,
   177  		Want: bb(10).Write("a").Newline().Write("b").SetDotHere(),
   178  	},
   179  	{
   180  		Name: "show only the cursor line when height is 1",
   181  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
   182  			Buffer: CodeBuffer{Content: "a\nb\nc\nd", Dot: 3},
   183  		}}),
   184  		Width: 10, Height: 1,
   185  		Want: bb(10).Write("b").SetDotHere(),
   186  	},
   187  	{
   188  		Name: "show lines after the cursor when all lines before the cursor are shown",
   189  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
   190  			Buffer: CodeBuffer{Content: "a\nb\nc\nd", Dot: 3},
   191  		}}),
   192  		Width: 10, Height: 3,
   193  		Want: bb(10).Write("a").Newline().Write("b").SetDotHere().
   194  			Newline().Write("c"),
   195  	},
   196  }
   197  
   198  func TestCodeArea_Render(t *testing.T) {
   199  	testRender(t, codeAreaRenderTests)
   200  }
   201  
   202  var codeAreaHandleTests = []handleTest{
   203  	{
   204  		Name:         "simple inserts",
   205  		Given:        NewCodeArea(CodeAreaSpec{}),
   206  		Events:       []term.Event{term.K('c'), term.K('o'), term.K('d'), term.K('e')},
   207  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "code", Dot: 4}},
   208  	},
   209  	{
   210  		Name:         "unicode inserts",
   211  		Given:        NewCodeArea(CodeAreaSpec{}),
   212  		Events:       []term.Event{term.K('你'), term.K('好')},
   213  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "你好", Dot: 6}},
   214  	},
   215  	{
   216  		Name:         "unterminated paste",
   217  		Given:        NewCodeArea(CodeAreaSpec{}),
   218  		Events:       []term.Event{term.PasteSetting(true), term.K('"'), term.K('x')},
   219  		WantNewState: CodeAreaState{},
   220  	},
   221  	{
   222  		Name:  "literal paste",
   223  		Given: NewCodeArea(CodeAreaSpec{}),
   224  		Events: []term.Event{
   225  			term.PasteSetting(true),
   226  			term.K('"'), term.K('x'),
   227  			term.PasteSetting(false)},
   228  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "\"x", Dot: 2}},
   229  	},
   230  	{
   231  		Name:  "literal paste swallowing functional keys",
   232  		Given: NewCodeArea(CodeAreaSpec{}),
   233  		Events: []term.Event{
   234  			term.PasteSetting(true),
   235  			term.K('a'), term.K(ui.F1), term.K('b'),
   236  			term.PasteSetting(false)},
   237  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "ab", Dot: 2}},
   238  	},
   239  	{
   240  		Name:  "quoted paste",
   241  		Given: NewCodeArea(CodeAreaSpec{QuotePaste: func() bool { return true }}),
   242  		Events: []term.Event{
   243  			term.PasteSetting(true),
   244  			term.K('"'), term.K('x'),
   245  			term.PasteSetting(false)},
   246  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "'\"x'", Dot: 4}},
   247  	},
   248  	{
   249  		Name:  "backspace at end of code",
   250  		Given: NewCodeArea(CodeAreaSpec{}),
   251  		Events: []term.Event{
   252  			term.K('c'), term.K('o'), term.K('d'), term.K('e'),
   253  			term.K(ui.Backspace)},
   254  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "cod", Dot: 3}},
   255  	},
   256  	{
   257  		Name: "backspace at middle of buffer",
   258  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
   259  			Buffer: CodeBuffer{Content: "code", Dot: 2}}}),
   260  		Events:       []term.Event{term.K(ui.Backspace)},
   261  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "cde", Dot: 1}},
   262  	},
   263  	{
   264  		Name: "backspace at beginning of buffer",
   265  		Given: NewCodeArea(CodeAreaSpec{State: CodeAreaState{
   266  			Buffer: CodeBuffer{Content: "code", Dot: 0}}}),
   267  		Events:       []term.Event{term.K(ui.Backspace)},
   268  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "code", Dot: 0}},
   269  	},
   270  	{
   271  		Name:  "backspace deleting unicode character",
   272  		Given: NewCodeArea(CodeAreaSpec{}),
   273  		Events: []term.Event{
   274  			term.K('你'), term.K('好'), term.K(ui.Backspace)},
   275  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "你", Dot: 3}},
   276  	},
   277  	// Regression test for https://b.elv.sh/1178
   278  	{
   279  		Name:  "Ctrl-H being equivalent to backspace",
   280  		Given: NewCodeArea(CodeAreaSpec{}),
   281  		Events: []term.Event{
   282  			term.K('c'), term.K('o'), term.K('d'), term.K('e'),
   283  			term.K('H', ui.Ctrl)},
   284  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "cod", Dot: 3}},
   285  	},
   286  	{
   287  		Name: "abbreviation expansion",
   288  		Given: NewCodeArea(CodeAreaSpec{
   289  			SimpleAbbreviations: func(f func(abbr, full string)) {
   290  				f("dn", "/dev/null")
   291  			},
   292  		}),
   293  		Events:       []term.Event{term.K('d'), term.K('n')},
   294  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "/dev/null", Dot: 9}},
   295  	},
   296  	{
   297  		Name: "abbreviation expansion 2",
   298  		Given: NewCodeArea(CodeAreaSpec{
   299  			SimpleAbbreviations: func(f func(abbr, full string)) {
   300  				f("||", " | less")
   301  			},
   302  		}),
   303  		Events:       []term.Event{term.K('x'), term.K('|'), term.K('|')},
   304  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "x | less", Dot: 8}},
   305  	},
   306  	{
   307  		Name: "abbreviation expansion after other content",
   308  		Given: NewCodeArea(CodeAreaSpec{
   309  			SimpleAbbreviations: func(f func(abbr, full string)) {
   310  				f("||", " | less")
   311  			},
   312  		}),
   313  		Events:       []term.Event{term.K('{'), term.K('e'), term.K('c'), term.K('h'), term.K('o'), term.K(' '), term.K('x'), term.K('}'), term.K('|'), term.K('|')},
   314  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "{echo x} | less", Dot: 15}},
   315  	},
   316  	{
   317  		Name: "abbreviation expansion preferring longest",
   318  		Given: NewCodeArea(CodeAreaSpec{
   319  			SimpleAbbreviations: func(f func(abbr, full string)) {
   320  				f("n", "none")
   321  				f("dn", "/dev/null")
   322  			},
   323  		}),
   324  		Events:       []term.Event{term.K('d'), term.K('n')},
   325  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "/dev/null", Dot: 9}},
   326  	},
   327  	{
   328  		Name: "abbreviation expansion interrupted by function key",
   329  		Given: NewCodeArea(CodeAreaSpec{
   330  			SimpleAbbreviations: func(f func(abbr, full string)) {
   331  				f("dn", "/dev/null")
   332  			},
   333  		}),
   334  		Events:       []term.Event{term.K('d'), term.K(ui.F1), term.K('n')},
   335  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "dn", Dot: 2}},
   336  	},
   337  	{
   338  		Name: "small word abbreviation expansion space trigger",
   339  		Given: NewCodeArea(CodeAreaSpec{
   340  			SmallWordAbbreviations: func(f func(abbr, full string)) {
   341  				f("eh", "echo hello")
   342  			},
   343  		}),
   344  		Events:       []term.Event{term.K('e'), term.K('h'), term.K(' ')},
   345  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "echo hello ", Dot: 11}},
   346  	},
   347  	{
   348  		Name: "small word abbreviation expansion non-space trigger",
   349  		Given: NewCodeArea(CodeAreaSpec{
   350  			SmallWordAbbreviations: func(f func(abbr, full string)) {
   351  				f("h", "hello")
   352  			},
   353  		}),
   354  		Events:       []term.Event{term.K('x'), term.K('['), term.K('h'), term.K(']')},
   355  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "x[hello]", Dot: 8}},
   356  	},
   357  	{
   358  		Name: "small word abbreviation expansion preceding char invalid",
   359  		Given: NewCodeArea(CodeAreaSpec{
   360  			SmallWordAbbreviations: func(f func(abbr, full string)) {
   361  				f("h", "hello")
   362  			},
   363  		}),
   364  		Events:       []term.Event{term.K('g'), term.K('h'), term.K(' ')},
   365  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "gh ", Dot: 3}},
   366  	},
   367  	{
   368  		Name: "small word abbreviation expansion after backspace preceding char invalid",
   369  		Given: NewCodeArea(CodeAreaSpec{
   370  			SmallWordAbbreviations: func(f func(abbr, full string)) {
   371  				f("h", "hello")
   372  			},
   373  		}),
   374  		Events: []term.Event{term.K('g'), term.K(' '), term.K(ui.Backspace),
   375  			term.K('h'), term.K(' ')},
   376  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "gh ", Dot: 3}},
   377  	},
   378  	{
   379  		Name: "command abbreviation expansion",
   380  		Given: NewCodeArea(CodeAreaSpec{
   381  			CommandAbbreviations: func(f func(abbr, full string)) {
   382  				f("eh", "echo hello")
   383  			},
   384  		}),
   385  		Events:       []term.Event{term.K('e'), term.K('h'), term.K(' ')},
   386  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "echo hello ", Dot: 11}},
   387  	},
   388  	{
   389  		Name: "command abbreviation expansion not at start of line",
   390  		Given: NewCodeArea(CodeAreaSpec{
   391  			CommandAbbreviations: func(f func(abbr, full string)) {
   392  				f("eh", "echo hello")
   393  			},
   394  		}),
   395  		Events:       []term.Event{term.K('x'), term.K('|'), term.K('e'), term.K('h'), term.K(' ')},
   396  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "x|echo hello ", Dot: 13}},
   397  	},
   398  	{
   399  		Name: "command abbreviation expansion at start of second line",
   400  		Given: NewCodeArea(CodeAreaSpec{
   401  			CommandAbbreviations: func(f func(abbr, full string)) {
   402  				f("eh", "echo hello")
   403  			},
   404  			State: CodeAreaState{Buffer: CodeBuffer{Content: "echo\n", Dot: 5}},
   405  		}),
   406  		Events:       []term.Event{term.K('e'), term.K('h'), term.K(' ')},
   407  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "echo\necho hello ", Dot: 16}},
   408  	},
   409  	{
   410  		Name: "no command abbreviation expansion when not in command position",
   411  		Given: NewCodeArea(CodeAreaSpec{
   412  			CommandAbbreviations: func(f func(abbr, full string)) {
   413  				f("eh", "echo hello")
   414  			},
   415  		}),
   416  		Events:       []term.Event{term.K('x'), term.K(' '), term.K('e'), term.K('h'), term.K(' ')},
   417  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "x eh ", Dot: 5}},
   418  	},
   419  	{
   420  		Name: "key bindings",
   421  		Given: NewCodeArea(CodeAreaSpec{Bindings: MapBindings{
   422  			term.K('a'): func(w Widget) {
   423  				w.(*codeArea).State.Buffer.InsertAtDot("b")
   424  			}},
   425  		}),
   426  		Events:       []term.Event{term.K('a')},
   427  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "b", Dot: 1}},
   428  	},
   429  	{
   430  		// Regression test for #890.
   431  		Name: "key bindings do not apply when pasting",
   432  		Given: NewCodeArea(CodeAreaSpec{Bindings: MapBindings{
   433  			term.K('\n'): func(w Widget) {}},
   434  		}),
   435  		Events: []term.Event{
   436  			term.PasteSetting(true), term.K('\n'), term.PasteSetting(false)},
   437  		WantNewState: CodeAreaState{Buffer: CodeBuffer{Content: "\n", Dot: 1}},
   438  	},
   439  }
   440  
   441  func TestCodeArea_Handle(t *testing.T) {
   442  	testHandle(t, codeAreaHandleTests)
   443  }
   444  
   445  var codeAreaUnhandledEvents = []term.Event{
   446  	// Mouse events are unhandled
   447  	term.MouseEvent{},
   448  	// Function keys are unhandled (except Backspace)
   449  	term.K(ui.F1),
   450  	term.K('X', ui.Ctrl),
   451  }
   452  
   453  func TestCodeArea_Handle_UnhandledEvents(t *testing.T) {
   454  	w := NewCodeArea(CodeAreaSpec{})
   455  	for _, event := range codeAreaUnhandledEvents {
   456  		handled := w.Handle(event)
   457  		if handled {
   458  			t.Errorf("event %v got handled", event)
   459  		}
   460  	}
   461  }
   462  
   463  func TestCodeArea_Handle_AbbreviationExpansionInterruptedByExternalMutation(t *testing.T) {
   464  	w := NewCodeArea(CodeAreaSpec{
   465  		SimpleAbbreviations: func(f func(abbr, full string)) {
   466  			f("dn", "/dev/null")
   467  		},
   468  	})
   469  	w.Handle(term.K('d'))
   470  	w.MutateState(func(s *CodeAreaState) { s.Buffer.InsertAtDot("d") })
   471  	w.Handle(term.K('n'))
   472  	wantState := CodeAreaState{Buffer: CodeBuffer{Content: "ddn", Dot: 3}}
   473  	if state := w.CopyState(); !reflect.DeepEqual(state, wantState) {
   474  		t.Errorf("got state %v, want %v", state, wantState)
   475  	}
   476  }
   477  
   478  func TestCodeArea_Handle_EnterEmitsSubmit(t *testing.T) {
   479  	submitted := false
   480  	w := NewCodeArea(CodeAreaSpec{
   481  		OnSubmit: func() { submitted = true },
   482  		State:    CodeAreaState{Buffer: CodeBuffer{Content: "code", Dot: 4}}})
   483  	w.Handle(term.K('\n'))
   484  	if submitted != true {
   485  		t.Errorf("OnSubmit not triggered")
   486  	}
   487  }
   488  
   489  func TestCodeArea_Handle_DefaultNoopSubmit(t *testing.T) {
   490  	w := NewCodeArea(CodeAreaSpec{State: CodeAreaState{
   491  		Buffer: CodeBuffer{Content: "code", Dot: 4}}})
   492  	w.Handle(term.K('\n'))
   493  	// No panic, we are good
   494  }
   495  
   496  func TestCodeArea_State(t *testing.T) {
   497  	w := NewCodeArea(CodeAreaSpec{})
   498  	w.MutateState(func(s *CodeAreaState) { s.Buffer.Content = "code" })
   499  	if w.CopyState().Buffer.Content != "code" {
   500  		t.Errorf("state not mutated")
   501  	}
   502  }
   503  
   504  func TestCodeAreaState_ApplyPending(t *testing.T) {
   505  	applyPending := func(s CodeAreaState) CodeAreaState {
   506  		s.ApplyPending()
   507  		return s
   508  	}
   509  	tt.Test(t, applyPending,
   510  		Args(CodeAreaState{Buffer: CodeBuffer{}, Pending: PendingCode{0, 0, "ls"}}).
   511  			Rets(CodeAreaState{Buffer: CodeBuffer{Content: "ls", Dot: 2}, Pending: PendingCode{}}),
   512  		Args(CodeAreaState{Buffer: CodeBuffer{"x", 1}, Pending: PendingCode{0, 0, "ls"}}).
   513  			Rets(CodeAreaState{Buffer: CodeBuffer{Content: "lsx", Dot: 3}, Pending: PendingCode{}}),
   514  		// No-op when Pending is empty.
   515  		Args(CodeAreaState{Buffer: CodeBuffer{"x", 1}}).
   516  			Rets(CodeAreaState{Buffer: CodeBuffer{Content: "x", Dot: 1}}),
   517  		// HideRPrompt is kept intact.
   518  		Args(CodeAreaState{Buffer: CodeBuffer{"x", 1}, HideRPrompt: true}).
   519  			Rets(CodeAreaState{Buffer: CodeBuffer{Content: "x", Dot: 1}, HideRPrompt: true}),
   520  	)
   521  }