mirror of
https://github.com/neovim/neovim.git
synced 2025-09-05 19:08:15 +00:00
1299 lines
50 KiB
Lua
1299 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()
|
|
local out = exec_lua(function()
|
|
local map = {} ---@type table<string,boolean>
|
|
local load = function(p)
|
|
local name = p.spec.name ---@type string
|
|
map[name] = name == 'basic' and (p.spec.data.test == 'value') or (p.spec.data == 'value')
|
|
end
|
|
vim.pack.add({
|
|
{ src = repos_src.basic, data = { test = 'value' } },
|
|
{ src = repos_src.defbranch, data = 'value' },
|
|
}, { load = load })
|
|
return map
|
|
end)
|
|
eq({ basic = true, defbranch = true }, out)
|
|
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()
|
|
local out = exec_lua(function()
|
|
vim.pack.add({
|
|
{ src = repos_src.basic, data = { test = 'value' } },
|
|
{ src = repos_src.defbranch, data = 'value' },
|
|
})
|
|
local plugs = vim.pack.get()
|
|
---@type table<string,string>
|
|
return { basic = plugs[1].spec.data.test, defbranch = plugs[2].spec.data }
|
|
end)
|
|
eq({ basic = 'value', defbranch = 'value' }, out)
|
|
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)
|