require 'minitest' require 'minitest/autorun' require 'ttytest' class FzyTest < Minitest::Test FZY_PATH = File.expand_path('../../../fzy', __FILE__) LEFT = "\e[D" RIGHT = "\e[C" def test_empty_list @tty = interactive_fzy(input: %w[], before: "placeholder") @tty.assert_cursor_position(y: 1, x: 2) @tty.assert_matches <<~TTY placeholder > TTY @tty.send_keys('t') @tty.assert_cursor_position(y: 1, x: 3) @tty.assert_matches <<~TTY placeholder > t TTY @tty.send_keys('z') @tty.assert_cursor_position(y: 1, x: 4) @tty.assert_matches <<~TTY placeholder > tz TTY @tty.send_keys("\r") @tty.assert_cursor_position(y: 2, x: 0) @tty.assert_matches <<~TTY placeholder tz TTY end def test_one_item @tty = interactive_fzy(input: %w[test], before: "placeholder") @tty.assert_matches <<~TTY placeholder > test TTY @tty.assert_cursor_position(y: 1, x: 2) @tty.send_keys('t') @tty.assert_cursor_position(y: 1, x: 3) @tty.assert_matches <<~TTY placeholder > t test TTY @tty.send_keys('z') @tty.assert_cursor_position(y: 1, x: 4) @tty.assert_matches <<~TTY placeholder > tz TTY @tty.send_keys("\r") @tty.assert_cursor_position(y: 2, x: 0) @tty.assert_matches <<~TTY placeholder tz TTY end def test_two_items @tty = interactive_fzy(input: %w[test foo], before: "placeholder") @tty.assert_cursor_position(y: 1, x: 2) @tty.assert_matches <<~TTY placeholder > test foo TTY @tty.send_keys('t') @tty.assert_cursor_position(y: 1, x: 3) @tty.assert_matches <<~TTY placeholder > t test TTY @tty.send_keys('z') @tty.assert_cursor_position(y: 1, x: 4) @tty.assert_matches <<~TTY placeholder > tz TTY @tty.send_keys("\r") @tty.assert_matches <<~TTY placeholder tz TTY @tty.assert_cursor_position(y: 2, x: 0) end def ctrl(key) ((key.upcase.ord) - ('A'.ord) + 1).chr end def test_editing @tty = interactive_fzy(input: %w[test foo], before: "placeholder") @tty.assert_cursor_position(y: 1, x: 2) @tty.assert_matches <<~TTY placeholder > test foo TTY @tty.send_keys("foo bar baz") @tty.assert_cursor_position(y: 1, x: 13) @tty.assert_matches <<~TTY placeholder > foo bar baz TTY @tty.send_keys(ctrl('H')) @tty.assert_cursor_position(y: 1, x: 12) @tty.assert_matches <<~TTY placeholder > foo bar ba TTY @tty.send_keys(ctrl('W')) @tty.assert_cursor_position(y: 1, x: 10) @tty.assert_matches <<~TTY placeholder > foo bar TTY @tty.send_keys(ctrl('U')) @tty.assert_cursor_position(y: 1, x: 2) @tty.assert_matches <<~TTY placeholder > test foo TTY end def test_ctrl_d @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys('foo') @tty.assert_matches "> foo\nfoo" @tty.send_keys(ctrl('D')) @tty.assert_matches '' @tty.assert_cursor_position(y: 0, x: 0) end def test_ctrl_c @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys('foo') @tty.assert_matches "> foo\nfoo" @tty.send_keys(ctrl('C')) @tty.assert_matches '' @tty.assert_cursor_position(y: 0, x: 0) end def test_down_arrow @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\e[A\r") @tty.assert_matches "bar" @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\eOA\r") @tty.assert_matches "bar" end def test_up_arrow @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\e[A") # first down @tty.send_keys("\e[B\r") # and back up @tty.assert_matches "foo" @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\eOA") # first down @tty.send_keys("\e[B\r") # and back up @tty.assert_matches "foo" end def test_lines input10 = (1..10).map(&:to_s) input20 = (1..20).map(&:to_s) @tty = interactive_fzy(input: input10) @tty.assert_matches ">\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10" @tty = interactive_fzy(input: input20) @tty.assert_matches ">\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10" @tty = interactive_fzy(input: input10, args: "-l 5") @tty.assert_matches ">\n1\n2\n3\n4\n5" @tty = interactive_fzy(input: input10, args: "--lines=5") @tty.assert_matches ">\n1\n2\n3\n4\n5" end def test_prompt @tty = interactive_fzy @tty.send_keys("foo") @tty.assert_matches '> foo' @tty = interactive_fzy(args: "-p 'C:\\'") @tty.send_keys("foo") @tty.assert_matches 'C:\foo' @tty = interactive_fzy(args: "--prompt=\"foo bar \"") @tty.send_keys("baz") @tty.assert_matches "foo bar baz" end def test_show_scores expected_score = '( inf)' @tty = interactive_fzy(input: %w[foo bar], args: "-s") @tty.send_keys('foo') @tty.assert_matches "> foo\n#{expected_score} foo" @tty = interactive_fzy(input: %w[foo bar], args: "--show-scores") @tty.send_keys('foo') @tty.assert_matches "> foo\n#{expected_score} foo" expected_score = '( 0.89)' @tty = interactive_fzy(input: %w[foo bar], args: "-s") @tty.send_keys('f') @tty.assert_matches "> f\n#{expected_score} foo" end def test_large_input @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -l 3}) @tty.send_keys('34') @tty.assert_matches "> 34\n34\n340\n341" @tty.send_keys('5') @tty.assert_matches "> 345\n345\n3450\n3451" @tty.send_keys('z') @tty.assert_matches "> 345z" end def test_worker_count @tty = interactive_fzy(input: %w[foo bar], args: "-j1") @tty.send_keys('foo') @tty.assert_matches "> foo\nfoo" @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -j1 -l3}) @tty.send_keys('34') @tty.assert_matches "> 34\n34\n340\n341" @tty = TTYtest.new_terminal(%{seq 100000 | #{FZY_PATH} -j200 -l3}) @tty.send_keys('34') @tty.assert_matches "> 34\n34\n340\n341" end def test_initial_query @tty = interactive_fzy(input: %w[foo bar], args: "-q fo") @tty.assert_matches "> fo\nfoo" @tty.send_keys("o") @tty.assert_matches "> foo\nfoo" @tty.send_keys("o") @tty.assert_matches "> fooo" @tty = interactive_fzy(input: %w[foo bar], args: "-q asdf") @tty.assert_matches "> asdf" end def test_non_interactive @tty = interactive_fzy(input: %w[foo bar], args: "-e foo", before: "before", after: "after") @tty.assert_matches "before\nfoo\nafter" end def test_moving_text_cursor @tty = interactive_fzy(input: %w[foo bar]) @tty.send_keys("br") @tty.assert_matches "> br\nbar" @tty.assert_cursor_position(y: 0, x: 4) @tty.send_keys(LEFT) @tty.assert_cursor_position(y: 0, x: 3) @tty.assert_matches "> br\nbar" @tty.send_keys("a") @tty.assert_cursor_position(y: 0, x: 4) @tty.assert_matches "> bar\nbar" @tty.send_keys(ctrl("A")) # Ctrl-A @tty.assert_cursor_position(y: 0, x: 2) @tty.assert_matches "> bar\nbar" @tty.send_keys("foo") @tty.assert_cursor_position(y: 0, x: 5) @tty.assert_matches "> foobar" @tty.send_keys(ctrl("E")) # Ctrl-E @tty.assert_cursor_position(y: 0, x: 8) @tty.assert_matches "> foobar" @tty.send_keys("baz") # Ctrl-E @tty.assert_cursor_position(y: 0, x: 11) @tty.assert_matches "> foobarbaz" end # More info; # https://github.com/jhawthorn/fzy/issues/42 # https://cirw.in/blog/bracketed-paste def test_bracketed_paste_characters @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\e[200~foo\e[201~") @tty.assert_matches "> foo\nfoo" end # https://github.com/jhawthorn/fzy/issues/81 def test_slow_stdin_fast_user @tty = TTYtest.new_terminal(%{(sleep 0.5; echo aa; echo bc; echo bd) | #{FZY_PATH}}) # Before input has all come in, but wait for fzy to at least start sleep 0.1 @tty.send_keys("b\r") @tty.assert_matches "bc" end def test_unicode @tty = interactive_fzy(input: %w[English Français 日本語]) @tty.assert_matches <<~TTY > English Français 日本語 TTY @tty.assert_cursor_position(y: 0, x: 2) @tty.send_keys("ç") @tty.assert_matches <<~TTY > ç Français TTY @tty.assert_cursor_position(y: 0, x: 3) @tty.send_keys("\r") @tty.assert_matches "Français" end def test_unicode_backspace @tty = interactive_fzy @tty.send_keys "Français" @tty.assert_matches "> Français" @tty.assert_cursor_position(y: 0, x: 10) @tty.send_keys(ctrl('H') * 3) @tty.assert_matches "> Franç" @tty.assert_cursor_position(y: 0, x: 7) @tty.send_keys(ctrl('H')) @tty.assert_matches "> Fran" @tty.assert_cursor_position(y: 0, x: 6) @tty.send_keys('ce') @tty.assert_matches "> France" @tty = interactive_fzy @tty.send_keys "日本語" @tty.assert_matches "> 日本語" @tty.send_keys(ctrl('H')) @tty.assert_matches "> 日本" @tty.send_keys(ctrl('H')) @tty.assert_matches "> 日" @tty.send_keys(ctrl('H')) @tty.assert_matches "> " @tty.assert_cursor_position(y: 0, x: 2) end def test_unicode_delete_word @tty = interactive_fzy @tty.send_keys "Je parle Français" @tty.assert_matches "> Je parle Français" @tty.assert_cursor_position(y: 0, x: 19) @tty.send_keys(ctrl('W')) @tty.assert_matches "> Je parle" @tty.assert_cursor_position(y: 0, x: 11) @tty = interactive_fzy @tty.send_keys "日本語" @tty.assert_matches "> 日本語" @tty.send_keys(ctrl('W')) @tty.assert_matches "> " @tty.assert_cursor_position(y: 0, x: 2) end def test_unicode_cursor_movement @tty = interactive_fzy @tty.send_keys "Français" @tty.assert_cursor_position(y: 0, x: 10) @tty.send_keys(LEFT*5) @tty.assert_cursor_position(y: 0, x: 5) @tty.send_keys(RIGHT*3) @tty.assert_cursor_position(y: 0, x: 8) @tty = interactive_fzy @tty.send_keys "日本語" @tty.assert_matches "> 日本語" @tty.assert_cursor_position(y: 0, x: 8) @tty.send_keys(LEFT) @tty.assert_cursor_position(y: 0, x: 6) @tty.send_keys(LEFT) @tty.assert_cursor_position(y: 0, x: 4) @tty.send_keys(LEFT) @tty.assert_cursor_position(y: 0, x: 2) @tty.send_keys(LEFT) @tty.assert_cursor_position(y: 0, x: 2) @tty.send_keys(RIGHT*3) @tty.assert_cursor_position(y: 0, x: 8) @tty.send_keys(RIGHT) @tty.assert_cursor_position(y: 0, x: 8) end def test_long_strings ascii = "LongStringOfText" * 6 unicode = "LongStringOfText" * 3 @tty = interactive_fzy(input: [ascii, unicode]) @tty.assert_matches <<~TTY > LongStringOfTextLongStringOfTextLongStringOfTextLongStringOfTextLongStringOfText LongStringOfTextLongStringOfTextLongStri TTY end def test_help @tty = TTYtest.new_terminal(%{#{FZY_PATH} --help}) @tty.assert_matches < ') -q, --query=QUERY Use QUERY as the initial search string -e, --show-matches=QUERY Output the sorted matches of QUERY -t, --tty=TTY Specify file to use as TTY device (default /dev/tty) -s, --show-scores Show the scores of each match -0, --read-null Read input delimited by ASCII NUL characters -j, --workers NUM Use NUM workers for searching. (default is # of CPUs) -h, --help Display this help and exit -v, --version Output version information and exit TTY end private def interactive_fzy(input: [], before: nil, after: nil, args: "") cmd = [] cmd << %{echo "#{before}"} if before cmd << %{printf "#{input.join("\\n")}" | #{FZY_PATH} #{args}} cmd << %{echo "#{after}"} if after cmd = cmd.join("; ") TTYtest.new_terminal(cmd) end end