test: overhaul test setup and make commands

This commit is contained in:
Price Hiller 2024-05-19 07:01:20 -05:00 committed by windwp
parent 802c28e08b
commit df93be4d2e
12 changed files with 289 additions and 36 deletions

1
.gitignore vendored
View File

@ -0,0 +1 @@
tests/.deps

View File

@ -1,2 +1,8 @@
clean:
nvim --headless --clean -n -c "lua vim.fn.delete('./tests/.deps', 'rf')" +q
test:
nvim --headless --noplugin -u tests/minimal.vim -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/minimal.vim'}"
nvim --headless --clean -u tests/test.lua "$(FILE)"
lint:
stylua --check lua/ tests/
format:
stylua lua/ tests/

View File

@ -16,6 +16,8 @@ function M.init()
end
end
function M.ensure_ts_parsers_installed() end
function M.setup(opts)
internal.setup(opts)
vim.cmd([[augroup nvim_ts_xmltag]])

6
tests/.luarc.json Normal file
View File

@ -0,0 +1,6 @@
{
"diagnostics.globals": [
"it",
"describe"
]
}

111
tests/minimal_init.lua Normal file
View File

@ -0,0 +1,111 @@
local M = {}
local utils = require("tests.utils.utils")
local root = utils.paths.Root:push(".deps/")
---@class MinPlugin A plugin to download and register on the package path
---@alias PluginName string The plugin name, will be used as part of the git clone destination
---@alias PluginCloneInfo string | string[] The git url a plugin located at or a table of arguments to be passed to `git clone`
---@alias MinPlugins table<PluginName, PluginCloneInfo>
---Downloads a plugin from a given url and registers it on the 'runtimepath'
---@param plugin_name PluginName
---@param plugin_clone_args PluginCloneInfo
function M.load_plugin(plugin_name, plugin_clone_args)
local package_root = root:push("plugins/")
local install_destination = package_root:push(plugin_name):get()
vim.opt.runtimepath:append(install_destination)
if not vim.loop.fs_stat(package_root:get()) then
vim.fn.mkdir(package_root:get(), "p")
end
-- If the plugin install path already exists, we don't need to clone it again.
if not vim.loop.fs_stat(install_destination) then
print(string.format("[LOAD PLUGIN] Downloading plugin '%s' to '%s'", plugin_name, install_destination))
if type(plugin_clone_args) == "table" then
plugin_clone_args = table.concat(plugin_clone_args, " ")
end
vim.fn.system({
"git",
"clone",
"--depth=1",
plugin_clone_args,
install_destination,
})
if vim.v.shell_error > 0 then
error(
string.format("[LOAD PLUGIN] Failed to clone plugin: '%s' to '%s'!", plugin_name, install_destination),
vim.log.levels.ERROR
)
end
end
print(("[LOAD PLUGIN] Loaded plugin '%s'"):format(plugin_name))
end
---Do the initial setup. Downloads plugins, ensures the minimal init does not pollute the filesystem by keeping
---everything self contained to the CWD of the minimal init file. Run prior to running tests, reproducing issues, etc.
---@param plugins? MinPlugins
function M.setup(plugins)
print("[SETUP] Setting up minimal init")
-- Instead of disabling swap and a bunch of other stuff, we override default xdg locations for
-- Neovim so our test client is as close to a normal client in terms of options as possible
local xdg_root = root:push("xdg")
local std_paths = {
"cache",
"data",
"config",
"state",
}
for _, std_path in pairs(std_paths) do
local xdg_str = "XDG_" .. std_path:upper() .. "_HOME"
local xdg_path = xdg_root:push(std_path):get()
print(("[SETUP] Set vim.env.%-3s -> %s"):format(xdg_str, xdg_path))
vim.env[xdg_str] = xdg_path
---@diagnostic disable-next-line: param-type-mismatch
vim.fn.mkdir(vim.fn.stdpath(std_path), "p")
end
-- Ignore cleanups if specified in the environment
-- NOTE: Cleanup the xdg cache on exit so new runs of the minimal init doesn't share any previous state, e.g. shada
vim.api.nvim_create_autocmd("VimLeave", {
callback = function()
if vim.env.TEST_NO_CLEANUP and vim.env.TEST_NO_CLEANUP:lower() == "true" then
print("[CLEANUP]: `TEST_NO_CLEANUP` was specified, not removing: " .. xdg_root)
else
print("[CLEANUP]: `TEST_NO_CLEANUP` not specified, removing " .. xdg_root)
vim.fn.delete(xdg_root:get(), "rf")
end
end,
})
-- Empty the package path so we use only the plugins specified
vim.opt.packpath = {}
-- Install required plugins
if plugins ~= nil then
for plugin_name, plugin_clone_args in pairs(plugins) do
M.load_plugin(plugin_name, plugin_clone_args)
end
end
-- Ensure `nvim-ts-autotag` is registed on the runtimepath and set it up
utils.rtp_register_ts_autotag()
require("nvim-ts-autotag").setup({
enable = true,
enable_rename = true,
enable_close = true,
enable_close_on_slash = true,
})
print("[SETUP] Finished setting up minimal init")
end
M.setup({
["plenary.nvim"] = "https://github.com/nvim-lua/plenary.nvim",
["popup.nvim"] = "https://github.com/nvim-lua/popup.nvim",
["nvim-treesitter"] = "https://github.com/nvim-treesitter/nvim-treesitter",
["playground"] = "https://github.com/nvim-treesitter/playground",
["nvim-treesitter-rescript"] = "https://github.com/nkrkv/nvim-treesitter-rescript",
})

