Files
dotfiles/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_nav.lua
2026-07-29 08:36:58 +02:00

521 lines
17 KiB
Lua

local T
local expect = MiniTest.expect
local child = MiniTest.new_child_neovim()
local source = debug.getinfo(1, 'S').source
local root = vim.fn.fnamemodify(source:sub(2), ':p:h:h')
T = MiniTest.new_set({
hooks = {
pre_case = function()
child.restart({ '--noplugin', '-u', 'NONE', '-i', 'NONE' })
child.cmd('set runtimepath^=' .. vim.fn.fnameescape(root))
end,
post_case = function()
child.stop()
end,
},
})
T['moves through native horizontal and vertical splits without delegation'] = function()
local result = child.lua_func(function()
local calls = {}
package.loaded['herdr-splits.herdr'] = {
focus_pane = function(direction) calls[#calls + 1] = direction end,
is_in_session = function() return false end,
}
package.loaded['herdr-splits.nav'] = nil
local nav = require('herdr-splits.nav')
vim.o.splitright = true
vim.cmd('vsplit')
local horizontal = vim.api.nvim_tabpage_list_wins(0)
table.sort(horizontal, function(a, b)
return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
end)
vim.api.nvim_set_current_win(horizontal[1])
nav.move_cursor('right')
local moved_right = vim.api.nvim_get_current_win() == horizontal[2]
vim.cmd('only')
vim.o.splitbelow = true
vim.cmd('split')
local vertical = vim.api.nvim_tabpage_list_wins(0)
table.sort(vertical, function(a, b)
return vim.api.nvim_win_get_position(a)[1] < vim.api.nvim_win_get_position(b)[1]
end)
vim.api.nvim_set_current_win(vertical[1])
nav.move_cursor('down')
local moved_down = vim.api.nvim_get_current_win() == vertical[2]
return { moved_right = moved_right, moved_down = moved_down, calls = calls }
end)
expect.equality(result, { moved_right = true, moved_down = true, calls = {} })
end
T['applies typed navigation counts without wrapping or delegation'] = function()
child.lua_func(function()
_G.nav_delegations = {}
package.loaded['herdr-splits.herdr'] = {
is_in_session = function() return false end,
focus_pane = function(direction)
_G.nav_delegations[#_G.nav_delegations + 1] = direction
return true
end,
}
package.loaded['herdr-splits.nav'] = nil
local nav = require('herdr-splits.nav')
vim.o.splitright = true
for _ = 1, 3 do
vim.cmd('vsplit')
end
_G.nav_count_wins = vim.api.nvim_tabpage_list_wins(0)
table.sort(_G.nav_count_wins, function(a, b)
return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
end)
vim.api.nvim_set_current_win(_G.nav_count_wins[1])
vim.keymap.set('n', 'x', function()
nav.move_cursor('right', { at_edge = 'stop' })
end)
end)
child.type_keys('2x')
local after_two = child.lua_get('vim.api.nvim_get_current_win() == _G.nav_count_wins[3]')
child.type_keys('5x')
local exhausted_at_last = child.lua_get('vim.api.nvim_get_current_win() == _G.nav_count_wins[4]')
child.type_keys('5x')
local stayed_at_last = child.lua_get('vim.api.nvim_get_current_win() == _G.nav_count_wins[4]')
local delegations = child.lua_get('_G.nav_delegations')
expect.equality({
after_two = after_two,
exhausted_at_last = exhausted_at_last,
stayed_at_last = stayed_at_last,
delegations = delegations,
}, {
after_two = true,
exhausted_at_last = true,
stayed_at_last = true,
delegations = {},
})
end
T['delegates normal floats but not embedded floats'] = function()
local result = child.lua_func(function()
local calls = {}
package.loaded['herdr-splits.herdr'] = {
focus_pane = function(direction)
calls[#calls + 1] = direction
return true
end,
is_in_session = function() return false end,
}
package.loaded['herdr-splits.nav'] = nil
local nav = require('herdr-splits.nav')
local normal = vim.api.nvim_open_win(vim.api.nvim_create_buf(false, true), true, {
relative = 'editor', row = 1, col = 1, width = 12, height = 3, zindex = 50,
})
nav.move_cursor('right')
local normal_stayed = vim.api.nvim_get_current_win() == normal
vim.api.nvim_open_win(vim.api.nvim_create_buf(false, true), true, {
relative = 'editor', row = 2, col = 2, width = 12, height = 3, zindex = 49,
})
nav.move_cursor('left')
return { calls = calls, normal_stayed = normal_stayed }
end)
expect.equality(result, {
calls = { 'right' },
normal_stayed = true,
})
end
T['applies no-session stop, wrap, and callback edge behavior'] = function()
local result = child.lua_func(function()
package.loaded['herdr-splits.herdr'] = {
is_in_session = function() return false end,
}
package.loaded['herdr-splits.nav'] = nil
local nav = require('herdr-splits.nav')
local function horizontal_pair()
vim.cmd('only')
vim.o.splitright = true
vim.cmd('vsplit')
local wins = vim.api.nvim_tabpage_list_wins(0)
table.sort(wins, function(a, b)
return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
end)
vim.api.nvim_set_current_win(wins[1])
return wins
end
local stop_wins = horizontal_pair()
nav.move_cursor('left', { at_edge = 'stop' })
local stopped = vim.api.nvim_get_current_win() == stop_wins[1]
local wrap_wins = horizontal_pair()
nav.move_cursor('left', { at_edge = 'wrap' })
local wrapped = vim.api.nvim_get_current_win() == wrap_wins[2]
local callback_wins = horizontal_pair()
local callback = {}
nav.move_cursor('left', {
at_edge = function(ctx)
callback.direction = ctx.direction
callback.is_sidebar = ctx.is_sidebar
callback.has_split = type(ctx.split) == 'function'
callback.has_wrap = type(ctx.wrap) == 'function'
ctx.wrap()
end,
})
callback.wrapped = vim.api.nvim_get_current_win() == callback_wins[2]
return {
stopped = stopped,
wrapped = wrapped,
callback = callback,
}
end)
expect.equality(result, {
stopped = true,
wrapped = true,
callback = {
direction = 'left',
is_sidebar = false,
has_split = true,
has_wrap = true,
wrapped = true,
},
})
end
T['creates local edge splits and preserves split preferences'] = function()
local result = child.lua_func(function()
package.loaded['herdr-splits.herdr'] = {
is_in_session = function() return false end,
}
package.loaded['herdr-splits.nav'] = nil
local nav = require('herdr-splits.nav')
local cases = {}
local function exercise(direction, preference)
vim.cmd('only')
vim.o.splitright = preference
vim.o.splitbelow = preference
local before = #vim.api.nvim_tabpage_list_wins(0)
local original = vim.api.nvim_get_current_win()
nav.move_cursor(direction, { at_edge = 'split' })
local current = vim.api.nvim_get_current_win()
local original_position = vim.api.nvim_win_get_position(original)
local current_position = vim.api.nvim_win_get_position(current)
local placement_matches_preference
if direction == 'left' or direction == 'right' then
if preference then
placement_matches_preference = current_position[2] > original_position[2]
else
placement_matches_preference = current_position[2] < original_position[2]
end
elseif preference then
placement_matches_preference = current_position[1] > original_position[1]
else
placement_matches_preference = current_position[1] < original_position[1]
end
cases[#cases + 1] = {
direction = direction,
preference = preference,
created_one = #vim.api.nvim_tabpage_list_wins(0) == before + 1,
focused_new = current ~= original,
placement_matches_preference = placement_matches_preference,
splitright_restored = vim.o.splitright == preference,
splitbelow_restored = vim.o.splitbelow == preference,
}
end
for _, direction in ipairs({ 'left', 'right', 'up', 'down' }) do
exercise(direction, false)
exercise(direction, true)
end
return cases
end)
local expected = {}
for _, direction in ipairs({ 'left', 'right', 'up', 'down' }) do
for _, preference in ipairs({ false, true }) do
expected[#expected + 1] = {
direction = direction,
preference = preference,
created_one = true,
focused_new = true,
placement_matches_preference = true,
splitright_restored = true,
splitbelow_restored = true,
}
end
end
expect.equality(result, expected)
end
T['uses Herdr neighbors and falls back locally on focus or edge failures'] = function()
local result = child.lua_func(function()
local edge_queue = { false, false, '<nil>' }
local focus_queue = { true, false }
local calls = {}
package.loaded['herdr-splits.herdr'] = {
is_in_session = function() return true end,
unzoom_enabled = function() return false end,
current_pane_at_edge = function(direction)
calls[#calls + 1] = 'edge:' .. direction
local value = table.remove(edge_queue, 1)
if value == '<nil>' then return nil end
return value
end,
focus_pane = function(direction)
calls[#calls + 1] = 'focus:' .. direction
return table.remove(focus_queue, 1)
end,
}
package.loaded['herdr-splits.nav'] = nil
local nav = require('herdr-splits.nav')
local function at_left()
vim.cmd('only')
vim.o.splitright = true
vim.cmd('vsplit')
local wins = vim.api.nvim_tabpage_list_wins(0)
table.sort(wins, function(a, b)
return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
end)
vim.api.nvim_set_current_win(wins[1])
return wins
end
local success_wins = at_left()
nav.move_cursor('left')
local focus_success_stays = vim.api.nvim_get_current_win() == success_wins[1]
local failure_wins = at_left()
nav.move_cursor('left')
local focus_failure_wraps = vim.api.nvim_get_current_win() == failure_wins[2]
local unknown_wins = at_left()
nav.move_cursor('left')
local unknown_wraps = vim.api.nvim_get_current_win() == unknown_wins[2]
return {
focus_success_stays = focus_success_stays,
focus_failure_wraps = focus_failure_wraps,
unknown_wraps = unknown_wraps,
calls = calls,
}
end)
expect.equality(result, {
focus_success_stays = true,
focus_failure_wraps = true,
unknown_wraps = true,
calls = {
'edge:left', 'focus:left',
'edge:left', 'focus:left',
'edge:left',
},
})
end
T['unzooms and retries native movement before querying Herdr edges'] = function()
local result = child.lua_func(function()
local calls = {}
local start_win = vim.api.nvim_get_current_win()
package.loaded['herdr-splits.herdr'] = {
is_in_session = function() return true end,
unzoom_enabled = function() return true end,
current_pane_is_zoomed = function()
calls[#calls + 1] = 'zoomed'
return true
end,
unzoom = function()
calls[#calls + 1] = 'unzoom'
local old_splitright = vim.o.splitright
vim.o.splitright = true
vim.cmd('vsplit')
vim.api.nvim_set_current_win(start_win)
vim.o.splitright = old_splitright
return true
end,
current_pane_at_edge = function()
calls[#calls + 1] = 'edge'
return false
end,
focus_pane = function()
calls[#calls + 1] = 'focus'
return true
end,
}
package.loaded['herdr-splits.nav'] = nil
require('herdr-splits.nav').move_cursor('right')
return {
moved = vim.api.nvim_get_current_win() ~= start_win,
windows = #vim.api.nvim_tabpage_list_wins(0),
calls = calls,
}
end)
expect.equality(result, { moved = true, windows = 2, calls = { 'zoomed', 'unzoom' } })
end
T['wraps through the reverse Herdr neighbor unless nav_at_edge stops it'] = function()
local result = child.lua_func(function()
local mode = 'wrap'
local calls = {}
package.loaded['herdr-splits.herdr'] = {
is_in_session = function() return true end,
unzoom_enabled = function() return false end,
current_pane_at_edge = function(direction)
calls[#calls + 1] = 'edge:' .. direction
return direction == 'left'
end,
nav_at_edge = function() return mode end,
focus_pane = function(direction)
calls[#calls + 1] = 'focus:' .. direction
return true
end,
}
package.loaded['herdr-splits.nav'] = nil
local nav = require('herdr-splits.nav')
local function at_left()
vim.cmd('only')
vim.o.splitright = true
vim.cmd('vsplit')
local wins = vim.api.nvim_tabpage_list_wins(0)
table.sort(wins, function(a, b)
return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
end)
vim.api.nvim_set_current_win(wins[1])
return wins
end
local herdr_wrap_wins = at_left()
nav.move_cursor('left')
local herdr_wrap_stays = vim.api.nvim_get_current_win() == herdr_wrap_wins[1]
mode = 'stop'
local local_wrap_wins = at_left()
nav.move_cursor('left')
local local_wraps = vim.api.nvim_get_current_win() == local_wrap_wins[2]
return { herdr_wrap_stays = herdr_wrap_stays, local_wraps = local_wraps, calls = calls }
end)
expect.equality(result, {
herdr_wrap_stays = true,
local_wraps = true,
calls = { 'edge:left', 'edge:right', 'focus:right', 'edge:left' },
})
end
T['command-line window delegates to Herdr at the screen edge and never wincmds'] = function()
local result = child.lua_func(function()
local calls = {}
local edge = false
local nav_mode = 'wrap'
package.loaded['herdr-splits.win'] = {
is_command_line_window = function() return true end,
is_floating = function() return false end,
is_embedded_floating_window = function() return false end,
is_ignored_or_preview = function() return false end,
dir_keys = { left = 'h', right = 'l', up = 'k', down = 'j' },
dir_keys_reverse = { left = 'l', right = 'h', up = 'j', down = 'k' },
reverse_direction = { left = 'right', right = 'left', up = 'down', down = 'up' },
}
package.loaded['herdr-splits.herdr'] = {
is_in_session = function() return true end,
current_pane_at_edge = function(direction)
calls[#calls + 1] = 'edge:' .. direction
return edge
end,
nav_at_edge = function() return nav_mode end,
focus_pane = function(direction)
calls[#calls + 1] = 'focus:' .. direction
return true
end,
}
package.loaded['herdr-splits.nav'] = nil
local nav = require('herdr-splits.nav')
-- single window: will_wrap is true in every direction, mirroring the
-- full-width/bottom command-line window geometry.
vim.cmd('only')
edge = false
nav.move_cursor('left')
local c1 = vim.deepcopy(calls); calls = {}
edge = true
nav_mode = 'wrap'
nav.move_cursor('right')
local c2 = vim.deepcopy(calls); calls = {}
nav_mode = 'stop'
nav.move_cursor('right')
local c3 = vim.deepcopy(calls)
return { neighbor = c1, wrap_reverse = c2, stop_noop = c3 }
end)
expect.equality(result, {
neighbor = { 'edge:left', 'focus:left' },
wrap_reverse = { 'edge:right', 'focus:left' },
stop_noop = { 'edge:right' },
})
end
T['command-line window no-ops silently when not at a screen edge'] = function()
local result = child.lua_func(function()
package.loaded['herdr-splits.win'] = {
is_command_line_window = function() return true end,
is_floating = function() return false end,
is_embedded_floating_window = function() return false end,
is_ignored_or_preview = function() return false end,
dir_keys = { left = 'h', right = 'l', up = 'k', down = 'j' },
dir_keys_reverse = { left = 'l', right = 'h', up = 'j', down = 'k' },
reverse_direction = { left = 'right', right = 'left', up = 'down', down = 'up' },
}
local focused = {}
package.loaded['herdr-splits.herdr'] = {
is_in_session = function() return true end,
current_pane_at_edge = function() return false end,
nav_at_edge = function() return 'wrap' end,
focus_pane = function(d) focused[#focused + 1] = d; return true end,
}
package.loaded['herdr-splits.nav'] = nil
local nav = require('herdr-splits.nav')
vim.o.splitright = true
vim.cmd('vsplit')
local wins = vim.api.nvim_tabpage_list_wins(0)
table.sort(wins, function(a, b)
return vim.api.nvim_win_get_position(a)[2] < vim.api.nvim_win_get_position(b)[2]
end)
-- left window: 'right' has a Neovim neighbor -> will_wrap == false
vim.api.nvim_set_current_win(wins[1])
nav.move_cursor('right')
return { stayed = vim.api.nvim_get_current_win() == wins[1], focused = focused }
end)
-- Without the guard, wincmd l would move to wins[2]; with it, silent no-op.
expect.equality(result, { stayed = true, focused = {} })
end
return T