mirror of
https://github.com/neovim/neovim.git
synced 2025-09-05 19:08:15 +00:00

Problem: The load function in opts was difficult to use if you wished to customize based on the plugin being loaded. You could get the name, but without some way to mark a spec, that was of limited usefulness unless you wanted to hardcode a list of names in the function, or write a wrapper around the whole thing Solution: Allow users to provide an arbitrary data field in plugin specs so that they may receive info as to how to handle that plugin in load, get() and events, and act upon it Co-authored-by: BirdeeHub <birdee@localhost> Co-authored-by: Evgeni Chasnovski <evgeni.chasnovski@gmail.com>
1315 lines
50 KiB
Lua
1315 lines
50 KiB
Lua
local t = require('test.testutil')
|
|
local n = require('test.functional.testnvim')()
|
|
local Screen = require('test.functional.ui.screen')
|
|
local skip_integ = os.getenv('NVIM_TEST_INTEG') ~= '1'
|
|
|
|
local api = n.api
|
|
local fn = n.fn
|
|
|
|
local eq = t.eq
|
|
local matches = t.matches
|
|
local pcall_err = t.pcall_err
|
|
local exec_lua = n.exec_lua
|
|
|
|
-- Helpers ====================================================================
|
|
-- Installed plugins ----------------------------------------------------------
|
|
|
|
local function pack_get_dir()
|
|
return vim.fs.joinpath(fn.stdpath('data'), 'site', 'pack', 'core', 'opt')
|
|
end
|
|
|
|
local function pack_get_plug_path(plug_name)
|
|
return vim.fs.joinpath(pack_get_dir(), plug_name)
|
|
end
|
|
|
|
local function pack_exists(plug_name)
|
|
local path = vim.fs.joinpath(pack_get_dir(), plug_name)
|
|
return vim.uv.fs_stat(path) ~= nil
|
|
end
|
|
|
|
-- Test repos (to be installed) -----------------------------------------------
|
|
|
|
local repos_dir = vim.fs.abspath('test/functional/lua/pack-test-repos')
|
|
|
|
--- Map from repo name to its proper `src` used in plugin spec
|
|
--- @type table<string,string>
|
|
local repos_src = {}
|
|
|
|
local function repo_get_path(repo_name)
|
|
vim.validate('repo_name', repo_name, 'string')
|
|
return vim.fs.joinpath(repos_dir, repo_name)
|
|
end
|
|
|
|
local function repo_write_file(repo_name, rel_path, text, no_dedent, append)
|
|
local path = vim.fs.joinpath(repo_get_path(repo_name), rel_path)
|
|
fn.mkdir(vim.fs.dirname(path), 'p')
|
|
t.write_file(path, text, no_dedent, append)
|
|
end
|
|
|
|
--- @return vim.SystemCompleted
|
|
local function system_sync(cmd, opts)
|
|
return exec_lua(function()
|
|
local obj = vim.system(cmd, opts)
|
|
|
|
if opts and opts.timeout then
|
|
-- Minor delay before calling wait() so the timeout uv timer can have a headstart over the
|
|
-- internal call to vim.wait() in wait().
|
|
vim.wait(10)
|
|
end
|
|
|
|
local res = obj:wait()
|
|
|
|
-- Check the process is no longer running
|
|
assert(not vim.api.nvim_get_proc(obj.pid), 'process still exists')
|
|
|
|
return res
|
|
end)
|
|
end
|
|
|
|
local function git_cmd(cmd, repo_name)
|
|
local git_cmd_prefix = {
|
|
'git',
|
|
'-c',
|
|
'gc.auto=0',
|
|
'-c',
|
|
'user.name=Marvim',
|
|
'-c',
|
|
'user.email=marvim@neovim.io',
|
|
'-c',
|
|
'init.defaultBranch=main',
|
|
}
|
|
|
|
cmd = vim.list_extend(git_cmd_prefix, cmd)
|
|
local cwd = repo_get_path(repo_name)
|
|
local sys_opts = { cwd = cwd, text = true, clear_env = true }
|
|
local out = system_sync(cmd, sys_opts)
|
|
if out.code ~= 0 then
|
|
error(out.stderr)
|
|
end
|
|
return (out.stdout:gsub('\n+$', ''))
|
|
end
|
|
|
|
local function init_test_repo(repo_name)
|
|
local path = repo_get_path(repo_name)
|
|
fn.mkdir(path, 'p')
|
|
repos_src[repo_name] = 'file://' .. path
|
|
|
|
git_cmd({ 'init' }, repo_name)
|
|
end
|
|
|
|
local function git_add_commit(msg, repo_name)
|
|
git_cmd({ 'add', '*' }, repo_name)
|
|
git_cmd({ 'commit', '-m', msg }, repo_name)
|
|
end
|
|
|
|
local function git_get_hash(rev, repo_name)
|
|
return git_cmd({ 'rev-list', '-1', '--abbrev-commit', rev }, repo_name)
|
|
end
|
|
|
|
-- Common test repos ----------------------------------------------------------
|
|
--- @type table<string,function>
|
|
local repos_setup = {}
|
|
|
|
function repos_setup.basic()
|
|
init_test_repo('basic')
|
|
|
|
repo_write_file('basic', 'lua/basic.lua', 'return "basic init"')
|
|
git_add_commit('Initial commit for "basic"', 'basic')
|
|
repo_write_file('basic', 'lua/basic.lua', 'return "basic main"')
|
|
git_add_commit('Commit in `main` but not in `feat-branch`', 'basic')
|
|
|
|
git_cmd({ 'checkout', 'main~' }, 'basic')
|
|
git_cmd({ 'checkout', '-b', 'feat-branch' }, 'basic')
|
|
|
|
repo_write_file('basic', 'lua/basic.lua', 'return "basic some-tag"')
|
|
git_add_commit('Add commit for some tag', 'basic')
|
|
git_cmd({ 'tag', 'some-tag' }, 'basic')
|
|
|
|
repo_write_file('basic', 'lua/basic.lua', 'return "basic feat-branch"')
|
|
git_add_commit('Add important feature', 'basic')
|
|
|
|
-- Make sure that `main` is the default remote branch
|
|
git_cmd({ 'checkout', 'main' }, 'basic')
|
|
end
|
|
|
|
function repos_setup.plugindirs()
|
|
init_test_repo('plugindirs')
|
|
|
|
repo_write_file('plugindirs', 'lua/plugindirs.lua', 'return "plugindirs main"')
|
|
repo_write_file('plugindirs', 'plugin/dirs.lua', 'vim.g._plugin = true')
|
|
repo_write_file('plugindirs', 'plugin/dirs_log.lua', '_G.DL = _G.DL or {}; DL[#DL+1] = "p"')
|
|
repo_write_file('plugindirs', 'plugin/dirs.vim', 'let g:_plugin_vim=v:true')
|
|
repo_write_file('plugindirs', 'plugin/sub/dirs.lua', 'vim.g._plugin_sub = true')
|
|
repo_write_file('plugindirs', 'plugin/bad % name.lua', 'vim.g._plugin_bad = true')
|
|
repo_write_file('plugindirs', 'after/plugin/dirs.lua', 'vim.g._after_plugin = true')
|
|
repo_write_file('plugindirs', 'after/plugin/dirs_log.lua', '_G.DL = _G.DL or {}; DL[#DL+1] = "a"')
|
|
repo_write_file('plugindirs', 'after/plugin/dirs.vim', 'let g:_after_plugin_vim=v:true')
|
|
repo_write_file('plugindirs', 'after/plugin/sub/dirs.lua', 'vim.g._after_plugin_sub = true')
|
|
repo_write_file('plugindirs', 'after/plugin/bad % name.lua', 'vim.g._after_plugin_bad = true')
|
|
git_add_commit('Initial commit for "plugindirs"', 'plugindirs')
|
|
end
|
|
|
|
function repos_setup.helptags()
|
|
init_test_repo('helptags')
|
|
repo_write_file('helptags', 'lua/helptags.lua', 'return "helptags main"')
|
|
repo_write_file('helptags', 'doc/my-test-help.txt', '*my-test-help*')
|
|
repo_write_file('helptags', 'doc/bad % name.txt', '*my-test-help-bad*')
|
|
repo_write_file('helptags', 'doc/bad % dir/file.txt', '*my-test-help-sub-bad*')
|
|
git_add_commit('Initial commit for "helptags"', 'helptags')
|
|
end
|
|
|
|
function repos_setup.pluginerr()
|
|
init_test_repo('pluginerr')
|
|
|
|
repo_write_file('pluginerr', 'lua/pluginerr.lua', 'return "pluginerr main"')
|
|
repo_write_file('pluginerr', 'plugin/err.lua', 'error("Wow, an error")')
|
|
git_add_commit('Initial commit for "pluginerr"', 'pluginerr')
|
|
end
|
|
|
|
function repos_setup.defbranch()
|
|
init_test_repo('defbranch')
|
|
|
|
repo_write_file('defbranch', 'lua/defbranch.lua', 'return "defbranch main"')
|
|
git_add_commit('Initial commit for "defbranch"', 'defbranch')
|
|
|
|
-- Make `dev` the default remote branch
|
|
git_cmd({ 'checkout', '-b', 'dev' }, 'defbranch')
|
|
|
|
repo_write_file('defbranch', 'lua/defbranch.lua', 'return "defbranch dev"')
|
|
git_add_commit('Add to new default branch', 'defbranch')
|
|
end
|
|
|
|
function repos_setup.gitsuffix()
|
|
init_test_repo('gitsuffix.git')
|
|
|
|
repo_write_file('gitsuffix.git', 'lua/gitsuffix.lua', 'return "gitsuffix main"')
|
|
git_add_commit('Initial commit for "gitsuffix"', 'gitsuffix.git')
|
|
end
|
|
|
|
function repos_setup.semver()
|
|
init_test_repo('semver')
|
|
|
|
local add_tag = function(name)
|
|
repo_write_file('semver', 'lua/semver.lua', 'return "semver ' .. name .. '"')
|
|
git_add_commit('Add version ' .. name, 'semver')
|
|
git_cmd({ 'tag', name }, 'semver')
|
|
end
|
|
|
|
add_tag('v0.0.1')
|
|
add_tag('v0.0.2')
|
|
add_tag('v0.1.0')
|
|
add_tag('v0.1.1')
|
|
add_tag('v0.2.0-dev')
|
|
add_tag('v0.2.0')
|
|
add_tag('v0.3.0')
|
|
repo_write_file('semver', 'lua/semver.lua', 'return "semver middle-commit')
|
|
git_add_commit('Add middle commit', 'semver')
|
|
add_tag('0.3.1')
|
|
add_tag('v0.4')
|
|
add_tag('non-semver')
|
|
add_tag('v0.2.1') -- Intentionally add version not in order
|
|
add_tag('v1.0.0')
|
|
end
|
|
|
|
-- Utility --------------------------------------------------------------------
|
|
|
|
local function watch_events(event)
|
|
exec_lua(function()
|
|
_G.event_log = _G.event_log or {} --- @type table[]
|
|
vim.api.nvim_create_autocmd(event, {
|
|
callback = function(ev)
|
|
table.insert(_G.event_log, { event = ev.event, match = ev.match, data = ev.data })
|
|
end,
|
|
})
|
|
end)
|
|
end
|
|
|
|
--- @param log table[]
|
|
local function find_in_log(log, event, kind, repo_name, version)
|
|
local path = pack_get_plug_path(repo_name)
|
|
local spec = { name = repo_name, src = repos_src[repo_name], version = version }
|
|
local data = { kind = kind, path = path, spec = spec }
|
|
local entry = { event = event, match = vim.fs.abspath(path), data = data }
|
|
|
|
local res = 0
|
|
for i, tbl in ipairs(log) do
|
|
if vim.deep_equal(tbl, entry) then
|
|
res = i
|
|
break
|
|
end
|
|
end
|
|
eq(true, res > 0)
|
|
|
|
return res
|
|
end
|
|
|
|
local function validate_progress_report(title, step_names)
|
|
-- NOTE: Assumes that message history contains only progress report messages
|
|
local messages = vim.split(n.exec_capture('messages'), '\n')
|
|
local n_steps = #step_names
|
|
eq(n_steps + 2, #messages)
|
|
|
|
local init_msg = ('vim.pack: 0%% %s (0/%d)'):format(title, n_steps)
|
|
eq(init_msg, messages[1])
|
|
|
|
local steps_seen = {} --- @type table<string,boolean>
|
|
for i = 1, n_steps do
|
|
local percent = math.floor(100 * i / n_steps)
|
|
local msg = ('vim.pack: %3d%% %s (%d/%d)'):format(percent, title, i, n_steps)
|
|
-- NOTE: There is no guaranteed order (as it is async), so check that some
|
|
-- expected step name is used
|
|
local pattern = '^' .. vim.pesc(msg) .. ' %- (%S+)$'
|
|
local step = messages[i + 1]:match(pattern)
|
|
eq(true, vim.tbl_contains(step_names, step))
|
|
steps_seen[step] = true
|
|
end
|
|
|
|
-- Should report all steps
|
|
eq(n_steps, vim.tbl_count(steps_seen))
|
|
|
|
local final_msg = ('vim.pack: done %s (%d/%d)'):format(title, n_steps, n_steps)
|
|
eq(final_msg, messages[n_steps + 2])
|
|
end
|
|
|
|
local function is_jit()
|
|
return exec_lua('return package.loaded.jit ~= nil')
|
|
end
|
|
|
|
-- Tests ======================================================================
|
|
|
|
describe('vim.pack', function()
|
|
setup(function()
|
|
n.clear()
|
|
for _, r_setup in pairs(repos_setup) do
|
|
r_setup()
|
|
end
|
|
end)
|
|
|
|
before_each(function()
|
|
n.clear()
|
|
end)
|
|
|
|
after_each(function()
|
|
vim.fs.rm(pack_get_dir(), { force = true, recursive = true })
|
|
end)
|
|
|
|
teardown(function()
|
|
vim.fs.rm(repos_dir, { force = true, recursive = true })
|
|
end)
|
|
|
|
describe('add()', function()
|
|
it('installs only once', function()
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.basic })
|
|
end)
|
|
n.clear()
|
|
|
|
watch_events({ 'PackChanged' })
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.basic })
|
|
end)
|
|
eq(exec_lua('return #_G.event_log'), 0)
|
|
end)
|
|
|
|
it('passes data field through to opts.load', function()
|
|
eq(
|
|
2,
|
|
exec_lua(function()
|
|
local successes = 0
|
|
vim.pack.add({
|
|
{ name = 'tabletest', src = repos_src.basic, data = { test = 'value' } },
|
|
{ name = 'stringtest', src = repos_src.basic, data = 'value' },
|
|
}, {
|
|
confirm = false,
|
|
load = function(p)
|
|
if p.spec.name == 'tabletest' then
|
|
if p.spec.data.test == 'value' then
|
|
successes = successes + 1
|
|
end
|
|
end
|
|
if p.spec.name == 'stringtest' then
|
|
if p.spec.data == 'value' then
|
|
successes = successes + 1
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
return successes
|
|
end)
|
|
)
|
|
end)
|
|
|
|
it('asks for installation confirmation', function()
|
|
exec_lua(function()
|
|
---@diagnostic disable-next-line: duplicate-set-field
|
|
vim.fn.confirm = function(...)
|
|
_G.confirm_args = { ... }
|
|
-- Do not confirm installation to see what happens
|
|
return 0
|
|
end
|
|
end)
|
|
|
|
local err = pcall_err(exec_lua, function()
|
|
vim.pack.add({ repos_src.basic })
|
|
end)
|
|
|
|
matches('`basic`:\nInstallation was not confirmed', err)
|
|
eq(false, exec_lua('return pcall(require, "basic")'))
|
|
|
|
local confirm_msg = 'These plugins will be installed:\n\n' .. repos_src.basic .. '\n'
|
|
eq({ confirm_msg, 'Proceed? &Yes\n&No', 1, 'Question' }, exec_lua('return _G.confirm_args'))
|
|
end)
|
|
|
|
it('respects `opts.confirm`', function()
|
|
exec_lua(function()
|
|
_G.confirm_used = false
|
|
---@diagnostic disable-next-line: duplicate-set-field
|
|
vim.fn.confirm = function()
|
|
_G.confirm_used = true
|
|
return 1
|
|
end
|
|
|
|
vim.pack.add({ repos_src.basic }, { confirm = false })
|
|
end)
|
|
|
|
eq(false, exec_lua('return _G.confirm_used'))
|
|
eq('basic main', exec_lua('return require("basic")'))
|
|
end)
|
|
|
|
it('installs at proper version', function()
|
|
local out = exec_lua(function()
|
|
vim.pack.add({
|
|
{ src = repos_src.basic, version = 'feat-branch' },
|
|
})
|
|
-- Should have plugin available immediately after
|
|
return require('basic')
|
|
end)
|
|
|
|
eq('basic feat-branch', out)
|
|
|
|
local rtp = vim.tbl_map(t.fix_slashes, api.nvim_list_runtime_paths())
|
|
local plug_path = pack_get_plug_path('basic')
|
|
local after_dir = vim.fs.joinpath(plug_path, 'after')
|
|
eq(true, vim.tbl_contains(rtp, plug_path))
|
|
-- No 'after/' directory in runtimepath because it is not present in plugin
|
|
eq(false, vim.tbl_contains(rtp, after_dir))
|
|
end)
|
|
|
|
it('can install from the Internet', function()
|
|
t.skip(skip_integ, 'NVIM_TEST_INTEG not set: skipping network integration test')
|
|
exec_lua(function()
|
|
vim.pack.add({ 'https://github.com/neovim/nvim-lspconfig' })
|
|
end)
|
|
eq(true, exec_lua('return pcall(require, "lspconfig")'))
|
|
end)
|
|
|
|
describe('startup', function()
|
|
local init_lua = ''
|
|
before_each(function()
|
|
init_lua = vim.fs.joinpath(fn.stdpath('config'), 'init.lua')
|
|
fn.mkdir(vim.fs.dirname(init_lua), 'p')
|
|
end)
|
|
after_each(function()
|
|
pcall(vim.fs.rm, init_lua, { force = true })
|
|
end)
|
|
|
|
it('works in init.lua', function()
|
|
local pack_add_cmd = ('vim.pack.add({ %s })'):format(vim.inspect(repos_src.plugindirs))
|
|
fn.writefile({ pack_add_cmd, '_G.done = true' }, init_lua)
|
|
|
|
local validate_loaded = function()
|
|
eq('plugindirs main', exec_lua('return require("plugindirs")'))
|
|
|
|
-- Should source 'plugin/' and 'after/plugin/' exactly once
|
|
eq({ true, true }, n.exec_lua('return { vim.g._plugin, vim.g._after_plugin }'))
|
|
eq({ 'p', 'a' }, n.exec_lua('return _G.DL'))
|
|
end
|
|
|
|
-- Should auto-install but wait before executing code after it
|
|
n.clear({ args_rm = { '-u' } })
|
|
n.exec_lua('vim.wait(500, function() return _G.done end, 50)')
|
|
validate_loaded()
|
|
|
|
-- Should only `:packadd!` already installed plugin
|
|
n.clear({ args_rm = { '-u' } })
|
|
validate_loaded()
|
|
|
|
-- Should not load plugins if `--noplugin`, only adjust 'runtimepath'
|
|
n.clear({ args = { '--noplugin' }, args_rm = { '-u' } })
|
|
eq('plugindirs main', exec_lua('return require("plugindirs")'))
|
|
eq({}, n.exec_lua('return { vim.g._plugin, vim.g._after_plugin }'))
|
|
eq(vim.NIL, n.exec_lua('return _G.DL'))
|
|
end)
|
|
end)
|
|
|
|
it('shows progress report during installation', function()
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.basic, repos_src.defbranch })
|
|
end)
|
|
validate_progress_report('Installing plugins', { 'basic', 'defbranch' })
|
|
end)
|
|
|
|
it('triggers relevant events', function()
|
|
watch_events({ 'PackChangedPre', 'PackChanged' })
|
|
|
|
exec_lua(function()
|
|
-- Should provide event-data respecting manual and inferred default `version`
|
|
vim.pack.add({ { src = repos_src.basic, version = 'feat-branch' }, repos_src.defbranch })
|
|
end)
|
|
|
|
local log = exec_lua('return _G.event_log')
|
|
local installpre_basic = find_in_log(log, 'PackChangedPre', 'install', 'basic', 'feat-branch')
|
|
local installpre_defbranch = find_in_log(log, 'PackChangedPre', 'install', 'defbranch', nil)
|
|
local updatepre_basic = find_in_log(log, 'PackChangedPre', 'update', 'basic', 'feat-branch')
|
|
local updatepre_defbranch = find_in_log(log, 'PackChangedPre', 'update', 'defbranch', 'dev')
|
|
local update_basic = find_in_log(log, 'PackChanged', 'update', 'basic', 'feat-branch')
|
|
local update_defbranch = find_in_log(log, 'PackChanged', 'update', 'defbranch', 'dev')
|
|
local install_basic = find_in_log(log, 'PackChanged', 'install', 'basic', 'feat-branch')
|
|
local install_defbranch = find_in_log(log, 'PackChanged', 'install', 'defbranch', 'dev')
|
|
eq(8, #log)
|
|
|
|
-- NOTE: There is no guaranteed installation order among separate plugins (as it is async)
|
|
eq(true, installpre_basic < updatepre_basic)
|
|
eq(true, updatepre_basic < update_basic)
|
|
-- NOTE: "Install" is after "update" to indicate installation at correct version
|
|
eq(true, update_basic < install_basic)
|
|
|
|
eq(true, installpre_defbranch < updatepre_defbranch)
|
|
eq(true, updatepre_defbranch < update_defbranch)
|
|
eq(true, update_defbranch < install_defbranch)
|
|
end)
|
|
|
|
it('recognizes several `version` types', function()
|
|
local prev_commit = git_get_hash('HEAD~', 'defbranch')
|
|
exec_lua(function()
|
|
vim.pack.add({
|
|
{ src = repos_src.basic, version = 'some-tag' }, -- Tag
|
|
{ src = repos_src.defbranch, version = prev_commit }, -- Commit hash
|
|
{ src = repos_src.semver, version = vim.version.range('<1') }, -- Semver constraint
|
|
})
|
|
end)
|
|
|
|
eq('basic some-tag', exec_lua('return require("basic")'))
|
|
eq('defbranch main', exec_lua('return require("defbranch")'))
|
|
eq('semver v0.4', exec_lua('return require("semver")'))
|
|
end)
|
|
|
|
it('respects plugin/ and after/plugin/ scripts', function()
|
|
local function validate(load, ref)
|
|
local opts = { load = load }
|
|
local out = exec_lua(function()
|
|
-- Should handle bad plugin directory name
|
|
vim.pack.add({ { src = repos_src.plugindirs, name = 'plugin % dirs' } }, opts)
|
|
return {
|
|
vim.g._plugin,
|
|
vim.g._plugin_vim,
|
|
vim.g._plugin_sub,
|
|
vim.g._plugin_bad,
|
|
vim.g._after_plugin,
|
|
vim.g._after_plugin_vim,
|
|
vim.g._after_plugin_sub,
|
|
vim.g._after_plugin_bad,
|
|
}
|
|
end)
|
|
|
|
eq(ref, out)
|
|
|
|
-- Should add necessary directories to runtimepath regardless of `opts.load`
|
|
local rtp = vim.tbl_map(t.fix_slashes, api.nvim_list_runtime_paths())
|
|
local plug_path = pack_get_plug_path('plugin % dirs')
|
|
local after_dir = vim.fs.joinpath(plug_path, 'after')
|
|
eq(true, vim.tbl_contains(rtp, plug_path))
|
|
eq(true, vim.tbl_contains(rtp, after_dir))
|
|
end
|
|
|
|
validate(nil, { true, true, true, true, true, true, true, true })
|
|
|
|
n.clear()
|
|
validate(false, {})
|
|
end)
|
|
|
|
it('can use function `opts.load`', function()
|
|
local validate = function()
|
|
n.exec_lua(function()
|
|
_G.load_log = {}
|
|
local load = function(...)
|
|
table.insert(_G.load_log, { ... })
|
|
end
|
|
vim.pack.add({ repos_src.plugindirs, repos_src.basic }, { load = load })
|
|
end)
|
|
|
|
-- Order of execution should be the same as supplied in `add()`
|
|
local plugindirs_data = {
|
|
spec = { src = repos_src.plugindirs, name = 'plugindirs' },
|
|
path = pack_get_plug_path('plugindirs'),
|
|
}
|
|
local basic_data = {
|
|
spec = { src = repos_src.basic, name = 'basic' },
|
|
path = pack_get_plug_path('basic'),
|
|
}
|
|
-- - Only single table argument should be supplied to `load`
|
|
local ref_log = { { plugindirs_data }, { basic_data } }
|
|
eq(ref_log, n.exec_lua('return _G.load_log'))
|
|
|
|
-- Should not add plugin to the session in any way
|
|
eq(false, exec_lua('return pcall(require, "plugindirs")'))
|
|
eq(false, exec_lua('return pcall(require, "basic")'))
|
|
|
|
-- Should not source 'plugin/'
|
|
eq({}, n.exec_lua('return { vim.g._plugin, vim.g._after_plugin }'))
|
|
|
|
-- Plugins should still be marked as "active", since they were added
|
|
plugindirs_data.spec.version = 'main'
|
|
plugindirs_data.active = true
|
|
basic_data.spec.version = 'main'
|
|
basic_data.active = true
|
|
eq({ plugindirs_data, basic_data }, n.exec_lua('return vim.pack.get()'))
|
|
end
|
|
|
|
-- Works on initial install
|
|
validate()
|
|
|
|
-- Works when loading already installed plugin
|
|
n.clear()
|
|
validate()
|
|
end)
|
|
|
|
it('generates help tags', function()
|
|
exec_lua(function()
|
|
vim.pack.add({ { src = repos_src.helptags, name = 'help tags' } })
|
|
end)
|
|
local target_tags = fn.getcompletion('my-test', 'help')
|
|
table.sort(target_tags)
|
|
eq({ 'my-test-help', 'my-test-help-bad', 'my-test-help-sub-bad' }, target_tags)
|
|
end)
|
|
|
|
it('reports install/load errors after loading all input', function()
|
|
t.skip(not is_jit(), "Non LuaJIT reports errors differently due to 'coxpcall'")
|
|
local validate = function(err_pat)
|
|
local err = pcall_err(exec_lua, function()
|
|
vim.pack.add({
|
|
{ src = repos_src.basic, version = 'wrong-version' }, -- Error during initial checkout
|
|
{ src = repos_src.semver, version = vim.version.range('>=2.0.0') }, -- Missing version
|
|
{ src = repos_src.plugindirs, version = 'main' },
|
|
{ src = repos_src.pluginerr, version = 'main' }, -- Error during 'plugin/' source
|
|
})
|
|
end)
|
|
|
|
matches(err_pat, err)
|
|
|
|
-- Should have processed non-errored 'plugin/' and add to 'rtp'
|
|
eq('plugindirs main', exec_lua('return require("plugindirs")'))
|
|
eq(true, exec_lua('return vim.g._plugin'))
|
|
|
|
-- Should add plugin to 'rtp' even if 'plugin/' has error
|
|
eq('pluginerr main', exec_lua('return require("pluginerr")'))
|
|
end
|
|
|
|
-- During initial install
|
|
local err_pat_parts = {
|
|
'vim%.pack',
|
|
'`basic`:\n',
|
|
-- Should report available branches and tags if revision is absent
|
|
'`wrong%-version`',
|
|
'Available:\nTags: some%-tag\nBranches: feat%-branch, main',
|
|
-- Should report available branches and versions if no constraint match
|
|
'`semver`',
|
|
'Available:\nVersions: v1%.0%.0, v0%.4, 0%.3%.1, v0%.3%.0.*\nBranches: main\n',
|
|
'`pluginerr`:\n',
|
|
'Wow, an error',
|
|
}
|
|
validate(table.concat(err_pat_parts, '.*'))
|
|
|
|
-- During loading already installed plugin.
|
|
n.clear()
|
|
-- NOTE: There is no error for wrong `version`, because there is no check
|
|
-- for already installed plugins. Might change in the future.
|
|
validate('vim%.pack.*`pluginerr`:\n.*Wow, an error')
|
|
end)
|
|
|
|
it('normalizes each spec', function()
|
|
exec_lua(function()
|
|
vim.pack.add({
|
|
repos_src.basic, -- String should be inferred as `{ src = ... }`
|
|
{ src = repos_src.defbranch }, -- Default `version` is remote's default branch
|
|
{ src = repos_src['gitsuffix.git'] }, -- Default `name` comes from `src` repo name
|
|
{ src = repos_src.plugindirs, name = 'plugin/dirs' }, -- Ensure proper directory name
|
|
})
|
|
end)
|
|
|
|
eq('basic main', exec_lua('return require("basic")'))
|
|
eq('defbranch dev', exec_lua('return require("defbranch")'))
|
|
eq('gitsuffix main', exec_lua('return require("gitsuffix")'))
|
|
eq(true, exec_lua('return vim.g._plugin'))
|
|
|
|
eq(true, pack_exists('gitsuffix'))
|
|
eq(true, pack_exists('dirs'))
|
|
end)
|
|
|
|
it('handles problematic names', function()
|
|
exec_lua(function()
|
|
vim.pack.add({ { src = repos_src.basic, name = 'bad % name' } })
|
|
end)
|
|
eq('basic main', exec_lua('return require("basic")'))
|
|
end)
|
|
|
|
it('validates input', function()
|
|
local validate = function(err_pat, input)
|
|
local add_input = function()
|
|
vim.pack.add(input)
|
|
end
|
|
matches(err_pat, pcall_err(exec_lua, add_input))
|
|
end
|
|
|
|
-- Separate spec entries
|
|
validate('list', repos_src.basic)
|
|
validate('spec:.*table', { 1 })
|
|
validate('spec%.src:.*string', { { src = 1 } })
|
|
validate('spec%.src:.*non%-empty string', { { src = '' } })
|
|
validate('spec%.name:.*string', { { src = repos_src.basic, name = 1 } })
|
|
validate('spec%.name:.*non%-empty string', { { src = repos_src.basic, name = '' } })
|
|
validate(
|
|
'spec%.version:.*string or vim%.VersionRange',
|
|
{ { src = repos_src.basic, version = 1 } }
|
|
)
|
|
|
|
-- Conflicts in input array
|
|
local version_conflict = {
|
|
{ src = repos_src.basic, version = 'feat-branch' },
|
|
{ src = repos_src.basic, version = 'main' },
|
|
}
|
|
validate('Conflicting `version` for `basic`.*feat%-branch.*main', version_conflict)
|
|
|
|
local src_conflict = {
|
|
{ src = repos_src.basic, name = 'my-plugin' },
|
|
{ src = repos_src.semver, name = 'my-plugin' },
|
|
}
|
|
validate('Conflicting `src` for `my%-plugin`.*basic.*semver', src_conflict)
|
|
end)
|
|
end)
|
|
|
|
describe('update()', function()
|
|
-- Lua source code for the tested plugin named "fetch"
|
|
local fetch_lua_file = vim.fs.joinpath(pack_get_plug_path('fetch'), 'lua', 'fetch.lua')
|
|
-- Table with hashes used to test confirmation buffer and log content
|
|
local hashes --- @type table<string,string>
|
|
|
|
before_each(function()
|
|
-- Create a dedicated clean repo for which "push changes" will be mocked
|
|
init_test_repo('fetch')
|
|
|
|
repo_write_file('fetch', 'lua/fetch.lua', 'return "fetch init"')
|
|
git_add_commit('Initial commit for "fetch"', 'fetch')
|
|
|
|
repo_write_file('fetch', 'lua/fetch.lua', 'return "fetch main"')
|
|
git_add_commit('Commit from `main` to be removed', 'fetch')
|
|
|
|
hashes = { fetch_head = git_get_hash('HEAD', 'fetch') }
|
|
|
|
-- Install initial versions of tested plugins
|
|
exec_lua(function()
|
|
vim.pack.add({
|
|
repos_src.fetch,
|
|
{ src = repos_src.semver, version = 'v0.3.0' },
|
|
repos_src.defbranch,
|
|
})
|
|
end)
|
|
n.clear()
|
|
|
|
-- Mock remote repo update
|
|
-- - Force push
|
|
repo_write_file('fetch', 'lua/fetch.lua', 'return "fetch new"')
|
|
git_cmd({ 'add', '*' }, 'fetch')
|
|
git_cmd({ 'commit', '--amend', '-m', 'Commit to be added 1' }, 'fetch')
|
|
|
|
-- - Presence of a tag (should be shown in changelog)
|
|
git_cmd({ 'tag', 'dev-tag' }, 'fetch')
|
|
|
|
repo_write_file('fetch', 'lua/fetch.lua', 'return "fetch new 2"')
|
|
git_add_commit('Commit to be added 2', 'fetch')
|
|
end)
|
|
|
|
after_each(function()
|
|
pcall(vim.fs.rm, repo_get_path('fetch'), { force = true, recursive = true })
|
|
local log_path = vim.fs.joinpath(fn.stdpath('log'), 'nvim-pack.log')
|
|
pcall(vim.fs.rm, log_path, { force = true })
|
|
end)
|
|
|
|
describe('confirmation buffer', function()
|
|
it('works', function()
|
|
exec_lua(function()
|
|
vim.pack.add({
|
|
repos_src.fetch,
|
|
{ src = repos_src.semver, version = 'v0.3.0' },
|
|
{ src = repos_src.defbranch, version = 'does-not-exist' },
|
|
})
|
|
end)
|
|
eq({ 'return "fetch main"' }, fn.readfile(fetch_lua_file))
|
|
|
|
exec_lua(function()
|
|
-- Enable highlighting of special filetype
|
|
vim.cmd('filetype plugin on')
|
|
vim.pack.update()
|
|
end)
|
|
|
|
-- Buffer should be special and shown in a separate tabpage
|
|
eq(2, #api.nvim_list_tabpages())
|
|
eq(2, fn.tabpagenr())
|
|
eq(api.nvim_get_option_value('filetype', {}), 'nvim-pack')
|
|
eq(api.nvim_get_option_value('modifiable', {}), false)
|
|
eq(api.nvim_get_option_value('buftype', {}), 'acwrite')
|
|
local confirm_bufnr = api.nvim_get_current_buf()
|
|
local confirm_winnr = api.nvim_get_current_win()
|
|
local confirm_tabpage = api.nvim_get_current_tabpage()
|
|
eq(api.nvim_buf_get_name(0), 'nvim-pack://' .. confirm_bufnr .. '/confirm-update')
|
|
|
|
-- Adjust lines for a more robust screenshot testing
|
|
local fetch_src = repos_src.fetch
|
|
local fetch_path = pack_get_plug_path('fetch')
|
|
local semver_src = repos_src.semver
|
|
local semver_path = pack_get_plug_path('semver')
|
|
|
|
exec_lua(function()
|
|
-- Replace matches in line to preserve extmark highlighting
|
|
local function replace_in_line(i, pattern, repl)
|
|
local line = vim.api.nvim_buf_get_lines(0, i - 1, i, false)[1]
|
|
local from, to = line:find(pattern)
|
|
while from and to do
|
|
vim.api.nvim_buf_set_text(0, i - 1, from - 1, i - 1, to, { repl })
|
|
line = vim.api.nvim_buf_get_lines(0, i - 1, i, false)[1]
|
|
from, to = line:find(pattern)
|
|
end
|
|
end
|
|
|
|
vim.bo.modifiable = true
|
|
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
|
local pack_runtime = vim.fs.joinpath(vim.env.VIMRUNTIME, 'lua', 'vim', 'pack.lua')
|
|
-- NOTE: replace path to `vim.pack` in error traceback accounting for
|
|
-- possibly different slashes on Windows
|
|
local pack_runtime_pattern = vim.pesc(pack_runtime):gsub('/', '[\\/]') .. ':%d+'
|
|
for i = 1, #lines do
|
|
replace_in_line(i, pack_runtime_pattern, 'VIM_PACK_RUNTIME')
|
|
replace_in_line(i, vim.pesc(fetch_path), 'FETCH_PATH')
|
|
replace_in_line(i, vim.pesc(fetch_src), 'FETCH_SRC')
|
|
replace_in_line(i, vim.pesc(semver_path), 'SEMVER_PATH')
|
|
replace_in_line(i, vim.pesc(semver_src), 'SEMVER_SRC')
|
|
end
|
|
vim.bo.modified = false
|
|
vim.bo.modifiable = false
|
|
end)
|
|
|
|
-- Use screenshot to test highlighting, otherwise prefer text matching.
|
|
-- This requires computing target hashes on each test run because they
|
|
-- change due to source repos being cleanly created on each file test.
|
|
local screen
|
|
screen = Screen.new(85, 35)
|
|
|
|
hashes.fetch_new = git_get_hash('HEAD', 'fetch')
|
|
hashes.fetch_new_prev = git_get_hash('HEAD~', 'fetch')
|
|
hashes.semver_head = git_get_hash('v0.3.0', 'semver')
|
|
|
|
local tab_name = 'n' .. (t.is_os('win') and ':' or '') .. '//2/confirm-update'
|
|
|
|
local screen_lines = {
|
|
('{24: [No Name] }{5: %s }{2:%s }{24:X}|'):format(
|
|
tab_name,
|
|
t.is_os('win') and '' or ' '
|
|
),
|
|
'{19:^# Error ────────────────────────────────────────────────────────────────────────} |',
|
|
' |',
|
|
'{19:## defbranch} |',
|
|
' |',
|
|
' VIM_PACK_RUNTIME: `does-not-exist` is not a branch/tag/commit. Available: |',
|
|
' Tags: |',
|
|
' Branches: dev, main |',
|
|
' |',
|
|
'{101:# Update ───────────────────────────────────────────────────────────────────────} |',
|
|
' |',
|
|
'{101:## fetch} |',
|
|
'Path: {103:FETCH_PATH} |',
|
|
'Source: {103:FETCH_SRC} |',
|
|
('State before: {103:%s} |'):format(
|
|
hashes.fetch_head
|
|
),
|
|
('State after: {103:%s} {102:(main)} |'):format(
|
|
hashes.fetch_new
|
|
),
|
|
' |',
|
|
'Pending updates: |',
|
|
('{104:< %s │ Commit from `main` to be removed} |'):format(
|
|
hashes.fetch_head
|
|
),
|
|
('{105:> %s │ Commit to be added 2} |'):format(
|
|
hashes.fetch_new
|
|
),
|
|
('{105:> %s │ Commit to be added 1 (tag: dev-tag)} |'):format(
|
|
hashes.fetch_new_prev
|
|
),
|
|
' |',
|
|
'{102:# Same ─────────────────────────────────────────────────────────────────────────} |',
|
|
' |',
|
|
'{102:## semver} |',
|
|
'Path: {103:SEMVER_PATH} |',
|
|
'Source: {103:SEMVER_SRC} |',
|
|
('State: {103:%s} {102:(v0.3.0)} |'):format(
|
|
hashes.semver_head
|
|
),
|
|
' |',
|
|
'Available newer versions: |',
|
|
'• {102:v1.0.0} |',
|
|
'• {102:v0.4} |',
|
|
'• {102:0.3.1} |',
|
|
'{1:~ }|',
|
|
' |',
|
|
}
|
|
|
|
screen:add_extra_attr_ids({
|
|
[101] = { foreground = Screen.colors.Orange },
|
|
[102] = { foreground = Screen.colors.LightGray },
|
|
[103] = { foreground = Screen.colors.LightBlue },
|
|
[104] = { foreground = Screen.colors.NvimDarkRed },
|
|
[105] = { foreground = Screen.colors.NvimDarkGreen },
|
|
})
|
|
-- NOTE: Non LuaJIT reports errors differently due to 'coxpcall'
|
|
if is_jit() then
|
|
screen:expect(table.concat(screen_lines, '\n'))
|
|
end
|
|
|
|
-- `:write` should confirm
|
|
n.exec('write')
|
|
|
|
-- - Apply changes immediately
|
|
eq({ 'return "fetch new 2"' }, fn.readfile(fetch_lua_file))
|
|
|
|
-- - Clean up buffer+window+tabpage
|
|
eq(false, api.nvim_buf_is_valid(confirm_bufnr))
|
|
eq(false, api.nvim_win_is_valid(confirm_winnr))
|
|
eq(false, api.nvim_tabpage_is_valid(confirm_tabpage))
|
|
|
|
-- - Write to log file
|
|
local log_path = vim.fs.joinpath(fn.stdpath('log'), 'nvim-pack.log')
|
|
local log_lines = fn.readfile(log_path)
|
|
matches('========== Update %d%d%d%d%-%d%d%-%d%d %d%d:%d%d:%d%d ==========', log_lines[1])
|
|
local ref_log_lines = {
|
|
'# Update ───────────────────────────────────────────────────────────────────────',
|
|
'',
|
|
'## fetch',
|
|
'Path: ' .. fetch_path,
|
|
'Source: ' .. fetch_src,
|
|
'State before: ' .. hashes.fetch_head,
|
|
'State after: ' .. hashes.fetch_new .. ' (main)',
|
|
'',
|
|
'Pending updates:',
|
|
'< ' .. hashes.fetch_head .. ' │ Commit from `main` to be removed',
|
|
'> ' .. hashes.fetch_new .. ' │ Commit to be added 2',
|
|
'> ' .. hashes.fetch_new_prev .. ' │ Commit to be added 1 (tag: dev-tag)',
|
|
'',
|
|
}
|
|
eq(ref_log_lines, vim.list_slice(log_lines, 2))
|
|
end)
|
|
|
|
it('can be dismissed with `:quit`', function()
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.fetch })
|
|
vim.pack.update({ 'fetch' })
|
|
end)
|
|
eq('nvim-pack', api.nvim_get_option_value('filetype', {}))
|
|
|
|
-- Should not apply updates
|
|
n.exec('quit')
|
|
eq({ 'return "fetch main"' }, fn.readfile(fetch_lua_file))
|
|
end)
|
|
|
|
it('closes full tabpage', function()
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.fetch })
|
|
vim.pack.update()
|
|
end)
|
|
|
|
-- Confirm with `:write`
|
|
local confirm_tabpage = api.nvim_get_current_tabpage()
|
|
n.exec('-tab split other-tab')
|
|
local other_tabpage = api.nvim_get_current_tabpage()
|
|
n.exec('tabnext')
|
|
n.exec('write')
|
|
eq(true, api.nvim_tabpage_is_valid(other_tabpage))
|
|
eq(false, api.nvim_tabpage_is_valid(confirm_tabpage))
|
|
|
|
-- Not confirm with `:quit`
|
|
n.exec('tab split other-tab-2')
|
|
local other_tabpage_2 = api.nvim_get_current_tabpage()
|
|
exec_lua(function()
|
|
vim.pack.update()
|
|
end)
|
|
confirm_tabpage = api.nvim_get_current_tabpage()
|
|
|
|
-- - Temporary split window in tabpage should not matter
|
|
n.exec('vsplit other-buf')
|
|
n.exec('wincmd w')
|
|
|
|
n.exec('tabclose ' .. api.nvim_tabpage_get_number(other_tabpage_2))
|
|
eq(confirm_tabpage, api.nvim_get_current_tabpage())
|
|
n.exec('quit')
|
|
eq(false, api.nvim_tabpage_is_valid(confirm_tabpage))
|
|
end)
|
|
|
|
it('has in-process LSP features', function()
|
|
t.skip(not is_jit(), "Non LuaJIT reports errors differently due to 'coxpcall'")
|
|
exec_lua(function()
|
|
vim.pack.add({
|
|
repos_src.fetch,
|
|
{ src = repos_src.semver, version = 'v0.3.0' },
|
|
{ src = repos_src.defbranch, version = 'does-not-exist' },
|
|
})
|
|
vim.pack.update()
|
|
end)
|
|
|
|
eq(1, exec_lua('return #vim.lsp.get_clients({ bufnr = 0 })'))
|
|
|
|
-- textDocument/documentSymbol
|
|
exec_lua('vim.lsp.buf.document_symbol()')
|
|
local loclist = vim.tbl_map(function(x) --- @param x table
|
|
return {
|
|
lnum = x.lnum, --- @type integer
|
|
col = x.col, --- @type integer
|
|
end_lnum = x.end_lnum, --- @type integer
|
|
end_col = x.end_col, --- @type integer
|
|
text = x.text, --- @type string
|
|
}
|
|
end, fn.getloclist(0))
|
|
local ref_loclist = {
|
|
{ lnum = 1, col = 1, end_lnum = 9, end_col = 1, text = '[Namespace] Error' },
|
|
{ lnum = 3, col = 1, end_lnum = 9, end_col = 1, text = '[Module] defbranch' },
|
|
{ lnum = 9, col = 1, end_lnum = 22, end_col = 1, text = '[Namespace] Update' },
|
|
{ lnum = 11, col = 1, end_lnum = 22, end_col = 1, text = '[Module] fetch' },
|
|
{ lnum = 22, col = 1, end_lnum = 32, end_col = 1, text = '[Namespace] Same' },
|
|
{ lnum = 24, col = 1, end_lnum = 32, end_col = 1, text = '[Module] semver' },
|
|
}
|
|
eq(ref_loclist, loclist)
|
|
|
|
n.exec('lclose')
|
|
|
|
-- textDocument/hover
|
|
local confirm_winnr = api.nvim_get_current_win()
|
|
local validate_hover = function(pos, commit_msg)
|
|
api.nvim_win_set_cursor(0, pos)
|
|
exec_lua(function()
|
|
vim.lsp.buf.hover()
|
|
-- Default hover is async shown in floating window
|
|
vim.wait(1000, function()
|
|
return #vim.api.nvim_tabpage_list_wins(0) > 1
|
|
end)
|
|
end)
|
|
|
|
local all_wins = api.nvim_tabpage_list_wins(0)
|
|
eq(2, #all_wins)
|
|
local float_winnr = all_wins[1] == confirm_winnr and all_wins[2] or all_wins[1]
|
|
eq(true, api.nvim_win_get_config(float_winnr).relative ~= '')
|
|
|
|
local float_buf = api.nvim_win_get_buf(float_winnr)
|
|
local text = table.concat(api.nvim_buf_get_lines(float_buf, 0, -1, false), '\n')
|
|
|
|
local ref_pattern = 'Marvim <marvim@neovim%.io>\nDate:.*' .. vim.pesc(commit_msg)
|
|
matches(ref_pattern, text)
|
|
end
|
|
|
|
validate_hover({ 14, 0 }, 'Commit from `main` to be removed')
|
|
validate_hover({ 15, 0 }, 'Commit to be added 2')
|
|
validate_hover({ 18, 0 }, 'Commit from `main` to be removed')
|
|
validate_hover({ 19, 0 }, 'Commit to be added 2')
|
|
validate_hover({ 20, 0 }, 'Commit to be added 1')
|
|
validate_hover({ 27, 0 }, 'Add version v0.3.0')
|
|
validate_hover({ 30, 0 }, 'Add version v1.0.0')
|
|
validate_hover({ 31, 0 }, 'Add version v0.4')
|
|
validate_hover({ 32, 0 }, 'Add version 0.3.1')
|
|
end)
|
|
|
|
it('suggests newer versions when on non-tagged commit', function()
|
|
local commit = git_get_hash('0.3.1~', 'semver')
|
|
exec_lua(function()
|
|
-- Make fresh install for cleaner test
|
|
vim.pack.del({ 'semver' })
|
|
vim.pack.add({ { src = repos_src.semver, version = commit } })
|
|
vim.pack.update({ 'semver' })
|
|
end)
|
|
|
|
-- Should correctly infer that 0.3.0 is the latest version and suggest
|
|
-- versions greater than that
|
|
local confirm_text = table.concat(api.nvim_buf_get_lines(0, 0, -1, false), '\n')
|
|
matches('Available newer versions:\n• v1%.0%.0\n• v0%.4\n• 0%.3%.1$', confirm_text)
|
|
end)
|
|
end)
|
|
|
|
it('works with not active plugins', function()
|
|
exec_lua(function()
|
|
-- No plugins are added, but they are installed in `before_each()`
|
|
vim.pack.update({ 'fetch' })
|
|
end)
|
|
eq({ 'return "fetch main"' }, fn.readfile(fetch_lua_file))
|
|
n.exec('write')
|
|
eq({ 'return "fetch new 2"' }, fn.readfile(fetch_lua_file))
|
|
end)
|
|
|
|
it('can force update', function()
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.fetch })
|
|
vim.pack.update({ 'fetch' }, { force = true })
|
|
end)
|
|
|
|
-- Apply changes immediately
|
|
local fetch_src = repos_src.fetch
|
|
local fetch_path = pack_get_plug_path('fetch')
|
|
eq({ 'return "fetch new 2"' }, fn.readfile(fetch_lua_file))
|
|
|
|
-- No special buffer/window/tabpage
|
|
eq(1, #api.nvim_list_tabpages())
|
|
eq(1, #api.nvim_list_wins())
|
|
eq('', api.nvim_get_option_value('filetype', {}))
|
|
|
|
-- Write to log file
|
|
hashes.fetch_new = git_get_hash('HEAD', 'fetch')
|
|
hashes.fetch_new_prev = git_get_hash('HEAD~', 'fetch')
|
|
|
|
local log_path = vim.fs.joinpath(fn.stdpath('log'), 'nvim-pack.log')
|
|
local log_lines = fn.readfile(log_path)
|
|
matches('========== Update %d%d%d%d%-%d%d%-%d%d %d%d:%d%d:%d%d ==========', log_lines[1])
|
|
local ref_log_lines = {
|
|
'# Update ───────────────────────────────────────────────────────────────────────',
|
|
'',
|
|
'## fetch',
|
|
'Path: ' .. fetch_path,
|
|
'Source: ' .. fetch_src,
|
|
'State before: ' .. hashes.fetch_head,
|
|
'State after: ' .. hashes.fetch_new .. ' (main)',
|
|
'',
|
|
'Pending updates:',
|
|
'< ' .. hashes.fetch_head .. ' │ Commit from `main` to be removed',
|
|
'> ' .. hashes.fetch_new .. ' │ Commit to be added 2',
|
|
'> ' .. hashes.fetch_new_prev .. ' │ Commit to be added 1 (tag: dev-tag)',
|
|
'',
|
|
}
|
|
eq(ref_log_lines, vim.list_slice(log_lines, 2))
|
|
end)
|
|
|
|
it('shows progress report', function()
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.fetch, repos_src.defbranch })
|
|
vim.pack.update()
|
|
end)
|
|
|
|
-- During initial download
|
|
validate_progress_report('Downloading updates', { 'fetch', 'defbranch' })
|
|
n.exec('messages clear')
|
|
|
|
-- During application (only for plugins that have updates)
|
|
n.exec('write')
|
|
validate_progress_report('Applying updates', { 'fetch' })
|
|
|
|
-- During force update
|
|
n.clear()
|
|
repo_write_file('fetch', 'lua/fetch.lua', 'return "fetch new 3"')
|
|
git_add_commit('Commit to be added 3', 'fetch')
|
|
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.fetch, repos_src.defbranch })
|
|
vim.pack.update(nil, { force = true })
|
|
end)
|
|
validate_progress_report('Updating', { 'fetch', 'defbranch' })
|
|
end)
|
|
|
|
it('triggers relevant events', function()
|
|
watch_events({ 'PackChangedPre', 'PackChanged' })
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.fetch, repos_src.defbranch })
|
|
_G.event_log = {}
|
|
vim.pack.update()
|
|
end)
|
|
eq({}, exec_lua('return _G.event_log'))
|
|
|
|
-- Should trigger relevant events only for actually updated plugins
|
|
n.exec('write')
|
|
local log = exec_lua('return _G.event_log')
|
|
eq(1, find_in_log(log, 'PackChangedPre', 'update', 'fetch', 'main'))
|
|
eq(2, find_in_log(log, 'PackChanged', 'update', 'fetch', 'main'))
|
|
eq(2, #log)
|
|
end)
|
|
|
|
it('stashes before applying changes', function()
|
|
fn.writefile({ 'A text that will be stashed' }, fetch_lua_file)
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.fetch })
|
|
vim.pack.update()
|
|
vim.cmd('write')
|
|
end)
|
|
|
|
local fetch_path = pack_get_plug_path('fetch')
|
|
local stash_list = system_sync({ 'git', 'stash', 'list' }, { cwd = fetch_path }).stdout or ''
|
|
matches('vim%.pack: %d%d%d%d%-%d%d%-%d%d %d%d:%d%d:%d%d Stash before checkout', stash_list)
|
|
|
|
-- Update should still be applied
|
|
eq({ 'return "fetch new 2"' }, fn.readfile(fetch_lua_file))
|
|
end)
|
|
|
|
it('validates input', function()
|
|
local validate = function(err_pat, input)
|
|
local update_input = function()
|
|
vim.pack.update(input)
|
|
end
|
|
matches(err_pat, pcall_err(exec_lua, update_input))
|
|
end
|
|
|
|
validate('list', 1)
|
|
|
|
-- Should first check if every plugin name represents installed plugin
|
|
-- If not - stop early before any update
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.basic })
|
|
end)
|
|
|
|
validate('The following plugins are not installed: aaa, ccc', { 'aaa', 'basic', 'ccc' })
|
|
|
|
-- Empty list is allowed with warning
|
|
n.exec('messages clear')
|
|
exec_lua(function()
|
|
vim.pack.update({})
|
|
end)
|
|
eq('vim.pack: Nothing to update', n.exec_capture('messages'))
|
|
end)
|
|
end)
|
|
|
|
describe('get()', function()
|
|
local basic_spec = { name = 'basic', src = repos_src.basic, version = 'main' }
|
|
local basic_path = pack_get_plug_path('basic')
|
|
local defbranch_spec = { name = 'defbranch', src = repos_src.defbranch, version = 'dev' }
|
|
local defbranch_path = pack_get_plug_path('defbranch')
|
|
|
|
it('returns list of available plugins', function()
|
|
-- Should work just after installation
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.defbranch, repos_src.basic })
|
|
end)
|
|
eq({
|
|
-- Should preserve order in which plugins were `vim.pack.add()`ed
|
|
{ active = true, path = defbranch_path, spec = defbranch_spec },
|
|
{ active = true, path = basic_path, spec = basic_spec },
|
|
}, exec_lua('return vim.pack.get()'))
|
|
|
|
-- Should also list non-active plugins
|
|
n.clear()
|
|
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.basic })
|
|
end)
|
|
eq({
|
|
-- Should first list active, then non-active
|
|
{ active = true, path = basic_path, spec = basic_spec },
|
|
{ active = false, path = defbranch_path, spec = defbranch_spec },
|
|
}, exec_lua('return vim.pack.get()'))
|
|
end)
|
|
|
|
it('respects data field', function()
|
|
eq(
|
|
true,
|
|
exec_lua(function()
|
|
vim.pack.add {
|
|
{ src = repos_src.basic, data = { test = 'value' } },
|
|
}
|
|
for _, p in ipairs(vim.pack.get()) do
|
|
if p.spec.name == 'basic' and p.spec.data.test == 'value' then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end)
|
|
)
|
|
end)
|
|
|
|
it('works with `del()`', function()
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.defbranch, repos_src.basic })
|
|
end)
|
|
|
|
exec_lua(function()
|
|
_G.get_log = {}
|
|
vim.api.nvim_create_autocmd({ 'PackChangedPre', 'PackChanged' }, {
|
|
callback = function()
|
|
table.insert(_G.get_log, vim.pack.get())
|
|
end,
|
|
})
|
|
end)
|
|
|
|
-- Should not include removed plugins immediately after they are removed,
|
|
-- while still returning list without holes
|
|
exec_lua('vim.pack.del({ "defbranch" })')
|
|
eq({
|
|
{
|
|
{ active = true, path = defbranch_path, spec = defbranch_spec },
|
|
{ active = true, path = basic_path, spec = basic_spec },
|
|
},
|
|
{
|
|
{ active = true, path = basic_path, spec = basic_spec },
|
|
},
|
|
}, exec_lua('return _G.get_log'))
|
|
end)
|
|
end)
|
|
|
|
describe('del()', function()
|
|
it('works', function()
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.plugindirs, { src = repos_src.basic, version = 'feat-branch' } })
|
|
end)
|
|
eq(true, pack_exists('basic'))
|
|
eq(true, pack_exists('plugindirs'))
|
|
|
|
watch_events({ 'PackChangedPre', 'PackChanged' })
|
|
|
|
n.exec('messages clear')
|
|
exec_lua(function()
|
|
vim.pack.del({ 'basic', 'plugindirs' })
|
|
end)
|
|
eq(false, pack_exists('basic'))
|
|
eq(false, pack_exists('plugindirs'))
|
|
|
|
eq(
|
|
"vim.pack: Removed plugin 'plugindirs'\nvim.pack: Removed plugin 'basic'",
|
|
n.exec_capture('messages')
|
|
)
|
|
|
|
-- Should trigger relevant events in order as specified in `vim.pack.add()`
|
|
local log = exec_lua('return _G.event_log')
|
|
eq(1, find_in_log(log, 'PackChangedPre', 'delete', 'plugindirs', 'main'))
|
|
eq(2, find_in_log(log, 'PackChanged', 'delete', 'plugindirs', 'main'))
|
|
eq(3, find_in_log(log, 'PackChangedPre', 'delete', 'basic', 'feat-branch'))
|
|
eq(4, find_in_log(log, 'PackChanged', 'delete', 'basic', 'feat-branch'))
|
|
eq(4, #log)
|
|
end)
|
|
|
|
it('validates input', function()
|
|
local validate = function(err_pat, input)
|
|
local del_input = function()
|
|
vim.pack.del(input)
|
|
end
|
|
matches(err_pat, pcall_err(exec_lua, del_input))
|
|
end
|
|
|
|
validate('list', nil)
|
|
|
|
-- Should first check if every plugin name represents installed plugin
|
|
-- If not - stop early before any delete
|
|
exec_lua(function()
|
|
vim.pack.add({ repos_src.basic })
|
|
end)
|
|
|
|
validate('The following plugins are not installed: aaa, ccc', { 'aaa', 'basic', 'ccc' })
|
|
eq(true, pack_exists('basic'))
|
|
|
|
-- Empty list is allowed with warning
|
|
n.exec('messages clear')
|
|
exec_lua(function()
|
|
vim.pack.del({})
|
|
end)
|
|
eq('vim.pack: Nothing to remove', n.exec_capture('messages'))
|
|
end)
|
|
end)
|
|
end)
|