Repository navigation
Expand file tree
/
Copy pathdelegate_spec.lua
More file actions
303 lines (263 loc) · 11.7 KB
/
Copy pathdelegate_spec.lua
File metadata and controls
303 lines (263 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
local pack = require("pack")
local state = require("pack.state")
local loader = require("pack.loader")
local persist = require("pack.persist")
-- A stand-in for native vim.pack. add() synchronously invokes the load callback
-- for each spec (like native does after cloning) so our loader runs.
local function make_fake()
local fake = { added = {}, updated = {}, deleted = {}, get_result = {} }
function fake.add(specs, opts)
for _, s in ipairs(specs) do
table.insert(fake.added, s)
if opts and opts.load then
opts.load({ spec = s, path = "/fake/" .. s.name })
end
end
end
function fake.update(names, opts)
table.insert(fake.updated, { names = names, opts = opts })
end
function fake.del(names, opts)
table.insert(fake.deleted, { names = names, opts = opts })
end
function fake.get()
return fake.get_result
end
return fake
end
describe("pack.init native delegation", function()
local tmp_path, fake
local orig_load, orig_setup_triggers, orig_vim_pack
local loaded, triggered
before_each(function()
tmp_path = vim.fn.tempname() .. "-pack-disabled.json"
persist._set_path_for_testing(tmp_path)
loaded, triggered = {}, {}
orig_load = loader.load
orig_setup_triggers = loader.setup_triggers
loader.load = function(name) table.insert(loaded, name) end
loader.setup_triggers = function(p) table.insert(triggered, p.name) end
orig_vim_pack = vim.pack
fake = make_fake()
vim.pack = fake
end)
after_each(function()
loader.load = orig_load
loader.setup_triggers = orig_setup_triggers
vim.pack = orig_vim_pack
if vim.fn.filereadable(tmp_path) == 1 then vim.fn.delete(tmp_path) end
persist._set_path_for_testing(nil)
end)
local function do_setup(plugins)
pack.setup({ install_path = vim.fn.tempname() .. "-pack-install", plugins = plugins })
end
it("installs configured plugins through native vim.pack.add", function()
do_setup({ "user/foo.nvim", "user/bar.nvim" })
assert.equals(2, #fake.added)
local names = { fake.added[1].name, fake.added[2].name }
table.sort(names)
assert.same({ "bar.nvim", "foo.nvim" }, names)
-- src is the resolved clone URL, not the shorthand
assert.matches("^https://github.com/user/", fake.added[1].src)
end)
it("loads eager plugins and triggers lazy ones after native add", function()
do_setup({
{ "user/eager.nvim" },
{ "user/lazy.nvim", lazy = true, cmd = "LazyCmd" },
})
assert.same({ "eager.nvim" }, loaded)
assert.same({ "lazy.nvim" }, triggered)
end)
it("does not send disabled plugins to native", function()
-- Pre-mark bar disabled via the persisted set, then setup.
persist.set_disabled("bar.nvim", true)
do_setup({ "user/foo.nvim", "user/bar.nvim" })
assert.equals(1, #fake.added)
assert.equals("foo.nvim", fake.added[1].name)
end)
it("carries the resolved version pin into the native spec", function()
do_setup({ { "user/foo.nvim", tag = "v1.2.3" } })
assert.equals("v1.2.3", fake.added[1].version)
end)
it("preserves pack.nvim metadata on native-style (src=) specs", function()
do_setup({
{ src = "https://github.com/user/eager.nvim", name = "eager.nvim" },
{ src = "https://github.com/user/lazy.nvim", name = "lazy.nvim", lazy = true, cmd = "LazyCmd" },
})
-- src-style lazy spec keeps its lazy trigger (not eagerly loaded)
assert.same({ "eager.nvim" }, loaded)
assert.same({ "lazy.nvim" }, triggered)
local p = state.get_plugins()["lazy.nvim"]
assert.is_true(p.lazy)
assert.equals("LazyCmd", p.cmd)
assert.equals("https://github.com/user/lazy.nvim", p.url)
-- native spec round-trips src correctly
local added = {}
for _, s in ipairs(fake.added) do added[s.name] = s end
assert.equals("https://github.com/user/lazy.nvim", added["lazy.nvim"].src)
end)
it("vim.pack.add dynamically installs a new plugin via native", function()
do_setup({ "user/foo.nvim" })
local before = #fake.added
vim.pack.add({ "user/new.nvim" })
assert.equals(before + 1, #fake.added)
assert.equals("new.nvim", fake.added[#fake.added].name)
end)
it("vim.pack.del removes state and delegates to native del with force = true", function()
do_setup({ "user/foo.nvim" })
vim.pack.del("foo.nvim")
assert.is_nil(state.get_plugins()["foo.nvim"])
assert.equals(1, #fake.deleted)
assert.same({ "foo.nvim" }, fake.deleted[1].names)
assert.is_true(fake.deleted[1].opts and fake.deleted[1].opts.force)
end)
it("vim.pack.update delegates to native update", function()
do_setup({ "user/foo.nvim" })
vim.pack.update()
assert.equals(1, #fake.updated)
end)
it("re-running setup on top of the wrapper keeps delegating to real native", function()
do_setup({ "user/foo.nvim" })
-- vim.pack is now OUR wrapper. Re-run setup the way `:source $MYVIMRC` does,
-- WITHOUT restoring vim.pack first. Before the re-entrancy guard this made
-- native_pack point at the wrapper, so update/del/add recursed forever and
-- installs silently no-oped.
pack.setup({ plugins = {} })
-- native_pack must still be the real native (the fake), not the wrapper.
assert.equals(fake, pack.native_pack)
local before_updates = #fake.updated
vim.pack.update() -- would infinite-recurse before the fix
assert.equals(before_updates + 1, #fake.updated)
-- Installs must still reach native after a re-setup.
vim.pack.add({ "user/re.nvim" })
assert.equals("re.nvim", fake.added[#fake.added].name)
end)
it(":Pack sync updates managed plugins with force=true (no native confirm UI)", function()
-- Regression: sync used to call native update with no `force`, which pops
-- native vim.pack's own confirmation buffer. Sync must be non-interactive --
-- the dashboard IS the confirmation -- so it passes force=true (and targets
-- the managed plugins explicitly, driving the dashboard's own progress).
do_setup({ "user/foo.nvim" })
state.get_plugins()["foo.nvim"].behind = 1
vim.cmd("Pack sync")
local last = fake.updated[#fake.updated]
assert.same({ "foo.nvim" }, last.names)
assert.is_true(last.opts.force)
end)
it(":Pack restore delegates to native lockfile update", function()
do_setup({ "user/foo.nvim" })
vim.cmd("Pack restore")
local last = fake.updated[#fake.updated]
assert.equals("lockfile", last.opts.target)
end)
it(":Pack clean removes unmanaged/adopted plugins no longer in configured spec", function()
do_setup({ "user/foo.nvim" })
fake.get_result = {
{ spec = { name = "foo.nvim" }, path = "/path/foo.nvim" },
{ spec = { name = "old_deleted.nvim" }, path = "/path/old_deleted.nvim" },
}
-- Adopt old_deleted.nvim so it is present in state.plugins as managed = false
state.reconcile_from_native(require("pack").native_pack)
assert.is_not_nil(state.get_plugins()["old_deleted.nvim"])
assert.is_false(state.get_plugins()["old_deleted.nvim"].managed)
vim.cmd("Pack clean")
-- Check that native del was invoked for old_deleted.nvim
assert.equals(1, #fake.deleted)
assert.same({ "old_deleted.nvim" }, fake.deleted[1].names)
-- Check that old_deleted.nvim was removed from state
assert.is_nil(state.get_plugins()["old_deleted.nvim"])
-- Managed foo.nvim remains
assert.is_not_nil(state.get_plugins()["foo.nvim"])
end)
it("vim.pack.get falls through to native via metatable", function()
do_setup({ "user/foo.nvim" })
fake.get_result = { "sentinel" }
assert.same({ "sentinel" }, vim.pack.get())
end)
it("reconcile_from_native updates dir/rev/status from native get()", function()
do_setup({ "user/foo.nvim" })
local p = state.get_plugins()["foo.nvim"]
p.status = "missing"
fake.get_result = {
{ spec = { name = "foo.nvim" }, path = "/real/foo.nvim", rev = "cafebabe" },
}
state.reconcile_from_native(require("pack").native_pack)
assert.equals("/real/foo.nvim", p.dir)
assert.equals("cafebabe", p.rev)
assert.equals("installed", p.status)
end)
it("installs the vim.pack.add wrapper in setup without blocking on native reconcile", function()
local pack = require("pack")
-- Note: no local finally/restore here -- the describe-level after_each
-- (above) unconditionally restores vim.pack = orig_vim_pack after every
-- test, including this one, so it's covered without relying on a
-- `finally` global (not provided by this repo's plenary busted shim).
local fake_native = {
add = function() end,
get = function()
return { { spec = { name = "adopted.nvim", src = "https://github.com/x/adopted.nvim" }, path = "/x/adopted.nvim" } }
end,
del = function() end,
update = function() end,
}
vim.pack = fake_native
pack.setup({ plugins = {} })
-- Wrapper is installed (not the raw native add).
assert.are_not.equal(fake_native.add, vim.pack.add)
-- Non-blocking setup does not scan disk for adopted plugins on startup...
assert.is_nil(require("pack.state").get_plugins()["adopted.nvim"])
-- ...reconciliation happens when requested (e.g. by ui.open)
state.reconcile_from_native(pack.native_pack)
local a = require("pack.state").get_plugins()["adopted.nvim"]
assert.is_not_nil(a)
assert.is_false(a.managed)
end)
it("keeps wrapper-added (imperative import) plugins managed alongside declarative specs", function()
-- Faithfully reproduce Feature B: an imported file calls the wrapped
-- vim.pack.add DURING load_plugins (i.e. during setup, before any state
-- rebuild). Build a real runtime module on rtp that does exactly that.
local rtp_dir = vim.fn.tempname() .. "-pack-imp"
local mod_name = "pack_imp_" .. tostring(vim.uv and vim.uv.hrtime() or os.clock()):gsub("%.", "_")
local mod_dir = rtp_dir .. "/lua/" .. mod_name
vim.fn.mkdir(mod_dir, "p")
vim.fn.writefile({
'vim.pack.add({ { src = "https://github.com/user/imp.nvim", name = "imp.nvim" } })',
"return {}",
}, mod_dir .. "/plugins.lua")
vim.opt.runtimepath:prepend(rtp_dir)
-- Fake native: .add is a no-op recorder, .get returns {} so reconcile does
-- not adopt/overwrite anything. Restored by the describe after_each.
vim.pack = {
add = function() end,
get = function() return {} end,
del = function() end,
update = function() end,
}
require("pack").setup({ plugins = { "user/decl.nvim", { import = mod_name } } })
-- Module is only needed during setup's load_plugins; clean it off rtp now.
package.loaded[mod_name .. ".plugins"] = nil
vim.opt.runtimepath:remove(rtp_dir)
vim.fn.delete(rtp_dir, "rf")
local plugins = state.get_plugins()
-- Declarative spec survived (setup registers additively, not via a
-- destructive state.init that would wipe the import-time record).
assert.is_not_nil(plugins["decl.nvim"])
assert.is_true(plugins["decl.nvim"].managed)
-- The imperative wrapper-added spec (added during import) is present and
-- MANAGED (Feature B), not wiped and not demoted to managed=false.
assert.is_not_nil(plugins["imp.nvim"])
assert.is_true(plugins["imp.nvim"].managed)
end)
it("silences git submodule and cloning notifications when is_silent is true", function()
local notified = false
local dummy_notify = function(msg)
notified = true
end
-- Run notification hook with silent = true
pack.setup({ ui = { silent = true }, plugins = {} })
vim.notify("Submodule 'dependencies/neotest' registered for path 'dependencies/neotest'")
assert.is_false(notified, "submodule notification should be silenced")
vim.notify("Cloning into '/some/path/neotest-bash/dependencies/neotest'...")
assert.is_false(notified, "cloning notification should be silenced")
end)
end)