aboutsummaryrefslogtreecommitdiff
path: root/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_nav.lua
diff options
context:
space:
mode:
authorPablu <me@pablu.de>2026-07-29 08:36:58 +0200
committerPablu <me@pablu.de>2026-07-29 08:36:58 +0200
commit75f2079ddf60f8901bd436e1cb908c189a8a9037 (patch)
tree114877450da032bddfc648a044a0af64e9b58449 /dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_nav.lua
parent3f66debcc79bd995797c029338de36a0b463f20f (diff)
Add jj and herdr
Diffstat (limited to 'dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_nav.lua')
-rw-r--r--dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_nav.lua520
1 files changed, 520 insertions, 0 deletions
diff --git a/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_nav.lua b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_nav.lua
new file mode 100644
index 0000000..f88a80d
--- /dev/null
+++ b/dot_config/herdr/plugins/github/herdr-splits-d880c4924f2b/tests/test_nav.lua
@@ -0,0 +1,520 @@
+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