local h = require("test.git.helpers") local t = require("test") ---Replicate the user's global cursor-restore autocmd. Scoped to a ---named augroup + cleanup so it doesn't leak between tests. local function install_cursor_restore_autocmd() local group = vim.api.nvim_create_augroup("test.cursor_restore", { clear = true }) vim.api.nvim_create_autocmd("BufReadPost", { group = group, pattern = "*", command = 'silent! normal! g`"zv', }) t.defer(function() pcall(vim.api.nvim_del_augroup_by_name, "test.cursor_restore") end) end ---@param sidebar_buf integer ---@param needle string ---@return integer? local function find_line(sidebar_buf, needle) for i, l in ipairs(vim.api.nvim_buf_get_lines(sidebar_buf, 0, -1, false)) do if l:match(needle) then return i end end end ---Find the gitstatus sidebar window in the current tabpage. ---@return integer? sidebar_buf ---@return integer? sidebar_win local function find_sidebar() for _, w in ipairs(vim.api.nvim_tabpage_list_wins(0)) do local b = vim.api.nvim_win_get_buf(w) if vim.bo[b].filetype == "gitstatus" then return b, w end end end ---Find a diff-role window in the given tabpage (or current). ---@param role "left"|"right" ---@param tab integer? ---@return integer? local function find_diff_win(role, tab) for _, w in ipairs(vim.api.nvim_tabpage_list_wins(tab or 0)) do if vim.w[w].git_diff_role == role then return w end end end ---@param file_path string ---@param committed_content string ---@param worktree_content string ---@return integer sidebar_win ---@return integer entry_line local function setup_sidebar_with_unstaged_file( file_path, committed_content, worktree_content ) local repo = h.make_repo({ [file_path] = committed_content }) t.write(repo, file_path, worktree_content) vim.cmd("cd " .. repo) require("git.status_view").open({ placement = "sidebar" }) local sidebar_buf, sidebar_win = find_sidebar() assert(sidebar_buf, "sidebar buffer should exist") assert(sidebar_win, "sidebar window should exist") local r = assert( require("git.core.repo").find(vim.fn.getcwd()), "repo should resolve for the test worktree" ) r:refresh() t.wait_for(function() return r.status and #r.status:rows("unstaged") > 0 end, "git status to report unstaged changes") local entry_line = assert( find_line(sidebar_buf, vim.pesc(file_path) .. "$"), file_path .. " should appear in sidebar" ) return sidebar_win, entry_line end t.test("stage with diff open: sidebar cursor stays put", function() install_cursor_restore_autocmd() local sidebar_win, line = setup_sidebar_with_unstaged_file( "zsh/rc", "ZSH=true\n", "ZSH=true\nmodified\n" ) vim.api.nvim_set_current_win(sidebar_win) vim.api.nvim_win_set_cursor(sidebar_win, { line, 0 }) t.press("") t.wait_for(function() return find_diff_win("left") ~= nil end, "diff windows to appear") local r = assert(require("git.core.repo").find(vim.fn.getcwd())) vim.api.nvim_set_current_win(sidebar_win) t.press("s") t.wait_for(function() return #r.status:rows("staged") > 0 end, "stage to propagate to repo state") t.eq( vim.api.nvim_win_get_cursor(sidebar_win), { line, 0 }, "sidebar cursor should remain at the entry's original line" ) end) t.test( "stage with diff open: diff foldmethod is preserved on refresh", function() local sidebar_win, line = setup_sidebar_with_unstaged_file( "zsh/rc", "# vim: set ft=zsh nowrap:\nZSH=true\n", "# vim: set ft=zsh nowrap:\nZSH=true\nmodified\n" ) vim.api.nvim_set_current_win(sidebar_win) vim.api.nvim_win_set_cursor(sidebar_win, { line, 0 }) t.press("") t.wait_for(function() return find_diff_win("left") ~= nil end, "diff windows to appear") local left_win = assert(find_diff_win("left")) t.eq( vim.wo[left_win].foldmethod, "diff", "left diff foldmethod should be 'diff' after Tab" ) local r = assert(require("git.core.repo").find(vim.fn.getcwd())) vim.api.nvim_set_current_win(sidebar_win) t.press("s") t.wait_for(function() return #r.status:rows("staged") > 0 end, "stage to propagate to repo state") t.eq( vim.wo[left_win].foldmethod, "diff", "left diff foldmethod should still be 'diff' after stage refresh" ) end ) t.test( " in a second tabpage opens the diff inside that tabpage", function() local sidebar_win, line = setup_sidebar_with_unstaged_file("foo.txt", "v1\n", "v2\n") local tab1 = vim.api.nvim_get_current_tabpage() -- First show diff in tab1, so state.diff_*_win point at tab1. vim.api.nvim_set_current_win(sidebar_win) vim.api.nvim_win_set_cursor(sidebar_win, { line, 0 }) t.press("") t.wait_for(function() return find_diff_win("left", tab1) ~= nil end, "diff windows in tab1 to appear") vim.cmd("tabnew") require("git.status_view").open({ placement = "sidebar" }) local tab2 = vim.api.nvim_get_current_tabpage() t.truthy(tab2 ~= tab1, "tabnew should produce a distinct tabpage") local _, sidebar_win2 = find_sidebar() assert(sidebar_win2, "sidebar window should exist in tab2") vim.api.nvim_set_current_win(sidebar_win2) vim.api.nvim_win_set_cursor(sidebar_win2, { line, 0 }) t.press("") t.wait_for(function() return find_diff_win("left", tab2) ~= nil end, "diff windows in tab2 to appear") t.truthy( find_diff_win("right", tab2), "right diff window should be in tab2" ) end ) t.test("refresh on stage updates the index URI buffer's content", function() local sidebar_win, line = setup_sidebar_with_unstaged_file("foo.txt", "v1\n", "v2\n") vim.api.nvim_set_current_win(sidebar_win) vim.api.nvim_win_set_cursor(sidebar_win, { line, 0 }) t.press("") t.wait_for(function() return find_diff_win("left") ~= nil end, "diff windows to appear") local left_win = assert(find_diff_win("left")) local index_buf = vim.api.nvim_win_get_buf(left_win) t.eq( vim.api.nvim_buf_get_lines(index_buf, 0, -1, false), { "v1" }, "index pane should initially show committed content" ) vim.api.nvim_set_current_win(sidebar_win) t.press("s") t.wait_for(function() local first = vim.api.nvim_buf_get_lines(index_buf, 0, -1, false)[1] return first == "v2" end, "index pane to refresh to staged content") t.eq( vim.api.nvim_buf_get_lines(index_buf, 0, -1, false), { "v2" }, "index pane should reflect staged content after refresh" ) end)