mirror of
https://github.com/harivansh-afk/forge.nvim.git
synced 2026-04-15 07:04:47 +00:00
feat: tests and better vim validate
This commit is contained in:
parent
e7dd173bf4
commit
8899838e15
4 changed files with 451 additions and 3 deletions
9
.busted
Normal file
9
.busted
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
return {
|
||||||
|
_all = {
|
||||||
|
lua = 'nvim -l',
|
||||||
|
ROOT = { './spec/' },
|
||||||
|
},
|
||||||
|
default = {
|
||||||
|
verbose = true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,15 @@ dependencies = {
|
||||||
'lua >= 5.1',
|
'lua >= 5.1',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_dependencies = {
|
||||||
|
'nlua',
|
||||||
|
'busted >= 2.1.1',
|
||||||
|
}
|
||||||
|
|
||||||
|
test = {
|
||||||
|
type = 'busted',
|
||||||
|
}
|
||||||
|
|
||||||
build = {
|
build = {
|
||||||
type = 'builtin',
|
type = 'builtin',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -720,12 +720,87 @@ function M.config()
|
||||||
return v == false or type(v) == 'table'
|
return v == false or type(v) == 'table'
|
||||||
end, 'table or false')
|
end, 'table or false')
|
||||||
vim.validate('forge.display', cfg.display, 'table')
|
vim.validate('forge.display', cfg.display, 'table')
|
||||||
vim.validate('forge.display.icons', cfg.display.icons, 'table')
|
|
||||||
vim.validate('forge.display.widths', cfg.display.widths, 'table')
|
|
||||||
vim.validate('forge.display.limits', cfg.display.limits, 'table')
|
|
||||||
vim.validate('forge.ci', cfg.ci, 'table')
|
vim.validate('forge.ci', cfg.ci, 'table')
|
||||||
vim.validate('forge.ci.lines', cfg.ci.lines, 'number')
|
vim.validate('forge.ci.lines', cfg.ci.lines, 'number')
|
||||||
|
|
||||||
|
vim.validate('forge.display.icons', cfg.display.icons, 'table')
|
||||||
|
vim.validate('forge.display.icons.open', cfg.display.icons.open, 'string')
|
||||||
|
vim.validate('forge.display.icons.merged', cfg.display.icons.merged, 'string')
|
||||||
|
vim.validate('forge.display.icons.closed', cfg.display.icons.closed, 'string')
|
||||||
|
vim.validate('forge.display.icons.pass', cfg.display.icons.pass, 'string')
|
||||||
|
vim.validate('forge.display.icons.fail', cfg.display.icons.fail, 'string')
|
||||||
|
vim.validate('forge.display.icons.pending', cfg.display.icons.pending, 'string')
|
||||||
|
vim.validate('forge.display.icons.skip', cfg.display.icons.skip, 'string')
|
||||||
|
vim.validate('forge.display.icons.unknown', cfg.display.icons.unknown, 'string')
|
||||||
|
|
||||||
|
vim.validate('forge.display.widths', cfg.display.widths, 'table')
|
||||||
|
vim.validate('forge.display.widths.title', cfg.display.widths.title, 'number')
|
||||||
|
vim.validate('forge.display.widths.author', cfg.display.widths.author, 'number')
|
||||||
|
vim.validate('forge.display.widths.name', cfg.display.widths.name, 'number')
|
||||||
|
vim.validate('forge.display.widths.branch', cfg.display.widths.branch, 'number')
|
||||||
|
|
||||||
|
vim.validate('forge.display.limits', cfg.display.limits, 'table')
|
||||||
|
vim.validate('forge.display.limits.pulls', cfg.display.limits.pulls, 'number')
|
||||||
|
vim.validate('forge.display.limits.issues', cfg.display.limits.issues, 'number')
|
||||||
|
vim.validate('forge.display.limits.runs', cfg.display.limits.runs, 'number')
|
||||||
|
|
||||||
|
local key_or_false = function(v)
|
||||||
|
return v == false or type(v) == 'string'
|
||||||
|
end
|
||||||
|
if type(cfg.keys) == 'table' then
|
||||||
|
local keys = cfg.keys --[[@as forge.KeysConfig]]
|
||||||
|
if keys.pr ~= nil then
|
||||||
|
vim.validate('forge.keys.pr', keys.pr, 'table')
|
||||||
|
for _, k in ipairs({
|
||||||
|
'checkout',
|
||||||
|
'diff',
|
||||||
|
'worktree',
|
||||||
|
'ci',
|
||||||
|
'browse',
|
||||||
|
'manage',
|
||||||
|
'create',
|
||||||
|
'filter',
|
||||||
|
'refresh',
|
||||||
|
}) do
|
||||||
|
vim.validate('forge.keys.pr.' .. k, keys.pr[k], key_or_false, 'string or false')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if keys.issue ~= nil then
|
||||||
|
vim.validate('forge.keys.issue', keys.issue, 'table')
|
||||||
|
for _, k in ipairs({ 'browse', 'close', 'filter', 'refresh' }) do
|
||||||
|
vim.validate('forge.keys.issue.' .. k, keys.issue[k], key_or_false, 'string or false')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if keys.ci ~= nil then
|
||||||
|
vim.validate('forge.keys.ci', keys.ci, 'table')
|
||||||
|
for _, k in ipairs({ 'log', 'browse', 'failed', 'passed', 'running', 'all', 'refresh' }) do
|
||||||
|
vim.validate('forge.keys.ci.' .. k, keys.ci[k], key_or_false, 'string or false')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if keys.commits ~= nil then
|
||||||
|
vim.validate('forge.keys.commits', keys.commits, 'table')
|
||||||
|
for _, k in ipairs({ 'checkout', 'diff', 'browse', 'yank' }) do
|
||||||
|
vim.validate(
|
||||||
|
'forge.keys.commits.' .. k,
|
||||||
|
keys.commits[k],
|
||||||
|
key_or_false,
|
||||||
|
'string or false'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if keys.branches ~= nil then
|
||||||
|
vim.validate('forge.keys.branches', keys.branches, 'table')
|
||||||
|
for _, k in ipairs({ 'diff', 'browse' }) do
|
||||||
|
vim.validate(
|
||||||
|
'forge.keys.branches.' .. k,
|
||||||
|
keys.branches[k],
|
||||||
|
key_or_false,
|
||||||
|
'string or false'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
for name, source in pairs(cfg.sources) do
|
for name, source in pairs(cfg.sources) do
|
||||||
vim.validate('forge.sources.' .. name, source, 'table')
|
vim.validate('forge.sources.' .. name, source, 'table')
|
||||||
if source.hosts ~= nil then
|
if source.hosts ~= nil then
|
||||||
|
|
|
||||||
355
spec/init_spec.lua
Normal file
355
spec/init_spec.lua
Normal file
|
|
@ -0,0 +1,355 @@
|
||||||
|
vim.opt.runtimepath:prepend(vim.fn.getcwd())
|
||||||
|
|
||||||
|
package.preload['fzf-lua.utils'] = function()
|
||||||
|
return {
|
||||||
|
ansi_from_hl = function(_, text)
|
||||||
|
return text
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local forge = require('forge')
|
||||||
|
|
||||||
|
describe('config', function()
|
||||||
|
after_each(function()
|
||||||
|
vim.g.forge = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('returns defaults when vim.g.forge is nil', function()
|
||||||
|
vim.g.forge = nil
|
||||||
|
local cfg = forge.config()
|
||||||
|
assert.equals(10000, cfg.ci.lines)
|
||||||
|
assert.equals(45, cfg.display.widths.title)
|
||||||
|
assert.equals(100, cfg.display.limits.pulls)
|
||||||
|
assert.equals('+', cfg.display.icons.open)
|
||||||
|
assert.equals('<cr>', cfg.keys.pr.checkout)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('deep-merges partial user config', function()
|
||||||
|
vim.g.forge = { ci = { lines = 500 }, display = { icons = { open = '>' } } }
|
||||||
|
local cfg = forge.config()
|
||||||
|
assert.equals(500, cfg.ci.lines)
|
||||||
|
assert.equals('>', cfg.display.icons.open)
|
||||||
|
assert.equals('m', cfg.display.icons.merged)
|
||||||
|
assert.equals(45, cfg.display.widths.title)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('sets keys to false when user requests it', function()
|
||||||
|
vim.g.forge = { keys = false }
|
||||||
|
local cfg = forge.config()
|
||||||
|
assert.is_false(cfg.keys)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('format_pr', function()
|
||||||
|
local fields = {
|
||||||
|
number = 'number',
|
||||||
|
title = 'title',
|
||||||
|
state = 'state',
|
||||||
|
author = 'login',
|
||||||
|
created_at = 'created_at',
|
||||||
|
}
|
||||||
|
|
||||||
|
it('formats open PR with state icon', function()
|
||||||
|
local entry =
|
||||||
|
{ number = 42, title = 'fix bug', state = 'OPEN', login = 'alice', created_at = '' }
|
||||||
|
local result = forge.format_pr(entry, fields, true)
|
||||||
|
assert.truthy(result:find('+'))
|
||||||
|
assert.truthy(result:find('#42'))
|
||||||
|
assert.truthy(result:find('fix bug'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('formats merged PR', function()
|
||||||
|
local entry =
|
||||||
|
{ number = 7, title = 'add feature', state = 'MERGED', login = 'bob', created_at = '' }
|
||||||
|
local result = forge.format_pr(entry, fields, true)
|
||||||
|
assert.truthy(result:find('m'))
|
||||||
|
assert.truthy(result:find('#7'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('formats closed PR', function()
|
||||||
|
local entry = { number = 3, title = 'stale', state = 'CLOSED', login = 'eve', created_at = '' }
|
||||||
|
local result = forge.format_pr(entry, fields, true)
|
||||||
|
assert.truthy(result:find('x'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('omits state prefix when show_state is false', function()
|
||||||
|
local entry = { number = 1, title = 'no state', state = 'OPEN', login = 'dev', created_at = '' }
|
||||||
|
local result = forge.format_pr(entry, fields, false)
|
||||||
|
assert.truthy(result:find('#1'))
|
||||||
|
assert.falsy(result:match('^+'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('truncates long titles', function()
|
||||||
|
local long_title = string.rep('a', 100)
|
||||||
|
local entry = { number = 9, title = long_title, state = 'OPEN', login = 'x', created_at = '' }
|
||||||
|
local result = forge.format_pr(entry, fields, false)
|
||||||
|
assert.falsy(result:find(long_title))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('extracts author from table with login field', function()
|
||||||
|
local entry =
|
||||||
|
{ number = 5, title = 't', state = 'OPEN', login = { login = 'nested' }, created_at = '' }
|
||||||
|
local result = forge.format_pr(entry, fields, false)
|
||||||
|
assert.truthy(result:find('nested'))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('format_issue', function()
|
||||||
|
local fields = {
|
||||||
|
number = 'number',
|
||||||
|
title = 'title',
|
||||||
|
state = 'state',
|
||||||
|
author = 'author',
|
||||||
|
created_at = 'created_at',
|
||||||
|
}
|
||||||
|
|
||||||
|
it('formats open issue', function()
|
||||||
|
local entry =
|
||||||
|
{ number = 10, title = 'bug report', state = 'open', author = 'alice', created_at = '' }
|
||||||
|
local result = forge.format_issue(entry, fields, true)
|
||||||
|
assert.truthy(result:find('+'))
|
||||||
|
assert.truthy(result:find('#10'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('formats closed issue', function()
|
||||||
|
local entry = { number = 11, title = 'done', state = 'closed', author = 'bob', created_at = '' }
|
||||||
|
local result = forge.format_issue(entry, fields, true)
|
||||||
|
assert.truthy(result:find('x'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('handles opened state (GitLab)', function()
|
||||||
|
local entry =
|
||||||
|
{ number = 12, title = 'mr issue', state = 'opened', author = 'c', created_at = '' }
|
||||||
|
local result = forge.format_issue(entry, fields, true)
|
||||||
|
assert.truthy(result:find('+'))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('format_check', function()
|
||||||
|
it('maps pass bucket', function()
|
||||||
|
local result = forge.format_check({ name = 'lint', bucket = 'pass' })
|
||||||
|
assert.truthy(result:find('%*'))
|
||||||
|
assert.truthy(result:find('lint'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('maps fail bucket', function()
|
||||||
|
local result = forge.format_check({ name = 'build', bucket = 'fail' })
|
||||||
|
assert.truthy(result:find('x'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('maps pending bucket', function()
|
||||||
|
local result = forge.format_check({ name = 'test', bucket = 'pending' })
|
||||||
|
assert.truthy(result:find('~'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('maps skipping bucket', function()
|
||||||
|
local result = forge.format_check({ name = 'optional', bucket = 'skipping' })
|
||||||
|
assert.truthy(result:find('%-'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('maps cancel bucket', function()
|
||||||
|
local result = forge.format_check({ name = 'cancelled', bucket = 'cancel' })
|
||||||
|
assert.truthy(result:find('%-'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('maps unknown bucket', function()
|
||||||
|
local result = forge.format_check({ name = 'mystery', bucket = 'something_else' })
|
||||||
|
assert.truthy(result:find('%?'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('defaults to pending when bucket is nil', function()
|
||||||
|
local result = forge.format_check({ name = 'none' })
|
||||||
|
assert.truthy(result:find('~'))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('format_run', function()
|
||||||
|
it('formats successful run with branch', function()
|
||||||
|
local run =
|
||||||
|
{ name = 'CI', branch = 'main', status = 'success', event = 'push', created_at = '' }
|
||||||
|
local result = forge.format_run(run)
|
||||||
|
assert.truthy(result:find('%*'))
|
||||||
|
assert.truthy(result:find('CI'))
|
||||||
|
assert.truthy(result:find('main'))
|
||||||
|
assert.truthy(result:find('push'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('formats failed run without branch', function()
|
||||||
|
local run = {
|
||||||
|
name = 'Deploy',
|
||||||
|
branch = '',
|
||||||
|
status = 'failure',
|
||||||
|
event = 'workflow_dispatch',
|
||||||
|
created_at = '',
|
||||||
|
}
|
||||||
|
local result = forge.format_run(run)
|
||||||
|
assert.truthy(result:find('x'))
|
||||||
|
assert.truthy(result:find('manual'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('maps in_progress status', function()
|
||||||
|
local run =
|
||||||
|
{ name = 'Test', branch = '', status = 'in_progress', event = 'push', created_at = '' }
|
||||||
|
local result = forge.format_run(run)
|
||||||
|
assert.truthy(result:find('~'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('maps cancelled status', function()
|
||||||
|
local run = { name = 'Old', branch = '', status = 'cancelled', event = 'push', created_at = '' }
|
||||||
|
local result = forge.format_run(run)
|
||||||
|
assert.truthy(result:find('%-'))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('filter_checks', function()
|
||||||
|
local checks = {
|
||||||
|
{ name = 'a', bucket = 'pass' },
|
||||||
|
{ name = 'b', bucket = 'fail' },
|
||||||
|
{ name = 'c', bucket = 'pending' },
|
||||||
|
{ name = 'd', bucket = 'skipping' },
|
||||||
|
}
|
||||||
|
|
||||||
|
it('returns all checks sorted by severity when filter is nil', function()
|
||||||
|
local result = forge.filter_checks(vim.deepcopy(checks), nil)
|
||||||
|
assert.equals(4, #result)
|
||||||
|
assert.equals('b', result[1].name)
|
||||||
|
assert.equals('c', result[2].name)
|
||||||
|
assert.equals('a', result[3].name)
|
||||||
|
assert.equals('d', result[4].name)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('returns all checks when filter is "all"', function()
|
||||||
|
local result = forge.filter_checks(vim.deepcopy(checks), 'all')
|
||||||
|
assert.equals(4, #result)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('filters to specific bucket', function()
|
||||||
|
local result = forge.filter_checks(vim.deepcopy(checks), 'fail')
|
||||||
|
assert.equals(1, #result)
|
||||||
|
assert.equals('b', result[1].name)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('returns empty when no matches', function()
|
||||||
|
local result = forge.filter_checks(vim.deepcopy(checks), 'cancel')
|
||||||
|
assert.equals(0, #result)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('relative_time via format_pr', function()
|
||||||
|
local fields = { number = 'n', title = 't', state = 's', author = 'a', created_at = 'ts' }
|
||||||
|
|
||||||
|
it('shows minutes for recent timestamps', function()
|
||||||
|
local ts = os.date('%Y-%m-%dT%H:%M:%SZ', os.time() - 120)
|
||||||
|
local entry = { n = 1, t = 'x', s = 'open', a = 'u', ts = ts }
|
||||||
|
local result = forge.format_pr(entry, fields, false)
|
||||||
|
assert.truthy(result:match('%d+m'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('shows hours', function()
|
||||||
|
local ts = os.date('%Y-%m-%dT%H:%M:%SZ', os.time() - 7200)
|
||||||
|
local entry = { n = 1, t = 'x', s = 'open', a = 'u', ts = ts }
|
||||||
|
local result = forge.format_pr(entry, fields, false)
|
||||||
|
assert.truthy(result:match('%d+h'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('shows days', function()
|
||||||
|
local ts = os.date('%Y-%m-%dT%H:%M:%SZ', os.time() - 172800)
|
||||||
|
local entry = { n = 1, t = 'x', s = 'open', a = 'u', ts = ts }
|
||||||
|
local result = forge.format_pr(entry, fields, false)
|
||||||
|
assert.truthy(result:match('%d+d'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('returns empty for nil timestamp', function()
|
||||||
|
local entry = { n = 1, t = 'x', s = 'open', a = 'u', ts = nil }
|
||||||
|
local result = forge.format_pr(entry, fields, false)
|
||||||
|
assert.truthy(result)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('returns empty for empty string timestamp', function()
|
||||||
|
local entry = { n = 1, t = 'x', s = 'open', a = 'u', ts = '' }
|
||||||
|
local result = forge.format_pr(entry, fields, false)
|
||||||
|
assert.truthy(result)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('returns empty for garbage timestamp', function()
|
||||||
|
local entry = { n = 1, t = 'x', s = 'open', a = 'u', ts = 'not-a-date' }
|
||||||
|
local result = forge.format_pr(entry, fields, false)
|
||||||
|
assert.truthy(result)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('config validation', function()
|
||||||
|
after_each(function()
|
||||||
|
vim.g.forge = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('rejects non-table sources', function()
|
||||||
|
vim.g.forge = { sources = 'bad' }
|
||||||
|
assert.has_error(function()
|
||||||
|
forge.config()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('rejects non-table display', function()
|
||||||
|
vim.g.forge = { display = 42 }
|
||||||
|
assert.has_error(function()
|
||||||
|
forge.config()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('rejects non-number ci.lines', function()
|
||||||
|
vim.g.forge = { ci = { lines = 'many' } }
|
||||||
|
assert.has_error(function()
|
||||||
|
forge.config()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('rejects non-string icon', function()
|
||||||
|
vim.g.forge = { display = { icons = { open = 123 } } }
|
||||||
|
assert.has_error(function()
|
||||||
|
forge.config()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('rejects non-number width', function()
|
||||||
|
vim.g.forge = { display = { widths = { title = 'wide' } } }
|
||||||
|
assert.has_error(function()
|
||||||
|
forge.config()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('rejects non-number limit', function()
|
||||||
|
vim.g.forge = { display = { limits = { pulls = true } } }
|
||||||
|
assert.has_error(function()
|
||||||
|
forge.config()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('rejects non-string key binding', function()
|
||||||
|
vim.g.forge = { keys = { pr = { checkout = 42 } } }
|
||||||
|
assert.has_error(function()
|
||||||
|
forge.config()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('accepts false for individual key binding', function()
|
||||||
|
vim.g.forge = { keys = { pr = { checkout = false } } }
|
||||||
|
local cfg = forge.config()
|
||||||
|
assert.is_false(cfg.keys.pr.checkout)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('rejects keys as a string', function()
|
||||||
|
vim.g.forge = { keys = 'none' }
|
||||||
|
assert.has_error(function()
|
||||||
|
forge.config()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('rejects non-table source hosts', function()
|
||||||
|
vim.g.forge = { sources = { custom = { hosts = 99 } } }
|
||||||
|
assert.has_error(function()
|
||||||
|
forge.config()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue