gitsigns.nvim/test/gs_helpers.lua
2024-07-03 11:50:20 +01:00

361 lines
8.6 KiB
Lua

local helpers = require('nvim-test.helpers')
local timeout = 2000
local M = helpers
local exec_lua = helpers.exec_lua
local matches = helpers.matches
local eq = helpers.eq
local buf_get_var = helpers.api.nvim_buf_get_var
local system = helpers.fn.system
M.scratch = os.getenv('PJ_ROOT') .. '/scratch'
M.gitdir = M.scratch .. '/.git'
M.test_file = M.scratch .. '/dummy.txt'
M.newfile = M.scratch .. '/newfile.txt'
M.test_config = {
debug_mode = true,
_test_mode = true,
signs = {
add = { text = '+' },
delete = { text = '_' },
change = { text = '~' },
topdelete = { text = '^' },
changedelete = { text = '%' },
untracked = { text = '#' },
},
on_attach = {
{ 'n', 'mhs', '<cmd>lua require"gitsigns".stage_hunk()<CR>' },
{ 'n', 'mhu', '<cmd>lua require"gitsigns".undo_stage_hunk()<CR>' },
{ 'n', 'mhr', '<cmd>lua require"gitsigns".reset_hunk()<CR>' },
{ 'n', 'mhp', '<cmd>lua require"gitsigns".preview_hunk()<CR>' },
{ 'n', 'mhS', '<cmd>lua require"gitsigns".stage_buffer()<CR>' },
{ 'n', 'mhU', '<cmd>lua require"gitsigns".reset_buffer_index()<CR>' },
},
attach_to_untracked = true,
update_debounce = 5,
}
local test_file_text = {
'This',
'is',
'a',
'file',
'used',
'for',
'testing',
'gitsigns.',
'The',
'content',
"doesn't",
'matter,',
'it',
'just',
'needs',
'to',
'be',
'static.',
}
--- Run a git command
local function git(args)
system({ 'git', '-C', M.scratch, unpack(args) })
end
--- Run a git command and add a delay
function M.git(args)
git(args)
-- helpers.sleep(10)
end
function M.cleanup()
system({ 'rm', '-rf', M.scratch })
end
function M.git_init()
git({ 'init', '-b', 'master' })
-- Always force color to test settings don't interfere with gitsigns systems
-- commands (addresses #23)
git({ 'config', 'color.branch', 'always' })
git({ 'config', 'color.ui', 'always' })
git({ 'config', 'color.diff', 'always' })
git({ 'config', 'color.interactive', 'always' })
git({ 'config', 'color.status', 'always' })
git({ 'config', 'color.grep', 'always' })
git({ 'config', 'color.pager', 'true' })
git({ 'config', 'color.decorate', 'always' })
git({ 'config', 'color.showbranch', 'always' })
git({ 'config', 'merge.conflictStyle', 'merge' })
git({ 'config', 'user.email', 'tester@com.com' })
git({ 'config', 'user.name', 'tester' })
git({ 'config', 'init.defaultBranch', 'master' })
end
--- Setup a basic git repository in directory `helpers.scratch` with a single file
--- `helpers.test_file` committed.
--- @param opts? {test_file_text?: string[], no_add?: boolean}
function M.setup_test_repo(opts)
local text = opts and opts.test_file_text or test_file_text
M.cleanup()
system({ 'mkdir', M.scratch })
M.git_init()
system({ 'touch', M.test_file })
M.write_to_file(M.test_file, text)
if not (opts and opts.no_add) then
git({ 'add', M.test_file })
git({ 'commit', '-m', 'init commit' })
end
-- helpers.sleep(20)
end
--- @param cond fun()
--- @param interval? integer
function M.expectf(cond, interval)
local duration = 0
interval = interval or 1
while duration < timeout do
local ok, ret = pcall(cond)
if ok and (ret == nil or ret == true) then
return
end
duration = duration + interval
helpers.sleep(interval)
interval = interval * 2
end
cond()
end
function M.edit(path)
helpers.api.nvim_command('edit ' .. path)
end
--- @param path string
--- @param text string[]
function M.write_to_file(path, text)
local f = assert(io.open(path, 'wb'))
for _, l in ipairs(text) do
f:write(l)
f:write('\n')
end
f:close()
end
--- @param line string
--- @param spec string|{next:boolean, pattern:boolean, text:string}
--- @return boolean
local function match_spec_elem(line, spec)
if spec.pattern then
if line:match(spec.text) then
return true
end
elseif spec.next then
-- local matcher = spec.pattern and matches or eq
-- matcher(spec.text, line)
if spec.pattern then
matches(spec.text, line)
else
eq(spec.text, line)
end
return true
end
return spec == line
end
--- Match lines in spec. Not all lines have to match
--- @param lines string[]
--- @param spec table<integer, (string|{next:boolean, pattern:boolean, text:string})?>
function M.match_lines(lines, spec)
local i = 1
for _, line in ipairs(lines) do
local s = spec[i]
if line ~= '' and s and match_spec_elem(line, s) then
i = i + 1
end
end
if i < #spec + 1 then
local unmatched_msg = table.concat(
vim.tbl_map(function(v)
return string.format(' - %s', v.text or v)
end, spec),
'\n'
)
local lines_msg = table.concat(
vim.tbl_map(function(v)
return string.format(' - %s', v)
end, lines),
'\n'
)
error(('Did not match patterns:\n%s\nwith:\n%s'):format(unmatched_msg, lines_msg))
end
end
function M.p(str)
return { text = str, pattern = true }
end
function M.n(str)
return { text = str, next = true }
end
function M.np(str)
return { text = str, pattern = true, next = true }
end
--- @return string[]
function M.debug_messages()
--- @type string[]
local r = exec_lua("return require'gitsigns.debug.log'.get()")
for i, line in ipairs(r) do
-- Remove leading timestamp
r[i] = line:gsub('^[0-9.]+ D ', '')
end
return r
end
--- Like match_debug_messages but elements in spec are unordered
--- @param spec table<integer, (string|{next:boolean, pattern:boolean, text:string})?>
function M.match_dag(spec)
M.expectf(function()
local messages = M.debug_messages()
for _, s in ipairs(spec) do
M.match_lines(messages, { s })
end
end)
end
--- @param spec table<integer, (string|{next:boolean, pattern:boolean, text:string})?>
function M.match_debug_messages(spec)
M.expectf(function()
M.match_lines(M.debug_messages(), spec)
end)
end
function M.setup_gitsigns(config, on_attach)
exec_lua(
[[
local config, on_attach = ...
if config and config.on_attach then
local maps = config.on_attach
config.on_attach = function(bufnr)
for _, map in ipairs(maps) do
vim.keymap.set(map[1], map[2], map[3], {buffer = bufnr})
end
end
end
if on_attach then
config.on_attach = function()
return false
end
end
require('gitsigns').setup(config)
]],
config,
on_attach
)
end
--- @param bufnr integer
--- @param x string
--- @return any
local function buf_var(bufnr, x)
return exec_lua(
[[
local bufnr, x = ...
return vim.b[bufnr][x]
]],
bufnr,
x
)
end
--- @param status table<string,string|integer>
--- @param bufnr integer
local function check_status(status, bufnr)
if next(status) == nil then
eq(vim.NIL, buf_var(bufnr, 'gitsigns_head'), 'b:gitsigns_head is unexpectedly set')
eq(
vim.NIL,
buf_var(bufnr, 'gitsigns_status_dict'),
'b:gitsigns_status_dict is unexpectedly set'
)
return
end
eq(status.head, buf_get_var(bufnr, 'gitsigns_head'), 'b:gitsigns_head does not match')
--- @type table<string,string|integer>
local bstatus = buf_get_var(bufnr, 'gitsigns_status_dict')
for _, i in ipairs({ 'added', 'changed', 'removed', 'head' }) do
eq(status[i], bstatus[i], string.format("status['%s'] did not match gitsigns_status_dict", i))
end
-- Catch any extra keys
for i, v in pairs(status) do
eq(v, bstatus[i], string.format("status['%s'] did not match gitsigns_status_dict", i))
end
end
--- @param signs table<string,integer>
--- @param bufnr integer
local function check_signs(signs, bufnr)
local buf_signs = {} --- @type string[]
local buf_marks = helpers.api.nvim_buf_get_extmarks(bufnr, -1, 0, -1, { details = true })
for _, s in ipairs(buf_marks) do
buf_signs[#buf_signs + 1] = s[4].sign_hl_group
end
--- @type table<string,integer>
local act = {}
for _, name in ipairs(buf_signs) do
for t, hl in pairs({
added = 'GitSignsAdd',
changed = 'GitSignsChange',
delete = 'GitSignsDelete',
changedelete = 'GitSignsChangedelete',
topdelete = 'GitSignsTopdelete',
untracked = 'GitSignsUntracked',
}) do
if name == hl then
act[t] = (act[t] or 0) + 1
end
end
end
eq(signs, act, vim.inspect(buf_signs))
end
--- @param attrs {signs:table<string,integer>,status:table<string,string|integer>}
--- @param bufnr? integer
function M.check(attrs, bufnr)
bufnr = bufnr or 0
if not attrs then
return
end
local status = attrs.status
local signs = attrs.signs
M.expectf(function()
if status then
check_status(status, bufnr)
end
if signs then
check_signs(signs, bufnr)
end
end)
end
return M