fzy

terminal fuzzy finder picker

git clone https://9o.is/git/fzy.git

acceptance_test.rb

(12849B)


      1 # coding: utf-8
      2 require 'minitest'
      3 require 'minitest/autorun'
      4 require 'ttytest'
      5 
      6 class FzyTest < Minitest::Test
      7   FZY_PATH = File.expand_path('../../../fzy', __FILE__)
      8 
      9   LEFT =  "\e[D"
     10   RIGHT = "\e[C"
     11 
     12   def test_empty_list
     13     @tty = interactive_fzy(input: %w[], before: "placeholder")
     14     @tty.assert_cursor_position(y: 1, x: 2)
     15     @tty.assert_matches <<~TTY
     16       placeholder
     17       >
     18     TTY
     19 
     20     @tty.send_keys('t')
     21     @tty.assert_cursor_position(y: 1, x: 3)
     22     @tty.assert_matches <<~TTY
     23       placeholder
     24       > t
     25     TTY
     26 
     27     @tty.send_keys('z')
     28     @tty.assert_cursor_position(y: 1, x: 4)
     29     @tty.assert_matches <<~TTY
     30       placeholder
     31       > tz
     32     TTY
     33 
     34     @tty.send_keys("\r")
     35     @tty.assert_cursor_position(y: 2, x: 0)
     36     @tty.assert_matches <<~TTY
     37       placeholder
     38       tz
     39     TTY
     40   end
     41 
     42   def test_one_item
     43     @tty = interactive_fzy(input: %w[test], before: "placeholder")
     44     @tty.assert_matches <<~TTY
     45       placeholder
     46       >
     47       test
     48     TTY
     49     @tty.assert_cursor_position(y: 1, x: 2)
     50 
     51     @tty.send_keys('t')
     52     @tty.assert_cursor_position(y: 1, x: 3)
     53     @tty.assert_matches <<~TTY
     54       placeholder
     55       > t
     56       test
     57     TTY
     58 
     59     @tty.send_keys('z')
     60     @tty.assert_cursor_position(y: 1, x: 4)
     61     @tty.assert_matches <<~TTY
     62       placeholder
     63       > tz
     64     TTY
     65 
     66     @tty.send_keys("\r")
     67     @tty.assert_cursor_position(y: 2, x: 0)
     68     @tty.assert_matches <<~TTY
     69       placeholder
     70       tz
     71     TTY
     72   end
     73 
     74   def test_two_items
     75     @tty = interactive_fzy(input: %w[test foo], before: "placeholder")
     76     @tty.assert_cursor_position(y: 1, x: 2)
     77     @tty.assert_matches <<~TTY
     78       placeholder
     79       >
     80       test
     81       foo
     82     TTY
     83 
     84     @tty.send_keys('t')
     85     @tty.assert_cursor_position(y: 1, x: 3)
     86     @tty.assert_matches <<~TTY
     87       placeholder
     88       > t
     89       test
     90     TTY
     91 
     92     @tty.send_keys('z')
     93     @tty.assert_cursor_position(y: 1, x: 4)
     94     @tty.assert_matches <<~TTY
     95       placeholder
     96       > tz
     97     TTY
     98 
     99     @tty.send_keys("\r")
    100     @tty.assert_matches <<~TTY
    101       placeholder
    102       tz
    103     TTY
    104     @tty.assert_cursor_position(y: 2, x: 0)
    105   end
    106 
    107   def ctrl(key)
    108     ((key.upcase.ord) - ('A'.ord) + 1).chr
    109   end
    110 
    111   def test_editing
    112     @tty = interactive_fzy(input: %w[test foo], before: "placeholder")
    113     @tty.assert_cursor_position(y: 1, x: 2)
    114     @tty.assert_matches <<~TTY
    115       placeholder
    116       >
    117       test
    118       foo
    119     TTY
    120 
    121     @tty.send_keys("foo bar baz")
    122     @tty.assert_cursor_position(y: 1, x: 13)
    123     @tty.assert_matches <<~TTY
    124       placeholder
    125       > foo bar baz
    126     TTY
    127 
    128     @tty.send_keys(ctrl('H'))
    129     @tty.assert_cursor_position(y: 1, x: 12)
    130     @tty.assert_matches <<~TTY
    131       placeholder
    132       > foo bar ba
    133     TTY
    134 
    135     @tty.send_keys(ctrl('W'))
    136     @tty.assert_cursor_position(y: 1, x: 10)
    137     @tty.assert_matches <<~TTY
    138       placeholder
    139       > foo bar
    140     TTY
    141 
    142     @tty.send_keys(ctrl('U'))
    143     @tty.assert_cursor_position(y: 1, x: 2)
    144     @tty.assert_matches <<~TTY
    145       placeholder
    146       >
    147       test
    148       foo
    149     TTY
    150   end
    151 
    152   def test_ctrl_d
    153     @tty = interactive_fzy(input: %w[foo bar])
    154     @tty.assert_matches ">\nfoo\nbar"
    155 
    156     @tty.send_keys('foo')
    157     @tty.assert_matches "> foo\nfoo"
    158 
    159     @tty.send_keys(ctrl('D'))
    160     @tty.assert_matches ''
    161     @tty.assert_cursor_position(y: 0, x: 0)
    162   end
    163 
    164   def test_ctrl_c
    165     @tty = interactive_fzy(input: %w[foo bar])
    166     @tty.assert_matches ">\nfoo\nbar"
    167 
    168     @tty.send_keys('foo')
    169     @tty.assert_matches "> foo\nfoo"
    170 
    171     @tty.send_keys(ctrl('C'))
    172     @tty.assert_matches ''
    173     @tty.assert_cursor_position(y: 0, x: 0)
    174   end
    175 
    176   def test_down_arrow
    177     @tty = interactive_fzy(input: %w[foo bar])
    178     @tty.assert_matches ">\nfoo\nbar"
    179     @tty.send_keys("\e[A\r")
    180     @tty.assert_matches "bar"
    181 
    182     @tty = interactive_fzy(input: %w[foo bar])
    183     @tty.assert_matches ">\nfoo\nbar"
    184     @tty.send_keys("\eOA\r")
    185     @tty.assert_matches "bar"
    186   end
    187 
    188   def test_up_arrow
    189     @tty = interactive_fzy(input: %w[foo bar])
    190     @tty.assert_matches ">\nfoo\nbar"
    191     @tty.send_keys("\e[A")   # first down
    192     @tty.send_keys("\e[B\r") # and back up
    193     @tty.assert_matches "foo"
    194 
    195     @tty = interactive_fzy(input: %w[foo bar])
    196     @tty.assert_matches ">\nfoo\nbar"
    197     @tty.send_keys("\eOA")   # first down
    198     @tty.send_keys("\e[B\r") # and back up
    199     @tty.assert_matches "foo"
    200   end
    201 
    202   def test_lines
    203     input10 = (1..10).map(&:to_s)
    204     input20 = (1..20).map(&:to_s)
    205 
    206     @tty = interactive_fzy(input: input10)
    207     @tty.assert_matches ">\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10"
    208 
    209     @tty = interactive_fzy(input: input20)
    210     @tty.assert_matches ">\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10"
    211 
    212     @tty = interactive_fzy(input: input10, args: "-l 5")
    213     @tty.assert_matches ">\n1\n2\n3\n4\n5"
    214 
    215     @tty = interactive_fzy(input: input10, args: "--lines=5")
    216     @tty.assert_matches ">\n1\n2\n3\n4\n5"
    217   end
    218 
    219   def test_prompt
    220     @tty = interactive_fzy
    221     @tty.send_keys("foo")
    222     @tty.assert_matches '> foo'
    223 
    224     @tty = interactive_fzy(args: "-p 'C:\\'")
    225     @tty.send_keys("foo")
    226     @tty.assert_matches 'C:\foo'
    227 
    228     @tty = interactive_fzy(args: "--prompt=\"foo bar \"")
    229     @tty.send_keys("baz")
    230     @tty.assert_matches "foo bar baz"
    231   end
    232 
    233   def test_show_scores
    234     expected_score = '(  inf)'
    235     @tty = interactive_fzy(input: %w[foo bar], args: "-s")
    236     @tty.send_keys('foo')
    237     @tty.assert_matches "> foo\n#{expected_score} foo"
    238 
    239     @tty = interactive_fzy(input: %w[foo bar], args: "--show-scores")
    240     @tty.send_keys('foo')
    241     @tty.assert_matches "> foo\n#{expected_score} foo"
    242 
    243     expected_score = '( 0.89)'
    244     @tty = interactive_fzy(input: %w[foo bar], args: "-s")
    245     @tty.send_keys('f')
    246     @tty.assert_matches "> f\n#{expected_score} foo"
    247   end
    248 
    249   def test_large_input
    250     @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -l 3})
    251     @tty.send_keys('34')
    252     @tty.assert_matches "> 34\n34\n340\n341"
    253 
    254     @tty.send_keys('5')
    255     @tty.assert_matches "> 345\n345\n3450\n3451"
    256 
    257     @tty.send_keys('z')
    258     @tty.assert_matches "> 345z"
    259   end
    260 
    261   def test_worker_count
    262     @tty = interactive_fzy(input: %w[foo bar], args: "-j1")
    263     @tty.send_keys('foo')
    264     @tty.assert_matches "> foo\nfoo"
    265 
    266     @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -j1 -l3})
    267     @tty.send_keys('34')
    268     @tty.assert_matches "> 34\n34\n340\n341"
    269 
    270     @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -j200 -l3})
    271     @tty.send_keys('34')
    272     @tty.assert_matches "> 34\n34\n340\n341"
    273   end
    274 
    275   def test_initial_query
    276     @tty = interactive_fzy(input: %w[foo bar], args: "-q fo")
    277     @tty.assert_matches "> fo\nfoo"
    278     @tty.send_keys("o")
    279     @tty.assert_matches "> foo\nfoo"
    280     @tty.send_keys("o")
    281     @tty.assert_matches "> fooo"
    282 
    283     @tty = interactive_fzy(input: %w[foo bar], args: "-q asdf")
    284     @tty.assert_matches "> asdf"
    285   end
    286 
    287   def test_non_interactive
    288     @tty = interactive_fzy(input: %w[foo bar], args: "-e foo", before: "before", after: "after")
    289     @tty.assert_matches "before\nfoo\nafter"
    290   end
    291 
    292   def test_moving_text_cursor
    293     @tty = interactive_fzy(input: %w[foo bar])
    294     @tty.send_keys("br")
    295     @tty.assert_matches "> br\nbar"
    296     @tty.assert_cursor_position(y: 0, x: 4)
    297 
    298     @tty.send_keys(LEFT)
    299     @tty.assert_cursor_position(y: 0, x: 3)
    300     @tty.assert_matches "> br\nbar"
    301     @tty.send_keys("a")
    302     @tty.assert_cursor_position(y: 0, x: 4)
    303     @tty.assert_matches "> bar\nbar"
    304 
    305     @tty.send_keys(ctrl("A")) # Ctrl-A
    306     @tty.assert_cursor_position(y: 0, x: 2)
    307     @tty.assert_matches "> bar\nbar"
    308     @tty.send_keys("foo")
    309     @tty.assert_cursor_position(y: 0, x: 5)
    310     @tty.assert_matches "> foobar"
    311 
    312     @tty.send_keys(ctrl("E")) # Ctrl-E
    313     @tty.assert_cursor_position(y: 0, x: 8)
    314     @tty.assert_matches "> foobar"
    315     @tty.send_keys("baz") # Ctrl-E
    316     @tty.assert_cursor_position(y: 0, x: 11)
    317     @tty.assert_matches "> foobarbaz"
    318   end
    319 
    320   # More info;
    321   # https://github.com/jhawthorn/fzy/issues/42
    322   # https://cirw.in/blog/bracketed-paste
    323   def test_bracketed_paste_characters
    324     @tty = interactive_fzy(input: %w[foo bar])
    325     @tty.assert_matches ">\nfoo\nbar"
    326     @tty.send_keys("\e[200~foo\e[201~")
    327     @tty.assert_matches "> foo\nfoo"
    328   end
    329 
    330   # https://github.com/jhawthorn/fzy/issues/81
    331   def test_slow_stdin_fast_user
    332     @tty = TTYtest.new_terminal(%{(sleep 0.5; echo aa; echo bc; echo bd) | #{FZY_PATH}})
    333 
    334     # Before input has all come in, but wait for fzy to at least start
    335     sleep 0.1
    336 
    337     @tty.send_keys("b\r")
    338     @tty.assert_matches "bc"
    339   end
    340 
    341   def test_unicode
    342     @tty = interactive_fzy(input: %w[English Français 日本語])
    343     @tty.assert_matches <<~TTY
    344       >
    345       English
    346       Français
    347       日本語
    348     TTY
    349     @tty.assert_cursor_position(y: 0, x: 2)
    350 
    351     @tty.send_keys("ç")
    352     @tty.assert_matches <<~TTY
    353       > ç
    354       Français
    355     TTY
    356     @tty.assert_cursor_position(y: 0, x: 3)
    357 
    358     @tty.send_keys("\r")
    359     @tty.assert_matches "Français"
    360   end
    361 
    362   def test_unicode_backspace
    363     @tty = interactive_fzy
    364     @tty.send_keys "Français"
    365     @tty.assert_matches "> Français"
    366     @tty.assert_cursor_position(y: 0, x: 10)
    367 
    368     @tty.send_keys(ctrl('H') * 3)
    369     @tty.assert_matches "> Franç"
    370     @tty.assert_cursor_position(y: 0, x: 7)
    371 
    372     @tty.send_keys(ctrl('H'))
    373     @tty.assert_matches "> Fran"
    374     @tty.assert_cursor_position(y: 0, x: 6)
    375 
    376     @tty.send_keys('ce')
    377     @tty.assert_matches "> France"
    378 
    379     @tty = interactive_fzy
    380     @tty.send_keys "日本語"
    381     @tty.assert_matches "> 日本語"
    382     @tty.send_keys(ctrl('H'))
    383     @tty.assert_matches "> 日本"
    384     @tty.send_keys(ctrl('H'))
    385     @tty.assert_matches "> 日"
    386     @tty.send_keys(ctrl('H'))
    387     @tty.assert_matches "> "
    388     @tty.assert_cursor_position(y: 0, x: 2)
    389   end
    390 
    391   def test_unicode_delete_word
    392     @tty = interactive_fzy
    393     @tty.send_keys "Je parle Français"
    394     @tty.assert_matches "> Je parle Français"
    395     @tty.assert_cursor_position(y: 0, x: 19)
    396 
    397     @tty.send_keys(ctrl('W'))
    398     @tty.assert_matches "> Je parle"
    399     @tty.assert_cursor_position(y: 0, x: 11)
    400 
    401     @tty = interactive_fzy
    402     @tty.send_keys "日本語"
    403     @tty.assert_matches "> 日本語"
    404     @tty.send_keys(ctrl('W'))
    405     @tty.assert_matches "> "
    406     @tty.assert_cursor_position(y: 0, x: 2)
    407   end
    408 
    409   def test_unicode_cursor_movement
    410     @tty = interactive_fzy
    411     @tty.send_keys "Français"
    412     @tty.assert_cursor_position(y: 0, x: 10)
    413 
    414     @tty.send_keys(LEFT*5)
    415     @tty.assert_cursor_position(y: 0, x: 5)
    416 
    417     @tty.send_keys(RIGHT*3)
    418     @tty.assert_cursor_position(y: 0, x: 8)
    419 
    420     @tty = interactive_fzy
    421     @tty.send_keys "日本語"
    422     @tty.assert_matches "> 日本語"
    423     @tty.assert_cursor_position(y: 0, x: 8)
    424     @tty.send_keys(LEFT)
    425     @tty.assert_cursor_position(y: 0, x: 6)
    426     @tty.send_keys(LEFT)
    427     @tty.assert_cursor_position(y: 0, x: 4)
    428     @tty.send_keys(LEFT)
    429     @tty.assert_cursor_position(y: 0, x: 2)
    430     @tty.send_keys(LEFT)
    431     @tty.assert_cursor_position(y: 0, x: 2)
    432     @tty.send_keys(RIGHT*3)
    433     @tty.assert_cursor_position(y: 0, x: 8)
    434     @tty.send_keys(RIGHT)
    435     @tty.assert_cursor_position(y: 0, x: 8)
    436   end
    437 
    438   def test_long_strings
    439     ascii = "LongStringOfText" * 6
    440     unicode = "LongStringOfText" * 3
    441 
    442     @tty = interactive_fzy(input: [ascii, unicode])
    443     @tty.assert_matches <<~TTY
    444       >
    445       LongStringOfTextLongStringOfTextLongStringOfTextLongStringOfTextLongStringOfText
    446       LongStringOfTextLongStringOfTextLongStri
    447     TTY
    448   end
    449 
    450   def test_show_info
    451     @tty = interactive_fzy(input: %w[foo bar baz], args: "-i")
    452     @tty.assert_matches ">\n[3/3]\nfoo\nbar\nbaz"
    453     @tty.send_keys("ba")
    454     @tty.assert_matches "> ba\n[2/3]\nbar\nbaz"
    455     @tty.send_keys("q")
    456     @tty.assert_matches "> baq\n[0/3]"
    457   end
    458 
    459   def test_help
    460     @tty = TTYtest.new_terminal(%{#{FZY_PATH} --help})
    461     @tty.assert_matches <<TTY
    462 Usage: fzy [OPTION]...
    463  -l, --lines=LINES        Specify how many lines of results to show (default 10)
    464  -p, --prompt=PROMPT      Input prompt (default '> ')
    465  -q, --query=QUERY        Use QUERY as the initial search string
    466  -e, --show-matches=QUERY Output the sorted matches of QUERY
    467  -t, --tty=TTY            Specify file to use as TTY device (default /dev/tty)
    468  -s, --show-scores        Show the scores of each match
    469  -0, --read-null          Read input delimited by ASCII NUL characters
    470  -j, --workers NUM        Use NUM workers for searching. (default is # of CPUs)
    471  -i, --show-info          Show selection info line
    472  -h, --help     Display this help and exit
    473  -v, --version  Output version information and exit
    474 TTY
    475   end
    476 
    477   private
    478 
    479   def interactive_fzy(input: [], before: nil, after: nil, args: "")
    480     cmd = []
    481     cmd << %{echo "#{before}"} if before
    482     cmd << %{printf "#{input.join("\\n")}" | #{FZY_PATH} #{args}}
    483     cmd << %{echo "#{after}"} if after
    484     cmd = cmd.join("; ")
    485     TTYtest.new_terminal(cmd)
    486   end
    487 end