Skip to content

feat(tests): Add baseline testing using Plenary's Busted hooks #106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: test
test:
nvim --headless --noplugin -u tests/mininit.lua -c "PlenaryBustedDirectory tests/neo-tree/ { minimal_init = 'tests/mininit.lua' }"

.PHONY: format
format:
stylua ./lua ./tests
75 changes: 75 additions & 0 deletions tests/helpers/util.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
local utils = {}

local Path = require("plenary.path")
local testdir = Path:new(vim.env.TMPDIR or "/tmp", "neo-tree-testing"):absolute()

local function rm_test_dir()
if vim.fn.isdirectory(testdir) == 1 then
vim.fn.delete(testdir, "rf")
end
end

utils.setup_test_fs = function()
rm_test_dir()

-- Need a list-style map here to ensure that things happen in the correct order.
--
-- When/if editing this, be cautious as (for now) other tests might be accessing
-- files from within this array by index
local fs = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a wrapper function for this, such that you can request a file from the tree in a simpler way (that is also more resistant to change in this list)?

Right now you have to:

local fs = utils.setup_test_fs()
local testfile = fs.content[3].abspath

which is obviously not the best thing because as soon as someone messes with the ordering here, all of those references break. One idea might be to give each file a "handle", and access it by the handle, so that a wrapper function can access it like:

local fs = utils.setup_test_ts()
local testfile = fs.get("top_level_file")

which would return a file that suffices the criteria that is being asked for. You could have handles like "empty_dir", "nested_file" and whatnot which might make this a bit better? This turns into a "yo dawg" issue, where you need tests for your tests and so on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the fs.get(id) method is a good idea. When specifying the nodes, there can be an id property which is optional, and will default to the relative path. makefs can then create a lookup table where the keys are that id and the values are the content items. I think this is simple enough that we don't need to worry about.

basedir = testdir,
content = {
{
name = "foo",
type = "dir",
content = {
{ name = "foofile1.txt", type = "file" },
{ name = "foofile2.txt", type = "file" },
},
},
{ name = "bar", type = "dir" },
{ name = "topfile1.txt", type = "file" },
},
}
local function makefs(content, basedir)
for _, info in ipairs(content) do
if info.type == "dir" then
info.abspath = Path:new(basedir, info.name):absolute()
vim.fn.mkdir(info.abspath, "p")
if info.content then
makefs(info.content, info.abspath)
end
elseif info.type == "file" then
info.abspath = Path:new(basedir, info.name):absolute()
vim.fn.writefile({}, info.abspath)
end
end
end
makefs(fs.content, testdir)
vim.cmd("tcd " .. testdir)
return fs
end

utils.teardown_test_fs = function()
rm_test_dir()
end

utils.clear_test_state = function()
-- TODO: Clear internal state?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cseickel thoughts on what is being done for cleanup here? It doesn't seem like quite enough as a lot of the internal state is still retained.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be very simple to add a manager._clear_state() method. This is where the decision to store all application state in a single object really pays off.

Another way to accomplish the same thing is by creating a new tab and closing the old one. Of course that in itself could have bugs which need to be tested. I think creating a manager._clear_state() is the best way to go.

vim.cmd("top new | wincmd o")
local keepbufnr = vim.api.nvim_get_current_buf()
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if bufnr ~= keepbufnr then
vim.api.nvim_buf_delete(bufnr, { force = true })
end
end
assert(#vim.api.nvim_tabpage_list_wins(0) == 1, "Failed to properly clear tab")
assert(#vim.api.nvim_list_bufs() == 1, "Failed to properly clear buffers")
end

utils.editfile = function(testfile)
vim.cmd("e " .. testfile)
assert.are.same(vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ":p"), vim.fn.fnamemodify(testfile, ":p"))
end

return utils
49 changes: 49 additions & 0 deletions tests/helpers/verify.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
local verify = {}

verify.eventually = function(timeout, assertfunc, failmsg, ...)
local success, args = false, { ... }
vim.wait(timeout or 1000, function()
success = assertfunc(unpack(args))
return success
end)
assert(success, failmsg)
end

verify.after = function(timeout, assertfunc, failmsg)
vim.wait(timeout, function()
return false
end)
assert(assertfunc(), failmsg)
end

verify.bufnr_is_not = function(start_bufnr, timeout)
verify.eventually(timeout or 500, function()
return start_bufnr ~= vim.api.nvim_get_current_buf()
end, string.format("Current buffer is '%s' when expected to not be", start_bufnr))
end

verify.tree_focused = function(timeout)
verify.eventually(timeout or 1000, function()
return vim.api.nvim_buf_get_option(0, "filetype") == "neo-tree"
end, "Current buffer is not a 'neo-tree' filetype")
end

verify.tree_node_is = function(expected_node_id, timeout)
verify.eventually(timeout or 500, function()
local state = require("neo-tree.sources.manager").get_state("filesystem")
local node = state.tree:get_node()
if not node then
return false
end
local node_id = node:get_id()
if node_id ~= expected_node_id then
return false
end
if state.position.node_id ~= expected_node_id then
return false
end
return true
end, string.format("Tree node '%s' not focused", expected_node_id))
end

return verify
24 changes: 24 additions & 0 deletions tests/mininit.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- Need the absolute path as when doing the testing we will issue things like `tcd` to change directory
-- to where our temporary filesystem lives
vim.opt.rtp = {
vim.fn.fnamemodify(vim.trim(vim.fn.system("git rev-parse --show-toplevel")), ":p"),
vim.env.VIMRUNTIME,
}

vim.cmd([[
packadd plenary.nvim
packadd nui.nvim
]])

require("neo-tree").setup()

vim.opt.swapfile = false

vim.cmd([[
runtime plugin/neo-tree.vim
]])

-- For debugging
P = function(...)
print(unpack(vim.tbl_map(vim.inspect, { ... })))
end