2025-01-25 19:57:40 +09:00
# frozen_string_literal: true
require_relative 'lib/common'
# Testing basic features of fzf
class TestCore < TestInteractive
def test_fzf_default_command
tmux . send_keys fzf . sub ( 'FZF_DEFAULT_COMMAND=' , "FZF_DEFAULT_COMMAND='echo hello'" ), :Enter
tmux . until { | lines | assert_equal '> hello' , lines [- 3 ] }
tmux . send_keys :Enter
assert_equal 'hello' , fzf_output
end
def test_fzf_default_command_failure
tmux . send_keys fzf . sub ( 'FZF_DEFAULT_COMMAND=' , 'FZF_DEFAULT_COMMAND=false' ), :Enter
tmux . until { | lines | assert_includes lines [- 2 ] , ' [Command failed: false] ─' }
tmux . send_keys :Enter
end
def test_key_bindings
tmux . send_keys " #{ FZF } -q 'foo bar foo-bar'" , :Enter
tmux . until { | lines | assert_equal '> foo bar foo-bar' , lines . last }
# CTRL-A
tmux . send_keys 'C-A' , '('
tmux . until { | lines | assert_equal '> (foo bar foo-bar' , lines . last }
# META-F
tmux . send_keys :Escape , :f , ')'
tmux . until { | lines | assert_equal '> (foo) bar foo-bar' , lines . last }
# CTRL-B
tmux . send_keys 'C-B' , 'var'
tmux . until { | lines | assert_equal '> (foovar) bar foo-bar' , lines . last }
# Left, CTRL-D
tmux . send_keys :Left , :Left , 'C-D'
tmux . until { | lines | assert_equal '> (foovr) bar foo-bar' , lines . last }
# META-BS
tmux . send_keys :Escape , :BSpace
tmux . until { | lines | assert_equal '> (r) bar foo-bar' , lines . last }
# CTRL-Y
tmux . send_keys 'C-Y' , 'C-Y'
tmux . until { | lines | assert_equal '> (foovfoovr) bar foo-bar' , lines . last }
# META-B
tmux . send_keys :Escape , :b , :Space , :Space
tmux . until { | lines | assert_equal '> ( foovfoovr) bar foo-bar' , lines . last }
# CTRL-F / Right
tmux . send_keys 'C-F' , :Right , '/'
tmux . until { | lines | assert_equal '> ( fo/ovfoovr) bar foo-bar' , lines . last }
# CTRL-H / BS
tmux . send_keys 'C-H' , :BSpace
tmux . until { | lines | assert_equal '> ( fovfoovr) bar foo-bar' , lines . last }
# CTRL-E
tmux . send_keys 'C-E' , 'baz'
tmux . until { | lines | assert_equal '> ( fovfoovr) bar foo-barbaz' , lines . last }
# CTRL-U
tmux . send_keys 'C-U'
tmux . until { | lines | assert_equal '>' , lines . last }
# CTRL-Y
tmux . send_keys 'C-Y'
tmux . until { | lines | assert_equal '> ( fovfoovr) bar foo-barbaz' , lines . last }
# CTRL-W
tmux . send_keys 'C-W' , 'bar-foo'
tmux . until { | lines | assert_equal '> ( fovfoovr) bar bar-foo' , lines . last }
# META-D
tmux . send_keys :Escape , :b , :Escape , :b , :Escape , :d , 'C-A' , 'C-Y'
tmux . until { | lines | assert_equal '> bar( fovfoovr) bar -foo' , lines . last }
# CTRL-M
tmux . send_keys 'C-M'
tmux . until { | lines | refute_equal '>' , lines . last }
end
def test_file_word
tmux . send_keys " #{ FZF } -q '--/foo bar/foo-bar/baz' --filepath-word" , :Enter
tmux . until { | lines | assert_equal '> --/foo bar/foo-bar/baz' , lines . last }
tmux . send_keys :Escape , :b
tmux . send_keys :Escape , :b
tmux . send_keys :Escape , :b
tmux . send_keys :Escape , :d
tmux . send_keys :Escape , :f
tmux . send_keys :Escape , :BSpace
tmux . until { | lines | assert_equal '> --///baz' , lines . last }
end
def test_multi_order
tmux . send_keys "seq 1 10 | #{ fzf ( :multi ) } " , :Enter
tmux . until { | lines | assert_equal '>' , lines . last }
tmux . send_keys :Tab , :Up , :Up , :Tab , :Tab , :Tab , # 3, 2
'C-K' , 'C-K' , 'C-K' , 'C-K' , :BTab , :BTab , # 5, 6
:PgUp , 'C-J' , :Down , :Tab , :Tab # 8, 7
tmux . until { | lines | assert_equal ' 10/10 (6)' , lines [- 2 ] }
tmux . send_keys 'C-M'
assert_equal %w[3 2 5 6 8 7] , fzf_output_lines
end
2025-09-05 12:38:22 +02:00
def test_subword_forward
tmux . send_keys " #{ FZF } --bind K:kill-subword,F:forward-subword -q 'foo bar foo-bar fooFooBar'" , :Enter , :Home
tmux . until { | lines | assert_equal '> foo bar foo-bar fooFooBar' , lines . last }
tmux . send_keys 'F' , :Delete
tmux . until { | lines | assert_equal '> foobar foo-bar fooFooBar' , lines . last }
tmux . send_keys 'K'
tmux . until { | lines | assert_equal '> foo foo-bar fooFooBar' , lines . last }
tmux . send_keys 'F' , 'K'
tmux . until { | lines | assert_equal '> foo foo fooFooBar' , lines . last }
tmux . send_keys 'F' , 'F' , 'K'
tmux . until { | lines | assert_equal '> foo foo fooFoo' , lines . last }
end
def test_subword_backward
tmux . send_keys " #{ FZF } --bind K:backward-kill-subword,B:backward-subword -q 'foo bar foo-bar fooBar'" , :Enter
tmux . until { | lines | assert_equal '> foo bar foo-bar fooBar' , lines . last }
tmux . send_keys 'B' , :BSpace
tmux . until { | lines | assert_equal '> foo bar foo-bar foBar' , lines . last }
tmux . send_keys 'K'
tmux . until { | lines | assert_equal '> foo bar foo-bar Bar' , lines . last }
tmux . send_keys 'B' , :BSpace
tmux . until { | lines | assert_equal '> foo bar foobar Bar' , lines . last }
tmux . send_keys 'B' , 'B' , :BSpace
tmux . until { | lines | assert_equal '> foobar foobar Bar' , lines . last }
end
2025-01-25 19:57:40 +09:00
def test_multi_max
tmux . send_keys "seq 1 10 | #{ FZF } -m 3 --bind A:select-all,T:toggle-all --preview 'echo [{+}]/{}'" , :Enter
2025-01-27 02:08:52 +09:00
tmux . until { | lines | assert_equal 10 , lines . match_count }
2025-01-25 19:57:40 +09:00
tmux . send_keys '1'
tmux . until do | lines |
assert_includes lines [ 1 ] , ' [1]/1 '
assert lines [- 2 ]&. start_with? ( ' 2/10 ' )
end
tmux . send_keys 'A'
tmux . until do | lines |
assert_includes lines [ 1 ] , ' [1 10]/1 '
assert lines [- 2 ]&. start_with? ( ' 2/10 (2/3)' )
end
tmux . send_keys :BSpace
tmux . until { | lines | assert lines [- 2 ]&. start_with? ( ' 10/10 (2/3)' ) }
tmux . send_keys 'T'
tmux . until do | lines |
assert_includes lines [ 1 ] , ' [2 3 4]/1 '
assert lines [- 2 ]&. start_with? ( ' 10/10 (3/3)' )
end
%w[T A] . each do | key |
tmux . send_keys key
tmux . until do | lines |
assert_includes lines [ 1 ] , ' [1 5 6]/1 '
assert lines [- 2 ]&. start_with? ( ' 10/10 (3/3)' )
end
end
tmux . send_keys :BTab
tmux . until do | lines |
assert_includes lines [ 1 ] , ' [5 6]/2 '
assert lines [- 2 ]&. start_with? ( ' 10/10 (2/3)' )
end
[ :BTab , :BTab , 'A' ]. each do | key |
tmux . send_keys key
tmux . until do | lines |
assert_includes lines [ 1 ] , ' [5 6 2]/3 '
assert lines [- 2 ]&. start_with? ( ' 10/10 (3/3)' )
end
end
tmux . send_keys '2'
tmux . until { | lines | assert lines [- 2 ]&. start_with? ( ' 1/10 (3/3)' ) }
tmux . send_keys 'T'
tmux . until do | lines |
assert_includes lines [ 1 ] , ' [5 6]/2 '
assert lines [- 2 ]&. start_with? ( ' 1/10 (2/3)' )
end
tmux . send_keys :BSpace
tmux . until { | lines | assert lines [- 2 ]&. start_with? ( ' 10/10 (2/3)' ) }
tmux . send_keys 'A'
tmux . until do | lines |
assert_includes lines [ 1 ] , ' [5 6 1]/1 '
assert lines [- 2 ]&. start_with? ( ' 10/10 (3/3)' )
end
end
def test_multi_action
tmux . send_keys "seq 10 | #{ FZF } --bind 'a:change-multi,b:change-multi(3),c:change-multi(xxx),d:change-multi(0)'" , :Enter
2025-01-27 02:08:52 +09:00
tmux . until { | lines | assert_equal 10 , lines . match_count }
2025-01-25 19:57:40 +09:00
tmux . until { | lines | assert lines [- 2 ]&. start_with? ( ' 10/10 ' ) }
tmux . send_keys 'a'
tmux . until { | lines | assert lines [- 2 ]&. start_with? ( ' 10/10 (0)' ) }
tmux . send_keys 'b'
tmux . until { | lines | assert lines [- 2 ]&. start_with? ( ' 10/10 (0/3)' ) }
tmux . send_keys :BTab
tmux . until { | lines | assert lines [- 2 ]&. start_with? ( ' 10/10 (1/3)' ) }
tmux . send_keys 'c'
tmux . send_keys :BTab
tmux . until { | lines | assert lines [- 2 ]&. start_with? ( ' 10/10 (2/3)' ) }
tmux . send_keys 'd'
tmux . until do | lines |
assert lines [- 2 ]&. start_with? ( ' 10/10 ' ) && ! lines [- 2 ]&. include? ( '(' )
end
end
def test_with_nth
[ true , false ]. each do | multi |
tmux . send_keys "(echo ' 1st 2nd 3rd/';
echo ' first second third/') |
#{ fzf ( multi && :multi , :x , :nth , 2 , :with_nth , '2,-1,1' ) } " ,
:Enter
tmux . until { | lines | assert_equal multi ? ' 2/2 (0)' : ' 2/2' , lines [- 2 ] }
# Transformed list
lines = tmux . capture
assert_equal ' second third/first' , lines [- 4 ]
assert_equal '> 2nd 3rd/1st' , lines [- 3 ]
# However, the output must not be transformed
if multi
tmux . send_keys :BTab , :BTab
tmux . until { | lines | assert_equal ' 2/2 (2)' , lines [- 2 ] }
tmux . send_keys :Enter
assert_equal [ ' 1st 2nd 3rd/' , ' first second third/' ] , fzf_output_lines
else
tmux . send_keys '^' , '3'
tmux . until { | lines | assert_equal ' 1/2' , lines [- 2 ] }
tmux . send_keys :Enter
assert_equal [ ' 1st 2nd 3rd/' ] , fzf_output_lines
end
end
end
def test_scroll
[ true , false ]. each do | rev |
tmux . send_keys "seq 1 100 | #{ fzf ( rev && :reverse ) } " , :Enter
tmux . until { | lines | assert_equal ' 100/100' , lines [ rev ? 1 : - 2 ] }
tmux . send_keys ( * Array . new ( 110 ) { rev ? :Down : :Up })
tmux . until { | lines | assert_includes lines , '> 100' }
tmux . send_keys :Enter
assert_equal '100' , fzf_output
end
end
def test_select_1
tmux . send_keys "seq 1 100 | #{ fzf ( :with_nth , '..,..' , :print_query , :q , 5555 , :'1' ) } " , :Enter
assert_equal %w[5555 55] , fzf_output_lines
end
2025-02-26 00:24:41 +09:00
def test_select_1_accept_nth
tmux . send_keys "seq 1 100 | #{ fzf ( :with_nth , '..,..' , :print_query , :q , 5555 , :'1' , :accept_nth , '"{1} // {1}"' ) } " , :Enter
assert_equal [ '5555' , '55 // 55' ] , fzf_output_lines
end
2025-01-25 19:57:40 +09:00
def test_exit_0
tmux . send_keys "seq 1 100 | #{ fzf ( :with_nth , '..,..' , :print_query , :q , 555_555 , :'0' ) } " , :Enter
assert_equal %w[555555] , fzf_output_lines
end
def test_select_1_exit_0_fail
[ :'0' , :'1' , % i [ 1 0 ]]. each do | opt |
tmux . send_keys "seq 1 100 | #{ fzf ( :print_query , :multi , :q , 5 , * opt ) } " , :Enter
tmux . until { | lines | assert_equal '> 5' , lines . last }
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | assert_equal ' 19/100 (3)' , lines [- 2 ] }
tmux . send_keys :Enter
assert_equal %w[5 5 50 51] , fzf_output_lines
end
end
def test_query_unicode
tmux . paste "(echo abc; echo $' \\ 352 \\ 260 \\ 200 \\ 353 \\ 202 \\ 230 \\ 353 \\ 213 \\ 244') | #{ fzf ( :query , "$' \\ 352 \\ 260 \\ 200 \\ 353 \\ 213 \\ 244'" ) } "
tmux . until { | lines | assert_equal ' 1/2' , lines [- 2 ] }
tmux . send_keys :Enter
assert_equal %w[가나다] , fzf_output_lines
end
def test_sync
tmux . send_keys "seq 1 100 | #{ FZF } --multi | awk '{print $1 $1}' | #{ fzf ( :sync ) } " , :Enter
tmux . until { | lines | assert_equal '>' , lines [- 1 ] }
tmux . send_keys 9
tmux . until { | lines | assert_equal ' 19/100 (0)' , lines [- 2 ] }
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | assert_equal ' 19/100 (3)' , lines [- 2 ] }
tmux . send_keys :Enter
tmux . until { | lines | assert_equal '>' , lines [- 1 ] }
tmux . send_keys 'C-K' , :Enter
assert_equal %w[9090] , fzf_output_lines
end
def test_tac
tmux . send_keys "seq 1 1000 | #{ fzf ( :tac , :multi ) } " , :Enter
tmux . until { | lines | assert_equal ' 1000/1000 (0)' , lines [- 2 ] }
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | assert_equal ' 1000/1000 (3)' , lines [- 2 ] }
tmux . send_keys :Enter
assert_equal %w[1000 999 998] , fzf_output_lines
end
def test_tac_sort
tmux . send_keys "seq 1 1000 | #{ fzf ( :tac , :multi ) } " , :Enter
tmux . until { | lines | assert_equal ' 1000/1000 (0)' , lines [- 2 ] }
tmux . send_keys '99'
tmux . until { | lines | assert_equal ' 28/1000 (0)' , lines [- 2 ] }
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | assert_equal ' 28/1000 (3)' , lines [- 2 ] }
tmux . send_keys :Enter
assert_equal %w[99 999 998] , fzf_output_lines
end
def test_tac_nosort
tmux . send_keys "seq 1 1000 | #{ fzf ( :tac , :no_sort , :multi ) } " , :Enter
tmux . until { | lines | assert_equal ' 1000/1000 (0)' , lines [- 2 ] }
tmux . send_keys '00'
tmux . until { | lines | assert_equal ' 10/1000 (0)' , lines [- 2 ] }
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | assert_equal ' 10/1000 (3)' , lines [- 2 ] }
tmux . send_keys :Enter
assert_equal %w[1000 900 800] , fzf_output_lines
end
def test_expect
test = lambda do | key , feed , expected = key |
tmux . send_keys "seq 1 100 | #{ fzf ( :expect , key , :prompt , "[ #{ key } ]" ) } " , :Enter
tmux . until { | lines | assert_equal ' 100/100' , lines [- 2 ] }
tmux . send_keys '55'
tmux . until { | lines | assert_equal ' 1/100' , lines [- 2 ] }
tmux . send_keys ( * feed )
tmux . prepare
assert_equal [ expected , '55' ] , fzf_output_lines
end
test . call ( 'ctrl-t' , 'C-T' )
test . call ( 'ctrl-t' , 'Enter' , '' )
test . call ( 'alt-c' , % i [ Escape c ] )
test . call ( 'f1' , 'f1' )
test . call ( 'f2' , 'f2' )
test . call ( 'f3' , 'f3' )
test . call ( 'f2,f4' , 'f2' , 'f2' )
test . call ( 'f2,f4' , 'f4' , 'f4' )
test . call ( 'alt-/' , % i [ Escape /])
%w[f5 f6 f7 f8 f9 f10].each do |key|
test.call('f5,f6,f7,f8,f9,f10', key, key)
end
test.call('@', '@')
end
def test_expect_with_bound_actions
tmux.send_keys "seq 1 100 | #{ fzf ( '--query 1 --print-query --expect z --bind z:up+up' ) } ", :Enter
tmux.until { |lines| assert_equal 20, lines.match_count }
tmux.send_keys('z')
assert_equal %w[1 z 1], fzf_output_lines
end
def test_expect_print_query
tmux.send_keys "seq 1 100 | #{ fzf ( '--expect=alt-z' , :print_query ) } ", :Enter
tmux.until { |lines| assert_equal ' 100/ 100 ', lines[-2] }
tmux.send_keys ' 55 '
tmux.until { |lines| assert_equal ' 1 / 100 ', lines[-2] }
tmux.send_keys :Escape, :z
assert_equal %w[55 alt-z 55], fzf_output_lines
end
def test_expect_printable_character_print_query
tmux.send_keys "seq 1 100 | #{fzf(' -- expect = z -- print - query ')}", :Enter
tmux.until { |lines| assert_equal ' 100 / 100 ', lines[-2] }
tmux.send_keys ' 55 '
tmux.until { |lines| assert_equal ' 1 / 100 ', lines[-2] }
tmux.send_keys ' z '
assert_equal %w[55 z 55], fzf_output_lines
end
def test_expect_print_query_select_1
tmux.send_keys "seq 1 100 | #{fzf(' - q55 - 1 -- expect = alt - z -- print - query ')}", :Enter
assert_equal [' 55 ', '', ' 55 '], fzf_output_lines
end
def test_toggle_sort
[' -- toggle - sort = ctrl - r ', ' -- bind = ctrl - r : toggle - sort '].each do |opt|
tmux.send_keys "seq 1 111 | #{fzf("-m +s --tac #{opt} -q11")}", :Enter
tmux.until { |lines| assert_equal ' > 111 ', lines[-3] }
tmux.send_keys :Tab
2026-03-14 22:10:55 +09:00
tmux.until { |lines| assert_equal ' 4 / 111 ( 1 ) - S ', lines[-2] }
2025-01-25 19:57:40 +09:00
tmux.send_keys ' C - R '
tmux.until { |lines| assert_equal ' > 11 ', lines[-3] }
tmux.send_keys :Tab
2026-03-14 22:10:55 +09:00
tmux.until { |lines| assert_equal ' 4 / 111 ( 2 ) + S ', lines[-2] }
2025-01-25 19:57:40 +09:00
tmux.send_keys :Enter
assert_equal %w[111 11], fzf_output_lines
end
end
def test_invalid_cache
tmux.send_keys "(echo d; echo D; echo x) | #{fzf(' - q d ')}", :Enter
tmux.until { |lines| assert_equal ' 2 / 3 ', lines[-2] }
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal ' 3 / 3 ', lines[-2] }
tmux.send_keys :D
tmux.until { |lines| assert_equal ' 1 / 3 ', lines[-2] }
tmux.send_keys :Enter
end
def test_invalid_cache_query_type
command = %[(echo ' foo $bar '; echo ' barfoo '; echo ' foo ^ bar '; echo "foo' 1 - 2 "; seq 100) | #{ FZF } ]
# Suffix match
tmux.send_keys command, :Enter
tmux.until { |lines| assert_equal 104, lines.match_count }
tmux.send_keys 'foo$'
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys 'bar'
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Enter
# Prefix match
tmux.prepare
tmux.send_keys command, :Enter
tmux.until { |lines| assert_equal 104, lines.match_count }
tmux.send_keys '^bar'
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys 'C-a', 'foo'
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Enter
# Exact match
tmux.prepare
tmux.send_keys command, :Enter
tmux.until { |lines| assert_equal 104, lines.match_count }
tmux.send_keys " '12"
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys ' C - a ', ' foo '
tmux.until { |lines| assert_equal 1, lines.match_count }
end
def test_bind
2025-01-27 09:28:40 +09:00
tmux.send_keys "seq 1 1000 | #{fzf(' - m -- bind = ctrl - j : accept , u ,:, U : up , X ,,, Z : toggle - up , t : toggle ')}", :Enter
2025-01-25 19:57:40 +09:00
tmux.until { |lines| assert_equal ' 1000 / 1000 ( 0 ) ', lines[-2] }
2025-01-27 09:28:40 +09:00
tmux.send_keys ' uU : ', ' X , Z ', ' tt ', ' uu ', ' ttt ', ' C - j '
2025-01-25 19:57:40 +09:00
assert_equal %w[4 5 6 9], fzf_output_lines
end
def test_bind_print_query
tmux.send_keys "seq 1 1000 | #{fzf(' - m -- bind = ctrl - j : print - query ')}", :Enter
tmux.until { |lines| assert_equal ' 1000 / 1000 ( 0 ) ', lines[-2] }
tmux.send_keys ' print - my - query ', ' C - j '
assert_equal %w[print-my-query], fzf_output_lines
end
def test_bind_replace_query
tmux.send_keys "seq 1 1000 | #{fzf(' -- print - query -- bind = ctrl - j : replace - query ')}", :Enter
tmux.send_keys ' 1 '
tmux.until { |lines| assert_equal ' 272 / 1000 ', lines[-2] }
tmux.send_keys ' C - k ', ' C - j '
tmux.until { |lines| assert_equal ' 29 / 1000 ', lines[-2] }
tmux.until { |lines| assert_equal ' > 10 ', lines[-1] }
end
2026-08-08 19:10:51 +09:00
def test_bind_replace_query_does_not_mutate_item
tmux.send_keys "echo ' 한글 abcde ' | #{fzf(' -- bind = ctrl - j : replace - query , ctrl - o : clear - query ')}", :Enter
tmux.until { |lines| assert_equal ' 1 / 1 ', lines[-2] }
tmux.send_keys ' C - j '
tmux.until { |lines| assert_equal ' > 한글 abcde ', lines[-1] }
# Editing away from the end used to write into the item itself
tmux.send_keys :Left, :BSpace
tmux.until { |lines| assert_equal ' > 한글 abce ', lines[-1] }
tmux.send_keys ' C - o '
tmux.until { |lines| assert_equal ' > ', lines[-1] }
tmux.until { |lines| assert_equal ' > 한글 abcde ', lines[-3] }
end
2025-01-25 19:57:40 +09:00
def test_select_all_deselect_all_toggle_all
tmux.send_keys "seq 100 | #{fzf(' -- bind ctrl - a : select - all , ctrl - d : deselect - all , ctrl - t : toggle - all -- multi ')}", :Enter
tmux.until { |lines| assert_equal ' 100 / 100 ( 0 ) ', lines[-2] }
tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| assert_equal ' 100 / 100 ( 3 ) ', lines[-2] }
tmux.send_keys ' C - t '
tmux.until { |lines| assert_equal ' 100 / 100 ( 97 ) ', lines[-2] }
tmux.send_keys ' C - a '
tmux.until { |lines| assert_equal ' 100 / 100 ( 100 ) ', lines[-2] }
tmux.send_keys :Tab, :Tab
tmux.until { |lines| assert_equal ' 100 / 100 ( 98 ) ', lines[-2] }
tmux.send_keys ' 100 '
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys ' C - d '
tmux.until { |lines| assert_equal ' 1 / 100 ( 97 ) ', lines[-2] }
tmux.send_keys ' C - u '
tmux.until { |lines| assert_equal 100, lines.match_count }
tmux.send_keys ' C - d '
tmux.until { |lines| assert_equal ' 100 / 100 ( 0 ) ', lines[-2] }
tmux.send_keys :BTab, :BTab
tmux.until { |lines| assert_equal ' 100 / 100 ( 2 ) ', lines[-2] }
tmux.send_keys 0
tmux.until { |lines| assert_equal ' 10 / 100 ( 2 ) ', lines[-2] }
tmux.send_keys ' C - a '
tmux.until { |lines| assert_equal ' 10 / 100 ( 12 ) ', lines[-2] }
tmux.send_keys :Enter
assert_equal %w[1 2 10 20 30 40 50 60 70 80 90 100],
fzf_output_lines
end
def test_history
history_file = ' / tmp / fzf - test - history '
# History with limited number of entries
FileUtils.rm_f(history_file)
opts = "--history=#{history_file} --history-size=4"
input = %w[00 11 22 33 44]
input.each do |keys|
tmux.prepare
tmux.send_keys "seq 100 | #{FZF} #{opts}", :Enter
tmux.until { |lines| assert_equal ' 100 / 100 ', lines[-2] }
tmux.send_keys keys
tmux.until { |lines| assert_equal ' 1 / 100 ', lines[-2] }
tmux.send_keys :Enter
end
wait do
assert_path_exists history_file
assert_equal input[1..], File.readlines(history_file, chomp: true)
end
# Update history entries (not changed on disk)
tmux.send_keys "seq 100 | #{FZF} #{opts}", :Enter
tmux.until { |lines| assert_equal ' 100 / 100 ', lines[-2] }
tmux.send_keys ' C - p '
tmux.until { |lines| assert_equal ' > 44 ', lines[-1] }
tmux.send_keys ' C - p '
tmux.until { |lines| assert_equal ' > 33 ', lines[-1] }
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal ' > 3 ', lines[-1] }
tmux.send_keys 1
tmux.until { |lines| assert_equal ' > 31 ', lines[-1] }
tmux.send_keys ' C - p '
tmux.until { |lines| assert_equal ' > 22 ', lines[-1] }
tmux.send_keys ' C - n '
tmux.until { |lines| assert_equal ' > 31 ', lines[-1] }
tmux.send_keys 0
tmux.until { |lines| assert_equal ' > 310 ', lines[-1] }
tmux.send_keys :Enter
wait do
assert_path_exists history_file
assert_equal %w[22 33 44 310], File.readlines(history_file, chomp: true)
end
# Respect --bind option
tmux.send_keys "seq 100 | #{FZF} #{opts} --bind ctrl-p:next-history,ctrl-n:previous-history", :Enter
tmux.until { |lines| assert_equal ' 100 / 100 ', lines[-2] }
tmux.send_keys ' C - n ', ' C - n ', ' C - n ', ' C - n ', ' C - p '
tmux.until { |lines| assert_equal ' > 33 ', lines[-1] }
tmux.send_keys :Enter
ensure
FileUtils.rm_f(history_file)
end
def test_cycle
tmux.send_keys "seq 8 | #{FZF} --cycle", :Enter
tmux.until { |lines| assert_equal ' 8 / 8 ', lines[-2] }
tmux.send_keys :Down
tmux.until { |lines| assert_equal ' > 8 ', lines[-10] }
tmux.send_keys :Down
tmux.until { |lines| assert_equal ' > 7 ', lines[-9] }
tmux.send_keys :Up
tmux.until { |lines| assert_equal ' > 8 ', lines[-10] }
tmux.send_keys :PgUp
tmux.until { |lines| assert_equal ' > 8 ', lines[-10] }
tmux.send_keys :Up
tmux.until { |lines| assert_equal ' > 1 ', lines[-3] }
tmux.send_keys :PgDn
tmux.until { |lines| assert_equal ' > 1 ', lines[-3] }
tmux.send_keys :Down
tmux.until { |lines| assert_equal ' > 8 ', lines[-10] }
end
def test_header_lines
tmux.send_keys "seq 100 | #{fzf(' -- header - lines = 10 - q 5 ')}", :Enter
2.times do
tmux.until do |lines|
assert_equal ' 18 / 90 ', lines[-2]
assert_equal ' 1 ', lines[-3]
assert_equal ' 2 ', lines[-4]
assert_equal ' > 50 ', lines[-13]
end
tmux.send_keys :Down
end
tmux.send_keys :Enter
assert_equal ' 50 ', fzf_output
end
def test_header_lines_reverse
tmux.send_keys "seq 100 | #{fzf(' -- header - lines = 10 - q 5 -- reverse ')}", :Enter
2.times do
tmux.until do |lines|
assert_equal ' 18 / 90 ', lines[1]
assert_equal ' 1 ', lines[2]
assert_equal ' 2 ', lines[3]
assert_equal ' > 50 ', lines[12]
end
tmux.send_keys :Up
end
tmux.send_keys :Enter
assert_equal ' 50 ', fzf_output
end
def test_header_lines_reverse_list
tmux.send_keys "seq 100 | #{fzf(' -- header - lines = 10 - q 5 -- layout = reverse - list ')}", :Enter
2.times do
tmux.until do |lines|
2025-02-01 16:54:46 +09:00
assert_equal ' 9 ', lines[8]
assert_equal ' 10 ', lines[9]
assert_equal ' > 50 ', lines[10]
2025-01-25 19:57:40 +09:00
assert_equal ' 18 / 90 ', lines[-2]
end
tmux.send_keys :Up
end
tmux.send_keys :Enter
assert_equal ' 50 ', fzf_output
end
def test_header_lines_overflow
tmux.send_keys "seq 100 | #{fzf(' -- header - lines = 200 ')}", :Enter
tmux.until do |lines|
assert_equal ' 0 / 0 ', lines[-2]
assert_equal ' 1 ', lines[-3]
end
tmux.send_keys :Enter
assert_equal '', fzf_output
end
def test_header_lines_with_nth
tmux.send_keys "seq 100 | #{fzf(' -- header - lines 5 -- with - nth 1 , 1 , 1 , 1 , 1 ')}", :Enter
tmux.until do |lines|
assert_equal ' 95 / 95 ', lines[-2]
assert_equal ' 11111 ', lines[-3]
assert_equal ' 55555 ', lines[-7]
assert_equal ' > 66666 ', lines[-8]
end
tmux.send_keys :Enter
assert_equal ' 6 ', fzf_output
end
def test_header
tmux.send_keys %[seq 100 | #{FZF} --header "$(head -5 #{FILE})"], :Enter
header = File.readlines(FILE, chomp: true).take(5)
tmux.until do |lines|
assert_equal ' 100 / 100 ', lines[-2]
assert_equal header.map { |line| " #{line}".rstrip }, lines[-7..-3]
assert_equal ' > 1 ', lines[-8]
end
end
def test_header_reverse
tmux.send_keys %[seq 100 | #{FZF} --header "$(head -5 #{FILE})" --reverse], :Enter
header = File.readlines(FILE, chomp: true).take(5)
tmux.until do |lines|
assert_equal ' 100 / 100 ', lines[1]
assert_equal header.map { |line| " #{line}".rstrip }, lines[2..6]
assert_equal ' > 1 ', lines[7]
end
end
def test_header_reverse_list
tmux.send_keys %[seq 100 | #{FZF} --header "$(head -5 #{FILE})" --layout=reverse-list], :Enter
header = File.readlines(FILE, chomp: true).take(5)
tmux.until do |lines|
assert_equal ' 100 / 100 ', lines[-2]
assert_equal header.map { |line| " #{line}".rstrip }, lines[-7..-3]
assert_equal ' > 1 ', lines[0]
end
end
def test_header_and_header_lines
tmux.send_keys %[seq 100 | #{FZF} --header-lines 10 --header "$(head -5 #{FILE})"], :Enter
header = File.readlines(FILE, chomp: true).take(5)
tmux.until do |lines|
assert_equal ' 90 / 90 ', lines[-2]
assert_equal header.map { |line| " #{line}".rstrip }, lines[-7...-2]
assert_equal (' 1 '..' 10 ').to_a.reverse, lines[-17...-7]
end
end
def test_header_and_header_lines_reverse
tmux.send_keys %[seq 100 | #{FZF} --reverse --header-lines 10 --header "$(head -5 #{FILE})"], :Enter
header = File.readlines(FILE, chomp: true).take(5)
tmux.until do |lines|
assert_equal ' 90 / 90 ', lines[1]
assert_equal header.map { |line| " #{line}".rstrip }, lines[2...7]
assert_equal (' 1 '..' 10 ').to_a, lines[7...17]
end
end
def test_header_and_header_lines_reverse_list
tmux.send_keys %[seq 100 | #{FZF} --layout=reverse-list --header-lines 10 --header "$(head -5 #{FILE})"], :Enter
header = File.readlines(FILE, chomp: true).take(5)
tmux.until do |lines|
assert_equal ' 90 / 90 ', lines[-2]
assert_equal header.map { |line| " #{line}".rstrip }, lines[-7...-2]
2025-02-01 16:54:46 +09:00
assert_equal (' 1 '..' 10 ').to_a, lines.take(10)
2025-01-25 19:57:40 +09:00
end
end
def test_cancel
tmux.send_keys "seq 10 | #{FZF} --bind 2:cancel", :Enter
tmux.until { |lines| assert_equal ' 10 / 10 ', lines[-2] }
tmux.send_keys ' 123 '
tmux.until do |lines|
assert_equal ' > 3 ', lines[-1]
assert_equal ' 1 / 10 ', lines[-2]
end
tmux.send_keys ' C - y ', ' C - y '
tmux.until { |lines| assert_equal ' > 311 ', lines[-1] }
tmux.send_keys 2
tmux.until { |lines| assert_equal ' > ', lines[-1] }
tmux.send_keys 2
tmux.prepare
end
def test_margin
tmux.send_keys "yes | head -1000 | #{FZF} --margin 5,3", :Enter
tmux.until do |lines|
assert_equal '', lines[4]
assert_equal ' y ', lines[5]
end
tmux.send_keys :Enter
end
def test_margin_reverse
tmux.send_keys "seq 1000 | #{FZF} --margin 7,5 --reverse", :Enter
tmux.until { |lines| assert_equal ' 1000 / 1000 ', lines[1 + 7] }
tmux.send_keys :Enter
end
def test_margin_reverse_list
tmux.send_keys "yes | head -1000 | #{FZF} --margin 5,3 --layout=reverse-list", :Enter
tmux.until do |lines|
assert_equal '', lines[4]
assert_equal ' > y ', lines[5]
end
tmux.send_keys :Enter
end
def test_tabstop
writelines(%W[f\too\tba\tr\tbaz\tbarfooq\tux])
{
1 => ' > f oo ba r baz barfooq ux ',
2 => ' > f oo ba r baz barfooq ux ',
3 => ' > f oo ba r baz barfooq ux ',
4 => ' > f oo ba r baz barfooq ux ',
5 => ' > f oo ba r baz barfooq ux ',
6 => ' > f oo ba r baz barfooq ux ',
7 => ' > f oo ba r baz barfooq ux ',
8 => ' > f oo ba r baz barfooq ux ',
9 => ' > f oo ba r baz barfooq ux '
}.each do |ts, exp|
tmux.prepare
tmux.send_keys %(cat #{tempname} | fzf --tabstop=#{ts}), :Enter
tmux.until(true) do |lines|
assert_equal exp, lines[-3]
end
tmux.send_keys :Enter
end
end
def test_exit_0_exit_code
`echo foo | #{FZF} -q bar -0`
assert_equal 1, $CHILD_STATUS.exitstatus
end
def test_invalid_option
lines = `#{FZF} --foobar 2>&1`
assert_equal 2, $CHILD_STATUS.exitstatus
assert_includes lines, ' unknown option : -- foobar '
end
def test_exitstatus_empty
{ ' 99 ' => ' 0 ', ' 999 ' => ' 1 ' }.each do |query, status|
tmux.send_keys "seq 100 | #{FZF} -q #{query}; echo --$?--", :Enter
tmux.until { |lines| assert_match %r{ [10]/100}, lines[-2] }
tmux.send_keys :Enter
tmux.until { |lines| assert_equal "--#{status}--", lines.last }
end
end
def test_hscroll_off
writelines([(' = ' * 10_000) + ' 01234567 89 '])
[0, 3, 6].each do |off|
tmux.prepare
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 --bind space:toggle-hscroll < #{tempname}", :Enter
tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + ' ·· ') }
tmux.send_keys ' 9 '
tmux.until { |lines| assert lines[-3]&.end_with?(' 789 ') }
tmux.send_keys :Space
tmux.until { |lines| assert lines[-3]&.end_with?(' = ·· ') }
tmux.send_keys :Space
tmux.until { |lines| assert lines[-3]&.end_with?(' 789 ') }
tmux.send_keys :Enter
end
end
def test_partial_caching
tmux.send_keys ' seq 1000 | fzf - e ', :Enter
tmux.until { |lines| assert_equal ' 1000 / 1000 ', lines[-2] }
tmux.send_keys 11
tmux.until { |lines| assert_equal ' 19 / 1000 ', lines[-2] }
tmux.send_keys ' C - a ', "' "
tmux.until { |lines| assert_equal ' 28/1000', lines[-2] }
tmux.send_keys :Enter
end
def test_jump
tmux.send_keys " seq 1000 | #{fzf("--multi --jump-labels 12345 --bind 'ctrl-j:jump'")}", :Enter
tmux . until { | lines | assert_equal ' 1000/1000 (0)' , lines [- 2 ] }
tmux . send_keys 'C-j'
tmux . until { | lines | assert_equal '5 5' , lines [- 7 ] }
tmux . until { | lines | assert_equal ' 6' , lines [- 8 ] }
tmux . send_keys '5'
tmux . until { | lines | assert_equal '> 5' , lines [- 7 ] }
tmux . send_keys :Tab
tmux . until { | lines | assert_equal ' >5' , lines [- 7 ] }
tmux . send_keys 'C-j'
tmux . until { | lines | assert_equal '5>5' , lines [- 7 ] }
tmux . send_keys '2'
tmux . until { | lines | assert_equal '> 2' , lines [- 4 ] }
tmux . send_keys :Tab
tmux . until { | lines | assert_equal ' >2' , lines [- 4 ] }
tmux . send_keys 'C-j'
tmux . until { | lines | assert_equal '5>5' , lines [- 7 ] }
# Press any key other than jump labels to cancel jump
tmux . send_keys '6'
tmux . until { | lines | assert_equal '> 1' , lines [- 3 ] }
tmux . send_keys :Tab
tmux . until { | lines | assert_equal '>>1' , lines [- 3 ] }
tmux . send_keys :Enter
assert_equal %w[5 2 1] , fzf_output_lines
end
def test_jump_accept
tmux . send_keys "seq 1000 | #{ fzf ( "--multi --jump-labels 12345 --bind 'ctrl-j:jump-accept'" ) } " , :Enter
tmux . until { | lines | assert_equal ' 1000/1000 (0)' , lines [- 2 ] }
tmux . send_keys 'C-j'
tmux . until { | lines | assert_equal '5 5' , lines [- 7 ] }
tmux . send_keys '3'
assert_equal '3' , fzf_output
end
def test_jump_events
tmux . send_keys "seq 1000 | #{ FZF } --multi --jump-labels 12345 --bind 'ctrl-j:jump,jump:preview(echo jumped to {}),jump-cancel:preview(echo jump cancelled at {})'" , :Enter
tmux . until { | lines | assert_equal ' 1000/1000 (0)' , lines [- 2 ] }
tmux . send_keys 'C-j'
tmux . until { | lines | assert_includes lines [- 7 ] , '5 5' }
tmux . send_keys '3'
tmux . until { | lines | assert ( lines . any? { it . include? ( 'jumped to 3' ) }) }
tmux . send_keys 'C-j'
tmux . until { | lines | assert_includes lines [- 7 ] , '5 5' }
tmux . send_keys 'C-c'
tmux . until { | lines | assert ( lines . any? { it . include? ( 'jump cancelled at 3' ) }) }
end
2025-02-22 19:05:30 +09:00
def test_jump_no_pointer
tmux . send_keys "seq 100 | #{ FZF } --pointer= --jump-labels 12345 --bind ctrl-j:jump" , :Enter
tmux . until { | lines | assert_equal 100 , lines . match_count }
tmux . send_keys 'C-j'
tmux . until { | lines | assert_equal '5 5' , lines [- 7 ] }
tmux . send_keys 'C-c'
tmux . until { | lines | assert_equal ' 5' , lines [- 7 ] }
end
def test_jump_no_pointer_no_marker
tmux . send_keys "seq 100 | #{ FZF } --pointer= --marker= --jump-labels 12345 --bind ctrl-j:jump" , :Enter
tmux . until { | lines | assert_equal 100 , lines . match_count }
tmux . send_keys 'C-j'
tmux . until { | lines | assert_equal '55' , lines [- 7 ] }
tmux . send_keys 'C-c'
tmux . until { | lines | assert_equal '5' , lines [- 7 ] }
end
2025-01-25 19:57:40 +09:00
def test_pointer
tmux . send_keys "seq 10 | #{ fzf ( "--pointer '>>'" ) } " , :Enter
# Assert that specified pointer is displayed
tmux . until { | lines | assert_equal '>> 1' , lines [- 3 ] }
end
def test_pointer_with_jump
tmux . send_keys "seq 10 | #{ FZF } --multi --jump-labels 12345 --bind 'ctrl-j:jump' --pointer '>>'" , :Enter
tmux . until { | lines | assert_equal ' 10/10 (0)' , lines [- 2 ] }
tmux . send_keys 'C-j'
# Correctly padded jump label should appear
tmux . until { | lines | assert_equal '5 5' , lines [- 7 ] }
tmux . until { | lines | assert_equal ' 6' , lines [- 8 ] }
tmux . send_keys '5'
# Assert that specified pointer is displayed
tmux . until { | lines | assert_equal '>> 5' , lines [- 7 ] }
end
def test_marker
tmux . send_keys "seq 10 | #{ FZF } --multi --marker '>>'" , :Enter
tmux . until { | lines | assert_equal ' 10/10 (0)' , lines [- 2 ] }
tmux . send_keys :BTab
# Assert that specified marker is displayed
tmux . until { | lines | assert_equal ' >>1' , lines [- 3 ] }
end
def test_no_clear
tmux . send_keys "seq 10 | #{ fzf ( '--no-clear --inline-info --height 5' ) } " , :Enter
prompt = '> < 10/10'
tmux . until { | lines | assert_equal prompt , lines [- 1 ] }
tmux . send_keys :Enter
assert_equal %w[1] , fzf_output_lines
tmux . until { | lines | assert_equal prompt , lines [- 1 ] }
end
def test_info_hidden
tmux . send_keys 'seq 10 | fzf --info=hidden --no-separator' , :Enter
tmux . until { | lines | assert_equal '> 1' , lines [- 2 ] }
end
def test_info_inline_separator
tmux . send_keys 'seq 10 | fzf --info=inline:___ --no-separator' , :Enter
tmux . until { | lines | assert_equal '> ___10/10' , lines [- 1 ] }
end
def test_change_first_last
tmux . send_keys %(seq 1000 | #{ FZF } --bind change:first,alt-Z:last) , :Enter
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . send_keys :Up
tmux . until { | lines | assert_equal '> 2' , lines [- 4 ] }
tmux . send_keys 1
tmux . until { | lines | assert_equal '> 1' , lines [- 3 ] }
tmux . send_keys :Up
tmux . until { | lines | assert_equal '> 10' , lines [- 4 ] }
tmux . send_keys 1
tmux . until { | lines | assert_equal '> 11' , lines [- 3 ] }
tmux . send_keys 'C-u'
tmux . until { | lines | assert_equal '> 1' , lines [- 3 ] }
tmux . send_keys :Escape , 'Z'
tmux . until { | lines | assert_equal '> 1000' , lines [ 0 ] }
tmux . send_keys :Enter
end
def test_pos
tmux . send_keys %(seq 1000 | #{ FZF } --bind 'a:pos(3),b:pos(-3),c:pos(1),d:pos(-1),e:pos(0)' --preview 'echo {}/{}') , :Enter
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . send_keys :a
tmux . until { | lines | assert_includes lines [ 1 ] , ' 3/3' }
tmux . send_keys :b
tmux . until { | lines | assert_includes lines [ 1 ] , ' 998/998' }
tmux . send_keys :c
tmux . until { | lines | assert_includes lines [ 1 ] , ' 1/1' }
tmux . send_keys :d
tmux . until { | lines | assert_includes lines [ 1 ] , ' 1000/1000' }
tmux . send_keys :e
tmux . until { | lines | assert_includes lines [ 1 ] , ' 1/1' }
end
def test_put
tmux . send_keys %(seq 1000 | #{ FZF } --bind 'a:put+put,b:put+put(ravo)' --preview 'echo {q}/{q}') , :Enter
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . send_keys :a
tmux . until { | lines | assert_includes lines [ 1 ] , ' aa/aa' }
tmux . send_keys :b
tmux . until { | lines | assert_includes lines [ 1 ] , ' aabravo/aabravo' }
end
2026-06-02 20:20:51 +09:00
def test_transform_put
tmux . send_keys %(seq 1000 | #{ FZF } --bind 'a:transform:echo put') , :Enter
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . send_keys :a
tmux . until { | lines | assert_equal '> a' , lines . last }
tmux . send_keys :b
tmux . until { | lines | assert_equal '> ab' , lines . last }
end
# The async callback runs in a later iteration, but 'put' must still insert
# the key that triggered the bg-transform (snapshot of the scheduling event).
def test_bg_transform_put
tmux . send_keys %(seq 1000 | #{ FZF } --bind 'a:bg-transform:sleep 0.5; echo put') , :Enter
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . send_keys 'ab'
tmux . until { | lines | assert_equal '> ba' , lines . last }
end
2025-01-25 19:57:40 +09:00
def test_accept_non_empty
tmux . send_keys %(seq 1000 | #{ fzf ( '--print-query --bind enter:accept-non-empty' ) } ) , :Enter
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . send_keys 'foo'
tmux . until { | lines | assert_equal ' 0/1000' , lines [- 2 ] }
# fzf doesn't exit since there's no selection
tmux . send_keys :Enter
tmux . until { | lines | assert_equal ' 0/1000' , lines [- 2 ] }
tmux . send_keys 'C-u'
tmux . until { | lines | assert_equal ' 1000/1000' , lines [- 2 ] }
tmux . send_keys '999'
tmux . until { | lines | assert_equal ' 1/1000' , lines [- 2 ] }
tmux . send_keys :Enter
assert_equal %w[999 999] , fzf_output_lines
end
def test_accept_non_empty_with_multi_selection
tmux . send_keys %(seq 1000 | #{ fzf ( '-m --print-query --bind enter:accept-non-empty' ) } ) , :Enter
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . send_keys :Tab
tmux . until { | lines | assert_equal ' 1000/1000 (1)' , lines [- 2 ] }
tmux . send_keys 'foo'
tmux . until { | lines | assert_equal ' 0/1000 (1)' , lines [- 2 ] }
# fzf will exit in this case even though there's no match for the current query
tmux . send_keys :Enter
assert_equal %w[foo 1] , fzf_output_lines
end
def test_accept_non_empty_with_empty_list
tmux . send_keys %(: | #{ fzf ( '-q foo --print-query --bind enter:accept-non-empty' ) } ) , :Enter
tmux . until { | lines | assert_equal ' 0/0' , lines [- 2 ] }
tmux . send_keys :Enter
# fzf will exit anyway since input list is empty
assert_equal %w[foo] , fzf_output_lines
end
def test_accept_or_print_query_without_match
tmux . send_keys %(seq 1000 | #{ fzf ( '--bind enter:accept-or-print-query' ) } ) , :Enter
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . send_keys 99_999
tmux . until { | lines | assert_equal 0 , lines . match_count }
tmux . send_keys :Enter
assert_equal %w[99999] , fzf_output_lines
end
def test_accept_or_print_query_with_match
tmux . send_keys %(seq 1000 | #{ fzf ( '--bind enter:accept-or-print-query' ) } ) , :Enter
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . send_keys '^99$'
tmux . until { | lines | assert_equal 1 , lines . match_count }
tmux . send_keys :Enter
assert_equal %w[99] , fzf_output_lines
end
def test_accept_or_print_query_with_multi_selection
tmux . send_keys %(seq 1000 | #{ fzf ( '--bind enter:accept-or-print-query --multi' ) } ) , :Enter
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | assert_equal 3 , lines . select_count }
tmux . send_keys 99_999
tmux . until { | lines | assert_equal 0 , lines . match_count }
tmux . send_keys :Enter
assert_equal %w[1 2 3] , fzf_output_lines
end
def test_inverse_only_search_should_not_sort_the_result
# Filter
assert_equal %w[aaaaa b ccc] ,
`printf '%s \n ' aaaaa b ccc BAD | #{ FZF } -f '!bad'` . lines ( chomp : true )
# Interactive
tmux . send_keys %(printf '%s \n ' aaaaa b ccc BAD | #{ FZF } -q '!bad') , :Enter
tmux . until do | lines |
assert_equal 4 , lines . item_count
assert_equal 3 , lines . match_count
end
tmux . until { | lines | assert_equal '> aaaaa' , lines [- 3 ] }
tmux . until { | lines | assert_equal ' b' , lines [- 4 ] }
tmux . until { | lines | assert_equal ' ccc' , lines [- 5 ] }
end
def test_disabled
tmux . send_keys %(seq 1000 | #{ FZF } --query 333 --disabled --bind a:enable-search,b:disable-search,c:toggle-search --preview 'echo {} {q}') , :Enter
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . until { | lines | assert_includes lines [ 1 ] , ' 1 333 ' }
tmux . send_keys 'foo'
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . until { | lines | assert_includes lines [ 1 ] , ' 1 333foo ' }
# Already disabled, no change
tmux . send_keys 'b'
tmux . until { | lines | assert_equal 1000 , lines . match_count }
# Enable search
tmux . send_keys 'a'
tmux . until { | lines | assert_equal 0 , lines . match_count }
tmux . send_keys :BSpace , :BSpace , :BSpace
tmux . until { | lines | assert_equal 1 , lines . match_count }
tmux . until { | lines | assert_includes lines [ 1 ] , ' 333 333 ' }
# Toggle search -> disabled again, but retains the previous result
tmux . send_keys 'c'
tmux . send_keys 'foo'
tmux . until { | lines | assert_includes lines [ 1 ] , ' 333 333foo ' }
tmux . until { | lines | assert_equal 1 , lines . match_count }
# Enabled, no match
tmux . send_keys 'c'
tmux . until { | lines | assert_equal 0 , lines . match_count }
tmux . until { | lines | assert_includes lines [ 1 ] , ' 333foo ' }
end
def test_clear_query
tmux . send_keys %(: | #{ FZF } --query foo --bind space:clear-query) , :Enter
tmux . until { | lines | assert_equal 0 , lines . item_count }
tmux . until { | lines | assert_equal '> foo' , lines . last }
tmux . send_keys 'C-a' , 'bar'
tmux . until { | lines | assert_equal '> barfoo' , lines . last }
tmux . send_keys :Space
tmux . until { | lines | assert_equal '>' , lines . last }
end
def test_change_query
tmux . send_keys %(: | #{ FZF } --query foo --bind space:change-query:foobar) , :Enter
tmux . until { | lines | assert_equal 0 , lines . item_count }
tmux . until { | lines | assert_equal '> foo' , lines . last }
tmux . send_keys :Space , 'baz'
tmux . until { | lines | assert_equal '> foobarbaz' , lines . last }
end
def test_transform_query
tmux . send_keys %{ #{ FZF } --bind 'ctrl-r:transform-query(rev <<< {q}),ctrl-u:transform-query: tr "[:lower:]" "[:upper:]" <<< {q}' --query bar} , :Enter
tmux . until { | lines | assert_equal '> bar' , lines [- 1 ] }
tmux . send_keys 'C-r'
tmux . until { | lines | assert_equal '> rab' , lines [- 1 ] }
tmux . send_keys 'C-u'
tmux . until { | lines | assert_equal '> RAB' , lines [- 1 ] }
end
def test_transform_prompt
tmux . send_keys %{ #{ FZF } --bind 'ctrl-r:transform-query(rev <<< {q}),ctrl-u:transform-query: tr "[:lower:]" "[:upper:]" <<< {q}' --query bar} , :Enter
tmux . until { | lines | assert_equal '> bar' , lines [- 1 ] }
tmux . send_keys 'C-r'
tmux . until { | lines | assert_equal '> rab' , lines [- 1 ] }
tmux . send_keys 'C-u'
tmux . until { | lines | assert_equal '> RAB' , lines [- 1 ] }
end
def test_transform
tmux . send_keys %{ #{ FZF } --bind 'focus:transform:echo "change-prompt({fzf:action})"'} , :Enter
tmux . until { | lines | assert_equal 'start' , lines [- 1 ] }
tmux . send_keys :Up
tmux . until { | lines | assert_equal 'up' , lines [- 1 ] }
end
2025-01-26 01:50:08 +09:00
def test_search
tmux . send_keys %(seq 100 | #{ FZF } --query 0 --bind space:search:1) , :Enter
tmux . until { | lines | assert_equal 10 , lines . match_count }
tmux . send_keys :Space
tmux . until { | lines | assert_equal 20 , lines . match_count }
tmux . send_keys '0'
tmux . until { | lines | assert_equal 1 , lines . match_count }
end
def test_transform_search
tmux . send_keys %(seq 1000 | #{ FZF } --bind 'change:transform-search:echo {q}{q}') , :Enter
tmux . until { | lines | assert_equal 1000 , lines . match_count }
tmux . send_keys '1'
tmux . until { | lines | assert_equal 28 , lines . match_count }
tmux . send_keys :BSpace , '0'
tmux . until { | lines | assert_equal 10 , lines . match_count }
end
2026-07-05 11:05:57 +09:00
def test_wait_action
tmux . send_keys %((seq 100; sleep 15) | #{ FZF } --bind 'start:search(1)+wait+best') , :Enter
tmux . until { | lines | assert_equal 20 , lines . match_count }
tmux . until { | lines | assert lines . any_include? ( '20/100 (..)' ) }
tmux . send_keys 'C-c'
tmux . until { | lines | refute lines . any_include? ( '20/100 (..)' ) }
# Ctrl-C cancels the wait; fzf keeps running and accepts input again
tmux . send_keys '99'
tmux . until { | lines | assert_equal 1 , lines . match_count }
end
def test_wait_action_start
# 'start:wait' blocks on the initial load until reading completes
tmux . send_keys %((seq 100; sleep 15) | #{ FZF } --bind 'start:wait') , :Enter
tmux . until { | lines | assert_equal 100 , lines . match_count }
tmux . until { | lines | assert lines . any_include? ( '(..)' ) }
tmux . send_keys 'C-c'
tmux . until { | lines | refute lines . any_include? ( '(..)' ) }
# Ctrl-C cancels the wait; fzf keeps running and accepts input again
tmux . send_keys '99'
tmux . until { | lines | assert_equal 1 , lines . match_count }
end
2026-07-05 11:07:44 +09:00
def test_wait_action_bg_transform
# A bg-transform result is unrelated to the wait, so it's applied while the
# wait is still blocking rather than dropped. The long read keeps the wait
# blocked so the (instant) bg-transform completes during the block; the
# header must show while '(..)' is still displayed.
tmux . send_keys %((seq 100; sleep 2) | #{ FZF } --bind 'start:bg-transform-header(echo hello)+search(5)+wait') , :Enter
tmux . until { | lines | assert ( lines . any_include? ( '(..)' ) && lines . any_include? ( 'hello' )) }
end
def test_wait_action_bg_transform_actions
# Actions parsed from a generic bg-transform result are also applied
# while wait-blocked
tmux . send_keys %((seq 100; sleep 2) | #{ FZF } --bind 'start:bg-transform(echo change-header:hello)+search(5)+wait') , :Enter
tmux . until { | lines | assert ( lines . any_include? ( '(..)' ) && lines . any_include? ( 'hello' )) }
end
def test_wait_action_bg_transform_join
# A 'wait' in a bg-transform result body joins the ongoing wait; the
# actions after it run when the wait unblocks instead of being dropped
tmux . send_keys %((seq 100; sleep 2) | #{ FZF } --bind 'start:bg-transform(echo change-header{hello}+wait+change-footer{world})+search(5)+wait') , :Enter
tmux . until { | lines | assert ( lines . any_include? ( '(..)' ) && lines . any_include? ( 'hello' ) && ! lines . any_include? ( 'world' )) }
tmux . until { | lines | assert ( lines . any_include? ( 'world' ) && ! lines . any_include? ( '(..)' )) }
end
2026-07-05 11:05:57 +09:00
def test_wait_action_query_change
# Query-editing actions must also make wait block; accept must run on the
# results of the new query, not the stale ones
tmux . send_keys %(seq 100 | #{ fzf ( "--bind 'space:change-query(55)+wait+accept'" ) } ) , :Enter
tmux . until { | lines | assert_equal 100 , lines . match_count }
tmux . send_keys :Space
assert_equal '55' , fzf_output
end
def test_wait_action_trigger_join
# A wait armed inside a triggered chord defers the remaining actions of
# the outer binding as well
tmux . send_keys %((seq 100; sleep 2) | #{ FZF } --bind 'start:trigger(x)+change-footer(world)' --bind 'x:search(5)+wait') , :Enter
tmux . until { | lines | assert ( lines . any_include? ( '(..)' ) && ! lines . any_include? ( 'world' )) }
tmux . until { | lines | assert ( lines . any_include? ( 'world' ) && ! lines . any_include? ( '(..)' )) }
end
def test_wait_action_trigger_siblings
# Chords after a wait-arming chord are deferred, not dropped
tmux . send_keys %((seq 100; sleep 2) | #{ FZF } --bind 'start:trigger(x,y)+change-footer(world)' --bind 'x:search(5)+wait' --bind 'y:change-header(hello)') , :Enter
tmux . until { | lines | assert ( lines . any_include? ( '(..)' ) && ! lines . any_include? ( 'hello' ) && ! lines . any_include? ( 'world' )) }
tmux . until { | lines | assert ( lines . any_include? ( 'hello' ) && lines . any_include? ( 'world' ) && ! lines . any_include? ( '(..)' )) }
end
def test_wait_action_cancel_rearm
# Deferral works even when the wait is cancelled and re-armed within a
# single action list. --query keeps 'cancel' non-fatal in case the Space
# arrives after the initial wait has already unblocked.
tmux . send_keys %((seq 100; sleep 2) | #{ FZF } --query 5 --bind 'start:wait' --bind 'space:cancel+trigger(x)+change-footer(world)' --bind 'x:search(5)+wait') , :Enter
tmux . until { | lines | assert lines . any_include? ( '(..)' ) }
tmux . send_keys :Space
tmux . until { | lines | assert ( lines . any_include? ( 'world' ) && ! lines . any_include? ( '(..)' )) }
end
def test_wait_action_jump
# A pending jump action must survive the wake-up event after unblock
tmux . send_keys %((seq 100; sleep 2) | #{ FZF } --jump-labels abc --bind 'start:search(5)+wait+jump') , :Enter
tmux . until { | lines | assert_equal 'a 5' , lines [- 3 ] }
tmux . send_keys 'a'
tmux . until { | lines | assert_equal '> 5' , lines [- 3 ] }
end
2026-07-05 11:08:46 +09:00
def test_wait_action_bracketed_paste
# A wait armed by a binding fired from a pasted character must not break
# bracketed paste handling: paste-end passes through the block so
# t.pasting is cleared and the pending search is dispatched
tmux . send_keys %(seq 100 | #{ FZF } --bind 'a:put(a)+wait+first') , :Enter
tmux . until { | lines | assert_equal 100 , lines . match_count }
tmux . paste_bracketed ( '1a2' )
# Search for the edited query must run; blocked forever before the fix
tmux . until { | lines | assert_equal 0 , lines . match_count }
# Filtering must still work afterwards
tmux . send_keys 'C-u' , '55'
tmux . until { | lines | assert_equal 1 , lines . match_count }
end
2026-07-05 11:05:57 +09:00
def test_wait_action_expect
# --expect keys are ignored while wait-blocked, like the rest of the input
tmux . send_keys %((seq 100; sleep 2) | #{ fzf ( '--expect ctrl-t --bind start:wait' ) } ) , :Enter
tmux . until { | lines | assert lines . any_include? ( '(..)' ) }
tmux . send_keys 'C-t'
# fzf must still be running; the wait unblocks when loading completes
tmux . until { | lines | assert_equal 100 , lines . match_count }
tmux . until { | lines | refute lines . any_include? ( '(..)' ) }
tmux . send_keys 'C-t'
assert_equal %w[ctrl-t 1] , fzf_output_lines
end
2026-07-05 11:07:44 +09:00
def test_track_blocked_bg_transform
# A bg-transform result completing while track-blocked is applied, not
# dropped. The header must show while '+T*' is still displayed.
tmux . send_keys "seq 100 | #{ FZF } --track --id-nth .. --bind 'ctrl-r:bg-transform-header(echo hello)+reload(sleep 2; seq 100)'" , :Enter
tmux . until { | lines | assert_includes lines [- 2 ] , '+T' }
tmux . send_keys 'C-r'
tmux . until { | lines | assert ( lines . any_include? ( '+T*' ) && lines . any_include? ( 'hello' )) }
end
2025-01-25 19:57:40 +09:00
def test_clear_selection
tmux . send_keys %(seq 100 | #{ FZF } --multi --bind space:clear-selection) , :Enter
tmux . until { | lines | assert_equal 100 , lines . match_count }
tmux . send_keys :Tab
tmux . until { | lines | assert_equal ' 100/100 (1)' , lines [- 2 ] }
tmux . send_keys 'foo'
tmux . until { | lines | assert_equal ' 0/100 (1)' , lines [- 2 ] }
tmux . send_keys :Space
tmux . until { | lines | assert_equal ' 0/100 (0)' , lines [- 2 ] }
end
def test_backward_delete_char_eof
tmux . send_keys "seq 1000 | #{ FZF } --bind 'bs:backward-delete-char/eof'" , :Enter
tmux . until { | lines | assert_equal ' 1000/1000' , lines [- 2 ] }
tmux . send_keys '11'
tmux . until { | lines | assert_equal '> 11' , lines [- 1 ] }
tmux . send_keys :BSpace
tmux . until { | lines | assert_equal '> 1' , lines [- 1 ] }
tmux . send_keys :BSpace
tmux . until { | lines | assert_equal '>' , lines [- 1 ] }
tmux . send_keys :BSpace
tmux . prepare
end
def test_strip_xterm_osc_sequence
%W[ \x07 \x1b \\] . each do | esc |
writelines ( [ %(printf $1" \e ]4;3;rgb:aa/bb/cc #{ esc } "$2) ] )
File . chmod ( 0 o755 , tempname )
tmux . prepare
tmux . send_keys \
%(echo foo bar | #{ FZF } --preview ' #{ tempname } {2} {1}') , :Enter
tmux . until { | lines | assert lines . any_include? ( 'bar foo' ) }
tmux . send_keys :Enter
end
end
def test_keep_right
tmux . send_keys "seq 10000 | #{ FZF } --read0 --keep-right --no-multi-line --bind space:toggle-multi-line" , :Enter
tmux . until { | lines | assert lines . any_include? ( '9999␊10000' ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( '> 1' ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( '9999␊10000' ) }
end
2026-03-06 18:53:23 +09:00
def test_freeze_left_tabstop
2026-03-07 18:13:23 +09:00
writelines ( %W[1 \t 2 \t 3] )
2026-03-06 18:53:23 +09:00
# With --freeze-left 1 and --tabstop=2:
# Frozen left: "1" (width 1)
# Middle starts with "\t" at prefix width 1, tabstop 2 → 1 space
# Then "2" at column 2, next "\t" at column 3 → 1 space, then "3"
tmux . send_keys %(cat #{ tempname } | #{ FZF } --tabstop=2 --freeze-left 1) , :Enter
tmux . until { | lines | assert_equal '> 1 2 3' , lines [- 3 ] }
end
2025-11-09 14:54:12 +09:00
def test_freeze_left_keep_right
2026-02-01 11:08:11 +09:00
tmux . send_keys %(seq 10000 | #{ FZF } --read0 --delimiter " \n " --freeze-left 3 --keep-right --ellipsis XX --no-multi-line --bind space:toggle-multi-line) , :Enter
2025-11-09 14:54:12 +09:00
tmux . until { | lines | assert_match ( /^> 1␊2␊3XX.*10000␊$/ , lines [- 3 ] ) }
tmux . send_keys '5'
tmux . until { | lines | assert_match ( /^> 1␊2␊3␊4␊5␊.*XX$/ , lines [- 3 ] ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( '> 1' ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( '1␊2␊3␊4␊5␊' ) }
end
2025-11-09 19:41:58 +09:00
def test_freeze_left_and_right
2026-02-01 11:08:11 +09:00
tmux . send_keys %(seq 10000 | tr " \n " ' ' | #{ FZF } --freeze-left 3 --freeze-right 3 --ellipsis XX) , :Enter
2025-11-09 19:41:58 +09:00
tmux . until { | lines | assert_match ( /XX9998 9999 10000$/ , lines [- 3 ] ) }
tmux . send_keys "'1000"
2026-02-01 11:08:11 +09:00
tmux . until { | lines | assert_match ( /^> 1 2 3XX.*XX9998 9999 10000$/ , lines [- 3 ] ) }
2025-11-09 19:41:58 +09:00
end
2025-11-13 22:38:49 +09:00
def test_freeze_left_and_right_delimiter
2026-02-01 11:08:11 +09:00
tmux . send_keys %(seq 10000 | tr " \n " ' ' | sed 's/ / , /g' | #{ FZF } --freeze-left 3 --freeze-right 3 --ellipsis XX --delimiter ' , ') , :Enter
2025-11-13 22:38:49 +09:00
tmux . until { | lines | assert_match ( /XX, 9999 , 10000 ,$/ , lines [- 3 ] ) }
tmux . send_keys "'1000"
2026-02-01 11:08:11 +09:00
tmux . until { | lines | assert_match ( /^> 1 , 2 , 3 ,XX.*XX, 9999 , 10000 ,$/ , lines [- 3 ] ) }
2025-11-13 22:38:49 +09:00
end
2025-11-09 19:41:58 +09:00
def test_freeze_right_exceed_range
2026-02-01 11:08:11 +09:00
tmux . send_keys %(seq 10000 | tr " \n " ' ' | #{ FZF } --freeze-right 100000 --ellipsis XX) , :Enter
2025-11-09 19:41:58 +09:00
[ '' , "'1000" ]. each do | query |
tmux . send_keys query
tmux . until { | lines | assert lines . any_include? ( "> #{ query } " . strip ) }
tmux . until do | lines |
assert_match ( / 9998 9999 10000$/ , lines [- 3 ] )
assert_equal ( 1 , lines [- 3 ]. scan ( 'XX' ) . size )
end
end
end
def test_freeze_right_exceed_range_with_freeze_left
2026-02-01 11:08:11 +09:00
tmux . send_keys %(seq 10000 | tr " \n " ' ' | #{ FZF } --freeze-left 3 --freeze-right 100000 --ellipsis XX) , :Enter
2025-11-09 19:41:58 +09:00
tmux . until do | lines |
assert_match ( /^> 1 2 3XX.*9998 9999 10000$/ , lines [- 3 ] )
assert_equal ( 1 , lines [- 3 ]. scan ( 'XX' ) . size )
end
end
2025-11-20 01:09:36 +01:00
def test_freeze_right_with_ellipsis_and_scrolling
tmux . send_keys "{ seq 6; ruby -e 'print \" g \" *1000, \"\\ n \" '; seq 8 100; } | #{ FZF } --ellipsis='777' --freeze-right 1 --scroll-off 0 --bind a:offset-up" , :Enter
tmux . until { | lines | assert_equal ' 100/100' , lines [- 2 ] }
tmux . send_keys ( * Array . new ( 6 ) { :a })
tmux . until do | lines |
assert_match ( /> 777g+$/ , lines [- 3 ] )
2026-02-01 11:08:11 +09:00
assert_equal ( 1 , lines . count { | l | l . end_with? ( 'g' ) })
2025-11-20 01:09:36 +01:00
end
end
2025-01-25 19:57:40 +09:00
def test_backward_eof
tmux . send_keys "echo foo | #{ FZF } --bind 'backward-eof:reload(seq 100)'" , :Enter
tmux . until { | lines | lines . item_count == 1 && lines . match_count == 1 }
tmux . send_keys 'x'
tmux . until { | lines | lines . item_count == 1 && lines . match_count == 0 }
tmux . send_keys :BSpace
tmux . until { | lines | lines . item_count == 1 && lines . match_count == 1 }
tmux . send_keys :BSpace
tmux . until { | lines | lines . item_count == 100 && lines . match_count == 100 }
end
def test_change_prompt
tmux . send_keys " #{ FZF } --bind 'a:change-prompt(a> ),b:change-prompt:b> ' --query foo" , :Enter
tmux . until { | lines | assert_equal '> foo' , lines [- 1 ] }
tmux . send_keys 'a'
tmux . until { | lines | assert_equal 'a> foo' , lines [- 1 ] }
tmux . send_keys 'b'
tmux . until { | lines | assert_equal 'b> foo' , lines [- 1 ] }
end
def test_select_deselect
tmux . send_keys "seq 3 | #{ FZF } --multi --bind up:deselect+up,down:select+down" , :Enter
tmux . until { | lines | assert_equal 3 , lines . match_count }
tmux . send_keys :Tab
tmux . until { | lines | assert_equal 1 , lines . select_count }
tmux . send_keys :Up
tmux . until { | lines | assert_equal 0 , lines . select_count }
tmux . send_keys :Down , :Down
tmux . until { | lines | assert_equal 2 , lines . select_count }
tmux . send_keys :Tab
tmux . until { | lines | assert_equal 1 , lines . select_count }
tmux . send_keys :Down , :Down
tmux . until { | lines | assert_equal 2 , lines . select_count }
tmux . send_keys :Up
tmux . until { | lines | assert_equal 1 , lines . select_count }
tmux . send_keys :Down
tmux . until { | lines | assert_equal 1 , lines . select_count }
tmux . send_keys :Down
tmux . until { | lines | assert_equal 2 , lines . select_count }
end
2025-02-01 11:12:59 +09:00
def test_unbind_rebind_toggle_bind
tmux . send_keys "seq 100 | #{ FZF } --bind 'c:clear-query,d:unbind(c,d),e:rebind(c,d),f:toggle-bind(c)'" , :Enter
2025-01-27 02:08:52 +09:00
tmux . until { | lines | assert_equal 100 , lines . match_count }
2025-01-25 19:57:40 +09:00
tmux . send_keys 'ab'
tmux . until { | lines | assert_equal '> ab' , lines [- 1 ] }
tmux . send_keys 'c'
tmux . until { | lines | assert_equal '>' , lines [- 1 ] }
tmux . send_keys 'dabcd'
tmux . until { | lines | assert_equal '> abcd' , lines [- 1 ] }
tmux . send_keys 'ecabddc'
tmux . until { | lines | assert_equal '> abdc' , lines [- 1 ] }
2025-02-01 11:12:59 +09:00
tmux . send_keys 'fcabfc'
tmux . until { | lines | assert_equal '> abc' , lines [- 1 ] }
tmux . send_keys 'fc'
tmux . until { | lines | assert_equal '>' , lines [- 1 ] }
2025-01-25 19:57:40 +09:00
end
def test_scroll_off
tmux . send_keys "seq 1000 | #{ FZF } --scroll-off=3 --bind l:last" , :Enter
2025-01-27 02:08:52 +09:00
tmux . until { | lines | assert_equal 1000 , lines . match_count }
2025-01-25 19:57:40 +09:00
height = tmux . until { | lines | lines } . first . to_i
tmux . send_keys :PgUp
tmux . until do | lines |
assert_equal height + 3 , lines . first . to_i
assert_equal "> #{ height } " , lines [ 3 ]. strip
end
tmux . send_keys :Up
tmux . until { | lines | assert_equal "> #{ height + 1 } " , lines [ 3 ]. strip }
tmux . send_keys 'l'
tmux . until { | lines | assert_equal '> 1000' , lines . first . strip }
tmux . send_keys :PgDn
tmux . until { | lines | assert_equal "> #{ 1000 - height + 1 } " , lines . reverse [ 5 ]. strip }
tmux . send_keys :Down
tmux . until { | lines | assert_equal "> #{ 1000 - height } " , lines . reverse [ 5 ]. strip }
end
def test_scroll_off_large
tmux . send_keys "seq 1000 | #{ FZF } --scroll-off=9999" , :Enter
2025-01-27 02:08:52 +09:00
tmux . until { | lines | assert_equal 1000 , lines . match_count }
2025-01-25 19:57:40 +09:00
height = tmux . until { | lines | lines } . first . to_i
tmux . send_keys :PgUp
tmux . until { | lines | assert_equal "> #{ height } " , lines [ height / 2 ]. strip }
tmux . send_keys :Up
tmux . until { | lines | assert_equal "> #{ height + 1 } " , lines [ height / 2 ]. strip }
tmux . send_keys :Up
tmux . until { | lines | assert_equal "> #{ height + 2 } " , lines [ height / 2 ]. strip }
tmux . send_keys :Down
tmux . until { | lines | assert_equal "> #{ height + 1 } " , lines [ height / 2 ]. strip }
end
def test_ellipsis
tmux . send_keys 'seq 1000 | tr "\n" , | fzf --ellipsis=SNIPSNIP -e -q500' , :Enter
tmux . until { | lines | assert_equal 1 , lines . match_count }
tmux . until { | lines | assert_match ( /^> SNIPSNIP.*SNIPSNIP$/ , lines [- 3 ] ) }
end
def test_start_event
tmux . send_keys 'seq 100 | fzf --multi --sync --preview-window hidden:border-none --bind "start:select-all+last+preview(echo welcome)"' , :Enter
tmux . until do | lines |
assert_match ( />100.*welcome/ , lines [ 0 ] )
assert_includes ( lines [- 2 ] , '100/100 (100)' )
end
end
def test_focus_event
tmux . send_keys 'seq 100 | fzf --bind "focus:transform-prompt(echo [[{}]]),?:unbind(focus)"' , :Enter
tmux . until { | lines | assert_includes ( lines [- 1 ] , '[[1]]' ) }
tmux . send_keys :Up
tmux . until { | lines | assert_includes ( lines [- 1 ] , '[[2]]' ) }
tmux . send_keys :X
tmux . until { | lines | assert_includes ( lines [- 1 ] , '[[]]' ) }
tmux . send_keys :BSpace
tmux . until { | lines | assert_includes ( lines [- 1 ] , '[[1]]' ) }
tmux . send_keys :X
tmux . until { | lines | assert_includes ( lines [- 1 ] , '[[]]' ) }
tmux . send_keys '?'
tmux . send_keys :BSpace
tmux . until { | lines | assert_equal 100 , lines . match_count }
tmux . until { | lines | refute_includes ( lines [- 1 ] , '[[1]]' ) }
end
2026-10-10 20:59:13 +09:00
def assert_focus_log ( expected )
wait do
assert_path_exists tempname
assert_equal expected , File . readlines ( tempname , chomp : true )
end
end
def test_focus_event_after_reload
# Without --id-nth, the item at the same index after reload is a new item
tmux . send_keys %(printf 'a\\nb\\n' | #{ FZF } --bind 'focus:execute-silent(echo {} >> #{ tempname } )' --bind 'ctrl-r:reload(printf "c\\nd\\n")') , :Enter
tmux . until { | lines | assert_includes lines , '> a' }
assert_focus_log ( %w[a] )
tmux . send_keys 'C-r'
tmux . until { | lines | assert_includes lines , '> c' }
assert_focus_log ( %w[a c] )
end
def test_focus_event_after_reload_id_nth_same_key
# Same --id-nth key after reload: no focus event
tmux . send_keys %(printf '1 a\\n2 b\\n' | #{ FZF } --track --id-nth 1 --bind 'focus:execute-silent(echo {} >> #{ tempname } )' --bind 'ctrl-r:reload(printf "0 x\\n1 aa\\n2 bb\\n")') , :Enter
tmux . until { | lines | assert_includes lines , '> 1 a' }
assert_focus_log ( [ '1 a' ] )
tmux . send_keys 'C-r'
tmux . until { | lines | assert_includes lines , '> 1 aa' }
tmux . send_keys :Up
tmux . until { | lines | assert_includes lines , '> 2 bb' }
assert_focus_log ( [ '1 a' , '2 bb' ] )
end
def test_focus_event_after_reload_id_nth_key_not_found
tmux . send_keys %(printf '1 a\\n2 b\\n' | #{ FZF } --track --id-nth 1 --bind 'focus:execute-silent(echo {} >> #{ tempname } )' --bind 'ctrl-r:reload(printf "3 c\\n4 d\\n")') , :Enter
tmux . until { | lines | assert_includes lines , '> 1 a' }
assert_focus_log ( [ '1 a' ] )
tmux . send_keys 'C-r'
tmux . until { | lines | assert_includes lines , '> 3 c' }
assert_focus_log ( [ '1 a' , '3 c' ] )
end
def test_focus_event_after_async_reload_id_nth
# No focus event for the items shown before the tracked item arrives.
# 'result' binding makes fzf redraw the list while blocked.
tmux . send_keys %(printf '1 a\\n2 b\\n' | #{ FZF } --track --id-nth 1 --bind 'result:change-footer(R)' --bind 'focus:execute-silent(echo {} >> #{ tempname } )' --bind 'ctrl-r:reload(printf "0 x\\n"; sleep 1; printf "1 aa\\n2 bb\\n")') , :Enter
tmux . until { | lines | assert_includes lines , '> 1 a' }
assert_focus_log ( [ '1 a' ] )
tmux . send_keys 'C-r'
tmux . until { | lines | assert_includes lines [- 2 ] , '+T*' }
tmux . until do | lines |
assert_includes lines , '> 1 aa'
refute_includes lines [- 2 ] , '+T*'
end
tmux . send_keys :Up
tmux . until { | lines | assert_includes lines , '> 2 bb' }
assert_focus_log ( [ '1 a' , '2 bb' ] )
end
2025-01-25 19:57:40 +09:00
def test_result_event
tmux . send_keys '(echo 0; seq 10) | fzf --bind "result:pos(2)"' , :Enter
2025-01-27 02:08:52 +09:00
tmux . until { | lines | assert_equal 11 , lines . match_count }
2025-01-25 19:57:40 +09:00
tmux . until { | lines | assert_includes lines , '> 1' }
tmux . send_keys '9'
tmux . until { | lines | assert_includes lines , '> 9' }
tmux . send_keys :BSpace
tmux . until { | lines | assert_includes lines , '> 1' }
end
2026-06-14 19:37:22 +09:00
def test_result_final_event
tmux . send_keys %[(seq 100; sleep 1; seq 100) | #{ FZF } \\
--query 1 \\
--bind 'result:transform-header(echo "R=$FZF_MATCH_COUNT")' \\
--bind 'result-final:transform-footer(echo "F=$FZF_MATCH_COUNT")'] , :Enter
tmux . until { | lines | assert lines . any_include? ( 'R=20' ) }
tmux . until { | lines | refute lines . any_include? ( 'F=20' ) }
tmux . until { | lines | assert lines . any_include? ( 'R=40' ) }
tmux . until { | lines | assert lines . any_include? ( 'F=40' ) }
end
2026-05-17 23:54:59 +09:00
def test_every_event
tmux . send_keys %(seq 100 | fzf --bind 'every(0.2):transform-prompt(cat #{ tempname } )') , :Enter
tmux . until { | lines | assert_equal 100 , lines . match_count }
# Trigger external state changes; the every() tick should pick them up.
writelines ( [ 'AAA>' ] )
tmux . until { | lines | assert_includes lines [- 1 ] , 'AAA>' }
writelines ( [ 'BBB>' ] )
tmux . until { | lines | assert_includes lines [- 1 ] , 'BBB>' }
end
def test_every_event_multiple_independent_timers
# Two timers with different durations should fire independently.
fast = tempname + '.fast'
slow = tempname + '.slow'
FileUtils . rm_f ( fast )
FileUtils . rm_f ( slow )
tmux . send_keys %(seq 100 | fzf \\
--bind 'every(0.1):execute-silent(printf . >> #{ fast } )' \\
--bind 'every(0.5):execute-silent(printf . >> #{ slow } )') , :Enter
tmux . until { | lines | assert_equal 100 , lines . match_count }
2026-05-19 18:54:15 +09:00
sleep ( 1 . 2 )
2026-05-17 23:54:59 +09:00
a = File . exist? ( fast ) ? File . size ( fast ) : 0
b = File . exist? ( slow ) ? File . size ( slow ) : 0
# Sanity: faster timer fired more times.
2026-05-19 18:54:15 +09:00
assert_operator a , :> , b , "fast timer should fire more ( #{ a } vs #{ b } )"
2026-05-17 23:54:59 +09:00
# Sanity: slow timer fired at least once.
2026-05-19 18:54:15 +09:00
assert_operator b , :>= , 1 , "slow timer should have fired at least once ( #{ b } )"
2026-05-17 23:54:59 +09:00
ensure
FileUtils . rm_f ( fast )
FileUtils . rm_f ( slow )
end
def test_every_event_unbind
tmux . send_keys %(seq 100 | fzf --bind 'every(0.1):transform-header(date +%S.%N)' --bind 'space:unbind(every(0.1))+change-header(STOPPED)') , :Enter
tmux . until { | lines | assert_equal 100 , lines . match_count }
# Header should be ticking
tmux . until { | lines | assert_match ( /^ \d{2}\.\d+/ , lines [- 3 ] ) }
tmux . send_keys :Space
tmux . until { | lines | assert_includes lines [- 3 ] , 'STOPPED' }
2026-05-19 18:54:15 +09:00
sleep ( 0 . 4 )
2026-05-17 23:54:59 +09:00
# Header must stay STOPPED after the unbind
assert_includes tmux . capture [- 3 ] , 'STOPPED'
end
def test_fzf_idle_time_env
# FZF_IDLE_TIME + FZF_IDLE_TIME_MS combined with every() implement idle-based behavior.
tmux . send_keys %(seq 100 | fzf --bind 'every(0.2):transform-header(echo "s=$FZF_IDLE_TIME ms_ok=$((FZF_IDLE_TIME_MS / 1000 == FZF_IDLE_TIME))")') , :Enter
tmux . until { | lines | assert_equal 100 , lines . match_count }
# Idle counter advances without any input; ms/1000 stays consistent with seconds.
tmux . until { | lines | assert_includes lines [- 3 ] , 's=1 ms_ok=1' }
tmux . until { | lines | assert_includes lines [- 3 ] , 's=2 ms_ok=1' }
# Any keystroke resets the counter
tmux . send_keys 'x'
tmux . until { | lines | assert_includes lines [- 3 ] , 's=0 ms_ok=1' }
tmux . send_keys :BSpace
# And it advances again afterwards
tmux . until { | lines | assert_includes lines [- 3 ] , 's=1 ms_ok=1' }
end
def test_every_event_rejects_invalid_arg
%w[every(0) every(-1) every(abc) every()] . each do | spec |
tmux . send_keys %(seq 1 | fzf --bind ' #{ spec } :abort' 2>&1; echo done=$?) , :Enter
tmux . until { | lines | assert ( lines . any? { | l | l . include? ( 'done=2' ) }) }
tmux . send_keys 'clear' , :Enter
end
end
def test_fzf_key_ignores_synthetic_events
tmux . send_keys %(seq 100 | fzf --bind 'every(0.2):transform-prompt(echo "[$FZF_KEY]> ")') , :Enter
tmux . until { | lines | assert_equal 100 , lines . match_count }
# No user input yet: prompt should show empty FZF_KEY
tmux . until { | lines | assert_includes lines [- 1 ] , '[]>' }
tmux . send_keys 'x'
tmux . until { | lines | assert_includes lines [- 1 ] , '[x]>' }
# every() ticks shouldn't overwrite FZF_KEY
2026-05-19 18:54:15 +09:00
sleep ( 1 )
2026-05-17 23:54:59 +09:00
assert_includes tmux . capture [- 1 ] , '[x]>'
end
2025-01-25 19:57:40 +09:00
def test_labels_center
tmux . send_keys 'echo x | fzf --border --border-label foobar --preview : --preview-label barfoo --bind "space:change-border-label(foobarfoo)+change-preview-label(barfoobar),enter:transform-border-label(echo foo{}foo)+transform-preview-label(echo bar{}bar)"' , :Enter
tmux . until do
assert_includes ( it [ 0 ] , '─foobar─' )
assert_includes ( it [ 1 ] , '─barfoo─' )
end
tmux . send_keys :space
tmux . until do
assert_includes ( it [ 0 ] , '─foobarfoo─' )
assert_includes ( it [ 1 ] , '─barfoobar─' )
end
tmux . send_keys :Enter
tmux . until do
assert_includes ( it [ 0 ] , '─fooxfoo─' )
assert_includes ( it [ 1 ] , '─barxbar─' )
end
end
def test_labels_left
tmux . send_keys ': | fzf --border rounded --preview-window border-rounded --border-label foobar --border-label-pos 2 --preview : --preview-label barfoo --preview-label-pos 2' , :Enter
tmux . until do
assert_includes ( it [ 0 ] , '╭foobar─' )
assert_includes ( it [ 1 ] , '╭barfoo─' )
end
end
def test_labels_right
tmux . send_keys ': | fzf --border rounded --preview-window border-rounded --border-label foobar --border-label-pos -2 --preview : --preview-label barfoo --preview-label-pos -2' , :Enter
tmux . until do
assert_includes ( it [ 0 ] , '─foobar╮' )
assert_includes ( it [ 1 ] , '─barfoo╮' )
end
end
def test_labels_bottom
tmux . send_keys ': | fzf --border rounded --preview-window border-rounded --border-label foobar --border-label-pos 2:bottom --preview : --preview-label barfoo --preview-label-pos -2:bottom' , :Enter
tmux . until do
assert_includes ( it [- 1 ] , '╰foobar─' )
assert_includes ( it [- 2 ] , '─barfoo╯' )
end
end
def test_labels_variables
tmux . send_keys ': | fzf --border --border-label foobar --preview "echo \$FZF_BORDER_LABEL // \$FZF_PREVIEW_LABEL" --preview-label barfoo --bind "space:change-border-label(barbaz)+change-preview-label(bazbar)+refresh-preview,enter:transform-border-label(echo 123)+transform-preview-label(echo 456)+refresh-preview"' , :Enter
tmux . until do
assert_includes ( it [ 0 ] , '─foobar─' )
assert_includes ( it [ 1 ] , '─barfoo─' )
assert_includes ( it [ 2 ] , ' foobar // barfoo ' )
end
tmux . send_keys :Space
tmux . until do
assert_includes ( it [ 0 ] , '─barbaz─' )
assert_includes ( it [ 1 ] , '─bazbar─' )
assert_includes ( it [ 2 ] , ' barbaz // bazbar ' )
end
tmux . send_keys :Enter
tmux . until do
assert_includes ( it [ 0 ] , '─123─' )
assert_includes ( it [ 1 ] , '─456─' )
assert_includes ( it [ 2 ] , ' 123 // 456 ' )
end
end
def test_info_separator_unicode
tmux . send_keys 'seq 100 | fzf -q55' , :Enter
tmux . until { assert_includes ( it [- 2 ] , ' 1/100 ─' ) }
end
def test_info_separator_no_unicode
tmux . send_keys 'seq 100 | fzf -q55 --no-unicode' , :Enter
tmux . until { assert_includes ( it [- 2 ] , ' 1/100 -' ) }
end
def test_info_separator_repeat
tmux . send_keys 'seq 100 | fzf -q55 --separator _-' , :Enter
tmux . until { assert_includes ( it [- 2 ] , ' 1/100 _-_-' ) }
end
def test_info_separator_ansi_colors_and_tabs
tmux . send_keys "seq 100 | fzf -q55 --tabstop 4 --separator $' \\ x1b[33ma \\ tb'" , :Enter
tmux . until { assert_includes ( it [- 2 ] , ' 1/100 a ba ba' ) }
end
def test_info_no_separator
tmux . send_keys 'seq 100 | fzf -q55 --no-separator' , :Enter
tmux . until { assert_operator ( it [- 2 ] , :== , ' 1/100' ) }
end
def test_info_right
tmux . send_keys " #{ FZF } --info=right --separator x --bind 'start:reload:seq 100; sleep 10'" , :Enter
tmux . until { assert_match ( %r{xxx [⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100} , it [- 2 ] ) }
end
def test_info_inline_right
tmux . send_keys " #{ FZF } --info=inline-right --bind 'start:reload:seq 100; sleep 10'" , :Enter
tmux . until { assert_match ( %r{[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100} , it [- 1 ] ) }
end
def test_info_inline_right_clearance
tmux . send_keys "seq 100000 | #{ FZF } --info inline-right" , :Enter
tmux . until { assert_match ( %r{100000/100000} , it [- 1 ] ) }
tmux . send_keys 'x'
tmux . until { assert_match ( %r{ 0/100000} , it [- 1 ] ) }
end
def test_info_command
tmux . send_keys ( %(seq 10000 | #{ FZF } --separator x --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"') , :Enter )
tmux . until { assert_match ( %r{^ --1/10000/10000-- xx} , it [- 2 ] ) }
tmux . send_keys :Up
tmux . until { assert_match ( %r{^ --2/10000/10000-- xx} , it [- 2 ] ) }
end
def test_info_command_inline
tmux . send_keys ( %(seq 10000 | #{ FZF } --separator x --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"' --info inline:xx) , :Enter )
tmux . until { assert_match ( %r{^> xx--1/10000/10000-- xx} , it [- 1 ] ) }
end
def test_info_command_right
tmux . send_keys ( %(seq 10000 | #{ FZF } --separator x --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"' --info right) , :Enter )
tmux . until { assert_match ( %r{xx --1/10000/10000-- *$} , it [- 2 ] ) }
end
def test_info_command_inline_right
tmux . send_keys ( %(seq 10000 | #{ FZF } --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"' --info inline-right) , :Enter )
tmux . until { assert_match ( %r{ --1/10000/10000-- *$} , it [- 1 ] ) }
end
2025-08-08 18:51:24 +09:00
def test_info_command_inline_right_no_ansi
tmux . send_keys ( %(seq 10000 | #{ FZF } --info-command 'echo -e "--$FZF_POS/$FZF_INFO--"' --info inline-right) , :Enter )
tmux . until { assert_match ( %r{ --1/10000/10000-- *$} , it [- 1 ] ) }
end
2025-01-25 19:57:40 +09:00
def test_info_command_and_focus
tmux . send_keys ( %(seq 100 | #{ FZF } --separator x --info-command 'echo $FZF_POS' --bind focus:clear-query) , :Enter )
tmux . until { assert_match ( /^ 1 xx/ , it [- 2 ] ) }
tmux . send_keys :Up
tmux . until { assert_match ( /^ 2 xx/ , it [- 2 ] ) }
end
def test_prev_next_selected
tmux . send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected' , :Enter
2025-01-27 02:08:52 +09:00
tmux . until { | lines | assert_equal 10 , lines . match_count }
2025-01-25 19:57:40 +09:00
tmux . send_keys :BTab , :BTab , :Up , :BTab
tmux . until { | lines | assert_equal 3 , lines . select_count }
tmux . send_keys 'C-n'
tmux . until { | lines | assert_includes lines , '>>4' }
tmux . send_keys 'C-n'
tmux . until { | lines | assert_includes lines , '>>2' }
tmux . send_keys 'C-n'
tmux . until { | lines | assert_includes lines , '>>1' }
tmux . send_keys 'C-n'
tmux . until { | lines | assert_includes lines , '>>4' }
tmux . send_keys 'C-p'
tmux . until { | lines | assert_includes lines , '>>1' }
tmux . send_keys 'C-p'
tmux . until { | lines | assert_includes lines , '>>2' }
end
def test_track
tmux . send_keys "seq 1000 | #{ FZF } --query 555 --track --bind t:toggle-track" , :Enter
tmux . until do | lines |
assert_equal 1 , lines . match_count
assert_includes lines , '> 555'
end
tmux . send_keys :BSpace
index = tmux . until do | lines |
assert_equal 28 , lines . match_count
assert_includes lines , '> 555'
end . index ( '> 555' )
tmux . send_keys :BSpace
tmux . until do | lines |
assert_equal 271 , lines . match_count
assert_equal '> 555' , lines [ index ]
end
tmux . send_keys :BSpace
tmux . until do | lines |
assert_equal 1000 , lines . match_count
assert_equal '> 555' , lines [ index ]
end
tmux . send_keys '555'
tmux . until do | lines |
assert_equal 1 , lines . match_count
assert_includes lines , '> 555'
assert_includes lines [- 2 ] , '+T'
end
tmux . send_keys 't'
tmux . until do | lines |
refute_includes lines [- 2 ] , '+T'
end
tmux . send_keys :BSpace
tmux . until do | lines |
assert_equal 28 , lines . match_count
assert_includes lines , '> 55'
end
tmux . send_keys :BSpace
tmux . until do | lines |
assert_equal 271 , lines . match_count
assert_includes lines , '> 5'
end
tmux . send_keys 't'
tmux . until do | lines |
assert_includes lines [- 2 ] , '+T'
end
tmux . send_keys :BSpace
tmux . until do | lines |
assert_equal 1000 , lines . match_count
assert_includes lines , '> 5'
end
end
def test_track_action
2026-01-26 11:16:59 +09:00
tmux . send_keys "seq 1000 | #{ FZF } --pointer x --query 555 --bind t:track,T:up+track" , :Enter
2025-01-25 19:57:40 +09:00
tmux . until do | lines |
assert_equal 1 , lines . match_count
2026-01-26 11:16:59 +09:00
assert_includes lines , 'x 555'
2025-01-25 19:57:40 +09:00
assert_includes lines , '> 555'
end
tmux . send_keys :BSpace
tmux . until do | lines |
assert_equal 28 , lines . match_count
2026-01-26 11:16:59 +09:00
assert_includes lines , 'x 55'
2025-01-25 19:57:40 +09:00
assert_includes lines , '> 55'
end
tmux . send_keys :t
tmux . until do | lines |
assert_includes lines [- 2 ] , '+t'
end
tmux . send_keys :BSpace
tmux . until do | lines |
assert_equal 271 , lines . match_count
2026-01-26 11:16:59 +09:00
assert_includes lines , 'x 55'
assert_includes lines , '> 5'
2025-01-25 19:57:40 +09:00
end
# Automatically disabled when the tracking item is no longer visible
tmux . send_keys '4'
tmux . until do | lines |
assert_equal 28 , lines . match_count
refute_includes lines [- 2 ] , '+t'
end
tmux . send_keys :BSpace
tmux . until do | lines |
assert_equal 271 , lines . match_count
2026-01-26 11:16:59 +09:00
assert_includes lines , 'x 52'
2025-01-25 19:57:40 +09:00
assert_includes lines , '> 5'
end
tmux . send_keys :t
tmux . until do | lines |
assert_includes lines [- 2 ] , '+t'
end
2026-01-26 11:16:59 +09:00
# Automatically disabled when the focus has moved
2025-01-25 19:57:40 +09:00
tmux . send_keys :Up
tmux . until do | lines |
2026-01-26 11:16:59 +09:00
assert_includes lines , 'x 53'
2025-01-25 19:57:40 +09:00
refute_includes lines [- 2 ] , '+t'
end
2026-01-26 11:16:59 +09:00
# Should work even when combined with a focus moving actions
tmux . send_keys 'T'
tmux . until do | lines |
assert_includes lines , 'x 54'
assert_includes lines [- 2 ] , '+t'
end
tmux . send_keys 'T'
tmux . until do | lines |
assert_includes lines , 'x 55'
assert_includes lines [- 2 ] , '+t'
end
2025-01-25 19:57:40 +09:00
end
2026-03-13 01:18:43 +09:00
def test_track_nth_reload_whole_line
2026-03-14 21:49:16 +09:00
# --track --id-nth .. should track by entire line across reloads
tmux . send_keys "seq 1000 | #{ FZF } --track --id-nth .. --bind 'ctrl-r:reload:seq 1000 | sort -R'" , :Enter
2026-03-13 01:18:43 +09:00
tmux . until { | lines | assert_equal 1000 , lines . match_count }
# Move to item 555
tmux . send_keys '555'
tmux . until do | lines |
assert_equal 1 , lines . match_count
assert_includes lines , '> 555'
end
tmux . send_keys :BSpace , :BSpace , :BSpace
2026-04-19 19:19:43 +09:00
# Reload with shuffled order - cursor should track "555"
2026-03-13 01:18:43 +09:00
tmux . send_keys 'C-r'
tmux . until do | lines |
assert_equal 1000 , lines . match_count
assert_includes lines , '> 555'
assert_includes lines [- 2 ] , '+T'
refute_includes lines [- 2 ] , '+T*'
end
end
def test_track_nth_reload_field
2026-03-14 21:49:16 +09:00
# --track --id-nth 1 should track by first field across reloads
tmux . send_keys "printf '1 apple \\ n2 banana \\ n3 cherry \\ n' | #{ FZF } --track --id-nth 1 --bind 'ctrl-r:reload:printf \" 1 apricot \\ n2 blueberry \\ n3 cranberry \\ n \" '" , :Enter
2026-03-13 01:18:43 +09:00
tmux . until do | lines |
assert_equal 3 , lines . match_count
assert_includes lines , '> 1 apple'
end
# Move up to "2 banana"
tmux . send_keys :Up
tmux . until { | lines | assert_includes lines , '> 2 banana' }
2026-04-19 19:19:43 +09:00
# Reload - the second field changes, but first field "2" stays
2026-03-13 01:18:43 +09:00
tmux . send_keys 'C-r'
tmux . until do | lines |
assert_equal 3 , lines . match_count
assert_includes lines , '> 2 blueberry'
end
end
def test_track_nth_reload_no_match
# When tracked item is not found after reload, cursor stays at current position
2026-03-14 21:49:16 +09:00
tmux . send_keys "printf 'alpha \\ nbeta \\ ngamma \\ n' | #{ FZF } --track --id-nth .. --bind 'ctrl-r:reload:printf \" delta \\ nepsilon \\ nzeta \\ n \" '" , :Enter
2026-03-13 01:18:43 +09:00
tmux . until { | lines | assert_equal 3 , lines . match_count }
tmux . send_keys :Up
tmux . until { | lines | assert_includes lines , '> beta' }
2026-04-19 19:19:43 +09:00
# Reload with completely different items - no match for "beta"
2026-03-13 01:18:43 +09:00
# Cursor stays at the same position (second item)
tmux . send_keys 'C-r'
tmux . until do | lines |
assert_equal 3 , lines . match_count
assert_includes lines , '> epsilon'
refute_includes lines [- 2 ] , '+T*'
end
end
def test_track_nth_blocked_indicator
# +T* should appear during reload and disappear when match is found
2026-03-14 21:49:16 +09:00
tmux . send_keys "seq 100 | #{ FZF } --track --id-nth .. --bind 'ctrl-r:reload:sleep 1; seq 100 | sort -R'" , :Enter
2026-03-13 01:18:43 +09:00
tmux . until do | lines |
assert_equal 100 , lines . match_count
assert_includes lines [- 2 ] , '+T'
end
2026-04-19 19:19:43 +09:00
# Trigger slow reload - should show +T* while blocked
2026-03-13 01:18:43 +09:00
tmux . send_keys 'C-r'
tmux . until { | lines | assert_includes lines [- 2 ] , '+T*' }
# After reload completes, +T* should clear back to +T
tmux . until do | lines |
assert_equal 100 , lines . match_count
assert_includes lines [- 2 ] , '+T'
refute_includes lines [- 2 ] , '+T*'
end
end
def test_track_nth_abort_unblocks
# Escape during track-blocked state should unblock, not quit
2026-03-14 21:49:16 +09:00
tmux . send_keys "seq 100 | #{ FZF } --track --id-nth .. --bind 'ctrl-r:reload:sleep 3; seq 100'" , :Enter
2026-03-13 01:18:43 +09:00
tmux . until do | lines |
assert_equal 100 , lines . match_count
assert_includes lines [- 2 ] , '+T'
end
# Trigger slow reload
tmux . send_keys 'C-r'
tmux . until { | lines | assert_includes lines [- 2 ] , '+T*' }
# Escape should unblock, not quit fzf
tmux . send_keys :Escape
tmux . until do | lines |
assert_includes lines [- 2 ] , '+T'
refute_includes lines [- 2 ] , '+T*'
end
end
def test_track_nth_reload_async_unblocks_early
# With async reload, +T* should clear as soon as the match streams in,
# even while loading is still in progress.
# sleep 1 first so +T* is observable, then the match arrives, then more items after a delay.
2026-03-14 21:49:16 +09:00
tmux . send_keys "seq 5 | #{ FZF } --track --id-nth .. --bind 'ctrl-r:reload:sleep 1; echo 1; sleep 2; seq 2 10'" , :Enter
2026-03-13 01:18:43 +09:00
tmux . until do | lines |
assert_equal 5 , lines . match_count
assert_includes lines , '> 1'
end
2026-04-19 19:19:43 +09:00
# Trigger reload - blocked during initial sleep
2026-03-13 01:18:43 +09:00
tmux . send_keys 'C-r'
tmux . until { | lines | assert_includes lines [- 2 ] , '+T*' }
# Match "1" arrives, unblocks before the remaining items load
tmux . until do | lines |
assert_equal 1 , lines . match_count
assert_includes lines , '> 1'
assert_includes lines [- 2 ] , '+T'
refute_includes lines [- 2 ] , '+T*'
end
end
def test_track_nth_reload_sync_blocks_until_complete
# With reload-sync, +T* should stay until the entire stream is complete,
# even though the match arrives early in the stream.
2026-03-14 21:49:16 +09:00
tmux . send_keys "seq 5 | #{ FZF } --track --id-nth .. --bind 'ctrl-r:reload-sync:sleep 1; echo 1; sleep 2; seq 2 10'" , :Enter
2026-03-13 01:18:43 +09:00
tmux . until do | lines |
assert_equal 5 , lines . match_count
assert_includes lines , '> 1'
end
2026-04-19 19:19:43 +09:00
# Trigger reload-sync - every observable state must be either:
2026-03-13 01:18:43 +09:00
# 1. +T* (still blocked), or
# 2. final state (count=10, +T without *)
# Any other combination (e.g. unblocked while count < 10) is a bug.
tmux . send_keys 'C-r'
tmux . until do | lines |
info = lines [- 2 ]
blocked = info &. include? ( '+T*' )
unless blocked
raise "Unblocked before stream complete (count: #{ lines . match_count } )" if lines . match_count != 10
assert_includes info , '+T'
assert_includes lines , '> 1'
end
! blocked
end
end
def test_track_nth_toggle_track_unblocks
# toggle-track during track-blocked state should unblock and disable tracking
2026-03-14 21:49:16 +09:00
tmux . send_keys "seq 100 | #{ FZF } --track --id-nth .. --bind 'ctrl-r:reload:sleep 5; seq 100' --bind 'ctrl-t:toggle-track'" , :Enter
2026-03-13 01:18:43 +09:00
tmux . until do | lines |
assert_equal 100 , lines . match_count
assert_includes lines [- 2 ] , '+T'
end
# Trigger slow reload
tmux . send_keys 'C-r'
tmux . until { | lines | assert_includes lines [- 2 ] , '+T*' }
# toggle-track should unblock and disable tracking before reload completes
tmux . send_keys 'C-t'
tmux . until ( timeout : 3 ) do | lines |
refute_includes lines [- 2 ] , '+T'
end
end
def test_track_nth_reload_async_no_match
# With async reload, when tracked item is not found, cursor stays at
# current position after stream completes
2026-03-14 21:49:16 +09:00
tmux . send_keys "printf 'alpha \\ nbeta \\ ngamma \\ n' | #{ FZF } --track --id-nth .. --bind 'ctrl-r:reload:sleep 1; printf \" delta \\ nepsilon \\ nzeta \\ n \" '" , :Enter
2026-03-13 01:18:43 +09:00
tmux . until { | lines | assert_equal 3 , lines . match_count }
tmux . send_keys :Up
tmux . until { | lines | assert_includes lines , '> beta' }
2026-04-19 19:19:43 +09:00
# Reload with completely different items - no match for "beta"
2026-03-13 01:18:43 +09:00
tmux . send_keys 'C-r'
tmux . until { | lines | assert_includes lines [- 2 ] , '+T*' }
# After stream completes, unblocks with cursor at same position (second item)
tmux . until do | lines |
assert_equal 3 , lines . match_count
assert_includes lines , '> epsilon'
refute_includes lines [- 2 ] , '+T*'
end
end
2026-03-14 21:49:16 +09:00
def test_track_action_with_id_nth
# track-current with --id-nth should track by specified field
tmux . send_keys "printf '1 apple \\ n2 banana \\ n3 cherry \\ n' | #{ FZF } --id-nth 1 --bind 'ctrl-t:track-current,ctrl-r:reload:printf \" 1 apricot \\ n2 blueberry \\ n3 cranberry \\ n \" '" , :Enter
2026-03-13 01:18:43 +09:00
tmux . until { | lines | assert_equal 3 , lines . match_count }
2026-03-14 21:49:16 +09:00
# Move to "2 banana" and activate tracking
2026-03-13 01:18:43 +09:00
tmux . send_keys :Up
tmux . until { | lines | assert_includes lines , '> 2 banana' }
tmux . send_keys 'C-t'
tmux . until { | lines | assert_includes lines [- 2 ] , '+t' }
2026-04-19 19:19:43 +09:00
# Reload - should track by field "2"
2026-03-13 01:18:43 +09:00
tmux . send_keys 'C-r'
tmux . until do | lines |
assert_equal 3 , lines . match_count
assert_includes lines , '> 2 blueberry'
end
end
2026-03-14 21:49:16 +09:00
def test_id_nth_preserve_multi_selection
# --id-nth with --multi should preserve selections across reload-sync
File . write ( tempname , "1 apricot \n 2 blueberry \n 3 cranberry \n " )
tmux . send_keys "printf '1 apple \\ n2 banana \\ n3 cherry \\ n' | #{ fzf ( "--multi --id-nth 1 --bind 'ctrl-r:reload-sync:cat #{ tempname } '" ) } " , :Enter
tmux . until { | lines | assert_equal 3 , lines . match_count }
# Select first item (1 apple) and third item (3 cherry)
tmux . send_keys :Tab
tmux . send_keys :Up , :Up , :Tab
tmux . until { | lines | assert_includes lines [- 2 ] , '(2)' }
2026-04-19 19:19:43 +09:00
# Reload - selections should be preserved by id-nth key
2026-03-14 21:49:16 +09:00
tmux . send_keys 'C-r'
tmux . until do | lines |
assert_equal 3 , lines . match_count
assert_includes lines [- 2 ] , '(2)'
assert ( lines . any? { | l | l . include? ( 'apricot' ) })
end
# Accept and verify the correct items were preserved
tmux . send_keys :Enter
assert_equal [ '1 apricot' , '3 cranberry' ] , fzf_output_lines
end
2025-01-25 19:57:40 +09:00
def test_one_and_zero
tmux . send_keys "seq 10 | #{ FZF } --bind 'zero:preview(echo no match),one:preview(echo {} is the only match)'" , :Enter
tmux . send_keys '1'
tmux . until do | lines |
assert_equal 2 , lines . match_count
refute ( lines . any? { it . include? ( 'only match' ) })
refute ( lines . any? { it . include? ( 'no match' ) })
end
tmux . send_keys '0'
tmux . until do | lines |
assert_equal 1 , lines . match_count
assert ( lines . any? { it . include? ( 'only match' ) })
end
tmux . send_keys '0'
tmux . until do | lines |
assert_equal 0 , lines . match_count
assert ( lines . any? { it . include? ( 'no match' ) })
end
end
def test_height_range_with_exit_0
tmux . send_keys "seq 10 | #{ FZF } --height ~10% --exit-0" , :Enter
2025-01-27 02:08:52 +09:00
tmux . until { | lines | assert_equal 10 , lines . match_count }
2025-01-25 19:57:40 +09:00
tmux . send_keys :c
tmux . until { | lines | assert_equal 0 , lines . match_count }
end
def test_delete_with_modifiers
if ENV [ 'GITHUB_ACTION' ]
# Expected: "[3]"
# Actual: "[]3;5~"
skip ( 'CTRL-DELETE is not properly handled in GitHub Actions environment' )
end
tmux . send_keys "seq 100 | #{ FZF } --bind 'ctrl-delete:up+up,shift-delete:down,focus:transform-prompt:echo [{}]'" , :Enter
2025-01-27 02:08:52 +09:00
tmux . until { | lines | assert_equal 100 , lines . match_count }
2025-01-25 19:57:40 +09:00
tmux . send_keys 'C-Delete'
tmux . until { | lines | assert_equal '[3]' , lines [- 1 ] }
tmux . send_keys 'S-Delete'
tmux . until { | lines | assert_equal '[2]' , lines [- 1 ] }
end
def test_fzf_pos
tmux . send_keys "seq 100 | #{ FZF } --preview 'echo $FZF_POS / $FZF_MATCH_COUNT'" , :Enter
tmux . until { | lines | assert ( lines . any? { | line | line . include? ( '1 / 100' ) }) }
tmux . send_keys :Up
tmux . until { | lines | assert ( lines . any? { | line | line . include? ( '2 / 100' ) }) }
tmux . send_keys '99'
tmux . until { | lines | assert ( lines . any? { | line | line . include? ( '1 / 1' ) }) }
tmux . send_keys '99'
tmux . until { | lines | assert ( lines . any? { | line | line . include? ( '0 / 0' ) }) }
end
def test_change_nth
input = [
*[ '' ] * 1000 ,
'foo bar bar bar bar' ,
'foo foo bar bar bar' ,
'foo foo foo bar bar' ,
'foo foo foo foo bar' ,
*[ '' ] * 1000
]
writelines ( input )
nths = '1,2..4,-1,-3..,..2'
tmux . send_keys %( #{ FZF } -qfoo -n #{ nths } --bind 'space:change-nth(2|3|4|5|),result:transform-prompt:echo "[$FZF_NTH] "' < #{ tempname } ) , :Enter
tmux . until do | lines |
assert lines . any_include? ( "[ #{ nths } ] foo" )
assert_equal 4 , lines . match_count
end
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( '[2] foo' )
assert_equal 3 , lines . match_count
end
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( '[3] foo' )
assert_equal 2 , lines . match_count
end
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( '[4] foo' )
assert_equal 1 , lines . match_count
end
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( '[5] foo' )
assert_equal 0 , lines . match_count
end
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( "[ #{ nths } ] foo" )
assert_equal 4 , lines . match_count
end
end
2025-02-02 01:48:52 +09:00
2026-03-01 16:09:54 +09:00
def test_change_with_nth
input = [
'foo bar baz' ,
'aaa bbb ccc' ,
'xxx yyy zzz'
]
writelines ( input )
# Start with field 1 only, cycle through fields, verify $FZF_WITH_NTH via prompt
tmux . send_keys %( #{ FZF } --with-nth 1 --bind 'space:change-with-nth(2|3|1),result:transform-prompt:echo "[$FZF_WITH_NTH]> "' < #{ tempname } ) , :Enter
tmux . until do | lines |
assert_equal 3 , lines . item_count
assert lines . any_include? ( '[1]>' )
assert lines . any_include? ( 'foo' )
refute lines . any_include? ( 'bar' )
end
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( '[2]>' )
assert lines . any_include? ( 'bar' )
refute lines . any_include? ( 'foo' )
end
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( '[3]>' )
assert lines . any_include? ( 'baz' )
refute lines . any_include? ( 'bar' )
end
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( '[1]>' )
assert lines . any_include? ( 'foo' )
refute lines . any_include? ( 'bar' )
end
end
def test_change_with_nth_default
# Empty value restores the default --with-nth
tmux . send_keys %(echo -e 'a b c \n d e f' | #{ FZF } --with-nth 1 --bind 'space:change-with-nth(2|)') , :Enter
tmux . until do | lines |
assert_equal 2 , lines . item_count
assert lines . any_include? ( 'a' )
refute lines . any_include? ( 'b' )
end
# Switch to field 2
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( 'b' )
refute lines . any_include? ( 'a' )
end
# Empty restores default (field 1)
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( 'a' )
refute lines . any_include? ( 'b' )
end
end
2026-10-20 14:44:55 +09:00
def test_change_nth_next_value
tmux . send_keys %(echo 'a b c d' | #{ FZF } --nth 1 --bind 'space:change-nth(1|2|3|4),x:change-nth(3),result:transform-prompt:echo "[$FZF_NTH]> "') , :Enter
tmux . until { | lines | assert lines . any_include? ( '[1]>' ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( '[2]>' ) }
# Changed by another binding. The next value is the one after '3'.
tmux . send_keys :x
tmux . until { | lines | assert lines . any_include? ( '[3]>' ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( '[4]>' ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( '[1]>' ) }
end
def test_change_nth_all_fields_is_current
# '--nth ..' is stored as an empty list, which must match '..'
tmux . send_keys %(echo 'a b c' | #{ FZF } --nth .. --bind 'space:change-nth(..|1|2)' --bind 'result:transform-prompt:echo "[$FZF_NTH]> "') , :Enter
tmux . until { | lines | assert lines . any_include? ( '[]>' ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( '[1]>' ) }
end
def test_change_nth_repeated_value
tmux . send_keys %(echo 'a b c' | #{ FZF } --nth 1 --bind 'space:change-nth(1|2|1|3)' --bind 'result:transform-prompt:echo "[$FZF_NTH]> "') , :Enter
tmux . until { | lines | assert lines . any_include? ( '[1]>' ) }
%w[2 1 3 1 2] . each do | nth |
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( "[ #{ nth } ]>" ) }
end
end
def test_change_nth_skips_invalid_value
tmux . send_keys %(echo 'a b c' | #{ FZF } --nth 2 --bind 'space:change-nth(1|x|3)' --bind 'result:transform-prompt:echo "[$FZF_NTH]> "') , :Enter
tmux . until { | lines | assert lines . any_include? ( '[2]>' ) }
%w[1 3 1] . each do | nth |
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( "[ #{ nth } ]>" ) }
end
end
def test_change_nth_only_invalid_values_left
# 'x' is invalid and '3' is current, so nothing changes
tmux . send_keys %(echo 'a b c' | #{ FZF } --nth 1 --bind 'a:change-nth(3),space:change-nth(x|3)+transform-prompt:echo "S[$FZF_NTH]> "' --bind 'result:transform-prompt:echo "[$FZF_NTH]> "') , :Enter
tmux . until { | lines | assert lines . any_include? ( '[1]>' ) }
tmux . send_keys :a
tmux . until { | lines | assert lines . any_include? ( '[3]>' ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( 'S[3]>' ) }
end
def test_change_nth_invalid_value_ignored
tmux . send_keys %(echo 'a b c' | #{ FZF } --nth 1 --bind 'a:change-nth(3),space:change-nth(bad)+transform-prompt:echo "S[$FZF_NTH]> "' --bind 'b:transform-nth(echo bad)+transform-prompt:echo "T[$FZF_NTH]> "' --bind 'result:transform-prompt:echo "[$FZF_NTH]> "') , :Enter
tmux . until { | lines | assert lines . any_include? ( '[1]>' ) }
tmux . send_keys :a
tmux . until { | lines | assert lines . any_include? ( '[3]>' ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( 'S[3]>' ) }
tmux . send_keys :b
tmux . until { | lines | assert lines . any_include? ( 'T[3]>' ) }
end
def test_change_nth_from_transform
# A new action is created on each run, so the next value is found from the current state
tmux . send_keys %(echo 'a b c' | #{ FZF } --nth 1 --bind 'space:transform:echo "change-nth(1|2|3)"' --bind 'result:transform-prompt:echo "[$FZF_NTH]> "') , :Enter
tmux . until { | lines | assert lines . any_include? ( '[1]>' ) }
%w[2 3 1] . each do | nth |
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( "[ #{ nth } ]>" ) }
end
end
def test_change_with_nth_skips_default_value
tmux . send_keys %(echo 'a b c' | #{ FZF } --with-nth 1 --bind 'space:change-with-nth(|2)') , :Enter
tmux . until { | lines | assert lines . any_include? ( '> a' ) }
# Empty value is the default (field 1), which is current
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( '> b' ) }
end
def test_change_with_nth_skips_invalid_value
tmux . send_keys %(echo 'a b c' | #{ FZF } --with-nth 1 --bind 'space:change-with-nth(1|foo|2)') , :Enter
tmux . until { | lines | assert lines . any_include? ( '> a' ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( '> b' ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( '> a' ) }
end
2026-03-01 16:09:54 +09:00
def test_transform_with_nth_search
input = [
'alpha bravo charlie' ,
'delta echo foxtrot' ,
'golf hotel india'
]
writelines ( input )
tmux . send_keys %( #{ FZF } --with-nth 1 --bind 'space:transform-with-nth(echo 2)' -q '^bravo$' < #{ tempname } ) , :Enter
tmux . until do | lines |
assert_equal 0 , lines . match_count
end
tmux . send_keys :Space
tmux . until do | lines |
assert_equal 1 , lines . match_count
end
end
def test_bg_transform_with_nth_output
tmux . send_keys %(echo -e 'a b c \n d e f' | #{ FZF } --with-nth 2 --bind 'space:bg-transform-with-nth(echo 3)') , :Enter
tmux . until do | lines |
assert_equal 2 , lines . item_count
assert lines . any_include? ( 'b' )
end
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( 'c' )
refute lines . any_include? ( 'b' )
end
tmux . send_keys :Enter
tmux . until { | lines | assert lines . any_include? ( 'a b c' ) || lines . any_include? ( 'd e f' ) }
end
2026-10-20 14:09:55 +09:00
def test_transform_nth_output_does_not_replace_command
# '|' in the output must not be treated as a list to cycle through
File . write ( tempname , '' )
tmux . send_keys %(echo 'a b' | #{ FZF } --with-nth .. --bind "space:transform-nth(echo >> #{ tempname } ; echo '1|2')+transform-with-nth(echo >> #{ tempname } ; echo '1|2')") , :Enter
tmux . until { | lines | assert_equal 1 , lines . item_count }
3 . times do | i |
tmux . send_keys :Space
wait { assert_equal 2 * ( i + 1 ), File . readlines ( tempname ) . length }
end
end
def test_transform_with_nth_output_with_pipe
File . write ( tempname , '{2}|{1}' )
tmux . send_keys %(echo 'a b' | #{ FZF } --with-nth .. --bind 'space:transform-with-nth(cat #{ tempname } )') , :Enter
tmux . until { | lines | assert lines . any_include? ( 'a b' ) }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( 'b|a' ) }
end
2026-05-18 18:48:00 +09:00
# Regression: actions emitted by bg-transform must affect the iteration that
# processes the async result, not the (no-longer-active) iteration that
# scheduled the transform. Covers reload (newCommand) and exclude (denylist).
def test_bg_transform_action_output
tmux . send_keys %(seq 5 | #{ FZF } --bind 'a:bg-transform(echo reload:seq 10 20),b:bg-transform(echo exclude)') , :Enter
tmux . until { | lines | assert_equal 5 , lines . item_count }
tmux . send_keys :a
tmux . until do | lines |
assert_equal 11 , lines . match_count
assert_includes lines , '> 10'
end
tmux . send_keys :b
tmux . until do | lines |
assert_equal 10 , lines . match_count
assert_includes lines , '> 11'
end
end
2026-03-01 16:09:54 +09:00
def test_change_with_nth_search
input = [
'alpha bravo charlie' ,
'delta echo foxtrot' ,
'golf hotel india'
]
writelines ( input )
tmux . send_keys %( #{ FZF } --with-nth 1 --bind 'space:change-with-nth(2)' -q '^bravo$' < #{ tempname } ) , :Enter
tmux . until do | lines |
assert_equal 0 , lines . match_count
end
tmux . send_keys :Space
tmux . until do | lines |
assert_equal 1 , lines . match_count
end
end
def test_change_with_nth_output
tmux . send_keys %(echo -e 'a b c \n d e f' | #{ FZF } --with-nth 2 --bind 'space:change-with-nth(3)') , :Enter
tmux . until do | lines |
assert_equal 2 , lines . item_count
assert lines . any_include? ( 'b' )
end
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( 'c' )
refute lines . any_include? ( 'b' )
end
tmux . send_keys :Enter
tmux . until { | lines | assert lines . any_include? ( 'a b c' ) || lines . any_include? ( 'd e f' ) }
end
def test_change_with_nth_selection
# Items: field1 has unique values, field2 has 'match' or 'miss'
input = [
'one match x' ,
'two miss y' ,
'three match z'
]
writelines ( input )
# Start showing field 2 (match/miss), query 'match', select all matches, then switch to field 3
tmux . send_keys %( #{ FZF } --with-nth 2 --multi --bind 'ctrl-a:select-all,space:change-with-nth(3)' -q match < #{ tempname } ) , :Enter
tmux . until do | lines |
assert_equal 2 , lines . match_count
end
# Select all matching items
tmux . send_keys 'C-a'
tmux . until do | lines |
assert lines . any_include? ( '(2)' )
end
# Now change with-nth to field 3; 'x' and 'z' don't contain 'match'
tmux . send_keys :Space
tmux . until do | lines |
assert_equal 0 , lines . match_count
# Selections of non-matching items should be cleared
assert lines . any_include? ( '(0)' )
end
end
def test_change_with_nth_multiline
# Each item has 3 lines: "N-a\nN-b\nN-c"
# --with-nth 1 shows 1 line per item, --with-nth 1..3 shows 3 lines per item
tmux . send_keys %(seq 20 | xargs -I{} printf '{}-a\\n{}-b\\n{}-c\\0' | #{ FZF } --read0 --delimiter " \n " --with-nth 1 --bind 'space:change-with-nth(1..3|1)' --no-sort) , :Enter
tmux . until do | lines |
assert_equal 20 , lines . item_count
assert lines . any_include? ( '1-a' )
refute lines . any_include? ( '1-b' )
end
# Expand to 3 lines per item
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( '1-a' )
assert lines . any_include? ( '1-b' )
assert lines . any_include? ( '1-c' )
end
# Scroll down a few items
5 . times { tmux . send_keys :Down }
tmux . until do | lines |
assert lines . any_include? ( '6-a' )
assert lines . any_include? ( '6-b' )
assert lines . any_include? ( '6-c' )
end
# Collapse back to 1 line per item
tmux . send_keys :Space
tmux . until do | lines |
assert lines . any_include? ( '6-a' )
refute lines . any_include? ( '6-b' )
end
# Scroll down more after collapse
5 . times { tmux . send_keys :Down }
tmux . until do | lines |
assert lines . any_include? ( '11-a' )
refute lines . any_include? ( '11-b' )
end
end
2025-02-02 01:48:52 +09:00
def test_env_vars
2025-04-22 08:51:43 -03:00
def env_vars
return {} unless File . exist? ( tempname )
File . readlines ( tempname ) . select { it . start_with? ( 'FZF_' ) } . to_h do
key , val = it . chomp . split ( '=' , 2 )
2025-02-02 01:48:52 +09:00
[ key . to_sym , val ]
end
end
2026-02-25 00:50:43 +09:00
tmux . send_keys %({ echo foo; seq 100; } | #{ FZF } --header-lines 1 --multi --reverse --preview-window 0 --preview 'env | grep ^FZF_ | sort > #{ tempname } ' --no-input --bind enter:show-input+refresh-preview,space:disable-search+refresh-preview) , :Enter
2025-02-02 01:48:52 +09:00
expected = {
2025-10-04 22:50:33 +09:00
FZF_DIRECTION : 'down' ,
2025-02-02 02:48:04 +09:00
FZF_TOTAL_COUNT : '100' ,
FZF_MATCH_COUNT : '100' ,
2025-02-02 01:48:52 +09:00
FZF_SELECT_COUNT : '0' ,
2025-02-02 02:48:04 +09:00
FZF_ACTION : 'start' ,
FZF_KEY : '' ,
FZF_POS : '1' ,
2026-05-23 22:28:03 +09:00
FZF_CURRENT_ITEM : '1' ,
2025-02-02 02:48:04 +09:00
FZF_QUERY : '' ,
2025-04-22 08:51:43 -03:00
FZF_POINTER : '>' ,
FZF_PROMPT : '> ' ,
2025-02-02 02:48:04 +09:00
FZF_INPUT_STATE : 'hidden'
2025-02-02 01:48:52 +09:00
}
2025-04-22 08:51:43 -03:00
tmux . until do
assert_equal expected , env_vars . slice ( * expected . keys )
2025-02-02 01:48:52 +09:00
end
tmux . send_keys :Enter
2025-04-22 08:51:43 -03:00
tmux . until do
2025-02-02 01:48:52 +09:00
expected . merge! ( FZF_INPUT_STATE : 'enabled' , FZF_ACTION : 'show-input' , FZF_KEY : 'enter' )
2025-04-22 08:51:43 -03:00
assert_equal expected , env_vars . slice ( * expected . keys )
2025-02-02 01:48:52 +09:00
end
tmux . send_keys :Tab , :Tab
2025-04-22 08:51:43 -03:00
tmux . until do
2026-05-23 22:28:03 +09:00
expected . merge! ( FZF_ACTION : 'toggle-down' , FZF_KEY : 'tab' , FZF_POS : '3' , FZF_CURRENT_ITEM : '3' , FZF_SELECT_COUNT : '2' )
2025-04-22 08:51:43 -03:00
assert_equal expected , env_vars . slice ( * expected . keys )
2025-02-02 01:48:52 +09:00
end
tmux . send_keys '99'
2025-04-22 08:51:43 -03:00
tmux . until do
2026-05-23 22:28:03 +09:00
expected . merge! ( FZF_ACTION : 'char' , FZF_KEY : '9' , FZF_QUERY : '99' , FZF_MATCH_COUNT : '1' , FZF_POS : '1' , FZF_CURRENT_ITEM : '99' )
2025-04-22 08:51:43 -03:00
assert_equal expected , env_vars . slice ( * expected . keys )
2025-02-02 01:48:52 +09:00
end
tmux . send_keys :Space
2025-04-22 08:51:43 -03:00
tmux . until do
2025-02-02 01:48:52 +09:00
expected . merge! ( FZF_INPUT_STATE : 'disabled' , FZF_ACTION : 'disable-search' , FZF_KEY : 'space' )
2025-04-22 08:51:43 -03:00
assert_equal expected , env_vars . slice ( * expected . keys )
2025-02-02 01:48:52 +09:00
end
end
2025-02-02 22:54:53 +09:00
2026-06-23 20:49:50 +09:00
def test_env_current_item_size_limit
preview = %[(echo START; env | grep '^FZF_CURRENT_ITEM='; echo END) > #{ tempname } ]
# Large item (> 64 KB) is omitted so it cannot overflow ARG_MAX and break exec
tmux . send_keys %(head -c 70000 /dev/zero | tr '\\0' a | #{ FZF } --preview-window 0 --preview " #{ preview } ") , :Enter
wait do
content = File . exist? ( tempname ) ? File . read ( tempname ) : ''
assert_includes content , 'END'
refute_includes content , 'FZF_CURRENT_ITEM='
end
tmux . send_keys :Enter
FileUtils . rm_f ( tempname )
# Smaller item is exported as usual
tmux . send_keys %(head -c 1000 /dev/zero | tr '\\0' a | #{ FZF } --preview-window 0 --preview " #{ preview } ") , :Enter
wait do
content = File . exist? ( tempname ) ? File . read ( tempname ) : ''
assert_includes content , 'END'
assert_includes content , 'FZF_CURRENT_ITEM=' + ( 'a' * 1000 )
end
end
2025-02-02 22:54:53 +09:00
def test_abort_action_chain
tmux . send_keys %(seq 100 | #{ FZF } --bind 'load:accept+up+up' > #{ tempname } ) , :Enter
wait do
assert_path_exists tempname
assert_equal '1' , File . read ( tempname ) . chomp
end
tmux . send_keys %(seq 100 | #{ FZF } --bind 'load:abort+become(echo {})' > #{ tempname } ) , :Enter
wait do
assert_path_exists tempname
assert_equal '' , File . read ( tempname ) . chomp
end
end
2025-02-09 11:53:35 +09:00
2025-02-09 13:52:20 +09:00
def test_exclude_multi
tmux . send_keys %(seq 1000 | #{ FZF } --multi --bind 'a:exclude-multi,b:reload(seq 1000),c:reload-sync(seq 1000)') , :Enter
2025-02-09 13:22:33 +09:00
2025-02-09 13:37:22 +09:00
tmux . until do | lines |
assert_equal 1000 , lines . match_count
assert_includes lines , '> 1'
end
2025-02-09 13:22:33 +09:00
tmux . send_keys :a
2025-02-09 13:37:22 +09:00
tmux . until do | lines |
assert_includes lines , '> 2'
assert_equal 999 , lines . match_count
end
2025-02-09 13:22:33 +09:00
tmux . send_keys :Up , :BTab , :BTab , :BTab , :a
2025-02-09 13:37:22 +09:00
tmux . until do | lines |
assert_equal 996 , lines . match_count
assert_includes lines , '> 9'
end
2025-02-09 13:22:33 +09:00
tmux . send_keys :b
2025-02-09 13:37:22 +09:00
tmux . until do | lines |
assert_equal 1000 , lines . match_count
assert_includes lines , '> 5'
end
2025-02-09 13:22:33 +09:00
tmux . send_keys :Tab , :Tab , :Tab , :a
2025-02-09 13:37:22 +09:00
tmux . until do | lines |
assert_equal 997 , lines . match_count
assert_includes lines , '> 2'
end
2025-02-09 13:22:33 +09:00
tmux . send_keys :c
2025-02-09 13:37:22 +09:00
tmux . until do | lines |
assert_equal 1000 , lines . match_count
assert_includes lines , '> 2'
end
# TODO: We should also check the behavior of 'exclude' during reloads
end
2025-02-09 13:52:20 +09:00
def test_exclude
tmux . send_keys %(seq 1000 | #{ FZF } --multi --bind 'a:exclude,b:reload(seq 1000),c:reload-sync(seq 1000)') , :Enter
2025-02-09 13:37:22 +09:00
tmux . until do | lines |
assert_equal 1000 , lines . match_count
assert_includes lines , '> 1'
end
tmux . send_keys :a
tmux . until do | lines |
assert_includes lines , '> 2'
assert_equal 999 , lines . match_count
end
tmux . send_keys :Up , :BTab , :BTab , :BTab , :a
tmux . until do | lines |
assert_equal 998 , lines . match_count
assert_equal 3 , lines . select_count
assert_includes lines , '> 7'
end
tmux . send_keys :b
tmux . until do | lines |
assert_equal 1000 , lines . match_count
assert_equal 0 , lines . select_count
assert_includes lines , '> 5'
end
tmux . send_keys :Tab , :Tab , :Tab , :a
tmux . until do | lines |
assert_equal 999 , lines . match_count
assert_equal 3 , lines . select_count
assert_includes lines , '>>3'
end
tmux . send_keys :a
tmux . until do | lines |
assert_equal 998 , lines . match_count
assert_equal 2 , lines . select_count
assert_includes lines , '>>4'
end
tmux . send_keys :c
tmux . until do | lines |
assert_equal 1000 , lines . match_count
assert_includes lines , '> 2'
end
2025-02-09 13:22:33 +09:00
# TODO: We should also check the behavior of 'exclude' during reloads
end
2025-02-09 11:53:35 +09:00
def test_accept_nth
tmux . send_keys %((echo "foo bar baz"; echo "bar baz foo") | #{ FZF } --multi --accept-nth 2,2 --sync --bind start:select-all+accept > #{ tempname } ) , :Enter
wait do
assert_path_exists tempname
assert_equal [ 'bar bar' , 'baz baz' ] , File . readlines ( tempname , chomp : true )
end
end
def test_accept_nth_string_delimiter
tmux . send_keys %(echo "foo ,bar,baz" | #{ FZF } -d, --accept-nth 2,2,1,3,1 --sync --bind start:accept > #{ tempname } ) , :Enter
wait do
assert_path_exists tempname
2026-03-07 13:39:55 +09:00
# Last delimiter is removed
assert_equal [ 'bar,bar,foo ,bazfoo ' ] , File . readlines ( tempname , chomp : true )
2025-02-09 11:53:35 +09:00
end
end
def test_accept_nth_regex_delimiter
2026-03-07 13:39:55 +09:00
tmux . send_keys %(echo "foo :,:bar,baz" | #{ FZF } --delimiter=' *[:,]+ *' --accept-nth 2,2,1,3,1 --sync --bind start:accept > #{ tempname } ) , :Enter
2025-02-09 11:53:35 +09:00
wait do
assert_path_exists tempname
# Last delimiter and the whitespaces are removed
assert_equal [ 'bar,bar,foo :,:bazfoo' ] , File . readlines ( tempname , chomp : true )
end
end
2025-02-12 20:15:04 +09:00
2025-02-21 21:21:55 +08:00
def test_accept_nth_regex_delimiter_strip_last
tmux . send_keys %((echo "foo:,bar:,baz"; echo "foo:,bar:,baz:,qux:,") | #{ FZF } --multi --delimiter='[:,]+' --accept-nth 2.. --sync --bind 'load:select-all+accept' > #{ tempname } ) , :Enter
wait do
assert_path_exists tempname
# Last delimiter and the whitespaces are removed
assert_equal [ 'bar:,baz' , 'bar:,baz:,qux' ] , File . readlines ( tempname , chomp : true )
end
end
2025-02-12 20:15:04 +09:00
def test_accept_nth_template
2026-03-07 13:39:55 +09:00
tmux . send_keys %(echo "foo ,bar,baz" | #{ FZF } -d " *, *" --accept-nth '[{n}] 1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{ tempname } ) , :Enter
2025-02-12 20:15:04 +09:00
wait do
assert_path_exists tempname
# Last delimiter and the whitespaces are removed
2025-02-23 19:47:56 +09:00
assert_equal [ '[0] 1st: foo, 3rd: baz, 2nd: bar' ] , File . readlines ( tempname , chomp : true )
2025-02-12 20:15:04 +09:00
end
end
2025-03-14 16:46:23 +09:00
def test_ghost
2025-03-28 23:35:20 +09:00
tmux . send_keys %(seq 100 | #{ FZF } --prompt 'X ' --ghost 'Type in query ...' --bind 'space:change-ghost:Y Z' --bind 'enter:transform-ghost:echo Z Y') , :Enter
2025-03-14 16:46:23 +09:00
tmux . until do | lines |
assert_equal 100 , lines . match_count
assert_includes lines , 'X Type in query ...'
end
tmux . send_keys '100'
tmux . until do | lines |
assert_equal 1 , lines . match_count
assert_includes lines , 'X 100'
end
tmux . send_keys 'C-u'
tmux . until do | lines |
assert_equal 100 , lines . match_count
assert_includes lines , 'X Type in query ...'
end
2025-03-28 23:35:20 +09:00
tmux . send_keys :Space
tmux . until { | lines | assert_includes lines , 'X Y Z' }
tmux . send_keys :Enter
tmux . until { | lines | assert_includes lines , 'X Z Y' }
2025-03-14 16:46:23 +09:00
end
2025-03-15 18:42:08 +09:00
def test_ghost_inline
tmux . send_keys %(seq 100 | #{ FZF } --info 'inline: Y' --no-separator --prompt 'X ' --ghost 'Type in query ...') , :Enter
tmux . until do | lines |
assert_includes lines , 'X Type in query ... Y100/100'
end
tmux . send_keys '100'
tmux . until do | lines |
assert_includes lines , 'X 100 Y1/100'
end
tmux . send_keys 'C-u'
tmux . until do | lines |
assert_includes lines , 'X Type in query ... Y100/100'
end
end
2025-03-23 11:06:18 +09:00
def test_offset_middle
tmux . send_keys %(seq 1000 | #{ FZF } --sync --no-input --reverse --height 5 --scroll-off 0 --bind space:offset-middle) , :Enter
line = nil
tmux . until { | lines | line = lines . index ( '> 1' ) }
tmux . send_keys :PgDn
2025-05-17 11:20:20 +09:00
tmux . until { | lines | assert_includes lines [ line + 4 ] , '> 5' }
2025-03-23 11:06:18 +09:00
tmux . send_keys :Space
2025-05-17 11:20:20 +09:00
tmux . until { | lines | assert_includes lines [ line + 2 ] , '> 5' }
2025-03-23 11:06:18 +09:00
end
2025-03-25 21:06:04 +09:00
def test_no_input_query
tmux . send_keys %(seq 1000 | #{ FZF } --no-input --query 555 --bind space:toggle-input) , :Enter
tmux . until { | lines | assert_includes lines , '> 555' }
tmux . send_keys :Space
tmux . until do | lines |
assert_equal 1 , lines . match_count
assert_includes lines , '> 555'
end
end
2025-03-26 10:34:52 +09:00
def test_no_input_change_query
tmux . send_keys %(seq 1000 | #{ FZF } --multi --query 999 --no-input --bind 'enter:show-input+change-query(555)+hide-input,space:change-query(555)+select') , :Enter
tmux . until { | lines | assert_includes lines , '> 999' }
tmux . send_keys :Space
tmux . until do | lines |
assert_includes lines , '>>999'
refute_includes lines , '> 555'
end
tmux . send_keys :Enter
tmux . until do | lines |
refute_includes lines , '>>999'
assert_includes lines , '> 555'
end
end
2025-03-26 15:47:43 +09:00
def test_search_override_query_in_no_input_mode
tmux . send_keys %(seq 1000 | #{ FZF } --sync --no-input --bind 'enter:show-input+change-query(555)+hide-input+search(999),space:search(111)+show-input+change-query(777)') , :Enter
tmux . until { | lines | assert_includes lines , '> 1' }
tmux . send_keys :Enter
tmux . until { | lines | assert_includes lines , '> 999' }
tmux . send_keys :Space
tmux . until { | lines | assert_includes lines , '> 777' }
end
2025-03-28 21:27:44 +09:00
def test_change_pointer
tmux . send_keys %(seq 2 | #{ FZF } --bind 'a:change-pointer(a),b:change-pointer(bb),c:change-pointer(),d:change-pointer(ddd)') , :Enter
tmux . until { | lines | assert_includes lines , '> 1' }
tmux . send_keys 'a'
tmux . until { | lines | assert_includes lines , 'a 1' }
tmux . send_keys 'b'
tmux . until { | lines | assert_includes lines , 'bb 1' }
tmux . send_keys 'c'
tmux . until { | lines | assert_includes lines , ' 1' }
tmux . send_keys 'd'
tmux . until { | lines | refute_includes lines , 'ddd 1' }
tmux . send_keys :Up
tmux . until { | lines | assert_includes lines , ' 2' }
end
def test_transform_pointer
tmux . send_keys %(seq 2 | #{ FZF } --bind 'a:transform-pointer(echo a),b:transform-pointer(echo bb),c:transform-pointer(),d:transform-pointer(echo ddd)') , :Enter
tmux . until { | lines | assert_includes lines , '> 1' }
tmux . send_keys 'a'
tmux . until { | lines | assert_includes lines , 'a 1' }
tmux . send_keys 'b'
tmux . until { | lines | assert_includes lines , 'bb 1' }
tmux . send_keys 'c'
tmux . until { | lines | assert_includes lines , ' 1' }
tmux . send_keys 'd'
tmux . until { | lines | refute_includes lines , 'ddd 1' }
tmux . send_keys :Up
tmux . until { | lines | assert_includes lines , ' 2' }
end
2025-03-28 23:22:09 +09:00
def test_change_header_on_header_window
tmux . send_keys %(seq 100 | #{ FZF } --list-border --input-border --bind 'start:change-header(foo),space:change-header(bar)') , :Enter
2025-07-06 20:04:03 +09:00
tmux . until do | lines |
assert lines . any_include? ( '100/100' )
assert lines . any_include? ( 'foo' )
end
2025-03-28 23:22:09 +09:00
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( 'bar' ) }
end
2025-04-11 20:46:49 +09:00
def test_trailing_new_line
tmux . send_keys %(echo -en "foo \n " | fzf --read0 --no-multi-line) , :Enter
tmux . until { | lines | assert_includes lines , '> foo␊' }
end
2025-06-16 00:39:11 +09:00
def test_async_transform
time = Time . now
tmux . send_keys %(
seq 100 | #{ FZF } --style full --border --preview : \
--bind 'focus:bg-transform-header(sleep 0.5; echo th.)' \
--bind 'focus:+bg-transform-footer(sleep 0.5; echo tf.)' \
--bind 'focus:+bg-transform-border-label(sleep 0.5; echo tbl.)' \
--bind "focus:+bg-transform-preview-label(sleep 0.5; echo tpl.)" \
--bind 'focus:+bg-transform-input-label(sleep 0.5; echo til.)' \
--bind 'focus:+bg-transform-list-label(sleep 0.5; echo tll.)' \
--bind 'focus:+bg-transform-header-label(sleep 0.5; echo thl.)' \
--bind 'focus:+bg-transform-footer-label(sleep 0.5; echo tfl.)' \
--bind 'focus:+bg-transform-prompt(sleep 0.5; echo tp.)' \
--bind 'focus:+bg-transform-ghost(sleep 0.5; echo tg.)'
) . strip , :Enter
tmux . until do | lines |
assert lines . any_include? ( '100/100' )
%w[th tf tbl tpl til tll thl tfl tp tg] . each do
assert lines . any_include? ( " #{ it } ." )
end
end
elapsed = Time . now - time
2025-09-22 21:23:54 +09:00
assert_operator elapsed , :< , 2
2025-06-16 00:39:11 +09:00
end
2025-06-21 17:21:03 +09:00
def test_bg_cancel
tmux . send_keys %(seq 0 1 | #{ FZF } --bind 'space:bg-cancel+bg-transform-header(sleep {}; echo [{}])') , :Enter
tmux . until { assert_equal 2 , it . match_count }
tmux . send_keys '1'
tmux . until { assert_equal 1 , it . match_count }
tmux . send_keys :Space
tmux . send_keys :BSpace
tmux . until { assert_equal 2 , it . match_count }
tmux . send_keys :Space
tmux . until { | lines | assert lines . any_include? ( '[0]' ) }
2025-09-22 21:23:54 +09:00
sleep ( 2 )
2025-06-21 17:21:03 +09:00
tmux . until do | lines |
assert lines . any_include? ( '[0]' )
refute lines . any_include? ( '[1]' )
end
end
2025-07-02 21:28:11 +09:00
def test_render_order
tmux . send_keys %(seq 100 | #{ FZF } --bind='focus:preview(echo boom)+change-footer(bam)') , :Enter
tmux . until { assert_equal 100 , it . match_count }
tmux . until { assert it . any_include? ( 'boom' ) }
tmux . until { assert it . any_include? ( 'bam' ) }
end
2025-07-06 10:05:25 +09:00
def test_multi_event
tmux . send_keys %(seq 100 | #{ FZF } --multi --bind 'multi:transform-footer:(( FZF_SELECT_COUNT )) && echo "Selected $FZF_SELECT_COUNT item(s)"') , :Enter
tmux . until { assert_equal 100 , it . match_count }
tmux . send_keys :Tab
tmux . until { assert_equal 1 , it . select_count }
tmux . until { assert it . any_include? ( 'Selected 1 item(s)' ) }
tmux . send_keys :Tab
tmux . until { assert_equal 0 , it . select_count }
tmux . until { refute it . any_include? ( 'Selected' ) }
end
2025-07-06 22:02:12 +09:00
def test_preserve_selection_on_revision_bump
tmux . send_keys %(seq 100 | #{ FZF } --multi --sync --query "'1" --bind 'a:select-all+change-header(pressed a),b:change-header(pressed b)+change-nth(1),c:exclude') , :Enter
tmux . until do
assert_equal 20 , it . match_count
assert_equal 0 , it . select_count
end
tmux . send_keys :a
tmux . until do
assert_equal 20 , it . match_count
assert_equal 20 , it . select_count
assert it . any_include? ( 'pressed a' )
end
tmux . send_keys :b
tmux . until do
assert_equal 20 , it . match_count
assert_equal 20 , it . select_count
refute it . any_include? ( 'pressed a' )
assert it . any_include? ( 'pressed b' )
end
tmux . send_keys :a
tmux . until do
assert_equal 20 , it . match_count
assert_equal 20 , it . select_count
assert it . any_include? ( 'pressed a' )
refute it . any_include? ( 'pressed b' )
end
tmux . send_keys :c
tmux . until do
assert_equal 19 , it . match_count
assert_equal 19 , it . select_count
end
end
2025-07-23 19:39:01 +09:00
def test_trigger
tmux . send_keys %(seq 100 | #{ FZF } --bind 'a:up+trigger(a),b:trigger(a,a,b,a)') , :Enter
tmux . until { assert_equal 100 , it . match_count }
tmux . until { | lines | assert_includes lines , '> 1' }
tmux . send_keys :a
tmux . until { | lines | assert_includes lines , '> 3' }
tmux . send_keys :b
tmux . until { | lines | assert_includes lines , '> 9' }
end
2025-08-03 00:28:50 +09:00
def test_change_nth_unset_default
2026-10-20 16:19:59 +09:00
tmux . send_keys %(echo foo bar | #{ FZF } --nth 2 --query fb --bind space:change-nth:..) , :Enter
2025-08-03 00:28:50 +09:00
tmux . until do
assert_equal 1 , it . item_count
assert_equal 0 , it . match_count
end
tmux . send_keys :Space
tmux . until do
assert_equal 1 , it . item_count
assert_equal 1 , it . match_count
end
end
2026-01-31 13:25:50 +09:00
2026-10-20 16:19:59 +09:00
def test_change_nth_empty_restores_default
tmux . send_keys %(echo foo bar | #{ FZF } --nth 2 --query fb --bind 'a:change-nth(..),space:change-nth(),b:transform-nth(true)') , :Enter
tmux . until { | lines | assert_equal 0 , lines . match_count }
tmux . send_keys :a
tmux . until { | lines | assert_equal 1 , lines . match_count }
tmux . send_keys :Space
tmux . until { | lines | assert_equal 0 , lines . match_count }
tmux . send_keys :a
tmux . until { | lines | assert_equal 1 , lines . match_count }
tmux . send_keys :b
tmux . until { | lines | assert_equal 0 , lines . match_count }
end
2026-02-23 01:45:47 +09:00
def test_change_header_lines
tmux . send_keys %(seq 10 | #{ FZF } --header-lines 3 --bind 'space:change-header-lines(5),enter:transform-header-lines(echo 1)') , :Enter
tmux . until do | lines |
assert_equal 7 , lines . item_count
assert lines . any_include? ( '> 4' )
end
tmux . send_keys :Space
tmux . until do | lines |
assert_equal 5 , lines . item_count
assert lines . any_include? ( '> 6' )
end
tmux . send_keys :Enter
tmux . until do | lines |
assert_equal 9 , lines . item_count
assert lines . any_include? ( '> 6' )
end
end
def test_change_header_lines_to_zero
tmux . send_keys %(seq 5 | #{ FZF } --header-lines 3 --bind 'space:bg-transform-header-lines(echo 0)') , :Enter
tmux . until do | lines |
assert_equal 2 , lines . item_count
assert lines . any_include? ( '> 4' )
end
tmux . send_keys :Space
tmux . until do | lines |
assert_equal 5 , lines . item_count
# All items are now in the list, cursor stays on item 4
assert lines . any_include? ( '> 4' )
end
end
def test_change_header_lines_deselect
# Selected items that become part of the header should be deselected
tmux . send_keys %(seq 10 | #{ FZF } --multi --header-lines 0 --bind 'space:change-header-lines(3),enter:change-header-lines(1)') , :Enter
tmux . until do | lines |
assert_equal 10 , lines . item_count
assert lines . any_include? ( '> 1' )
end
# Select items 1, 2, 3 (these will become header lines)
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | assert_equal 3 , lines . select_count }
# Also select item 4 (this should remain selected)
tmux . send_keys :BTab
tmux . until { | lines | assert_equal 4 , lines . select_count }
# Change header-lines to 3: items 1, 2, 3 become headers and should be deselected
tmux . send_keys :Space
tmux . until do | lines |
assert_equal 7 , lines . item_count
assert_equal 1 , lines . select_count
assert lines . any_include? ( '> 5' )
end
# Change header-lines to 1
tmux . send_keys :Enter
tmux . until do | lines |
assert_equal 9 , lines . item_count
assert_equal 1 , lines . select_count
assert lines . any_include? ( '> 5' )
end
end
def test_change_header_lines_reverse
tmux . send_keys %(seq 10 | #{ FZF } --header-lines 2 --reverse --bind 'space:change-header-lines(4)') , :Enter
tmux . until do | lines |
assert_equal 8 , lines . item_count
assert lines . any_include? ( '> 3' )
end
tmux . send_keys :Space
tmux . until do | lines |
assert_equal 6 , lines . item_count
assert lines . any_include? ( '> 5' )
end
end
2026-01-31 13:25:50 +09:00
def test_zero_width_characters
tmux . send_keys %(for i in {1..1000}; do string+="a̱$i"; printf '\\e[43m%s\\e[0m\\n' "$string"; done | #{ FZF } --ansi --query a500 --ellipsis XX) , :Enter
tmux . until do | lines |
assert_equal 981 , lines . match_count
assert_match ( /^> XX.*a̱500/ , lines [- 3 ] )
assert ( lines . reverse . drop ( 5 ) . all? { it . match? ( /^ XX.*a̱500.*XX/ ) })
end
end
2025-01-25 19:57:40 +09:00
end