diff --git a/.busted b/.busted new file mode 100644 index 0000000..02c43cb --- /dev/null +++ b/.busted @@ -0,0 +1,9 @@ +return { + default = { + ROOT = { 'tests' }, + -- Each tests/*_test.lua is a native busted spec. Match only those: the + -- pattern is anchored so shared modules like pack_update_test_helpers.lua + -- (which carries the substring `_test`) are not discovered as specs. + pattern = '_test%.lua$', + }, +} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..a88cb19 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,57 @@ +name: Lint + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + luacheck: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Lua + uses: leafo/gh-actions-lua@v13 + with: + luaVersion: "luajit-2.1" + + - name: Set up LuaRocks + uses: leafo/gh-actions-luarocks@v6 + + - name: Install luacheck + run: luarocks install luacheck + + - name: Run luacheck + run: luacheck lua/ tests/ + + typecheck: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Install Neovim + uses: rhysd/action-setup-vim@v1 + with: + neovim: true + version: v0.12.0 + + - name: Install lua-language-server + run: | + mkdir -p "$HOME/lua-ls" + curl -sL "https://github.com/LuaLS/lua-language-server/releases/download/${LUALS_VERSION}/lua-language-server-${LUALS_VERSION}-linux-x64.tar.gz" \ + | tar -xz -C "$HOME/lua-ls" + echo "$HOME/lua-ls/bin" >> "$GITHUB_PATH" + env: + LUALS_VERSION: "3.17.1" + + - name: Resolve VIMRUNTIME + run: echo "VIMRUNTIME=$(nvim --clean --headless --cmd 'lua io.write(vim.env.VIMRUNTIME)' --cmd 'quit')" >> "$GITHUB_ENV" + + - name: Type-check lua/ + run: lua-language-server --check "$PWD/lua" --checklevel=Warning --configpath="$PWD/.luarc.json" diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index c01e848..c0f8d3a 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Neovim uses: rhysd/action-setup-vim@v1 @@ -21,9 +21,16 @@ jobs: neovim: true version: ${{ matrix.neovim-version }} - - name: Run tests - run: nvim --version && nvim -u NONE --headless -S run_tests.lua + - name: Set up LuaJIT + uses: leafo/gh-actions-lua@v13 + with: + luaVersion: "luajit-2.1" + + - name: Set up LuaRocks + uses: leafo/gh-actions-luarocks@v6 - - name: Check test results - if: failure() - run: echo "Tests failed - check logs above" + - name: Install busted + run: luarocks install --tree .luarocks busted 2.3.0 + + - name: Run tests + run: nvim --version && nvim -u NONE -l tests/busted.lua diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..f29f940 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,18 @@ +name: Release Please + +on: + push: + branches: [main] + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b3903b7..ff722d0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Neovim uses: rhysd/action-setup-vim@v1 @@ -21,9 +21,16 @@ jobs: neovim: true version: ${{ matrix.neovim-version }} - - name: Run tests - run: nvim --version && nvim -u NONE --headless -S run_tests.lua + - name: Set up LuaJIT + uses: leafo/gh-actions-lua@v13 + with: + luaVersion: "luajit-2.1" + + - name: Set up LuaRocks + uses: leafo/gh-actions-luarocks@v6 - - name: Check test results - if: failure() - run: echo "Tests failed - check logs above" + - name: Install busted + run: luarocks install --tree .luarocks busted 2.3.0 + + - name: Run tests + run: nvim --version && nvim -u NONE -l tests/busted.lua diff --git a/.gitignore b/.gitignore index 40ddbcd..f51c08f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .DS_Store .claude/ + +# Project-local LuaRocks tree (busted and its dependencies for tests) +.luarocks/ diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..1f385ae --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,25 @@ +-- Lint configuration for zpack.nvim. +-- Warning reference: https://luacheck.readthedocs.io/en/stable/warnings.html + +std = "luajit" +codes = true + +-- zpack runs inside Neovim; `vim` is an injected global. +read_globals = { "vim" } + +-- The project has no line-length convention (no stylua/editorconfig). luacheck +-- here guards correctness — unused/shadowed vars, undefined globals — not +-- formatting, so the line-length check is left off. +max_line_length = false + +-- The suite runs under busted (describe/it/assert globals). The harness also +-- stubs vim.* (vim.notify, vim.pack.add, ...) and threads shared state +-- through the _G.test_state table, so under tests/ `vim` is written to and +-- `_G` is used directly. Test files also carry documentary callback args and +-- assertion-scaffolding locals; their unused-variable hygiene (2xx/23x) is +-- intentionally not linted. +files["tests"] = { + std = "luajit+busted", + globals = { "vim", "_G" }, + ignore = { "21", "23" }, +} diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000..e49b359 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "runtime": { + "version": "LuaJIT", + "path": ["lua/?.lua", "lua/?/init.lua", "tests/?.lua", "?.lua"] + }, + "workspace": { + "library": ["$VIMRUNTIME/lua"], + "checkThirdParty": false + }, + "diagnostics": { + "globals": ["vim"] + } +} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..41ea87d --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.2.1" +} diff --git a/README.md b/README.md index b5c8708..a1700b2 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,14 @@ On Neovim 0.13+, several subcommands map to native `vim.pack` commands you can u `clean`, `build`, and `load` have no native equivalent. The `:ZPack` subcommands are concise, consistent shortcuts — reach for the native commands when you want their extra flags (e.g. `:packupdate ++offline`). +#### Health Check + +Run `:checkhealth zpack` to diagnose a broken or unexpected setup. It verifies +the Neovim version and `vim.pack` availability, whether `setup()` was called, +validates your configuration (flagging deprecated options), and prints a +status summary of every registered plugin. + + #### Configurations ```lua diff --git a/doc/zpack.txt b/doc/zpack.txt index bfc7458..5eda1d9 100644 --- a/doc/zpack.txt +++ b/doc/zpack.txt @@ -241,6 +241,29 @@ performance (table, optional) Performance-related settings. - `vim_loader`: Enable vim.loader caching. Default: `true` +------------------------------------------------------------------------------ +4.6 HEALTH CHECK *zpack-health* + +zpack ships a healthcheck. Run it to diagnose a broken or unexpected setup: > + + :checkhealth zpack +< +The report has these sections: + +Environment Confirms Neovim is 0.12.0+ and that `vim.pack` is available. + +Setup Whether |zpack.setup()| has been called. zpack is inactive + until it has. + +Configuration Validates the resolved |zpack-setup-options| and lists any + deprecated or removed options still in use, with their + replacements. + +Plugins A status summary of every registered plugin: how many are + loaded, lazy, pending, installing, or disabled by `cond`. + +Reporting a bug A minimal-config template and the steps for filing an issue. + ============================================================================== 5. WHY ZPACK? *zpack-why-zpack* diff --git a/lua/zpack/deprecation.lua b/lua/zpack/deprecation.lua index e927049..f828dbb 100644 --- a/lua/zpack/deprecation.lua +++ b/lua/zpack/deprecation.lua @@ -1,7 +1,24 @@ +-- Deprecation and removal notices for zpack. +-- +-- This custom layer is kept deliberately rather than migrated to +-- vim.deprecate(); the evaluation: +-- * Legacy : commands notify on EVERY invocation (see +-- notify_legacy_command) so a notice that scrolled past is not lost. +-- vim.deprecate() routes through vim.notify_once and would dedup it, +-- conflicting with that intent. +-- * `add`/`auto_import` are already removed, not pending removal, so +-- vim.deprecate()'s "will be removed in version X" framing is wrong. +-- * The option notices below carry copy-paste replacement snippets that +-- vim.deprecate()'s single-line `alternative` argument cannot express. +-- Deprecated options actually in use are also surfaced by :checkhealth zpack. + local utils = require('zpack.utils') local M = {} +-- Every M.removed/M.deprecated entry must carry both `message` and +-- `replacement` strings: notify_removed/notify_deprecated format them +-- unguarded, and :checkhealth zpack splits `replacement` into advice lines. M.removed = { add = { message = "zpack.add() has been removed. Pass specs directly to setup():", @@ -34,6 +51,16 @@ M.deprecated = { }, } +-- setup() option keys that are deprecated or removed, for :checkhealth zpack +-- to report when one is still passed. Kept here as the single authoritative +-- list rather than derived from M.removed/M.deprecated: M.removed also holds +-- `add` (a removed function, not an option key) and `cmd_prefix` has only +-- computed notices with no static entry — so the key set is neither table's +-- keys. Update this list whenever M.removed/M.deprecated gains an option. +M.deprecated_option_keys = { + 'cmd_prefix', 'confirm', 'disable_vim_loader', 'plugins_dir', 'auto_import', +} + M.notify_removed = function(key) local entry = M.removed[key] if not entry then return end diff --git a/lua/zpack/health.lua b/lua/zpack/health.lua new file mode 100644 index 0000000..0a18638 --- /dev/null +++ b/lua/zpack/health.lua @@ -0,0 +1,173 @@ +---:checkhealth zpack — diagnostics for a zpack setup. +--- +---Surfaces, in the editor, what a misconfigured setup otherwise only reveals +---as a runtime error: an unsupported Neovim version, a missing `vim.pack`, +---`setup()` never being called, malformed config, deprecated options still +---in use, or specs that never resolved. +--- +---Discovered automatically by `:checkhealth zpack` — nothing registers it. + +local M = {} + +local ISSUES_URL = 'https://github.com/zuqini/zpack.nvim/issues' + +local MINIMAL_CONFIG = table.concat({ + "require('zpack').setup({", + ' spec = {', + " { 'user/plugin' },", + ' },', + '})', +}, '\n') + +local function check_environment() + vim.health.start('Environment') + + if vim.fn.has('nvim-0.12') == 1 then + vim.health.ok('Neovim ' .. tostring(vim.version()) .. ' (>= 0.12.0 required)') + else + vim.health.error('Neovim 0.12.0+ is required; zpack will not load on this version') + end + + if type(vim.pack) == 'table' and type(vim.pack.add) == 'function' then + vim.health.ok('vim.pack is available') + else + vim.health.error('vim.pack is not available — zpack is a thin layer over it', { + 'Build or install a Neovim that ships vim.pack (0.12.0+).', + }) + end +end + +---@param state table The zpack.state module +local function check_setup(state) + vim.health.start('Setup') + + if state.is_setup then + vim.health.ok('setup() has been called') + else + vim.health.warn('setup() has not been called — zpack is inactive', { + 'Call it from your config:', + MINIMAL_CONFIG, + }) + end +end + +---@param state table The zpack.state module +local function check_config(state) + vim.health.start('Configuration') + + if not state.is_setup or not state.config then + vim.health.info('No configuration to check (setup() has not run)') + return + end + + local errors = require('zpack.validate').validate_config(state.config) + if #errors == 0 then + vim.health.ok('setup() options are valid') + else + for _, err in ipairs(errors) do + vim.health.error('Invalid option: ' .. err) + end + end + + if #state.deprecations == 0 then + vim.health.ok('No deprecated options in use') + else + local deprecation = require('zpack.deprecation') + -- A replacement snippet can span several lines; split it so each renders + -- as its own advice line instead of one bullet with embedded newlines. + local function advice(entry) + local lines = { entry.message } + vim.list_extend(lines, vim.split(entry.replacement, '\n', { trimempty = true })) + return lines + end + for _, key in ipairs(state.deprecations) do + local removed = deprecation.removed[key] + local deprecated = deprecation.deprecated[key] + if removed then + vim.health.warn(('Removed option still passed: %s'):format(key), advice(removed)) + elseif deprecated then + vim.health.warn(('Deprecated option in use: %s'):format(key), advice(deprecated)) + else + -- cmd_prefix is in deprecated_option_keys but has no static + -- removed/deprecated entry (its notice is computed) — warn bare. + vim.health.warn(('Deprecated option in use: %s'):format(key)) + end + end + end +end + +---@param state table The zpack.state module +local function check_plugins(state) + vim.health.start('Plugins') + + if not state.is_setup then + vim.health.info('No plugins registered (setup() has not run)') + return + end + + local plugins = require('zpack.api').get_plugins() + if #plugins == 0 then + vim.health.warn('No plugins registered', { + 'If you expected plugins here, check that lua/plugins/ contains spec', + 'files, or that you passed spec = { ... } to setup().', + }) + return + end + + local counts = { loaded = 0, loading = 0, pending = 0, disabled = 0, installing = 0 } + local lazy_count = 0 + for _, plugin in ipairs(plugins) do + counts[plugin.status] = (counts[plugin.status] or 0) + 1 + if plugin.lazy then + lazy_count = lazy_count + 1 + end + end + + vim.health.ok(('%d plugin(s) registered'):format(#plugins)) + -- Status counts partition #plugins; lazy is orthogonal (a lazy plugin is + -- also loaded/pending/...), so it is reported on its own line below. + vim.health.info(('loaded: %d pending: %d'):format(counts.loaded, counts.pending)) + + if counts.loading > 0 then + vim.health.info(('loading: %d'):format(counts.loading)) + end + if counts.installing > 0 then + local cmd_name = (state.config and state.config.cmd_name) or 'ZPack' + vim.health.warn(('installing: %d — not yet on disk'):format(counts.installing), { + ('Restart Neovim, or run :%s update, once installation finishes.'):format(cmd_name), + }) + end + if counts.disabled > 0 then + vim.health.info(('disabled by cond: %d'):format(counts.disabled)) + end + if lazy_count > 0 then + vim.health.info(('lazy: %d'):format(lazy_count)) + end +end + +local function check_bug_report() + vim.health.start('Reporting a bug') + vim.health.info(table.concat({ + 'Reproduce with a minimal config, then open an issue at', + ISSUES_URL .. ' including:', + ' - the minimal config below (edited to trigger the bug)', + ' - the full :checkhealth zpack output', + ' - your Neovim version (nvim --version)', + '', + 'Minimal config — save as repro.lua, run with: nvim -u repro.lua', + '', + MINIMAL_CONFIG, + }, '\n')) +end + +---Entry point for `:checkhealth zpack`. +function M.check() + local state = require('zpack.state') + check_environment() + check_setup(state) + check_config(state) + check_plugins(state) + check_bug_report() +end + +return M diff --git a/lua/zpack/hooks.lua b/lua/zpack/hooks.lua index 2c40551..4e9030f 100644 --- a/lua/zpack/hooks.lua +++ b/lua/zpack/hooks.lua @@ -8,7 +8,8 @@ local M = {} ---@return boolean M.try_call_hook = function(src, hook_name) local registry_entry = state.spec_registry[src] - local spec = registry_entry.merged_spec + -- merged_spec is always populated post-resolve_all, when hooks run. + local spec = registry_entry.merged_spec --[[@as zpack.Spec]] local hook = spec[hook_name] --[[@as fun(plugin: zpack.Plugin)]] if not hook then @@ -30,7 +31,7 @@ M.try_call_hook = function(src, hook_name) return true end ----@param build string|fun(plugin: zpack.Plugin) +---@param build string|fun(plugin: zpack.Plugin?) ---@param plugin zpack.Plugin? M.execute_build = function(build, plugin) if type(build) == "string" then diff --git a/lua/zpack/import.lua b/lua/zpack/import.lua index e0b359d..6beb7ca 100644 --- a/lua/zpack/import.lua +++ b/lua/zpack/import.lua @@ -1,5 +1,6 @@ local utils = require('zpack.utils') local state = require('zpack.state') +local validate = require('zpack.validate') local M = {} @@ -10,7 +11,10 @@ local imported_modules = {} ---@return string|nil source URL/path, or nil if invalid ---@return string|nil error message if validation fails local normalize_source = function(spec) - if spec[1] then + -- `[1]` must be a string: a non-string (an over-nested spec, a typo) would + -- crash the concat or coerce to a garbage URL. Fall through to the nil/err + -- result so import_one_spec skips it instead of aborting setup(). + if type(spec[1]) == 'string' then return 'https://github.com/' .. spec[1] elseif spec.src then return spec.src @@ -23,17 +27,6 @@ local normalize_source = function(spec) end end ----@param spec zpack.Spec ----@return string -local get_source_url = function(spec) - local src, err = normalize_source(spec) - if not src then - utils.schedule_notify(err, vim.log.levels.ERROR) - error(err) - end - return src -end - ---Check if a table has any non-integer keys ---@param tbl table ---@return boolean @@ -150,62 +143,101 @@ local import_from_module = function(module_path, ctx) end end ----@param spec_item_or_list zpack.Spec|zpack.Spec[] +---Register a spec's dependencies into the dependency graph and import them. +---@param spec zpack.Spec +---@param src string Normalized source of the parent spec ---@param ctx zpack.ProcessContext -M.import_specs = function(spec_item_or_list, ctx) - local specs = is_single_spec(spec_item_or_list) - and { spec_item_or_list } - or spec_item_or_list --[[@as zpack.Spec[] ]] - - for _, spec in ipairs(specs) do - if is_import_spec(spec) then - if spec.enabled == false or (type(spec.enabled) == "function" and not spec.enabled()) then - goto continue +local register_dependencies = function(spec, src, ctx) + local dep_specs = normalize_dependencies(spec.dependencies, src) + state.dependency_graph[src] = state.dependency_graph[src] or {} + local dep_ctx = vim.tbl_extend('force', ctx, { is_dependency = true }) + for _, dep_spec in ipairs(dep_specs) do + local dep_src, err = normalize_source(dep_spec) + if not dep_src then + if err then + utils.schedule_notify(("Invalid dependency for %s: %s"):format(src, err), vim.log.levels.WARN) end - import_from_module(spec.import, ctx) - goto continue + else + if not state.dependency_graph[src][dep_src] then + state.dependency_graph[src][dep_src] = true + state.reverse_dependency_graph[dep_src] = state.reverse_dependency_graph[dep_src] or {} + state.reverse_dependency_graph[dep_src][src] = true + end + M.import_specs(dep_spec, dep_ctx) end + end +end - local src = get_source_url(spec) - local is_dep = ctx.is_dependency or false - - spec._import_order = state.import_order - state.import_order = state.import_order + 1 - spec._is_dependency = is_dep - - if state.spec_registry[src] then - table.insert(state.spec_registry[src].specs, spec) - else - state.spec_registry[src] = { specs = { spec }, load_status = "pending" } +---Process one spec: an import spec recurses into a module directory; a plugin +---spec is registered into spec_registry and has its dependencies walked. +---@param spec zpack.Spec +---@param ctx zpack.ProcessContext +local import_one_spec = function(spec, ctx) + local spec_errors = validate.validate_spec(spec) + if #spec_errors > 0 then + local label = type(spec) == 'table' and validate.spec_label(spec) or tostring(spec) + utils.schedule_notify( + ('zpack: invalid spec "%s":\n %s'):format(label, table.concat(spec_errors, '\n ')), + vim.log.levels.WARN + ) + -- A non-table spec cannot be processed further; the warning is the + -- actionable signal. A table spec continues: a bad field is advisory and + -- the spec still imports, while a missing source is caught downstream. + if type(spec) ~= 'table' then + return end + end - -- Walk dependencies unconditionally. `enabled` is evaluated post-merge - -- in resolve_all; gating the dep walk on the raw pre-merge spec would - -- call function-form `enabled` eagerly at import time and diverge from - -- the merged truth. prune_disabled_subtrees handles cleanup afterward. - if spec.dependencies then - local dep_specs = normalize_dependencies(spec.dependencies, src) - state.dependency_graph[src] = state.dependency_graph[src] or {} - local dep_ctx = vim.tbl_extend('force', ctx, { is_dependency = true }) - for _, dep_spec in ipairs(dep_specs) do - local dep_src, err = normalize_source(dep_spec) - if not dep_src then - if err then - utils.schedule_notify(("Invalid dependency for %s: %s"):format(src, err), vim.log.levels.WARN) - end - goto skip_dep - end - if not state.dependency_graph[src][dep_src] then - state.dependency_graph[src][dep_src] = true - state.reverse_dependency_graph[dep_src] = state.reverse_dependency_graph[dep_src] or {} - state.reverse_dependency_graph[dep_src][src] = true - end - M.import_specs(dep_spec, dep_ctx) - ::skip_dep:: - end + if is_import_spec(spec) then + -- A non-string `import` cannot name a module directory; it was already + -- reported by validate_spec above. Skip it rather than crashing in + -- import_from_module's path arithmetic. + if type(spec.import) ~= 'string' then + return + end + if spec.enabled == false or (type(spec.enabled) == "function" and not spec.enabled()) then + return end + import_from_module(spec.import, ctx) + return + end + + -- A sourceless spec was already reported by validate_spec above; skip it + -- rather than letting the loader abort setup() partway through. + local src = normalize_source(spec) + if not src then + return + end + local is_dep = ctx.is_dependency or false + + spec._import_order = state.import_order + state.import_order = state.import_order + 1 + spec._is_dependency = is_dep + + if state.spec_registry[src] then + table.insert(state.spec_registry[src].specs, spec) + else + state.spec_registry[src] = { specs = { spec }, load_status = "pending" } + end + + -- Walk dependencies unconditionally. `enabled` is evaluated post-merge + -- in resolve_all; gating the dep walk on the raw pre-merge spec would + -- call function-form `enabled` eagerly at import time and diverge from + -- the merged truth. prune_disabled_subtrees handles cleanup afterward. + if spec.dependencies then + register_dependencies(spec, src, ctx) + end +end + +---@param spec_item_or_list zpack.Spec|zpack.Spec[] +---@param ctx zpack.ProcessContext +M.import_specs = function(spec_item_or_list, ctx) + local specs = is_single_spec(spec_item_or_list) + and { spec_item_or_list } + or spec_item_or_list --[[@as zpack.Spec[] ]] - ::continue:: + for _, spec in ipairs(specs) do + import_one_spec(spec, ctx) end end diff --git a/lua/zpack/init.lua b/lua/zpack/init.lua index 9ce6358..d892090 100644 --- a/lua/zpack/init.lua +++ b/lua/zpack/init.lua @@ -55,6 +55,8 @@ end ---@field plugins_dir? string @deprecated Use { import = 'dir' } in spec instead ---@field confirm? boolean @deprecated Use defaults.confirm instead ---@field disable_vim_loader? boolean @deprecated Use performance.vim_loader instead +---@field cmd_prefix? string @deprecated Legacy : commands; use : +---@field auto_import? any @deprecated Removed; pass specs to setup() instead local config = { cmd_name = 'ZPack', @@ -98,24 +100,46 @@ M.setup = function(opts) require('zpack.utils').schedule_notify('zpack.setup() has already been called', vim.log.levels.WARN) return end - state.is_setup = true opts = opts or {} + + -- Report malformed options up front. Validation is advisory: setup + -- continues, and the `type(...) == 'table'` guards on the section merges + -- below ignore a bad field instead of crashing in `vim.tbl_extend`. + local config_errors = require('zpack.validate').validate_config(opts) + if #config_errors > 0 then + require('zpack.utils').schedule_notify( + 'zpack.setup: invalid options:\n ' .. table.concat(config_errors, '\n '), + vim.log.levels.ERROR + ) + end + + state.is_setup = true + local deprecation = require('zpack.deprecation') + -- Record deprecated/removed options for :checkhealth zpack. Assigned fresh + -- so the list reflects only this setup() call. + state.deprecations = {} + for _, key in ipairs(deprecation.deprecated_option_keys) do + if opts[key] ~= nil then + state.deprecations[#state.deprecations + 1] = key + end + end + if opts.cmd_name ~= nil then config.cmd_name = opts.cmd_name end - if opts.defaults ~= nil then + if type(opts.defaults) == 'table' then config.defaults = vim.tbl_extend('force', config.defaults, opts.defaults) end - if opts.performance ~= nil then + if type(opts.performance) == 'table' then config.performance = vim.tbl_extend('force', config.performance, opts.performance) end - if opts.profiling ~= nil then + if type(opts.profiling) == 'table' then config.profiling = vim.tbl_extend('force', config.profiling, opts.profiling) end @@ -131,6 +155,12 @@ M.setup = function(opts) config.performance.vim_loader = not opts.disable_vim_loader end + -- Expose the fully merged config so :checkhealth and other tooling can + -- introspect it. Sections are flat one-level tables, so the `tbl_extend` + -- merges above are sufficient (no `tbl_deep_extend` needed). This is the + -- same table `setup()` works from, not a copy — treat it as read-only. + state.config = config + if config.performance.vim_loader then vim.loader.enable() end @@ -145,12 +175,12 @@ M.setup = function(opts) local ctx = create_context({ confirm = config.defaults.confirm, defaults = config.defaults }) local import = require('zpack.import') - local spec = opts.spec or (opts[1] and opts) or nil + local spec = (type(opts.spec) == 'table' and opts.spec) or (opts[1] and opts) or nil if spec then import.import_specs(spec, ctx) end - if opts.plugins_dir ~= nil then + if type(opts.plugins_dir) == 'string' then deprecation.notify_deprecated('plugins_dir') import.import_specs({ import = opts.plugins_dir }, ctx) elseif not spec then @@ -174,15 +204,13 @@ M.VERSION = api.VERSION ---Return a snapshot of every plugin zpack knows about. Plugins disabled by ---`enabled = false` are pruned during setup and will not appear. See ----|zpack.PluginInfo| for the returned shape. Alias for |zpack.api.get_plugins|. ----@return zpack.PluginInfo[] +---|zpack.PluginInfo| for the returned shape. Alias for |zpack.api.get_plugins|; +---the signature is inherited from there. M.get_plugins = api.get_plugins ---Look up a single plugin by its resolved name. Returns nil when no plugin ----with that name is registered; never throws. Alias for ----|zpack.api.get_plugin|. ----@param name string ----@return zpack.PluginInfo? +---with that name is registered; never throws. Alias for |zpack.api.get_plugin|; +---the signature is inherited from there. M.get_plugin = api.get_plugin return M diff --git a/lua/zpack/lazy.lua b/lua/zpack/lazy.lua index ff3b7f1..4c7b4c8 100644 --- a/lua/zpack/lazy.lua +++ b/lua/zpack/lazy.lua @@ -42,7 +42,7 @@ local function has_lazy_parent(dep_src) for parent_src in pairs(parents) do local parent_entry = state.spec_registry[parent_src] if parent_entry and parent_entry.merged_spec then - local parent_spec = parent_entry.merged_spec + local parent_spec = parent_entry.merged_spec --[[@as zpack.Spec]] if parent_spec.lazy == true then state.lazy_parent_cache[dep_src] = true return true @@ -97,22 +97,20 @@ M.process_all = function(ctx) for _, pack_spec in ipairs(ctx.registered_lazy_packs) do local registry_entry = state.spec_registry[pack_spec.src] - if not registry_entry or not registry_entry.merged_spec then - goto continue - end - local spec = registry_entry.merged_spec - local plugin = registry_entry.plugin + if registry_entry and registry_entry.merged_spec then + local spec = registry_entry.merged_spec --[[@as zpack.Spec]] + local plugin = registry_entry.plugin - local event = utils.resolve_field(spec.event, plugin) - local ft = utils.resolve_field(spec.ft, plugin) + local event = utils.resolve_field(spec.event, plugin) + local ft = utils.resolve_field(spec.ft, plugin) - if event then - event_handler.setup(pack_spec, spec, event) - end - if ft then - ft_handler.setup(pack_spec, ft) + if event then + event_handler.setup(pack_spec, spec, event) + end + if ft then + ft_handler.setup(pack_spec, ft) + end end - ::continue:: end cmd_handler.setup(ctx.registered_lazy_packs) keys_handler.setup(ctx.registered_lazy_packs) diff --git a/lua/zpack/merge.lua b/lua/zpack/merge.lua index 0b3e6d6..5cfd996 100644 --- a/lua/zpack/merge.lua +++ b/lua/zpack/merge.lua @@ -130,7 +130,9 @@ local function merge_and_cond(base, incoming) if type(incoming) == "function" then incoming_result = incoming(plugin) end - return base_result and incoming_result + -- Both results are booleans here (the function branches reassigned + -- them); the cast just states what the analyzer cannot narrow. + return base_result and incoming_result --[[@as boolean]] end end @@ -159,7 +161,9 @@ local function merge_and_enabled(base, incoming) if type(incoming) == "function" then incoming_result = incoming() end - return base_result and incoming_result + -- Both results are booleans here (the function branches reassigned + -- them); the cast just states what the analyzer cannot narrow. + return base_result and incoming_result --[[@as boolean]] end end @@ -178,28 +182,29 @@ function M.merge_specs(base, incoming) for k in pairs(incoming) do all_keys[k] = true end for key in pairs(all_keys) do - if key == "opts" then - -- Skip: opts is resolved lazily via resolve_opts at load time. - -- See the comment above M.field_strategies. - elseif internal_fields[key] then - result[key] = incoming[key] ~= nil and incoming[key] or base[key] - else - local strategy = M.field_strategies[key] or M.OVERRIDE - local base_val = base[key] - local incoming_val = incoming[key] - - if incoming_val == nil then - result[key] = base_val - elseif base_val == nil then - result[key] = incoming_val - elseif strategy == M.OVERRIDE then - result[key] = incoming_val - elseif strategy == M.LIST_EXTEND then - result[key] = extend_unique(to_array(base_val), to_array(incoming_val)) - elseif strategy == M.AND_LOGIC_COND then - result[key] = merge_and_cond(base_val, incoming_val) - elseif strategy == M.AND_LOGIC_ENABLED then - result[key] = merge_and_enabled(base_val, incoming_val) + -- 'opts' is intentionally not merged here: it is resolved lazily via + -- resolve_opts at load time (see the comment above M.field_strategies). + if key ~= "opts" then + if internal_fields[key] then + result[key] = incoming[key] ~= nil and incoming[key] or base[key] + else + local strategy = M.field_strategies[key] or M.OVERRIDE + local base_val = base[key] + local incoming_val = incoming[key] + + if incoming_val == nil then + result[key] = base_val + elseif base_val == nil then + result[key] = incoming_val + elseif strategy == M.OVERRIDE then + result[key] = incoming_val + elseif strategy == M.LIST_EXTEND then + result[key] = extend_unique(to_array(base_val), to_array(incoming_val)) + elseif strategy == M.AND_LOGIC_COND then + result[key] = merge_and_cond(base_val, incoming_val) + elseif strategy == M.AND_LOGIC_ENABLED then + result[key] = merge_and_enabled(base_val, incoming_val) + end end end end diff --git a/lua/zpack/plugin_loader.lua b/lua/zpack/plugin_loader.lua index 00d6859..8211665 100644 --- a/lua/zpack/plugin_loader.lua +++ b/lua/zpack/plugin_loader.lua @@ -94,7 +94,8 @@ M.process_spec = function(pack_spec, opts) registry_entry.load_status = "loading" - local spec = registry_entry.merged_spec + -- merged_spec is always populated post-resolve_all, when plugins load. + local spec = registry_entry.merged_spec --[[@as zpack.Spec]] local plugin = registry_entry.plugin if not plugin then @@ -102,7 +103,8 @@ M.process_spec = function(pack_spec, opts) return end - local name = plugin.spec.name + -- vim.pack.add's load callback has populated spec.name by this point. + local name = plugin.spec.name --[[@as string]] vim.cmd.packadd({ name, bang = opts.bang }) -- :packadd sources plugin/ but never after/plugin/. Source them explicitly. diff --git a/lua/zpack/startup.lua b/lua/zpack/startup.lua index 9e1d766..1f8d6e5 100644 --- a/lua/zpack/startup.lua +++ b/lua/zpack/startup.lua @@ -101,7 +101,7 @@ M.process_all = function(ctx) end local entry = state.spec_registry[pack_spec.src] - local spec = entry.merged_spec + local spec = entry.merged_spec --[[@as zpack.Spec]] if spec.config or entry.has_opts then loader.run_config(pack_spec.src, entry.plugin, spec) end @@ -109,7 +109,7 @@ M.process_all = function(ctx) for _, pack_spec in ipairs(sorted_packs) do local entry = state.spec_registry[pack_spec.src] - local spec = entry.merged_spec + local spec = entry.merged_spec --[[@as zpack.Spec]] local keys = util.resolve_field(spec.keys, entry.plugin) if keys then keymap.apply_keys(keys) diff --git a/lua/zpack/state.lua b/lua/zpack/state.lua index 7c58c61..d28b82f 100644 --- a/lua/zpack/state.lua +++ b/lua/zpack/state.lua @@ -3,6 +3,16 @@ local M = {} ---@type boolean M.is_setup = false +---The fully merged config from the last successful `setup()`. Populated for +---introspection (e.g. |zpack.health|); nil until `setup()` completes. +---@type zpack.Config? +M.config = nil + +---Names of deprecated/removed `setup()` options the user passed, recorded so +---`:checkhealth zpack` can report them. +---@type string[] +M.deprecations = {} + M.lazy_group = vim.api.nvim_create_augroup('LazyPack', { clear = true }) M.startup_group = vim.api.nvim_create_augroup('StartupPack', { clear = true }) M.lazy_build_group = vim.api.nvim_create_augroup('LazyBuildPack', { clear = true }) diff --git a/lua/zpack/validate.lua b/lua/zpack/validate.lua new file mode 100644 index 0000000..f691a08 --- /dev/null +++ b/lua/zpack/validate.lua @@ -0,0 +1,147 @@ +---Input validation for zpack. +--- +---Malformed `setup()` options or plugin specs otherwise surface as cryptic +---downstream errors (a `vim.tbl_extend` crash, a nil index deep in the +---loader). This module turns them into actionable, field-named messages. +--- +---Each field check runs |vim.validate()| wrapped in `pcall`, so a failure +---is collected as a message string instead of thrown. Callers decide what to +---do with the collected errors — `setup()` aborts on config errors, `import` +---warns on spec errors, and `health` reports them. This module is a leaf +---(no `require`s) so |zpack.health| can reuse it freely. + +local M = {} + +---@alias zpack.validate.Type string|string[] + +---Run one vim.validate check, appending a message to `errors` on failure. +---@param errors string[] +---@param name string Field path, e.g. 'defaults.confirm' +---@param value any +---@param validator zpack.validate.Type +local function check(errors, name, value, validator) + -- optional = true: every config/spec field is optional (nil means unset). + local ok, err = pcall(vim.validate, name, value, validator, true) + if not ok then + errors[#errors + 1] = err + end +end + +---Validate the options table passed to `zpack.setup()`. +--- +---Validates the raw `opts` (rather than the post-merge config) because the +---per-section `vim.tbl_extend('force', ...)` merges in `setup()` themselves +---throw when handed a non-table — validation has to run first. It is also +---safe to call on a merged `zpack.Config`: the legacy/`spec` fields are +---simply absent and pass as optional, which is how |zpack.health| reuses it. +--- +---`cmd_name`/`cmd_prefix` are intentionally excluded: `zpack.commands` +---validates them (type *and* naming rule) and degrades gracefully, so +---re-checking here would only double up the notification. +---@param opts any The table passed to `zpack.setup()` +---@return string[] errors Field-path messages; empty when valid +function M.validate_config(opts) + if type(opts) ~= 'table' then + return { ('zpack.setup: expected table, got %s'):format(type(opts)) } + end + + local errors = {} + check(errors, 'spec', opts.spec, 'table') + check(errors, 'defaults', opts.defaults, 'table') + check(errors, 'performance', opts.performance, 'table') + check(errors, 'profiling', opts.profiling, 'table') + + if type(opts.defaults) == 'table' then + check(errors, 'defaults.cond', opts.defaults.cond, { 'boolean', 'function' }) + check(errors, 'defaults.confirm', opts.defaults.confirm, 'boolean') + end + if type(opts.performance) == 'table' then + check(errors, 'performance.vim_loader', opts.performance.vim_loader, 'boolean') + end + if type(opts.profiling) == 'table' then + check(errors, 'profiling.loader', opts.profiling.loader, 'boolean') + check(errors, 'profiling.require', opts.profiling.require, 'boolean') + end + + -- Legacy options: deprecated but still honored, so still type-checked. + check(errors, 'confirm', opts.confirm, 'boolean') + check(errors, 'disable_vim_loader', opts.disable_vim_loader, 'boolean') + check(errors, 'plugins_dir', opts.plugins_dir, 'string') + + return errors +end + +---Expected type for each named `zpack.Spec` field. Polymorphic fields (event, +---cmd, ft, keys, version, dependencies) are checked loosely against the union +---of their base types — enough to catch `event = 123` without false positives +---on the function/table forms. The positional `[1]` source is checked +---separately in `validate_spec` so this table stays string-keyed. +---@type table +local SPEC_FIELD_TYPES = { + src = 'string', + dir = 'string', + url = 'string', + name = 'string', + main = 'string', + import = 'string', + sem_version = 'string', + branch = 'string', + tag = 'string', + commit = 'string', + lazy = 'boolean', + module = 'boolean', + priority = 'number', + init = 'function', + enabled = { 'boolean', 'function' }, + cond = { 'boolean', 'function' }, + build = { 'string', 'function' }, + config = { 'function', 'boolean' }, + opts = { 'table', 'function' }, + event = { 'string', 'table', 'function' }, + cmd = { 'string', 'table', 'function' }, + ft = { 'string', 'table', 'function' }, + keys = { 'string', 'table', 'function' }, + pattern = { 'string', 'table' }, + version = { 'string', 'table' }, + dependencies = { 'string', 'table' }, +} + +---`SPEC_FIELD_TYPES` keys in a stable sort order, so a spec with several bad +---fields reports them deterministically. The sort is invariant — done once. +local SORTED_SPEC_FIELDS = vim.tbl_keys(SPEC_FIELD_TYPES) +table.sort(SORTED_SPEC_FIELDS) + +---Validate a single plugin spec. +---@param spec any A `zpack.Spec` entry +---@return string[] errors Field-named messages; empty when valid +function M.validate_spec(spec) + if type(spec) ~= 'table' then + return { ('expected spec table, got %s'):format(type(spec)) } + end + + local errors = {} + check(errors, '[1]', spec[1], 'string') + for _, field in ipairs(SORTED_SPEC_FIELDS) do + check(errors, field, spec[field], SPEC_FIELD_TYPES[field]) + end + + -- Every field above is optional, so a spec with no source at all passes + -- every type check yet cannot be loaded — `import.normalize_source` would + -- otherwise fail and the spec would silently never import. An `import` + -- spec recurses into a directory and legitimately carries no source. + if spec.import == nil and spec[1] == nil and spec.src == nil + and spec.url == nil and spec.dir == nil then + errors[#errors + 1] = 'spec has no plugin source — set one of [1], src, url, dir, or import' + end + + return errors +end + +---Best-effort human-readable identifier for a spec, for error messages. +---@param spec table +---@return string +function M.spec_label(spec) + return spec[1] or spec.src or spec.url or spec.dir or spec.name or spec.import or '' +end + +return M diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..657cae2 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "simple", + "include-component-in-tag": false, + "packages": { + ".": { + "package-name": "zpack.nvim" + } + } +} diff --git a/run_tests.lua b/run_tests.lua deleted file mode 100644 index fa4492b..0000000 --- a/run_tests.lua +++ /dev/null @@ -1,13 +0,0 @@ -local project_root = vim.fn.getcwd() - -if vim.fn.filereadable(project_root .. '/run_tests.lua') == 0 then - error('run_tests.lua must be run from the zpack.nvim root directory. Are you in the right directory?') -end - -package.path = project_root .. '/lua/?.lua;' - .. project_root .. '/lua/?/init.lua;' - .. project_root .. '/tests/?.lua;' - .. package.path - -require('run_all') -vim.cmd('qa!') diff --git a/tests/TESTING.md b/tests/TESTING.md index 989d3a3..c963fa9 100644 --- a/tests/TESTING.md +++ b/tests/TESTING.md @@ -2,78 +2,114 @@ Comprehensive test suite for zpack.nvim covering all major flows and lazy-loading paths. +The suite runs under [busted](https://lunarmodules.github.io/busted/). Because +the tests exercise real Neovim APIs (`vim.api`, `vim.pack`, autocmds, +`vim.uv`, ...), busted runs *inside* Neovim via `nvim -l` rather than under a +standalone Lua interpreter. + ## Running Tests -### Run all tests +### One-time setup -**Must be run from the project root directory** — `run_tests.lua` uses `getcwd()` to set up `package.path`, so running from a different directory will fail to find modules. +busted and its dependencies are installed into a project-local LuaRocks tree +(`.luarocks/`, gitignored). Install it built against LuaJIT so its native +dependency (luafilesystem) is ABI-compatible with Neovim's bundled LuaJIT: ```bash -cd /path/to/zpack.nvim -nvim -u NONE --headless -S run_tests.lua +luarocks --lua-version=5.1 --lua-dir= --tree .luarocks install busted 2.3.0 ``` -Or from within Neovim after `:cd` to the project root: +`` is your LuaJIT install prefix — e.g. `$(brew --prefix luajit)` +on macOS. -```vim -:cd /path/to/zpack.nvim -:source run_tests.lua -``` +### Run all tests -### Run specific test module +From the project root: -```vim -:lua package.path = vim.fn.getcwd() .. '/lua/?.lua;' .. vim.fn.getcwd() .. '/tests/?.lua;' .. package.path -:lua require('setup_test')() +```bash +nvim -u NONE -l tests/busted.lua ``` -### Test Results +The command exits 0 when the suite is green and non-zero when any test fails. + +### Run a subset -All tests use mocked `vim.pack.add` to avoid actual plugin installation attempts. This allows tests to run quickly and reliably without network access or real plugin repositories. +busted's CLI flags are passed through. For example, run only tests whose name +matches a pattern, or shuffle run order to surface ordering dependencies: + +```bash +nvim -u NONE -l tests/busted.lua --filter "lazy" +nvim -u NONE -l tests/busted.lua --shuffle +``` ## Test Structure -- `helpers.lua` - Test helper functions and assertions -- `*_test.lua` - Individual test modules -- `run_all.lua` - Test runner that executes all tests +- `tests/busted.lua` — bootstraps busted to run in-process under `nvim -l`. +- `tests/*_test.lua` — native busted spec files. Each registers its + `describe`/`it` blocks at load time; busted discovers them directly via the + `.busted` `pattern`. +- `tests/helpers.lua` — the mocked `vim.pack` test environment + (`setup_test_env`/`cleanup_test_env`) plus shared utilities. It also + registers the `assert.contains` luassert assertion. +- `tests/pack_update_test_helpers.lua` — parameterised cases shared by + `zupdate_test.lua` and `zrestore_test.lua`. It is `require`'d, not discovered + as a spec (its filename is intentionally not `*_test.lua`). +- `.busted` — busted configuration (test root and spec-file pattern). + +All tests use a mocked `vim.pack` to avoid real plugin installation, so they +run quickly and without network access. ## Writing New Tests -Use the test helpers to write new tests: +Add a `tests/_test.lua` spec file. busted discovers it automatically — +there is no registration step: ```lua local helpers = require('helpers') -return function() - helpers.describe("Your Test Suite", function() - helpers.test("should do something", function() - helpers.setup_test_env() - - -- Your test code here - helpers.assert_equal(actual, expected, "message") +describe("Your Test Suite", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) - helpers.cleanup_test_env() - end) + it("should do something", function() + -- Your test code here + assert.are.equal(expected, actual) end) -end +end) ``` +Pure unit tests that do not touch the mocked `vim.pack` environment can omit +the `before_each`/`after_each` pair. + ### Available Assertions -- `assert_equal(actual, expected, msg)` - Check equality -- `assert_true(condition, msg)` - Check if true -- `assert_false(condition, msg)` - Check if false -- `assert_nil(value, msg)` - Check if nil -- `assert_not_nil(value, msg)` - Check if not nil -- `assert_table_contains(tbl, value, msg)` - Check if table contains value +Assertions come from busted's bundled [luassert](https://github.com/lunarmodules/luassert): + +- `assert.are.equal(expected, actual)` — `==` (identity) equality +- `assert.are.same(expected, actual)` — deep/recursive equality +- `assert.is_truthy(value)` / `assert.is_falsy(value)` — truthiness +- `assert.is_true(value)` / `assert.is_false(value)` — strict boolean equality +- `assert.is_nil(value)` / `assert.is_not_nil(value)` — nil checks +- `assert.contains(tbl, value)` — list membership (registered by `helpers.lua`) + +A failed assertion is reported by busted as a **failure**; an uncaught error +(nil index, `require` failure, ...) is reported as an **error**. ### Test Environment -- `setup_test_env()` - Initialize test environment -- `cleanup_test_env()` - Clean up after test -- Always call these at the start and end of each test to ensure isolation +- `before_each(helpers.setup_test_env)` installs the mocked `vim.pack` + environment and the shared `_G.test_state` table before each test. +- `after_each(helpers.cleanup_test_env)` restores the real `vim.*` functions + and resets zpack's module state after each test — on both the passing and + failing path — so tests stay isolated. ## Notes -- Each test should clean up after itself to prevent state pollution -- Mock plugins are used (e.g., 'test/plugin') to avoid actual vim.pack operations +- Test isolation is handled by `before_each`/`after_each`; individual tests do + not need to clean up the mock environment themselves. +- Mock plugins are used (e.g. `test/plugin`) to avoid actual `vim.pack` operations. +- CI runs the suite (`nvim -u NONE -l tests/busted.lua`) on every push and + pull request; see `.github/workflows/tests.yml`. CI's LuaRocks is already + bound to LuaJIT, so it installs busted with just + `luarocks install --tree .luarocks busted 2.3.0` — no `--lua-version` / + `--lua-dir` needed. diff --git a/tests/busted.lua b/tests/busted.lua new file mode 100644 index 0000000..ad1e0f4 --- /dev/null +++ b/tests/busted.lua @@ -0,0 +1,26 @@ +-- Bootstrap busted to run the zpack suite inside Neovim via `nvim -l`. +-- +-- The suite exercises real Neovim APIs (vim.api, vim.pack, autocmds, +-- vim.uv, ...), so it must run inside Neovim rather than standalone Lua. +-- busted is installed into a project-local LuaRocks tree (.luarocks/), built +-- against LuaJIT so its C dependency (luafilesystem) is ABI-compatible with +-- Neovim's bundled LuaJIT. Running busted in-process (standalone = false) +-- avoids the hang seen when busted re-execs through an external interpreter. +-- +-- Usage (from the repo root): +-- nvim -l tests/busted.lua + +local root = vim.fn.getcwd() +local rocks = root .. '/.luarocks/share/lua/5.1' + +package.path = table.concat({ + rocks .. '/?.lua', + rocks .. '/?/init.lua', + root .. '/lua/?.lua', + root .. '/lua/?/init.lua', + root .. '/tests/?.lua', + package.path, +}, ';') +package.cpath = root .. '/.luarocks/lib/lua/5.1/?.so;' .. package.cpath + +require('busted.runner')({ standalone = false }) diff --git a/tests/cmd_name_test.lua b/tests/cmd_name_test.lua index 6efc4b1..45fba5a 100644 --- a/tests/cmd_name_test.lua +++ b/tests/cmd_name_test.lua @@ -1,128 +1,89 @@ local helpers = require('helpers') -return function() - helpers.describe("Command Name Configuration", function() - helpers.test("default cmd_name creates :ZPack command", function() - helpers.setup_test_env() +describe("Command Name Configuration", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) - require('zpack').setup({ spec = {}, defaults = { confirm = false } }) + it("default cmd_name creates :ZPack command", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false } }) - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_not_nil(cmds['ZPack'], "ZPack command should exist") - - helpers.cleanup_test_env() - end) - - helpers.test("custom cmd_name creates the configured command", function() - helpers.setup_test_env() - - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = 'Pack' }) - - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_not_nil(cmds['Pack'], "Pack command should exist") - helpers.assert_nil(cmds['ZPack'], "Default ZPack command should not exist with custom name") - - helpers.cleanup_test_env() - helpers.delete_zpack_commands('Pack') - end) - - helpers.test("subcommand completion lists available subcommands", function() - helpers.setup_test_env() - - require('zpack').setup({ spec = {}, defaults = { confirm = false } }) - - local completions = vim.fn.getcompletion('ZPack ', 'cmdline') - helpers.assert_table_contains(completions, 'update', "update subcommand") - helpers.assert_table_contains(completions, 'restore', "restore subcommand") - helpers.assert_table_contains(completions, 'clean', "clean subcommand") - helpers.assert_table_contains(completions, 'build', "build subcommand") - helpers.assert_table_contains(completions, 'load', "load subcommand") - helpers.assert_table_contains(completions, 'delete', "delete subcommand") - - helpers.cleanup_test_env() - end) - - helpers.test("empty cmd_name is rejected", function() - helpers.setup_test_env() - - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = '' }) - - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_nil(cmds['ZPack'], "ZPack command should not exist when cmd_name rejected") - - helpers.cleanup_test_env() - end) - - helpers.test("lowercase cmd_name is rejected", function() - helpers.setup_test_env() - - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = 'pack' }) - - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_nil(cmds['pack'], "Commands with lowercase name should not be created") - helpers.assert_nil(cmds['ZPack'], "Default command should not be created") - - helpers.cleanup_test_env() - end) - - helpers.test("cmd_name with hyphen is rejected", function() - helpers.setup_test_env() - - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = 'My-Pack' }) + local cmds = vim.api.nvim_get_commands({}) + assert.is_not_nil(cmds['ZPack'], "ZPack command should exist") + end) - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_nil(cmds['My-Pack'], "Command with hyphen should not be created") - helpers.assert_nil(cmds['ZPack'], "Default command should not be created") + it("custom cmd_name creates the configured command", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = 'Pack' }) - helpers.cleanup_test_env() - end) + local cmds = vim.api.nvim_get_commands({}) + assert.is_not_nil(cmds['Pack'], "Pack command should exist") + assert.is_nil(cmds['ZPack'], "Default ZPack command should not exist with custom name") + helpers.delete_zpack_commands('Pack') + end) - helpers.test("cmd_name starting with digit is rejected", function() - helpers.setup_test_env() + it("subcommand completion lists available subcommands", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false } }) - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = '123' }) + local completions = vim.fn.getcompletion('ZPack ', 'cmdline') + assert.contains(completions, 'update') + assert.contains(completions, 'restore') + assert.contains(completions, 'clean') + assert.contains(completions, 'build') + assert.contains(completions, 'load') + assert.contains(completions, 'delete') + end) - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_nil(cmds['123'], "Command starting with digit should not be created") - helpers.assert_nil(cmds['ZPack'], "Default command should not be created") + it("empty cmd_name is rejected", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = '' }) - helpers.cleanup_test_env() - end) + local cmds = vim.api.nvim_get_commands({}) + assert.is_nil(cmds['ZPack'], "ZPack command should not exist when cmd_name rejected") + end) - helpers.test("cmd_name with special characters is rejected", function() - helpers.setup_test_env() + it("lowercase cmd_name is rejected", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = 'pack' }) - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = 'Pack!' }) + local cmds = vim.api.nvim_get_commands({}) + assert.is_nil(cmds['pack'], "Commands with lowercase name should not be created") + assert.is_nil(cmds['ZPack'], "Default command should not be created") + end) - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_nil(cmds['Pack!'], "Command with special char should not be created") - helpers.assert_nil(cmds['ZPack'], "Default command should not be created") + it("cmd_name with hyphen is rejected", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = 'My-Pack' }) - helpers.cleanup_test_env() - end) + local cmds = vim.api.nvim_get_commands({}) + assert.is_nil(cmds['My-Pack'], "Command with hyphen should not be created") + assert.is_nil(cmds['ZPack'], "Default command should not be created") + end) - helpers.test("cmd_name with whitespace is rejected", function() - helpers.setup_test_env() + it("cmd_name starting with digit is rejected", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = '123' }) - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = ' Z' }) + local cmds = vim.api.nvim_get_commands({}) + assert.is_nil(cmds['123'], "Command starting with digit should not be created") + assert.is_nil(cmds['ZPack'], "Default command should not be created") + end) - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_nil(cmds[' Z'], "Command with whitespace should not be created") - helpers.assert_nil(cmds['ZPack'], "Default command should not be created") + it("cmd_name with special characters is rejected", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = 'Pack!' }) - helpers.cleanup_test_env() - end) + local cmds = vim.api.nvim_get_commands({}) + assert.is_nil(cmds['Pack!'], "Command with special char should not be created") + assert.is_nil(cmds['ZPack'], "Default command should not be created") + end) - helpers.test("cmd_name with digits after first letter is valid", function() - helpers.setup_test_env() + it("cmd_name with whitespace is rejected", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = ' Z' }) - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = 'Z2' }) + local cmds = vim.api.nvim_get_commands({}) + assert.is_nil(cmds[' Z'], "Command with whitespace should not be created") + assert.is_nil(cmds['ZPack'], "Default command should not be created") + end) - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_not_nil(cmds['Z2'], "Z2 command should exist") + it("cmd_name with digits after first letter is valid", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = 'Z2' }) - helpers.cleanup_test_env() - helpers.delete_zpack_commands('Z2') - end) + local cmds = vim.api.nvim_get_commands({}) + assert.is_not_nil(cmds['Z2'], "Z2 command should exist") + helpers.delete_zpack_commands('Z2') end) -end +end) diff --git a/tests/cmd_prefix_test.lua b/tests/cmd_prefix_test.lua index 92f346e..3b04f20 100644 --- a/tests/cmd_prefix_test.lua +++ b/tests/cmd_prefix_test.lua @@ -1,219 +1,180 @@ local helpers = require('helpers') -return function() - helpers.describe("Legacy cmd_prefix Commands (deprecated)", function() - helpers.test("default prefix registers Z-prefixed legacy commands", function() - helpers.setup_test_env() - - require('zpack').setup({ spec = {}, defaults = { confirm = false } }) - - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_not_nil(cmds['ZUpdate'], "ZUpdate command should exist") - helpers.assert_not_nil(cmds['ZRestore'], "ZRestore command should exist") - helpers.assert_not_nil(cmds['ZClean'], "ZClean command should exist") - helpers.assert_not_nil(cmds['ZBuild'], "ZBuild command should exist") - helpers.assert_not_nil(cmds['ZLoad'], "ZLoad command should exist") - helpers.assert_not_nil(cmds['ZDelete'], "ZDelete command should exist") - - helpers.cleanup_test_env() - end) - - helpers.test("custom cmd_prefix registers prefixed legacy commands", function() - helpers.setup_test_env() - - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_prefix = 'Pack' }) - - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_not_nil(cmds['PackUpdate'], "PackUpdate command should exist") - helpers.assert_not_nil(cmds['PackDelete'], "PackDelete command should exist") - helpers.assert_nil(cmds['ZUpdate'], "default ZUpdate should not exist with a custom prefix") - - helpers.cleanup_test_env() - helpers.delete_zpack_commands(nil, 'Pack') - end) - - helpers.test("empty cmd_prefix registers bare legacy commands", function() - helpers.setup_test_env() - - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_prefix = '' }) - - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_not_nil(cmds['Update'], "Update command should exist") - helpers.assert_not_nil(cmds['Delete'], "Delete command should exist") +describe("Legacy cmd_prefix Commands (deprecated)", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("default prefix registers Z-prefixed legacy commands", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false } }) + + local cmds = vim.api.nvim_get_commands({}) + assert.is_not_nil(cmds['ZUpdate'], "ZUpdate command should exist") + assert.is_not_nil(cmds['ZRestore'], "ZRestore command should exist") + assert.is_not_nil(cmds['ZClean'], "ZClean command should exist") + assert.is_not_nil(cmds['ZBuild'], "ZBuild command should exist") + assert.is_not_nil(cmds['ZLoad'], "ZLoad command should exist") + assert.is_not_nil(cmds['ZDelete'], "ZDelete command should exist") + end) - helpers.cleanup_test_env() - helpers.delete_zpack_commands(nil, '') - end) + it("custom cmd_prefix registers prefixed legacy commands", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_prefix = 'Pack' }) - helpers.test("cmd_prefix with digits after the first letter is valid", function() - helpers.setup_test_env() + local cmds = vim.api.nvim_get_commands({}) + assert.is_not_nil(cmds['PackUpdate'], "PackUpdate command should exist") + assert.is_not_nil(cmds['PackDelete'], "PackDelete command should exist") + assert.is_nil(cmds['ZUpdate'], "default ZUpdate should not exist with a custom prefix") + helpers.delete_zpack_commands(nil, 'Pack') + end) - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_prefix = 'Z2' }) + it("empty cmd_prefix registers bare legacy commands", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_prefix = '' }) - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_not_nil(cmds['Z2Update'], "Z2Update command should exist") - helpers.assert_not_nil(cmds['Z2Delete'], "Z2Delete command should exist") + local cmds = vim.api.nvim_get_commands({}) + assert.is_not_nil(cmds['Update'], "Update command should exist") + assert.is_not_nil(cmds['Delete'], "Delete command should exist") + helpers.delete_zpack_commands(nil, '') + end) - helpers.cleanup_test_env() - helpers.delete_zpack_commands(nil, 'Z2') - end) + it("cmd_prefix with digits after the first letter is valid", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_prefix = 'Z2' }) - helpers.test("invoking a legacy command warns with its :ZPack replacement", function() - helpers.setup_test_env() + local cmds = vim.api.nvim_get_commands({}) + assert.is_not_nil(cmds['Z2Update'], "Z2Update command should exist") + assert.is_not_nil(cmds['Z2Delete'], "Z2Delete command should exist") + helpers.delete_zpack_commands(nil, 'Z2') + end) - require('zpack').setup({ spec = { { 'test/plugin-a' } }, defaults = { confirm = false } }) + it("invoking a legacy command warns with its :ZPack replacement", function() + require('zpack').setup({ spec = { { 'test/plugin-a' } }, defaults = { confirm = false } }) - helpers.flush_pending() - _G.test_state.notifications = {} + helpers.flush_pending() + _G.test_state.notifications = {} - vim.cmd('ZClean') - helpers.flush_pending() + vim.cmd('ZClean') + helpers.flush_pending() - local found = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find(":ZClean is deprecated", 1, true) - and notif.msg:find("Use :ZPack clean", 1, true) then - found = true - break - end + local found = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find(":ZClean is deprecated", 1, true) + and notif.msg:find("Use :ZPack clean", 1, true) then + found = true + break end - helpers.assert_true(found, "invoking :ZClean should warn to use :ZPack clean") - - helpers.cleanup_test_env() - end) - - helpers.test("legacy commands reference the configured cmd_name", function() - helpers.setup_test_env() + end + assert.is_truthy(found, "invoking :ZClean should warn to use :ZPack clean") + end) - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = 'MyPack' }) + it("legacy commands reference the configured cmd_name", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_name = 'MyPack' }) - helpers.flush_pending() - _G.test_state.notifications = {} + helpers.flush_pending() + _G.test_state.notifications = {} - vim.cmd('ZLoad') -- no bang, no arg -> warns "Use :! load" - helpers.flush_pending() + vim.cmd('ZLoad') -- no bang, no arg -> warns "Use :! load" + helpers.flush_pending() - local found = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find(':MyPack! load', 1, true) then - found = true - break - end + local found = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find(':MyPack! load', 1, true) then + found = true + break end - helpers.assert_true(found, "legacy :ZLoad should point at the configured :MyPack command") - - helpers.cleanup_test_env() - helpers.delete_zpack_commands('MyPack') - end) - - helpers.test("legacy command with extra arguments warns like the dispatcher", function() - helpers.setup_test_env() + end + assert.is_truthy(found, "legacy :ZLoad should point at the configured :MyPack command") + helpers.delete_zpack_commands('MyPack') + end) - require('zpack').setup({ spec = { { 'test/plugin-a' } }, defaults = { confirm = false } }) + it("legacy command with extra arguments warns like the dispatcher", function() + require('zpack').setup({ spec = { { 'test/plugin-a' } }, defaults = { confirm = false } }) - helpers.flush_pending() - _G.test_state.notifications = {} + helpers.flush_pending() + _G.test_state.notifications = {} - vim.cmd('ZUpdate plugin-a extra-arg') - helpers.flush_pending() + vim.cmd('ZUpdate plugin-a extra-arg') + helpers.flush_pending() - helpers.assert_equal(#_G.test_state.vim_pack_update_calls, 0, "update must not run when given extra arguments") + assert.are.equal(0, #_G.test_state.vim_pack_update_calls) - local found_warning = false - local misleading_error = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find('at most one argument') and notif.level == vim.log.levels.WARN then - found_warning = true - end - if notif.msg:find('not found in spec') then - misleading_error = true - end + local found_warning = false + local misleading_error = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find('at most one argument') and notif.level == vim.log.levels.WARN then + found_warning = true end - helpers.assert_true(found_warning, "legacy :ZUpdate should warn about too many arguments") - helpers.assert_false(misleading_error, 'legacy :ZUpdate must not emit the misleading joined-args error') - - helpers.cleanup_test_env() - end) - - helpers.test("legacy clean rejects positional arguments without a raw Vim error", function() - helpers.setup_test_env() + if notif.msg:find('not found in spec') then + misleading_error = true + end + end + assert.is_truthy(found_warning, "legacy :ZUpdate should warn about too many arguments") + assert.is_falsy(misleading_error, 'legacy :ZUpdate must not emit the misleading joined-args error') + end) - require('zpack').setup({ spec = { { 'test/plugin-a' } }, defaults = { confirm = false } }) + it("legacy clean rejects positional arguments without a raw Vim error", function() + require('zpack').setup({ spec = { { 'test/plugin-a' } }, defaults = { confirm = false } }) - helpers.flush_pending() - _G.test_state.notifications = {} + helpers.flush_pending() + _G.test_state.notifications = {} - local ok = pcall(vim.cmd, 'ZClean junk') - helpers.flush_pending() + local ok = pcall(vim.cmd, 'ZClean junk') + helpers.flush_pending() - helpers.assert_true(ok, "legacy :ZClean junk must not raise a raw Vim parse error") + assert.is_truthy(ok, "legacy :ZClean junk must not raise a raw Vim parse error") - local found_warning = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find('no arguments') and notif.level == vim.log.levels.WARN then - found_warning = true - break - end + local found_warning = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find('no arguments') and notif.level == vim.log.levels.WARN then + found_warning = true + break end - helpers.assert_true(found_warning, "legacy :ZClean should warn that clean accepts no arguments") - - helpers.cleanup_test_env() - end) - - helpers.test("invalid cmd_prefix emits an error plus a deprecation notice and registers no legacy commands", function() - helpers.setup_test_env() + end + assert.is_truthy(found_warning, "legacy :ZClean should warn that clean accepts no arguments") + end) - require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_prefix = 'My-Pack' }) + it("invalid cmd_prefix emits an error plus a deprecation notice and registers no legacy commands", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false }, cmd_prefix = 'My-Pack' }) - helpers.flush_pending() + helpers.flush_pending() - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_nil(cmds['My-PackUpdate'], "no legacy commands should be registered for an invalid prefix") - helpers.assert_nil(cmds['ZUpdate'], "an invalid prefix should not fall back to the default Z prefix") - helpers.assert_not_nil(cmds['ZPack'], "the primary :ZPack command should still be registered") + local cmds = vim.api.nvim_get_commands({}) + assert.is_nil(cmds['My-PackUpdate'], "no legacy commands should be registered for an invalid prefix") + assert.is_nil(cmds['ZUpdate'], "an invalid prefix should not fall back to the default Z prefix") + assert.is_not_nil(cmds['ZPack'], "the primary :ZPack command should still be registered") - local found_error = false - local found_deprecation = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find('Invalid cmd_prefix') and notif.level == vim.log.levels.ERROR then - found_error = true - end - if notif.msg:find('DEPRECATED') and notif.msg:find('cmd_prefix', 1, true) - and notif.level == vim.log.levels.WARN then - found_deprecation = true - end + local found_error = false + local found_deprecation = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find('Invalid cmd_prefix') and notif.level == vim.log.levels.ERROR then + found_error = true end - helpers.assert_true(found_error, "an invalid cmd_prefix should emit an error notification") - helpers.assert_true(found_deprecation, "an invalid cmd_prefix should also emit the cmd_prefix deprecation notice") - - helpers.cleanup_test_env() - end) - - helpers.test("non-string cmd_prefix does not abort setup", function() - helpers.setup_test_env() + if notif.msg:find('DEPRECATED') and notif.msg:find('cmd_prefix', 1, true) + and notif.level == vim.log.levels.WARN then + found_deprecation = true + end + end + assert.is_truthy(found_error, "an invalid cmd_prefix should emit an error notification") + assert.is_truthy(found_deprecation, "an invalid cmd_prefix should also emit the cmd_prefix deprecation notice") + end) - local ok = pcall(require('zpack').setup, { - spec = {}, - defaults = { confirm = false }, - cmd_prefix = {}, - }) - helpers.assert_true(ok, "setup() must not abort when cmd_prefix is not a string") + it("non-string cmd_prefix does not abort setup", function() + local ok = pcall(require('zpack').setup, { + spec = {}, + defaults = { confirm = false }, + cmd_prefix = {}, + }) + assert.is_truthy(ok, "setup() must not abort when cmd_prefix is not a string") - helpers.flush_pending() + helpers.flush_pending() - local cmds = vim.api.nvim_get_commands({}) - helpers.assert_not_nil(cmds['ZPack'], "the primary :ZPack command should still be registered") + local cmds = vim.api.nvim_get_commands({}) + assert.is_not_nil(cmds['ZPack'], "the primary :ZPack command should still be registered") - local found_error = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find('Invalid cmd_prefix') and notif.level == vim.log.levels.ERROR then - found_error = true - break - end + local found_error = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find('Invalid cmd_prefix') and notif.level == vim.log.levels.ERROR then + found_error = true + break end - helpers.assert_true(found_error, "a non-string cmd_prefix should emit an error notification") - - helpers.cleanup_test_env() - end) + end + assert.is_truthy(found_error, "a non-string cmd_prefix should emit an error notification") end) -end +end) diff --git a/tests/conditional_test.lua b/tests/conditional_test.lua index db8e96d..a93345e 100644 --- a/tests/conditional_test.lua +++ b/tests/conditional_test.lua @@ -1,399 +1,336 @@ local helpers = require('helpers') -return function() - helpers.describe("Conditional Loading", function() - helpers.test("enabled=false prunes plugin from registry", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - enabled = false, - }, +describe("Conditional Loading", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("enabled=false prunes plugin from registry", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { + { + 'test/plugin', + enabled = false, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - helpers.assert_nil(state.spec_registry[src], "enabled=false plugin should be pruned from spec_registry") - helpers.assert_false( - vim.tbl_contains(state.registered_plugin_names, 'plugin'), - "Plugin should not be in registered_plugin_names when enabled=false" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['plugin'], - "Plugin should not be passed to vim.pack.add when enabled=false" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("enabled=true allows plugin registration", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - enabled = true, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - helpers.assert_not_nil(state.spec_registry[src], "Plugin should be registered when enabled=true") - - helpers.cleanup_test_env() - end) - - helpers.test("enabled function returning false prunes plugin from registry", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - enabled = function() return false end, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - helpers.assert_nil(state.spec_registry[src], "enabled fn returning false should prune from spec_registry") - helpers.assert_false( - vim.tbl_contains(state.registered_plugin_names, 'plugin'), - "Plugin should not be in registered_plugin_names" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['plugin'], - "Plugin should not be passed to vim.pack.add" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("enabled function returning nil counts as disabled and prunes", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - enabled = function() return nil end, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_nil( - state.spec_registry['https://github.com/test/plugin'], - "enabled fn returning nil should be treated as disabled and pruned" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['plugin'], - "nil-returning enabled should not reach vim.pack.add" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("enabled function returning true allows registration", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - enabled = function() return true end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + assert.is_nil(state.spec_registry[src], "enabled=false plugin should be pruned from spec_registry") + assert.is_falsy( + vim.tbl_contains(state.registered_plugin_names, 'plugin'), + "Plugin should not be in registered_plugin_names when enabled=false" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['plugin'], + "Plugin should not be passed to vim.pack.add when enabled=false" + ) + end) + + it("enabled=true allows plugin registration", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { + { + 'test/plugin', + enabled = true, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - helpers.assert_not_nil( - state.spec_registry[src], - "Plugin should be registered when enabled function returns true" - ) + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + assert.is_not_nil(state.spec_registry[src], "Plugin should be registered when enabled=true") + end) - helpers.cleanup_test_env() - end) + it("enabled function returning false prunes plugin from registry", function() + local state = require('zpack.state') - helpers.test("cond=false prevents plugin loading", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + require('zpack').setup({ + spec = { + { + 'test/plugin', + enabled = function() return false end, + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + assert.is_nil(state.spec_registry[src], "enabled fn returning false should prune from spec_registry") + assert.is_falsy( + vim.tbl_contains(state.registered_plugin_names, 'plugin'), + "Plugin should not be in registered_plugin_names" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['plugin'], + "Plugin should not be passed to vim.pack.add" + ) + end) - local spec = { - 'test/plugin', - cond = false, - } + it("enabled function returning nil counts as disabled and prunes", function() + local state = require('zpack.state') - local should_load = utils.check_cond(spec) - helpers.assert_false(should_load, "Plugin should not load when cond=false") + require('zpack').setup({ + spec = { + { + 'test/plugin', + enabled = function() return nil end, + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_nil( + state.spec_registry['https://github.com/test/plugin'], + "enabled fn returning nil should be treated as disabled and pruned" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['plugin'], + "nil-returning enabled should not reach vim.pack.add" + ) + end) - helpers.cleanup_test_env() - end) + it("enabled function returning true allows registration", function() + local state = require('zpack.state') - helpers.test("cond=true allows plugin loading", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + require('zpack').setup({ + spec = { + { + 'test/plugin', + enabled = function() return true end, + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + assert.is_not_nil( + state.spec_registry[src], + "Plugin should be registered when enabled function returns true" + ) + end) - local spec = { - 'test/plugin', - cond = true, - } + it("cond=false prevents plugin loading", function() + local utils = require('zpack.utils') - local should_load = utils.check_cond(spec) - helpers.assert_true(should_load, "Plugin should load when cond=true") + local spec = { + 'test/plugin', + cond = false, + } - helpers.cleanup_test_env() - end) + local should_load = utils.check_cond(spec) + assert.is_falsy(should_load, "Plugin should not load when cond=false") + end) - helpers.test("cond function returning false prevents loading", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + it("cond=true allows plugin loading", function() + local utils = require('zpack.utils') - local spec = { - 'test/plugin', - cond = function() return false end, - } + local spec = { + 'test/plugin', + cond = true, + } - local should_load = utils.check_cond(spec) - helpers.assert_false(should_load, "Plugin should not load when cond function returns false") + local should_load = utils.check_cond(spec) + assert.is_truthy(should_load, "Plugin should load when cond=true") + end) - helpers.cleanup_test_env() - end) + it("cond function returning false prevents loading", function() + local utils = require('zpack.utils') - helpers.test("cond function returning true allows loading", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + local spec = { + 'test/plugin', + cond = function() return false end, + } - local spec = { - 'test/plugin', - cond = function() return true end, - } + local should_load = utils.check_cond(spec) + assert.is_falsy(should_load, "Plugin should not load when cond function returns false") + end) - local should_load = utils.check_cond(spec) - helpers.assert_true(should_load, "Plugin should load when cond function returns true") + it("cond function returning true allows loading", function() + local utils = require('zpack.utils') - helpers.cleanup_test_env() - end) + local spec = { + 'test/plugin', + cond = function() return true end, + } - helpers.test("cond nil defaults to true", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + local should_load = utils.check_cond(spec) + assert.is_truthy(should_load, "Plugin should load when cond function returns true") + end) - local spec = { - 'test/plugin', - } + it("cond nil defaults to true", function() + local utils = require('zpack.utils') - local should_load = utils.check_cond(spec) - helpers.assert_true(should_load, "Plugin should load when cond is nil (default true)") + local spec = { + 'test/plugin', + } - helpers.cleanup_test_env() - end) + local should_load = utils.check_cond(spec) + assert.is_truthy(should_load, "Plugin should load when cond is nil (default true)") + end) - helpers.test("enabled and cond work together", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("enabled and cond work together", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/plugin', - enabled = true, - cond = false, - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + enabled = true, + cond = false, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - helpers.assert_not_nil( - state.spec_registry[src], - "Plugin should be registered (enabled=true)" - ) - - local utils = require('zpack.utils') - local spec = state.spec_registry[src].merged_spec - local should_load = utils.check_cond(spec) - helpers.assert_false(should_load, "Plugin should not load (cond=false)") - - helpers.cleanup_test_env() - end) - - helpers.test("enabled prevents config execution", function() - helpers.setup_test_env() - local config_ran = false - - require('zpack').setup({ - spec = { - { - 'test/plugin', - enabled = false, - config = function() - config_ran = true - end, - }, - }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + assert.is_not_nil( + state.spec_registry[src], + "Plugin should be registered (enabled=true)" + ) + + local utils = require('zpack.utils') + local spec = state.spec_registry[src].merged_spec + local should_load = utils.check_cond(spec) + assert.is_falsy(should_load, "Plugin should not load (cond=false)") + end) - helpers.flush_pending() - helpers.assert_false(config_ran, "Config should not run when enabled=false") + it("enabled prevents config execution", function() + local config_ran = false + + require('zpack').setup({ + spec = { + { + 'test/plugin', + enabled = false, + config = function() + config_ran = true + end, + }, + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() + assert.is_falsy(config_ran, "Config should not run when enabled=false") + end) - helpers.test("lazy=false overrides lazy triggers", function() - helpers.setup_test_env() - local lazy_module = require('zpack.lazy') + it("lazy=false overrides lazy triggers", function() + local lazy_module = require('zpack.lazy') - local spec = { - 'test/plugin', - cmd = 'TestCommand', - lazy = false, - } + local spec = { + 'test/plugin', + cmd = 'TestCommand', + lazy = false, + } - helpers.assert_false(lazy_module.is_lazy(spec), "Plugin should not be lazy when lazy=false") + assert.is_falsy(lazy_module.is_lazy(spec), "Plugin should not be lazy when lazy=false") + end) - helpers.cleanup_test_env() - end) + it("lazy=true forces lazy loading even without triggers", function() + local lazy_module = require('zpack.lazy') - helpers.test("lazy=true forces lazy loading even without triggers", function() - helpers.setup_test_env() - local lazy_module = require('zpack.lazy') + local spec = { + 'test/plugin', + lazy = true, + } - local spec = { - 'test/plugin', - lazy = true, - } + assert.is_truthy(lazy_module.is_lazy(spec), "Plugin should be lazy when lazy=true") + end) - helpers.assert_true(lazy_module.is_lazy(spec), "Plugin should be lazy when lazy=true") + it("default_cond=false prevents loading when spec.cond is nil", function() + local utils = require('zpack.utils') - helpers.cleanup_test_env() - end) + local spec = { + 'test/plugin', + } - helpers.test("default_cond=false prevents loading when spec.cond is nil", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + local should_load = utils.check_cond(spec, nil, false) + assert.is_falsy(should_load, "Plugin should not load when default_cond=false and spec.cond is nil") + end) - local spec = { - 'test/plugin', - } + it("default_cond function returning false prevents loading", function() + local utils = require('zpack.utils') - local should_load = utils.check_cond(spec, nil, false) - helpers.assert_false(should_load, "Plugin should not load when default_cond=false and spec.cond is nil") + local spec = { + 'test/plugin', + } - helpers.cleanup_test_env() - end) + local should_load = utils.check_cond(spec, nil, function() return false end) + assert.is_falsy(should_load, "Plugin should not load when default_cond function returns false") + end) - helpers.test("default_cond function returning false prevents loading", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + it("spec.cond overrides default_cond", function() + local utils = require('zpack.utils') - local spec = { - 'test/plugin', - } + local spec = { + 'test/plugin', + cond = true, + } - local should_load = utils.check_cond(spec, nil, function() return false end) - helpers.assert_false(should_load, "Plugin should not load when default_cond function returns false") + local should_load = utils.check_cond(spec, nil, false) + assert.is_truthy(should_load, "Plugin should load when spec.cond=true even if default_cond=false") + end) - helpers.cleanup_test_env() - end) + it("spec.cond=false overrides default_cond=true", function() + local utils = require('zpack.utils') - helpers.test("spec.cond overrides default_cond", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + local spec = { + 'test/plugin', + cond = false, + } - local spec = { - 'test/plugin', - cond = true, - } + local should_load = utils.check_cond(spec, nil, true) + assert.is_falsy(should_load, "Plugin should not load when spec.cond=false even if default_cond=true") + end) - local should_load = utils.check_cond(spec, nil, false) - helpers.assert_true(should_load, "Plugin should load when spec.cond=true even if default_cond=false") + it("cond function receives nil-safe plugin arg at merge-pipeline time", function() + local state = require('zpack.state') - helpers.cleanup_test_env() - end) + require('zpack').setup({ + spec = { + { + 'test/plugin', + cond = function(plugin) return plugin ~= nil end, + config = function() end, + }, + }, + defaults = { confirm = false }, + }) - helpers.test("spec.cond=false overrides default_cond=true", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + helpers.flush_pending() - local spec = { - 'test/plugin', - cond = false, - } + local entry = state.spec_registry['https://github.com/test/plugin'] + assert.is_not_nil(entry, "Registry entry should exist") + assert.are.equal(true, entry.cond_result) + end) - local should_load = utils.check_cond(spec, nil, true) - helpers.assert_false(should_load, "Plugin should not load when spec.cond=false even if default_cond=true") + it("check_enabled handles merge_and-composed functions without crashing", function() + local merge = require('zpack.merge') + local utils = require('zpack.utils') - helpers.cleanup_test_env() - end) + local base = { enabled = function() return true end } + local incoming = { enabled = function() return false end } + local merged = merge.merge_specs(base, incoming) - helpers.test("cond function receives nil-safe plugin arg at merge-pipeline time", function() - helpers.setup_test_env() - local state = require('zpack.state') + assert.are.equal("function", type(merged.enabled)) + local result = utils.check_enabled(merged) + assert.is_falsy(result, "merged function AND_LOGIC should evaluate to false") - require('zpack').setup({ - spec = { - { - 'test/plugin', - cond = function(plugin) return plugin ~= nil end, - config = function() end, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local entry = state.spec_registry['https://github.com/test/plugin'] - helpers.assert_not_nil(entry, "Registry entry should exist") - helpers.assert_equal( - entry.cond_result, - true, - "cond function should receive a non-nil plugin arg at registration" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("check_enabled handles merge_and-composed functions without crashing", function() - helpers.setup_test_env() - local merge = require('zpack.merge') - local utils = require('zpack.utils') - - local base = { enabled = function() return true end } - local incoming = { enabled = function() return false end } - local merged = merge.merge_specs(base, incoming) - - helpers.assert_equal(type(merged.enabled), "function", "merged enabled should be a function") - local result = utils.check_enabled(merged) - helpers.assert_false(result, "merged function AND_LOGIC should evaluate to false") - - local both_true = merge.merge_specs( - { enabled = function() return true end }, - { enabled = function() return true end } - ) - helpers.assert_true(utils.check_enabled(both_true), "both-true functions should merge to true") - - helpers.cleanup_test_env() - end) + local both_true = merge.merge_specs( + { enabled = function() return true end }, + { enabled = function() return true end } + ) + assert.is_truthy(utils.check_enabled(both_true), "both-true functions should merge to true") end) -end +end) diff --git a/tests/dependencies_test.lua b/tests/dependencies_test.lua index 129c189..a813e19 100644 --- a/tests/dependencies_test.lua +++ b/tests/dependencies_test.lua @@ -1,1657 +1,1487 @@ local helpers = require('helpers') -return function() - helpers.describe("Dependencies Field", function() - helpers.test("string dependency is registered", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { 'test/dep' }, - }, - }, - defaults = { confirm = false }, - }) +describe("Dependencies Field", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) - helpers.flush_pending() - local state = require('zpack.state') - - helpers.assert_not_nil(state.spec_registry['https://github.com/test/parent']) - helpers.assert_not_nil(state.spec_registry['https://github.com/test/dep']) + it("string dependency is registered", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { 'test/dep' }, + }, + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() + local state = require('zpack.state') - helpers.test("array of string dependencies are registered", function() - helpers.setup_test_env() + assert.is_not_nil(state.spec_registry['https://github.com/test/parent']) + assert.is_not_nil(state.spec_registry['https://github.com/test/dep']) + end) - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { 'test/dep1', 'test/dep2' }, - }, + it("array of string dependencies are registered", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { 'test/dep1', 'test/dep2' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - - helpers.assert_not_nil(state.spec_registry['https://github.com/test/dep1']) - helpers.assert_not_nil(state.spec_registry['https://github.com/test/dep2']) + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() + local state = require('zpack.state') - helpers.test("inline spec dependency is registered", function() - helpers.setup_test_env() + assert.is_not_nil(state.spec_registry['https://github.com/test/dep1']) + assert.is_not_nil(state.spec_registry['https://github.com/test/dep2']) + end) - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { - { 'test/dep', opts = { from_dep = true } }, - }, + it("inline spec dependency is registered", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { + { 'test/dep', opts = { from_dep = true } }, }, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - local state = require('zpack.state') - local dep_entry = state.spec_registry['https://github.com/test/dep'] + helpers.flush_pending() + local state = require('zpack.state') + local dep_entry = state.spec_registry['https://github.com/test/dep'] - helpers.assert_not_nil(dep_entry) - helpers.assert_equal(dep_entry.specs[1].opts.from_dep, true) - - helpers.cleanup_test_env() - end) - - helpers.test("dependency graph is populated", function() - helpers.setup_test_env() + assert.is_not_nil(dep_entry) + assert.are.equal(true, dep_entry.specs[1].opts.from_dep) + end) - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { 'test/dep1', 'test/dep2' }, - }, + it("dependency graph is populated", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { 'test/dep1', 'test/dep2' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local parent_src = 'https://github.com/test/parent' + }, + defaults = { confirm = false }, + }) - helpers.assert_not_nil(state.dependency_graph[parent_src]) - helpers.assert_equal(vim.tbl_count(state.dependency_graph[parent_src]), 2) + helpers.flush_pending() + local state = require('zpack.state') + local parent_src = 'https://github.com/test/parent' - helpers.cleanup_test_env() - end) - - helpers.test("dependency specs are marked as dependencies", function() - helpers.setup_test_env() + assert.is_not_nil(state.dependency_graph[parent_src]) + assert.are.equal(2, vim.tbl_count(state.dependency_graph[parent_src])) + end) - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { - { 'test/dep', opts = {} }, - }, + it("dependency specs are marked as dependencies", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { + { 'test/dep', opts = {} }, }, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - local state = require('zpack.state') - local dep_entry = state.spec_registry['https://github.com/test/dep'] + helpers.flush_pending() + local state = require('zpack.state') + local dep_entry = state.spec_registry['https://github.com/test/dep'] - helpers.assert_true(dep_entry.specs[1]._is_dependency, "dependency spec should be marked") + assert.is_truthy(dep_entry.specs[1]._is_dependency, "dependency spec should be marked") + end) - helpers.cleanup_test_env() - end) + it("standalone spec is not marked as dependency", function() + require('zpack').setup({ + spec = { + { 'test/plugin', opts = {} }, + }, + defaults = { confirm = false }, + }) - helpers.test("standalone spec is not marked as dependency", function() - helpers.setup_test_env() + helpers.flush_pending() + local state = require('zpack.state') + local entry = state.spec_registry['https://github.com/test/plugin'] - require('zpack').setup({ - spec = { - { 'test/plugin', opts = {} }, + assert.is_falsy(entry.specs[1]._is_dependency or false) + end) + + it("dependencies are loaded before parent on lazy trigger", function() + local load_order = {} + + require('zpack').setup({ + spec = { + { + 'test/parent', + cmd = 'ParentCmd', + dependencies = { 'test/dep' }, + config = function() + table.insert(load_order, 'parent') + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local entry = state.spec_registry['https://github.com/test/plugin'] - - helpers.assert_false(entry.specs[1]._is_dependency or false) - - helpers.cleanup_test_env() - end) - - helpers.test("dependencies are loaded before parent on lazy trigger", function() - helpers.setup_test_env() - local load_order = {} - - require('zpack').setup({ - spec = { - { - 'test/parent', - cmd = 'ParentCmd', - dependencies = { 'test/dep' }, - config = function() - table.insert(load_order, 'parent') - end, - }, - { - 'test/dep', - config = function() - table.insert(load_order, 'dep') - end, - }, + { + 'test/dep', + config = function() + table.insert(load_order, 'dep') + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_equal(#load_order, 1, "only standalone dep should load at startup") - helpers.assert_equal(load_order[1], 'dep') + }, + defaults = { confirm = false }, + }) - pcall(vim.cmd, 'ParentCmd') - helpers.flush_pending() + helpers.flush_pending() + assert.are.equal(1, #load_order) + assert.are.equal('dep', load_order[1]) - helpers.assert_equal(#load_order, 2, "parent should load on trigger") - helpers.assert_equal(load_order[2], 'parent') + pcall(vim.cmd, 'ParentCmd') + helpers.flush_pending() - helpers.cleanup_test_env() - end) + assert.are.equal(2, #load_order) + assert.are.equal('parent', load_order[2]) + end) - helpers.test("startup plugin dependencies are loaded before parent", function() - helpers.setup_test_env() - local load_order = {} + it("startup plugin dependencies are loaded before parent", function() + local load_order = {} - require('zpack').setup({ - spec = { - { - 'test/parent', - opts = {}, - dependencies = { - { 'test/dep', config = function() table.insert(load_order, 'dep') end }, - }, - config = function() table.insert(load_order, 'parent') end, + require('zpack').setup({ + spec = { + { + 'test/parent', + opts = {}, + dependencies = { + { 'test/dep', config = function() table.insert(load_order, 'dep') end }, }, + config = function() table.insert(load_order, 'parent') end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - helpers.assert_equal(#load_order, 2, "both plugins should load at startup") - helpers.assert_equal(load_order[1], 'dep', "dependency should load first") - helpers.assert_equal(load_order[2], 'parent', "parent should load second") - - helpers.cleanup_test_env() - end) - - helpers.test("dependency-only plugin inherits lazy from parent", function() - helpers.setup_test_env() + assert.are.equal(2, #load_order) + assert.are.equal('dep', load_order[1]) + assert.are.equal('parent', load_order[2]) + end) - require('zpack').setup({ - spec = { - { - 'test/parent', - cmd = 'LazyCmd', - dependencies = { - { 'test/dep-only' }, - }, + it("dependency-only plugin inherits lazy from parent", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + cmd = 'LazyCmd', + dependencies = { + { 'test/dep-only' }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') + }, + defaults = { confirm = false }, + }) - helpers.assert_true( - state.unloaded_plugin_names['dep-only'] or false, - "dependency-only plugin should be lazy when parent is lazy" - ) + helpers.flush_pending() + local state = require('zpack.state') - helpers.cleanup_test_env() - end) + assert.is_truthy( + state.unloaded_plugin_names['dep-only'] or false, + "dependency-only plugin should be lazy when parent is lazy" + ) + end) - helpers.test("standalone spec overrides lazy inheritance from dependency", function() - helpers.setup_test_env() - local load_order = {} + it("standalone spec overrides lazy inheritance from dependency", function() + local load_order = {} - require('zpack').setup({ - spec = { - { - 'test/lazy-parent', - cmd = 'LazyCmd', - dependencies = { - { 'test/shared' }, - }, - }, - { - 'test/shared', - config = function() table.insert(load_order, 'shared') end, + require('zpack').setup({ + spec = { + { + 'test/lazy-parent', + cmd = 'LazyCmd', + dependencies = { + { 'test/shared' }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - - helpers.assert_equal(#load_order, 1, "shared should load at startup") - helpers.assert_equal(load_order[1], 'shared') - helpers.assert_nil( - state.unloaded_plugin_names['shared'], - "standalone spec should make plugin startup, not lazy" - ) - helpers.assert_true( - state.unloaded_plugin_names['lazy-parent'] or false, - "lazy-parent should still be lazy" - ) + { + 'test/shared', + config = function() table.insert(load_order, 'shared') end, + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + + assert.are.equal(1, #load_order) + assert.are.equal('shared', load_order[1]) + assert.is_nil( + state.unloaded_plugin_names['shared'], + "standalone spec should make plugin startup, not lazy" + ) + assert.is_truthy( + state.unloaded_plugin_names['lazy-parent'] or false, + "lazy-parent should still be lazy" + ) + end) - helpers.cleanup_test_env() - end) - - helpers.test("nested dependencies are supported", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { - { - 'test/child', - dependencies = { - { 'test/grandchild' }, - }, + it("nested dependencies are supported", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { + { + 'test/child', + dependencies = { + { 'test/grandchild' }, }, }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') + }, + defaults = { confirm = false }, + }) - helpers.assert_not_nil(state.spec_registry['https://github.com/test/parent']) - helpers.assert_not_nil(state.spec_registry['https://github.com/test/child']) - helpers.assert_not_nil(state.spec_registry['https://github.com/test/grandchild']) + helpers.flush_pending() + local state = require('zpack.state') - helpers.cleanup_test_env() - end) - - helpers.test("duplicate dependencies are not added twice to graph", function() - helpers.setup_test_env() + assert.is_not_nil(state.spec_registry['https://github.com/test/parent']) + assert.is_not_nil(state.spec_registry['https://github.com/test/child']) + assert.is_not_nil(state.spec_registry['https://github.com/test/grandchild']) + end) - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { 'test/dep', 'test/dep' }, - }, + it("duplicate dependencies are not added twice to graph", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { 'test/dep', 'test/dep' }, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - local state = require('zpack.state') - local parent_src = 'https://github.com/test/parent' + helpers.flush_pending() + local state = require('zpack.state') + local parent_src = 'https://github.com/test/parent' - helpers.assert_equal(vim.tbl_count(state.dependency_graph[parent_src]), 1, "should not duplicate dependency") + assert.are.equal(1, vim.tbl_count(state.dependency_graph[parent_src])) + end) - helpers.cleanup_test_env() - end) + it("reverse dependency graph is populated", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { 'test/dep' }, + }, + }, + defaults = { confirm = false }, + }) - helpers.test("reverse dependency graph is populated", function() - helpers.setup_test_env() + helpers.flush_pending() + local state = require('zpack.state') + local dep_src = 'https://github.com/test/dep' + local parent_src = 'https://github.com/test/parent' - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { 'test/dep' }, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local dep_src = 'https://github.com/test/dep' - local parent_src = 'https://github.com/test/parent' - - helpers.assert_not_nil(state.reverse_dependency_graph[dep_src]) - helpers.assert_true(state.reverse_dependency_graph[dep_src][parent_src], "parent should be in reverse graph") - - helpers.cleanup_test_env() - end) - - helpers.test("circular dependency is detected at runtime and handled gracefully", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/a', - dependencies = { - { - 'test/b', - dependencies = { 'test/a' }, - }, + assert.is_not_nil(state.reverse_dependency_graph[dep_src]) + assert.is_truthy(state.reverse_dependency_graph[dep_src][parent_src], "parent should be in reverse graph") + end) + + it("circular dependency is detected at runtime and handled gracefully", function() + require('zpack').setup({ + spec = { + { + 'test/a', + dependencies = { + { + 'test/b', + dependencies = { 'test/a' }, }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local a_src = 'https://github.com/test/a' - local b_src = 'https://github.com/test/b' - - helpers.assert_not_nil(state.spec_registry[a_src], "a should be registered") - helpers.assert_not_nil(state.spec_registry[b_src], "b should be registered") - helpers.assert_equal(state.spec_registry[a_src].load_status, "loaded", "a should be loaded") - helpers.assert_equal(state.spec_registry[b_src].load_status, "loaded", "b should be loaded") - - helpers.cleanup_test_env() - end) - - helpers.test("self-dependency is detected at runtime and handled gracefully", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/self', - dependencies = { 'test/self' }, - }, - }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local a_src = 'https://github.com/test/a' + local b_src = 'https://github.com/test/b' + + assert.is_not_nil(state.spec_registry[a_src], "a should be registered") + assert.is_not_nil(state.spec_registry[b_src], "b should be registered") + assert.are.equal("loaded", state.spec_registry[a_src].load_status) + assert.are.equal("loaded", state.spec_registry[b_src].load_status) + end) - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/self' + it("self-dependency is detected at runtime and handled gracefully", function() + require('zpack').setup({ + spec = { + { + 'test/self', + dependencies = { 'test/self' }, + }, + }, + defaults = { confirm = false }, + }) - helpers.assert_not_nil(state.spec_registry[src], "plugin should be registered") - helpers.assert_equal(state.spec_registry[src].load_status, "loaded", "plugin should be loaded") + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/self' - helpers.cleanup_test_env() - end) + assert.is_not_nil(state.spec_registry[src], "plugin should be registered") + assert.are.equal("loaded", state.spec_registry[src].load_status) + end) - helpers.test("three-way circular dependency in startup plugins is handled gracefully", function() - helpers.setup_test_env() - local load_order = {} + it("three-way circular dependency in startup plugins is handled gracefully", function() + local load_order = {} - require('zpack').setup({ - spec = { - { - 'test/p1', - dependencies = { 'test/p2' }, - config = function() table.insert(load_order, 'p1') end, - }, - { - 'test/p2', - dependencies = { 'test/p3' }, - config = function() table.insert(load_order, 'p2') end, - }, - { - 'test/p3', - dependencies = { 'test/p1' }, - config = function() table.insert(load_order, 'p3') end, - }, + require('zpack').setup({ + spec = { + { + 'test/p1', + dependencies = { 'test/p2' }, + config = function() table.insert(load_order, 'p1') end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - - helpers.assert_equal(state.spec_registry['https://github.com/test/p1'].load_status, "loaded") - helpers.assert_equal(state.spec_registry['https://github.com/test/p2'].load_status, "loaded") - helpers.assert_equal(state.spec_registry['https://github.com/test/p3'].load_status, "loaded") - helpers.assert_equal(#load_order, 3, "all three plugins should load despite cycle") - - helpers.cleanup_test_env() - end) - - helpers.test("standalone spec loads before dependent even when also declared as dependency", function() - helpers.setup_test_env() - local load_order = {} - - -- Simulates blink.lua + pkl.lua scenario: - -- LuaSnip defined standalone in blink.lua AND as dependency in pkl.lua - -- blink.cmp defined after LuaSnip in same file - -- LuaSnip should load first due to import order - require('zpack').setup({ - spec = { - { 'test/luasnip', config = function() table.insert(load_order, 'luasnip') end }, - { 'test/blink', config = function() table.insert(load_order, 'blink') end }, - { - 'test/pkl', - ft = 'pkl', - dependencies = { 'test/luasnip' }, - }, + { + 'test/p2', + dependencies = { 'test/p3' }, + config = function() table.insert(load_order, 'p2') end, + }, + { + 'test/p3', + dependencies = { 'test/p1' }, + config = function() table.insert(load_order, 'p3') end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() + local state = require('zpack.state') - helpers.assert_equal(#load_order, 2, "both startup plugins should load") - helpers.assert_equal(load_order[1], 'luasnip', "luasnip should load first (lower import order)") - helpers.assert_equal(load_order[2], 'blink', "blink should load second") + assert.are.equal("loaded", state.spec_registry['https://github.com/test/p1'].load_status) + assert.are.equal("loaded", state.spec_registry['https://github.com/test/p2'].load_status) + assert.are.equal("loaded", state.spec_registry['https://github.com/test/p3'].load_status) + assert.are.equal(3, #load_order) + end) + + it("standalone spec loads before dependent even when also declared as dependency", function() + local load_order = {} + + -- Simulates blink.lua + pkl.lua scenario: + -- LuaSnip defined standalone in blink.lua AND as dependency in pkl.lua + -- blink.cmp defined after LuaSnip in same file + -- LuaSnip should load first due to import order + require('zpack').setup({ + spec = { + { 'test/luasnip', config = function() table.insert(load_order, 'luasnip') end }, + { 'test/blink', config = function() table.insert(load_order, 'blink') end }, + { + 'test/pkl', + ft = 'pkl', + dependencies = { 'test/luasnip' }, + }, + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() - helpers.test("src_to_pack_spec index is populated", function() - helpers.setup_test_env() + assert.are.equal(2, #load_order) + assert.are.equal('luasnip', load_order[1]) + assert.are.equal('blink', load_order[2]) + end) - require('zpack').setup({ - spec = { - { 'test/plugin' }, - }, - defaults = { confirm = false }, - }) + it("src_to_pack_spec index is populated", function() + require('zpack').setup({ + spec = { + { 'test/plugin' }, + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' - helpers.assert_not_nil(state.src_to_pack_spec[src], "src_to_pack_spec should be populated") - helpers.assert_equal(state.src_to_pack_spec[src].src, src) + assert.is_not_nil(state.src_to_pack_spec[src], "src_to_pack_spec should be populated") + assert.are.equal(src, state.src_to_pack_spec[src].src) + end) - helpers.cleanup_test_env() - end) + it("src_to_pack_spec contains resolved pack_spec with name", function() + require('zpack').setup({ + spec = { + { 'test/no-explicit-name' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/no-explicit-name' + + assert.is_not_nil(state.src_to_pack_spec[src], "src_to_pack_spec should be populated") + assert.is_not_nil(state.src_to_pack_spec[src].name, "pack_spec should have resolved name") + assert.are.equal("no-explicit-name", state.src_to_pack_spec[src].name) + end) - helpers.test("src_to_pack_spec contains resolved pack_spec with name", function() - helpers.setup_test_env() + it("startup plugin with lazy dependency loads dep before config", function() + local load_order = {} - require('zpack').setup({ - spec = { - { 'test/no-explicit-name' }, + require('zpack').setup({ + spec = { + { + 'test/lazy-dep', + event = 'VeryLazy', + config = function() table.insert(load_order, 'lazy-dep') end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/no-explicit-name' - - helpers.assert_not_nil(state.src_to_pack_spec[src], "src_to_pack_spec should be populated") - helpers.assert_not_nil(state.src_to_pack_spec[src].name, "pack_spec should have resolved name") - helpers.assert_equal(state.src_to_pack_spec[src].name, "no-explicit-name") - - helpers.cleanup_test_env() - end) - - helpers.test("startup plugin with lazy dependency loads dep before config", function() - helpers.setup_test_env() - local load_order = {} - - require('zpack').setup({ - spec = { - { - 'test/lazy-dep', - event = 'VeryLazy', - config = function() table.insert(load_order, 'lazy-dep') end, - }, - { - 'test/startup-parent', - dependencies = { 'test/lazy-dep' }, - config = function() table.insert(load_order, 'startup-parent') end, - }, + { + 'test/startup-parent', + dependencies = { 'test/lazy-dep' }, + config = function() table.insert(load_order, 'startup-parent') end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_equal(#load_order, 2, "both plugins should load") - helpers.assert_equal(load_order[1], 'lazy-dep', "lazy dependency should load first") - helpers.assert_equal(load_order[2], 'startup-parent', "startup parent should load second") - - helpers.cleanup_test_env() - end) - - helpers.test("startup plugin with multiple lazy dependencies loads all deps", function() - helpers.setup_test_env() - local load_order = {} - - require('zpack').setup({ - spec = { - { 'test/lazy-dep1', cmd = 'Dep1Cmd', config = function() table.insert(load_order, 'lazy-dep1') end }, - { 'test/lazy-dep2', ft = 'testft', config = function() table.insert(load_order, 'lazy-dep2') end }, - { - 'test/startup-parent', - dependencies = { 'test/lazy-dep1', 'test/lazy-dep2' }, - config = function() table.insert(load_order, 'startup-parent') end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.are.equal(2, #load_order) + assert.are.equal('lazy-dep', load_order[1]) + assert.are.equal('startup-parent', load_order[2]) + end) + + it("startup plugin with multiple lazy dependencies loads all deps", function() + local load_order = {} + + require('zpack').setup({ + spec = { + { 'test/lazy-dep1', cmd = 'Dep1Cmd', config = function() table.insert(load_order, 'lazy-dep1') end }, + { 'test/lazy-dep2', ft = 'testft', config = function() table.insert(load_order, 'lazy-dep2') end }, + { + 'test/startup-parent', + dependencies = { 'test/lazy-dep1', 'test/lazy-dep2' }, + config = function() table.insert(load_order, 'startup-parent') end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.are.equal(3, #load_order) + assert.is_truthy( + vim.tbl_contains(vim.list_slice(load_order, 1, 2), 'lazy-dep1'), + "lazy-dep1 should load before parent" + ) + assert.is_truthy( + vim.tbl_contains(vim.list_slice(load_order, 1, 2), 'lazy-dep2'), + "lazy-dep2 should load before parent" + ) + assert.are.equal('startup-parent', load_order[3]) + end) - helpers.flush_pending() + it("dependency-only plugin is loaded for startup parent even when lazy parent exists", function() + local load_order = {} - helpers.assert_equal(#load_order, 3, "all three plugins should load") - helpers.assert_true( - vim.tbl_contains(vim.list_slice(load_order, 1, 2), 'lazy-dep1'), - "lazy-dep1 should load before parent" - ) - helpers.assert_true( - vim.tbl_contains(vim.list_slice(load_order, 1, 2), 'lazy-dep2'), - "lazy-dep2 should load before parent" - ) - helpers.assert_equal(load_order[3], 'startup-parent', "startup parent should load last") - - helpers.cleanup_test_env() - end) - - helpers.test("dependency-only plugin is loaded for startup parent even when lazy parent exists", function() - helpers.setup_test_env() - local load_order = {} - - require('zpack').setup({ - spec = { - { - 'test/lazy-parent', - cmd = 'LazyCmd', - dependencies = { - { 'test/shared-dep', config = function() table.insert(load_order, 'shared-dep') end }, - }, - }, - { - 'test/startup-parent', - dependencies = { 'test/shared-dep' }, - config = function() table.insert(load_order, 'startup-parent') end, + require('zpack').setup({ + spec = { + { + 'test/lazy-parent', + cmd = 'LazyCmd', + dependencies = { + { 'test/shared-dep', config = function() table.insert(load_order, 'shared-dep') end }, }, }, - defaults = { confirm = false }, - }) + { + 'test/startup-parent', + dependencies = { 'test/shared-dep' }, + config = function() table.insert(load_order, 'startup-parent') end, + }, + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - helpers.assert_equal(#load_order, 2, "shared-dep and startup-parent should load") - helpers.assert_equal(load_order[1], 'shared-dep', "shared dependency should load first") - helpers.assert_equal(load_order[2], 'startup-parent', "startup parent should load second") + assert.are.equal(2, #load_order) + assert.are.equal('shared-dep', load_order[1]) + assert.are.equal('startup-parent', load_order[2]) - local state = require('zpack.state') - helpers.assert_true( - state.unloaded_plugin_names['lazy-parent'] or false, - "lazy-parent should still be unloaded" - ) + local state = require('zpack.state') + assert.is_truthy( + state.unloaded_plugin_names['lazy-parent'] or false, + "lazy-parent should still be unloaded" + ) + end) + + it("deeply nested startup dependencies (4+ levels) load in order", function() + local load_order = {} - helpers.cleanup_test_env() - end) - - helpers.test("deeply nested startup dependencies (4+ levels) load in order", function() - helpers.setup_test_env() - local load_order = {} - - require('zpack').setup({ - spec = { - { - 'test/level-a', - dependencies = { - { - 'test/level-b', - dependencies = { - { - 'test/level-c', - dependencies = { - { - 'test/level-d', - dependencies = { - { 'test/level-e', config = function() table.insert(load_order, 'e') end }, - }, - config = function() table.insert(load_order, 'd') end, + require('zpack').setup({ + spec = { + { + 'test/level-a', + dependencies = { + { + 'test/level-b', + dependencies = { + { + 'test/level-c', + dependencies = { + { + 'test/level-d', + dependencies = { + { 'test/level-e', config = function() table.insert(load_order, 'e') end }, }, + config = function() table.insert(load_order, 'd') end, }, - config = function() table.insert(load_order, 'c') end, }, + config = function() table.insert(load_order, 'c') end, }, - config = function() table.insert(load_order, 'b') end, }, + config = function() table.insert(load_order, 'b') end, }, - config = function() table.insert(load_order, 'a') end, }, + config = function() table.insert(load_order, 'a') end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_equal(#load_order, 5, "all 5 levels should load") - helpers.assert_equal(load_order[1], 'e', "deepest dependency (e) should load first") - helpers.assert_equal(load_order[2], 'd', "d should load second") - helpers.assert_equal(load_order[3], 'c', "c should load third") - helpers.assert_equal(load_order[4], 'b', "b should load fourth") - helpers.assert_equal(load_order[5], 'a', "root (a) should load last") - - helpers.cleanup_test_env() - end) - - helpers.test("diamond dependency pattern loads shared dependency once", function() - helpers.setup_test_env() - local load_order = {} - - -- Diamond pattern: - -- A (shared base) - -- / \ - -- B C - -- \ / - -- D (root) - require('zpack').setup({ - spec = { - { - 'test/d-root', - dependencies = { - { - 'test/d-left', - dependencies = { { 'test/d-base', config = function() table.insert(load_order, 'base') end } }, - config = function() table.insert(load_order, 'left') end, - }, - { - 'test/d-right', - dependencies = { 'test/d-base' }, - config = function() table.insert(load_order, 'right') end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.are.equal(5, #load_order) + assert.are.equal('e', load_order[1]) + assert.are.equal('d', load_order[2]) + assert.are.equal('c', load_order[3]) + assert.are.equal('b', load_order[4]) + assert.are.equal('a', load_order[5]) + end) + + it("diamond dependency pattern loads shared dependency once", function() + local load_order = {} + + -- Diamond pattern: + -- A (shared base) + -- / \ + -- B C + -- \ / + -- D (root) + require('zpack').setup({ + spec = { + { + 'test/d-root', + dependencies = { + { + 'test/d-left', + dependencies = { { 'test/d-base', config = function() table.insert(load_order, 'base') end } }, + config = function() table.insert(load_order, 'left') end, + }, + { + 'test/d-right', + dependencies = { 'test/d-base' }, + config = function() table.insert(load_order, 'right') end, }, - config = function() table.insert(load_order, 'root') end, }, + config = function() table.insert(load_order, 'root') end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - local state = require('zpack.state') + helpers.flush_pending() + local state = require('zpack.state') - local base_count = 0 - for _, name in ipairs(load_order) do - if name == 'base' then base_count = base_count + 1 end - end + local base_count = 0 + for _, name in ipairs(load_order) do + if name == 'base' then base_count = base_count + 1 end + end - helpers.assert_equal(base_count, 1, "shared base should load exactly once") - helpers.assert_equal(#load_order, 4, "all 4 plugins should load") + assert.are.equal(1, base_count) + assert.are.equal(4, #load_order) - local base_pos = vim.fn.index(load_order, 'base') + 1 - local left_pos = vim.fn.index(load_order, 'left') + 1 - local right_pos = vim.fn.index(load_order, 'right') + 1 - local root_pos = vim.fn.index(load_order, 'root') + 1 + local base_pos = vim.fn.index(load_order, 'base') + 1 + local left_pos = vim.fn.index(load_order, 'left') + 1 + local right_pos = vim.fn.index(load_order, 'right') + 1 + local root_pos = vim.fn.index(load_order, 'root') + 1 - helpers.assert_true(base_pos < left_pos, "base should load before left") - helpers.assert_true(base_pos < right_pos, "base should load before right") - helpers.assert_true(left_pos < root_pos, "left should load before root") - helpers.assert_true(right_pos < root_pos, "right should load before root") + assert.is_truthy(base_pos < left_pos, "base should load before left") + assert.is_truthy(base_pos < right_pos, "base should load before right") + assert.is_truthy(left_pos < root_pos, "left should load before root") + assert.is_truthy(right_pos < root_pos, "right should load before root") - helpers.assert_equal( - state.spec_registry['https://github.com/test/d-base'].load_status, - "loaded", - "base should be fully loaded" - ) + assert.are.equal("loaded", state.spec_registry['https://github.com/test/d-base'].load_status) + end) - helpers.cleanup_test_env() - end) - - local function has_warning_matching(fragments) - for _, notif in ipairs(_G.test_state.notifications) do - if notif.level == vim.log.levels.WARN then - local all_match = true - for _, frag in ipairs(fragments) do - if not notif.msg:find(frag, 1, true) then - all_match = false - break - end - end - if all_match then - return true + local function has_warning_matching(fragments) + for _, notif in ipairs(_G.test_state.notifications) do + if notif.level == vim.log.levels.WARN then + local all_match = true + for _, frag in ipairs(fragments) do + if not notif.msg:find(frag, 1, true) then + all_match = false + break end end + if all_match then + return true + end end - return false end + return false + end - helpers.test("cond=false on a dep still runs user config and warns", function() - helpers.setup_test_env() - local state = require('zpack.state') - _G.test_state.dep_config_ran = nil + it("cond=false on a dep still runs user config and warns", function() + local state = require('zpack.state') + _G.test_state.dep_config_ran = nil - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { 'test/dep' }, - }, - { - 'test/dep', - cond = false, - opts = { foo = 1 }, - config = function(_, opts) - _G.test_state.dep_config_ran = opts - end, - }, + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { 'test/dep' }, + }, + { + 'test/dep', + cond = false, + opts = { foo = 1 }, + config = function(_, opts) + _G.test_state.dep_config_ran = opts + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.wait_for_condition(function() - return _G.test_state.dep_config_ran ~= nil - end, 200) + helpers.wait_for_condition(function() + return _G.test_state.dep_config_ran ~= nil + end, 200) - local dep_entry = state.spec_registry['https://github.com/test/dep'] - helpers.assert_not_nil(dep_entry, "dep registry entry should exist") - helpers.assert_equal(dep_entry.cond_result, false, "dep cond_result should be false") + local dep_entry = state.spec_registry['https://github.com/test/dep'] + assert.is_not_nil(dep_entry, "dep registry entry should exist") + assert.are.equal(false, dep_entry.cond_result) - helpers.assert_not_nil(_G.test_state.dep_config_ran, "config should have run via dep chain") - helpers.assert_equal(_G.test_state.dep_config_ran.foo, 1, "user opts should have been passed") + assert.is_not_nil(_G.test_state.dep_config_ran, "config should have run via dep chain") + assert.are.equal(1, _G.test_state.dep_config_ran.foo) - helpers.assert_true( - has_warning_matching({ 'test/dep', 'test/parent', 'cond=false' }), - "expected a cond=false override warning mentioning test/dep and test/parent" - ) + assert.is_truthy( + has_warning_matching({ 'test/dep', 'test/parent', 'cond=false' }), + "expected a cond=false override warning mentioning test/dep and test/parent" + ) - _G.test_state.dep_config_ran = nil - helpers.cleanup_test_env() - end) + _G.test_state.dep_config_ran = nil + end) - helpers.test("enabled=false on a dep does not run user config and propagates to disable parent", function() - helpers.setup_test_env() - local state = require('zpack.state') - _G.test_state.dep_config_ran = nil + it("enabled=false on a dep does not run user config and propagates to disable parent", function() + local state = require('zpack.state') + _G.test_state.dep_config_ran = nil - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { 'test/dep' }, - }, - { - 'test/dep', - enabled = false, - opts = { foo = 1 }, - config = function(_, opts) - _G.test_state.dep_config_ran = opts - end, - }, + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { 'test/dep' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_nil( - state.spec_registry['https://github.com/test/dep'], - "disabled dep should be pruned from spec_registry" - ) - helpers.assert_nil( - state.spec_registry['https://github.com/test/parent'], - "parent should be pruned after propagation (required dep is disabled)" - ) - - helpers.assert_nil(_G.test_state.dep_config_ran, "config should NOT have run") - helpers.assert_nil( - _G.test_state.registered_pack_specs['dep'], - "disabled dep should not be passed to vim.pack.add" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['parent'], - "propagation-disabled parent should not reach vim.pack.add" - ) - - helpers.assert_true( - has_warning_matching({ 'test/parent', 'test/dep', 'enabled=false' }), - "expected a propagation warning naming the parent, dep, and enabled=false" - ) - - _G.test_state.dep_config_ran = nil - helpers.cleanup_test_env() - end) - - helpers.test("cond=false standalone runs no config and emits no dep warning", function() - helpers.setup_test_env() - _G.test_state.dep_config_ran = nil - - require('zpack').setup({ - spec = { - { - 'test/solo', - cond = false, - opts = { foo = 1 }, - config = function(_, opts) - _G.test_state.dep_config_ran = opts - end, - }, + { + 'test/dep', + enabled = false, + opts = { foo = 1 }, + config = function(_, opts) + _G.test_state.dep_config_ran = opts + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_nil(_G.test_state.dep_config_ran, "standalone cond=false should not run config") - helpers.assert_false( - has_warning_matching({ 'cond=false' }), - "unexpected cond=false override warning for standalone plugin" - ) - - _G.test_state.dep_config_ran = nil - helpers.cleanup_test_env() - end) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_nil( + state.spec_registry['https://github.com/test/dep'], + "disabled dep should be pruned from spec_registry" + ) + assert.is_nil( + state.spec_registry['https://github.com/test/parent'], + "parent should be pruned after propagation (required dep is disabled)" + ) + + assert.is_nil(_G.test_state.dep_config_ran, "config should NOT have run") + assert.is_nil( + _G.test_state.registered_pack_specs['dep'], + "disabled dep should not be passed to vim.pack.add" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['parent'], + "propagation-disabled parent should not reach vim.pack.add" + ) + + assert.is_truthy( + has_warning_matching({ 'test/parent', 'test/dep', 'enabled=false' }), + "expected a propagation warning naming the parent, dep, and enabled=false" + ) + + _G.test_state.dep_config_ran = nil + end) - helpers.test("enabled AND_LOGIC: one spec false disables the merged plugin", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("cond=false standalone runs no config and emits no dep warning", function() + _G.test_state.dep_config_ran = nil - require('zpack').setup({ - spec = { - { - 'test/shared', - enabled = false, - }, - { - 'test/shared', - opts = { foo = 1 }, - }, + require('zpack').setup({ + spec = { + { + 'test/solo', + cond = false, + opts = { foo = 1 }, + config = function(_, opts) + _G.test_state.dep_config_ran = opts + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - helpers.assert_nil( - state.spec_registry['https://github.com/test/shared'], - "plugin with any fragment disabling it should be pruned" - ) - helpers.assert_false( - vim.tbl_contains(state.registered_plugin_names, 'shared'), - "disabled plugin should not be in registered_plugin_names" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['shared'], - "disabled plugin should not be passed to vim.pack.add" - ) + assert.is_nil(_G.test_state.dep_config_ran, "standalone cond=false should not run config") + assert.is_falsy( + has_warning_matching({ 'cond=false' }), + "unexpected cond=false override warning for standalone plugin" + ) - helpers.cleanup_test_env() - end) + _G.test_state.dep_config_ran = nil + end) - helpers.test("enabled AND_LOGIC with two functions: one false disables plugin", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("enabled AND_LOGIC: one spec false disables the merged plugin", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/shared', - enabled = function() return true end, - }, - { - 'test/shared', - enabled = function() return false end, - opts = { foo = 1 }, - }, + require('zpack').setup({ + spec = { + { + 'test/shared', + enabled = false, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_nil( - state.spec_registry['https://github.com/test/shared'], - "merged function AND_LOGIC yielding false should prune the plugin" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['shared'], - "function-disabled plugin should not be passed to vim.pack.add" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("enabled=false on a parent prunes its exclusive dep subtree", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/disabled-parent', - enabled = false, - dependencies = { 'test/exclusive-dep' }, - }, + { + 'test/shared', + opts = { foo = 1 }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_nil( - state.spec_registry['https://github.com/test/exclusive-dep'], - "exclusive dep of a disabled parent should not be registered" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['exclusive-dep'], - "exclusive dep should not reach vim.pack.add" - ) - - helpers.cleanup_test_env() - end) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_nil( + state.spec_registry['https://github.com/test/shared'], + "plugin with any fragment disabling it should be pruned" + ) + assert.is_falsy( + vim.tbl_contains(state.registered_plugin_names, 'shared'), + "disabled plugin should not be in registered_plugin_names" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['shared'], + "disabled plugin should not be passed to vim.pack.add" + ) + end) - helpers.test("enabled=function(false) on a parent prunes its exclusive dep subtree", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("enabled AND_LOGIC with two functions: one false disables plugin", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/fn-disabled-parent', - enabled = function() return false end, - dependencies = { 'test/fn-exclusive-dep' }, - }, + require('zpack').setup({ + spec = { + { + 'test/shared', + enabled = function() return true end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_nil( - state.spec_registry['https://github.com/test/fn-exclusive-dep'], - "exclusive dep of a function-disabled parent should not be registered" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("shared dep survives when one of its parents is enabled=false", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/dead-parent', - enabled = false, - dependencies = { 'test/shared-dep' }, - }, - { - 'test/live-parent', - dependencies = { 'test/shared-dep' }, - }, + { + 'test/shared', + enabled = function() return false end, + opts = { foo = 1 }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_not_nil( - state.spec_registry['https://github.com/test/shared-dep'], - "shared dep should still be registered via the live parent" - ) - helpers.assert_not_nil( - _G.test_state.registered_pack_specs['shared-dep'], - "shared dep should reach vim.pack.add via the live parent" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['dead-parent'], - "dead parent should not reach vim.pack.add" - ) - - helpers.cleanup_test_env() - end) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_nil( + state.spec_registry['https://github.com/test/shared'], + "merged function AND_LOGIC yielding false should prune the plugin" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['shared'], + "function-disabled plugin should not be passed to vim.pack.add" + ) + end) - helpers.test("multi-fragment: enabled=false in one fragment prunes dep from another fragment", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("enabled=false on a parent prunes its exclusive dep subtree", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { 'test/leaky-parent', enabled = false }, - { 'test/leaky-parent', dependencies = { 'test/leaky-dep' } }, + require('zpack').setup({ + spec = { + { + 'test/disabled-parent', + enabled = false, + dependencies = { 'test/exclusive-dep' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_nil( - state.spec_registry['https://github.com/test/leaky-dep'], - "dep registered via a fragment whose merged enabled=false must be pruned post-merge" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['leaky-dep'], - "orphaned dep should not reach vim.pack.add" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['leaky-parent'], - "disabled parent should not reach vim.pack.add" - ) - - helpers.cleanup_test_env() - end) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_nil( + state.spec_registry['https://github.com/test/exclusive-dep'], + "exclusive dep of a disabled parent should not be registered" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['exclusive-dep'], + "exclusive dep should not reach vim.pack.add" + ) + end) - helpers.test("enabled=false on a parent transitively prunes an exclusive dep chain", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("enabled=function(false) on a parent prunes its exclusive dep subtree", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/chain-a', - enabled = false, - dependencies = { - { 'test/chain-b', dependencies = { 'test/chain-c' } }, - }, - }, + require('zpack').setup({ + spec = { + { + 'test/fn-disabled-parent', + enabled = function() return false end, + dependencies = { 'test/fn-exclusive-dep' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() + }, + defaults = { confirm = false }, + }) - helpers.assert_nil( - state.spec_registry['https://github.com/test/chain-b'], - "exclusive dep B should be pruned" - ) - helpers.assert_nil( - state.spec_registry['https://github.com/test/chain-c'], - "transitive exclusive dep C should be pruned" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['chain-b'], - "B should not reach vim.pack.add" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['chain-c'], - "C should not reach vim.pack.add" - ) + helpers.flush_pending() - helpers.cleanup_test_env() - end) + assert.is_nil( + state.spec_registry['https://github.com/test/fn-exclusive-dep'], + "exclusive dep of a function-disabled parent should not be registered" + ) + end) - helpers.test("nested A->B->C: middle plugin cond=false still loads its own deps", function() - helpers.setup_test_env() - _G.test_state.b_config_ran = nil - _G.test_state.c_config_ran = nil + it("shared dep survives when one of its parents is enabled=false", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/n-a', - dependencies = { 'test/n-b' }, - }, - { - 'test/n-b', - cond = false, - dependencies = { 'test/n-c' }, - opts = { b = true }, - config = function(_, opts) - _G.test_state.b_config_ran = opts - end, - }, - { - 'test/n-c', - opts = { c = true }, - config = function(_, opts) - _G.test_state.c_config_ran = opts - end, - }, + require('zpack').setup({ + spec = { + { + 'test/dead-parent', + enabled = false, + dependencies = { 'test/shared-dep' }, }, - defaults = { confirm = false }, - }) - - helpers.wait_for_condition(function() - return _G.test_state.b_config_ran ~= nil and _G.test_state.c_config_ran ~= nil - end, 200) - - helpers.assert_not_nil( - _G.test_state.b_config_ran, - "cond=false middle dep still runs config when pulled via dep chain" - ) - helpers.assert_not_nil( - _G.test_state.c_config_ran, - "transitive dep C should load via B" - ) - helpers.assert_true( - has_warning_matching({ 'test/n-b', 'test/n-a', 'cond=false' }), - "expected cond=false override warning naming B and its parent A" - ) - - _G.test_state.b_config_ran = nil - _G.test_state.c_config_ran = nil - helpers.cleanup_test_env() - end) - - helpers.test("cond function on a dep receives the live plugin", function() - helpers.setup_test_env() - _G.test_state.cond_plugin_arg = nil - - require('zpack').setup({ - spec = { - { - 'test/cond-fn-parent', - dependencies = { 'test/cond-fn-dep' }, - }, - { - 'test/cond-fn-dep', - cond = function(plugin) - _G.test_state.cond_plugin_arg = plugin - return true - end, - }, + { + 'test/live-parent', + dependencies = { 'test/shared-dep' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_not_nil( - _G.test_state.cond_plugin_arg, - "cond function on a dep should receive the plugin arg at register time" - ) - helpers.assert_not_nil( - _G.test_state.cond_plugin_arg.spec, - "plugin arg should carry spec" - ) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_not_nil( + state.spec_registry['https://github.com/test/shared-dep'], + "shared dep should still be registered via the live parent" + ) + assert.is_not_nil( + _G.test_state.registered_pack_specs['shared-dep'], + "shared dep should reach vim.pack.add via the live parent" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['dead-parent'], + "dead parent should not reach vim.pack.add" + ) + end) - _G.test_state.cond_plugin_arg = nil - helpers.cleanup_test_env() - end) + it("multi-fragment: enabled=false in one fragment prunes dep from another fragment", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { + { 'test/leaky-parent', enabled = false }, + { 'test/leaky-parent', dependencies = { 'test/leaky-dep' } }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_nil( + state.spec_registry['https://github.com/test/leaky-dep'], + "dep registered via a fragment whose merged enabled=false must be pruned post-merge" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['leaky-dep'], + "orphaned dep should not reach vim.pack.add" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['leaky-parent'], + "disabled parent should not reach vim.pack.add" + ) + end) - helpers.test("lazy parent with cond=false dep: dep loads and warns at trigger time", function() - helpers.setup_test_env() - _G.test_state.lazy_dep_config_ran = nil + it("enabled=false on a parent transitively prunes an exclusive dep chain", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/lazy-cond-parent', - event = 'User ZpackTestTrigger', - dependencies = { 'test/lazy-cond-dep' }, - }, - { - 'test/lazy-cond-dep', - cond = false, - opts = { trig = true }, - config = function(_, opts) - _G.test_state.lazy_dep_config_ran = opts - end, + require('zpack').setup({ + spec = { + { + 'test/chain-a', + enabled = false, + dependencies = { + { 'test/chain-b', dependencies = { 'test/chain-c' } }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_nil( - _G.test_state.lazy_dep_config_ran, - "dep should not load before lazy parent triggers" - ) - - vim.api.nvim_exec_autocmds('User', { pattern = 'ZpackTestTrigger' }) - helpers.flush_pending() - - helpers.assert_not_nil( - _G.test_state.lazy_dep_config_ran, - "dep should load when lazy parent triggers" - ) - helpers.assert_true( - has_warning_matching({ 'test/lazy-cond-dep', 'test/lazy-cond-parent', 'cond=false' }), - "expected cond=false warning when lazy parent triggers" - ) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_nil( + state.spec_registry['https://github.com/test/chain-b'], + "exclusive dep B should be pruned" + ) + assert.is_nil( + state.spec_registry['https://github.com/test/chain-c'], + "transitive exclusive dep C should be pruned" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['chain-b'], + "B should not reach vim.pack.add" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['chain-c'], + "C should not reach vim.pack.add" + ) + end) - _G.test_state.lazy_dep_config_ran = nil - helpers.cleanup_test_env() - end) - - helpers.test("cross-file import: cond=false dep pulled in as dep of another file still runs with warning", function() - helpers.setup_test_env() - local state = require('zpack.state') - _G.test_state.imported_dep_config_ran = nil - - local utils = require('zpack.utils') - local original_lsdir = utils.lsdir - local original_stdpath = vim.fn.stdpath - vim.fn.stdpath = function() return '/mock/config' end - utils.lsdir = function(path) - if path == '/mock/config/lua/zpack_test_import_cond' then - return { - { name = 'dep_with_cond.lua', type = 'file' }, - { name = 'parent_using_dep.lua', type = 'file' }, - } - end - return {} - end + it("nested A->B->C: middle plugin cond=false still loads its own deps", function() + _G.test_state.b_config_ran = nil + _G.test_state.c_config_ran = nil - package.loaded['zpack_test_import_cond.dep_with_cond'] = { + require('zpack').setup({ + spec = { + { + 'test/n-a', + dependencies = { 'test/n-b' }, + }, { - 'test/imported-dep', + 'test/n-b', cond = false, - opts = { from_user = true }, + dependencies = { 'test/n-c' }, + opts = { b = true }, config = function(_, opts) - _G.test_state.imported_dep_config_ran = opts + _G.test_state.b_config_ran = opts end, }, - } - package.loaded['zpack_test_import_cond.parent_using_dep'] = { - { 'test/imported-parent', dependencies = { 'test/imported-dep' } }, - } - - require('zpack').setup({ - spec = { { import = 'zpack_test_import_cond' } }, - defaults = { confirm = false }, - }) - - helpers.wait_for_condition(function() - return _G.test_state.imported_dep_config_ran ~= nil - end, 200) - - helpers.assert_not_nil( - _G.test_state.imported_dep_config_ran, - "imported dep's user config should run via dep chain" - ) - helpers.assert_equal( - _G.test_state.imported_dep_config_ran.from_user, - true, - "user-supplied opts should flow through" - ) - local dep_entry = state.spec_registry['https://github.com/test/imported-dep'] - helpers.assert_equal(dep_entry.cond_result, false, "cond_result should be false") - helpers.assert_true( - has_warning_matching({ 'test/imported-dep', 'test/imported-parent', 'cond=false' }), - "expected cond=false warning across imports" - ) - - utils.lsdir = original_lsdir - vim.fn.stdpath = original_stdpath - package.loaded['zpack_test_import_cond.dep_with_cond'] = nil - package.loaded['zpack_test_import_cond.parent_using_dep'] = nil - _G.test_state.imported_dep_config_ran = nil - helpers.cleanup_test_env() - end) - - helpers.test("cross-file import: enabled=false dep pulled in as dep of another file skips user config", function() - helpers.setup_test_env() - _G.test_state.imported_dep_config_ran = nil - - local utils = require('zpack.utils') - local original_lsdir = utils.lsdir - local original_stdpath = vim.fn.stdpath - vim.fn.stdpath = function() return '/mock/config' end - utils.lsdir = function(path) - if path == '/mock/config/lua/zpack_test_import_en' then - return { - { name = 'dep_with_enabled.lua', type = 'file' }, - { name = 'parent_using_dep.lua', type = 'file' }, - } - end - return {} - end - - package.loaded['zpack_test_import_en.dep_with_enabled'] = { { - 'test/en-dep', - enabled = false, - opts = { from_user = true }, + 'test/n-c', + opts = { c = true }, config = function(_, opts) - _G.test_state.imported_dep_config_ran = opts + _G.test_state.c_config_ran = opts end, }, - } - package.loaded['zpack_test_import_en.parent_using_dep'] = { - { 'test/en-parent', dependencies = { 'test/en-dep' } }, - } - - require('zpack').setup({ - spec = { { import = 'zpack_test_import_en' } }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() + }, + defaults = { confirm = false }, + }) + + helpers.wait_for_condition(function() + return _G.test_state.b_config_ran ~= nil and _G.test_state.c_config_ran ~= nil + end, 200) + + assert.is_not_nil( + _G.test_state.b_config_ran, + "cond=false middle dep still runs config when pulled via dep chain" + ) + assert.is_not_nil( + _G.test_state.c_config_ran, + "transitive dep C should load via B" + ) + assert.is_truthy( + has_warning_matching({ 'test/n-b', 'test/n-a', 'cond=false' }), + "expected cond=false override warning naming B and its parent A" + ) + + _G.test_state.b_config_ran = nil + _G.test_state.c_config_ran = nil + end) - helpers.assert_nil( - _G.test_state.imported_dep_config_ran, - "enabled=false dep should NOT run user config even when pulled via import+dep" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['en-dep'], - "enabled=false dep should not reach vim.pack.add" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['en-parent'], - "parent should be propagation-disabled when its required dep is enabled=false" - ) - helpers.assert_true( - has_warning_matching({ 'test/en-parent', 'test/en-dep', 'enabled=false' }), - "expected propagation warning naming parent, dep, and enabled=false" - ) + it("cond function on a dep receives the live plugin", function() + _G.test_state.cond_plugin_arg = nil - utils.lsdir = original_lsdir - vim.fn.stdpath = original_stdpath - package.loaded['zpack_test_import_en.dep_with_enabled'] = nil - package.loaded['zpack_test_import_en.parent_using_dep'] = nil - _G.test_state.imported_dep_config_ran = nil - helpers.cleanup_test_env() - end) - - helpers.test("diamond dep with one cond=false path still loads shared dep", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/live-diamond-parent', - dependencies = { 'test/diamond-dep' }, - }, - { - 'test/cond-diamond-parent', - cond = false, - dependencies = { 'test/diamond-dep' }, - }, + require('zpack').setup({ + spec = { + { + 'test/cond-fn-parent', + dependencies = { 'test/cond-fn-dep' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_not_nil( - state.spec_registry['https://github.com/test/diamond-dep'], - "diamond dep should be registered" - ) - helpers.assert_not_nil( - _G.test_state.registered_pack_specs['diamond-dep'], - "diamond dep should reach vim.pack.add" - ) - helpers.assert_not_nil( - _G.test_state.registered_pack_specs['live-diamond-parent'], - "live diamond parent should load" - ) - - helpers.cleanup_test_env() - end) + { + 'test/cond-fn-dep', + cond = function(plugin) + _G.test_state.cond_plugin_arg = plugin + return true + end, + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_not_nil( + _G.test_state.cond_plugin_arg, + "cond function on a dep should receive the plugin arg at register time" + ) + assert.is_not_nil( + _G.test_state.cond_plugin_arg.spec, + "plugin arg should carry spec" + ) + + _G.test_state.cond_plugin_arg = nil + end) - helpers.test("enabled=false on a dep transitively disables a grandparent chain", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("lazy parent with cond=false dep: dep loads and warns at trigger time", function() + _G.test_state.lazy_dep_config_ran = nil - require('zpack').setup({ - spec = { - { - 'test/prop-grand', - dependencies = { 'test/prop-mid' }, - }, - { - 'test/prop-mid', - dependencies = { 'test/prop-leaf' }, - }, - { - 'test/prop-leaf', - enabled = false, - }, + require('zpack').setup({ + spec = { + { + 'test/lazy-cond-parent', + event = 'User ZpackTestTrigger', + dependencies = { 'test/lazy-cond-dep' }, + }, + { + 'test/lazy-cond-dep', + cond = false, + opts = { trig = true }, + config = function(_, opts) + _G.test_state.lazy_dep_config_ran = opts + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_nil( + _G.test_state.lazy_dep_config_ran, + "dep should not load before lazy parent triggers" + ) + + vim.api.nvim_exec_autocmds('User', { pattern = 'ZpackTestTrigger' }) + helpers.flush_pending() + + assert.is_not_nil( + _G.test_state.lazy_dep_config_ran, + "dep should load when lazy parent triggers" + ) + assert.is_truthy( + has_warning_matching({ 'test/lazy-cond-dep', 'test/lazy-cond-parent', 'cond=false' }), + "expected cond=false warning when lazy parent triggers" + ) + + _G.test_state.lazy_dep_config_ran = nil + end) - helpers.flush_pending() + it("cross-file import: cond=false dep pulled in as dep of another file still runs with warning", function() + local state = require('zpack.state') + _G.test_state.imported_dep_config_ran = nil + + local utils = require('zpack.utils') + local original_lsdir = utils.lsdir + local original_stdpath = vim.fn.stdpath + vim.fn.stdpath = function() return '/mock/config' end + utils.lsdir = function(path) + if path == '/mock/config/lua/zpack_test_import_cond' then + return { + { name = 'dep_with_cond.lua', type = 'file' }, + { name = 'parent_using_dep.lua', type = 'file' }, + } + end + return {} + end - helpers.assert_nil( - state.spec_registry['https://github.com/test/prop-leaf'], - "leaf should be pruned" - ) - helpers.assert_nil( - state.spec_registry['https://github.com/test/prop-mid'], - "mid should be pruned (propagation-disabled, depends on leaf)" - ) - helpers.assert_nil( - state.spec_registry['https://github.com/test/prop-grand'], - "grand should be pruned (propagation-disabled, depends transitively on leaf)" - ) + package.loaded['zpack_test_import_cond.dep_with_cond'] = { + { + 'test/imported-dep', + cond = false, + opts = { from_user = true }, + config = function(_, opts) + _G.test_state.imported_dep_config_ran = opts + end, + }, + } + package.loaded['zpack_test_import_cond.parent_using_dep'] = { + { 'test/imported-parent', dependencies = { 'test/imported-dep' } }, + } + + require('zpack').setup({ + spec = { { import = 'zpack_test_import_cond' } }, + defaults = { confirm = false }, + }) + + helpers.wait_for_condition(function() + return _G.test_state.imported_dep_config_ran ~= nil + end, 200) + + assert.is_not_nil( + _G.test_state.imported_dep_config_ran, + "imported dep's user config should run via dep chain" + ) + assert.are.equal(true, _G.test_state.imported_dep_config_ran.from_user) + local dep_entry = state.spec_registry['https://github.com/test/imported-dep'] + assert.are.equal(false, dep_entry.cond_result) + assert.is_truthy( + has_warning_matching({ 'test/imported-dep', 'test/imported-parent', 'cond=false' }), + "expected cond=false warning across imports" + ) + + utils.lsdir = original_lsdir + vim.fn.stdpath = original_stdpath + package.loaded['zpack_test_import_cond.dep_with_cond'] = nil + package.loaded['zpack_test_import_cond.parent_using_dep'] = nil + _G.test_state.imported_dep_config_ran = nil + end) - for _, name in ipairs({ 'prop-grand', 'prop-mid', 'prop-leaf' }) do - helpers.assert_nil( - _G.test_state.registered_pack_specs[name], - name .. " should not reach vim.pack.add" - ) + it("cross-file import: enabled=false dep pulled in as dep of another file skips user config", function() + _G.test_state.imported_dep_config_ran = nil + + local utils = require('zpack.utils') + local original_lsdir = utils.lsdir + local original_stdpath = vim.fn.stdpath + vim.fn.stdpath = function() return '/mock/config' end + utils.lsdir = function(path) + if path == '/mock/config/lua/zpack_test_import_en' then + return { + { name = 'dep_with_enabled.lua', type = 'file' }, + { name = 'parent_using_dep.lua', type = 'file' }, + } end + return {} + end - helpers.assert_true( - has_warning_matching({ 'test/prop-mid', 'test/prop-leaf', 'enabled=false' }), - "expected propagation warning: mid disabled because leaf has enabled=false" - ) - helpers.assert_true( - has_warning_matching({ 'test/prop-grand', 'test/prop-mid', 'enabled=false' }), - "expected propagation warning: grand disabled because mid has enabled=false" - ) - - helpers.cleanup_test_env() - end) + package.loaded['zpack_test_import_en.dep_with_enabled'] = { + { + 'test/en-dep', + enabled = false, + opts = { from_user = true }, + config = function(_, opts) + _G.test_state.imported_dep_config_ran = opts + end, + }, + } + package.loaded['zpack_test_import_en.parent_using_dep'] = { + { 'test/en-parent', dependencies = { 'test/en-dep' } }, + } + + require('zpack').setup({ + spec = { { import = 'zpack_test_import_en' } }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_nil( + _G.test_state.imported_dep_config_ran, + "enabled=false dep should NOT run user config even when pulled via import+dep" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['en-dep'], + "enabled=false dep should not reach vim.pack.add" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['en-parent'], + "parent should be propagation-disabled when its required dep is enabled=false" + ) + assert.is_truthy( + has_warning_matching({ 'test/en-parent', 'test/en-dep', 'enabled=false' }), + "expected propagation warning naming parent, dep, and enabled=false" + ) + + utils.lsdir = original_lsdir + vim.fn.stdpath = original_stdpath + package.loaded['zpack_test_import_en.dep_with_enabled'] = nil + package.loaded['zpack_test_import_en.parent_using_dep'] = nil + _G.test_state.imported_dep_config_ran = nil + end) - helpers.test("enabled=false on a shared dep disables all dependents", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("diamond dep with one cond=false path still loads shared dep", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/shared-dep-a', - dependencies = { 'test/shared-disabled' }, - }, - { - 'test/shared-dep-b', - dependencies = { 'test/shared-disabled' }, - }, - { - 'test/shared-disabled', - enabled = false, - }, + require('zpack').setup({ + spec = { + { + 'test/live-diamond-parent', + dependencies = { 'test/diamond-dep' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_nil( - state.spec_registry['https://github.com/test/shared-dep-a'], - "dep-a should be pruned (propagation-disabled)" - ) - helpers.assert_nil( - state.spec_registry['https://github.com/test/shared-dep-b'], - "dep-b should be pruned (propagation-disabled)" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['shared-dep-a'], - "dep-a should not reach vim.pack.add" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['shared-dep-b'], - "dep-b should not reach vim.pack.add" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("enabled=false propagates even when the parent has other healthy deps", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/multi-dep-parent', - dependencies = { 'test/healthy-dep', 'test/sick-dep' }, - }, - { - 'test/sick-dep', - enabled = false, - }, + { + 'test/cond-diamond-parent', + cond = false, + dependencies = { 'test/diamond-dep' }, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_not_nil( + state.spec_registry['https://github.com/test/diamond-dep'], + "diamond dep should be registered" + ) + assert.is_not_nil( + _G.test_state.registered_pack_specs['diamond-dep'], + "diamond dep should reach vim.pack.add" + ) + assert.is_not_nil( + _G.test_state.registered_pack_specs['live-diamond-parent'], + "live diamond parent should load" + ) + end) - helpers.flush_pending() + it("enabled=false on a dep transitively disables a grandparent chain", function() + local state = require('zpack.state') - helpers.assert_nil( - state.spec_registry['https://github.com/test/multi-dep-parent'], - "parent with one disabled required dep should be pruned" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['multi-dep-parent'], - "parent should not reach vim.pack.add" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['healthy-dep'], - "healthy dep is now exclusive to a disabled parent, should be pruned" + require('zpack').setup({ + spec = { + { + 'test/prop-grand', + dependencies = { 'test/prop-mid' }, + }, + { + 'test/prop-mid', + dependencies = { 'test/prop-leaf' }, + }, + { + 'test/prop-leaf', + enabled = false, + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_nil( + state.spec_registry['https://github.com/test/prop-leaf'], + "leaf should be pruned" + ) + assert.is_nil( + state.spec_registry['https://github.com/test/prop-mid'], + "mid should be pruned (propagation-disabled, depends on leaf)" + ) + assert.is_nil( + state.spec_registry['https://github.com/test/prop-grand'], + "grand should be pruned (propagation-disabled, depends transitively on leaf)" + ) + + for _, name in ipairs({ 'prop-grand', 'prop-mid', 'prop-leaf' }) do + assert.is_nil( + _G.test_state.registered_pack_specs[name], + name .. " should not reach vim.pack.add" ) + end - helpers.cleanup_test_env() - end) + assert.is_truthy( + has_warning_matching({ 'test/prop-mid', 'test/prop-leaf', 'enabled=false' }), + "expected propagation warning: mid disabled because leaf has enabled=false" + ) + assert.is_truthy( + has_warning_matching({ 'test/prop-grand', 'test/prop-mid', 'enabled=false' }), + "expected propagation warning: grand disabled because mid has enabled=false" + ) + end) - helpers.test("enabled=function(false) on a dep propagates to parent", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("enabled=false on a shared dep disables all dependents", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/fn-prop-parent', - dependencies = { 'test/fn-prop-dep' }, - }, - { - 'test/fn-prop-dep', - enabled = function() return false end, - }, + require('zpack').setup({ + spec = { + { + 'test/shared-dep-a', + dependencies = { 'test/shared-disabled' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() + { + 'test/shared-dep-b', + dependencies = { 'test/shared-disabled' }, + }, + { + 'test/shared-disabled', + enabled = false, + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_nil( + state.spec_registry['https://github.com/test/shared-dep-a'], + "dep-a should be pruned (propagation-disabled)" + ) + assert.is_nil( + state.spec_registry['https://github.com/test/shared-dep-b'], + "dep-b should be pruned (propagation-disabled)" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['shared-dep-a'], + "dep-a should not reach vim.pack.add" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['shared-dep-b'], + "dep-b should not reach vim.pack.add" + ) + end) - helpers.assert_nil( - state.spec_registry['https://github.com/test/fn-prop-parent'], - "parent should be pruned when dep's enabled function returns false" - ) - helpers.assert_nil( - _G.test_state.registered_pack_specs['fn-prop-parent'], - "parent should not reach vim.pack.add" - ) + it("enabled=false propagates even when the parent has other healthy deps", function() + local state = require('zpack.state') - helpers.cleanup_test_env() - end) + require('zpack').setup({ + spec = { + { + 'test/multi-dep-parent', + dependencies = { 'test/healthy-dep', 'test/sick-dep' }, + }, + { + 'test/sick-dep', + enabled = false, + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_nil( + state.spec_registry['https://github.com/test/multi-dep-parent'], + "parent with one disabled required dep should be pruned" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['multi-dep-parent'], + "parent should not reach vim.pack.add" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['healthy-dep'], + "healthy dep is now exclusive to a disabled parent, should be pruned" + ) + end) - helpers.test("cond=false on a dep does NOT propagate to parent (soft gate preserved)", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("enabled=function(false) on a dep propagates to parent", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/cond-soft-parent', - dependencies = { 'test/cond-soft-dep' }, - }, - { - 'test/cond-soft-dep', - cond = false, - }, + require('zpack').setup({ + spec = { + { + 'test/fn-prop-parent', + dependencies = { 'test/fn-prop-dep' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() + { + 'test/fn-prop-dep', + enabled = function() return false end, + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + assert.is_nil( + state.spec_registry['https://github.com/test/fn-prop-parent'], + "parent should be pruned when dep's enabled function returns false" + ) + assert.is_nil( + _G.test_state.registered_pack_specs['fn-prop-parent'], + "parent should not reach vim.pack.add" + ) + end) - local parent_entry = state.spec_registry['https://github.com/test/cond-soft-parent'] - helpers.assert_not_nil(parent_entry, "parent entry should exist") - helpers.assert_false( - parent_entry.enabled_result == false, - "parent must NOT be disabled just because a dep has cond=false (cond is soft)" - ) - helpers.assert_not_nil( - _G.test_state.registered_pack_specs['cond-soft-parent'], - "parent should still reach vim.pack.add" - ) - helpers.assert_not_nil( - _G.test_state.registered_pack_specs['cond-soft-dep'], - "cond=false dep should still reach vim.pack.add as a dep of an enabled parent" - ) + it("cond=false on a dep does NOT propagate to parent (soft gate preserved)", function() + local state = require('zpack.state') - helpers.cleanup_test_env() - end) - - helpers.test("enabled function is evaluated once per spec, not eagerly at import", function() - helpers.setup_test_env() - local call_count = 0 - - require('zpack').setup({ - spec = { - { - 'test/single-eval', - enabled = function() - call_count = call_count + 1 - return false - end, - dependencies = { 'test/single-eval-dep' }, - }, + require('zpack').setup({ + spec = { + { + 'test/cond-soft-parent', + dependencies = { 'test/cond-soft-dep' }, + }, + { + 'test/cond-soft-dep', + cond = false, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local parent_entry = state.spec_registry['https://github.com/test/cond-soft-parent'] + assert.is_not_nil(parent_entry, "parent entry should exist") + assert.is_falsy( + parent_entry.enabled_result == false, + "parent must NOT be disabled just because a dep has cond=false (cond is soft)" + ) + assert.is_not_nil( + _G.test_state.registered_pack_specs['cond-soft-parent'], + "parent should still reach vim.pack.add" + ) + assert.is_not_nil( + _G.test_state.registered_pack_specs['cond-soft-dep'], + "cond=false dep should still reach vim.pack.add as a dep of an enabled parent" + ) + end) - helpers.flush_pending() + it("enabled function is evaluated once per spec, not eagerly at import", function() + local call_count = 0 - helpers.assert_equal( - call_count, - 1, - "enabled function should be called exactly once (post-merge), not also at import time" - ) + require('zpack').setup({ + spec = { + { + 'test/single-eval', + enabled = function() + call_count = call_count + 1 + return false + end, + dependencies = { 'test/single-eval-dep' }, + }, + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() - helpers.test("cond_result is nil before register_all and set after", function() - helpers.setup_test_env() - local state = require('zpack.state') + assert.are.equal(1, call_count) + end) - require('zpack').setup({ - spec = { - { 'test/cond-none' }, - { 'test/cond-yes', cond = true }, - { 'test/cond-no', cond = false }, - }, - defaults = { confirm = false }, - }) + it("cond_result is nil before register_all and set after", function() + local state = require('zpack.state') - helpers.flush_pending() + require('zpack').setup({ + spec = { + { 'test/cond-none' }, + { 'test/cond-yes', cond = true }, + { 'test/cond-no', cond = false }, + }, + defaults = { confirm = false }, + }) - helpers.assert_equal( - state.spec_registry['https://github.com/test/cond-none'].cond_result, - true, - "plugin without cond should end up with cond_result=true after register_all" - ) - helpers.assert_equal( - state.spec_registry['https://github.com/test/cond-yes'].cond_result, - true, - "plugin with cond=true should end up with cond_result=true after register_all" - ) - helpers.assert_equal( - state.spec_registry['https://github.com/test/cond-no'].cond_result, - false, - "plugin with cond=false should end up with cond_result=false after register_all" - ) + helpers.flush_pending() - helpers.cleanup_test_env() - end) + assert.are.equal(true, state.spec_registry['https://github.com/test/cond-none'].cond_result) + assert.are.equal(true, state.spec_registry['https://github.com/test/cond-yes'].cond_result) + assert.are.equal(false, state.spec_registry['https://github.com/test/cond-no'].cond_result) end) -end +end) diff --git a/tests/deprecation_test.lua b/tests/deprecation_test.lua index 468124a..e6a62df 100644 --- a/tests/deprecation_test.lua +++ b/tests/deprecation_test.lua @@ -1,127 +1,105 @@ local helpers = require('helpers') -return function() - helpers.describe("Deprecated Options", function() - helpers.test("deprecated confirm option shows warning", function() - helpers.setup_test_env() +describe("Deprecated Options", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) - require('zpack').setup({ spec = {}, confirm = false }) + it("deprecated confirm option shows warning", function() + require('zpack').setup({ spec = {}, confirm = false }) - helpers.flush_pending() + helpers.flush_pending() - local found_deprecation = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find("DEPRECATED") and notif.msg:find("confirm") then - found_deprecation = true - break - end + local found_deprecation = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find("DEPRECATED") and notif.msg:find("confirm") then + found_deprecation = true + break end + end - helpers.assert_true(found_deprecation, "Should show deprecation warning for confirm") - - helpers.cleanup_test_env() - end) - - helpers.test("deprecated disable_vim_loader option shows warning", function() - helpers.setup_test_env() + assert.is_truthy(found_deprecation, "Should show deprecation warning for confirm") + end) - require('zpack').setup({ spec = {}, disable_vim_loader = true }) + it("deprecated disable_vim_loader option shows warning", function() + require('zpack').setup({ spec = {}, disable_vim_loader = true }) - helpers.flush_pending() + helpers.flush_pending() - local found_deprecation = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find("DEPRECATED") and notif.msg:find("disable_vim_loader") then - found_deprecation = true - break - end + local found_deprecation = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find("DEPRECATED") and notif.msg:find("disable_vim_loader") then + found_deprecation = true + break end + end - helpers.assert_true(found_deprecation, "Should show deprecation warning for disable_vim_loader") - - helpers.cleanup_test_env() - end) - - helpers.test("deprecated plugins_dir option shows warning", function() - helpers.setup_test_env() + assert.is_truthy(found_deprecation, "Should show deprecation warning for disable_vim_loader") + end) - require('zpack').setup({ plugins_dir = 'my_plugins' }) + it("deprecated plugins_dir option shows warning", function() + require('zpack').setup({ plugins_dir = 'my_plugins' }) - helpers.flush_pending() + helpers.flush_pending() - local found_deprecation = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find("DEPRECATED") and notif.msg:find("plugins_dir") then - found_deprecation = true - break - end + local found_deprecation = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find("DEPRECATED") and notif.msg:find("plugins_dir") then + found_deprecation = true + break end + end - helpers.assert_true(found_deprecation, "Should show deprecation warning for plugins_dir") - - helpers.cleanup_test_env() - end) - - helpers.test("invoking legacy :Z* commands emits the deprecation warning every time", function() - helpers.setup_test_env() + assert.is_truthy(found_deprecation, "Should show deprecation warning for plugins_dir") + end) - require('zpack').setup({ spec = { { 'test/plugin-a' } }, defaults = { confirm = false } }) + it("invoking legacy :Z* commands emits the deprecation warning every time", function() + require('zpack').setup({ spec = { { 'test/plugin-a' } }, defaults = { confirm = false } }) - helpers.flush_pending() - _G.test_state.notifications = {} + helpers.flush_pending() + _G.test_state.notifications = {} - vim.cmd('ZClean') - vim.cmd('ZClean') -- repeated use must re-emit the warning - vim.cmd('ZUpdate') - helpers.flush_pending() + vim.cmd('ZClean') + vim.cmd('ZClean') -- repeated use must re-emit the warning + vim.cmd('ZUpdate') + helpers.flush_pending() - local count = 0 - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find("DEPRECATED") and notif.msg:find("Use :ZPack") then - count = count + 1 - end + local count = 0 + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find("DEPRECATED") and notif.msg:find("Use :ZPack") then + count = count + 1 end - helpers.assert_equal(count, 3, "deprecation warning must fire on every legacy command invocation") - - helpers.cleanup_test_env() - end) - - helpers.test("legacy :ZDelete delegates to the new delete subcommand with force=true", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { { 'test/plugin-a' }, { 'test/plugin-b' } }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() + end + assert.are.equal(3, count) + end) - vim.cmd('ZDelete plugin-a') - helpers.flush_pending() + it("legacy :ZDelete delegates to the new delete subcommand with force=true", function() + require('zpack').setup({ + spec = { { 'test/plugin-a' }, { 'test/plugin-b' } }, + defaults = { confirm = false }, + }) - helpers.assert_equal(#_G.test_state.vim_pack_del_calls, 1, "vim.pack.del must be called by legacy shim") - local call = _G.test_state.vim_pack_del_calls[1] - helpers.assert_true(call.opts.force, "legacy :ZDelete must propagate force=true to Sub.delete") - helpers.assert_table_contains(call.names, 'plugin-a', "legacy :ZDelete plugin-a must target plugin-a") + helpers.flush_pending() - helpers.cleanup_test_env() - end) + vim.cmd('ZDelete plugin-a') + helpers.flush_pending() - helpers.test("deprecated options still register plugins", function() - helpers.setup_test_env() - local state = require('zpack.state') + assert.are.equal(1, #_G.test_state.vim_pack_del_calls) + local call = _G.test_state.vim_pack_del_calls[1] + assert.is_truthy(call.opts.force, "legacy :ZDelete must propagate force=true to Sub.delete") + assert.contains(call.names, 'plugin-a') + end) - require('zpack').setup({ - spec = { { 'test/plugin' } }, - confirm = false, - }) + it("deprecated options still register plugins", function() + local state = require('zpack.state') - helpers.flush_pending() + require('zpack').setup({ + spec = { { 'test/plugin' } }, + confirm = false, + }) - local src = 'https://github.com/test/plugin' - helpers.assert_not_nil(state.spec_registry[src], "Plugin should be registered") + helpers.flush_pending() - helpers.cleanup_test_env() - end) + local src = 'https://github.com/test/plugin' + assert.is_not_nil(state.spec_registry[src], "Plugin should be registered") end) -end +end) diff --git a/tests/dispatch_test.lua b/tests/dispatch_test.lua index 6bc034e..92fec46 100644 --- a/tests/dispatch_test.lua +++ b/tests/dispatch_test.lua @@ -1,120 +1,104 @@ local helpers = require('helpers') -return function() - helpers.describe(":ZPack subcommand dispatch", function() - helpers.test("a bang on a non-bang subcommand warns and does not run", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { { 'test/plugin-a' } }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - _G.test_state.notifications = {} - - -- An installed plugin absent from the spec gives `clean` something to - -- delete, so a wrongly-accepted bang would surface as a non-zero - -- vim_pack_del_calls below. - _G.test_state.registered_pack_specs['orphan-plugin'] = { - src = 'test/orphan-plugin', - name = 'orphan-plugin', - } - - vim.cmd('ZPack! clean') - helpers.flush_pending() - - helpers.assert_equal(#_G.test_state.vim_pack_del_calls, 0, "clean must not run when given a rejected bang") - - local found_warning = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find('does not accept') and notif.level == vim.log.levels.WARN then - found_warning = true - break - end +describe(":ZPack subcommand dispatch", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("a bang on a non-bang subcommand warns and does not run", function() + require('zpack').setup({ + spec = { { 'test/plugin-a' } }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + _G.test_state.notifications = {} + + -- An installed plugin absent from the spec gives `clean` something to + -- delete, so a wrongly-accepted bang would surface as a non-zero + -- vim_pack_del_calls below. + _G.test_state.registered_pack_specs['orphan-plugin'] = { + src = 'test/orphan-plugin', + name = 'orphan-plugin', + } + + vim.cmd('ZPack! clean') + helpers.flush_pending() + + assert.are.equal(0, #_G.test_state.vim_pack_del_calls) + + local found_warning = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find('does not accept') and notif.level == vim.log.levels.WARN then + found_warning = true + break end - helpers.assert_true(found_warning, "should warn that clean does not accept a bang") - - helpers.cleanup_test_env() - end) - - helpers.test("extra positional arguments warn and do not run", function() - helpers.setup_test_env() + end + assert.is_truthy(found_warning, "should warn that clean does not accept a bang") + end) - require('zpack').setup({ - spec = { { 'test/plugin-a' } }, - defaults = { confirm = false }, - }) + it("extra positional arguments warn and do not run", function() + require('zpack').setup({ + spec = { { 'test/plugin-a' } }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - _G.test_state.notifications = {} + helpers.flush_pending() + _G.test_state.notifications = {} - vim.cmd('ZPack update plugin-a extra-arg') - helpers.flush_pending() + vim.cmd('ZPack update plugin-a extra-arg') + helpers.flush_pending() - helpers.assert_equal(#_G.test_state.vim_pack_update_calls, 0, "update must not run when given extra arguments") + assert.are.equal(0, #_G.test_state.vim_pack_update_calls) - local found_warning = false - local misleading_error = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find('at most one argument') and notif.level == vim.log.levels.WARN then - found_warning = true - end - if notif.msg:find('not found in spec') then - misleading_error = true - end + local found_warning = false + local misleading_error = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find('at most one argument') and notif.level == vim.log.levels.WARN then + found_warning = true end - helpers.assert_true(found_warning, "should warn about too many arguments") - helpers.assert_false(misleading_error, 'must not emit the misleading joined-args "not found in spec" error') - - helpers.cleanup_test_env() - end) - - helpers.test("clean rejects positional arguments", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { { 'test/plugin-a' } }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - _G.test_state.notifications = {} - - vim.cmd('ZPack clean junk') - helpers.flush_pending() - - local found_warning = false - local clean_ran = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find('no arguments') and notif.level == vim.log.levels.WARN then - found_warning = true - end - if notif.msg:find('unused plugin') then - clean_ran = true - end + if notif.msg:find('not found in spec') then + misleading_error = true end - helpers.assert_true(found_warning, "should warn that clean accepts no arguments") - helpers.assert_false(clean_ran, "clean must not run when given arguments") + end + assert.is_truthy(found_warning, "should warn about too many arguments") + assert.is_falsy(misleading_error, 'must not emit the misleading joined-args "not found in spec" error') + end) - helpers.cleanup_test_env() - end) + it("clean rejects positional arguments", function() + require('zpack').setup({ + spec = { { 'test/plugin-a' } }, + defaults = { confirm = false }, + }) - helpers.test("completion after a bang-attached subcommand targets its arguments", function() - helpers.setup_test_env() + helpers.flush_pending() + _G.test_state.notifications = {} - require('zpack').setup({ - spec = { { 'test/lazy-plugin', cmd = 'TestCommand' } }, - defaults = { confirm = false }, - }) + vim.cmd('ZPack clean junk') + helpers.flush_pending() + + local found_warning = false + local clean_ran = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find('no arguments') and notif.level == vim.log.levels.WARN then + found_warning = true + end + if notif.msg:find('unused plugin') then + clean_ran = true + end + end + assert.is_truthy(found_warning, "should warn that clean accepts no arguments") + assert.is_falsy(clean_ran, "clean must not run when given arguments") + end) - helpers.flush_pending() + it("completion after a bang-attached subcommand targets its arguments", function() + require('zpack').setup({ + spec = { { 'test/lazy-plugin', cmd = 'TestCommand' } }, + defaults = { confirm = false }, + }) - local completions = vim.fn.getcompletion('ZPack!load ', 'cmdline') - helpers.assert_table_contains(completions, 'lazy-plugin', - "':ZPack!load ' should complete unloaded plugin names, not subcommands") + helpers.flush_pending() - helpers.cleanup_test_env() - end) + local completions = vim.fn.getcompletion('ZPack!load ', 'cmdline') + assert.contains(completions, 'lazy-plugin') end) -end +end) diff --git a/tests/health_test.lua b/tests/health_test.lua new file mode 100644 index 0000000..e44b76e --- /dev/null +++ b/tests/health_test.lua @@ -0,0 +1,145 @@ +---@diagnostic disable: duplicate-set-field +local helpers = require('helpers') + +---Replace vim.health with a capturing stub. Returns the report and a restore fn. +local function capture_health() + local report = { sections = {} } + local current + local original = vim.health + vim.health = { + start = function(name) + current = { name = name, items = {} } + table.insert(report.sections, current) + end, + ok = function(msg) table.insert(current.items, { kind = 'ok', msg = msg }) end, + warn = function(msg, advice) + table.insert(current.items, { kind = 'warn', msg = msg, advice = advice }) + end, + error = function(msg, advice) + table.insert(current.items, { kind = 'error', msg = msg, advice = advice }) + end, + info = function(msg) table.insert(current.items, { kind = 'info', msg = msg }) end, + } + return report, function() vim.health = original end +end + +local function section(report, name) + for _, sec in ipairs(report.sections) do + if sec.name == name then + return sec + end + end + return nil +end + +---True when `sec` has an item of `kind` whose message contains every needle. +local function has_item(sec, kind, needles) + for _, item in ipairs(sec.items) do + if item.kind == kind then + local matched = true + for _, needle in ipairs(needles) do + if not item.msg:find(needle, 1, true) then + matched = false + break + end + end + if matched then + return true + end + end + end + return false +end + +---Run health.check() against a freshly-required health module. +local function run_check() + package.loaded['zpack.health'] = nil + require('zpack.health').check() +end + +describe("Health Check", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("check() produces every report section without error", function() + local report, restore = capture_health() + + local ok, err = pcall(run_check) + restore() + assert.is_truthy(ok, "health.check() should not throw: " .. tostring(err)) + + for _, name in ipairs({ 'Environment', 'Setup', 'Configuration', 'Plugins', 'Reporting a bug' }) do + assert.is_not_nil(section(report, name), "missing section: " .. name) + end + end) + + it("environment section reports Neovim and vim.pack as OK", function() + local report, restore = capture_health() + run_check() + restore() + + local env = section(report, 'Environment') + assert.is_truthy(has_item(env, 'ok', { 'Neovim' }), "Neovim version should be OK") + assert.is_truthy(has_item(env, 'ok', { 'vim.pack' }), "vim.pack should be OK") + end) + + it("warns when setup() has not been called", function() + local report, restore = capture_health() + run_check() + restore() + + local setup = section(report, 'Setup') + assert.is_truthy(has_item(setup, 'warn', { 'setup()' }), + "Setup section should warn that setup() was not called") + end) + + it("after a valid setup, config is OK and plugins are counted", function() + require('zpack').setup({ + spec = { { 'test/plugin' } }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local report, restore = capture_health() + run_check() + restore() + + local setup = section(report, 'Setup') + assert.is_truthy(has_item(setup, 'ok', { 'setup()' }), "Setup should be OK") + + local config = section(report, 'Configuration') + assert.is_truthy(has_item(config, 'ok', { 'valid' }), "Configuration should be valid") + assert.is_truthy(has_item(config, 'ok', { 'No deprecated options' }), + "no deprecated options should be reported") + + local plugins = section(report, 'Plugins') + assert.is_truthy(has_item(plugins, 'ok', { 'plugin(s) registered' }), + "Plugins section should report a registered count") + end) + + it("surfaces deprecated options in use", function() + require('zpack').setup({ spec = {}, confirm = false }) + helpers.flush_pending() + + local report, restore = capture_health() + run_check() + restore() + + local config = section(report, 'Configuration') + assert.is_truthy(has_item(config, 'warn', { 'confirm' }), + "Configuration section should warn about the deprecated confirm option") + end) + + it("surfaces an invalid merged config", function() + require('zpack').setup({ spec = {}, defaults = { confirm = 'yes' } }) + helpers.flush_pending() + + local report, restore = capture_health() + run_check() + restore() + + local config = section(report, 'Configuration') + assert.is_truthy(has_item(config, 'error', { 'defaults.confirm' }), + "Configuration section should report the bad defaults.confirm field") + end) +end) diff --git a/tests/helpers.lua b/tests/helpers.lua index 5e8b160..77f750c 100644 --- a/tests/helpers.lua +++ b/tests/helpers.lua @@ -1,106 +1,30 @@ ---@diagnostic disable: duplicate-set-field -local M = {} - -M.test_results = {} -M.test_count = 0 -M.passed_count = 0 -M.failed_count = 0 - -function M.reset() - M.test_results = {} - M.test_count = 0 - M.passed_count = 0 - M.failed_count = 0 -end - -function M.assert_equal(actual, expected, msg) - if actual ~= expected then - error(string.format( - "%s\nExpected: %s\nActual: %s", - msg or "Assertion failed", - vim.inspect(expected), - vim.inspect(actual) - )) - end -end - -function M.assert_true(condition, msg) - if not condition then - error(msg or "Expected true but got false") - end -end +local luassert = require('luassert') +local say = require('say') -function M.assert_false(condition, msg) - if condition then - error(msg or "Expected false but got true") - end -end - -function M.assert_nil(value, msg) - if value ~= nil then - error(string.format("%s\nExpected nil but got: %s", msg or "Assertion failed", vim.inspect(value))) - end -end +local M = {} -function M.assert_not_nil(value, msg) - if value == nil then - error(msg or "Expected non-nil value") +-- `assert.contains(tbl, value)` — a list-membership assertion luassert has no +-- built-in for. Registered as a real luassert assertion (rather than a bare +-- helper that raises via error()) so a failed containment check is classified +-- by busted as a 'failure', not an 'error', like every other assertion. +local function table_contains(_, arguments) + local tbl = arguments[1] + if type(tbl) ~= 'table' then + return false end -end - -function M.assert_table_contains(tbl, value, msg) for _, v in ipairs(tbl) do - if v == value then - return + if v == arguments[2] then + return true end end - error(string.format( - "%s\nTable does not contain: %s\nTable: %s", - msg or "Assertion failed", - vim.inspect(value), - vim.inspect(tbl) - )) -end - -function M.test(name, fn) - M.test_count = M.test_count + 1 - local success, err = pcall(fn) - - if success then - M.passed_count = M.passed_count + 1 - table.insert(M.test_results, { name = name, passed = true }) - print(string.format("✓ %s", name)) - else - M.failed_count = M.failed_count + 1 - table.insert(M.test_results, { name = name, passed = false, error = err }) - print(string.format("✗ %s", name)) - print(string.format(" Error: %s", err)) - M.cleanup_test_env() - end -end - -function M.describe(description, fn) - print(string.format("\n%s", description)) - fn() + return false end -function M.summary() - print(string.format("\n%s", string.rep("=", 60))) - print(string.format("Tests: %d total, %d passed, %d failed", - M.test_count, M.passed_count, M.failed_count)) - print(string.rep("=", 60)) - - if M.failed_count > 0 then - print("\nFailed tests:") - for _, result in ipairs(M.test_results) do - if not result.passed then - print(string.format(" - %s", result.name)) - end - end - end - - return M.failed_count == 0 -end +say:set('assertion.contains.positive', 'Expected table to contain value.\nTable:\n%s\nValue:\n%s') +say:set('assertion.contains.negative', 'Expected table to not contain value.\nTable:\n%s\nValue:\n%s') +luassert:register('assertion', 'contains', table_contains, + 'assertion.contains.positive', 'assertion.contains.negative') function M.setup_test_env() _G.test_state = { @@ -276,14 +200,6 @@ function M.cleanup_test_env() end end -function M.track_plugin_load(plugin_name) - table.insert(_G.test_state.loaded_plugins, plugin_name) -end - -function M.track_hook_execution(hook_name, plugin_src) - table.insert(_G.test_state.executed_hooks, { hook = hook_name, src = plugin_src }) -end - function M.wait_for_condition(condition, timeout_ms, interval_ms) timeout_ms = timeout_ms or 1000 interval_ms = interval_ms or 10 diff --git a/tests/import_test.lua b/tests/import_test.lua index 1a77f4a..7f2eb66 100644 --- a/tests/import_test.lua +++ b/tests/import_test.lua @@ -1,274 +1,279 @@ local helpers = require('helpers') -return function() - helpers.describe("Spec Import", function() - helpers.test("import loads *.lua files from directory", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local original_lsdir = utils.lsdir - local original_stdpath = vim.fn.stdpath - vim.fn.stdpath = function() return '/mock/config' end - utils.lsdir = function(path) - if path == '/mock/config/lua/test_plugins' then - return { - { name = 'foo.lua', type = 'file' }, - { name = 'bar.lua', type = 'file' }, - } - end - return {} +describe("Spec Import", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("import loads *.lua files from directory", function() + local utils = require('zpack.utils') + local original_lsdir = utils.lsdir + local original_stdpath = vim.fn.stdpath + vim.fn.stdpath = function() return '/mock/config' end + utils.lsdir = function(path) + if path == '/mock/config/lua/test_plugins' then + return { + { name = 'foo.lua', type = 'file' }, + { name = 'bar.lua', type = 'file' }, + } end + return {} + end - package.loaded['test_plugins.foo'] = { 'test/foo-plugin' } - package.loaded['test_plugins.bar'] = { 'test/bar-plugin' } - - local state = require('zpack.state') - require('zpack').setup({ { import = 'test_plugins' } }) - helpers.flush_pending() - - helpers.assert_not_nil(state.spec_registry['https://github.com/test/foo-plugin'], "foo-plugin should be registered") - helpers.assert_not_nil(state.spec_registry['https://github.com/test/bar-plugin'], "bar-plugin should be registered") - - utils.lsdir = original_lsdir - vim.fn.stdpath = original_stdpath - package.loaded['test_plugins.foo'] = nil - package.loaded['test_plugins.bar'] = nil - helpers.cleanup_test_env() - end) - - helpers.test("import loads */init.lua files from subdirectories", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local original_lsdir = utils.lsdir - local original_stdpath = vim.fn.stdpath - local original_fs_stat = vim.uv.fs_stat - vim.fn.stdpath = function() return '/mock/config' end - utils.lsdir = function(path) - if path == '/mock/config/lua/test_plugins' then - return { - { name = 'mini', type = 'directory' }, - } - end - return {} + package.loaded['test_plugins.foo'] = { 'test/foo-plugin' } + package.loaded['test_plugins.bar'] = { 'test/bar-plugin' } + + local state = require('zpack.state') + require('zpack').setup({ { import = 'test_plugins' } }) + helpers.flush_pending() + + assert.is_not_nil(state.spec_registry['https://github.com/test/foo-plugin'], "foo-plugin should be registered") + assert.is_not_nil(state.spec_registry['https://github.com/test/bar-plugin'], "bar-plugin should be registered") + + utils.lsdir = original_lsdir + vim.fn.stdpath = original_stdpath + package.loaded['test_plugins.foo'] = nil + package.loaded['test_plugins.bar'] = nil + end) + + it("import loads */init.lua files from subdirectories", function() + local utils = require('zpack.utils') + local original_lsdir = utils.lsdir + local original_stdpath = vim.fn.stdpath + local original_fs_stat = vim.uv.fs_stat + vim.fn.stdpath = function() return '/mock/config' end + utils.lsdir = function(path) + if path == '/mock/config/lua/test_plugins' then + return { + { name = 'mini', type = 'directory' }, + } end - vim.uv.fs_stat = function(path) - if path == '/mock/config/lua/test_plugins/mini/init.lua' then - return { type = 'file' } - end - return original_fs_stat(path) + return {} + end + vim.uv.fs_stat = function(path) + if path == '/mock/config/lua/test_plugins/mini/init.lua' then + return { type = 'file' } end + return original_fs_stat(path) + end + + package.loaded['test_plugins.mini'] = { 'test/mini-plugin' } + + local state = require('zpack.state') + require('zpack').setup({ { import = 'test_plugins' } }) + helpers.flush_pending() + + assert.is_not_nil(state.spec_registry['https://github.com/test/mini-plugin'], + "mini-plugin should be registered") - package.loaded['test_plugins.mini'] = { 'test/mini-plugin' } - - local state = require('zpack.state') - require('zpack').setup({ { import = 'test_plugins' } }) - helpers.flush_pending() - - helpers.assert_not_nil(state.spec_registry['https://github.com/test/mini-plugin'], - "mini-plugin should be registered") - - utils.lsdir = original_lsdir - vim.fn.stdpath = original_stdpath - vim.uv.fs_stat = original_fs_stat - package.loaded['test_plugins.mini'] = nil - helpers.cleanup_test_env() - end) - - helpers.test("import loads both *.lua and */init.lua", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local original_lsdir = utils.lsdir - local original_stdpath = vim.fn.stdpath - local original_fs_stat = vim.uv.fs_stat - vim.fn.stdpath = function() return '/mock/config' end - utils.lsdir = function(path) - if path == '/mock/config/lua/test_plugins' then - return { - { name = 'telescope.lua', type = 'file' }, - { name = 'mini', type = 'directory' }, - } - end - return {} + utils.lsdir = original_lsdir + vim.fn.stdpath = original_stdpath + vim.uv.fs_stat = original_fs_stat + package.loaded['test_plugins.mini'] = nil + end) + + it("import loads both *.lua and */init.lua", function() + local utils = require('zpack.utils') + local original_lsdir = utils.lsdir + local original_stdpath = vim.fn.stdpath + local original_fs_stat = vim.uv.fs_stat + vim.fn.stdpath = function() return '/mock/config' end + utils.lsdir = function(path) + if path == '/mock/config/lua/test_plugins' then + return { + { name = 'telescope.lua', type = 'file' }, + { name = 'mini', type = 'directory' }, + } end - vim.uv.fs_stat = function(path) - if path == '/mock/config/lua/test_plugins/mini/init.lua' then - return { type = 'file' } - end - return original_fs_stat(path) + return {} + end + vim.uv.fs_stat = function(path) + if path == '/mock/config/lua/test_plugins/mini/init.lua' then + return { type = 'file' } end + return original_fs_stat(path) + end + + package.loaded['test_plugins.telescope'] = { 'test/telescope' } + package.loaded['test_plugins.mini'] = { 'test/mini' } + + local state = require('zpack.state') + require('zpack').setup({ { import = 'test_plugins' } }) + helpers.flush_pending() - package.loaded['test_plugins.telescope'] = { 'test/telescope' } - package.loaded['test_plugins.mini'] = { 'test/mini' } - - local state = require('zpack.state') - require('zpack').setup({ { import = 'test_plugins' } }) - helpers.flush_pending() - - helpers.assert_not_nil(state.spec_registry['https://github.com/test/telescope'], "telescope should be registered") - helpers.assert_not_nil(state.spec_registry['https://github.com/test/mini'], "mini should be registered") - - utils.lsdir = original_lsdir - vim.fn.stdpath = original_stdpath - vim.uv.fs_stat = original_fs_stat - package.loaded['test_plugins.telescope'] = nil - package.loaded['test_plugins.mini'] = nil - helpers.cleanup_test_env() - end) - - helpers.test("import only goes 1 level deep for init.lua", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local original_lsdir = utils.lsdir - local original_stdpath = vim.fn.stdpath - local original_fs_stat = vim.uv.fs_stat - vim.fn.stdpath = function() return '/mock/config' end - utils.lsdir = function(path) - if path == '/mock/config/lua/test_plugins' then - return { - { name = 'level1', type = 'directory' }, - } - end - return {} + assert.is_not_nil(state.spec_registry['https://github.com/test/telescope'], "telescope should be registered") + assert.is_not_nil(state.spec_registry['https://github.com/test/mini'], "mini should be registered") + + utils.lsdir = original_lsdir + vim.fn.stdpath = original_stdpath + vim.uv.fs_stat = original_fs_stat + package.loaded['test_plugins.telescope'] = nil + package.loaded['test_plugins.mini'] = nil + end) + + it("import only goes 1 level deep for init.lua", function() + local utils = require('zpack.utils') + local original_lsdir = utils.lsdir + local original_stdpath = vim.fn.stdpath + local original_fs_stat = vim.uv.fs_stat + vim.fn.stdpath = function() return '/mock/config' end + utils.lsdir = function(path) + if path == '/mock/config/lua/test_plugins' then + return { + { name = 'level1', type = 'directory' }, + } end - vim.uv.fs_stat = function(path) - if path == '/mock/config/lua/test_plugins/level1/init.lua' then - return { type = 'file' } - end - return original_fs_stat(path) + return {} + end + vim.uv.fs_stat = function(path) + if path == '/mock/config/lua/test_plugins/level1/init.lua' then + return { type = 'file' } end + return original_fs_stat(path) + end + + package.loaded['test_plugins.level1'] = { 'test/level1-plugin' } - package.loaded['test_plugins.level1'] = { 'test/level1-plugin' } - - local state = require('zpack.state') - require('zpack').setup({ { import = 'test_plugins' } }) - helpers.flush_pending() - - helpers.assert_not_nil(state.spec_registry['https://github.com/test/level1-plugin'], - "level1-plugin should be registered") - - utils.lsdir = original_lsdir - vim.fn.stdpath = original_stdpath - vim.uv.fs_stat = original_fs_stat - package.loaded['test_plugins.level1'] = nil - helpers.cleanup_test_env() - end) - - helpers.test("import with enabled=false skips import", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local original_lsdir = utils.lsdir - local original_stdpath = vim.fn.stdpath - vim.fn.stdpath = function() return '/mock/config' end - utils.lsdir = function(path) - if path == '/mock/config/lua/test_plugins' then - return { - { name = 'foo.lua', type = 'file' }, - } - end - return {} + local state = require('zpack.state') + require('zpack').setup({ { import = 'test_plugins' } }) + helpers.flush_pending() + + assert.is_not_nil(state.spec_registry['https://github.com/test/level1-plugin'], + "level1-plugin should be registered") + + utils.lsdir = original_lsdir + vim.fn.stdpath = original_stdpath + vim.uv.fs_stat = original_fs_stat + package.loaded['test_plugins.level1'] = nil + end) + + it("import with enabled=false skips import", function() + local utils = require('zpack.utils') + local original_lsdir = utils.lsdir + local original_stdpath = vim.fn.stdpath + vim.fn.stdpath = function() return '/mock/config' end + utils.lsdir = function(path) + if path == '/mock/config/lua/test_plugins' then + return { + { name = 'foo.lua', type = 'file' }, + } end + return {} + end - package.loaded['test_plugins.foo'] = { 'test/foo-plugin' } - - local state = require('zpack.state') - require('zpack').setup({ { import = 'test_plugins', enabled = false } }) - helpers.flush_pending() - - helpers.assert_nil(state.spec_registry['https://github.com/test/foo-plugin'], - "foo-plugin should NOT be registered when enabled=false") - - utils.lsdir = original_lsdir - vim.fn.stdpath = original_stdpath - package.loaded['test_plugins.foo'] = nil - helpers.cleanup_test_env() - end) - - helpers.test("nested import works (init.lua with import)", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local original_lsdir = utils.lsdir - local original_stdpath = vim.fn.stdpath - local original_fs_stat = vim.uv.fs_stat - vim.fn.stdpath = function() return '/mock/config' end - utils.lsdir = function(path) - if path == '/mock/config/lua/test_plugins' then - return { - { name = 'mini', type = 'directory' }, - } - elseif path == '/mock/config/lua/test_plugins/mini' then - return { - { name = 'ai.lua', type = 'file' }, - { name = 'surround.lua', type = 'file' }, - } - end - return {} + package.loaded['test_plugins.foo'] = { 'test/foo-plugin' } + + local state = require('zpack.state') + require('zpack').setup({ { import = 'test_plugins', enabled = false } }) + helpers.flush_pending() + + assert.is_nil(state.spec_registry['https://github.com/test/foo-plugin'], + "foo-plugin should NOT be registered when enabled=false") + + utils.lsdir = original_lsdir + vim.fn.stdpath = original_stdpath + package.loaded['test_plugins.foo'] = nil + end) + + it("nested import works (init.lua with import)", function() + local utils = require('zpack.utils') + local original_lsdir = utils.lsdir + local original_stdpath = vim.fn.stdpath + local original_fs_stat = vim.uv.fs_stat + vim.fn.stdpath = function() return '/mock/config' end + utils.lsdir = function(path) + if path == '/mock/config/lua/test_plugins' then + return { + { name = 'mini', type = 'directory' }, + } + elseif path == '/mock/config/lua/test_plugins/mini' then + return { + { name = 'ai.lua', type = 'file' }, + { name = 'surround.lua', type = 'file' }, + } end - vim.uv.fs_stat = function(path) - if path == '/mock/config/lua/test_plugins/mini/init.lua' then - return { type = 'file' } - end - return original_fs_stat(path) + return {} + end + vim.uv.fs_stat = function(path) + if path == '/mock/config/lua/test_plugins/mini/init.lua' then + return { type = 'file' } end + return original_fs_stat(path) + end + + package.loaded['test_plugins.mini'] = { import = 'test_plugins.mini' } + package.loaded['test_plugins.mini.ai'] = { 'echasnovski/mini.ai' } + package.loaded['test_plugins.mini.surround'] = { 'echasnovski/mini.surround' } + + local state = require('zpack.state') + require('zpack').setup({ { import = 'test_plugins' } }) + helpers.flush_pending() + + assert.is_not_nil(state.spec_registry['https://github.com/echasnovski/mini.ai'], + "mini.ai should be registered") + assert.is_not_nil(state.spec_registry['https://github.com/echasnovski/mini.surround'], + "mini.surround should be registered") + + utils.lsdir = original_lsdir + vim.fn.stdpath = original_stdpath + vim.uv.fs_stat = original_fs_stat + package.loaded['test_plugins.mini'] = nil + package.loaded['test_plugins.mini.ai'] = nil + package.loaded['test_plugins.mini.surround'] = nil + end) - package.loaded['test_plugins.mini'] = { import = 'test_plugins.mini' } - package.loaded['test_plugins.mini.ai'] = { 'echasnovski/mini.ai' } - package.loaded['test_plugins.mini.surround'] = { 'echasnovski/mini.surround' } - - local state = require('zpack.state') - require('zpack').setup({ { import = 'test_plugins' } }) - helpers.flush_pending() - - helpers.assert_not_nil(state.spec_registry['https://github.com/echasnovski/mini.ai'], - "mini.ai should be registered") - helpers.assert_not_nil(state.spec_registry['https://github.com/echasnovski/mini.surround'], - "mini.surround should be registered") - - utils.lsdir = original_lsdir - vim.fn.stdpath = original_stdpath - vim.uv.fs_stat = original_fs_stat - package.loaded['test_plugins.mini'] = nil - package.loaded['test_plugins.mini.ai'] = nil - package.loaded['test_plugins.mini.surround'] = nil - helpers.cleanup_test_env() - end) - - helpers.test("duplicate import is skipped", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local original_lsdir = utils.lsdir - local original_stdpath = vim.fn.stdpath - local lsdir_call_count = 0 - vim.fn.stdpath = function() return '/mock/config' end - utils.lsdir = function(path) - if path == '/mock/config/lua/test_plugins' then - lsdir_call_count = lsdir_call_count + 1 - return { - { name = 'foo.lua', type = 'file' }, - } - end - return {} + it("duplicate import is skipped", function() + local utils = require('zpack.utils') + local original_lsdir = utils.lsdir + local original_stdpath = vim.fn.stdpath + local lsdir_call_count = 0 + vim.fn.stdpath = function() return '/mock/config' end + utils.lsdir = function(path) + if path == '/mock/config/lua/test_plugins' then + lsdir_call_count = lsdir_call_count + 1 + return { + { name = 'foo.lua', type = 'file' }, + } end + return {} + end - package.loaded['test_plugins.foo'] = { 'test/foo-plugin' } + package.loaded['test_plugins.foo'] = { 'test/foo-plugin' } - require('zpack').setup({ - { import = 'test_plugins' }, - { import = 'test_plugins' }, - }) - helpers.flush_pending() + require('zpack').setup({ + { import = 'test_plugins' }, + { import = 'test_plugins' }, + }) + helpers.flush_pending() - helpers.assert_equal(lsdir_call_count, 1, "import should only be processed once") + assert.are.equal(1, lsdir_call_count) + + utils.lsdir = original_lsdir + vim.fn.stdpath = original_stdpath + package.loaded['test_plugins.foo'] = nil + end) + + it("spec with a non-string import is skipped, not crashed on", function() + local state = require('zpack.state') + -- A malformed `import` (a number/table instead of a module path) is + -- reported by validation; setup() must not abort partway through. + require('zpack').setup({ { import = 123 } }) + helpers.flush_pending() + + assert.is_truthy(state.is_setup, + "setup() should complete despite the malformed import spec") + end) - utils.lsdir = original_lsdir - vim.fn.stdpath = original_stdpath - package.loaded['test_plugins.foo'] = nil - helpers.cleanup_test_env() - end) + it("spec with a non-string source is skipped, not crashed on", function() + local state = require('zpack.state') + -- An over-nested spec — `[1]` is a table, not a "user/plugin" string. + -- normalize_source must not crash concatenating it; the spec is + -- skipped and setup() completes rather than aborting partway through. + require('zpack').setup({ spec = { { { 'user/plugin' } } } }) + helpers.flush_pending() + + assert.is_truthy(state.is_setup, + "setup() should complete despite the malformed spec") + assert.is_nil(next(state.spec_registry), + "the malformed spec must be skipped, not registered") end) -end +end) diff --git a/tests/is_single_spec_test.lua b/tests/is_single_spec_test.lua index 7ed4321..800ee11 100644 --- a/tests/is_single_spec_test.lua +++ b/tests/is_single_spec_test.lua @@ -1,151 +1,129 @@ local helpers = require('helpers') -return function() - helpers.describe("is_single_spec heuristic", function() - helpers.test("single spec with string source and opts is detected", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin', opts = { foo = true } }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local entry = state.spec_registry['https://github.com/test/plugin'] +describe("is_single_spec heuristic", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("single spec with string source and opts is detected", function() + require('zpack').setup({ + spec = { + { 'test/plugin', opts = { foo = true } }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local entry = state.spec_registry['https://github.com/test/plugin'] + + assert.is_not_nil(entry, "plugin should be registered") + assert.is_truthy(entry.has_opts, "entry should record that opts were contributed") + assert.are.equal(true, entry.sorted_specs[1].opts.foo) + end) - helpers.assert_not_nil(entry, "plugin should be registered") - helpers.assert_true(entry.has_opts, "entry should record that opts were contributed") - helpers.assert_equal(entry.sorted_specs[1].opts.foo, true, "opts should be preserved") + it("list of bare string specs is treated as list", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b' }, + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() + local state = require('zpack.state') - helpers.test("list of bare string specs is treated as list", function() - helpers.setup_test_env() + assert.is_not_nil(state.spec_registry['https://github.com/test/plugin-a']) + assert.is_not_nil(state.spec_registry['https://github.com/test/plugin-b']) + end) - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - - helpers.assert_not_nil(state.spec_registry['https://github.com/test/plugin-a']) - helpers.assert_not_nil(state.spec_registry['https://github.com/test/plugin-b']) - - helpers.cleanup_test_env() - end) - - helpers.test("single dependency spec with opts preserves all fields", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { - 'test/dep', - init = function() _G._test_dep_init_called = true end, - opts = { select = { lookahead = true } }, - }, + it("single dependency spec with opts preserves all fields", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { + 'test/dep', + init = function() _G._test_dep_init_called = true end, + opts = { select = { lookahead = true } }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local dep_entry = state.spec_registry['https://github.com/test/dep'] - - helpers.assert_not_nil(dep_entry, "dependency should be registered") - helpers.assert_true(dep_entry.has_opts, "dep entry should record that opts were contributed") - helpers.assert_not_nil(dep_entry.sorted_specs[1].opts, "opts should be preserved on dependency") - helpers.assert_equal(dep_entry.sorted_specs[1].opts.select.lookahead, true, - "opts fields should be preserved") - helpers.assert_not_nil(dep_entry.merged_spec.init, "init should be preserved on dependency") - - _G._test_dep_init_called = nil - helpers.cleanup_test_env() - end) - - helpers.test("list of string dependencies are all registered", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { 'test/dep-a', 'test/dep-b' }, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local dep_entry = state.spec_registry['https://github.com/test/dep'] + + assert.is_not_nil(dep_entry, "dependency should be registered") + assert.is_truthy(dep_entry.has_opts, "dep entry should record that opts were contributed") + assert.is_not_nil(dep_entry.sorted_specs[1].opts, "opts should be preserved on dependency") + assert.are.equal(true, dep_entry.sorted_specs[1].opts.select.lookahead) + assert.is_not_nil(dep_entry.merged_spec.init, "init should be preserved on dependency") + + _G._test_dep_init_called = nil + end) + + it("list of string dependencies are all registered", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { 'test/dep-a', 'test/dep-b' }, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - local state = require('zpack.state') + helpers.flush_pending() + local state = require('zpack.state') - helpers.assert_not_nil(state.spec_registry['https://github.com/test/dep-a'], - "first string dep should be registered") - helpers.assert_not_nil(state.spec_registry['https://github.com/test/dep-b'], - "second string dep should be registered") + assert.is_not_nil(state.spec_registry['https://github.com/test/dep-a'], + "first string dep should be registered") + assert.is_not_nil(state.spec_registry['https://github.com/test/dep-b'], + "second string dep should be registered") + end) - helpers.cleanup_test_env() - end) + it("list of table specs is treated as list", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a', opts = {} }, + { 'test/plugin-b', opts = { bar = true } }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + + assert.is_not_nil(state.spec_registry['https://github.com/test/plugin-a'], + "first spec should be registered") + assert.is_not_nil(state.spec_registry['https://github.com/test/plugin-b'], + "second spec should be registered") + end) - helpers.test("list of table specs is treated as list", function() - helpers.setup_test_env() + it("single dependency with config preserves config function", function() + local config_called = false - require('zpack').setup({ - spec = { - { 'test/plugin-a', opts = {} }, - { 'test/plugin-b', opts = { bar = true } }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - - helpers.assert_not_nil(state.spec_registry['https://github.com/test/plugin-a'], - "first spec should be registered") - helpers.assert_not_nil(state.spec_registry['https://github.com/test/plugin-b'], - "second spec should be registered") - - helpers.cleanup_test_env() - end) - - helpers.test("single dependency with config preserves config function", function() - helpers.setup_test_env() - local config_called = false - - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { - 'test/dep-with-config', - config = function() config_called = true end, - }, + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { + 'test/dep-with-config', + config = function() config_called = true end, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local dep_entry = state.spec_registry['https://github.com/test/dep-with-config'] + }, + defaults = { confirm = false }, + }) - helpers.assert_not_nil(dep_entry, "dependency should be registered") - helpers.assert_not_nil(dep_entry.merged_spec.config, "config should be preserved on dependency") + helpers.flush_pending() + local state = require('zpack.state') + local dep_entry = state.spec_registry['https://github.com/test/dep-with-config'] - helpers.cleanup_test_env() - end) + assert.is_not_nil(dep_entry, "dependency should be registered") + assert.is_not_nil(dep_entry.merged_spec.config, "config should be preserved on dependency") end) -end +end) diff --git a/tests/lazy_cmd_test.lua b/tests/lazy_cmd_test.lua index 1360ac6..6d7fe5a 100644 --- a/tests/lazy_cmd_test.lua +++ b/tests/lazy_cmd_test.lua @@ -1,122 +1,102 @@ local helpers = require('helpers') -return function() - helpers.describe("Lazy Loading - Commands", function() - helpers.test("plugin with cmd creates command placeholder", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - cmd = 'TestCommand', - }, +describe("Lazy Loading - Commands", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("plugin with cmd creates command placeholder", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + cmd = 'TestCommand', }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local commands = vim.api.nvim_get_commands({}) - helpers.assert_not_nil(commands.TestCommand, "Command should be created") + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) - - helpers.test("plugin with multiple cmds creates all commands", function() - helpers.setup_test_env() + helpers.flush_pending() + local commands = vim.api.nvim_get_commands({}) + assert.is_not_nil(commands.TestCommand, "Command should be created") + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - cmd = { 'TestCmd1', 'TestCmd2', 'TestCmd3' }, - }, + it("plugin with multiple cmds creates all commands", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + cmd = { 'TestCmd1', 'TestCmd2', 'TestCmd3' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local commands = vim.api.nvim_get_commands({}) - helpers.assert_not_nil(commands.TestCmd1, "Command 1 should be created") - helpers.assert_not_nil(commands.TestCmd2, "Command 2 should be created") - helpers.assert_not_nil(commands.TestCmd3, "Command 3 should be created") - - helpers.cleanup_test_env() - end) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local commands = vim.api.nvim_get_commands({}) + assert.is_not_nil(commands.TestCmd1, "Command 1 should be created") + assert.is_not_nil(commands.TestCmd2, "Command 2 should be created") + assert.is_not_nil(commands.TestCmd3, "Command 3 should be created") + end) - helpers.test("lazy cmd plugin does not load at startup", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("lazy cmd plugin does not load at startup", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/plugin', - cmd = 'TestCommand', - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + cmd = 'TestCommand', }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - helpers.assert_equal( - state.spec_registry[src].load_status, - "pending", - "Lazy cmd plugin should not be loaded at startup" - ) + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) - - helpers.test("plugin loads when command is invoked", function() - helpers.setup_test_env() - local loaded = false + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + assert.are.equal("pending", state.spec_registry[src].load_status) + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - cmd = 'TestCommand', - config = function() - loaded = true - end, - }, + it("plugin loads when command is invoked", function() + local loaded = false + + require('zpack').setup({ + spec = { + { + 'test/plugin', + cmd = 'TestCommand', + config = function() + loaded = true + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - pcall(vim.cmd, 'TestCommand') - helpers.flush_pending() - helpers.assert_true(loaded, "Plugin should load when command is invoked") - - helpers.cleanup_test_env() - end) - - helpers.test("plugin loads when command is invoked with args", function() - helpers.setup_test_env() - local loaded = false + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + pcall(vim.cmd, 'TestCommand') + helpers.flush_pending() + assert.is_truthy(loaded, "Plugin should load when command is invoked") + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - cmd = 'TestCommand', - config = function() - loaded = true - end, - }, + it("plugin loads when command is invoked with args", function() + local loaded = false + + require('zpack').setup({ + spec = { + { + 'test/plugin', + cmd = 'TestCommand', + config = function() + loaded = true + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - pcall(vim.cmd, 'TestCommand somearg') - helpers.flush_pending() - helpers.assert_true(loaded, "Plugin should load when command is invoked with args") - - helpers.cleanup_test_env() - end) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + pcall(vim.cmd, 'TestCommand somearg') + helpers.flush_pending() + assert.is_truthy(loaded, "Plugin should load when command is invoked with args") end) -end +end) diff --git a/tests/lazy_event_test.lua b/tests/lazy_event_test.lua index de3ec4d..85fc878 100644 --- a/tests/lazy_event_test.lua +++ b/tests/lazy_event_test.lua @@ -1,694 +1,640 @@ local helpers = require('helpers') -return function() - helpers.describe("Lazy Loading - Events", function() - helpers.test("inline event pattern is parsed correctly", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'BufReadPre *.lua', - }, +describe("Lazy Loading - Events", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("inline event pattern is parsed correctly", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'BufReadPre *.lua', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) + assert.is_not_nil( + helpers.find_autocmd(autocmds, 'BufReadPre', '*.lua'), + "Inline event pattern should create autocmd" + ) + end) - helpers.flush_pending() - local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) - helpers.assert_not_nil( - helpers.find_autocmd(autocmds, 'BufReadPre', '*.lua'), - "Inline event pattern should create autocmd" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("EventSpec with pattern creates autocmd with pattern", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = { - event = 'BufRead', - pattern = '*.rs', - }, - }, - }, - defaults = { confirm = false }, - }) + it("EventSpec with pattern creates autocmd with pattern", function() + local state = require('zpack.state') - helpers.flush_pending() - local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) - helpers.assert_not_nil( - helpers.find_autocmd(autocmds, 'BufReadPost', '*.rs'), - "EventSpec pattern should create autocmd with pattern" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("EventSpec with multiple patterns creates autocmd", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = { - event = 'BufRead', - pattern = { '*.lua', '*.vim' }, - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = { + event = 'BufRead', + pattern = '*.rs', }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) - local found = helpers.find_autocmd(autocmds, 'BufReadPost', '*.lua') - or helpers.find_autocmd(autocmds, 'BufReadPost', '*.vim') - helpers.assert_not_nil(found, "EventSpec with multiple patterns should create autocmd") - - helpers.cleanup_test_env() - end) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) + assert.is_not_nil( + helpers.find_autocmd(autocmds, 'BufReadPost', '*.rs'), + "EventSpec pattern should create autocmd with pattern" + ) + end) - helpers.test("global pattern fallback is applied to events", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("EventSpec with multiple patterns creates autocmd", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/plugin', + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = { event = 'BufRead', - pattern = '*.md', + pattern = { '*.lua', '*.vim' }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) - helpers.assert_not_nil( - helpers.find_autocmd(autocmds, 'BufReadPost', '*.md'), - "Global pattern should be applied to events" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("VeryLazy event creates UIEnter autocmd", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'VeryLazy', - }, - }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) + local found = helpers.find_autocmd(autocmds, 'BufReadPost', '*.lua') + or helpers.find_autocmd(autocmds, 'BufReadPost', '*.vim') + assert.is_not_nil(found, "EventSpec with multiple patterns should create autocmd") + end) - helpers.flush_pending() - local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) - helpers.assert_not_nil( - helpers.find_autocmd(autocmds, 'UIEnter'), - "VeryLazy should create UIEnter autocmd" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("multiple EventSpecs with different patterns", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = { - { event = 'BufReadPre', pattern = '*.lua' }, - { event = 'BufNewFile', pattern = '*.rs' }, - }, - }, - }, - defaults = { confirm = false }, - }) + it("global pattern fallback is applied to events", function() + local state = require('zpack.state') - helpers.flush_pending() - local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) - helpers.assert_not_nil( - helpers.find_autocmd(autocmds, 'BufReadPre', '*.lua'), - "Should create BufReadPre autocmd with *.lua pattern" - ) - helpers.assert_not_nil( - helpers.find_autocmd(autocmds, 'BufNewFile', '*.rs'), - "Should create BufNewFile autocmd with *.rs pattern" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("re-fired event forwards original ev.data", function() - helpers.setup_test_env() - - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) - - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'BufReadPost', - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'BufRead', + pattern = '*.md', }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) + assert.is_not_nil( + helpers.find_autocmd(autocmds, 'BufReadPost', '*.md'), + "Global pattern should be applied to events" + ) + end) - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_data.lua') - local received_data = nil + it("VeryLazy event creates UIEnter autocmd", function() + local state = require('zpack.state') - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - vim.api.nvim_create_autocmd('BufReadPost', { - group = test_group, - pattern = '*', - callback = function(ev) - received_data = ev.data - end, - once = true, - }) - end + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'VeryLazy', + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) + assert.is_not_nil( + helpers.find_autocmd(autocmds, 'UIEnter'), + "VeryLazy should create UIEnter autocmd" + ) + end) - vim.api.nvim_exec_autocmds('BufReadPost', { - buffer = buf, - data = { client_id = 42 }, - }) + it("multiple EventSpecs with different patterns", function() + local state = require('zpack.state') - helpers.flush_pending() + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = { + { event = 'BufReadPre', pattern = '*.lua' }, + { event = 'BufNewFile', pattern = '*.rs' }, + }, + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) + assert.is_not_nil( + helpers.find_autocmd(autocmds, 'BufReadPre', '*.lua'), + "Should create BufReadPre autocmd with *.lua pattern" + ) + assert.is_not_nil( + helpers.find_autocmd(autocmds, 'BufNewFile', '*.rs'), + "Should create BufNewFile autocmd with *.rs pattern" + ) + end) - helpers.assert_not_nil(received_data, "Re-fired event should forward data") - helpers.assert_equal(received_data.client_id, 42, "Re-fired event data should contain original fields") + it("re-fired event forwards original ev.data", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(test_group) - helpers.cleanup_test_env() - end) + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'BufReadPost', + }, + }, + defaults = { confirm = false }, + }) - helpers.test("re-fire triggers when buffer is still valid", function() - helpers.setup_test_env() + helpers.flush_pending() - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_data.lua') + local received_data = nil - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'BufReadPost', - }, - }, - defaults = { confirm = false }, + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) + vim.api.nvim_create_autocmd('BufReadPost', { + group = test_group, + pattern = '*', + callback = function(ev) + received_data = ev.data + end, + once = true, }) + end - helpers.flush_pending() + vim.api.nvim_exec_autocmds('BufReadPost', { + buffer = buf, + data = { client_id = 42 }, + }) - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_refire.lua') - local refire_count = 0 + helpers.flush_pending() - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - vim.api.nvim_create_autocmd('BufReadPost', { - group = test_group, - pattern = '*', - callback = function() - refire_count = refire_count + 1 - end, - once = true, - }) - end - - vim.api.nvim_exec_autocmds('BufReadPost', { buffer = buf }) + assert.is_not_nil(received_data, "Re-fired event should forward data") + assert.are.equal(42, received_data.client_id) - helpers.flush_pending() + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(test_group) + end) - helpers.assert_equal(refire_count, 1, "Should re-fire event when buffer is valid") + it("re-fire triggers when buffer is still valid", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(test_group) - helpers.cleanup_test_env() - end) + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'BufReadPost', + }, + }, + defaults = { confirm = false }, + }) - helpers.test("re-fire skips invalid buffer without error", function() - helpers.setup_test_env() + helpers.flush_pending() - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_refire.lua') + local refire_count = 0 - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'BufReadPost', - }, - }, - defaults = { confirm = false }, + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) + vim.api.nvim_create_autocmd('BufReadPost', { + group = test_group, + pattern = '*', + callback = function() + refire_count = refire_count + 1 + end, + once = true, }) + end - helpers.flush_pending() + vim.api.nvim_exec_autocmds('BufReadPost', { buffer = buf }) - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_invalid.lua') - local refire_count = 0 - - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - vim.api.nvim_buf_delete(buf, { force = true }) - vim.api.nvim_create_autocmd('BufReadPost', { - group = test_group, - pattern = '*', - callback = function() - refire_count = refire_count + 1 - end, - once = true, - }) - end + helpers.flush_pending() - local ok = pcall(vim.api.nvim_exec_autocmds, 'BufReadPost', { buffer = buf }) + assert.are.equal(1, refire_count) - helpers.flush_pending() + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(test_group) + end) - helpers.assert_true(ok, "Should not error when buffer becomes invalid during lazy-load") - helpers.assert_equal(refire_count, 0, "Should skip re-fire when buffer is no longer valid") + it("re-fire skips invalid buffer without error", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(test_group) - helpers.cleanup_test_env() - end) + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'BufReadPost', + }, + }, + defaults = { confirm = false }, + }) - helpers.test("re-fire chains BufReadPre before BufReadPost", function() - helpers.setup_test_env() + helpers.flush_pending() - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_invalid.lua') + local refire_count = 0 - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'BufReadPost', - }, - }, - defaults = { confirm = false }, + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) + vim.api.nvim_buf_delete(buf, { force = true }) + vim.api.nvim_create_autocmd('BufReadPost', { + group = test_group, + pattern = '*', + callback = function() + refire_count = refire_count + 1 + end, + once = true, }) + end - helpers.flush_pending() - - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_chain.lua') - local fired_events = {} + local ok = pcall(vim.api.nvim_exec_autocmds, 'BufReadPost', { buffer = buf }) - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - vim.api.nvim_create_autocmd('BufReadPre', { - group = test_group, - pattern = '*', - callback = function() table.insert(fired_events, 'BufReadPre') end, - once = true, - }) - vim.api.nvim_create_autocmd('BufReadPost', { - group = test_group, - pattern = '*', - callback = function() table.insert(fired_events, 'BufReadPost') end, - once = true, - }) - end + helpers.flush_pending() - vim.api.nvim_exec_autocmds('BufReadPost', { buffer = buf }) + assert.is_truthy(ok, "Should not error when buffer becomes invalid during lazy-load") + assert.are.equal(0, refire_count) - helpers.flush_pending() + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(test_group) + end) - helpers.assert_equal(#fired_events, 2, "Should fire both BufReadPre and BufReadPost") - helpers.assert_equal(fired_events[1], 'BufReadPre', "BufReadPre should fire first") - helpers.assert_equal(fired_events[2], 'BufReadPost', "BufReadPost should fire second") + it("re-fire chains BufReadPre before BufReadPost", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(test_group) - helpers.cleanup_test_env() - end) + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'BufReadPost', + }, + }, + defaults = { confirm = false }, + }) - helpers.test("re-fire chains BufReadPre and BufReadPost before FileType", function() - helpers.setup_test_env() + helpers.flush_pending() - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_chain.lua') + local fired_events = {} - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'FileType', - }, - }, - defaults = { confirm = false }, + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) + vim.api.nvim_create_autocmd('BufReadPre', { + group = test_group, + pattern = '*', + callback = function() table.insert(fired_events, 'BufReadPre') end, + once = true, }) + vim.api.nvim_create_autocmd('BufReadPost', { + group = test_group, + pattern = '*', + callback = function() table.insert(fired_events, 'BufReadPost') end, + once = true, + }) + end - helpers.flush_pending() - - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_ft_chain.lua') - local fired_events = {} - - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - for _, ev_name in ipairs({ 'BufReadPre', 'BufReadPost', 'FileType' }) do - vim.api.nvim_create_autocmd(ev_name, { - group = test_group, - pattern = '*', - callback = function() table.insert(fired_events, ev_name) end, - once = true, - }) - end - end - - vim.api.nvim_exec_autocmds('FileType', { buffer = buf }) - - helpers.flush_pending() + vim.api.nvim_exec_autocmds('BufReadPost', { buffer = buf }) - helpers.assert_equal(#fired_events, 3, "Should fire all three events") - helpers.assert_equal(fired_events[1], 'BufReadPre', "BufReadPre should fire first") - helpers.assert_equal(fired_events[2], 'BufReadPost', "BufReadPost should fire second") - helpers.assert_equal(fired_events[3], 'FileType', "FileType should fire last") + helpers.flush_pending() - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(test_group) - helpers.cleanup_test_env() - end) + assert.are.equal(2, #fired_events) + assert.are.equal('BufReadPre', fired_events[1]) + assert.are.equal('BufReadPost', fired_events[2]) - helpers.test("re-fire chain only forwards data to the original event", function() - helpers.setup_test_env() + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(test_group) + end) - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) + it("re-fire chains BufReadPre and BufReadPost before FileType", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'BufReadPost', - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'FileType', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_data_chain.lua') - local pre_data = 'not_called' - local post_data = 'not_called' + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_ft_chain.lua') + local fired_events = {} - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - vim.api.nvim_create_autocmd('BufReadPre', { + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) + for _, ev_name in ipairs({ 'BufReadPre', 'BufReadPost', 'FileType' }) do + vim.api.nvim_create_autocmd(ev_name, { group = test_group, pattern = '*', - callback = function(ev) pre_data = ev.data end, - once = true, - }) - vim.api.nvim_create_autocmd('BufReadPost', { - group = test_group, - pattern = '*', - callback = function(ev) post_data = ev.data end, + callback = function() table.insert(fired_events, ev_name) end, once = true, }) end + end - vim.api.nvim_exec_autocmds('BufReadPost', { - buffer = buf, - data = { client_id = 99 }, - }) - - helpers.flush_pending() + vim.api.nvim_exec_autocmds('FileType', { buffer = buf }) - helpers.assert_nil(pre_data, "BufReadPre should not receive data") - helpers.assert_not_nil(post_data, "BufReadPost should receive data") - helpers.assert_equal(post_data.client_id, 99, "BufReadPost data should match original") + helpers.flush_pending() - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(test_group) - helpers.cleanup_test_env() - end) + assert.are.equal(3, #fired_events) + assert.are.equal('BufReadPre', fired_events[1]) + assert.are.equal('BufReadPost', fired_events[2]) + assert.are.equal('FileType', fired_events[3]) - helpers.test("event without chain does not fire dependency events", function() - helpers.setup_test_env() + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(test_group) + end) - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) + it("re-fire chain only forwards data to the original event", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'LspAttach', - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'BufReadPost', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_no_chain.lua') - local fired_events = {} + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_data_chain.lua') + local pre_data = 'not_called' + local post_data = 'not_called' - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - vim.api.nvim_create_autocmd('LspAttach', { - group = test_group, - pattern = '*', - callback = function() table.insert(fired_events, 'LspAttach') end, - once = true, - }) - vim.api.nvim_create_autocmd('BufReadPre', { - group = test_group, - pattern = '*', - callback = function() table.insert(fired_events, 'BufReadPre') end, - once = true, - }) - end - - vim.api.nvim_exec_autocmds('LspAttach', { buffer = buf }) + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) + vim.api.nvim_create_autocmd('BufReadPre', { + group = test_group, + pattern = '*', + callback = function(ev) pre_data = ev.data end, + once = true, + }) + vim.api.nvim_create_autocmd('BufReadPost', { + group = test_group, + pattern = '*', + callback = function(ev) post_data = ev.data end, + once = true, + }) + end - helpers.flush_pending() + vim.api.nvim_exec_autocmds('BufReadPost', { + buffer = buf, + data = { client_id = 99 }, + }) - helpers.assert_equal(#fired_events, 1, "Should only fire the original event") - helpers.assert_equal(fired_events[1], 'LspAttach', "Only LspAttach should fire, no chain") + helpers.flush_pending() - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(test_group) - helpers.cleanup_test_env() - end) + assert.is_nil(pre_data, "BufReadPre should not receive data") + assert.is_not_nil(post_data, "BufReadPost should receive data") + assert.are.equal(99, post_data.client_id) - helpers.test("re-fire does not double-fire pre-existing augroup handlers", function() - helpers.setup_test_env() + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(test_group) + end) - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local pre_existing_group = vim.api.nvim_create_augroup('PreExisting', { clear = true }) + it("event without chain does not fire dependency events", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'BufReadPost', - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'LspAttach', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_dedup.lua') - local pre_existing_count = 0 + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_no_chain.lua') + local fired_events = {} - vim.api.nvim_create_autocmd('BufReadPost', { - group = pre_existing_group, + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) + vim.api.nvim_create_autocmd('LspAttach', { + group = test_group, pattern = '*', - callback = function() - pre_existing_count = pre_existing_count + 1 - end, + callback = function() table.insert(fired_events, 'LspAttach') end, + once = true, }) + vim.api.nvim_create_autocmd('BufReadPre', { + group = test_group, + pattern = '*', + callback = function() table.insert(fired_events, 'BufReadPre') end, + once = true, + }) + end - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - end + vim.api.nvim_exec_autocmds('LspAttach', { buffer = buf }) + + helpers.flush_pending() + + assert.are.equal(1, #fired_events) + assert.are.equal('LspAttach', fired_events[1]) - vim.api.nvim_exec_autocmds('BufReadPost', { buffer = buf }) + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(test_group) + end) - helpers.flush_pending() + it("re-fire does not double-fire pre-existing augroup handlers", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local pre_existing_group = vim.api.nvim_create_augroup('PreExisting', { clear = true }) - helpers.assert_equal(pre_existing_count, 1, "Pre-existing handler should fire once (original only), not twice") + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'BufReadPost', + }, + }, + defaults = { confirm = false }, + }) - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(pre_existing_group) - helpers.cleanup_test_env() - end) + helpers.flush_pending() - helpers.test("re-fire fires only newly-added augroup handlers", function() - helpers.setup_test_env() + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_dedup.lua') + local pre_existing_count = 0 - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local pre_existing_group = vim.api.nvim_create_augroup('PreExisting', { clear = true }) - local new_group = vim.api.nvim_create_augroup('NewPlugin', { clear = true }) + vim.api.nvim_create_autocmd('BufReadPost', { + group = pre_existing_group, + pattern = '*', + callback = function() + pre_existing_count = pre_existing_count + 1 + end, + }) - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'BufReadPost', - }, - }, - defaults = { confirm = false }, - }) + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) + end - helpers.flush_pending() + vim.api.nvim_exec_autocmds('BufReadPost', { buffer = buf }) - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_new_only.lua') - local pre_existing_count = 0 - local new_count = 0 + helpers.flush_pending() + assert.are.equal(1, pre_existing_count) + + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(pre_existing_group) + end) + + it("re-fire fires only newly-added augroup handlers", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local pre_existing_group = vim.api.nvim_create_augroup('PreExisting', { clear = true }) + local new_group = vim.api.nvim_create_augroup('NewPlugin', { clear = true }) + + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'BufReadPost', + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_new_only.lua') + local pre_existing_count = 0 + local new_count = 0 + + vim.api.nvim_create_autocmd('BufReadPost', { + group = pre_existing_group, + pattern = '*', + callback = function() + pre_existing_count = pre_existing_count + 1 + end, + }) + + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) vim.api.nvim_create_autocmd('BufReadPost', { - group = pre_existing_group, + group = new_group, pattern = '*', callback = function() - pre_existing_count = pre_existing_count + 1 + new_count = new_count + 1 end, }) + end - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - vim.api.nvim_create_autocmd('BufReadPost', { - group = new_group, - pattern = '*', - callback = function() - new_count = new_count + 1 - end, - }) - end - - vim.api.nvim_exec_autocmds('BufReadPost', { buffer = buf }) - - helpers.flush_pending() - - helpers.assert_equal(pre_existing_count, 1, "Pre-existing handler should fire once (original only)") - helpers.assert_equal(new_count, 1, "New handler should fire once (re-fire only)") + vim.api.nvim_exec_autocmds('BufReadPost', { buffer = buf }) - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(pre_existing_group) - vim.api.nvim_del_augroup_by_id(new_group) - helpers.cleanup_test_env() - end) + helpers.flush_pending() - helpers.test("re-fire pcall prevents one group error from breaking the chain", function() - helpers.setup_test_env() + assert.are.equal(1, pre_existing_count) + assert.are.equal(1, new_count) - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local error_group = vim.api.nvim_create_augroup('ErrorPlugin', { clear = true }) - local ok_group = vim.api.nvim_create_augroup('OkPlugin', { clear = true }) + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(pre_existing_group) + vim.api.nvim_del_augroup_by_id(new_group) + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'BufReadPost', - }, + it("re-fire pcall prevents one group error from breaking the chain", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local error_group = vim.api.nvim_create_augroup('ErrorPlugin', { clear = true }) + local ok_group = vim.api.nvim_create_augroup('OkPlugin', { clear = true }) + + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'BufReadPost', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_pcall.lua') - local ok_fired = false + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_pcall.lua') + local ok_fired = false - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - vim.api.nvim_create_autocmd('BufReadPre', { - group = error_group, - pattern = '*', - callback = function() error("intentional test error") end, - once = true, - }) - vim.api.nvim_create_autocmd('BufReadPost', { - group = ok_group, - pattern = '*', - callback = function() ok_fired = true end, - once = true, - }) - end + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) + vim.api.nvim_create_autocmd('BufReadPre', { + group = error_group, + pattern = '*', + callback = function() error("intentional test error") end, + once = true, + }) + vim.api.nvim_create_autocmd('BufReadPost', { + group = ok_group, + pattern = '*', + callback = function() ok_fired = true end, + once = true, + }) + end - local ok = pcall(vim.api.nvim_exec_autocmds, 'BufReadPost', { buffer = buf }) + local ok = pcall(vim.api.nvim_exec_autocmds, 'BufReadPost', { buffer = buf }) - helpers.flush_pending() + helpers.flush_pending() - helpers.assert_true(ok_fired, "BufReadPost handler should still fire despite BufReadPre error") + assert.is_truthy(ok_fired, "BufReadPost handler should still fire despite BufReadPre error") - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(error_group) - vim.api.nvim_del_augroup_by_id(ok_group) - helpers.cleanup_test_env() - end) + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(error_group) + vim.api.nvim_del_augroup_by_id(ok_group) + end) - helpers.test("lazy event plugin does not load at startup", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("lazy event plugin does not load at startup", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/plugin', - event = 'BufRead', - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + event = 'BufRead', }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - helpers.assert_equal( - state.spec_registry[src].load_status, - "pending", - "Lazy event plugin should not be loaded at startup" - ) + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + assert.are.equal("pending", state.spec_registry[src].load_status) end) -end +end) diff --git a/tests/lazy_ft_test.lua b/tests/lazy_ft_test.lua index dea7bda..2fc948c 100644 --- a/tests/lazy_ft_test.lua +++ b/tests/lazy_ft_test.lua @@ -1,304 +1,280 @@ local helpers = require('helpers') -return function() - helpers.describe("Lazy Loading - FileType", function() - helpers.test("single filetype creates FileType autocmd", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - ft = 'rust', - }, +describe("Lazy Loading - FileType", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("single filetype creates FileType autocmd", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { + { + 'test/plugin', + ft = 'rust', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) + assert.is_not_nil( + helpers.find_autocmd(autocmds, 'FileType', 'rust'), + "Single filetype should create FileType autocmd" + ) + end) - helpers.flush_pending() - local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) - helpers.assert_not_nil( - helpers.find_autocmd(autocmds, 'FileType', 'rust'), - "Single filetype should create FileType autocmd" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("multiple filetypes create FileType autocmd with all patterns", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - ft = { 'lua', 'vim', 'python' }, - }, - }, - defaults = { confirm = false }, - }) + it("multiple filetypes create FileType autocmd with all patterns", function() + local state = require('zpack.state') - helpers.flush_pending() - local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) - local found = helpers.find_autocmd(autocmds, 'FileType', 'lua') - or helpers.find_autocmd(autocmds, 'FileType', 'vim') - or helpers.find_autocmd(autocmds, 'FileType', 'python') - helpers.assert_not_nil(found, "Multiple filetypes should create FileType autocmd") - - helpers.cleanup_test_env() - end) - - helpers.test("ft re-fire chains BufReadPre, BufReadPost, and FileType", function() - helpers.setup_test_env() - - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) - - require('zpack').setup({ - spec = { - { - 'test/plugin', - ft = 'lua', - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + ft = { 'lua', 'vim', 'python' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_ft.lua') - local fired_events = {} - - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - for _, ev_name in ipairs({ 'BufReadPre', 'BufReadPost', 'FileType' }) do - vim.api.nvim_create_autocmd(ev_name, { - group = test_group, - pattern = '*', - callback = function() table.insert(fired_events, ev_name) end, - once = true, - }) - end - end - - vim.api.nvim_set_current_buf(buf) - vim.bo[buf].filetype = 'lua' - - helpers.flush_pending() - - helpers.assert_equal(#fired_events, 3, "Should fire all three events in chain") - helpers.assert_equal(fired_events[1], 'BufReadPre', "BufReadPre should fire first") - helpers.assert_equal(fired_events[2], 'BufReadPost', "BufReadPost should fire second") - helpers.assert_equal(fired_events[3], 'FileType', "FileType should fire last") - - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(test_group) - helpers.cleanup_test_env() - end) - - helpers.test("ft re-fire forwards data only to FileType", function() - helpers.setup_test_env() + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local autocmds = vim.api.nvim_get_autocmds({ group = state.lazy_group }) + local found = helpers.find_autocmd(autocmds, 'FileType', 'lua') + or helpers.find_autocmd(autocmds, 'FileType', 'vim') + or helpers.find_autocmd(autocmds, 'FileType', 'python') + assert.is_not_nil(found, "Multiple filetypes should create FileType autocmd") + end) - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) + it("ft re-fire chains BufReadPre, BufReadPost, and FileType", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) - require('zpack').setup({ - spec = { - { - 'test/plugin', - ft = 'lua', - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + ft = 'lua', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_ft_data.lua') - local pre_data = 'not_called' - local post_data = 'not_called' - local ft_data = 'not_called' + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_ft.lua') + local fired_events = {} - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - vim.api.nvim_create_autocmd('BufReadPre', { + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) + for _, ev_name in ipairs({ 'BufReadPre', 'BufReadPost', 'FileType' }) do + vim.api.nvim_create_autocmd(ev_name, { group = test_group, pattern = '*', - callback = function(ev) pre_data = ev.data end, - once = true, - }) - vim.api.nvim_create_autocmd('BufReadPost', { - group = test_group, - pattern = '*', - callback = function(ev) post_data = ev.data end, - once = true, - }) - vim.api.nvim_create_autocmd('FileType', { - group = test_group, - pattern = '*', - callback = function(ev) ft_data = ev.data end, + callback = function() table.insert(fired_events, ev_name) end, once = true, }) end + end - vim.api.nvim_set_current_buf(buf) - vim.bo[buf].filetype = 'lua' - - helpers.flush_pending() + vim.api.nvim_set_current_buf(buf) + vim.bo[buf].filetype = 'lua' - helpers.assert_nil(pre_data, "BufReadPre should not receive data") - helpers.assert_nil(post_data, "BufReadPost should not receive data") + helpers.flush_pending() - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(test_group) - helpers.cleanup_test_env() - end) + assert.are.equal(3, #fired_events) + assert.are.equal('BufReadPre', fired_events[1]) + assert.are.equal('BufReadPost', fired_events[2]) + assert.are.equal('FileType', fired_events[3]) - helpers.test("ft re-fire fires ALL FileType handlers including pre-existing", function() - helpers.setup_test_env() + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(test_group) + end) - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local pre_existing_group = vim.api.nvim_create_augroup('PreExistingFT', { clear = true }) - local new_group = vim.api.nvim_create_augroup('NewPluginFT', { clear = true }) + it("ft re-fire forwards data only to FileType", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local test_group = vim.api.nvim_create_augroup('ZpackTest', { clear = true }) - require('zpack').setup({ - spec = { - { - 'test/plugin', - ft = 'lua', - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + ft = 'lua', }, - defaults = { confirm = false }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_ft_data.lua') + local pre_data = 'not_called' + local post_data = 'not_called' + local ft_data = 'not_called' + + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) + vim.api.nvim_create_autocmd('BufReadPre', { + group = test_group, + pattern = '*', + callback = function(ev) pre_data = ev.data end, + once = true, }) + vim.api.nvim_create_autocmd('BufReadPost', { + group = test_group, + pattern = '*', + callback = function(ev) post_data = ev.data end, + once = true, + }) + vim.api.nvim_create_autocmd('FileType', { + group = test_group, + pattern = '*', + callback = function(ev) ft_data = ev.data end, + once = true, + }) + end - helpers.flush_pending() + vim.api.nvim_set_current_buf(buf) + vim.bo[buf].filetype = 'lua' - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_ft_all.lua') - local pre_existing_count = 0 - local new_count = 0 + helpers.flush_pending() + + assert.is_nil(pre_data, "BufReadPre should not receive data") + assert.is_nil(post_data, "BufReadPost should not receive data") + + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(test_group) + end) + it("ft re-fire fires ALL FileType handlers including pre-existing", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local pre_existing_group = vim.api.nvim_create_augroup('PreExistingFT', { clear = true }) + local new_group = vim.api.nvim_create_augroup('NewPluginFT', { clear = true }) + + require('zpack').setup({ + spec = { + { + 'test/plugin', + ft = 'lua', + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_ft_all.lua') + local pre_existing_count = 0 + local new_count = 0 + + vim.api.nvim_create_autocmd('FileType', { + group = pre_existing_group, + pattern = '*', + callback = function() + pre_existing_count = pre_existing_count + 1 + end, + }) + + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) vim.api.nvim_create_autocmd('FileType', { - group = pre_existing_group, + group = new_group, pattern = '*', callback = function() - pre_existing_count = pre_existing_count + 1 + new_count = new_count + 1 end, }) + end - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - vim.api.nvim_create_autocmd('FileType', { - group = new_group, - pattern = '*', - callback = function() - new_count = new_count + 1 - end, - }) - end - - vim.api.nvim_set_current_buf(buf) - vim.bo[buf].filetype = 'lua' - - helpers.flush_pending() + vim.api.nvim_set_current_buf(buf) + vim.bo[buf].filetype = 'lua' - helpers.assert_true(pre_existing_count >= 2, "Pre-existing FileType handler should fire from both original and re-fire") - helpers.assert_true(new_count >= 1, "New FileType handler should fire from re-fire") + helpers.flush_pending() - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(pre_existing_group) - vim.api.nvim_del_augroup_by_id(new_group) - helpers.cleanup_test_env() - end) + assert.is_truthy(pre_existing_count >= 2, "Pre-existing FileType handler should fire from both original and re-fire") + assert.is_truthy(new_count >= 1, "New FileType handler should fire from re-fire") - helpers.test("ft re-fire deduplicates BufReadPost chain events", function() - helpers.setup_test_env() - - local loader = require('zpack.plugin_loader') - local original_process_spec = loader.process_spec - local pre_existing_group = vim.api.nvim_create_augroup('PreExistingBRP', { clear = true }) - local new_group = vim.api.nvim_create_augroup('NewPluginBRP', { clear = true }) + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(pre_existing_group) + vim.api.nvim_del_augroup_by_id(new_group) + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - ft = 'lua', - }, + it("ft re-fire deduplicates BufReadPost chain events", function() + local loader = require('zpack.plugin_loader') + local original_process_spec = loader.process_spec + local pre_existing_group = vim.api.nvim_create_augroup('PreExistingBRP', { clear = true }) + local new_group = vim.api.nvim_create_augroup('NewPluginBRP', { clear = true }) + + require('zpack').setup({ + spec = { + { + 'test/plugin', + ft = 'lua', }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_ft_dedup.lua') - local pre_existing_count = 0 - local new_count = 0 - + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(buf, vim.fn.tempname() .. '/test_ft_dedup.lua') + local pre_existing_count = 0 + local new_count = 0 + + vim.api.nvim_create_autocmd('BufReadPost', { + group = pre_existing_group, + pattern = '*', + callback = function() + pre_existing_count = pre_existing_count + 1 + end, + }) + + loader.process_spec = function(pack_spec) + original_process_spec(pack_spec) vim.api.nvim_create_autocmd('BufReadPost', { - group = pre_existing_group, + group = new_group, pattern = '*', callback = function() - pre_existing_count = pre_existing_count + 1 + new_count = new_count + 1 end, }) + end - loader.process_spec = function(pack_spec) - original_process_spec(pack_spec) - vim.api.nvim_create_autocmd('BufReadPost', { - group = new_group, - pattern = '*', - callback = function() - new_count = new_count + 1 - end, - }) - end - - vim.api.nvim_set_current_buf(buf) - vim.bo[buf].filetype = 'lua' + vim.api.nvim_set_current_buf(buf) + vim.bo[buf].filetype = 'lua' - helpers.flush_pending() + helpers.flush_pending() - helpers.assert_equal(pre_existing_count, 0, "Pre-existing BufReadPost handler should not fire from re-fire") - helpers.assert_equal(new_count, 1, "New BufReadPost handler should fire once from re-fire") + assert.are.equal(0, pre_existing_count) + assert.are.equal(1, new_count) - loader.process_spec = original_process_spec - vim.api.nvim_del_augroup_by_id(pre_existing_group) - vim.api.nvim_del_augroup_by_id(new_group) - helpers.cleanup_test_env() - end) + loader.process_spec = original_process_spec + vim.api.nvim_del_augroup_by_id(pre_existing_group) + vim.api.nvim_del_augroup_by_id(new_group) + end) - helpers.test("lazy ft plugin does not load at startup", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("lazy ft plugin does not load at startup", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/plugin', - ft = 'lua', - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + ft = 'lua', }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - helpers.assert_equal( - state.spec_registry[src].load_status, - "pending", - "Lazy ft plugin should not be loaded at startup" - ) + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + assert.are.equal("pending", state.spec_registry[src].load_status) end) -end +end) diff --git a/tests/lazy_keys_test.lua b/tests/lazy_keys_test.lua index 0e4f4eb..5ebe453 100644 --- a/tests/lazy_keys_test.lua +++ b/tests/lazy_keys_test.lua @@ -1,739 +1,646 @@ local helpers = require('helpers') -return function() - helpers.describe("Lazy Loading - Keymaps", function() - helpers.test("KeySpec supports string shorthand", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - keys = 'tk', - config = function() end, - }, +describe("Lazy Loading - Keymaps", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("KeySpec supports string shorthand", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + keys = 'tk', + config = function() end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' tk' then - found = true - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' tk' then + found = true + break end - helpers.assert_true(found, "String key should create keymap") - - helpers.cleanup_test_env() - end) - - helpers.test("KeySpec supports table format with desc", function() - helpers.setup_test_env() + end + assert.is_truthy(found, "String key should create keymap") + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - keys = { - { 'td', function() end, desc = 'Test description' }, - }, + it("KeySpec supports table format with desc", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + keys = { + { 'td', function() end, desc = 'Test description' }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found_with_desc = false - for _, map in ipairs(keymaps) do - if map.lhs == ' td' and map.desc == 'Test description' then - found_with_desc = true - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found_with_desc = false + for _, map in ipairs(keymaps) do + if map.lhs == ' td' and map.desc == 'Test description' then + found_with_desc = true + break end - helpers.assert_true(found_with_desc, "KeySpec should create keymap with description") - - helpers.cleanup_test_env() - end) - - helpers.test("KeySpec supports custom modes", function() - helpers.setup_test_env() + end + assert.is_truthy(found_with_desc, "KeySpec should create keymap with description") + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - keys = { - { 'tv', function() end, mode = { 'n', 'v' } }, - }, + it("KeySpec supports custom modes", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + keys = { + { 'tv', function() end, mode = { 'n', 'v' } }, }, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - local normal_maps = vim.api.nvim_get_keymap('n') - local visual_maps = vim.api.nvim_get_keymap('v') + helpers.flush_pending() + local normal_maps = vim.api.nvim_get_keymap('n') + local visual_maps = vim.api.nvim_get_keymap('v') - local found_in_normal = false - local found_in_visual = false + local found_in_normal = false + local found_in_visual = false - for _, map in ipairs(normal_maps) do - if map.lhs == ' tv' then - found_in_normal = true - break - end + for _, map in ipairs(normal_maps) do + if map.lhs == ' tv' then + found_in_normal = true + break end + end - for _, map in ipairs(visual_maps) do - if map.lhs == ' tv' then - found_in_visual = true - break - end + for _, map in ipairs(visual_maps) do + if map.lhs == ' tv' then + found_in_visual = true + break end + end - helpers.assert_true(found_in_normal, "KeySpec should create keymap in normal mode") - helpers.assert_true(found_in_visual, "KeySpec should create keymap in visual mode") - - helpers.cleanup_test_env() - end) + assert.is_truthy(found_in_normal, "KeySpec should create keymap in normal mode") + assert.is_truthy(found_in_visual, "KeySpec should create keymap in visual mode") + end) - helpers.test("lazy keys plugin does not load at startup", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("lazy keys plugin does not load at startup", function() + local state = require('zpack.state') - require('zpack').setup({ - spec = { - { - 'test/plugin', - keys = 'tl', - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + keys = 'tl', }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - helpers.assert_equal( - state.spec_registry[src].load_status, - "pending", - "Lazy keys plugin should not be loaded at startup" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("KeySpec forwards expr=true", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - lazy = false, - keys = { - { 'te', function() return 'foo' end, expr = true }, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + assert.are.equal("pending", state.spec_registry[src].load_status) + end) + + it("KeySpec forwards expr=true", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + lazy = false, + keys = { + { 'te', function() return 'foo' end, expr = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' te' then - found = true - helpers.assert_equal(map.expr, 1, "expr should be forwarded to vim.keymap.set") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' te' then + found = true + assert.are.equal(1, map.expr) + break end - helpers.assert_true(found, "Eager KeySpec should create keymap") - - helpers.cleanup_test_env() - end) - - helpers.test("KeySpec expr=true: rhs return value is fed as keys", function() - helpers.setup_test_env() - - local target_hits = 0 - vim.keymap.set('n', 'gxxq', function() target_hits = target_hits + 1 end) - - local rhs_called = 0 - require('zpack').setup({ - spec = { - { - 'test/plugin', - lazy = false, - keys = { - -- remap=true so the returned 'gxxq' goes through the target mapping below. - { 'gxxe', function() rhs_called = rhs_called + 1; return 'gxxq' end, expr = true, remap = true }, - }, + end + assert.is_truthy(found, "Eager KeySpec should create keymap") + end) + + it("KeySpec expr=true: rhs return value is fed as keys", function() + local target_hits = 0 + vim.keymap.set('n', 'gxxq', function() target_hits = target_hits + 1 end) + + local rhs_called = 0 + require('zpack').setup({ + spec = { + { + 'test/plugin', + lazy = false, + keys = { + -- remap=true so the returned 'gxxq' goes through the target mapping below. + { 'gxxe', function() rhs_called = rhs_called + 1; return 'gxxq' end, expr = true, remap = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - vim.api.nvim_feedkeys('gxxe', 'mx', false) - helpers.assert_equal(rhs_called, 1, "expr rhs should be invoked when lhs is fed") - helpers.assert_equal(target_hits, 1, "rhs return value should be replayed as keys") - - pcall(vim.keymap.del, 'n', 'gxxe') - pcall(vim.keymap.del, 'n', 'gxxq') - helpers.cleanup_test_env() - end) - - helpers.test("KeySpec forwards silent=true", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - lazy = false, - keys = { - { 'ts', function() end, silent = true }, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + vim.api.nvim_feedkeys('gxxe', 'mx', false) + assert.are.equal(1, rhs_called) + assert.are.equal(1, target_hits) + + pcall(vim.keymap.del, 'n', 'gxxe') + pcall(vim.keymap.del, 'n', 'gxxq') + end) + + it("KeySpec forwards silent=true", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + lazy = false, + keys = { + { 'ts', function() end, silent = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' ts' then - found = true - helpers.assert_equal(map.silent, 1, "silent should be forwarded to vim.keymap.set") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' ts' then + found = true + assert.are.equal(1, map.silent) + break end - helpers.assert_true(found, "Eager KeySpec should create keymap") - - helpers.cleanup_test_env() - end) - - helpers.test("KeySpec forwards noremap=true", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - lazy = false, - keys = { - { 'tn', function() end, noremap = true }, - }, + end + assert.is_truthy(found, "Eager KeySpec should create keymap") + end) + + it("KeySpec forwards noremap=true", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + lazy = false, + keys = { + { 'tn', function() end, noremap = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' tn' then - found = true - helpers.assert_equal(map.noremap, 1, "noremap should be forwarded to vim.keymap.set") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' tn' then + found = true + assert.are.equal(1, map.noremap) + break end - helpers.assert_true(found, "Eager KeySpec should create keymap") - - helpers.cleanup_test_env() - end) - - -- vim.keymap.set ignores `noremap` and derives it from `remap`. Without the - -- alias translation in keymap.lua, the keymap below would still come out - -- non-remappable (noremap=1) because `remap` defaults to false. - helpers.test("KeySpec forwards noremap=false (lazy.nvim alias for remap=true)", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - lazy = false, - keys = { - { 'tnf', function() end, noremap = false }, - }, + end + assert.is_truthy(found, "Eager KeySpec should create keymap") + end) + + -- vim.keymap.set ignores `noremap` and derives it from `remap`. Without the + -- alias translation in keymap.lua, the keymap below would still come out + -- non-remappable (noremap=1) because `remap` defaults to false. + it("KeySpec forwards noremap=false (lazy.nvim alias for remap=true)", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + lazy = false, + keys = { + { 'tnf', function() end, noremap = false }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' tnf' then - found = true - helpers.assert_equal(map.noremap, 0, "noremap=false should produce a remappable keymap") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' tnf' then + found = true + assert.are.equal(0, map.noremap) + break end - helpers.assert_true(found, "Eager KeySpec should create keymap") - - helpers.cleanup_test_env() - end) - - -- Explicit `remap` should win over `noremap` when both are set. - helpers.test("KeySpec: explicit remap takes precedence over noremap alias", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - lazy = false, - keys = { - { 'tnp', function() end, remap = true, noremap = true }, - }, + end + assert.is_truthy(found, "Eager KeySpec should create keymap") + end) + + -- Explicit `remap` should win over `noremap` when both are set. + it("KeySpec: explicit remap takes precedence over noremap alias", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + lazy = false, + keys = { + { 'tnp', function() end, remap = true, noremap = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' tnp' then - found = true - helpers.assert_equal(map.noremap, 0, "remap=true wins; resulting keymap should be remappable") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' tnp' then + found = true + assert.are.equal(0, map.noremap) + break end - helpers.assert_true(found, "Eager KeySpec should create keymap") - - helpers.cleanup_test_env() - end) - - helpers.test("KeySpec forwards replace_keycodes=true with expr", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - lazy = false, - keys = { - { 'tr', function() return '' end, expr = true, replace_keycodes = true }, - }, + end + assert.is_truthy(found, "Eager KeySpec should create keymap") + end) + + it("KeySpec forwards replace_keycodes=true with expr", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + lazy = false, + keys = { + { 'tr', function() return '' end, expr = true, replace_keycodes = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' tr' then - found = true - helpers.assert_equal(map.expr, 1, "expr should be forwarded") - helpers.assert_equal(map.replace_keycodes, 1, "replace_keycodes should be forwarded") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' tr' then + found = true + assert.are.equal(1, map.expr) + assert.are.equal(1, map.replace_keycodes) + break end - helpers.assert_true(found, "Eager KeySpec should create keymap") - - helpers.cleanup_test_env() - end) - - helpers.test("Lazy proxy keymap is not expr", function() - helpers.setup_test_env() + end + assert.is_truthy(found, "Eager KeySpec should create keymap") + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - keys = { - { 'tx', function() return 'foo' end, expr = true }, - }, + it("Lazy proxy keymap is not expr", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + keys = { + { 'tx', function() return 'foo' end, expr = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' tx' then - found = true - helpers.assert_equal(map.expr, 0, "Lazy proxy mapping must not be expr") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' tx' then + found = true + assert.are.equal(0, map.expr) + break end - helpers.assert_true(found, "Lazy proxy keymap should be installed") - - helpers.cleanup_test_env() - end) - - helpers.test("KeySpec forwards remap=true alone", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - lazy = false, - keys = { - { 'tra', function() end, remap = true }, - }, + end + assert.is_truthy(found, "Lazy proxy keymap should be installed") + end) + + it("KeySpec forwards remap=true alone", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + lazy = false, + keys = { + { 'tra', function() end, remap = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' tra' then - found = true - helpers.assert_equal(map.noremap, 0, "remap=true should produce a remappable keymap") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' tra' then + found = true + assert.are.equal(0, map.noremap) + break end - helpers.assert_true(found, "Eager KeySpec should create keymap") - - helpers.cleanup_test_env() - end) - - helpers.test("KeySpec defaults replace_keycodes to true when expr=true", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - lazy = false, - keys = { - { 'trd', function() return '' end, expr = true }, - }, + end + assert.is_truthy(found, "Eager KeySpec should create keymap") + end) + + it("KeySpec defaults replace_keycodes to true when expr=true", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + lazy = false, + keys = { + { 'trd', function() return '' end, expr = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' trd' then - found = true - helpers.assert_equal(map.expr, 1, "expr should be forwarded") - helpers.assert_equal(map.replace_keycodes, 1, "replace_keycodes should default to true when expr=true") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' trd' then + found = true + assert.are.equal(1, map.expr) + assert.are.equal(1, map.replace_keycodes) + break end - helpers.assert_true(found, "Eager KeySpec should create keymap") - - helpers.cleanup_test_env() - end) - - helpers.test("Lazy proxy keymap forwards nowait=true", function() - helpers.setup_test_env() + end + assert.is_truthy(found, "Eager KeySpec should create keymap") + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - keys = { - { 'tw', function() end, nowait = true }, - }, + it("Lazy proxy keymap forwards nowait=true", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + keys = { + { 'tw', function() end, nowait = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' tw' then - found = true - helpers.assert_equal(map.nowait, 1, "Lazy proxy should forward nowait") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' tw' then + found = true + assert.are.equal(1, map.nowait) + break end - helpers.assert_true(found, "Lazy proxy keymap should be installed") - - helpers.cleanup_test_env() - end) - - helpers.test("Lazy proxy keymap forwards silent=true", function() - helpers.setup_test_env() + end + assert.is_truthy(found, "Lazy proxy keymap should be installed") + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - keys = { - { 'tsl', function() end, silent = true }, - }, + it("Lazy proxy keymap forwards silent=true", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + keys = { + { 'tsl', function() end, silent = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' tsl' then - found = true - helpers.assert_equal(map.silent, 1, "Lazy proxy should forward silent") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' tsl' then + found = true + assert.are.equal(1, map.silent) + break end - helpers.assert_true(found, "Lazy proxy keymap should be installed") - - helpers.cleanup_test_env() - end) - - helpers.test("KeySpec preserves explicit replace_keycodes=false override", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - lazy = false, - keys = { - { 'tro', function() return '' end, expr = true, replace_keycodes = false }, - }, + end + assert.is_truthy(found, "Lazy proxy keymap should be installed") + end) + + it("KeySpec preserves explicit replace_keycodes=false override", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + lazy = false, + keys = { + { 'tro', function() return '' end, expr = true, replace_keycodes = false }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' tro' then - found = true - helpers.assert_equal(map.expr, 1, "expr should be forwarded") - helpers.assert_equal(map.replace_keycodes, 0, "explicit replace_keycodes=false must override the expr-implied default") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' tro' then + found = true + assert.are.equal(1, map.expr) + assert.are.equal(0, map.replace_keycodes) + break end - helpers.assert_true(found, "Eager KeySpec should create keymap") - - helpers.cleanup_test_env() - end) - - helpers.test("Lazy proxy keymap forwards remap=true", function() - helpers.setup_test_env() + end + assert.is_truthy(found, "Eager KeySpec should create keymap") + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - keys = { - { 'txr', function() end, remap = true }, - }, + it("Lazy proxy keymap forwards remap=true", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + keys = { + { 'txr', function() end, remap = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' txr' then - found = true - helpers.assert_equal(map.noremap, 0, "Lazy proxy should reflect remap=true (noremap=0)") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' txr' then + found = true + assert.are.equal(0, map.noremap) + break end - helpers.assert_true(found, "Lazy proxy keymap should be installed") - - helpers.cleanup_test_env() - end) - - helpers.test("Lazy proxy keymap translates noremap=false alias", function() - helpers.setup_test_env() + end + assert.is_truthy(found, "Lazy proxy keymap should be installed") + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - keys = { - { 'txn', function() end, noremap = false }, - }, + it("Lazy proxy keymap translates noremap=false alias", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + keys = { + { 'txn', function() end, noremap = false }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' txn' then - found = true - helpers.assert_equal(map.noremap, 0, "Lazy proxy should translate noremap=false to remap=true") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' txn' then + found = true + assert.are.equal(0, map.noremap) + break end - helpers.assert_true(found, "Lazy proxy keymap should be installed") - - helpers.cleanup_test_env() - end) - - -- vim.keymap.set raises if replace_keycodes is set without expr. The opts - -- whitelist used to forward replace_keycodes unconditionally, which would - -- crash apply_keys for a user spec like { lhs, rhs, replace_keycodes = true }. - helpers.test("KeySpec drops replace_keycodes when expr is unset", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - lazy = false, - keys = { - { 'trk', function() end, replace_keycodes = true }, - }, + end + assert.is_truthy(found, "Lazy proxy keymap should be installed") + end) + + -- vim.keymap.set raises if replace_keycodes is set without expr. The opts + -- whitelist used to forward replace_keycodes unconditionally, which would + -- crash apply_keys for a user spec like { lhs, rhs, replace_keycodes = true }. + it("KeySpec drops replace_keycodes when expr is unset", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + lazy = false, + keys = { + { 'trk', function() end, replace_keycodes = true }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local keymaps = vim.api.nvim_get_keymap('n') - local found = false - for _, map in ipairs(keymaps) do - if map.lhs == ' trk' then - found = true - helpers.assert_equal(map.expr, 0, "expr should not be implied") - helpers.assert_equal(map.replace_keycodes, 0, "replace_keycodes should be dropped when expr is unset") - break - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local keymaps = vim.api.nvim_get_keymap('n') + local found = false + for _, map in ipairs(keymaps) do + if map.lhs == ' trk' then + found = true + assert.are.equal(0, map.expr) + assert.are.equal(0, map.replace_keycodes) + break end - helpers.assert_true(found, "Eager KeySpec should create keymap without crashing") - - helpers.cleanup_test_env() - end) - - -- Covers the post-trigger apply_keys path (plugin_loader.lua → apply_keys), - -- which the eager `lazy = false` tests above don't exercise. After the proxy - -- fires and the plugin loads, the real keymap must carry the user's opts. - helpers.test("Lazy proxy load handoff: real keymap forwards silent and noremap alias", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - keys = { - { 'tlh', function() end, silent = true, noremap = false }, - }, + end + assert.is_truthy(found, "Eager KeySpec should create keymap without crashing") + end) + + -- Covers the post-trigger apply_keys path (plugin_loader.lua → apply_keys), + -- which the eager `lazy = false` tests above don't exercise. After the proxy + -- fires and the plugin loads, the real keymap must carry the user's opts. + it("Lazy proxy load handoff: real keymap forwards silent and noremap alias", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + keys = { + { 'tlh', function() end, silent = true, noremap = false }, }, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - local function find_map(lhs) - for _, map in ipairs(vim.api.nvim_get_keymap('n')) do - if map.lhs == lhs then return map end - end - return nil + local function find_map(lhs) + for _, map in ipairs(vim.api.nvim_get_keymap('n')) do + if map.lhs == lhs then return map end end + return nil + end - vim.api.nvim_feedkeys(' tlh', 'mx', false) - helpers.flush_pending() - - local real = find_map(' tlh') - helpers.assert_not_nil(real, "Real keymap should exist after lazy proxy fires") - helpers.assert_equal(real.silent, 1, "Post-load real keymap should forward silent=true") - helpers.assert_equal(real.noremap, 0, "Post-load real keymap should reflect noremap=false alias") + vim.api.nvim_feedkeys(' tlh', 'mx', false) + helpers.flush_pending() - helpers.cleanup_test_env() - end) - - helpers.test("Lazy proxy load handoff installs the real expr keymap", function() - helpers.setup_test_env() + local real = find_map(' tlh') + assert.is_not_nil(real, "Real keymap should exist after lazy proxy fires") + assert.are.equal(1, real.silent) + assert.are.equal(0, real.noremap) + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - keys = { - { 'txp', function() return '' end, expr = true }, - }, + it("Lazy proxy load handoff installs the real expr keymap", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + keys = { + { 'txp', function() return '' end, expr = true }, }, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - local function find_map(lhs) - for _, map in ipairs(vim.api.nvim_get_keymap('n')) do - if map.lhs == lhs then return map end - end - return nil + local function find_map(lhs) + for _, map in ipairs(vim.api.nvim_get_keymap('n')) do + if map.lhs == lhs then return map end end + return nil + end - local proxy = find_map(' txp') - helpers.assert_not_nil(proxy, "Proxy keymap should be installed") - helpers.assert_equal(proxy.expr, 0, "Proxy must not be expr") - - vim.api.nvim_feedkeys(' txp', 'mx', false) - helpers.flush_pending() - - local real = find_map(' txp') - helpers.assert_not_nil(real, "Real keymap should exist after lazy proxy fires") - helpers.assert_equal(real.expr, 1, "Post-load real keymap should be expr=true") + local proxy = find_map(' txp') + assert.is_not_nil(proxy, "Proxy keymap should be installed") + assert.are.equal(0, proxy.expr) - helpers.cleanup_test_env() - end) + vim.api.nvim_feedkeys(' txp', 'mx', false) + helpers.flush_pending() - -- Regression for the 'i'-mode feedkeys behavior documented in keys.lua. - helpers.test("Lazy proxy preserves typeahead order for multi-key sequences", function() - helpers.setup_test_env() - - local order = {} - vim.keymap.set('n', 'zb', function() table.insert(order, 'b') end) + local real = find_map(' txp') + assert.is_not_nil(real, "Real keymap should exist after lazy proxy fires") + assert.are.equal(1, real.expr) + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - keys = { { 'zi' } }, - config = function() - vim.keymap.set('n', 'zi', function() table.insert(order, 'i') end) - end, - }, + -- Regression for the 'i'-mode feedkeys behavior documented in keys.lua. + it("Lazy proxy preserves typeahead order for multi-key sequences", function() + local order = {} + vim.keymap.set('n', 'zb', function() table.insert(order, 'b') end) + + require('zpack').setup({ + spec = { + { + 'test/plugin', + keys = { { 'zi' } }, + config = function() + vim.keymap.set('n', 'zi', function() table.insert(order, 'i') end) + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - vim.api.nvim_feedkeys('zizb', 'mx', false) - helpers.flush_pending() + helpers.flush_pending() + vim.api.nvim_feedkeys('zizb', 'mx', false) + helpers.flush_pending() - helpers.assert_equal(table.concat(order), 'ib', - "expected order 'ib' (re-fed `zi` before queued `zb`)") + assert.are.equal('ib', table.concat(order)) - pcall(vim.keymap.del, 'n', 'zi') - pcall(vim.keymap.del, 'n', 'zb') - helpers.cleanup_test_env() - end) + pcall(vim.keymap.del, 'n', 'zi') + pcall(vim.keymap.del, 'n', 'zb') end) -end +end) diff --git a/tests/lifecycle_test.lua b/tests/lifecycle_test.lua index 5038970..40c3356 100644 --- a/tests/lifecycle_test.lua +++ b/tests/lifecycle_test.lua @@ -1,236 +1,201 @@ local helpers = require('helpers') -return function() - helpers.describe("Plugin Lifecycle Hooks", function() - helpers.test("init hook runs before plugin loads", function() - helpers.setup_test_env() - local init_ran = false - local config_ran = false - local init_ran_before_config = false - - require('zpack').setup({ - spec = { - { - 'test/plugin', - init = function() - init_ran = true - if not config_ran then - init_ran_before_config = true - end - end, - config = function() - config_ran = true - end, - }, +describe("Plugin Lifecycle Hooks", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("init hook runs before plugin loads", function() + local init_ran = false + local config_ran = false + local init_ran_before_config = false + + require('zpack').setup({ + spec = { + { + 'test/plugin', + init = function() + init_ran = true + if not config_ran then + init_ran_before_config = true + end + end, + config = function() + config_ran = true + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_true(init_ran, "init hook should run") - helpers.assert_true(init_ran_before_config, "init should run before config") - - helpers.cleanup_test_env() - end) - - helpers.test("config hook runs after plugin loads", function() - helpers.setup_test_env() - local config_ran = false - - require('zpack').setup({ - spec = { - { - 'test/plugin', - config = function() - config_ran = true - end, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_true(config_ran, "config hook should run") - - helpers.cleanup_test_env() - end) - - helpers.test("init runs for lazy plugins at setup time", function() - helpers.setup_test_env() - local init_ran = false - - require('zpack').setup({ - spec = { - { - 'test/plugin', - cmd = 'TestCommand', - init = function() - init_ran = true - end, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_true(init_ran, "init should run for lazy plugins at setup time") - - helpers.cleanup_test_env() - end) - - helpers.test("init runs only once for lazy plugins", function() - helpers.setup_test_env() - local init_count = 0 - - require('zpack').setup({ - spec = { - { - 'test/plugin', - cmd = 'TestCommand', - init = function() - init_count = init_count + 1 - end, - config = function() end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_truthy(init_ran, "init hook should run") + assert.is_truthy(init_ran_before_config, "init should run before config") + end) + + it("config hook runs after plugin loads", function() + local config_ran = false + + require('zpack').setup({ + spec = { + { + 'test/plugin', + config = function() + config_ran = true + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_equal(init_count, 1, "init should run once at startup") - - pcall(vim.cmd, 'TestCommand') - helpers.flush_pending() - helpers.assert_equal(init_count, 1, "init should not run again when plugin loads") - - helpers.cleanup_test_env() - end) - - helpers.test("config does not run for lazy plugins at setup time", function() - helpers.setup_test_env() - local config_ran = false - - require('zpack').setup({ - spec = { - { - 'test/plugin', - cmd = 'TestCommand', - config = function() - config_ran = true - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_truthy(config_ran, "config hook should run") + end) + + it("init runs for lazy plugins at setup time", function() + local init_ran = false + + require('zpack').setup({ + spec = { + { + 'test/plugin', + cmd = 'TestCommand', + init = function() + init_ran = true + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - helpers.assert_false(config_ran, "config should not run for lazy plugins at setup time") + helpers.flush_pending() + assert.is_truthy(init_ran, "init should run for lazy plugins at setup time") + end) + + it("init runs only once for lazy plugins", function() + local init_count = 0 + + require('zpack').setup({ + spec = { + { + 'test/plugin', + cmd = 'TestCommand', + init = function() + init_count = init_count + 1 + end, + config = function() end, + }, + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() + assert.are.equal(1, init_count) - helpers.test("build hook is string command", function() - helpers.setup_test_env() + pcall(vim.cmd, 'TestCommand') + helpers.flush_pending() + assert.are.equal(1, init_count) + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - build = 'echo "build completed"', - }, + it("config does not run for lazy plugins at setup time", function() + local config_ran = false + + require('zpack').setup({ + spec = { + { + 'test/plugin', + cmd = 'TestCommand', + config = function() + config_ran = true + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - local spec = state.spec_registry[src].merged_spec - helpers.assert_not_nil(spec.build, "Build hook should be stored") - helpers.assert_equal( - type(spec.build), - 'string', - "Build hook should be string" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("build hook is function", function() - helpers.setup_test_env() - local build_fn = function() end - - require('zpack').setup({ - spec = { - { - 'test/plugin', - build = build_fn, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_falsy(config_ran, "config should not run for lazy plugins at setup time") + end) + + it("build hook is string command", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + build = 'echo "build completed"', }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - local spec = state.spec_registry[src].merged_spec - helpers.assert_not_nil(spec.build, "Build hook should be stored") - helpers.assert_equal( - type(spec.build), - 'function', - "Build hook should be function" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("init and config hooks work together", function() - helpers.setup_test_env() - local execution_order = {} - - require('zpack').setup({ - spec = { - { - 'test/plugin', - init = function() - table.insert(execution_order, 'init') - end, - config = function() - table.insert(execution_order, 'config') - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' + local spec = state.spec_registry[src].merged_spec + assert.is_not_nil(spec.build, "Build hook should be stored") + assert.are.equal('string', type(spec.build)) + end) + + it("build hook is function", function() + local build_fn = function() end + + require('zpack').setup({ + spec = { + { + 'test/plugin', + build = build_fn, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_equal(#execution_order, 2, "Both hooks should run") - helpers.assert_equal(execution_order[1], 'init', "init should run first") - helpers.assert_equal(execution_order[2], 'config', "config should run second") - - helpers.cleanup_test_env() - end) - - helpers.test("config hook can access plugin module", function() - helpers.setup_test_env() - local can_access_globals = false - - require('zpack').setup({ - spec = { - { - 'test/plugin', - config = function() - can_access_globals = (vim ~= nil and vim.fn ~= nil) - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' + local spec = state.spec_registry[src].merged_spec + assert.is_not_nil(spec.build, "Build hook should be stored") + assert.are.equal('function', type(spec.build)) + end) + + it("init and config hooks work together", function() + local execution_order = {} + + require('zpack').setup({ + spec = { + { + 'test/plugin', + init = function() + table.insert(execution_order, 'init') + end, + config = function() + table.insert(execution_order, 'config') + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.are.equal(2, #execution_order) + assert.are.equal('init', execution_order[1]) + assert.are.equal('config', execution_order[2]) + end) + + it("config hook can access plugin module", function() + local can_access_globals = false - helpers.flush_pending() - helpers.assert_true(can_access_globals, "config should have access to vim globals") + require('zpack').setup({ + spec = { + { + 'test/plugin', + config = function() + can_access_globals = (vim ~= nil and vim.fn ~= nil) + end, + }, + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() + assert.is_truthy(can_access_globals, "config should have access to vim globals") end) -end +end) diff --git a/tests/merge_test.lua b/tests/merge_test.lua index ea8e6de..5144bdb 100644 --- a/tests/merge_test.lua +++ b/tests/merge_test.lua @@ -1,465 +1,397 @@ local helpers = require('helpers') -return function() - helpers.describe("Spec Merging", function() - helpers.test("duplicate specs are merged", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin', opts = { a = 1 } }, - { 'test/plugin', opts = { b = 2 } }, +describe("Spec Merging", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("duplicate specs are merged", function() + require('zpack').setup({ + spec = { + { 'test/plugin', opts = { a = 1 } }, + { 'test/plugin', opts = { b = 2 } }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' + + assert.are.equal(2, #state.spec_registry[src].specs) + assert.is_not_nil(state.spec_registry[src].merged_spec, "should have merged_spec") + end) + + it("opts are deep merged", function() + local received_opts = nil + + require('zpack').setup({ + spec = { + { 'test/plugin', opts = { a = 1, nested = { x = 1 } } }, + { + 'test/plugin', + opts = { b = 2, nested = { y = 2 } }, + config = function(plugin, opts) + received_opts = opts + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - - helpers.assert_equal(#state.spec_registry[src].specs, 2, "should have 2 specs") - helpers.assert_not_nil(state.spec_registry[src].merged_spec, "should have merged_spec") - - helpers.cleanup_test_env() - end) - - helpers.test("opts are deep merged", function() - helpers.setup_test_env() - local received_opts = nil - - require('zpack').setup({ - spec = { - { 'test/plugin', opts = { a = 1, nested = { x = 1 } } }, - { - 'test/plugin', - opts = { b = 2, nested = { y = 2 } }, - config = function(plugin, opts) - received_opts = opts - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_not_nil(received_opts, "config should receive merged opts") + assert.are.equal(1, received_opts.a) + assert.are.equal(2, received_opts.b) + assert.are.equal(1, received_opts.nested.x) + assert.are.equal(2, received_opts.nested.y) + end) + + it("override fields use last value", function() + require('zpack').setup({ + spec = { + { 'test/plugin', priority = 10, main = 'first' }, + { 'test/plugin', priority = 20, main = 'second' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' + local spec = state.spec_registry[src].merged_spec + + assert.are.equal(20, spec.priority) + assert.are.equal('second', spec.main) + end) + + it("list fields are extended uniquely", function() + require('zpack').setup({ + spec = { + { 'test/plugin', event = 'BufReadPre', cmd = 'Cmd1' }, + { 'test/plugin', event = { 'BufWritePost', 'BufReadPre' }, cmd = 'Cmd2' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' + local spec = state.spec_registry[src].merged_spec + + assert.are.equal(2, #spec.event) + assert.are.equal(2, #spec.cmd) + end) + + it("config function uses last declared", function() + local config_count = 0 + local which_config = nil + + require('zpack').setup({ + spec = { + { + 'test/plugin', + config = function() + config_count = config_count + 1 + which_config = 'first' + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_not_nil(received_opts, "config should receive merged opts") - helpers.assert_equal(received_opts.a, 1) - helpers.assert_equal(received_opts.b, 2) - helpers.assert_equal(received_opts.nested.x, 1) - helpers.assert_equal(received_opts.nested.y, 2) - - helpers.cleanup_test_env() - end) - - helpers.test("override fields use last value", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin', priority = 10, main = 'first' }, - { 'test/plugin', priority = 20, main = 'second' }, + { + 'test/plugin', + config = function() + config_count = config_count + 1 + which_config = 'second' + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - local spec = state.spec_registry[src].merged_spec + helpers.flush_pending() + assert.are.equal(1, config_count) + assert.are.equal('second', which_config) + end) - helpers.assert_equal(spec.priority, 20) - helpers.assert_equal(spec.main, 'second') + it("function-based opts receives accumulated opts", function() + local received_accumulated = nil - helpers.cleanup_test_env() - end) + require('zpack').setup({ + spec = { + { 'test/plugin', opts = { base = true } }, + { + 'test/plugin', + opts = function(plugin, accumulated) + received_accumulated = accumulated + return vim.tbl_extend('force', accumulated, { added = true }) + end, + config = function() end, + }, + }, + defaults = { confirm = false }, + }) - helpers.test("list fields are extended uniquely", function() - helpers.setup_test_env() + helpers.flush_pending() + assert.is_not_nil(received_accumulated, "should receive accumulated opts") + assert.are.equal(true, received_accumulated.base) + end) - require('zpack').setup({ - spec = { - { 'test/plugin', event = 'BufReadPre', cmd = 'Cmd1' }, - { 'test/plugin', event = { 'BufWritePost', 'BufReadPre' }, cmd = 'Cmd2' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - local spec = state.spec_registry[src].merged_spec - - helpers.assert_equal(#spec.event, 2, "should have 2 unique events") - helpers.assert_equal(#spec.cmd, 2, "should have 2 commands") - - helpers.cleanup_test_env() - end) - - helpers.test("config function uses last declared", function() - helpers.setup_test_env() - local config_count = 0 - local which_config = nil - - require('zpack').setup({ - spec = { - { - 'test/plugin', - config = function() - config_count = config_count + 1 - which_config = 'first' - end, - }, - { - 'test/plugin', - config = function() - config_count = config_count + 1 - which_config = 'second' - end, + it("standalone specs have priority over dependency specs", function() + local received_opts = nil + + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { + { 'test/dep', opts = { from_dep = true, conflict = 'dep' } }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_equal(config_count, 1, "config should only run once") - helpers.assert_equal(which_config, 'second', "should use last config") - - helpers.cleanup_test_env() - end) - - helpers.test("function-based opts receives accumulated opts", function() - helpers.setup_test_env() - local received_accumulated = nil - - require('zpack').setup({ - spec = { - { 'test/plugin', opts = { base = true } }, - { - 'test/plugin', - opts = function(plugin, accumulated) - received_accumulated = accumulated - return vim.tbl_extend('force', accumulated, { added = true }) - end, - config = function() end, - }, + { + 'test/dep', + opts = { from_standalone = true, conflict = 'standalone' }, + config = function(plugin, opts) + received_opts = opts + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_not_nil(received_accumulated, "should receive accumulated opts") - helpers.assert_equal(received_accumulated.base, true) - - helpers.cleanup_test_env() - end) - - helpers.test("standalone specs have priority over dependency specs", function() - helpers.setup_test_env() - local received_opts = nil - - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { - { 'test/dep', opts = { from_dep = true, conflict = 'dep' } }, - }, - }, - { + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_not_nil(received_opts) + assert.are.equal(true, received_opts.from_dep) + assert.are.equal(true, received_opts.from_standalone) + assert.are.equal('standalone', received_opts.conflict) + end) + + it("standalone branch is not overridden by nil dependency branch", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { 'test/dep', - opts = { from_standalone = true, conflict = 'standalone' }, - config = function(plugin, opts) - received_opts = opts - end, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_not_nil(received_opts) - helpers.assert_equal(received_opts.from_dep, true) - helpers.assert_equal(received_opts.from_standalone, true) - helpers.assert_equal(received_opts.conflict, 'standalone', "standalone should win") - - helpers.cleanup_test_env() - end) - - helpers.test("standalone branch is not overridden by nil dependency branch", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { - 'test/dep', - }, - }, - { - 'test/dep', - branch = 'main', - }, + { + 'test/dep', + branch = 'main', }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/dep' + }, + defaults = { confirm = false }, + }) - local merged_spec = state.spec_registry[src].merged_spec - helpers.assert_equal(merged_spec.branch, 'main', "standalone branch should be preserved") + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/dep' - local pack_spec = state.src_to_pack_spec[src] - helpers.assert_equal(pack_spec.version, 'main', "pack_spec.version should use merged branch") + local merged_spec = state.spec_registry[src].merged_spec + assert.are.equal('main', merged_spec.branch) - helpers.cleanup_test_env() - end) - - helpers.test("dependency branch is used when standalone has no branch", function() - helpers.setup_test_env() + local pack_spec = state.src_to_pack_spec[src] + assert.are.equal('main', pack_spec.version) + end) - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { - { 'test/dep', branch = 'develop' }, - }, - }, - { - 'test/dep', - config = function() end, + it("dependency branch is used when standalone has no branch", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { + { 'test/dep', branch = 'develop' }, }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/dep' + { + 'test/dep', + config = function() end, + }, + }, + defaults = { confirm = false }, + }) - local merged_spec = state.spec_registry[src].merged_spec - helpers.assert_equal(merged_spec.branch, 'develop', "dependency branch should be used when standalone has none") + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/dep' - local pack_spec = state.src_to_pack_spec[src] - helpers.assert_equal(pack_spec.version, 'develop', "pack_spec.version should use dependency branch") + local merged_spec = state.spec_registry[src].merged_spec + assert.are.equal('develop', merged_spec.branch) - helpers.cleanup_test_env() - end) + local pack_spec = state.src_to_pack_spec[src] + assert.are.equal('develop', pack_spec.version) + end) +end) + +describe("Merge Module Unit Tests", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("merge_specs skips opts (resolved lazily by resolve_opts)", function() + local merge = require('zpack.merge') + + -- opts is intentionally not collapsed by merge_specs. It is resolved + -- at load time by resolve_opts so function-form opts compose correctly + -- with a single authoritative code path. See merge.lua field_strategies + -- comment. Callers that need the deep-merged value should invoke + -- merge.resolve_opts(sorted_specs, plugin). + local base = { opts = { a = 1, nested = { x = 1 } } } + local incoming = { opts = { b = 2, nested = { y = 2 } } } + local result = merge.merge_specs(base, incoming) + + assert.is_nil(result.opts, "merge_specs must never populate opts") + + -- resolve_opts is the authoritative path and deep-merges table-form opts. + local resolved = merge.resolve_opts({ base, incoming }, {}) + assert.are.equal(1, resolved.a) + assert.are.equal(2, resolved.b) + assert.are.equal(1, resolved.nested.x) + assert.are.equal(2, resolved.nested.y) end) - helpers.describe("Merge Module Unit Tests", function() - helpers.test("merge_specs skips opts (resolved lazily by resolve_opts)", function() - helpers.setup_test_env() - local merge = require('zpack.merge') - - -- opts is intentionally not collapsed by merge_specs. It is resolved - -- at load time by resolve_opts so function-form opts compose correctly - -- with a single authoritative code path. See merge.lua field_strategies - -- comment. Callers that need the deep-merged value should invoke - -- merge.resolve_opts(sorted_specs, plugin). - local base = { opts = { a = 1, nested = { x = 1 } } } - local incoming = { opts = { b = 2, nested = { y = 2 } } } - local result = merge.merge_specs(base, incoming) - - helpers.assert_nil(result.opts, "merge_specs must never populate opts") - - -- resolve_opts is the authoritative path and deep-merges table-form opts. - local resolved = merge.resolve_opts({ base, incoming }, {}) - helpers.assert_equal(resolved.a, 1) - helpers.assert_equal(resolved.b, 2) - helpers.assert_equal(resolved.nested.x, 1) - helpers.assert_equal(resolved.nested.y, 2) - - helpers.cleanup_test_env() - end) - - helpers.test("merge_specs extends list fields uniquely", function() - helpers.setup_test_env() - local merge = require('zpack.merge') - - local base = { event = { 'A', 'B' } } - local incoming = { event = { 'B', 'C' } } - local result = merge.merge_specs(base, incoming) - - helpers.assert_equal(#result.event, 3) - - helpers.cleanup_test_env() - end) - - helpers.test("merge_specs uses AND logic for cond", function() - helpers.setup_test_env() - local merge = require('zpack.merge') - - local base = { cond = true } - local incoming = { cond = false } - local result = merge.merge_specs(base, incoming) - - helpers.assert_equal(result.cond, false) - - helpers.cleanup_test_env() - end) - - helpers.test("merge_and for enabled: function returning false composes to false", function() - helpers.setup_test_env() - local merge = require('zpack.merge') - local utils = require('zpack.utils') - - local base = { enabled = function() return false end } - local incoming = { enabled = true } - local result = merge.merge_specs(base, incoming) - - helpers.assert_equal(type(result.enabled), "function", "merged enabled should be callable") - helpers.assert_false( - utils.check_enabled(result), - "enabled fn returning false must propagate through merge, not collapse via ternary" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("merge_and for cond: function returning false composes to false", function() - helpers.setup_test_env() - local merge = require('zpack.merge') - local utils = require('zpack.utils') - - local base = { cond = function() return false end } - local incoming = { cond = true } - local result = merge.merge_specs(base, incoming) - - helpers.assert_equal(type(result.cond), "function", "merged cond should be callable") - helpers.assert_false( - utils.check_cond(result, {}), - "cond fn returning false must propagate through merge, not collapse via ternary" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("merge_and_enabled: merged function is called with no arguments", function() - helpers.setup_test_env() - local merge = require('zpack.merge') - local utils = require('zpack.utils') - - local received_arg - local base = { enabled = function(...) received_arg = select('#', ...); return true end } - local incoming = { enabled = function() return true end } - local result = merge.merge_specs(base, incoming) - utils.check_enabled(result) - - helpers.assert_equal( - received_arg, - 0, - "enabled functions must receive zero arguments when composed via merge" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("sort_specs puts dependencies before standalone", function() - helpers.setup_test_env() - local merge = require('zpack.merge') - - local specs = { - { _is_dependency = true, _import_order = 0 }, - { _is_dependency = false, _import_order = 1 }, - { _is_dependency = true, _import_order = 2 }, - } - local sorted = merge.sort_specs(specs) - - helpers.assert_true(sorted[1]._is_dependency, "first should be dependency") - helpers.assert_true(sorted[2]._is_dependency, "second should be dependency") - helpers.assert_false(sorted[3]._is_dependency, "last should be standalone (wins)") + it("merge_specs extends list fields uniquely", function() + local merge = require('zpack.merge') - helpers.cleanup_test_env() - end) + local base = { event = { 'A', 'B' } } + local incoming = { event = { 'B', 'C' } } + local result = merge.merge_specs(base, incoming) - helpers.test("resolve_opts accumulates through function opts", function() - helpers.setup_test_env() - local merge = require('zpack.merge') + assert.are.equal(3, #result.event) + end) - local specs = { - { opts = { a = 1 } }, - { - opts = function(plugin, acc) - return vim.tbl_extend('force', acc, { b = 2 }) - end, - }, - { opts = { c = 3 } }, - } - local result = merge.resolve_opts(specs, {}) + it("merge_specs uses AND logic for cond", function() + local merge = require('zpack.merge') - helpers.assert_equal(result.a, 1) - helpers.assert_equal(result.b, 2) - helpers.assert_equal(result.c, 3) + local base = { cond = true } + local incoming = { cond = false } + local result = merge.merge_specs(base, incoming) - helpers.cleanup_test_env() - end) + assert.are.equal(false, result.cond) + end) - helpers.test("keys with different modes are not deduplicated", function() - helpers.setup_test_env() + it("merge_and for enabled: function returning false composes to false", function() + local merge = require('zpack.merge') + local utils = require('zpack.utils') - require('zpack').setup({ - spec = { - { 'test/plugin', keys = { { 'a', mode = 'n', desc = 'normal' } } }, - { 'test/plugin', keys = { { 'a', mode = 'v', desc = 'visual' } } }, - }, - defaults = { confirm = false }, - }) + local base = { enabled = function() return false end } + local incoming = { enabled = true } + local result = merge.merge_specs(base, incoming) - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - local merged = state.spec_registry[src].merged_spec + assert.are.equal("function", type(result.enabled)) + assert.is_falsy( + utils.check_enabled(result), + "enabled fn returning false must propagate through merge, not collapse via ternary" + ) + end) - helpers.assert_equal(#merged.keys, 2, "both keys should be kept (different modes)") + it("merge_and for cond: function returning false composes to false", function() + local merge = require('zpack.merge') + local utils = require('zpack.utils') - helpers.cleanup_test_env() - end) + local base = { cond = function() return false end } + local incoming = { cond = true } + local result = merge.merge_specs(base, incoming) - helpers.test("keys with same lhs and mode are deduplicated", function() - helpers.setup_test_env() + assert.are.equal("function", type(result.cond)) + assert.is_falsy( + utils.check_cond(result, {}), + "cond fn returning false must propagate through merge, not collapse via ternary" + ) + end) - require('zpack').setup({ - spec = { - { 'test/plugin', keys = { { 'a', mode = 'n', desc = 'first' } } }, - { 'test/plugin', keys = { { 'a', mode = 'n', desc = 'second' } } }, - }, - defaults = { confirm = false }, - }) + it("merge_and_enabled: merged function is called with no arguments", function() + local merge = require('zpack.merge') + local utils = require('zpack.utils') - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - local merged = state.spec_registry[src].merged_spec + local received_arg + local base = { enabled = function(...) received_arg = select('#', ...); return true end } + local incoming = { enabled = function() return true end } + local result = merge.merge_specs(base, incoming) + utils.check_enabled(result) - helpers.assert_equal(#merged.keys, 1, "duplicate keys should be deduplicated") - helpers.assert_equal(merged.keys[1].desc, 'first', "first declaration wins") + assert.are.equal(0, received_arg) + end) - helpers.cleanup_test_env() - end) + it("sort_specs puts dependencies before standalone", function() + local merge = require('zpack.merge') - helpers.test("keys with same modes in different order are deduplicated", function() - helpers.setup_test_env() + local specs = { + { _is_dependency = true, _import_order = 0 }, + { _is_dependency = false, _import_order = 1 }, + { _is_dependency = true, _import_order = 2 }, + } + local sorted = merge.sort_specs(specs) - require('zpack').setup({ - spec = { - { 'test/plugin', keys = { { 'a', mode = { 'n', 'v' }, desc = 'first' } } }, - { 'test/plugin', keys = { { 'a', mode = { 'v', 'n' }, desc = 'second' } } }, - }, - defaults = { confirm = false }, - }) + assert.is_truthy(sorted[1]._is_dependency, "first should be dependency") + assert.is_truthy(sorted[2]._is_dependency, "second should be dependency") + assert.is_falsy(sorted[3]._is_dependency, "last should be standalone (wins)") + end) - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - local merged = state.spec_registry[src].merged_spec + it("resolve_opts accumulates through function opts", function() + local merge = require('zpack.merge') + + local specs = { + { opts = { a = 1 } }, + { + opts = function(plugin, acc) + return vim.tbl_extend('force', acc, { b = 2 }) + end, + }, + { opts = { c = 3 } }, + } + local result = merge.resolve_opts(specs, {}) + + assert.are.equal(1, result.a) + assert.are.equal(2, result.b) + assert.are.equal(3, result.c) + end) - helpers.assert_equal(#merged.keys, 1, "keys with same modes in different order should be deduplicated") + it("keys with different modes are not deduplicated", function() + require('zpack').setup({ + spec = { + { 'test/plugin', keys = { { 'a', mode = 'n', desc = 'normal' } } }, + { 'test/plugin', keys = { { 'a', mode = 'v', desc = 'visual' } } }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' + local merged = state.spec_registry[src].merged_spec + + assert.are.equal(2, #merged.keys) + end) + + it("keys with same lhs and mode are deduplicated", function() + require('zpack').setup({ + spec = { + { 'test/plugin', keys = { { 'a', mode = 'n', desc = 'first' } } }, + { 'test/plugin', keys = { { 'a', mode = 'n', desc = 'second' } } }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' + local merged = state.spec_registry[src].merged_spec + + assert.are.equal(1, #merged.keys) + assert.are.equal('first', merged.keys[1].desc) + end) - helpers.cleanup_test_env() - end) + it("keys with same modes in different order are deduplicated", function() + require('zpack').setup({ + spec = { + { 'test/plugin', keys = { { 'a', mode = { 'n', 'v' }, desc = 'first' } } }, + { 'test/plugin', keys = { { 'a', mode = { 'v', 'n' }, desc = 'second' } } }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' + local merged = state.spec_registry[src].merged_spec + + assert.are.equal(1, #merged.keys) end) -end +end) diff --git a/tests/module_loader_test.lua b/tests/module_loader_test.lua index 2007bb5..a741372 100644 --- a/tests/module_loader_test.lua +++ b/tests/module_loader_test.lua @@ -1,591 +1,534 @@ ---@diagnostic disable: duplicate-set-field local helpers = require('helpers') -return function() - helpers.describe("Module Loader", function() - helpers.test("module_to_src index is built for lazy plugins", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/lazy-plugin', lazy = true }, - { 'test/startup-plugin' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local module_loader = require('zpack.module_loader') - -- Access internal state via a test helper - local state = require('zpack.state') - local src = 'https://github.com/test/lazy-plugin' - helpers.assert_equal( - state.spec_registry[src].load_status, - "pending", - "Lazy plugin should be pending" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("module = false excludes plugin from module loader index", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/no-module-plugin', lazy = true, module = false, cmd = 'TestCmd' }, - }, - defaults = { confirm = false }, - }) +describe("Module Loader", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("module_to_src index is built for lazy plugins", function() + require('zpack').setup({ + spec = { + { 'test/lazy-plugin', lazy = true }, + { 'test/startup-plugin' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local module_loader = require('zpack.module_loader') + -- Access internal state via a test helper + local state = require('zpack.state') + local src = 'https://github.com/test/lazy-plugin' + assert.are.equal("pending", state.spec_registry[src].load_status) + end) + + it("module = false excludes plugin from module loader index", function() + require('zpack').setup({ + spec = { + { 'test/no-module-plugin', lazy = true, module = false, cmd = 'TestCmd' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local module_loader = require('zpack.module_loader') + local utils = require('zpack.utils') + -- The plugin should not be in the module index + -- We test this indirectly by checking the loader returns nil for this module + local result = module_loader.loader('nomoduleplugin') + assert.is_nil(result, "Loader should return nil for module=false plugin") + end) - helpers.flush_pending() + it("loader returns nil for unknown modules", function() + require('zpack').setup({ + spec = { + { 'test/some-plugin', lazy = true }, + }, + defaults = { confirm = false }, + }) - local module_loader = require('zpack.module_loader') - local utils = require('zpack.utils') - -- The plugin should not be in the module index - -- We test this indirectly by checking the loader returns nil for this module - local result = module_loader.loader('nomoduleplugin') - helpers.assert_nil(result, "Loader should return nil for module=false plugin") + helpers.flush_pending() - helpers.cleanup_test_env() - end) + local module_loader = require('zpack.module_loader') + local result = module_loader.loader('completely-unknown-module') + assert.is_nil(result, "Loader should return nil for unknown modules") + end) - helpers.test("loader returns nil for unknown modules", function() - helpers.setup_test_env() + it("loader returns nil for already loaded plugins", function() + require('zpack').setup({ + spec = { + { 'test/already-loaded', cmd = 'TestCmd' }, + }, + defaults = { confirm = false }, + }) - require('zpack').setup({ - spec = { - { 'test/some-plugin', lazy = true }, - }, - defaults = { confirm = false }, - }) + helpers.flush_pending() - helpers.flush_pending() + -- Manually set the plugin as loaded + local state = require('zpack.state') + local src = 'https://github.com/test/already-loaded' + state.spec_registry[src].load_status = "loaded" - local module_loader = require('zpack.module_loader') - local result = module_loader.loader('completely-unknown-module') - helpers.assert_nil(result, "Loader should return nil for unknown modules") + local module_loader = require('zpack.module_loader') + local result = module_loader.loader('already-loaded') + assert.is_nil(result, "Loader should return nil for already loaded plugins") + end) - helpers.cleanup_test_env() - end) + it("loader returns nil for plugins currently being loaded", function() + require('zpack').setup({ + spec = { + { 'test/loading-plugin', cmd = 'TestCmd' }, + }, + defaults = { confirm = false }, + }) - helpers.test("loader returns nil for already loaded plugins", function() - helpers.setup_test_env() + helpers.flush_pending() - require('zpack').setup({ - spec = { - { 'test/already-loaded', cmd = 'TestCmd' }, + -- Manually set the plugin as loading + local state = require('zpack.state') + local src = 'https://github.com/test/loading-plugin' + state.spec_registry[src].load_status = "loading" + + local module_loader = require('zpack.module_loader') + local result = module_loader.loader('loading-plugin') + assert.is_nil(result, "Loader should return nil for plugins being loaded") + end) + + it("loader triggers plugin load for pending lazy plugin", function() + local loaded = false + + require('zpack').setup({ + spec = { + { + 'test/trigger-plugin', + lazy = true, + config = function() + loaded = true + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() - helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/trigger-plugin' + assert.are.equal("pending", state.spec_registry[src].load_status) - -- Manually set the plugin as loaded - local state = require('zpack.state') - local src = 'https://github.com/test/already-loaded' - state.spec_registry[src].load_status = "loaded" + local module_loader = require('zpack.module_loader') + module_loader.loader('trigger-plugin') - local module_loader = require('zpack.module_loader') - local result = module_loader.loader('already-loaded') - helpers.assert_nil(result, "Loader should return nil for already loaded plugins") + helpers.flush_pending() + assert.is_truthy(loaded, "Config should have run") + assert.are.equal("loaded", state.spec_registry[src].load_status) + end) + + it("self-require during loading does not cause circular dependency error", function() + local config_called = false + local no_error = true + + require('zpack').setup({ + spec = { + { + 'test/self-require', + lazy = true, + config = function() + -- Simulate plugin code that requires itself during setup + local module_loader = require('zpack.module_loader') + -- This should return nil and not cause an error + local result = module_loader.loader('self-require') + if result ~= nil then + no_error = false + end + config_called = true + end, + }, + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() - helpers.test("loader returns nil for plugins currently being loaded", function() - helpers.setup_test_env() + local module_loader = require('zpack.module_loader') + module_loader.loader('self-require') - require('zpack').setup({ - spec = { - { 'test/loading-plugin', cmd = 'TestCmd' }, + helpers.flush_pending() + assert.is_truthy(config_called, "Config should have been called") + assert.is_truthy(no_error, "Self-require should return nil without error") + end) + + it("nested loading does not cause circular dependency error", function() + local plugin_a_loaded = false + local plugin_b_loaded = false + local no_circular_error = true + + require('zpack').setup({ + spec = { + { + 'test/plugin-a', + lazy = true, + config = function() + -- A's config triggers loading B + local module_loader = require('zpack.module_loader') + module_loader.loader('plugin-b') + -- After B loads, A tries to require itself (common pattern) + local result = module_loader.loader('plugin-a') + if result ~= nil then + no_circular_error = false + end + plugin_a_loaded = true + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - -- Manually set the plugin as loading - local state = require('zpack.state') - local src = 'https://github.com/test/loading-plugin' - state.spec_registry[src].load_status = "loading" - - local module_loader = require('zpack.module_loader') - local result = module_loader.loader('loading-plugin') - helpers.assert_nil(result, "Loader should return nil for plugins being loaded") - - helpers.cleanup_test_env() - end) - - helpers.test("loader triggers plugin load for pending lazy plugin", function() - helpers.setup_test_env() - local loaded = false - - require('zpack').setup({ - spec = { - { - 'test/trigger-plugin', - lazy = true, - config = function() - loaded = true - end, - }, + { + 'test/plugin-b', + lazy = true, + config = function() + plugin_b_loaded = true + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local state = require('zpack.state') - local src = 'https://github.com/test/trigger-plugin' - helpers.assert_equal(state.spec_registry[src].load_status, "pending", "Should start as pending") - - local module_loader = require('zpack.module_loader') - module_loader.loader('trigger-plugin') - - helpers.flush_pending() - helpers.assert_true(loaded, "Config should have run") - helpers.assert_equal(state.spec_registry[src].load_status, "loaded", "Should be loaded now") - - helpers.cleanup_test_env() - end) - - helpers.test("self-require during loading does not cause circular dependency error", function() - helpers.setup_test_env() - local config_called = false - local no_error = true - - require('zpack').setup({ - spec = { - { - 'test/self-require', - lazy = true, - config = function() - -- Simulate plugin code that requires itself during setup - local module_loader = require('zpack.module_loader') - -- This should return nil and not cause an error - local result = module_loader.loader('self-require') - if result ~= nil then - no_error = false - end - config_called = true - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local module_loader = require('zpack.module_loader') + module_loader.loader('plugin-a') + + helpers.flush_pending() + assert.is_truthy(plugin_a_loaded, "Plugin A should be loaded") + assert.is_truthy(plugin_b_loaded, "Plugin B should be loaded") + assert.is_truthy(no_circular_error, "No circular dependency error should occur") + end) + + it("A requires B requires A chain is handled gracefully", function() + local plugin_a_config_count = 0 + local plugin_b_config_count = 0 + local notifications = {} + + -- Capture notifications to check for circular dependency errors + local original_notify = vim.notify + vim.notify = function(msg, level) + table.insert(notifications, { msg = msg, level = level }) + end + + require('zpack').setup({ + spec = { + { + 'test/chain-a', + lazy = true, + config = function() + plugin_a_config_count = plugin_a_config_count + 1 + -- A's config requires B + local module_loader = require('zpack.module_loader') + module_loader.loader('chain-b') + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local module_loader = require('zpack.module_loader') - module_loader.loader('self-require') - - helpers.flush_pending() - helpers.assert_true(config_called, "Config should have been called") - helpers.assert_true(no_error, "Self-require should return nil without error") - - helpers.cleanup_test_env() - end) - - helpers.test("nested loading does not cause circular dependency error", function() - helpers.setup_test_env() - local plugin_a_loaded = false - local plugin_b_loaded = false - local no_circular_error = true - - require('zpack').setup({ - spec = { - { - 'test/plugin-a', - lazy = true, - config = function() - -- A's config triggers loading B - local module_loader = require('zpack.module_loader') - module_loader.loader('plugin-b') - -- After B loads, A tries to require itself (common pattern) - local result = module_loader.loader('plugin-a') - if result ~= nil then - no_circular_error = false - end - plugin_a_loaded = true - end, - }, - { - 'test/plugin-b', - lazy = true, - config = function() - plugin_b_loaded = true - end, - }, + { + 'test/chain-b', + lazy = true, + config = function() + plugin_b_config_count = plugin_b_config_count + 1 + -- B's config requires A back (should be no-op since A is loading) + local module_loader = require('zpack.module_loader') + module_loader.loader('chain-a') + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - local module_loader = require('zpack.module_loader') - module_loader.loader('plugin-a') + local module_loader = require('zpack.module_loader') + module_loader.loader('chain-a') - helpers.flush_pending() - helpers.assert_true(plugin_a_loaded, "Plugin A should be loaded") - helpers.assert_true(plugin_b_loaded, "Plugin B should be loaded") - helpers.assert_true(no_circular_error, "No circular dependency error should occur") + helpers.flush_pending() + vim.notify = original_notify - helpers.cleanup_test_env() - end) + assert.are.equal(1, plugin_a_config_count) + assert.are.equal(1, plugin_b_config_count) - helpers.test("A requires B requires A chain is handled gracefully", function() - helpers.setup_test_env() - local plugin_a_config_count = 0 - local plugin_b_config_count = 0 - local notifications = {} - - -- Capture notifications to check for circular dependency errors - local original_notify = vim.notify - vim.notify = function(msg, level) - table.insert(notifications, { msg = msg, level = level }) + -- Check no circular dependency error was reported + local has_circular_error = false + for _, n in ipairs(notifications) do + if n.msg and n.msg:find("Circular dependency") then + has_circular_error = true + break end + end + assert.is_falsy(has_circular_error, "Should not report circular dependency error") + end) - require('zpack').setup({ - spec = { - { - 'test/chain-a', - lazy = true, - config = function() - plugin_a_config_count = plugin_a_config_count + 1 - -- A's config requires B - local module_loader = require('zpack.module_loader') - module_loader.loader('chain-b') - end, - }, - { - 'test/chain-b', - lazy = true, - config = function() - plugin_b_config_count = plugin_b_config_count + 1 - -- B's config requires A back (should be no-op since A is loading) - local module_loader = require('zpack.module_loader') - module_loader.loader('chain-a') - end, - }, + it("explicit main is indexed in addition to plugin name", function() + local loaded = false + + require('zpack').setup({ + spec = { + { + 'test/weird-name', + lazy = true, + main = 'actual_module', + config = function() + loaded = true + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - local module_loader = require('zpack.module_loader') - module_loader.loader('chain-a') + local module_loader = require('zpack.module_loader') + -- Should be able to load via the main module name + module_loader.loader('actual_module') - helpers.flush_pending() - vim.notify = original_notify - - helpers.assert_equal(plugin_a_config_count, 1, "Plugin A config should run exactly once") - helpers.assert_equal(plugin_b_config_count, 1, "Plugin B config should run exactly once") + helpers.flush_pending() + assert.is_truthy(loaded, "Should load via explicit main module name") + end) - -- Check no circular dependency error was reported - local has_circular_error = false - for _, n in ipairs(notifications) do - if n.msg and n.msg:find("Circular dependency") then - has_circular_error = true - break - end - end - helpers.assert_false(has_circular_error, "Should not report circular dependency error") - - helpers.cleanup_test_env() - end) - - helpers.test("explicit main is indexed in addition to plugin name", function() - helpers.setup_test_env() - local loaded = false - - require('zpack').setup({ - spec = { - { - 'test/weird-name', - lazy = true, - main = 'actual_module', - config = function() - loaded = true - end, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local module_loader = require('zpack.module_loader') - -- Should be able to load via the main module name - module_loader.loader('actual_module') - - helpers.flush_pending() - helpers.assert_true(loaded, "Should load via explicit main module name") - - helpers.cleanup_test_env() - end) - - helpers.test("submodule require triggers parent plugin load", function() - helpers.setup_test_env() - local loaded = false - - require('zpack').setup({ - spec = { - { - 'test/parent-plugin', - lazy = true, - config = function() - loaded = true - end, - }, + it("submodule require triggers parent plugin load", function() + local loaded = false + + require('zpack').setup({ + spec = { + { + 'test/parent-plugin', + lazy = true, + config = function() + loaded = true + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - local module_loader = require('zpack.module_loader') - -- Requiring a submodule should trigger the parent plugin load - module_loader.loader('parent-plugin.utils.helpers') + local module_loader = require('zpack.module_loader') + -- Requiring a submodule should trigger the parent plugin load + module_loader.loader('parent-plugin.utils.helpers') - helpers.flush_pending() - helpers.assert_true(loaded, "Submodule require should trigger parent plugin load") - - helpers.cleanup_test_env() - end) + helpers.flush_pending() + assert.is_truthy(loaded, "Submodule require should trigger parent plugin load") + end) - helpers.test("module loader is installed at position 3 (after vim.loader)", function() - helpers.setup_test_env() + it("module loader is installed at position 3 (after vim.loader)", function() + require('zpack').setup({ + spec = { + { 'test/some-plugin', lazy = true }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local module_loader = require('zpack.module_loader') + local found_at_pos_3 = false + if package.loaders[3] == module_loader.loader then + found_at_pos_3 = true + end + assert.is_truthy(found_at_pos_3, "Module loader should be at position 3") + end) - require('zpack').setup({ - spec = { - { 'test/some-plugin', lazy = true }, + it("plugin with dots in name (mini.*) is matched correctly", function() + local loaded = false + + require('zpack').setup({ + spec = { + { + 'nvim-mini/mini.extra', + lazy = true, + config = function() + loaded = true + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - local module_loader = require('zpack.module_loader') - local found_at_pos_3 = false - if package.loaders[3] == module_loader.loader then - found_at_pos_3 = true - end - helpers.assert_true(found_at_pos_3, "Module loader should be at position 3") - - helpers.cleanup_test_env() - end) - - helpers.test("plugin with dots in name (mini.*) is matched correctly", function() - helpers.setup_test_env() - local loaded = false - - require('zpack').setup({ - spec = { - { - 'nvim-mini/mini.extra', - lazy = true, - config = function() - loaded = true - end, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local module_loader = require('zpack.module_loader') - -- require("mini.extra") should match plugin "mini.extra" - module_loader.loader('mini.extra') - - helpers.flush_pending() - helpers.assert_true(loaded, "mini.extra should load when require('mini.extra') is called") - - helpers.cleanup_test_env() - end) - - helpers.test("deeply nested module require matches plugin with dots", function() - helpers.setup_test_env() - local loaded = false - - require('zpack').setup({ - spec = { - { - 'nvim-mini/mini.extra', - lazy = true, - config = function() - loaded = true - end, - }, + local module_loader = require('zpack.module_loader') + -- require("mini.extra") should match plugin "mini.extra" + module_loader.loader('mini.extra') + + helpers.flush_pending() + assert.is_truthy(loaded, "mini.extra should load when require('mini.extra') is called") + end) + + it("deeply nested module require matches plugin with dots", function() + local loaded = false + + require('zpack').setup({ + spec = { + { + 'nvim-mini/mini.extra', + lazy = true, + config = function() + loaded = true + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local module_loader = require('zpack.module_loader') - -- require("mini.extra.ai_spec") should still match plugin "mini.extra" - module_loader.loader('mini.extra.ai_spec') - - helpers.flush_pending() - helpers.assert_true(loaded, "mini.extra should load when require('mini.extra.ai_spec') is called") - - helpers.cleanup_test_env() - end) - - helpers.test("filesystem scanning finds module when name doesn't match plugin", function() - helpers.setup_test_env() - local loaded = false - - local plugin_path, base_path = helpers.create_mock_plugin_dir('nvim-web-devicons', { 'nvim-web-devicons' }) - - local original_vim_pack_add = vim.pack.add - vim.pack.add = function(specs, opts) - opts = opts or {} - for _, pack_spec in ipairs(specs) do - local name = pack_spec.name or pack_spec.src:match('[^/]+$') - pack_spec.name = name - _G.test_state.registered_pack_specs[name] = pack_spec - local mock_plugin = { - spec = pack_spec, - path = plugin_path, - name = name, - } - if opts.load then - opts.load(mock_plugin) - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local module_loader = require('zpack.module_loader') + -- require("mini.extra.ai_spec") should still match plugin "mini.extra" + module_loader.loader('mini.extra.ai_spec') + + helpers.flush_pending() + assert.is_truthy(loaded, "mini.extra should load when require('mini.extra.ai_spec') is called") + end) + + it("filesystem scanning finds module when name doesn't match plugin", function() + local loaded = false + + local plugin_path, base_path = helpers.create_mock_plugin_dir('nvim-web-devicons', { 'nvim-web-devicons' }) + + local original_vim_pack_add = vim.pack.add + vim.pack.add = function(specs, opts) + opts = opts or {} + for _, pack_spec in ipairs(specs) do + local name = pack_spec.name or pack_spec.src:match('[^/]+$') + pack_spec.name = name + _G.test_state.registered_pack_specs[name] = pack_spec + local mock_plugin = { + spec = pack_spec, + path = plugin_path, + name = name, + } + if opts.load then + opts.load(mock_plugin) end end - - require('zpack').setup({ - spec = { - { - 'test/nvim-web-devicons', - lazy = true, - config = function() - loaded = true - end, - }, + end + + require('zpack').setup({ + spec = { + { + 'test/nvim-web-devicons', + lazy = true, + config = function() + loaded = true + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local module_loader = require('zpack.module_loader') - module_loader.loader('nvim-web-devicons') - - helpers.flush_pending() - helpers.assert_true(loaded, "Plugin should load via filesystem scanning when module name matches actual file") - - vim.pack.add = original_vim_pack_add - helpers.cleanup_mock_plugin_dir(base_path) - helpers.cleanup_test_env() - end) - - helpers.test("filesystem scanning finds module with different name via normalization", function() - helpers.setup_test_env() - local loaded = false - - local plugin_path, base_path = helpers.create_mock_plugin_dir('some-odd-plugin', { 'odd_module' }) - - local original_vim_pack_add = vim.pack.add - vim.pack.add = function(specs, opts) - opts = opts or {} - for _, pack_spec in ipairs(specs) do - local name = pack_spec.name or pack_spec.src:match('[^/]+$') - pack_spec.name = name - _G.test_state.registered_pack_specs[name] = pack_spec - local mock_plugin = { - spec = pack_spec, - path = plugin_path, - name = name, - } - if opts.load then - opts.load(mock_plugin) - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local module_loader = require('zpack.module_loader') + module_loader.loader('nvim-web-devicons') + + helpers.flush_pending() + assert.is_truthy(loaded, "Plugin should load via filesystem scanning when module name matches actual file") + + vim.pack.add = original_vim_pack_add + helpers.cleanup_mock_plugin_dir(base_path) + end) + + it("filesystem scanning finds module with different name via normalization", function() + local loaded = false + + local plugin_path, base_path = helpers.create_mock_plugin_dir('some-odd-plugin', { 'odd_module' }) + + local original_vim_pack_add = vim.pack.add + vim.pack.add = function(specs, opts) + opts = opts or {} + for _, pack_spec in ipairs(specs) do + local name = pack_spec.name or pack_spec.src:match('[^/]+$') + pack_spec.name = name + _G.test_state.registered_pack_specs[name] = pack_spec + local mock_plugin = { + spec = pack_spec, + path = plugin_path, + name = name, + } + if opts.load then + opts.load(mock_plugin) end end - - require('zpack').setup({ - spec = { - { - 'test/some-odd-plugin', - lazy = true, - config = function() - loaded = true - end, - }, + end + + require('zpack').setup({ + spec = { + { + 'test/some-odd-plugin', + lazy = true, + config = function() + loaded = true + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local module_loader = require('zpack.module_loader') - module_loader.loader('odd_module') - - helpers.flush_pending() - helpers.assert_true(loaded, "Plugin should load via filesystem scanning with normalized module name matching") - - vim.pack.add = original_vim_pack_add - helpers.cleanup_mock_plugin_dir(base_path) - helpers.cleanup_test_env() - end) - - helpers.test("filesystem scanning caches results", function() - helpers.setup_test_env() - local load_count = 0 - - local plugin_path, base_path = helpers.create_mock_plugin_dir('cached-plugin', { 'cached_mod' }) - - local original_vim_pack_add = vim.pack.add - vim.pack.add = function(specs, opts) - opts = opts or {} - for _, pack_spec in ipairs(specs) do - local name = pack_spec.name or pack_spec.src:match('[^/]+$') - pack_spec.name = name - _G.test_state.registered_pack_specs[name] = pack_spec - local mock_plugin = { - spec = pack_spec, - path = plugin_path, - name = name, - } - if opts.load then - opts.load(mock_plugin) - end + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local module_loader = require('zpack.module_loader') + module_loader.loader('odd_module') + + helpers.flush_pending() + assert.is_truthy(loaded, "Plugin should load via filesystem scanning with normalized module name matching") + + vim.pack.add = original_vim_pack_add + helpers.cleanup_mock_plugin_dir(base_path) + end) + + it("filesystem scanning caches results", function() + local load_count = 0 + + local plugin_path, base_path = helpers.create_mock_plugin_dir('cached-plugin', { 'cached_mod' }) + + local original_vim_pack_add = vim.pack.add + vim.pack.add = function(specs, opts) + opts = opts or {} + for _, pack_spec in ipairs(specs) do + local name = pack_spec.name or pack_spec.src:match('[^/]+$') + pack_spec.name = name + _G.test_state.registered_pack_specs[name] = pack_spec + local mock_plugin = { + spec = pack_spec, + path = plugin_path, + name = name, + } + if opts.load then + opts.load(mock_plugin) end end - - require('zpack').setup({ - spec = { - { - 'test/cached-plugin', - lazy = true, - config = function() - load_count = load_count + 1 - end, - }, + end + + require('zpack').setup({ + spec = { + { + 'test/cached-plugin', + lazy = true, + config = function() + load_count = load_count + 1 + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - local module_loader = require('zpack.module_loader') - module_loader.loader('cached_mod') - helpers.flush_pending() - module_loader.loader('cached_mod') - helpers.flush_pending() + local module_loader = require('zpack.module_loader') + module_loader.loader('cached_mod') + helpers.flush_pending() + module_loader.loader('cached_mod') + helpers.flush_pending() - helpers.assert_equal(load_count, 1, "Plugin should only load once, cache should prevent re-scanning") + assert.are.equal(1, load_count) - vim.pack.add = original_vim_pack_add - helpers.cleanup_mock_plugin_dir(base_path) - helpers.cleanup_test_env() - end) + vim.pack.add = original_vim_pack_add + helpers.cleanup_mock_plugin_dir(base_path) end) -end +end) diff --git a/tests/opts_test.lua b/tests/opts_test.lua index 43b63c5..ebc1783 100644 --- a/tests/opts_test.lua +++ b/tests/opts_test.lua @@ -1,465 +1,408 @@ local helpers = require('helpers') -return function() - helpers.describe("Plugin opts and auto-setup", function() - helpers.test("opts table is recorded on the registry entry", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - opts = { enabled = true, theme = 'dark' }, - }, +describe("Plugin opts and auto-setup", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("opts table is recorded on the registry entry", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + opts = { enabled = true, theme = 'dark' }, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - local entry = state.spec_registry[src] - helpers.assert_true(entry.has_opts, "has_opts should be true") - -- opts is intentionally not stored on merged_spec; the raw value lives - -- on sorted_specs and is resolved at load time via resolve_opts. - helpers.assert_nil(entry.merged_spec.opts, "merged_spec.opts must always be nil") - helpers.assert_equal(entry.sorted_specs[1].opts.enabled, true) - helpers.assert_equal(entry.sorted_specs[1].opts.theme, 'dark') - - helpers.cleanup_test_env() - end) - - helpers.test("opts function is recorded on the sorted spec", function() - helpers.setup_test_env() - local opts_fn = function() return { test = true } end - - require('zpack').setup({ - spec = { - { - 'test/plugin', - opts = opts_fn, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - local entry = state.spec_registry[src] - helpers.assert_true(entry.has_opts, "has_opts should be true for function-form opts") - helpers.assert_nil(entry.merged_spec.opts, "merged_spec.opts must always be nil") - helpers.assert_equal(type(entry.sorted_specs[1].opts), 'function') - - helpers.cleanup_test_env() - end) - - helpers.test("main field is stored in spec", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - main = 'custom.module', - opts = {}, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - local spec = state.spec_registry[src].merged_spec - helpers.assert_equal(spec.main, 'custom.module') - - helpers.cleanup_test_env() - end) - - helpers.test("config = true is stored in spec", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - config = true, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - local spec = state.spec_registry[src].merged_spec - helpers.assert_equal(spec.config, true) - - helpers.cleanup_test_env() - end) - - helpers.test("config function receives opts as second argument", function() - helpers.setup_test_env() - local received_opts = nil - - require('zpack').setup({ - spec = { - { - 'test/plugin', - opts = { foo = 'bar', num = 42 }, - config = function(plugin, opts) - received_opts = opts - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' + local entry = state.spec_registry[src] + assert.is_truthy(entry.has_opts, "has_opts should be true") + -- opts is intentionally not stored on merged_spec; the raw value lives + -- on sorted_specs and is resolved at load time via resolve_opts. + assert.is_nil(entry.merged_spec.opts, "merged_spec.opts must always be nil") + assert.are.equal(true, entry.sorted_specs[1].opts.enabled) + assert.are.equal('dark', entry.sorted_specs[1].opts.theme) + end) + + it("opts function is recorded on the sorted spec", function() + local opts_fn = function() return { test = true } end + + require('zpack').setup({ + spec = { + { + 'test/plugin', + opts = opts_fn, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_not_nil(received_opts, "config should receive opts") - helpers.assert_equal(received_opts.foo, 'bar') - helpers.assert_equal(received_opts.num, 42) - - helpers.cleanup_test_env() - end) - - helpers.test("config function receives resolved opts from function", function() - helpers.setup_test_env() - local received_opts = nil - - require('zpack').setup({ - spec = { - { - 'test/plugin', - opts = function(plugin) - return { from_fn = true, path = plugin.path } - end, - config = function(plugin, opts) - received_opts = opts - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' + local entry = state.spec_registry[src] + assert.is_truthy(entry.has_opts, "has_opts should be true for function-form opts") + assert.is_nil(entry.merged_spec.opts, "merged_spec.opts must always be nil") + assert.are.equal('function', type(entry.sorted_specs[1].opts)) + end) + + it("main field is stored in spec", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + main = 'custom.module', + opts = {}, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_not_nil(received_opts, "config should receive resolved opts") - helpers.assert_equal(received_opts.from_fn, true) - helpers.assert_not_nil(received_opts.path, "opts function should receive plugin data") - - helpers.cleanup_test_env() - end) - - helpers.test("config receives empty table when no opts specified", function() - helpers.setup_test_env() - local received_opts = nil - - require('zpack').setup({ - spec = { - { - 'test/plugin', - config = function(plugin, opts) - received_opts = opts - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' + local spec = state.spec_registry[src].merged_spec + assert.are.equal('custom.module', spec.main) + end) + + it("config = true is stored in spec", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + config = true, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_not_nil(received_opts, "config should receive opts table") - helpers.assert_equal(type(received_opts), 'table') - - helpers.cleanup_test_env() - end) - - helpers.test("lazy plugin with opts triggers config on load", function() - helpers.setup_test_env() - local config_ran = false - local received_opts = nil - - require('zpack').setup({ - spec = { - { - 'test/plugin', - cmd = 'TestCommand', - opts = { lazy_opt = true }, - config = function(plugin, opts) - config_ran = true - received_opts = opts - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local src = 'https://github.com/test/plugin' + local spec = state.spec_registry[src].merged_spec + assert.are.equal(true, spec.config) + end) + + it("config function receives opts as second argument", function() + local received_opts = nil + + require('zpack').setup({ + spec = { + { + 'test/plugin', + opts = { foo = 'bar', num = 42 }, + config = function(plugin, opts) + received_opts = opts + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_false(config_ran, "config should not run at setup for lazy plugin") - - pcall(vim.cmd, 'TestCommand') - helpers.flush_pending() - - helpers.assert_true(config_ran, "config should run when lazy plugin loads") - helpers.assert_equal(received_opts.lazy_opt, true) - - helpers.cleanup_test_env() - end) - - helpers.test("custom config function replaces auto-setup", function() - helpers.setup_test_env() - local config_call_count = 0 - - require('zpack').setup({ - spec = { - { - 'test/plugin', - opts = { some = 'opts' }, - config = function(plugin, opts) - config_call_count = config_call_count + 1 - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_not_nil(received_opts, "config should receive opts") + assert.are.equal('bar', received_opts.foo) + assert.are.equal(42, received_opts.num) + end) + + it("config function receives resolved opts from function", function() + local received_opts = nil + + require('zpack').setup({ + spec = { + { + 'test/plugin', + opts = function(plugin) + return { from_fn = true, path = plugin.path } + end, + config = function(plugin, opts) + received_opts = opts + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_not_nil(received_opts, "config should receive resolved opts") + assert.are.equal(true, received_opts.from_fn) + assert.is_not_nil(received_opts.path, "opts function should receive plugin data") + end) - helpers.flush_pending() - helpers.assert_equal(config_call_count, 1, "config should be called exactly once") + it("config receives empty table when no opts specified", function() + local received_opts = nil - helpers.cleanup_test_env() - end) + require('zpack').setup({ + spec = { + { + 'test/plugin', + config = function(plugin, opts) + received_opts = opts + end, + }, + }, + defaults = { confirm = false }, + }) - helpers.test("opts without config does not call config function", function() - helpers.setup_test_env() - local config_ran = false + helpers.flush_pending() + assert.is_not_nil(received_opts, "config should receive opts table") + assert.are.equal('table', type(received_opts)) + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - opts = { enabled = true }, - }, + it("lazy plugin with opts triggers config on load", function() + local config_ran = false + local received_opts = nil + + require('zpack').setup({ + spec = { + { + 'test/plugin', + cmd = 'TestCommand', + opts = { lazy_opt = true }, + config = function(plugin, opts) + config_ran = true + received_opts = opts + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - helpers.assert_false(config_ran, "no config function should be called") + helpers.flush_pending() + assert.is_falsy(config_ran, "config should not run at setup for lazy plugin") - helpers.cleanup_test_env() - end) + pcall(vim.cmd, 'TestCommand') + helpers.flush_pending() + + assert.is_truthy(config_ran, "config should run when lazy plugin loads") + assert.are.equal(true, received_opts.lazy_opt) end) - helpers.describe("resolve_main caching", function() - helpers.test("resolve_main result is cached in plugin.main", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - main = 'custom.module', - opts = {}, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local utils = require('zpack.utils') - local src = 'https://github.com/test/plugin' - local entry = state.spec_registry[src] - - local spec = entry.merged_spec - utils.resolve_main(entry.plugin, spec) - helpers.assert_equal(entry.plugin.main, 'custom.module') - - helpers.cleanup_test_env() - end) - - helpers.test("resolve_main uses cached value on subsequent calls", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - main = 'cached.module', - opts = {}, - }, + it("custom config function replaces auto-setup", function() + local config_call_count = 0 + + require('zpack').setup({ + spec = { + { + 'test/plugin', + opts = { some = 'opts' }, + config = function(plugin, opts) + config_call_count = config_call_count + 1 + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - local state = require('zpack.state') - local utils = require('zpack.utils') - local src = 'https://github.com/test/plugin' - local entry = state.spec_registry[src] + helpers.flush_pending() + assert.are.equal(1, config_call_count) + end) - local spec = entry.merged_spec - local first_result = utils.resolve_main(entry.plugin, spec) - spec.main = 'changed.module' - local second_result = utils.resolve_main(entry.plugin, spec) + it("opts without config does not call config function", function() + local config_ran = false - helpers.assert_equal(first_result, 'cached.module') - helpers.assert_equal(second_result, 'cached.module', "should use cached value") + require('zpack').setup({ + spec = { + { + 'test/plugin', + opts = { enabled = true }, + }, + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() + assert.is_falsy(config_ran, "no config function should be called") + end) +end) + +describe("resolve_main caching", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("resolve_main result is cached in plugin.main", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + main = 'custom.module', + opts = {}, + }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local utils = require('zpack.utils') + local src = 'https://github.com/test/plugin' + local entry = state.spec_registry[src] + + local spec = entry.merged_spec + utils.resolve_main(entry.plugin, spec) + assert.are.equal('custom.module', entry.plugin.main) end) - helpers.describe("mini.* plugin handling", function() - helpers.test("mini.* plugin uses plugin name as main module", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'nvim-mini/mini.surround', - opts = {}, - }, + it("resolve_main uses cached value on subsequent calls", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + main = 'cached.module', + opts = {}, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local utils = require('zpack.utils') - local src = 'https://github.com/nvim-mini/mini.surround' - local entry = state.spec_registry[src] - - local spec = entry.merged_spec - local main = utils.resolve_main(entry.plugin, spec) - helpers.assert_equal(main, 'mini.surround') - - helpers.cleanup_test_env() - end) - - helpers.test("mini.nvim does not use special handling", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'nvim-mini/mini.nvim', - opts = {}, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local utils = require('zpack.utils') + local src = 'https://github.com/test/plugin' + local entry = state.spec_registry[src] + + local spec = entry.merged_spec + local first_result = utils.resolve_main(entry.plugin, spec) + spec.main = 'changed.module' + local second_result = utils.resolve_main(entry.plugin, spec) + + assert.are.equal('cached.module', first_result) + assert.are.equal('cached.module', second_result) + end) +end) + +describe("mini.* plugin handling", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("mini.* plugin uses plugin name as main module", function() + require('zpack').setup({ + spec = { + { + 'nvim-mini/mini.surround', + opts = {}, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local state = require('zpack.state') - local utils = require('zpack.utils') - local src = 'https://github.com/nvim-mini/mini.nvim' - local entry = state.spec_registry[src] - - local spec = entry.merged_spec - local main = utils.resolve_main(entry.plugin, spec) - helpers.assert_nil(main, "mini.nvim should not match special case") - - helpers.cleanup_test_env() - end) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local utils = require('zpack.utils') + local src = 'https://github.com/nvim-mini/mini.surround' + local entry = state.spec_registry[src] + + local spec = entry.merged_spec + local main = utils.resolve_main(entry.plugin, spec) + assert.are.equal('mini.surround', main) end) - helpers.describe("plugin.main in config hooks", function() - helpers.test("plugin.main is available in config function", function() - helpers.setup_test_env() - local received_main = nil - - require('zpack').setup({ - spec = { - { - 'test/plugin', - main = 'test.module', - config = function(plugin, opts) - received_main = plugin.main - end, - }, + it("mini.nvim does not use special handling", function() + require('zpack').setup({ + spec = { + { + 'nvim-mini/mini.nvim', + opts = {}, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_equal(received_main, 'test.module') - - helpers.cleanup_test_env() - end) - - helpers.test("plugin.main is nil when not detected", function() - helpers.setup_test_env() - local received_main = "not_set" - - require('zpack').setup({ - spec = { - { - 'test/plugin', - config = function(plugin, opts) - received_main = plugin.main - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local state = require('zpack.state') + local utils = require('zpack.utils') + local src = 'https://github.com/nvim-mini/mini.nvim' + local entry = state.spec_registry[src] + + local spec = entry.merged_spec + local main = utils.resolve_main(entry.plugin, spec) + assert.is_nil(main, "mini.nvim should not match special case") + end) +end) + +describe("plugin.main in config hooks", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("plugin.main is available in config function", function() + local received_main = nil + + require('zpack').setup({ + spec = { + { + 'test/plugin', + main = 'test.module', + config = function(plugin, opts) + received_main = plugin.main + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_nil(received_main) + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() + assert.are.equal('test.module', received_main) end) - helpers.describe("normalize_name utility", function() - helpers.test("removes nvim- prefix", function() - helpers.setup_test_env() - local utils = require('zpack.utils') - - helpers.assert_equal(utils.normalize_name('nvim-treesitter'), 'treesitter') - helpers.assert_equal(utils.normalize_name('vim-surround'), 'surround') + it("plugin.main is nil when not detected", function() + local received_main = "not_set" - helpers.cleanup_test_env() - end) + require('zpack').setup({ + spec = { + { + 'test/plugin', + config = function(plugin, opts) + received_main = plugin.main + end, + }, + }, + defaults = { confirm = false }, + }) - helpers.test("removes .nvim suffix", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + helpers.flush_pending() + assert.is_nil(received_main) + end) +end) - helpers.assert_equal(utils.normalize_name('telescope.nvim'), 'telescope') - helpers.assert_equal(utils.normalize_name('plugin.vim'), 'plugin') +describe("normalize_name utility", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) - helpers.cleanup_test_env() - end) + it("removes nvim- prefix", function() + local utils = require('zpack.utils') - helpers.test("removes -lua and .lua suffixes", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + assert.are.equal('treesitter', utils.normalize_name('nvim-treesitter')) + assert.are.equal('surround', utils.normalize_name('vim-surround')) + end) - helpers.assert_equal(utils.normalize_name('plenary-lua'), 'plenary') - helpers.assert_equal(utils.normalize_name('plugin.lua'), 'plugin') + it("removes .nvim suffix", function() + local utils = require('zpack.utils') - helpers.cleanup_test_env() - end) + assert.are.equal('telescope', utils.normalize_name('telescope.nvim')) + assert.are.equal('plugin', utils.normalize_name('plugin.vim')) + end) - helpers.test("converts to lowercase and removes non-alpha", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + it("removes -lua and .lua suffixes", function() + local utils = require('zpack.utils') - helpers.assert_equal(utils.normalize_name('My-Plugin_123'), 'myplugin') - helpers.assert_equal(utils.normalize_name('CAPS'), 'caps') + assert.are.equal('plenary', utils.normalize_name('plenary-lua')) + assert.are.equal('plugin', utils.normalize_name('plugin.lua')) + end) - helpers.cleanup_test_env() - end) + it("converts to lowercase and removes non-alpha", function() + local utils = require('zpack.utils') - helpers.test("handles complex names", function() - helpers.setup_test_env() - local utils = require('zpack.utils') + assert.are.equal('myplugin', utils.normalize_name('My-Plugin_123')) + assert.are.equal('caps', utils.normalize_name('CAPS')) + end) - helpers.assert_equal(utils.normalize_name('nvim-lspconfig'), 'lspconfig') - helpers.assert_equal(utils.normalize_name('telescope-fzf-native.nvim'), 'telescopefzfnative') + it("handles complex names", function() + local utils = require('zpack.utils') - helpers.cleanup_test_env() - end) + assert.are.equal('lspconfig', utils.normalize_name('nvim-lspconfig')) + assert.are.equal('telescopefzfnative', utils.normalize_name('telescope-fzf-native.nvim')) end) -end +end) diff --git a/tests/pack_update_test_helpers.lua b/tests/pack_update_test_helpers.lua index a54e75c..2d2bb52 100644 --- a/tests/pack_update_test_helpers.lua +++ b/tests/pack_update_test_helpers.lua @@ -1,17 +1,160 @@ local helpers = require('helpers') +local assert = require('luassert') +-- Parameterised test bodies shared by zupdate_test.lua and zrestore_test.lua. +-- This module is require'd, not discovered as a busted spec (its filename is +-- deliberately not *_test.lua), so it pulls in luassert explicitly rather than +-- relying on the `assert` global busted injects only into spec files. local M = {} -function M.create_tests(config) +---Build the shared :ZPack update / :ZPack restore cases for a command. +---@param config table { command, expected_opts, error_prefix, supports_bang } +---@return table[] list of { name = string, fn = function } busted cases +function M.cases(config) local command = config.command local expected_opts = config.expected_opts local error_prefix = config.error_prefix - return function() - helpers.describe(command, function() - helpers.test(command .. " without args calls vim.pack.update correctly", function() - helpers.setup_test_env() - + local cases = {} + + cases[#cases + 1] = { + name = command .. " without args calls vim.pack.update correctly", + fn = function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + vim.cmd(command) + helpers.flush_pending() + + assert.are.equal(1, #_G.test_state.vim_pack_update_calls) + local call = _G.test_state.vim_pack_update_calls[1] + assert.is_nil(call.names, "names should be nil for all") + if expected_opts then + assert.is_not_nil(call.opts, "opts should be passed") + for k, v in pairs(expected_opts) do + assert.are.equal(v, call.opts[k]) + end + else + assert.is_nil(call.opts, "opts should be nil") + end + end, + } + + cases[#cases + 1] = { + name = command .. " with plugin name targets that plugin", + fn = function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + vim.cmd(command .. ' plugin-a') + helpers.flush_pending() + + assert.are.equal(1, #_G.test_state.vim_pack_update_calls) + local call = _G.test_state.vim_pack_update_calls[1] + assert.is_not_nil(call.names, "names should be provided") + assert.contains(call.names, 'plugin-a') + if expected_opts then + for k, v in pairs(expected_opts) do + assert.are.equal(v, call.opts[k]) + end + else + assert.is_nil(call.opts, "opts should be nil") + end + end, + } + + cases[#cases + 1] = { + name = command .. " with registered but not installed plugin still calls vim.pack.update", + fn = function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + _G.test_state.registered_pack_specs['plugin-a'] = nil + + vim.cmd(command .. ' plugin-a') + helpers.flush_pending() + + assert.are.equal(1, #_G.test_state.vim_pack_update_calls) + local call = _G.test_state.vim_pack_update_calls[1] + assert.is_not_nil(call.names, "names should be provided") + assert.contains(call.names, 'plugin-a') + end, + } + + cases[#cases + 1] = { + name = command .. " with plugin not in spec shows error", + fn = function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + vim.cmd(command .. ' non-existent-plugin') + helpers.flush_pending() + + assert.are.equal(0, #_G.test_state.vim_pack_update_calls) + + local found_error = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find('not found in spec') and notif.level == vim.log.levels.ERROR then + found_error = true + break + end + end + assert.is_truthy(found_error, "should notify error for plugin not in spec") + end, + } + + cases[#cases + 1] = { + name = command .. " tab completion returns registered plugin names", + fn = function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local completions = vim.fn.getcompletion(command .. ' ', 'cmdline') + assert.contains(completions, 'plugin-a') + assert.contains(completions, 'plugin-b') + end, + } + + if config.supports_bang then + local bang_command = command:gsub('^(%S+)', '%1!') + + cases[#cases + 1] = { + name = bang_command .. " passes force=true to vim.pack.update", + fn = function() require('zpack').setup({ spec = { { 'test/plugin-a' }, @@ -22,27 +165,25 @@ function M.create_tests(config) helpers.flush_pending() - vim.cmd(command) + vim.cmd(bang_command) helpers.flush_pending() - helpers.assert_equal(#_G.test_state.vim_pack_update_calls, 1, "vim.pack.update should be called once") + assert.are.equal(1, #_G.test_state.vim_pack_update_calls) local call = _G.test_state.vim_pack_update_calls[1] - helpers.assert_nil(call.names, "names should be nil for all") + assert.is_nil(call.names, "names should be nil for all") + assert.is_not_nil(call.opts, "opts should be passed with bang") + assert.are.equal(true, call.opts.force) if expected_opts then - helpers.assert_not_nil(call.opts, "opts should be passed") for k, v in pairs(expected_opts) do - helpers.assert_equal(call.opts[k], v, ("opts.%s should be '%s'"):format(k, v)) + assert.are.equal(v, call.opts[k]) end - else - helpers.assert_nil(call.opts, "opts should be nil") end + end, + } - helpers.cleanup_test_env() - end) - - helpers.test(command .. " with plugin name targets that plugin", function() - helpers.setup_test_env() - + cases[#cases + 1] = { + name = bang_command .. " with plugin name passes force=true and targets that plugin", + fn = function() require('zpack').setup({ spec = { { 'test/plugin-a' }, @@ -53,203 +194,27 @@ function M.create_tests(config) helpers.flush_pending() - vim.cmd(command .. ' plugin-a') + vim.cmd(bang_command .. ' plugin-a') helpers.flush_pending() - helpers.assert_equal(#_G.test_state.vim_pack_update_calls, 1, "vim.pack.update should be called once") + assert.are.equal(1, #_G.test_state.vim_pack_update_calls) local call = _G.test_state.vim_pack_update_calls[1] - helpers.assert_not_nil(call.names, "names should be provided") - helpers.assert_table_contains(call.names, 'plugin-a', "plugin-a should be in names list") + assert.contains(call.names, 'plugin-a') + assert.is_not_nil(call.opts, "opts should be passed with bang") + assert.are.equal(true, call.opts.force) if expected_opts then for k, v in pairs(expected_opts) do - helpers.assert_equal(call.opts[k], v, ("opts.%s should be '%s'"):format(k, v)) - end - else - helpers.assert_nil(call.opts, "opts should be nil") - end - - helpers.cleanup_test_env() - end) - - helpers.test(command .. " with registered but not installed plugin still calls vim.pack.update", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - _G.test_state.registered_pack_specs['plugin-a'] = nil - - vim.cmd(command .. ' plugin-a') - helpers.flush_pending() - - helpers.assert_equal(#_G.test_state.vim_pack_update_calls, 1, "vim.pack.update should be called once") - local call = _G.test_state.vim_pack_update_calls[1] - helpers.assert_not_nil(call.names, "names should be provided") - helpers.assert_table_contains(call.names, 'plugin-a', "plugin-a should be in names list") - - helpers.cleanup_test_env() - end) - - helpers.test(command .. " with plugin not in spec shows error", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - vim.cmd(command .. ' non-existent-plugin') - helpers.flush_pending() - - helpers.assert_equal(#_G.test_state.vim_pack_update_calls, 0, "vim.pack.update should not be called") - - local found_error = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find('not found in spec') and notif.level == vim.log.levels.ERROR then - found_error = true - break + assert.are.equal(v, call.opts[k]) end end - helpers.assert_true(found_error, "should notify error for plugin not in spec") - - helpers.cleanup_test_env() - end) - - helpers.test(command .. " tab completion returns registered plugin names", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local completions = vim.fn.getcompletion(command .. ' ', 'cmdline') - helpers.assert_table_contains(completions, 'plugin-a', "should complete plugin-a") - helpers.assert_table_contains(completions, 'plugin-b', "should complete plugin-b") - - helpers.cleanup_test_env() - end) - - if config.supports_bang then - local bang_command = command:gsub('^(%S+)', '%1!') - - helpers.test(bang_command .. " passes force=true to vim.pack.update", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - vim.cmd(bang_command) - helpers.flush_pending() - - helpers.assert_equal(#_G.test_state.vim_pack_update_calls, 1, "vim.pack.update should be called once") - local call = _G.test_state.vim_pack_update_calls[1] - helpers.assert_nil(call.names, "names should be nil for all") - helpers.assert_not_nil(call.opts, "opts should be passed with bang") - helpers.assert_equal(call.opts.force, true, "opts.force should be true with bang") - if expected_opts then - for k, v in pairs(expected_opts) do - helpers.assert_equal(call.opts[k], v, ("opts.%s should be preserved with bang"):format(k)) - end - end - - helpers.cleanup_test_env() - end) - - helpers.test(bang_command .. " with plugin name passes force=true and targets that plugin", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - vim.cmd(bang_command .. ' plugin-a') - helpers.flush_pending() - - helpers.assert_equal(#_G.test_state.vim_pack_update_calls, 1, "vim.pack.update should be called once") - local call = _G.test_state.vim_pack_update_calls[1] - helpers.assert_table_contains(call.names, 'plugin-a', "plugin-a should be in names list") - helpers.assert_not_nil(call.opts, "opts should be passed with bang") - helpers.assert_equal(call.opts.force, true, "opts.force should be true with bang") - if expected_opts then - for k, v in pairs(expected_opts) do - helpers.assert_equal(call.opts[k], v, ("opts.%s should be preserved with bang"):format(k)) - end - end - - helpers.cleanup_test_env() - end) - end - - if expected_opts and expected_opts.target == 'lockfile' then - helpers.test(command .. " shows error when no lockfile exists", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local call_log = _G.test_state.vim_pack_update_calls - local prev_update = vim.pack.update - vim.pack.update = function(names, opts) - table.insert(call_log, { names = names, opts = opts }) - error("no lockfile found") - end - - vim.cmd(command) - vim.pack.update = prev_update - helpers.flush_pending() - - local found_error = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find(error_prefix) and notif.msg:find('lockfile') and notif.level == vim.log.levels.ERROR then - found_error = true - break - end - end - helpers.assert_true(found_error, "should notify error when no lockfile exists") - - helpers.cleanup_test_env() - end) - end - - helpers.test(command .. " shows friendly error when vim.pack.update fails", function() - helpers.setup_test_env() + end, + } + end + if expected_opts and expected_opts.target == 'lockfile' then + cases[#cases + 1] = { + name = command .. " shows error when no lockfile exists", + fn = function() require('zpack').setup({ spec = { { 'test/plugin-a' }, @@ -263,7 +228,7 @@ function M.create_tests(config) local prev_update = vim.pack.update vim.pack.update = function(names, opts) table.insert(call_log, { names = names, opts = opts }) - error("simulated failure") + error("no lockfile found") end vim.cmd(command) @@ -272,17 +237,51 @@ function M.create_tests(config) local found_error = false for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find(error_prefix) and notif.level == vim.log.levels.ERROR then + if notif.msg:find(error_prefix) and notif.msg:find('lockfile') and notif.level == vim.log.levels.ERROR then found_error = true break end end - helpers.assert_true(found_error, "should notify friendly error when vim.pack.update fails") - - helpers.cleanup_test_env() - end) - end) + assert.is_truthy(found_error, "should notify error when no lockfile exists") + end, + } end + + cases[#cases + 1] = { + name = command .. " shows friendly error when vim.pack.update fails", + fn = function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local call_log = _G.test_state.vim_pack_update_calls + local prev_update = vim.pack.update + vim.pack.update = function(names, opts) + table.insert(call_log, { names = names, opts = opts }) + error("simulated failure") + end + + vim.cmd(command) + vim.pack.update = prev_update + helpers.flush_pending() + + local found_error = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find(error_prefix) and notif.level == vim.log.levels.ERROR then + found_error = true + break + end + end + assert.is_truthy(found_error, "should notify friendly error when vim.pack.update fails") + end, + } + + return cases end return M diff --git a/tests/plugin_data_test.lua b/tests/plugin_data_test.lua index c72e4f5..b3989e9 100644 --- a/tests/plugin_data_test.lua +++ b/tests/plugin_data_test.lua @@ -1,298 +1,257 @@ local helpers = require('helpers') -return function() - helpers.describe("Plugin Data (zpack.Plugin)", function() - helpers.test("plugin object is stored in registry after registration", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { 'test/plugin' }, - }, - defaults = { confirm = false }, - }) +describe("Plugin Data (zpack.Plugin)", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("plugin object is stored in registry after registration", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { + { 'test/plugin' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + assert.is_not_nil(state.spec_registry[src], "Plugin should be registered") + assert.is_not_nil(state.spec_registry[src].plugin, "Plugin data should be stored") + end) - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - helpers.assert_not_nil(state.spec_registry[src], "Plugin should be registered") - helpers.assert_not_nil(state.spec_registry[src].plugin, "Plugin data should be stored") + it("plugin object has spec field", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { + { 'test/plugin' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + local plugin = state.spec_registry[src].plugin + assert.is_not_nil(plugin, "Plugin data should exist") + assert.is_not_nil(plugin.spec, "Plugin should have spec field") + assert.is_not_nil(plugin.spec.src, "Plugin spec should have src") + end) - helpers.cleanup_test_env() - end) + it("plugin object has path field", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { + { 'test/plugin' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + local plugin = state.spec_registry[src].plugin + assert.is_not_nil(plugin, "Plugin data should exist") + assert.is_not_nil(plugin.path, "Plugin should have path field") + assert.are.equal('string', type(plugin.path)) + end) - helpers.test("plugin object has spec field", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("config hook receives plugin argument", function() + local received_plugin = nil - require('zpack').setup({ - spec = { - { 'test/plugin' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - local plugin = state.spec_registry[src].plugin - helpers.assert_not_nil(plugin, "Plugin data should exist") - helpers.assert_not_nil(plugin.spec, "Plugin should have spec field") - helpers.assert_not_nil(plugin.spec.src, "Plugin spec should have src") - - helpers.cleanup_test_env() - end) - - helpers.test("plugin object has path field", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { 'test/plugin' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - local plugin = state.spec_registry[src].plugin - helpers.assert_not_nil(plugin, "Plugin data should exist") - helpers.assert_not_nil(plugin.path, "Plugin should have path field") - helpers.assert_equal(type(plugin.path), 'string', "Plugin path should be a string") - - helpers.cleanup_test_env() - end) - - helpers.test("config hook receives plugin argument", function() - helpers.setup_test_env() - local received_plugin = nil - - require('zpack').setup({ - spec = { - { - 'test/plugin', - config = function(plugin) - received_plugin = plugin - end, - }, + require('zpack').setup({ + spec = { + { + 'test/plugin', + config = function(plugin) + received_plugin = plugin + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_not_nil(received_plugin, "config should receive plugin argument") - helpers.assert_not_nil(received_plugin.spec, "plugin should have spec") - helpers.assert_not_nil(received_plugin.path, "plugin should have path") - - helpers.cleanup_test_env() - end) - - helpers.test("init hook receives plugin argument", function() - helpers.setup_test_env() - local received_plugin = nil - - require('zpack').setup({ - spec = { - { - 'test/plugin', - init = function(plugin) - received_plugin = plugin - end, - }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_not_nil(received_plugin, "init should receive plugin argument") - helpers.assert_not_nil(received_plugin.spec, "plugin should have spec") - helpers.assert_not_nil(received_plugin.path, "plugin should have path") - - helpers.cleanup_test_env() - end) - - helpers.test("cond function receives plugin argument", function() - helpers.setup_test_env() - local received_plugin = nil - - require('zpack').setup({ - spec = { - { - 'test/plugin', - cond = function(plugin) - received_plugin = plugin - return true - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_not_nil(received_plugin, "config should receive plugin argument") + assert.is_not_nil(received_plugin.spec, "plugin should have spec") + assert.is_not_nil(received_plugin.path, "plugin should have path") + end) + + it("init hook receives plugin argument", function() + local received_plugin = nil + + require('zpack').setup({ + spec = { + { + 'test/plugin', + init = function(plugin) + received_plugin = plugin + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_not_nil(received_plugin, "cond should receive plugin argument") - helpers.assert_not_nil(received_plugin.spec, "plugin should have spec") - helpers.assert_not_nil(received_plugin.path, "plugin should have path") - - helpers.cleanup_test_env() - end) - - helpers.test("cond function can use plugin.path", function() - helpers.setup_test_env() - local path_received = nil - - require('zpack').setup({ - spec = { - { - 'test/plugin', - cond = function(plugin) - path_received = plugin.path - return true - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_not_nil(received_plugin, "init should receive plugin argument") + assert.is_not_nil(received_plugin.spec, "plugin should have spec") + assert.is_not_nil(received_plugin.path, "plugin should have path") + end) + + it("cond function receives plugin argument", function() + local received_plugin = nil + + require('zpack').setup({ + spec = { + { + 'test/plugin', + cond = function(plugin) + received_plugin = plugin + return true + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_not_nil(path_received, "cond should receive plugin.path") - helpers.assert_equal(type(path_received), 'string', "plugin.path should be a string") - - helpers.cleanup_test_env() - end) - - helpers.test("cmd can be a function returning commands", function() - helpers.setup_test_env() - local lazy_module = require('zpack.lazy') - - local spec = { - 'test/plugin', - cmd = function(plugin) - return { 'TestCmd1', 'TestCmd2' } - end, - } - - local mock_plugin = { spec = { src = 'test' }, path = '/mock/path' } - helpers.assert_true(lazy_module.is_lazy(spec, mock_plugin), "Plugin with cmd function should be lazy") - - helpers.cleanup_test_env() - end) - - helpers.test("event can be a function returning events", function() - helpers.setup_test_env() - local lazy_module = require('zpack.lazy') - - local spec = { - 'test/plugin', - event = function(plugin) - return 'VeryLazy' - end, - } - - local mock_plugin = { spec = { src = 'test' }, path = '/mock/path' } - helpers.assert_true(lazy_module.is_lazy(spec, mock_plugin), "Plugin with event function should be lazy") - - helpers.cleanup_test_env() - end) - - helpers.test("ft can be a function returning filetypes", function() - helpers.setup_test_env() - local lazy_module = require('zpack.lazy') - - local spec = { - 'test/plugin', - ft = function(plugin) - return { 'lua', 'vim' } - end, - } - - local mock_plugin = { spec = { src = 'test' }, path = '/mock/path' } - helpers.assert_true(lazy_module.is_lazy(spec, mock_plugin), "Plugin with ft function should be lazy") - - helpers.cleanup_test_env() - end) - - helpers.test("keys can be a function returning keymaps", function() - helpers.setup_test_env() - local lazy_module = require('zpack.lazy') - - local spec = { - 'test/plugin', - keys = function(plugin) - return { { 't', function() end, desc = 'Test' } } - end, - } - - local mock_plugin = { spec = { src = 'test' }, path = '/mock/path' } - helpers.assert_true(lazy_module.is_lazy(spec, mock_plugin), "Plugin with keys function should be lazy") - - helpers.cleanup_test_env() - end) - - helpers.test("function trigger returning nil means not lazy", function() - helpers.setup_test_env() - local lazy_module = require('zpack.lazy') - - local spec = { - 'test/plugin', - cmd = function(plugin) - return nil - end, - } - - local mock_plugin = { spec = { src = 'test' }, path = '/mock/path' } - helpers.assert_false(lazy_module.is_lazy(spec, mock_plugin), "Plugin with cmd function returning nil should not be lazy") - - helpers.cleanup_test_env() - end) - - helpers.test("cmd function receives plugin and returns value", function() - helpers.setup_test_env() - local received_plugin = nil - local returned_cmds = { 'TestCommand' } - - local spec = { - 'test/plugin', - cmd = function(plugin) - received_plugin = plugin - return returned_cmds - end, - } - - local mock_plugin = { spec = { src = 'test', name = 'plugin' }, path = '/mock/path' } - local lazy_module = require('zpack.lazy') - lazy_module.is_lazy(spec, mock_plugin) - - helpers.assert_not_nil(received_plugin, "cmd function should receive plugin") - helpers.assert_equal(received_plugin.path, '/mock/path', "plugin.path should match") - - helpers.cleanup_test_env() - end) - - helpers.test("startup plugin keys can be a function", function() - helpers.setup_test_env() - local keys_called = false - local received_plugin = nil - - require('zpack').setup({ - spec = { - { - 'test/plugin', - lazy = false, - keys = function(plugin) - keys_called = true - received_plugin = plugin - return { { 'test', function() end } } - end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_not_nil(received_plugin, "cond should receive plugin argument") + assert.is_not_nil(received_plugin.spec, "plugin should have spec") + assert.is_not_nil(received_plugin.path, "plugin should have path") + end) + + it("cond function can use plugin.path", function() + local path_received = nil + + require('zpack').setup({ + spec = { + { + 'test/plugin', + cond = function(plugin) + path_received = plugin.path + return true + end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_not_nil(path_received, "cond should receive plugin.path") + assert.are.equal('string', type(path_received)) + end) + + it("cmd can be a function returning commands", function() + local lazy_module = require('zpack.lazy') + + local spec = { + 'test/plugin', + cmd = function(plugin) + return { 'TestCmd1', 'TestCmd2' } + end, + } + + local mock_plugin = { spec = { src = 'test' }, path = '/mock/path' } + assert.is_truthy(lazy_module.is_lazy(spec, mock_plugin), "Plugin with cmd function should be lazy") + end) + + it("event can be a function returning events", function() + local lazy_module = require('zpack.lazy') - helpers.flush_pending() - helpers.assert_true(keys_called, "keys function should be called for startup plugin") - helpers.assert_not_nil(received_plugin, "keys function should receive plugin") + local spec = { + 'test/plugin', + event = function(plugin) + return 'VeryLazy' + end, + } + + local mock_plugin = { spec = { src = 'test' }, path = '/mock/path' } + assert.is_truthy(lazy_module.is_lazy(spec, mock_plugin), "Plugin with event function should be lazy") + end) + + it("ft can be a function returning filetypes", function() + local lazy_module = require('zpack.lazy') + + local spec = { + 'test/plugin', + ft = function(plugin) + return { 'lua', 'vim' } + end, + } + + local mock_plugin = { spec = { src = 'test' }, path = '/mock/path' } + assert.is_truthy(lazy_module.is_lazy(spec, mock_plugin), "Plugin with ft function should be lazy") + end) + + it("keys can be a function returning keymaps", function() + local lazy_module = require('zpack.lazy') + + local spec = { + 'test/plugin', + keys = function(plugin) + return { { 't', function() end, desc = 'Test' } } + end, + } + + local mock_plugin = { spec = { src = 'test' }, path = '/mock/path' } + assert.is_truthy(lazy_module.is_lazy(spec, mock_plugin), "Plugin with keys function should be lazy") + end) + + it("function trigger returning nil means not lazy", function() + local lazy_module = require('zpack.lazy') + + local spec = { + 'test/plugin', + cmd = function(plugin) + return nil + end, + } + + local mock_plugin = { spec = { src = 'test' }, path = '/mock/path' } + assert.is_falsy(lazy_module.is_lazy(spec, mock_plugin), "Plugin with cmd function returning nil should not be lazy") + end) + + it("cmd function receives plugin and returns value", function() + local received_plugin = nil + local returned_cmds = { 'TestCommand' } + + local spec = { + 'test/plugin', + cmd = function(plugin) + received_plugin = plugin + return returned_cmds + end, + } + + local mock_plugin = { spec = { src = 'test', name = 'plugin' }, path = '/mock/path' } + local lazy_module = require('zpack.lazy') + lazy_module.is_lazy(spec, mock_plugin) + + assert.is_not_nil(received_plugin, "cmd function should receive plugin") + assert.are.equal('/mock/path', received_plugin.path) + end) + + it("startup plugin keys can be a function", function() + local keys_called = false + local received_plugin = nil + + require('zpack').setup({ + spec = { + { + 'test/plugin', + lazy = false, + keys = function(plugin) + keys_called = true + received_plugin = plugin + return { { 'test', function() end } } + end, + }, + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + helpers.flush_pending() + assert.is_truthy(keys_called, "keys function should be called for startup plugin") + assert.is_not_nil(received_plugin, "keys function should receive plugin") end) -end +end) diff --git a/tests/priority_test.lua b/tests/priority_test.lua index 43c0b19..5421b82 100644 --- a/tests/priority_test.lua +++ b/tests/priority_test.lua @@ -1,218 +1,194 @@ local helpers = require('helpers') -return function() - helpers.describe("Priority-based Loading", function() - helpers.test("default priority is 50", function() - helpers.setup_test_env() - local utils = require('zpack.utils') - - require('zpack').setup({ - spec = { - { 'test/plugin' }, +describe("Priority-based Loading", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("default priority is 50", function() + local utils = require('zpack.utils') + + require('zpack').setup({ + spec = { + { 'test/plugin' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + local priority = utils.get_priority(src) + assert.are.equal(50, priority) + end) + + it("custom priority is stored", function() + local utils = require('zpack.utils') + + require('zpack').setup({ + spec = { + { + 'test/plugin', + priority = 100, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - local priority = utils.get_priority(src) - helpers.assert_equal(priority, 50, "Default priority should be 50") - - helpers.cleanup_test_env() - end) - - helpers.test("custom priority is stored", function() - helpers.setup_test_env() - local utils = require('zpack.utils') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - priority = 100, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + local priority = utils.get_priority(src) + assert.are.equal(100, priority) + end) + + it("higher priority plugins load first", function() + local load_order = {} + + require('zpack').setup({ + spec = { + { + 'test/plugin1', + priority = 100, + config = function() + table.insert(load_order, 'plugin1') + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - local priority = utils.get_priority(src) - helpers.assert_equal(priority, 100, "Custom priority should be stored") - - helpers.cleanup_test_env() - end) - - helpers.test("higher priority plugins load first", function() - helpers.setup_test_env() - local load_order = {} - - require('zpack').setup({ - spec = { - { - 'test/plugin1', - priority = 100, - config = function() - table.insert(load_order, 'plugin1') - end, - }, - { - 'test/plugin2', - priority = 200, - config = function() - table.insert(load_order, 'plugin2') - end, - }, - { - 'test/plugin3', - priority = 150, - config = function() - table.insert(load_order, 'plugin3') - end, - }, + { + 'test/plugin2', + priority = 200, + config = function() + table.insert(load_order, 'plugin2') + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_equal(#load_order, 3, "All plugins should load") - helpers.assert_equal(load_order[1], 'plugin2', "Plugin2 (priority 200) should load first") - helpers.assert_equal(load_order[2], 'plugin3', "Plugin3 (priority 150) should load second") - helpers.assert_equal(load_order[3], 'plugin1', "Plugin1 (priority 100) should load third") - - helpers.cleanup_test_env() - end) - - helpers.test("priority works with lazy loading", function() - helpers.setup_test_env() - local utils = require('zpack.utils') - - require('zpack').setup({ - spec = { - { - 'test/plugin', - cmd = 'TestCommand', - priority = 999, - }, + { + 'test/plugin3', + priority = 150, + config = function() + table.insert(load_order, 'plugin3') + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src = 'https://github.com/test/plugin' - local priority = utils.get_priority(src) - helpers.assert_equal(priority, 999, "Priority should work with lazy loading") - - helpers.cleanup_test_env() - end) - - helpers.test("compare_priority function sorts correctly", function() - helpers.setup_test_env() - local utils = require('zpack.utils') - - require('zpack').setup({ - spec = { - { 'test/plugin1', priority = 100 }, - { 'test/plugin2', priority = 50 }, - { 'test/plugin3', priority = 200 }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.are.equal(3, #load_order) + assert.are.equal('plugin2', load_order[1]) + assert.are.equal('plugin3', load_order[2]) + assert.are.equal('plugin1', load_order[3]) + end) + + it("priority works with lazy loading", function() + local utils = require('zpack.utils') + + require('zpack').setup({ + spec = { + { + 'test/plugin', + cmd = 'TestCommand', + priority = 999, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src1 = 'https://github.com/test/plugin1' - local src2 = 'https://github.com/test/plugin2' - local src3 = 'https://github.com/test/plugin3' - - helpers.assert_true( - utils.compare_priority(src3, src1), - "Plugin3 (200) should be higher priority than Plugin1 (100)" - ) - helpers.assert_true( - utils.compare_priority(src1, src2), - "Plugin1 (100) should be higher priority than Plugin2 (50)" - ) - helpers.assert_false( - utils.compare_priority(src2, src3), - "Plugin2 (50) should not be higher priority than Plugin3 (200)" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("compare_priority uses import order as tiebreaker", function() - helpers.setup_test_env() - local utils = require('zpack.utils') - - require('zpack').setup({ - spec = { - { 'test/first' }, -- import_order = 0 - { 'test/second' }, -- import_order = 1 - { 'test/third' }, -- import_order = 2 + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src = 'https://github.com/test/plugin' + local priority = utils.get_priority(src) + assert.are.equal(999, priority) + end) + + it("compare_priority function sorts correctly", function() + local utils = require('zpack.utils') + + require('zpack').setup({ + spec = { + { 'test/plugin1', priority = 100 }, + { 'test/plugin2', priority = 50 }, + { 'test/plugin3', priority = 200 }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src1 = 'https://github.com/test/plugin1' + local src2 = 'https://github.com/test/plugin2' + local src3 = 'https://github.com/test/plugin3' + + assert.is_truthy( + utils.compare_priority(src3, src1), + "Plugin3 (200) should be higher priority than Plugin1 (100)" + ) + assert.is_truthy( + utils.compare_priority(src1, src2), + "Plugin1 (100) should be higher priority than Plugin2 (50)" + ) + assert.is_falsy( + utils.compare_priority(src2, src3), + "Plugin2 (50) should not be higher priority than Plugin3 (200)" + ) + end) + + it("compare_priority uses import order as tiebreaker", function() + local utils = require('zpack.utils') + + require('zpack').setup({ + spec = { + { 'test/first' }, -- import_order = 0 + { 'test/second' }, -- import_order = 1 + { 'test/third' }, -- import_order = 2 + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + local src1 = 'https://github.com/test/first' + local src2 = 'https://github.com/test/second' + local src3 = 'https://github.com/test/third' + + assert.is_truthy( + utils.compare_priority(src1, src2), + "first (import 0) should come before second (import 1) when priority equal" + ) + assert.is_truthy( + utils.compare_priority(src2, src3), + "second (import 1) should come before third (import 2) when priority equal" + ) + assert.is_falsy( + utils.compare_priority(src3, src1), + "third (import 2) should not come before first (import 0)" + ) + end) + + it("priority affects lazy plugin load order on same trigger", function() + local load_order = {} + + require('zpack').setup({ + spec = { + { + 'test/plugin1', + event = 'VeryLazy', + priority = 50, + config = function() + table.insert(load_order, 'plugin1') + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - local src1 = 'https://github.com/test/first' - local src2 = 'https://github.com/test/second' - local src3 = 'https://github.com/test/third' - - helpers.assert_true( - utils.compare_priority(src1, src2), - "first (import 0) should come before second (import 1) when priority equal" - ) - helpers.assert_true( - utils.compare_priority(src2, src3), - "second (import 1) should come before third (import 2) when priority equal" - ) - helpers.assert_false( - utils.compare_priority(src3, src1), - "third (import 2) should not come before first (import 0)" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("priority affects lazy plugin load order on same trigger", function() - helpers.setup_test_env() - local load_order = {} - - require('zpack').setup({ - spec = { - { - 'test/plugin1', - event = 'VeryLazy', - priority = 50, - config = function() - table.insert(load_order, 'plugin1') - end, - }, - { - 'test/plugin2', - event = 'VeryLazy', - priority = 100, - config = function() - table.insert(load_order, 'plugin2') - end, - }, + { + 'test/plugin2', + event = 'VeryLazy', + priority = 100, + config = function() + table.insert(load_order, 'plugin2') + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - vim.api.nvim_exec_autocmds('UIEnter', {}) - helpers.flush_pending() - - if #load_order > 0 then - helpers.assert_equal( - load_order[1], - 'plugin2', - "Higher priority plugin should load first on same trigger" - ) - end - - helpers.cleanup_test_env() - end) + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + vim.api.nvim_exec_autocmds('UIEnter', {}) + helpers.flush_pending() + + if #load_order > 0 then + assert.are.equal('plugin2', load_order[1]) + end end) -end +end) diff --git a/tests/public_api_test.lua b/tests/public_api_test.lua index 0c728a8..d072356 100644 --- a/tests/public_api_test.lua +++ b/tests/public_api_test.lua @@ -17,625 +17,498 @@ local function find_by_name(list, name) return nil end -return function() - helpers.describe("Public API (zpack.get_plugins / get_plugin)", function() - helpers.test("get_plugins returns an entry for each registered plugin", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/alpha' }, - { 'test/beta' }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - local list = require('zpack').get_plugins() - helpers.assert_not_nil(find_by_name(list, 'alpha'), "alpha should be in get_plugins()") - helpers.assert_not_nil(find_by_name(list, 'beta'), "beta should be in get_plugins()") - - helpers.cleanup_test_env() - end) - - helpers.test("get_plugins entry shape has name, src, status, lazy", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/alpha' }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - local info = find_by_name(require('zpack').get_plugins(), 'alpha') - helpers.assert_not_nil(info, "alpha should be listed") - helpers.assert_equal(info.name, 'alpha', "name should be resolved plugin name") - helpers.assert_equal(info.src, 'https://github.com/test/alpha', "src should be git URL") - helpers.assert_equal(info.status, 'loaded', "eagerly loaded plugin should report loaded") - helpers.assert_equal(info.lazy, false, "non-lazy plugin should report lazy=false") - - helpers.cleanup_test_env() - end) - - helpers.test("PluginInfo exposes exactly the documented key set", function() - helpers.setup_test_env() +describe("Public API (zpack.get_plugins / get_plugin)", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("get_plugins returns an entry for each registered plugin", function() + require('zpack').setup({ + spec = { + { 'test/alpha' }, + { 'test/beta' }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local list = require('zpack').get_plugins() + assert.is_not_nil(find_by_name(list, 'alpha'), "alpha should be in get_plugins()") + assert.is_not_nil(find_by_name(list, 'beta'), "beta should be in get_plugins()") + end) - require('zpack').setup({ - spec = { - { 'test/alpha' }, - { 'test/gamma', cond = false }, - { 'test/delta', lazy = true }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() + it("get_plugins entry shape has name, src, status, lazy", function() + require('zpack').setup({ + spec = { + { 'test/alpha' }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local info = find_by_name(require('zpack').get_plugins(), 'alpha') + assert.is_not_nil(info, "alpha should be listed") + assert.are.equal('alpha', info.name) + assert.are.equal('https://github.com/test/alpha', info.src) + assert.are.equal('loaded', info.status) + assert.are.equal(false, info.lazy) + end) - local list = require('zpack').get_plugins() - helpers.assert_true(#list >= 3, "all three plugins should be listed") - for _, info in ipairs(list) do - for k in pairs(info) do - helpers.assert_true( - PLUGIN_INFO_KEYS[k] == true, - ("PluginInfo leaked undocumented key %q on %q — if this is intentional, bump zpack.api.VERSION and update PLUGIN_INFO_KEYS"):format(k, tostring(info.name)) - ) - end - -- Every key must be present (rawget catches silent-nil regressions - -- that `pairs` iteration would miss). - for k in pairs(PLUGIN_INFO_KEYS) do - helpers.assert_true( - rawget(info, k) ~= nil, - ("PluginInfo is missing required key %q on %q"):format(k, tostring(info.name)) - ) - end + it("PluginInfo exposes exactly the documented key set", function() + require('zpack').setup({ + spec = { + { 'test/alpha' }, + { 'test/gamma', cond = false }, + { 'test/delta', lazy = true }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local list = require('zpack').get_plugins() + assert.is_truthy(#list >= 3, "all three plugins should be listed") + for _, info in ipairs(list) do + for k in pairs(info) do + assert.is_truthy( + PLUGIN_INFO_KEYS[k] == true, + ("PluginInfo leaked undocumented key %q on %q — if this is intentional, bump zpack.api.VERSION and update PLUGIN_INFO_KEYS"):format(k, tostring(info.name)) + ) end + -- Every key must be present (rawget catches silent-nil regressions + -- that `pairs` iteration would miss). + for k in pairs(PLUGIN_INFO_KEYS) do + assert.is_truthy( + rawget(info, k) ~= nil, + ("PluginInfo is missing required key %q on %q"):format(k, tostring(info.name)) + ) + end + end + end) - helpers.cleanup_test_env() - end) - - helpers.test("enabled=false plugins are pruned and not returned", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/alpha', enabled = false }, - { 'test/beta' }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - local list = require('zpack').get_plugins() - helpers.assert_nil(find_by_name(list, 'alpha'), "enabled=false plugin must not appear in get_plugins()") - helpers.assert_not_nil(find_by_name(list, 'beta'), "beta should still be listed") - - helpers.cleanup_test_env() - end) - - helpers.test("enabled=false plugins do not reach vim.pack.add", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/alpha', enabled = false }, - { 'test/beta' }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - helpers.assert_nil(_G.test_state.registered_pack_specs['alpha'], "alpha must not be installed") - helpers.assert_not_nil(_G.test_state.registered_pack_specs['beta'], "beta should be installed") - - helpers.cleanup_test_env() - end) - - helpers.test("get_plugin returns nil for enabled=false plugins", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/alpha', enabled = false }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - helpers.assert_nil(require('zpack').get_plugin('alpha'), "pruned plugin must not be findable") - - helpers.cleanup_test_env() - end) - - helpers.test("lazy plugins report lazy=true and status=pending", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/alpha', lazy = true }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - local info = find_by_name(require('zpack').get_plugins(), 'alpha') - helpers.assert_not_nil(info, "lazy alpha should be listed") - helpers.assert_equal(info.lazy, true, "lazy should be true") - helpers.assert_equal(info.status, 'pending', "unloaded lazy plugin should be pending") - - helpers.cleanup_test_env() - end) - - helpers.test("get_plugin returns a single entry by name", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/alpha' }, - { 'test/beta' }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - local info = require('zpack').get_plugin('beta') - helpers.assert_not_nil(info, "get_plugin should find beta") - helpers.assert_equal(info.name, 'beta', "name should match") - helpers.assert_equal(info.src, 'https://github.com/test/beta', "src should match") - - helpers.cleanup_test_env() - end) - - helpers.test("get_plugin returns nil for unknown name", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { { 'test/alpha' } }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - helpers.assert_nil(require('zpack').get_plugin('does-not-exist'), "unknown name should return nil") - helpers.assert_nil(require('zpack').get_plugin(''), "empty name should return nil") - - helpers.cleanup_test_env() - end) - - helpers.test("cond=false reports status=disabled", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/alpha', cond = false }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - local info = require('zpack').get_plugin('alpha') - helpers.assert_not_nil(info, "cond-disabled alpha should still be listed") - helpers.assert_equal(info.status, 'disabled', "cond=false should report disabled") - - helpers.cleanup_test_env() - end) - - helpers.test("cond function returning false reports status=disabled", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/alpha', cond = function() return false end }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - local info = require('zpack').get_plugin('alpha') - helpers.assert_not_nil(info, "cond-fn-disabled alpha should still be listed") - helpers.assert_equal(info.status, 'disabled', "cond() == false should report disabled") - - helpers.cleanup_test_env() - end) - - helpers.test("cond_disabled lazy plugin still reports lazy=true", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/alpha', cond = false, lazy = true }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - local info = require('zpack').get_plugin('alpha') - helpers.assert_not_nil(info, "alpha should be listed") - helpers.assert_equal(info.status, 'disabled', "cond=false should report disabled") - helpers.assert_equal(info.lazy, true, "lazy field must be honest even when cond-disabled") - - helpers.cleanup_test_env() - end) - - helpers.test("cond_disabled event-triggered plugin reports lazy=true", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/alpha', cond = false, event = 'BufReadPost' }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() + it("enabled=false plugins are pruned and not returned", function() + require('zpack').setup({ + spec = { + { 'test/alpha', enabled = false }, + { 'test/beta' }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local list = require('zpack').get_plugins() + assert.is_nil(find_by_name(list, 'alpha'), "enabled=false plugin must not appear in get_plugins()") + assert.is_not_nil(find_by_name(list, 'beta'), "beta should still be listed") + end) - local info = require('zpack').get_plugin('alpha') - helpers.assert_not_nil(info, "alpha should be listed") - helpers.assert_equal(info.lazy, true, "event-triggered plugin should report lazy=true even when cond-disabled") + it("enabled=false plugins do not reach vim.pack.add", function() + require('zpack').setup({ + spec = { + { 'test/alpha', enabled = false }, + { 'test/beta' }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + assert.is_nil(_G.test_state.registered_pack_specs['alpha'], "alpha must not be installed") + assert.is_not_nil(_G.test_state.registered_pack_specs['beta'], "beta should be installed") + end) - helpers.cleanup_test_env() - end) + it("get_plugin returns nil for enabled=false plugins", function() + require('zpack').setup({ + spec = { + { 'test/alpha', enabled = false }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() - helpers.test("disable propagates and prunes parent + child", function() - helpers.setup_test_env() + assert.is_nil(require('zpack').get_plugin('alpha'), "pruned plugin must not be findable") + end) - require('zpack').setup({ - spec = { - { 'test/parent', dependencies = { 'test/child' } }, - { 'test/child', enabled = false }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() + it("lazy plugins report lazy=true and status=pending", function() + require('zpack').setup({ + spec = { + { 'test/alpha', lazy = true }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local info = find_by_name(require('zpack').get_plugins(), 'alpha') + assert.is_not_nil(info, "lazy alpha should be listed") + assert.are.equal(true, info.lazy) + assert.are.equal('pending', info.status) + end) - helpers.assert_nil(require('zpack').get_plugin('parent'), "parent should be pruned after dep-propagated disable") - helpers.assert_nil(require('zpack').get_plugin('child'), "child should be pruned after explicit disable") + it("get_plugin returns a single entry by name", function() + require('zpack').setup({ + spec = { + { 'test/alpha' }, + { 'test/beta' }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local info = require('zpack').get_plugin('beta') + assert.is_not_nil(info, "get_plugin should find beta") + assert.are.equal('beta', info.name) + assert.are.equal('https://github.com/test/beta', info.src) + end) - helpers.cleanup_test_env() - end) + it("get_plugin returns nil for unknown name", function() + require('zpack').setup({ + spec = { { 'test/alpha' } }, + defaults = { confirm = false }, + }) + helpers.flush_pending() - helpers.test("dep-only plugin is pruned when its only parent is disabled", function() - helpers.setup_test_env() + assert.is_nil(require('zpack').get_plugin('does-not-exist'), "unknown name should return nil") + assert.is_nil(require('zpack').get_plugin(''), "empty name should return nil") + end) - require('zpack').setup({ - spec = { - { 'test/parent', enabled = false, dependencies = { 'test/dep' } }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() + it("cond=false reports status=disabled", function() + require('zpack').setup({ + spec = { + { 'test/alpha', cond = false }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local info = require('zpack').get_plugin('alpha') + assert.is_not_nil(info, "cond-disabled alpha should still be listed") + assert.are.equal('disabled', info.status) + end) - helpers.assert_nil(require('zpack').get_plugin('parent'), "disabled parent should be pruned") - helpers.assert_nil(require('zpack').get_plugin('dep'), "dep-only child of disabled parent should be pruned") - helpers.assert_nil(_G.test_state.registered_pack_specs['dep'], "orphaned dep must not reach vim.pack.add") + it("cond function returning false reports status=disabled", function() + require('zpack').setup({ + spec = { + { 'test/alpha', cond = function() return false end }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local info = require('zpack').get_plugin('alpha') + assert.is_not_nil(info, "cond-fn-disabled alpha should still be listed") + assert.are.equal('disabled', info.status) + end) - helpers.cleanup_test_env() - end) + it("cond_disabled lazy plugin still reports lazy=true", function() + require('zpack').setup({ + spec = { + { 'test/alpha', cond = false, lazy = true }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local info = require('zpack').get_plugin('alpha') + assert.is_not_nil(info, "alpha should be listed") + assert.are.equal('disabled', info.status) + assert.are.equal(true, info.lazy) + end) - helpers.test("dep with another live parent survives when one parent is disabled", function() - helpers.setup_test_env() + it("cond_disabled event-triggered plugin reports lazy=true", function() + require('zpack').setup({ + spec = { + { 'test/alpha', cond = false, event = 'BufReadPost' }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local info = require('zpack').get_plugin('alpha') + assert.is_not_nil(info, "alpha should be listed") + assert.are.equal(true, info.lazy) + end) - require('zpack').setup({ - spec = { - { 'test/keeper', dependencies = { 'test/shared' } }, - { 'test/dropper', enabled = false, dependencies = { 'test/shared' } }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() + it("disable propagates and prunes parent + child", function() + require('zpack').setup({ + spec = { + { 'test/parent', dependencies = { 'test/child' } }, + { 'test/child', enabled = false }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + assert.is_nil(require('zpack').get_plugin('parent'), "parent should be pruned after dep-propagated disable") + assert.is_nil(require('zpack').get_plugin('child'), "child should be pruned after explicit disable") + end) - helpers.assert_not_nil(require('zpack').get_plugin('keeper'), "keeper should survive") - helpers.assert_not_nil(require('zpack').get_plugin('shared'), "shared dep should survive (still referenced by keeper)") - helpers.assert_nil(require('zpack').get_plugin('dropper'), "dropper should be pruned") + it("dep-only plugin is pruned when its only parent is disabled", function() + require('zpack').setup({ + spec = { + { 'test/parent', enabled = false, dependencies = { 'test/dep' } }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + assert.is_nil(require('zpack').get_plugin('parent'), "disabled parent should be pruned") + assert.is_nil(require('zpack').get_plugin('dep'), "dep-only child of disabled parent should be pruned") + assert.is_nil(_G.test_state.registered_pack_specs['dep'], "orphaned dep must not reach vim.pack.add") + end) - helpers.cleanup_test_env() - end) + it("dep with another live parent survives when one parent is disabled", function() + require('zpack').setup({ + spec = { + { 'test/keeper', dependencies = { 'test/shared' } }, + { 'test/dropper', enabled = false, dependencies = { 'test/shared' } }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + assert.is_not_nil(require('zpack').get_plugin('keeper'), "keeper should survive") + assert.is_not_nil(require('zpack').get_plugin('shared'), "shared dep should survive (still referenced by keeper)") + assert.is_nil(require('zpack').get_plugin('dropper'), "dropper should be pruned") + end) - helpers.test("status reports loading while a lazy-loaded plugin's config is running", function() - helpers.setup_test_env() - local observed_status - require('zpack').setup({ - spec = { - { - 'test/alpha', - lazy = true, - config = function() - local info = require('zpack').get_plugin('alpha') - observed_status = info and info.status - end, - }, + it("status reports loading while a lazy-loaded plugin's config is running", function() + local observed_status + require('zpack').setup({ + spec = { + { + 'test/alpha', + lazy = true, + config = function() + local info = require('zpack').get_plugin('alpha') + observed_status = info and info.status + end, }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - -- Before lazy-load, alpha is pending. - helpers.assert_equal(require('zpack').get_plugin('alpha').status, 'pending', "lazy plugin starts pending") + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() - pcall(vim.cmd, 'ZPack! load alpha') + -- Before lazy-load, alpha is pending. + assert.are.equal('pending', require('zpack').get_plugin('alpha').status) - helpers.assert_equal(observed_status, 'loading', "get_plugin inside config must see status=loading") - helpers.assert_equal(require('zpack').get_plugin('alpha').status, 'loaded', "status should be loaded after load completes") - - helpers.cleanup_test_env() - end) + pcall(vim.cmd, 'ZPack! load alpha') - helpers.test("PluginInfo does not expose a rev field (install state is vim.pack's)", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { { 'test/alpha' } }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - local info = require('zpack').get_plugin('alpha') - helpers.assert_not_nil(info, "alpha should exist") - helpers.assert_nil( - rawget(info, 'rev'), - "rev was intentionally removed from PluginInfo — consumers must use vim.pack.get" - ) + assert.are.equal('loading', observed_status) + assert.are.equal('loaded', require('zpack').get_plugin('alpha').status) + end) - helpers.cleanup_test_env() - end) + it("PluginInfo does not expose a rev field (install state is vim.pack's)", function() + require('zpack').setup({ + spec = { { 'test/alpha' } }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local info = require('zpack').get_plugin('alpha') + assert.is_not_nil(info, "alpha should exist") + assert.is_nil( + rawget(info, 'rev'), + "rev was intentionally removed from PluginInfo — consumers must use vim.pack.get" + ) + end) - helpers.test("get_plugins surfaces entries whose load callback has not fired as installing", function() - helpers.setup_test_env() - - -- Simulate vim.pack deferring the load callback (e.g. fresh install - -- awaiting confirmation): record the spec but don't invoke opts.load. - vim.pack.add = function(specs, _opts) - table.insert(_G.test_state.vim_pack_calls, specs) - for _, pack_spec in ipairs(specs) do - local name = pack_spec.name or pack_spec.src:match('[^/]+$') - pack_spec.name = name - _G.test_state.registered_pack_specs[name] = pack_spec - end + it("get_plugins surfaces entries whose load callback has not fired as installing", function() + -- Simulate vim.pack deferring the load callback (e.g. fresh install + -- awaiting confirmation): record the spec but don't invoke opts.load. + vim.pack.add = function(specs, _opts) + table.insert(_G.test_state.vim_pack_calls, specs) + for _, pack_spec in ipairs(specs) do + local name = pack_spec.name or pack_spec.src:match('[^/]+$') + pack_spec.name = name + _G.test_state.registered_pack_specs[name] = pack_spec end + end - local ok, err = pcall(function() - require('zpack').setup({ - spec = { { 'test/alpha' } }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - end) - helpers.assert_true(ok, "setup must not throw when load callback is deferred: " .. tostring(err)) - - local info = find_by_name(require('zpack').get_plugins(), 'alpha') - helpers.assert_not_nil(info, "pending-install entries must surface in get_plugins()") - helpers.assert_equal(info.status, 'installing', "deferred-load entry should report status=installing") - helpers.assert_equal(info.path, nil, "installing entries have no resolved path yet") - helpers.assert_equal(info.name, 'alpha', "installing entry should still carry a derivable name") - - -- get_plugin() is symmetric with get_plugins(): name_to_src is populated - -- at resolve_all time from merged_spec.name (or a derived basename), so - -- installing entries are findable by the same name they will carry once - -- the load callback fires. - local looked_up = require('zpack').get_plugin('alpha') - helpers.assert_not_nil(looked_up, "get_plugin must resolve installing entries") - helpers.assert_equal(looked_up.status, 'installing', "looked-up installing entry keeps status=installing") - helpers.assert_equal(looked_up.name, 'alpha', "looked-up installing entry has the same name as in get_plugins()") - - helpers.cleanup_test_env() - end) - - helpers.test("get_plugins does not include zpack itself", function() - helpers.setup_test_env() - _G.test_state.registered_pack_specs['zpack.nvim'] = { - name = 'zpack.nvim', - src = 'https://github.com/zuqini/zpack.nvim', - } - + local ok, err = pcall(function() require('zpack').setup({ spec = { { 'test/alpha' } }, defaults = { confirm = false }, }) helpers.flush_pending() - - local list = require('zpack').get_plugins() - helpers.assert_nil( - find_by_name(list, 'zpack.nvim'), - "zpack.nvim should not be in the API — consumers that need it can query vim.pack.get directly" - ) - helpers.assert_not_nil(find_by_name(list, 'alpha'), "user plugins should still be returned") - - helpers.cleanup_test_env() end) + assert.is_truthy(ok, "setup must not throw when load callback is deferred: " .. tostring(err)) + + local info = find_by_name(require('zpack').get_plugins(), 'alpha') + assert.is_not_nil(info, "pending-install entries must surface in get_plugins()") + assert.are.equal('installing', info.status) + assert.are.equal(nil, info.path) + assert.are.equal('alpha', info.name) + + -- get_plugin() is symmetric with get_plugins(): name_to_src is populated + -- at resolve_all time from merged_spec.name (or a derived basename), so + -- installing entries are findable by the same name they will carry once + -- the load callback fires. + local looked_up = require('zpack').get_plugin('alpha') + assert.is_not_nil(looked_up, "get_plugin must resolve installing entries") + assert.are.equal('installing', looked_up.status) + assert.are.equal('alpha', looked_up.name) + end) - helpers.test("get_plugins() is sorted by name", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/charlie' }, - { 'test/alpha' }, - { 'test/bravo' }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() + it("get_plugins does not include zpack itself", function() + _G.test_state.registered_pack_specs['zpack.nvim'] = { + name = 'zpack.nvim', + src = 'https://github.com/zuqini/zpack.nvim', + } + + require('zpack').setup({ + spec = { { 'test/alpha' } }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local list = require('zpack').get_plugins() + assert.is_nil( + find_by_name(list, 'zpack.nvim'), + "zpack.nvim should not be in the API — consumers that need it can query vim.pack.get directly" + ) + assert.is_not_nil(find_by_name(list, 'alpha'), "user plugins should still be returned") + end) - local list = require('zpack').get_plugins() - local names = {} - for _, info in ipairs(list) do - table.insert(names, info.name) - end - local prev = nil - for _, name in ipairs(names) do - if prev ~= nil then - helpers.assert_true(prev <= name, "get_plugins() must be sorted by name") - end - prev = name + it("get_plugins() is sorted by name", function() + require('zpack').setup({ + spec = { + { 'test/charlie' }, + { 'test/alpha' }, + { 'test/bravo' }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local list = require('zpack').get_plugins() + local names = {} + for _, info in ipairs(list) do + table.insert(names, info.name) + end + local prev = nil + for _, name in ipairs(names) do + if prev ~= nil then + assert.is_truthy(prev <= name, "get_plugins() must be sorted by name") end + prev = name + end + end) - helpers.cleanup_test_env() - end) - - helpers.test("get_plugin tolerates non-string arguments", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { { 'test/alpha' } }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - helpers.assert_nil(require('zpack').get_plugin(nil), "nil should return nil, not throw") - helpers.assert_nil(require('zpack').get_plugin(42), "number should return nil, not throw") - - helpers.cleanup_test_env() - end) - - helpers.test("zpack.api.VERSION is exposed as an integer >= 1", function() - helpers.setup_test_env() - - require('zpack').setup({ spec = {}, defaults = { confirm = false } }) - helpers.flush_pending() - - local version = require('zpack.api').VERSION - helpers.assert_equal(type(version), 'number', "VERSION must be numeric") - helpers.assert_true(version >= 1, "VERSION must be >= 1") - - helpers.cleanup_test_env() - end) + it("get_plugin tolerates non-string arguments", function() + require('zpack').setup({ + spec = { { 'test/alpha' } }, + defaults = { confirm = false }, + }) + helpers.flush_pending() - helpers.test("lazy via event trigger reports lazy=true", function() - helpers.setup_test_env() + assert.is_nil(require('zpack').get_plugin(nil), "nil should return nil, not throw") + assert.is_nil(require('zpack').get_plugin(42), "number should return nil, not throw") + end) - require('zpack').setup({ - spec = { - { 'test/alpha', event = 'BufReadPost' }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() + it("zpack.api.VERSION is exposed as an integer >= 1", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false } }) + helpers.flush_pending() - local info = require('zpack').get_plugin('alpha') - helpers.assert_not_nil(info, "alpha should be listed") - helpers.assert_equal(info.lazy, true, "event-triggered plugin should report lazy=true") + local version = require('zpack.api').VERSION + assert.are.equal('number', type(version)) + assert.is_truthy(version >= 1, "VERSION must be >= 1") + end) - helpers.cleanup_test_env() - end) + it("lazy via event trigger reports lazy=true", function() + require('zpack').setup({ + spec = { + { 'test/alpha', event = 'BufReadPost' }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local info = require('zpack').get_plugin('alpha') + assert.is_not_nil(info, "alpha should be listed") + assert.are.equal(true, info.lazy) + end) - helpers.test("lazy flag does not flip across installing → loaded lifecycle", function() - helpers.setup_test_env() - - -- First setup: defer the load callback so alpha surfaces as installing - -- with an event-triggered spec. The lazy flag must already be true. - vim.pack.add = function(specs, _opts) - table.insert(_G.test_state.vim_pack_calls, specs) - for _, pack_spec in ipairs(specs) do - local name = pack_spec.name or pack_spec.src:match('[^/]+$') - pack_spec.name = name - _G.test_state.registered_pack_specs[name] = pack_spec - end + it("lazy flag does not flip across installing → loaded lifecycle", function() + -- First setup: defer the load callback so alpha surfaces as installing + -- with an event-triggered spec. The lazy flag must already be true. + vim.pack.add = function(specs, _opts) + table.insert(_G.test_state.vim_pack_calls, specs) + for _, pack_spec in ipairs(specs) do + local name = pack_spec.name or pack_spec.src:match('[^/]+$') + pack_spec.name = name + _G.test_state.registered_pack_specs[name] = pack_spec end + end - require('zpack').setup({ - spec = { { 'test/alpha', event = 'BufReadPost' } }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - local installing = require('zpack').get_plugin('alpha') - helpers.assert_not_nil(installing, "installing entry must be findable") - helpers.assert_equal(installing.status, 'installing', "alpha should be installing") - helpers.assert_equal( - installing.lazy, true, - "event-triggered plugin must report lazy=true even before the load callback fires" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("force-loaded cond=false plugin reports loaded, not disabled", function() - helpers.setup_test_env() + require('zpack').setup({ + spec = { { 'test/alpha', event = 'BufReadPost' } }, + defaults = { confirm = false }, + }) + helpers.flush_pending() - require('zpack').setup({ - spec = { - { 'test/alpha', cond = false }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - -- Pre-condition: alpha is registered and reports disabled. - helpers.assert_equal( - require('zpack').get_plugin('alpha').status, 'disabled', - "cond=false plugin starts disabled" - ) + local installing = require('zpack').get_plugin('alpha') + assert.is_not_nil(installing, "installing entry must be findable") + assert.are.equal('installing', installing.status) + assert.are.equal(true, installing.lazy) + end) - -- `:ZPack! load alpha` force-loads past the cond gate (used e.g. when a - -- consumer deliberately wants the plugin loaded despite its cond). - pcall(vim.cmd, 'ZPack! load alpha') + it("force-loaded cond=false plugin reports loaded, not disabled", function() + require('zpack').setup({ + spec = { + { 'test/alpha', cond = false }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() - helpers.assert_equal( - require('zpack').get_plugin('alpha').status, 'loaded', - "force-loaded cond=false plugin must report loaded, not disabled" - ) + -- Pre-condition: alpha is registered and reports disabled. + assert.are.equal('disabled', require('zpack').get_plugin('alpha').status) - helpers.cleanup_test_env() - end) + -- `:ZPack! load alpha` force-loads past the cond gate (used e.g. when a + -- consumer deliberately wants the plugin loaded despite its cond). + pcall(vim.cmd, 'ZPack! load alpha') - helpers.test("cond=false dep pulled in by a live parent reports loaded", function() - helpers.setup_test_env() + assert.are.equal('loaded', require('zpack').get_plugin('alpha').status) + end) - require('zpack').setup({ - spec = { - { - 'test/parent', - dependencies = { { 'test/helper', cond = false } }, - }, + it("cond=false dep pulled in by a live parent reports loaded", function() + require('zpack').setup({ + spec = { + { + 'test/parent', + dependencies = { { 'test/helper', cond = false } }, }, - defaults = { confirm = false }, - }) - helpers.flush_pending() + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + -- Parent is eagerly loaded, which pulls helper in as a required dep. + -- plugin_loader warns that helper has cond=false but loads it anyway, + -- so derive_status must honor load_status over cond_result. + local helper = require('zpack').get_plugin('helper') + assert.is_not_nil(helper, "cond=false dep should still be registered") + assert.are.equal('loaded', helper.status) + end) - -- Parent is eagerly loaded, which pulls helper in as a required dep. - -- plugin_loader warns that helper has cond=false but loads it anyway, - -- so derive_status must honor load_status over cond_result. - local helper = require('zpack').get_plugin('helper') - helpers.assert_not_nil(helper, "cond=false dep should still be registered") - helpers.assert_equal( - helper.status, 'loaded', - "cond=false dep force-loaded by a live parent must report loaded" + it("get_plugin(info.name) round-trips every get_plugins() entry", function() + require('zpack').setup({ + spec = { + { 'test/alpha' }, + { 'test/beta', lazy = true }, + { 'test/gamma', cond = false }, + { 'test/delta', event = 'BufReadPost' }, + }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + local list = require('zpack').get_plugins() + assert.is_truthy(#list >= 4, "all four plugins should be listed") + for _, info in ipairs(list) do + local looked_up = require('zpack').get_plugin(info.name) + assert.is_not_nil( + looked_up, + ("get_plugin(%q) must round-trip get_plugins() entry"):format(info.name) ) - - helpers.cleanup_test_env() - end) - - helpers.test("get_plugin(info.name) round-trips every get_plugins() entry", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/alpha' }, - { 'test/beta', lazy = true }, - { 'test/gamma', cond = false }, - { 'test/delta', event = 'BufReadPost' }, - }, - defaults = { confirm = false }, - }) - helpers.flush_pending() - - local list = require('zpack').get_plugins() - helpers.assert_true(#list >= 4, "all four plugins should be listed") - for _, info in ipairs(list) do - local looked_up = require('zpack').get_plugin(info.name) - helpers.assert_not_nil( - looked_up, - ("get_plugin(%q) must round-trip get_plugins() entry"):format(info.name) - ) - helpers.assert_equal( - looked_up.name, info.name, - "round-tripped name must match the original entry" - ) - helpers.assert_equal( - looked_up.src, info.src, - "round-tripped src must match the original entry" - ) - end - - helpers.cleanup_test_env() - end) + assert.are.equal(info.name, looked_up.name) + assert.are.equal(info.src, looked_up.src) + end end) -end +end) diff --git a/tests/run_all.lua b/tests/run_all.lua deleted file mode 100644 index 289b8b7..0000000 --- a/tests/run_all.lua +++ /dev/null @@ -1,64 +0,0 @@ -local helpers = require('helpers') - --- Clean state before running any tests -package.loaded['zpack.state'] = nil -package.loaded['zpack.init'] = nil - -local test_modules = { - 'setup_test', - 'lazy_cmd_test', - 'lazy_keys_test', - 'lazy_event_test', - 'lazy_ft_test', - 'lifecycle_test', - 'priority_test', - 'conditional_test', - 'plugin_data_test', - 'cmd_name_test', - 'cmd_prefix_test', - 'dispatch_test', - 'version_test', - 'deprecation_test', - 'import_test', - 'zload_test', - 'opts_test', - 'utils_test', - 'merge_test', - 'dependencies_test', - 'module_loader_test', - 'zdelete_test', - 'zclean_test', - 'zupdate_test', - 'zrestore_test', - 'is_single_spec_test', - 'source_plugin_files_test', - 'public_api_test', -} - -print("\n" .. string.rep("=", 60)) -print("Running zpack.nvim Test Suite") -print(string.rep("=", 60)) - -helpers.reset() - -for _, module_name in ipairs(test_modules) do - local success, test_fn = pcall(require, module_name) - if success and type(test_fn) == 'function' then - test_fn() - else - print(string.format("Failed to load test module: %s", module_name)) - if not success then - print(string.format("Error: %s", test_fn)) - end - end -end - -local all_passed = helpers.summary() - -if all_passed then - print("\n✓ All tests passed!") - vim.cmd('qall!') -else - print("\n✗ Some tests failed") - vim.cmd('cquit!') -- Exit with error code 1 -end diff --git a/tests/setup_test.lua b/tests/setup_test.lua index ba70662..9cf4600 100644 --- a/tests/setup_test.lua +++ b/tests/setup_test.lua @@ -1,205 +1,169 @@ local helpers = require('helpers') -return function() - helpers.describe("Setup and Initialization", function() - helpers.test("setup() initializes zpack state", function() - helpers.setup_test_env() - local state = require('zpack.state') +describe("Setup and Initialization", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) - helpers.assert_false(state.is_setup, "State should not be setup initially") + it("setup() initializes zpack state", function() + local state = require('zpack.state') - require('zpack').setup({ spec = {}, defaults = { confirm = false } }) + assert.is_falsy(state.is_setup, "State should not be setup initially") - helpers.assert_true(state.is_setup, "State should be setup after setup()") - helpers.assert_not_nil(state.spec_registry, "Spec registry should exist") - helpers.assert_not_nil(state.lazy_group, "Lazy group should exist") - helpers.assert_not_nil(state.startup_group, "Startup group should exist") + require('zpack').setup({ spec = {}, defaults = { confirm = false } }) - helpers.cleanup_test_env() - end) + assert.is_truthy(state.is_setup, "State should be setup after setup()") + assert.is_not_nil(state.spec_registry, "Spec registry should exist") + assert.is_not_nil(state.lazy_group, "Lazy group should exist") + assert.is_not_nil(state.startup_group, "Startup group should exist") + end) - helpers.test("setup() cannot be called twice", function() - helpers.setup_test_env() - local state = require('zpack.state') + it("setup() cannot be called twice", function() + local state = require('zpack.state') - require('zpack').setup({ spec = {}, defaults = { confirm = false } }) - helpers.assert_true(state.is_setup, "State should be setup after first call") + require('zpack').setup({ spec = {}, defaults = { confirm = false } }) + assert.is_truthy(state.is_setup, "State should be setup after first call") - -- Second call should warn but state should remain setup - require('zpack').setup({ spec = {}, defaults = { confirm = false } }) - helpers.assert_true(state.is_setup, "State should still be setup after second call") + -- Second call should warn but state should remain setup + require('zpack').setup({ spec = {}, defaults = { confirm = false } }) + assert.is_truthy(state.is_setup, "State should still be setup after second call") + end) - helpers.cleanup_test_env() - end) + it("add() shows deprecation error", function() + require('zpack').setup({ spec = {}, defaults = { confirm = false } }) + require('zpack').add({ 'test/plugin' }) - helpers.test("add() shows deprecation error", function() - helpers.setup_test_env() + helpers.flush_pending() - require('zpack').setup({ spec = {}, defaults = { confirm = false } }) - require('zpack').add({ 'test/plugin' }) + local found_deprecation = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find("REMOVED") and notif.msg:find("add") then + found_deprecation = true + break + end + end - helpers.flush_pending() + assert.is_truthy(found_deprecation, "Should show deprecation error for add()") + end) - local found_deprecation = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find("REMOVED") and notif.msg:find("add") then - found_deprecation = true - break - end - end + it("setup() with specs as first argument registers plugins", function() + local state = require('zpack.state') + + require('zpack').setup({ + { 'test/plugin1' }, + { 'test/plugin2' }, + }) - helpers.assert_true(found_deprecation, "Should show deprecation error for add()") + local src1 = 'https://github.com/test/plugin1' + local src2 = 'https://github.com/test/plugin2' + assert.is_not_nil(state.spec_registry[src1], "Plugin 1 should be registered") + assert.is_not_nil(state.spec_registry[src2], "Plugin 2 should be registered") + end) - helpers.cleanup_test_env() - end) + it("setup() with single spec as first argument", function() + local state = require('zpack.state') - helpers.test("setup() with specs as first argument registers plugins", function() - helpers.setup_test_env() - local state = require('zpack.state') + require('zpack').setup({ 'test/plugin' }) + + local src = 'https://github.com/test/plugin' + assert.is_not_nil(state.spec_registry[src], "Single inline spec should be registered") + end) + + it("setup() with spec field registers single plugin", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { { 'test/plugin' } }, + defaults = { confirm = false }, + }) + + local src = 'https://github.com/test/plugin' + assert.is_not_nil(state.spec_registry[src], "Plugin should be registered") + local spec = state.spec_registry[src].merged_spec + assert.are.equal('test/plugin', spec[1]) + end) - require('zpack').setup({ + it("setup() with spec as single spec (not wrapped in list)", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { 'test/plugin', config = function() end }, + defaults = { confirm = false }, + }) + + local src = 'https://github.com/test/plugin' + assert.is_not_nil(state.spec_registry[src], "Single spec should be registered") + end) + + it("setup() with spec field registers multiple plugins", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { { 'test/plugin1' }, { 'test/plugin2' }, - }) + }, + defaults = { confirm = false }, + }) + + local src1 = 'https://github.com/test/plugin1' + local src2 = 'https://github.com/test/plugin2' + assert.is_not_nil(state.spec_registry[src1], "Plugin 1 should be registered") + assert.is_not_nil(state.spec_registry[src2], "Plugin 2 should be registered") + end) - local src1 = 'https://github.com/test/plugin1' - local src2 = 'https://github.com/test/plugin2' - helpers.assert_not_nil(state.spec_registry[src1], "Plugin 1 should be registered") - helpers.assert_not_nil(state.spec_registry[src2], "Plugin 2 should be registered") + it("plugin spec supports src field", function() + local state = require('zpack.state') - helpers.cleanup_test_env() - end) + require('zpack').setup({ + spec = { + { src = 'https://custom.url/plugin.git' }, + }, + defaults = { confirm = false }, + }) - helpers.test("setup() with single spec as first argument", function() - helpers.setup_test_env() - local state = require('zpack.state') + local src = 'https://custom.url/plugin.git' + assert.is_not_nil(state.spec_registry[src], "Plugin with src should be registered") + end) + + it("plugin spec supports url field (lazy.nvim compat)", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { + { url = 'https://custom.url/plugin.git' }, + }, + defaults = { confirm = false }, + }) - require('zpack').setup({ 'test/plugin' }) - - local src = 'https://github.com/test/plugin' - helpers.assert_not_nil(state.spec_registry[src], "Single inline spec should be registered") + local src = 'https://custom.url/plugin.git' + assert.is_not_nil(state.spec_registry[src], "Plugin with url should be registered") + end) + + it("plugin spec supports dir field (lazy.nvim compat)", function() + local state = require('zpack.state') - helpers.cleanup_test_env() - end) + require('zpack').setup({ + spec = { + { dir = '/path/to/local/plugin' }, + }, + defaults = { confirm = false }, + }) - helpers.test("setup() with spec field registers single plugin", function() - helpers.setup_test_env() - local state = require('zpack.state') + local src = '/path/to/local/plugin' + assert.is_not_nil(state.spec_registry[src], "Plugin with dir should be registered") + end) - require('zpack').setup({ - spec = { { 'test/plugin' } }, - defaults = { confirm = false }, - }) + it("dir field expands ~ to home directory", function() + local state = require('zpack.state') - local src = 'https://github.com/test/plugin' - helpers.assert_not_nil(state.spec_registry[src], "Plugin should be registered") - local spec = state.spec_registry[src].merged_spec - helpers.assert_equal(spec[1], 'test/plugin', "Spec should match") - - helpers.cleanup_test_env() - end) + require('zpack').setup({ + spec = { + { dir = '~/projects/my-plugin' }, + }, + defaults = { confirm = false }, + }) - helpers.test("setup() with spec as single spec (not wrapped in list)", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { 'test/plugin', config = function() end }, - defaults = { confirm = false }, - }) - - local src = 'https://github.com/test/plugin' - helpers.assert_not_nil(state.spec_registry[src], "Single spec should be registered") - - helpers.cleanup_test_env() - end) - - helpers.test("setup() with spec field registers multiple plugins", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { 'test/plugin1' }, - { 'test/plugin2' }, - }, - defaults = { confirm = false }, - }) - - local src1 = 'https://github.com/test/plugin1' - local src2 = 'https://github.com/test/plugin2' - helpers.assert_not_nil(state.spec_registry[src1], "Plugin 1 should be registered") - helpers.assert_not_nil(state.spec_registry[src2], "Plugin 2 should be registered") - - helpers.cleanup_test_env() - end) - - helpers.test("plugin spec supports src field", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { src = 'https://custom.url/plugin.git' }, - }, - defaults = { confirm = false }, - }) - - local src = 'https://custom.url/plugin.git' - helpers.assert_not_nil(state.spec_registry[src], "Plugin with src should be registered") - - helpers.cleanup_test_env() - end) - - helpers.test("plugin spec supports url field (lazy.nvim compat)", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { url = 'https://custom.url/plugin.git' }, - }, - defaults = { confirm = false }, - }) - - local src = 'https://custom.url/plugin.git' - helpers.assert_not_nil(state.spec_registry[src], "Plugin with url should be registered") - - helpers.cleanup_test_env() - end) - - helpers.test("plugin spec supports dir field (lazy.nvim compat)", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { dir = '/path/to/local/plugin' }, - }, - defaults = { confirm = false }, - }) - - local src = '/path/to/local/plugin' - helpers.assert_not_nil(state.spec_registry[src], "Plugin with dir should be registered") - - helpers.cleanup_test_env() - end) - - helpers.test("dir field expands ~ to home directory", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { dir = '~/projects/my-plugin' }, - }, - defaults = { confirm = false }, - }) - - local expected_src = vim.fn.expand('~/projects/my-plugin') - helpers.assert_not_nil(state.spec_registry[expected_src], "dir should expand ~ to home directory") - - helpers.cleanup_test_env() - end) + local expected_src = vim.fn.expand('~/projects/my-plugin') + assert.is_not_nil(state.spec_registry[expected_src], "dir should expand ~ to home directory") end) -end +end) diff --git a/tests/source_plugin_files_test.lua b/tests/source_plugin_files_test.lua index 5667373..eb2ac2b 100644 --- a/tests/source_plugin_files_test.lua +++ b/tests/source_plugin_files_test.lua @@ -1,179 +1,158 @@ local helpers = require('helpers') -return function() - helpers.describe("source_after_plugin_files", function() - helpers.test("sources lua files from after/plugin/ directory", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local tmpdir = vim.fn.tempname() - local after_dir = tmpdir .. "/after/plugin" - vim.fn.mkdir(after_dir, "p") - - local test_file = after_dir .. "/test_after.lua" - local f = io.open(test_file, "w") - f:write("_G._test_source_after_ran = true\n") - f:close() - - _G._test_source_after_ran = nil - utils.source_after_plugin_files(tmpdir) - - helpers.assert_true(_G._test_source_after_ran == true, - "after/plugin/ lua file should be sourced") - - _G._test_source_after_ran = nil - vim.fn.delete(tmpdir, "rf") - helpers.cleanup_test_env() - end) - - helpers.test("sources nested files from after/plugin/ subdirectories", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local tmpdir = vim.fn.tempname() - local subdir = tmpdir .. "/after/plugin/subdir" - vim.fn.mkdir(subdir, "p") - - local test_file = subdir .. "/foo.lua" - local f = io.open(test_file, "w") - f:write("_G._test_nested_ran = true\n") - f:close() - - _G._test_nested_ran = nil - utils.source_after_plugin_files(tmpdir) - - helpers.assert_true(_G._test_nested_ran == true, - "after/plugin/subdir/foo.lua should be sourced") - - _G._test_nested_ran = nil - vim.fn.delete(tmpdir, "rf") - helpers.cleanup_test_env() - end) - - helpers.test("sources deeply nested files", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local tmpdir = vim.fn.tempname() - local deep_dir = tmpdir .. "/after/plugin/a/b/c" - vim.fn.mkdir(deep_dir, "p") - - local test_file = deep_dir .. "/deep.lua" - local f = io.open(test_file, "w") - f:write("_G._test_deep_ran = true\n") - f:close() - - _G._test_deep_ran = nil - utils.source_after_plugin_files(tmpdir) - - helpers.assert_true(_G._test_deep_ran == true, - "after/plugin/a/b/c/deep.lua should be sourced") - - _G._test_deep_ran = nil - vim.fn.delete(tmpdir, "rf") - helpers.cleanup_test_env() - end) - - helpers.test("sources both top-level and nested files", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local tmpdir = vim.fn.tempname() - local after_dir = tmpdir .. "/after/plugin" - local sub_dir = after_dir .. "/sub" - vim.fn.mkdir(sub_dir, "p") - - local f = io.open(after_dir .. "/top.lua", "w") - f:write("_G._test_top_ran = true\n") - f:close() - - f = io.open(sub_dir .. "/deep.lua", "w") - f:write("_G._test_sub_deep_ran = true\n") - f:close() - - _G._test_top_ran = nil - _G._test_sub_deep_ran = nil - utils.source_after_plugin_files(tmpdir) - - helpers.assert_true(_G._test_top_ran == true, - "after/plugin/top.lua should be sourced") - helpers.assert_true(_G._test_sub_deep_ran == true, - "after/plugin/sub/deep.lua should be sourced") - - _G._test_top_ran = nil - _G._test_sub_deep_ran = nil - vim.fn.delete(tmpdir, "rf") - helpers.cleanup_test_env() - end) - - helpers.test("does not source same path twice", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local tmpdir = vim.fn.tempname() - local after_dir = tmpdir .. "/after/plugin" - vim.fn.mkdir(after_dir, "p") - - local test_file = after_dir .. "/counter.lua" - local f = io.open(test_file, "w") - f:write("_G._test_source_count = (_G._test_source_count or 0) + 1\n") - f:close() - - _G._test_source_count = nil - utils.source_after_plugin_files(tmpdir) - utils.source_after_plugin_files(tmpdir) - - helpers.assert_equal(_G._test_source_count, 1, - "file should only be sourced once even with multiple calls") - - _G._test_source_count = nil - vim.fn.delete(tmpdir, "rf") - helpers.cleanup_test_env() - end) - - helpers.test("handles missing directories gracefully", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local tmpdir = vim.fn.tempname() - vim.fn.mkdir(tmpdir, "p") - - local ok, err = pcall(utils.source_after_plugin_files, tmpdir) - helpers.assert_true(ok, "should not error on missing directories: " .. tostring(err)) - - vim.fn.delete(tmpdir, "rf") - helpers.cleanup_test_env() - end) - - helpers.test("skips non-lua non-vim files in nested dirs", function() - helpers.setup_test_env() - - local utils = require('zpack.utils') - local tmpdir = vim.fn.tempname() - local sub_dir = tmpdir .. "/after/plugin/sub" - vim.fn.mkdir(sub_dir, "p") - - local txt_file = sub_dir .. "/readme.txt" - local f = io.open(txt_file, "w") - f:write("_G._test_txt_sourced = true\n") - f:close() - - local lua_file = sub_dir .. "/real.lua" - f = io.open(lua_file, "w") - f:write("_G._test_lua_sourced = true\n") - f:close() - - _G._test_txt_sourced = nil - _G._test_lua_sourced = nil - utils.source_after_plugin_files(tmpdir) - - helpers.assert_nil(_G._test_txt_sourced, ".txt file should not be sourced") - helpers.assert_true(_G._test_lua_sourced == true, ".lua file should be sourced") - - _G._test_txt_sourced = nil - _G._test_lua_sourced = nil - vim.fn.delete(tmpdir, "rf") - helpers.cleanup_test_env() - end) +describe("source_after_plugin_files", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("sources lua files from after/plugin/ directory", function() + local utils = require('zpack.utils') + local tmpdir = vim.fn.tempname() + local after_dir = tmpdir .. "/after/plugin" + vim.fn.mkdir(after_dir, "p") + + local test_file = after_dir .. "/test_after.lua" + local f = io.open(test_file, "w") + f:write("_G._test_source_after_ran = true\n") + f:close() + + _G._test_source_after_ran = nil + utils.source_after_plugin_files(tmpdir) + + assert.is_truthy(_G._test_source_after_ran == true, + "after/plugin/ lua file should be sourced") + + _G._test_source_after_ran = nil + vim.fn.delete(tmpdir, "rf") + end) + + it("sources nested files from after/plugin/ subdirectories", function() + local utils = require('zpack.utils') + local tmpdir = vim.fn.tempname() + local subdir = tmpdir .. "/after/plugin/subdir" + vim.fn.mkdir(subdir, "p") + + local test_file = subdir .. "/foo.lua" + local f = io.open(test_file, "w") + f:write("_G._test_nested_ran = true\n") + f:close() + + _G._test_nested_ran = nil + utils.source_after_plugin_files(tmpdir) + + assert.is_truthy(_G._test_nested_ran == true, + "after/plugin/subdir/foo.lua should be sourced") + + _G._test_nested_ran = nil + vim.fn.delete(tmpdir, "rf") + end) + + it("sources deeply nested files", function() + local utils = require('zpack.utils') + local tmpdir = vim.fn.tempname() + local deep_dir = tmpdir .. "/after/plugin/a/b/c" + vim.fn.mkdir(deep_dir, "p") + + local test_file = deep_dir .. "/deep.lua" + local f = io.open(test_file, "w") + f:write("_G._test_deep_ran = true\n") + f:close() + + _G._test_deep_ran = nil + utils.source_after_plugin_files(tmpdir) + + assert.is_truthy(_G._test_deep_ran == true, + "after/plugin/a/b/c/deep.lua should be sourced") + + _G._test_deep_ran = nil + vim.fn.delete(tmpdir, "rf") + end) + + it("sources both top-level and nested files", function() + local utils = require('zpack.utils') + local tmpdir = vim.fn.tempname() + local after_dir = tmpdir .. "/after/plugin" + local sub_dir = after_dir .. "/sub" + vim.fn.mkdir(sub_dir, "p") + + local f = io.open(after_dir .. "/top.lua", "w") + f:write("_G._test_top_ran = true\n") + f:close() + + f = io.open(sub_dir .. "/deep.lua", "w") + f:write("_G._test_sub_deep_ran = true\n") + f:close() + + _G._test_top_ran = nil + _G._test_sub_deep_ran = nil + utils.source_after_plugin_files(tmpdir) + + assert.is_truthy(_G._test_top_ran == true, + "after/plugin/top.lua should be sourced") + assert.is_truthy(_G._test_sub_deep_ran == true, + "after/plugin/sub/deep.lua should be sourced") + + _G._test_top_ran = nil + _G._test_sub_deep_ran = nil + vim.fn.delete(tmpdir, "rf") + end) + + it("does not source same path twice", function() + local utils = require('zpack.utils') + local tmpdir = vim.fn.tempname() + local after_dir = tmpdir .. "/after/plugin" + vim.fn.mkdir(after_dir, "p") + + local test_file = after_dir .. "/counter.lua" + local f = io.open(test_file, "w") + f:write("_G._test_source_count = (_G._test_source_count or 0) + 1\n") + f:close() + + _G._test_source_count = nil + utils.source_after_plugin_files(tmpdir) + utils.source_after_plugin_files(tmpdir) + + assert.are.equal(1, _G._test_source_count) + + _G._test_source_count = nil + vim.fn.delete(tmpdir, "rf") + end) + + it("handles missing directories gracefully", function() + local utils = require('zpack.utils') + local tmpdir = vim.fn.tempname() + vim.fn.mkdir(tmpdir, "p") + + local ok, err = pcall(utils.source_after_plugin_files, tmpdir) + assert.is_truthy(ok, "should not error on missing directories: " .. tostring(err)) + + vim.fn.delete(tmpdir, "rf") + end) + + it("skips non-lua non-vim files in nested dirs", function() + local utils = require('zpack.utils') + local tmpdir = vim.fn.tempname() + local sub_dir = tmpdir .. "/after/plugin/sub" + vim.fn.mkdir(sub_dir, "p") + + local txt_file = sub_dir .. "/readme.txt" + local f = io.open(txt_file, "w") + f:write("_G._test_txt_sourced = true\n") + f:close() + + local lua_file = sub_dir .. "/real.lua" + f = io.open(lua_file, "w") + f:write("_G._test_lua_sourced = true\n") + f:close() + + _G._test_txt_sourced = nil + _G._test_lua_sourced = nil + utils.source_after_plugin_files(tmpdir) + + assert.is_nil(_G._test_txt_sourced, ".txt file should not be sourced") + assert.is_truthy(_G._test_lua_sourced == true, ".lua file should be sourced") + + _G._test_txt_sourced = nil + _G._test_lua_sourced = nil + vim.fn.delete(tmpdir, "rf") end) -end +end) diff --git a/tests/utils_test.lua b/tests/utils_test.lua index c59c198..6d83dd3 100644 --- a/tests/utils_test.lua +++ b/tests/utils_test.lua @@ -1,191 +1,163 @@ local helpers = require('helpers') -return function() - helpers.describe("lsdir utility", function() - helpers.test("lsdir returns entries for existing directory", function() - helpers.setup_test_env() +describe("lsdir utility", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) - local utils = require('zpack.utils') - local entries = utils.lsdir(vim.fn.stdpath('config') .. '/lua') + it("lsdir returns entries for existing directory", function() + local utils = require('zpack.utils') + local entries = utils.lsdir(vim.fn.stdpath('config') .. '/lua') - helpers.assert_equal(type(entries), 'table') + assert.are.equal('table', type(entries)) + end) - helpers.cleanup_test_env() - end) + it("lsdir returns empty table for non-existent directory", function() + local utils = require('zpack.utils') + local entries = utils.lsdir('/non/existent/path/that/does/not/exist') - helpers.test("lsdir returns empty table for non-existent directory", function() - helpers.setup_test_env() + assert.are.equal('table', type(entries)) + assert.are.equal(0, #entries) + end) - local utils = require('zpack.utils') - local entries = utils.lsdir('/non/existent/path/that/does/not/exist') + it("lsdir caches results", function() + local utils = require('zpack.utils') + local test_path = '/test/cache/path' - helpers.assert_equal(type(entries), 'table') - helpers.assert_equal(#entries, 0) + local call_count = 0 + local original_fs_scandir = vim.uv.fs_scandir + vim.uv.fs_scandir = function(path) + if path == test_path then + call_count = call_count + 1 + return nil + end + return original_fs_scandir(path) + end - helpers.cleanup_test_env() - end) + utils.lsdir(test_path) + utils.lsdir(test_path) + utils.lsdir(test_path) - helpers.test("lsdir caches results", function() - helpers.setup_test_env() + assert.are.equal(1, call_count) + + vim.uv.fs_scandir = original_fs_scandir + end) - local utils = require('zpack.utils') - local test_path = '/test/cache/path' + it("lsdir entries have name and type", function() + local utils = require('zpack.utils') + local original_fs_scandir = vim.uv.fs_scandir + local original_fs_scandir_next = vim.uv.fs_scandir_next - local call_count = 0 - local original_fs_scandir = vim.uv.fs_scandir - vim.uv.fs_scandir = function(path) - if path == test_path then - call_count = call_count + 1 - return nil + local call_idx = 0 + vim.uv.fs_scandir = function(path) + if path == '/mock/dir' then + return 'mock_handle' + end + return original_fs_scandir(path) + end + vim.uv.fs_scandir_next = function(handle) + if handle == 'mock_handle' then + call_idx = call_idx + 1 + if call_idx == 1 then + return 'file.lua', 'file' + elseif call_idx == 2 then + return 'subdir', 'directory' end - return original_fs_scandir(path) + return nil end + return original_fs_scandir_next(handle) + end - utils.lsdir(test_path) - utils.lsdir(test_path) - utils.lsdir(test_path) - - helpers.assert_equal(call_count, 1, "fs_scandir should only be called once due to caching") + local entries = utils.lsdir('/mock/dir') - vim.uv.fs_scandir = original_fs_scandir - helpers.cleanup_test_env() - end) + assert.are.equal(2, #entries) + assert.are.equal('file.lua', entries[1].name) + assert.are.equal('file', entries[1].type) + assert.are.equal('subdir', entries[2].name) + assert.are.equal('directory', entries[2].type) - helpers.test("lsdir entries have name and type", function() - helpers.setup_test_env() + vim.uv.fs_scandir = original_fs_scandir + vim.uv.fs_scandir_next = original_fs_scandir_next + end) - local utils = require('zpack.utils') - local original_fs_scandir = vim.uv.fs_scandir - local original_fs_scandir_next = vim.uv.fs_scandir_next + it("reset_lsdir_cache clears cache", function() + local utils = require('zpack.utils') + local test_path = '/test/reset/path' - local call_idx = 0 - vim.uv.fs_scandir = function(path) - if path == '/mock/dir' then - return 'mock_handle' - end - return original_fs_scandir(path) - end - vim.uv.fs_scandir_next = function(handle) - if handle == 'mock_handle' then - call_idx = call_idx + 1 - if call_idx == 1 then - return 'file.lua', 'file' - elseif call_idx == 2 then - return 'subdir', 'directory' - end - return nil - end - return original_fs_scandir_next(handle) + local call_count = 0 + local original_fs_scandir = vim.uv.fs_scandir + vim.uv.fs_scandir = function(path) + if path == test_path then + call_count = call_count + 1 + return nil end + return original_fs_scandir(path) + end - local entries = utils.lsdir('/mock/dir') + utils.lsdir(test_path) + assert.are.equal(1, call_count) - helpers.assert_equal(#entries, 2) - helpers.assert_equal(entries[1].name, 'file.lua') - helpers.assert_equal(entries[1].type, 'file') - helpers.assert_equal(entries[2].name, 'subdir') - helpers.assert_equal(entries[2].type, 'directory') + utils.reset_lsdir_cache() + utils.lsdir(test_path) + assert.are.equal(2, call_count) - vim.uv.fs_scandir = original_fs_scandir - vim.uv.fs_scandir_next = original_fs_scandir_next - helpers.cleanup_test_env() - end) + vim.uv.fs_scandir = original_fs_scandir + end) +end) + +describe("is_semver_like utility", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("detects wildcard patterns", function() + local utils = require('zpack.utils') + + assert.are.equal(true, utils.is_semver_like('1.*')) + assert.are.equal(true, utils.is_semver_like('*')) + assert.are.equal(true, utils.is_semver_like('1.2.*')) + assert.are.equal(true, utils.is_semver_like('1.2.x')) + assert.are.equal(true, utils.is_semver_like('1.x')) + assert.are.equal(true, utils.is_semver_like('1.X')) + assert.are.equal(true, utils.is_semver_like('1.2.X')) + assert.are.equal(true, utils.is_semver_like('1.2.y')) + assert.are.equal(true, utils.is_semver_like('1.a.b')) + end) - helpers.test("reset_lsdir_cache clears cache", function() - helpers.setup_test_env() + it("detects range operators", function() + local utils = require('zpack.utils') - local utils = require('zpack.utils') - local test_path = '/test/reset/path' + assert.are.equal(true, utils.is_semver_like('>=1.0.0')) + assert.are.equal(true, utils.is_semver_like('<=2.0.0')) + assert.are.equal(true, utils.is_semver_like('>1.0')) + assert.are.equal(true, utils.is_semver_like('<2.0')) + assert.are.equal(true, utils.is_semver_like('^1.0.0')) + assert.are.equal(true, utils.is_semver_like('~1.0.0')) + assert.are.equal(true, utils.is_semver_like('foo=bar')) + end) - local call_count = 0 - local original_fs_scandir = vim.uv.fs_scandir - vim.uv.fs_scandir = function(path) - if path == test_path then - call_count = call_count + 1 - return nil - end - return original_fs_scandir(path) - end + it("detects bare semver patterns", function() + local utils = require('zpack.utils') - utils.lsdir(test_path) - helpers.assert_equal(call_count, 1) + assert.are.equal(true, utils.is_semver_like('1.0.0')) + assert.are.equal(true, utils.is_semver_like('1.2')) + assert.are.equal(true, utils.is_semver_like('1.2.3.4')) + end) - utils.reset_lsdir_cache() - utils.lsdir(test_path) - helpers.assert_equal(call_count, 2, "fs_scandir should be called again after cache reset") + it("returns false for branch/tag names", function() + local utils = require('zpack.utils') - vim.uv.fs_scandir = original_fs_scandir - helpers.cleanup_test_env() - end) + assert.are.equal(false, utils.is_semver_like('main')) + assert.are.equal(false, utils.is_semver_like('master')) + assert.are.equal(false, utils.is_semver_like('v1.0.0')) + assert.are.equal(false, utils.is_semver_like('v1.x')) + assert.are.equal(false, utils.is_semver_like('release-1.0')) + assert.are.equal(false, utils.is_semver_like('feature/foo')) end) - helpers.describe("is_semver_like utility", function() - helpers.test("detects wildcard patterns", function() - helpers.setup_test_env() - local utils = require('zpack.utils') - - helpers.assert_equal(utils.is_semver_like('1.*'), true) - helpers.assert_equal(utils.is_semver_like('*'), true) - helpers.assert_equal(utils.is_semver_like('1.2.*'), true) - helpers.assert_equal(utils.is_semver_like('1.2.x'), true) - helpers.assert_equal(utils.is_semver_like('1.x'), true) - helpers.assert_equal(utils.is_semver_like('1.X'), true) - helpers.assert_equal(utils.is_semver_like('1.2.X'), true) - helpers.assert_equal(utils.is_semver_like('1.2.y'), true) - helpers.assert_equal(utils.is_semver_like('1.a.b'), true) - - helpers.cleanup_test_env() - end) - - helpers.test("detects range operators", function() - helpers.setup_test_env() - local utils = require('zpack.utils') - - helpers.assert_equal(utils.is_semver_like('>=1.0.0'), true) - helpers.assert_equal(utils.is_semver_like('<=2.0.0'), true) - helpers.assert_equal(utils.is_semver_like('>1.0'), true) - helpers.assert_equal(utils.is_semver_like('<2.0'), true) - helpers.assert_equal(utils.is_semver_like('^1.0.0'), true) - helpers.assert_equal(utils.is_semver_like('~1.0.0'), true) - helpers.assert_equal(utils.is_semver_like('foo=bar'), true) - - helpers.cleanup_test_env() - end) - - helpers.test("detects bare semver patterns", function() - helpers.setup_test_env() - local utils = require('zpack.utils') - - helpers.assert_equal(utils.is_semver_like('1.0.0'), true) - helpers.assert_equal(utils.is_semver_like('1.2'), true) - helpers.assert_equal(utils.is_semver_like('1.2.3.4'), true) - - helpers.cleanup_test_env() - end) - - helpers.test("returns false for branch/tag names", function() - helpers.setup_test_env() - local utils = require('zpack.utils') - - helpers.assert_equal(utils.is_semver_like('main'), false) - helpers.assert_equal(utils.is_semver_like('master'), false) - helpers.assert_equal(utils.is_semver_like('v1.0.0'), false) - helpers.assert_equal(utils.is_semver_like('v1.x'), false) - helpers.assert_equal(utils.is_semver_like('release-1.0'), false) - helpers.assert_equal(utils.is_semver_like('feature/foo'), false) - - helpers.cleanup_test_env() - end) - - helpers.test("returns false for non-strings", function() - helpers.setup_test_env() - local utils = require('zpack.utils') - - helpers.assert_equal(utils.is_semver_like(nil), false) - helpers.assert_equal(utils.is_semver_like(123), false) - helpers.assert_equal(utils.is_semver_like({}), false) - - helpers.cleanup_test_env() - end) + it("returns false for non-strings", function() + local utils = require('zpack.utils') + + assert.are.equal(false, utils.is_semver_like(nil)) + assert.are.equal(false, utils.is_semver_like(123)) + assert.are.equal(false, utils.is_semver_like({})) end) -end +end) diff --git a/tests/validate_test.lua b/tests/validate_test.lua new file mode 100644 index 0000000..22ad844 --- /dev/null +++ b/tests/validate_test.lua @@ -0,0 +1,204 @@ +---@diagnostic disable: duplicate-set-field +local helpers = require('helpers') + +---Find a notification whose message contains every needle, at an optional level. +local function find_notification(needles, level) + for _, notif in ipairs(_G.test_state.notifications) do + local matched = true + for _, needle in ipairs(needles) do + if not notif.msg:find(needle, 1, true) then + matched = false + break + end + end + if matched and (level == nil or notif.level == level) then + return notif + end + end + return nil +end + +describe("Config Validation", function() + it("validate_config accepts a valid options table", function() + local validate = require('zpack.validate') + local errors = validate.validate_config({ + cmd_name = 'ZPack', + spec = {}, + defaults = { confirm = false, cond = function() return true end }, + performance = { vim_loader = true }, + profiling = { loader = false, require = false }, + }) + assert.are.equal(0, #errors) + end) + + it("validate_config rejects a non-table opts", function() + local validate = require('zpack.validate') + local errors = validate.validate_config('nope') + assert.are.equal(1, #errors) + assert.is_truthy(errors[1]:find('expected table', 1, true) ~= nil, + "error should mention expected table") + end) + + it("validate_config flags a non-table defaults section", function() + local validate = require('zpack.validate') + local errors = validate.validate_config({ defaults = 'oops' }) + assert.are.equal(1, #errors) + assert.is_truthy(errors[1]:find('defaults:', 1, true) ~= nil, + "error should name the defaults field") + end) + + it("validate_config flags a wrong-typed nested field", function() + local validate = require('zpack.validate') + local errors = validate.validate_config({ defaults = { confirm = 'yes' } }) + assert.are.equal(1, #errors) + assert.is_truthy(errors[1]:find('defaults.confirm', 1, true) ~= nil, + "error should name defaults.confirm with its field path") + assert.is_truthy(errors[1]:find('boolean', 1, true) ~= nil, + "error should state the expected type") + end) + + it("validate_config flags a non-string legacy plugins_dir", function() + local validate = require('zpack.validate') + local errors = validate.validate_config({ plugins_dir = 123 }) + assert.are.equal(1, #errors) + assert.is_truthy(errors[1]:find('plugins_dir', 1, true) ~= nil, + "error should name plugins_dir") + end) + + it("validate_config accepts a merged config (no spec/legacy fields)", function() + local validate = require('zpack.validate') + -- Mirrors what :checkhealth passes: a fully merged zpack.Config. + local errors = validate.validate_config({ + cmd_name = 'ZPack', + defaults = { confirm = true }, + performance = { vim_loader = true }, + profiling = { loader = false, require = false }, + }) + assert.are.equal(0, #errors) + end) +end) + +describe("Spec Validation", function() + it("validate_spec accepts a valid spec", function() + local validate = require('zpack.validate') + local errors = validate.validate_spec({ + 'user/plugin', + lazy = true, + priority = 100, + event = 'BufRead', + cmd = { 'Foo', 'Bar' }, + config = true, + opts = {}, + enabled = function() return true end, + }) + assert.are.equal(0, #errors) + end) + + it("validate_spec flags a non-boolean lazy", function() + local validate = require('zpack.validate') + local errors = validate.validate_spec({ 'user/plugin', lazy = 'yes' }) + assert.are.equal(1, #errors) + assert.is_truthy(errors[1]:find('lazy', 1, true) ~= nil, "error should name lazy") + assert.is_truthy(errors[1]:find('boolean', 1, true) ~= nil, + "error should state expected boolean") + end) + + it("validate_spec flags a non-number priority", function() + local validate = require('zpack.validate') + local errors = validate.validate_spec({ 'user/plugin', priority = 'high' }) + assert.are.equal(1, #errors) + assert.is_truthy(errors[1]:find('priority', 1, true) ~= nil, "error should name priority") + end) + + it("validate_spec flags a wrong-typed [1] source", function() + local validate = require('zpack.validate') + local errors = validate.validate_spec({ 123 }) + assert.are.equal(1, #errors) + assert.is_truthy(errors[1]:find('[1]', 1, true) ~= nil, "error should name the [1] field") + end) + + it("validate_spec rejects a non-table spec", function() + local validate = require('zpack.validate') + local errors = validate.validate_spec('user/plugin') + assert.are.equal(1, #errors) + assert.is_truthy(errors[1]:find('expected spec table', 1, true) ~= nil, + "error should mention expected spec table") + end) + + it("validate_spec flags a spec with no plugin source", function() + local validate = require('zpack.validate') + local errors = validate.validate_spec({ event = 'BufRead' }) + assert.are.equal(1, #errors) + assert.is_truthy(errors[1]:find('no plugin source', 1, true) ~= nil, + "error should mention the missing source") + end) + + it("validate_spec accepts an import spec with no source", function() + local validate = require('zpack.validate') + local errors = validate.validate_spec({ import = 'plugins' }) + assert.are.equal(0, #errors) + end) +end) + +describe("Validation wired into setup()", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("invalid setup options emit an error notification but do not abort", function() + require('zpack').setup({ spec = {}, defaults = { confirm = 'yes' } }) + helpers.flush_pending() + + local notif = find_notification({ 'invalid options', 'defaults.confirm' }, vim.log.levels.ERROR) + assert.is_not_nil(notif, "an error notification naming defaults.confirm should be emitted") + + local cmds = vim.api.nvim_get_commands({}) + assert.is_not_nil(cmds['ZPack'], ":ZPack should still register — validation is advisory") + end) + + it("a non-table section is reported and ignored without crashing", function() + local ok = pcall(require('zpack').setup, { spec = {}, defaults = 'oops' }) + assert.is_truthy(ok, "setup must not crash on a non-table section") + + helpers.flush_pending() + + local notif = find_notification({ 'invalid options', 'defaults:' }, vim.log.levels.ERROR) + assert.is_not_nil(notif, "the bad defaults section should be reported") + + local cmds = vim.api.nvim_get_commands({}) + assert.is_not_nil(cmds['ZPack'], ":ZPack should still register") + end) + + it("a malformed spec emits a warning naming the field", function() + require('zpack').setup({ spec = { { 'test/plugin', lazy = 'yes' } } }) + helpers.flush_pending() + + local notif = find_notification({ 'invalid spec', 'test/plugin', 'lazy' }, vim.log.levels.WARN) + assert.is_not_nil(notif, "a warning naming the bad spec field should be emitted") + end) + + it("a sourceless spec is reported and does not abort setup()", function() + local ok = pcall(require('zpack').setup, { spec = { { event = 'BufRead' } } }) + assert.is_truthy(ok, "setup must not abort on a sourceless spec") + + helpers.flush_pending() + + local notif = find_notification({ 'invalid spec', 'no plugin source' }, vim.log.levels.WARN) + assert.is_not_nil(notif, "the sourceless spec should be reported") + + local cmds = vim.api.nvim_get_commands({}) + assert.is_not_nil(cmds['ZPack'], ":ZPack should still register") + end) + + it("a valid config emits no validation notification", function() + require('zpack').setup({ + spec = { { 'test/plugin' } }, + defaults = { confirm = false }, + }) + helpers.flush_pending() + + assert.is_nil(find_notification({ 'invalid options' }), + "no invalid-options notification for a valid config") + assert.is_nil(find_notification({ 'invalid spec' }), + "no invalid-spec notification for a valid spec") + end) +end) diff --git a/tests/version_test.lua b/tests/version_test.lua index 479acc2..bf13610 100644 --- a/tests/version_test.lua +++ b/tests/version_test.lua @@ -1,342 +1,294 @@ ---@diagnostic disable: duplicate-set-field local helpers = require('helpers') -return function() - helpers.describe("Version Normalization", function() - helpers.test("version field takes priority over other version fields", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/plugin', - version = 'main', - branch = 'develop', - tag = 'v1.0.0', - }, +describe("Version Normalization", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("version field takes priority over other version fields", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + version = 'main', + branch = 'develop', + tag = 'v1.0.0', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - local vim_pack_call = _G.test_state.vim_pack_calls[1] - helpers.assert_not_nil(vim_pack_call, "vim.pack.add should have been called") - helpers.assert_equal(vim_pack_call[1].version, 'main', "version field should take priority") - - helpers.cleanup_test_env() - end) - - helpers.test("sem_version field is wrapped with vim.version.range()", function() - helpers.setup_test_env() + local vim_pack_call = _G.test_state.vim_pack_calls[1] + assert.is_not_nil(vim_pack_call, "vim.pack.add should have been called") + assert.are.equal('main', vim_pack_call[1].version) + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - sem_version = '^6', - }, + it("sem_version field is wrapped with vim.version.range()", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + sem_version = '^6', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - local vim_pack_call = _G.test_state.vim_pack_calls[1] - helpers.assert_not_nil(vim_pack_call, "vim.pack.add should have been called") + local vim_pack_call = _G.test_state.vim_pack_calls[1] + assert.is_not_nil(vim_pack_call, "vim.pack.add should have been called") - local version = vim_pack_call[1].version - helpers.assert_not_nil(version, "version should be set") - helpers.assert_equal(type(version), 'table', "sem_version should be converted to vim.VersionRange") - helpers.assert_not_nil(version.from, "vim.VersionRange should have 'from' field") - - helpers.cleanup_test_env() - end) - - helpers.test("branch field maps to version", function() - helpers.setup_test_env() + local version = vim_pack_call[1].version + assert.is_not_nil(version, "version should be set") + assert.are.equal('table', type(version)) + assert.is_not_nil(version.from, "vim.VersionRange should have 'from' field") + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - branch = 'develop', - }, + it("branch field maps to version", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + branch = 'develop', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - local vim_pack_call = _G.test_state.vim_pack_calls[1] - helpers.assert_not_nil(vim_pack_call, "vim.pack.add should have been called") - helpers.assert_equal(vim_pack_call[1].version, 'develop', "branch should map to version") - - helpers.cleanup_test_env() - end) - - helpers.test("tag field maps to version", function() - helpers.setup_test_env() + local vim_pack_call = _G.test_state.vim_pack_calls[1] + assert.is_not_nil(vim_pack_call, "vim.pack.add should have been called") + assert.are.equal('develop', vim_pack_call[1].version) + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - tag = 'v1.0.0', - }, + it("tag field maps to version", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + tag = 'v1.0.0', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - local vim_pack_call = _G.test_state.vim_pack_calls[1] - helpers.assert_not_nil(vim_pack_call, "vim.pack.add should have been called") - helpers.assert_equal(vim_pack_call[1].version, 'v1.0.0', "tag should map to version") + local vim_pack_call = _G.test_state.vim_pack_calls[1] + assert.is_not_nil(vim_pack_call, "vim.pack.add should have been called") + assert.are.equal('v1.0.0', vim_pack_call[1].version) + end) - helpers.cleanup_test_env() - end) + it("commit field maps to version", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + commit = 'abc123def', + }, + }, + defaults = { confirm = false }, + }) - helpers.test("commit field maps to version", function() - helpers.setup_test_env() + local vim_pack_call = _G.test_state.vim_pack_calls[1] + assert.is_not_nil(vim_pack_call, "vim.pack.add should have been called") + assert.are.equal('abc123def', vim_pack_call[1].version) + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - commit = 'abc123def', - }, + it("sem_version takes priority over branch/tag/commit", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + sem_version = '^1.0.0', + branch = 'main', + tag = 'v1.0.0', + commit = 'abc123', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - local vim_pack_call = _G.test_state.vim_pack_calls[1] - helpers.assert_not_nil(vim_pack_call, "vim.pack.add should have been called") - helpers.assert_equal(vim_pack_call[1].version, 'abc123def', "commit should map to version") + local vim_pack_call = _G.test_state.vim_pack_calls[1] + assert.is_not_nil(vim_pack_call, "vim.pack.add should have been called") - helpers.cleanup_test_env() - end) - - helpers.test("sem_version takes priority over branch/tag/commit", function() - helpers.setup_test_env() + local version = vim_pack_call[1].version + assert.are.equal('table', type(version)) + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - sem_version = '^1.0.0', - branch = 'main', - tag = 'v1.0.0', - commit = 'abc123', - }, + it("branch takes priority over tag and commit", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + branch = 'develop', + tag = 'v1.0.0', + commit = 'abc123', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - local vim_pack_call = _G.test_state.vim_pack_calls[1] - helpers.assert_not_nil(vim_pack_call, "vim.pack.add should have been called") + local vim_pack_call = _G.test_state.vim_pack_calls[1] + assert.is_not_nil(vim_pack_call, "vim.pack.add should have been called") + assert.are.equal('develop', vim_pack_call[1].version) + end) - local version = vim_pack_call[1].version - helpers.assert_equal(type(version), 'table', "sem_version should be used and converted to vim.VersionRange") + it("tag takes priority over commit", function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + tag = 'v2.0.0', + commit = 'abc123', + }, + }, + defaults = { confirm = false }, + }) - helpers.cleanup_test_env() - end) + local vim_pack_call = _G.test_state.vim_pack_calls[1] + assert.is_not_nil(vim_pack_call, "vim.pack.add should have been called") + assert.are.equal('v2.0.0', vim_pack_call[1].version) + end) - helpers.test("branch takes priority over tag and commit", function() - helpers.setup_test_env() + it("no version fields results in nil version", function() + require('zpack').setup({ + spec = { + { 'test/plugin' }, + }, + defaults = { confirm = false }, + }) + + local vim_pack_call = _G.test_state.vim_pack_calls[1] + assert.is_not_nil(vim_pack_call, "vim.pack.add should have been called") + assert.is_nil(vim_pack_call[1].version, "version should be nil when no version fields provided") + end) - require('zpack').setup({ - spec = { - { - 'test/plugin', - branch = 'develop', - tag = 'v1.0.0', - commit = 'abc123', - }, + it("vim.VersionRange passed directly through version field", function() + local range = vim.version.range('^6') + require('zpack').setup({ + spec = { + { + 'test/plugin', + version = range, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - local vim_pack_call = _G.test_state.vim_pack_calls[1] - helpers.assert_not_nil(vim_pack_call, "vim.pack.add should have been called") - helpers.assert_equal(vim_pack_call[1].version, 'develop', "branch should take priority over tag and commit") - - helpers.cleanup_test_env() - end) + local vim_pack_call = _G.test_state.vim_pack_calls[1] + assert.is_not_nil(vim_pack_call, "vim.pack.add should have been called") + assert.are.equal(range, vim_pack_call[1].version) + end) - helpers.test("tag takes priority over commit", function() - helpers.setup_test_env() + it("semver-like version emits warning when vim.pack.add fails", function() + local mock_vim_pack_add = vim.pack.add + vim.pack.add = function() + error("some error from vim.pack.add") + end + local ok = pcall(function() require('zpack').setup({ spec = { { 'test/plugin', - tag = 'v2.0.0', - commit = 'abc123', + version = '1.*', }, }, defaults = { confirm = false }, }) - - local vim_pack_call = _G.test_state.vim_pack_calls[1] - helpers.assert_not_nil(vim_pack_call, "vim.pack.add should have been called") - helpers.assert_equal(vim_pack_call[1].version, 'v2.0.0', "tag should take priority over commit") - - helpers.cleanup_test_env() end) - helpers.test("no version fields results in nil version", function() - helpers.setup_test_env() + assert.are.equal(false, ok) + assert.are.equal(true, #_G.test_state.notifications >= 2) - require('zpack').setup({ - spec = { - { 'test/plugin' }, - }, - defaults = { confirm = false }, - }) - - local vim_pack_call = _G.test_state.vim_pack_calls[1] - helpers.assert_not_nil(vim_pack_call, "vim.pack.add should have been called") - helpers.assert_nil(vim_pack_call[1].version, "version should be nil when no version fields provided") + local found_header = false + local found_detail = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:match('`vim%.pack%.add` failed') then + found_header = true + end + if notif.msg:match('sem_version') and notif.msg:match('1%.%*') then + found_detail = true + end + end + assert.are.equal(true, found_header) + assert.are.equal(true, found_detail) - helpers.cleanup_test_env() - end) + vim.pack.add = mock_vim_pack_add + end) - helpers.test("vim.VersionRange passed directly through version field", function() - helpers.setup_test_env() + it("multiple semver-like versions emit multiple warnings", function() + local mock_vim_pack_add = vim.pack.add + vim.pack.add = function() + error("some error from vim.pack.add") + end - local range = vim.version.range('^6') + local ok = pcall(function() require('zpack').setup({ spec = { - { - 'test/plugin', - version = range, - }, + { 'test/plugin-a', version = '1.*' }, + { 'test/plugin-b', version = '^2.0.0' }, + { 'test/plugin-c', version = 'main' }, }, defaults = { confirm = false }, }) - - local vim_pack_call = _G.test_state.vim_pack_calls[1] - helpers.assert_not_nil(vim_pack_call, "vim.pack.add should have been called") - helpers.assert_equal(vim_pack_call[1].version, range, "vim.VersionRange should be passed through directly") - - helpers.cleanup_test_env() end) - helpers.test("semver-like version emits warning when vim.pack.add fails", function() - helpers.setup_test_env() + assert.are.equal(false, ok) - local mock_vim_pack_add = vim.pack.add - vim.pack.add = function() - error("some error from vim.pack.add") + local found_header = false + local detail_count = 0 + local found_plugin_a = false + local found_plugin_b = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:match('`vim%.pack%.add` failed') then + found_header = true end - - local ok = pcall(function() - require('zpack').setup({ - spec = { - { - 'test/plugin', - version = '1.*', - }, - }, - defaults = { confirm = false }, - }) - end) - - helpers.assert_equal(ok, false, "setup should fail when vim.pack.add throws") - helpers.assert_equal(#_G.test_state.notifications >= 2, true, "header and detail warnings should be emitted") - - local found_header = false - local found_detail = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:match('`vim%.pack%.add` failed') then - found_header = true + if notif.msg:match('sem_version') then + detail_count = detail_count + 1 + if notif.msg:match('1%.%*') and notif.msg:match('plugin%-a') then + found_plugin_a = true end - if notif.msg:match('sem_version') and notif.msg:match('1%.%*') then - found_detail = true + if notif.msg:match('%^2%.0%.0') and notif.msg:match('plugin%-b') then + found_plugin_b = true end end - helpers.assert_equal(found_header, true, "should have header mentioning vim.pack.add failed") - helpers.assert_equal(found_detail, true, "should have detail mentioning sem_version") + end - vim.pack.add = mock_vim_pack_add - helpers.cleanup_test_env() - end) + assert.are.equal(true, found_header) + assert.are.equal(2, detail_count) + assert.are.equal(true, found_plugin_a) + assert.are.equal(true, found_plugin_b) - helpers.test("multiple semver-like versions emit multiple warnings", function() - helpers.setup_test_env() + vim.pack.add = mock_vim_pack_add + end) - local mock_vim_pack_add = vim.pack.add - vim.pack.add = function() - error("some error from vim.pack.add") - end + it("no warning when vim.pack.add fails but no semver-like versions", function() + local mock_vim_pack_add = vim.pack.add + vim.pack.add = function() + error("some error from vim.pack.add") + end - local ok = pcall(function() - require('zpack').setup({ - spec = { - { 'test/plugin-a', version = '1.*' }, - { 'test/plugin-b', version = '^2.0.0' }, - { 'test/plugin-c', version = 'main' }, + local ok = pcall(function() + require('zpack').setup({ + spec = { + { + 'test/plugin', + version = 'nonexistent-branch', }, - defaults = { confirm = false }, - }) - end) - - helpers.assert_equal(ok, false, "setup should fail when vim.pack.add throws") - - local found_header = false - local detail_count = 0 - local found_plugin_a = false - local found_plugin_b = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:match('`vim%.pack%.add` failed') then - found_header = true - end - if notif.msg:match('sem_version') then - detail_count = detail_count + 1 - if notif.msg:match('1%.%*') and notif.msg:match('plugin%-a') then - found_plugin_a = true - end - if notif.msg:match('%^2%.0%.0') and notif.msg:match('plugin%-b') then - found_plugin_b = true - end - end - end - - helpers.assert_equal(found_header, true, "should have single header message") - helpers.assert_equal(detail_count, 2, "should emit exactly 2 detail warnings for semver-like versions") - helpers.assert_equal(found_plugin_a, true, "should warn about plugin-a with 1.*") - helpers.assert_equal(found_plugin_b, true, "should warn about plugin-b with ^2.0.0") - - vim.pack.add = mock_vim_pack_add - helpers.cleanup_test_env() + }, + defaults = { confirm = false }, + }) end) - helpers.test("no warning when vim.pack.add fails but no semver-like versions", function() - helpers.setup_test_env() + assert.are.equal(false, ok) - local mock_vim_pack_add = vim.pack.add - vim.pack.add = function() - error("some error from vim.pack.add") + local found_warning = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:match('sem_version') then + found_warning = true + break end + end + assert.are.equal(false, found_warning) - local ok = pcall(function() - require('zpack').setup({ - spec = { - { - 'test/plugin', - version = 'nonexistent-branch', - }, - }, - defaults = { confirm = false }, - }) - end) - - helpers.assert_equal(ok, false, "setup should fail when vim.pack.add throws") - - local found_warning = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:match('sem_version') then - found_warning = true - break - end - end - helpers.assert_equal(found_warning, false, "no warning should be emitted for non-semver-like versions") - - vim.pack.add = mock_vim_pack_add - helpers.cleanup_test_env() - end) + vim.pack.add = mock_vim_pack_add end) -end +end) diff --git a/tests/zclean_test.lua b/tests/zclean_test.lua index 9d3e29d..f35c226 100644 --- a/tests/zclean_test.lua +++ b/tests/zclean_test.lua @@ -1,95 +1,84 @@ local helpers = require('helpers') -return function() - helpers.describe("ZPack clean", function() - helpers.test("clean detects orphan plugins not in spec", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - _G.test_state.registered_pack_specs['orphan-plugin'] = { - src = 'test/orphan-plugin', - name = 'orphan-plugin', - } - - vim.cmd('ZPack clean') - helpers.flush_pending() - - helpers.assert_equal(#_G.test_state.vim_pack_del_calls, 1, "vim.pack.del should be called") - local call = _G.test_state.vim_pack_del_calls[1] - helpers.assert_table_contains(call.names, 'orphan-plugin', "orphan plugin should be in delete list") - helpers.assert_equal(#call.names, 1, "only orphan plugin should be deleted") - - helpers.cleanup_test_env() - end) - - helpers.test("clean does not delete plugins in spec", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - vim.cmd('ZPack clean') - helpers.flush_pending() - - helpers.assert_equal(#_G.test_state.vim_pack_del_calls, 0, "vim.pack.del should not be called") - - local found_info = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find('No unused plugins') and notif.level == vim.log.levels.INFO then - found_info = true - break - end - end - helpers.assert_true(found_info, "Should show info that no unused plugins exist") - - helpers.cleanup_test_env() - end) - - helpers.test("clean detects multiple orphan plugins", function() - helpers.setup_test_env() +describe("ZPack clean", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("clean detects orphan plugins not in spec", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + _G.test_state.registered_pack_specs['orphan-plugin'] = { + src = 'test/orphan-plugin', + name = 'orphan-plugin', + } + + vim.cmd('ZPack clean') + helpers.flush_pending() + + assert.are.equal(1, #_G.test_state.vim_pack_del_calls) + local call = _G.test_state.vim_pack_del_calls[1] + assert.contains(call.names, 'orphan-plugin') + assert.are.equal(1, #call.names) + end) - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - }, - defaults = { confirm = false }, - }) + it("clean does not delete plugins in spec", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b' }, + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - _G.test_state.registered_pack_specs['orphan-1'] = { - src = 'test/orphan-1', - name = 'orphan-1', - } - _G.test_state.registered_pack_specs['orphan-2'] = { - src = 'test/orphan-2', - name = 'orphan-2', - } + vim.cmd('ZPack clean') + helpers.flush_pending() - vim.cmd('ZPack clean') - helpers.flush_pending() + assert.are.equal(0, #_G.test_state.vim_pack_del_calls) - helpers.assert_equal(#_G.test_state.vim_pack_del_calls, 1, "vim.pack.del should be called once") - local call = _G.test_state.vim_pack_del_calls[1] - helpers.assert_equal(#call.names, 2, "both orphan plugins should be deleted") + local found_info = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find('No unused plugins') and notif.level == vim.log.levels.INFO then + found_info = true + break + end + end + assert.is_truthy(found_info, "Should show info that no unused plugins exist") + end) - helpers.cleanup_test_env() - end) + it("clean detects multiple orphan plugins", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + _G.test_state.registered_pack_specs['orphan-1'] = { + src = 'test/orphan-1', + name = 'orphan-1', + } + _G.test_state.registered_pack_specs['orphan-2'] = { + src = 'test/orphan-2', + name = 'orphan-2', + } + + vim.cmd('ZPack clean') + helpers.flush_pending() + + assert.are.equal(1, #_G.test_state.vim_pack_del_calls) + local call = _G.test_state.vim_pack_del_calls[1] + assert.are.equal(2, #call.names) end) -end +end) diff --git a/tests/zdelete_test.lua b/tests/zdelete_test.lua index 677672d..b2a52af 100644 --- a/tests/zdelete_test.lua +++ b/tests/zdelete_test.lua @@ -1,303 +1,265 @@ local helpers = require('helpers') -return function() - helpers.describe("ZPack delete", function() - helpers.test("delete single plugin uses force=true", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - vim.cmd('ZPack delete plugin-a') - helpers.flush_pending() - - helpers.assert_equal(#_G.test_state.vim_pack_del_calls, 1, "vim.pack.del should be called once") - local call = _G.test_state.vim_pack_del_calls[1] - helpers.assert_not_nil(call.opts, "opts should be passed to vim.pack.del") - helpers.assert_true(call.opts.force, "force option should be true") - helpers.assert_table_contains(call.names, 'plugin-a', "plugin-a should be in delete list") - - helpers.cleanup_test_env() - end) - - helpers.test("ZPack! delete all plugins uses force=true", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b' }, - { 'test/plugin-c' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - vim.cmd('ZPack! delete') - helpers.flush_pending() - - helpers.assert_equal(#_G.test_state.vim_pack_del_calls, 1, "vim.pack.del should be called once") - local call = _G.test_state.vim_pack_del_calls[1] - helpers.assert_not_nil(call.opts, "opts should be passed to vim.pack.del") - helpers.assert_true(call.opts.force, "force option should be true") - helpers.assert_equal(#call.names, 4, "all 4 plugins (3 + zpack.nvim) should be in delete list") - helpers.assert_table_contains(call.names, 'zpack.nvim', "zpack.nvim should be in delete list") - - helpers.cleanup_test_env() - end) - - helpers.test("delete! clears state for every registered plugin", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b' }, - { 'test/plugin-c' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local state = require('zpack.state') - helpers.assert_equal(#state.registered_plugins, 3, "3 plugins registered before delete!") - - vim.cmd('ZPack! delete') - helpers.flush_pending() - - helpers.assert_equal(#state.registered_plugins, 0, "registered_plugins should be empty after delete!") - helpers.assert_nil(next(state.spec_registry), "spec_registry should be empty after delete!") - helpers.assert_nil(next(state.src_to_pack_spec), "src_to_pack_spec should be empty after delete!") - - helpers.cleanup_test_env() - end) - - helpers.test("delete! also wipes state for cond-disabled plugins", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b', cond = false }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() +describe("ZPack delete", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("delete single plugin uses force=true", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + vim.cmd('ZPack delete plugin-a') + helpers.flush_pending() + + assert.are.equal(1, #_G.test_state.vim_pack_del_calls) + local call = _G.test_state.vim_pack_del_calls[1] + assert.is_not_nil(call.opts, "opts should be passed to vim.pack.del") + assert.is_truthy(call.opts.force, "force option should be true") + assert.contains(call.names, 'plugin-a') + end) - local state = require('zpack.state') - local src_b = 'https://github.com/test/plugin-b' - -- A cond-disabled plugin is installed and tracked in spec_registry, but - -- never reaches registered_plugins -- the set delete! used to wipe. - helpers.assert_equal(#state.registered_plugins, 1, "cond-disabled plugin is not in registered_plugins") - helpers.assert_not_nil(state.spec_registry[src_b], "cond-disabled plugin has a registry entry") + it("ZPack! delete all plugins uses force=true", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b' }, + { 'test/plugin-c' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + vim.cmd('ZPack! delete') + helpers.flush_pending() + + assert.are.equal(1, #_G.test_state.vim_pack_del_calls) + local call = _G.test_state.vim_pack_del_calls[1] + assert.is_not_nil(call.opts, "opts should be passed to vim.pack.del") + assert.is_truthy(call.opts.force, "force option should be true") + assert.are.equal(4, #call.names) + assert.contains(call.names, 'zpack.nvim') + end) - vim.cmd('ZPack! delete') - helpers.flush_pending() + it("delete! clears state for every registered plugin", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b' }, + { 'test/plugin-c' }, + }, + defaults = { confirm = false }, + }) - helpers.assert_nil(next(state.spec_registry), "delete! should wipe spec_registry, including cond-disabled plugins") - helpers.assert_nil(next(state.src_to_pack_spec), "src_to_pack_spec should be empty after delete!") + helpers.flush_pending() - helpers.cleanup_test_env() - end) + local state = require('zpack.state') + assert.are.equal(3, #state.registered_plugins) - helpers.test("delete without bang and no arg shows warning", function() - helpers.setup_test_env() + vim.cmd('ZPack! delete') + helpers.flush_pending() - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - }, - defaults = { confirm = false }, - }) + assert.are.equal(0, #state.registered_plugins) + assert.is_nil(next(state.spec_registry), "spec_registry should be empty after delete!") + assert.is_nil(next(state.src_to_pack_spec), "src_to_pack_spec should be empty after delete!") + end) - helpers.flush_pending() + it("delete! also wipes state for cond-disabled plugins", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b', cond = false }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local state = require('zpack.state') + local src_b = 'https://github.com/test/plugin-b' + -- A cond-disabled plugin is installed and tracked in spec_registry, but + -- never reaches registered_plugins -- the set delete! used to wipe. + assert.are.equal(1, #state.registered_plugins) + assert.is_not_nil(state.spec_registry[src_b], "cond-disabled plugin has a registry entry") + + vim.cmd('ZPack! delete') + helpers.flush_pending() + + assert.is_nil(next(state.spec_registry), "delete! should wipe spec_registry, including cond-disabled plugins") + assert.is_nil(next(state.src_to_pack_spec), "src_to_pack_spec should be empty after delete!") + end) - vim.cmd('ZPack delete') - helpers.flush_pending() + it("delete without bang and no arg shows warning", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + }, + defaults = { confirm = false }, + }) - helpers.assert_equal(#_G.test_state.vim_pack_del_calls, 0, "vim.pack.del should not be called") + helpers.flush_pending() - helpers.cleanup_test_env() - end) + vim.cmd('ZPack delete') + helpers.flush_pending() - helpers.test("delete clears dependency graph entries for deleted plugin", function() - helpers.setup_test_env() + assert.are.equal(0, #_G.test_state.vim_pack_del_calls) + end) - require('zpack').setup({ - spec = { - { 'test/plugin-a', dependencies = { 'test/plugin-b' } }, - { 'test/plugin-b', dependencies = { 'test/plugin-c' } }, - { 'test/plugin-c' }, - }, - defaults = { confirm = false }, - }) + it("delete clears dependency graph entries for deleted plugin", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a', dependencies = { 'test/plugin-b' } }, + { 'test/plugin-b', dependencies = { 'test/plugin-c' } }, + { 'test/plugin-c' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local state = require('zpack.state') + local src_b = 'https://github.com/test/plugin-b' + local src_a = 'https://github.com/test/plugin-a' + local src_c = 'https://github.com/test/plugin-c' + assert.is_not_nil(state.dependency_graph[src_b], "plugin-b should have dependency graph entry") + assert.is_not_nil(state.reverse_dependency_graph[src_b], "plugin-b should have reverse dependency graph entry") + + vim.cmd('ZPack delete plugin-b') + helpers.flush_pending() + + assert.is_nil(state.dependency_graph[src_b], "plugin-b dependency graph entry should be cleared") + assert.is_nil(state.reverse_dependency_graph[src_b], "plugin-b reverse dependency graph entry should be cleared") + + local a_deps = state.dependency_graph[src_a] + if a_deps then + assert.is_nil(a_deps[src_b], "plugin-b should be removed from plugin-a's dependencies") + end + + local c_rdeps = state.reverse_dependency_graph[src_c] + if c_rdeps then + assert.is_nil(c_rdeps[src_b], "plugin-b should be removed from plugin-c's reverse dependencies") + end + end) - helpers.flush_pending() + it("delete clears src_to_pack_spec for deleted plugin", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b' }, + }, + defaults = { confirm = false }, + }) - local state = require('zpack.state') - local src_b = 'https://github.com/test/plugin-b' - local src_a = 'https://github.com/test/plugin-a' - local src_c = 'https://github.com/test/plugin-c' - helpers.assert_not_nil(state.dependency_graph[src_b], "plugin-b should have dependency graph entry") - helpers.assert_not_nil(state.reverse_dependency_graph[src_b], "plugin-b should have reverse dependency graph entry") + helpers.flush_pending() - vim.cmd('ZPack delete plugin-b') - helpers.flush_pending() + local state = require('zpack.state') + local src_a = 'https://github.com/test/plugin-a' + local src_b = 'https://github.com/test/plugin-b' + assert.is_not_nil(state.src_to_pack_spec[src_a], "plugin-a should have src_to_pack_spec entry") - helpers.assert_nil(state.dependency_graph[src_b], "plugin-b dependency graph entry should be cleared") - helpers.assert_nil(state.reverse_dependency_graph[src_b], "plugin-b reverse dependency graph entry should be cleared") + vim.cmd('ZPack delete plugin-a') + helpers.flush_pending() - local a_deps = state.dependency_graph[src_a] - if a_deps then - helpers.assert_nil(a_deps[src_b], "plugin-b should be removed from plugin-a's dependencies") - end + assert.is_nil(state.src_to_pack_spec[src_a], "plugin-a src_to_pack_spec entry should be cleared") + assert.is_not_nil(state.src_to_pack_spec[src_b], "plugin-b src_to_pack_spec entry should remain") + end) - local c_rdeps = state.reverse_dependency_graph[src_c] - if c_rdeps then - helpers.assert_nil(c_rdeps[src_b], "plugin-b should be removed from plugin-c's reverse dependencies") + it("external vim.pack.del syncs zpack state via PackChanged", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + { 'test/plugin-b' }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + + local state = require('zpack.state') + local src_a = 'https://github.com/test/plugin-a' + local src_b = 'https://github.com/test/plugin-b' + assert.is_not_nil(state.spec_registry[src_a], "plugin-a should be registered") + assert.contains(state.registered_plugin_names, 'plugin-a') + + -- Simulate :packdel / a direct vim.pack.del call, bypassing :ZPack delete. + vim.pack.del({ 'plugin-a' }) + helpers.flush_pending() + + assert.is_nil(state.spec_registry[src_a], "plugin-a should be removed from registry") + assert.is_not_nil(state.spec_registry[src_b], "plugin-b should remain registered") + + local still_listed = false + for _, name in ipairs(state.registered_plugin_names) do + if name == 'plugin-a' then + still_listed = true end + end + assert.is_falsy(still_listed, "plugin-a should no longer be in registered names") + end) - helpers.cleanup_test_env() - end) - - helpers.test("delete clears src_to_pack_spec for deleted plugin", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local state = require('zpack.state') - local src_a = 'https://github.com/test/plugin-a' - local src_b = 'https://github.com/test/plugin-b' - helpers.assert_not_nil(state.src_to_pack_spec[src_a], "plugin-a should have src_to_pack_spec entry") - - vim.cmd('ZPack delete plugin-a') - helpers.flush_pending() - - helpers.assert_nil(state.src_to_pack_spec[src_a], "plugin-a src_to_pack_spec entry should be cleared") - helpers.assert_not_nil(state.src_to_pack_spec[src_b], "plugin-b src_to_pack_spec entry should remain") - - helpers.cleanup_test_env() - end) - - helpers.test("external vim.pack.del syncs zpack state via PackChanged", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - { 'test/plugin-b' }, - }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - local state = require('zpack.state') - local src_a = 'https://github.com/test/plugin-a' - local src_b = 'https://github.com/test/plugin-b' - helpers.assert_not_nil(state.spec_registry[src_a], "plugin-a should be registered") - helpers.assert_table_contains(state.registered_plugin_names, 'plugin-a', "plugin-a should be in registered names") - - -- Simulate :packdel / a direct vim.pack.del call, bypassing :ZPack delete. - vim.pack.del({ 'plugin-a' }) - helpers.flush_pending() - - helpers.assert_nil(state.spec_registry[src_a], "plugin-a should be removed from registry") - helpers.assert_not_nil(state.spec_registry[src_b], "plugin-b should remain registered") - - local still_listed = false - for _, name in ipairs(state.registered_plugin_names) do - if name == 'plugin-a' then - still_listed = true - end - end - helpers.assert_false(still_listed, "plugin-a should no longer be in registered names") - - helpers.cleanup_test_env() - end) - - helpers.test("firing a deleted lazy plugin's trigger does not error or load it", function() - helpers.setup_test_env() - local loaded = false - - require('zpack').setup({ - spec = { - { - 'test/plugin-a', - cmd = 'TestCommand', - config = function() - loaded = true - end, - }, + it("firing a deleted lazy plugin's trigger does not error or load it", function() + local loaded = false + + require('zpack').setup({ + spec = { + { + 'test/plugin-a', + cmd = 'TestCommand', + config = function() + loaded = true + end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - - helpers.assert_not_nil( - vim.api.nvim_get_commands({}).TestCommand, - "lazy cmd trigger should be registered before deletion" - ) + }, + defaults = { confirm = false }, + }) - -- Remove the plugin while its lazy trigger is still live. - vim.pack.del({ 'plugin-a' }) - helpers.flush_pending() + helpers.flush_pending() - local ok = pcall(vim.cmd, 'TestCommand') - helpers.flush_pending() + assert.is_not_nil( + vim.api.nvim_get_commands({}).TestCommand, + "lazy cmd trigger should be registered before deletion" + ) - helpers.assert_true(ok, "firing a deleted lazy plugin's command should not error") - helpers.assert_false(loaded, "deleted lazy plugin should not load when its trigger fires") + -- Remove the plugin while its lazy trigger is still live. + vim.pack.del({ 'plugin-a' }) + helpers.flush_pending() - helpers.cleanup_test_env() - end) + local ok = pcall(vim.cmd, 'TestCommand') + helpers.flush_pending() - helpers.test("delete non-existent plugin does not call vim.pack.del", function() - helpers.setup_test_env() + assert.is_truthy(ok, "firing a deleted lazy plugin's command should not error") + assert.is_falsy(loaded, "deleted lazy plugin should not load when its trigger fires") + end) - require('zpack').setup({ - spec = { - { 'test/plugin-a' }, - }, - defaults = { confirm = false }, - }) + it("delete non-existent plugin does not call vim.pack.del", function() + require('zpack').setup({ + spec = { + { 'test/plugin-a' }, + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() + helpers.flush_pending() - vim.cmd('ZPack delete non-existent-plugin') - helpers.flush_pending() + vim.cmd('ZPack delete non-existent-plugin') + helpers.flush_pending() - helpers.assert_equal(#_G.test_state.vim_pack_del_calls, 0, "vim.pack.del should not be called for non-existent plugin") + assert.are.equal(0, #_G.test_state.vim_pack_del_calls) - local found_error = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find('not installed') and notif.level == vim.log.levels.ERROR then - found_error = true - break - end + local found_error = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find('not installed') and notif.level == vim.log.levels.ERROR then + found_error = true + break end - helpers.assert_true(found_error, "should notify error for non-existent plugin") - - helpers.cleanup_test_env() - end) + end + assert.is_truthy(found_error, "should notify error for non-existent plugin") end) -end +end) diff --git a/tests/zload_test.lua b/tests/zload_test.lua index 98eb2af..2c40f3c 100644 --- a/tests/zload_test.lua +++ b/tests/zload_test.lua @@ -1,209 +1,186 @@ local helpers = require('helpers') -return function() - helpers.describe("ZPack load", function() - helpers.test("lazy plugin is tracked as unloaded", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/lazy-plugin', - cmd = 'TestCommand', - }, +describe("ZPack load", function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + it("lazy plugin is tracked as unloaded", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { + { + 'test/lazy-plugin', + cmd = 'TestCommand', }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_true( - state.unloaded_plugin_names['lazy-plugin'] == true, - "Lazy plugin should be tracked as unloaded" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("startup plugin is not tracked as unloaded", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/startup-plugin', - config = function() end, - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_truthy( + state.unloaded_plugin_names['lazy-plugin'] == true, + "Lazy plugin should be tracked as unloaded" + ) + end) + + it("startup plugin is not tracked as unloaded", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { + { + 'test/startup-plugin', + config = function() end, }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_nil( - state.unloaded_plugin_names['startup-plugin'], - "Startup plugin should not be in unloaded list" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("plugin is removed from unloaded list when loaded", function() - helpers.setup_test_env() - local state = require('zpack.state') - - require('zpack').setup({ - spec = { - { - 'test/lazy-plugin', - cmd = 'TestCommand', - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_nil( + state.unloaded_plugin_names['startup-plugin'], + "Startup plugin should not be in unloaded list" + ) + end) + + it("plugin is removed from unloaded list when loaded", function() + local state = require('zpack.state') + + require('zpack').setup({ + spec = { + { + 'test/lazy-plugin', + cmd = 'TestCommand', }, - defaults = { confirm = false }, - }) - - helpers.flush_pending() - helpers.assert_true( - state.unloaded_plugin_names['lazy-plugin'] == true, - "Plugin should be unloaded initially" - ) - - pcall(vim.cmd, 'TestCommand') - helpers.flush_pending() - - helpers.assert_nil( - state.unloaded_plugin_names['lazy-plugin'], - "Plugin should be removed from unloaded list after loading" - ) - - helpers.cleanup_test_env() - end) - - helpers.test("load without bang shows warning", function() - helpers.setup_test_env() - - require('zpack').setup({ - spec = { - { - 'test/lazy-plugin', - cmd = 'TestCommand', - }, + }, + defaults = { confirm = false }, + }) + + helpers.flush_pending() + assert.is_truthy( + state.unloaded_plugin_names['lazy-plugin'] == true, + "Plugin should be unloaded initially" + ) + + pcall(vim.cmd, 'TestCommand') + helpers.flush_pending() + + assert.is_nil( + state.unloaded_plugin_names['lazy-plugin'], + "Plugin should be removed from unloaded list after loading" + ) + end) + + it("load without bang shows warning", function() + require('zpack').setup({ + spec = { + { + 'test/lazy-plugin', + cmd = 'TestCommand', }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - _G.test_state.notifications = {} + helpers.flush_pending() + _G.test_state.notifications = {} - pcall(vim.cmd, 'ZPack load') - helpers.flush_pending() + pcall(vim.cmd, 'ZPack load') + helpers.flush_pending() - local found_warning = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find(':ZPack! load', 1, true) and notif.level == vim.log.levels.WARN then - found_warning = true - break - end + local found_warning = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find(':ZPack! load', 1, true) and notif.level == vim.log.levels.WARN then + found_warning = true + break end - helpers.assert_true(found_warning, "Should show warning about using :ZPack! load") - - helpers.cleanup_test_env() - end) - - helpers.test("ZPack! load loads all unloaded plugins", function() - helpers.setup_test_env() - local state = require('zpack.state') - local config_called = {} - - require('zpack').setup({ - spec = { - { - 'test/plugin-a', - cmd = 'TestA', - config = function() config_called['plugin-a'] = true end, - }, - { - 'test/plugin-b', - cmd = 'TestB', - config = function() config_called['plugin-b'] = true end, - }, - }, - defaults = { confirm = false }, - }) + end + assert.is_truthy(found_warning, "Should show warning about using :ZPack! load") + end) - helpers.flush_pending() - helpers.assert_equal(vim.tbl_count(state.unloaded_plugin_names), 2, "Should have 2 unloaded plugins") + it("ZPack! load loads all unloaded plugins", function() + local state = require('zpack.state') + local config_called = {} - vim.cmd('ZPack! load') - helpers.flush_pending() + require('zpack').setup({ + spec = { + { + 'test/plugin-a', + cmd = 'TestA', + config = function() config_called['plugin-a'] = true end, + }, + { + 'test/plugin-b', + cmd = 'TestB', + config = function() config_called['plugin-b'] = true end, + }, + }, + defaults = { confirm = false }, + }) - helpers.assert_equal(vim.tbl_count(state.unloaded_plugin_names), 0, "Should have 0 unloaded plugins") - helpers.assert_true(config_called['plugin-a'], "Plugin A config should be called") - helpers.assert_true(config_called['plugin-b'], "Plugin B config should be called") + helpers.flush_pending() + assert.are.equal(2, vim.tbl_count(state.unloaded_plugin_names)) - helpers.cleanup_test_env() - end) + vim.cmd('ZPack! load') + helpers.flush_pending() - helpers.test("ZPack! load with no unloaded plugins shows info message", function() - helpers.setup_test_env() + assert.are.equal(0, vim.tbl_count(state.unloaded_plugin_names)) + assert.is_truthy(config_called['plugin-a'], "Plugin A config should be called") + assert.is_truthy(config_called['plugin-b'], "Plugin B config should be called") + end) - require('zpack').setup({ - spec = { - { - 'test/startup-plugin', - config = function() end, - }, + it("ZPack! load with no unloaded plugins shows info message", function() + require('zpack').setup({ + spec = { + { + 'test/startup-plugin', + config = function() end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - _G.test_state.notifications = {} + helpers.flush_pending() + _G.test_state.notifications = {} - vim.cmd('ZPack! load') - helpers.flush_pending() + vim.cmd('ZPack! load') + helpers.flush_pending() - local found_info = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find('All plugins are already loaded') and notif.level == vim.log.levels.INFO then - found_info = true - break - end + local found_info = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find('All plugins are already loaded') and notif.level == vim.log.levels.INFO then + found_info = true + break end - helpers.assert_true(found_info, "Should show info that all plugins are loaded") - - helpers.cleanup_test_env() - end) - - helpers.test("load already-loaded plugin shows info message", function() - helpers.setup_test_env() + end + assert.is_truthy(found_info, "Should show info that all plugins are loaded") + end) - require('zpack').setup({ - spec = { - { - 'test/startup-plugin', - config = function() end, - }, + it("load already-loaded plugin shows info message", function() + require('zpack').setup({ + spec = { + { + 'test/startup-plugin', + config = function() end, }, - defaults = { confirm = false }, - }) + }, + defaults = { confirm = false }, + }) - helpers.flush_pending() - _G.test_state.notifications = {} + helpers.flush_pending() + _G.test_state.notifications = {} - pcall(vim.cmd, 'ZPack load startup-plugin') - helpers.flush_pending() + pcall(vim.cmd, 'ZPack load startup-plugin') + helpers.flush_pending() - local found_info = false - for _, notif in ipairs(_G.test_state.notifications) do - if notif.msg:find('already loaded') and notif.level == vim.log.levels.INFO then - found_info = true - break - end + local found_info = false + for _, notif in ipairs(_G.test_state.notifications) do + if notif.msg:find('already loaded') and notif.level == vim.log.levels.INFO then + found_info = true + break end - helpers.assert_true(found_info, "Should show info that plugin is already loaded") - - helpers.cleanup_test_env() - end) + end + assert.is_truthy(found_info, "Should show info that plugin is already loaded") end) -end +end) diff --git a/tests/zrestore_test.lua b/tests/zrestore_test.lua index 86e42a6..f489ab9 100644 --- a/tests/zrestore_test.lua +++ b/tests/zrestore_test.lua @@ -1,8 +1,16 @@ +local helpers = require('helpers') local pack_update_tests = require('pack_update_test_helpers') -return pack_update_tests.create_tests({ - command = 'ZPack restore', - expected_opts = { target = 'lockfile' }, - error_prefix = 'Restore failed', - supports_bang = true, -}) +describe('ZPack restore', function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + for _, case in ipairs(pack_update_tests.cases({ + command = 'ZPack restore', + expected_opts = { target = 'lockfile' }, + error_prefix = 'Restore failed', + supports_bang = true, + })) do + it(case.name, case.fn) + end +end) diff --git a/tests/zupdate_test.lua b/tests/zupdate_test.lua index fd0787b..dbe58b3 100644 --- a/tests/zupdate_test.lua +++ b/tests/zupdate_test.lua @@ -1,8 +1,16 @@ +local helpers = require('helpers') local pack_update_tests = require('pack_update_test_helpers') -return pack_update_tests.create_tests({ - command = 'ZPack update', - expected_opts = nil, - error_prefix = 'Update failed', - supports_bang = true, -}) +describe('ZPack update', function() + before_each(helpers.setup_test_env) + after_each(helpers.cleanup_test_env) + + for _, case in ipairs(pack_update_tests.cases({ + command = 'ZPack update', + expected_opts = nil, + error_prefix = 'Update failed', + supports_bang = true, + })) do + it(case.name, case.fn) + end +end)