View File

@ -1,6 +1,6 @@
local ts = require("nvim-treesitter.configs")
ts.setup({
ensure_installed = _G.ts_filetypes,
local helpers = require("tests.utils.helpers")
helpers.setup_nvim_treesitter({
highlight = { enable = true },
})
@ -171,10 +171,10 @@ local data = {
local autotag = require("nvim-ts-autotag")
autotag.test = true
local run_data = _G.Test_filter(data)
local run_data = helpers.Test_filter(data)
describe("[close slash tag]", function()
_G.Test_withfile(run_data, {
helpers.Test_withfile(run_data, {
mode = "i",
cursor_add = 0,
})

View File

@ -1,6 +1,6 @@
local ts = require("nvim-treesitter.configs")
ts.setup({
ensure_installed = _G.ts_filetypes,
local helpers = require("tests.utils.helpers")
helpers.setup_nvim_treesitter({
highlight = { enable = true },
})
@ -209,12 +209,12 @@ local data = {
local autotag = require("nvim-ts-autotag")
autotag.test = true
local run_data = _G.Test_filter(data)
local run_data = helpers.Test_filter(data)
describe("[close tag]", function()
_G.Test_withfile(run_data, {
helpers.Test_withfile(run_data, {
mode = "i",
cursor_add = 0,
before_each = function(value) end,
before_each = function() end,
})
end)

View File

@ -1,6 +1,6 @@
local ts = require("nvim-treesitter.configs")
ts.setup({
ensure_installed = _G.ts_filetypes,
local helpers = require("tests.utils.helpers")
helpers.setup_nvim_treesitter({
highlight = {
use_languagetree = false,
enable = true,
@ -315,11 +315,11 @@ local data = {
local autotag = require("nvim-ts-autotag")
autotag.test = true
local run_data = _G.Test_filter(data)
local run_data = helpers.Test_filter(data)
describe("[rename tag]", function()
_G.Test_withfile(run_data, {
helpers.Test_withfile(run_data, {
cursor_add = 0,
before_each = function(value) end,
before_each = function() end,
})
end)

13
tests/test.lua Normal file
View File

@ -0,0 +1,13 @@
require("tests.minimal_init")
---@type string
local test_file = vim.v.argv[#vim.v.argv]
if test_file == "" or not test_file:find("tests/specs/", nil, true) then
test_file = "tests/specs"
end
print("[STARTUP] Running all tests in " .. test_file)
require("plenary.test_harness").test_directory(test_file, {
minimal_init = "tests/minimal_init.lua",
sequential = true,
})

View File

@ -1,6 +1,13 @@
local test_utils = require("tests.utils.utils")
-- Some helpers depend on utilities from the main plugin, so we have to register the plugin on the
-- path if it isn't already present
test_utils.rtp_register_ts_autotag()
local utils = require("nvim-ts-autotag.utils")
local log = require("nvim-ts-autotag._log")
local api = vim.api
local M = {}
local helpers = {}
@ -16,21 +23,36 @@ function helpers.insert(text, is_replace)
helpers.feed("i" .. text, "x", is_replace)
end
utils.insert_char = function(text)
api.nvim_put({ text }, "c", true, true)
M.insert_char = function(text)
vim.api.nvim_put({ text }, "c", true, true)
end
utils.feed = function(text, num)
M.feed = function(text, num)
local result = ""
for _ = 1, num, 1 do
result = result .. text
end
api.nvim_feedkeys(api.nvim_replace_termcodes(result, true, false, true), "x", true)
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(result, true, false, true), "x", true)
end
_G.eq = assert.are.same
M.setup_nvim_treesitter = function(opts)
opts = vim.tbl_deep_extend("keep", opts or {}, {
ensure_installed = {
"html",
"javascript",
"typescript",
"svelte",
"vue",
"tsx",
"php",
"glimmer",
"rescript",
"embedded_template",
},
})
end
_G.Test_filter = function(data)
M.Test_filter = function(data)
local run_data = {}
for _, value in pairs(data) do
if value.only == true then
@ -50,34 +72,34 @@ local compare_text = function(linenr, text_after, name, cursor_add, end_cursor)
for i = 1, #text_after, 1 do
local t = string.gsub(text_after[i], "%|", "")
if t and new_text[i] and t:gsub("%s+$", "") ~= new_text[i]:gsub("%s+$", "") then
eq(t, new_text[i], "\n\n text error: " .. name .. "\n")
assert.are.same(t, new_text[i], "\n\n text error: " .. name .. "\n")
end
local p_after = string.find(text_after[i], "%|")
if p_after then
local row, col = utils.get_cursor()
if end_cursor then
eq(row, linenr + i - 2, "\n\n cursor row error: " .. name .. "\n")
eq(col + 1, end_cursor, "\n\n end cursor column error : " .. name .. "\n")
assert.are.same(row, linenr + i - 2, "\n\n cursor row error: " .. name .. "\n")
assert.are.same(col + 1, end_cursor, "\n\n end cursor column error : " .. name .. "\n")
else
eq(row, linenr + i - 2, "\n\n cursor row error: " .. name .. "\n")
assert.are.same(row, linenr + i - 2, "\n\n cursor row error: " .. name .. "\n")
p_after = p_after + cursor_add
eq(col, math.max(p_after - 2, 0), "\n\n cursor column error : " .. name .. "\n")
assert.are.same(col, math.max(p_after - 2, 0), "\n\n cursor column error : " .. name .. "\n")
end
end
end
return true
end
_G.Test_withfile = function(test_data, cb)
M.Test_withfile = function(test_data, cb)
for _, value in pairs(test_data) do
it("test " .. value.name, function(done)
it("test " .. value.name, function()
local text_before = {}
value.linenr = value.linenr or 1
local pos_before = {
linenr = value.linenr,
colnr = 0,
}
if not vim.tbl_islist(value.before) then
if not vim.islist(value.before) then
value.before = { value.before }
end
for index, text in pairs(value.before) do
@ -90,7 +112,7 @@ _G.Test_withfile = function(test_data, cb)
end
end
end
if not vim.tbl_islist(value.after) then
if not vim.islist(value.after) then
value.after = { value.after }
end
vim.bo.filetype = value.filetype or "text"
@ -137,14 +159,14 @@ _G.Test_withfile = function(test_data, cb)
end
end
_G.dump_node = function(node)
M.dump_node = function(node)
local text = utils.get_node_text(node)
for _, txt in pairs(text) do
log.debug(txt)
end
end
_G.dump_node_text = function(target)
M.dump_node_text = function(target)
for node in target:iter_children() do
local node_type = node:type()
local text = utils.get_node_text(node)
@ -152,3 +174,4 @@ _G.dump_node_text = function(target)
log.debug(text)
end
end
return M

77
tests/utils/paths.lua Normal file
View File

@ -0,0 +1,77 @@
local M = {}
---Search up from the current file until we find the full path of the base `tests/` directory and
---return it
---@param test_file string Filename to look for in each directory, if it exists then stop the search
---@return fun(): string dir A function wrapping the found directory
local function search_dir_up(test_file)
-- This is the path of the directory of the current file
local cur_dir = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":p")
---@diagnostic disable-next-line: param-type-mismatch
while not vim.uv.fs_stat(cur_dir .. "/" .. test_file, nil) and cur_dir ~= "/" do
cur_dir = vim.fn.fnamemodify(cur_dir, ":h")
end
if cur_dir == "/" then
error("Failed to locate the base 'tests/' directory!")
end
-- We return a wrapping function instead of a bare string so its easier to enforce "readonly"
-- uses of the searched directory
return function()
return cur_dir
end
end
--- WARN: DO NOT MUTATE THESE VALUES!
---
--- Table containing useful paths to directories within the plugin
M.static = {
--- Full path of the `tests/` directory
tests_dir = search_dir_up("test.lua"),
--- Full base path of the plugin where the `.git` directory resides
ts_autotag_dir = search_dir_up(".git"),
}
---@class nvim-ts-autotag.Root
---@field package path string
local Root = {
path = M.static.tests_dir(),
}
---@package
---@nodiscard
---@return nvim-ts-autotag.Root
function Root:new(o)
o = o or {}
setmetatable(o, self)
-- The last part of the path must be a "/" to work correctly
if self.path:sub(#self.path, #self.path) ~= "/" then
self.path = self.path .. "/"
end
self.__index = self
return o
end
--- Create a new instance, pushing the given path onto it
---@nodiscard
---@param path string The new path to add
---@return nvim-ts-autotag.Root
function Root:push(path)
local new_root = self:new()
-- Since we're extending the path, the current path must be a directory, ensure it ends in a "/"
if new_root.path:sub(#new_root.path) ~= "/" then
new_root.path = new_root.path .. "/"
end
new_root.path = new_root.path .. path
return new_root
end
--- Get the current path string
---@return string path
function Root:get()
return self.path
end
M.Root = Root:new()
return M

14
tests/utils/utils.lua Normal file
View File

@ -0,0 +1,14 @@
local path_utils = require("tests.utils.paths")
local M = {}
M.paths = path_utils
--- Register the main plugin (`nvim-ts-autotag`) on the runtimepath if it hasn't already been
--- registered
M.rtp_register_ts_autotag = function()
if not vim.list_contains(vim.opt.runtimepath, path_utils.static.ts_autotag_dir()) then
vim.opt.runtimepath:append(path_utils.static.ts_autotag_dir())
end
end
return M