diff --git a/lua/neotest/lib/file/init.lua b/lua/neotest/lib/file/init.lua index 274d7d00..507b0aa3 100644 --- a/lua/neotest/lib/file/init.lua +++ b/lua/neotest/lib/file/init.lua @@ -129,11 +129,33 @@ function M.parse_dir_from_files(root, files) paths[elem.path] = elem end local sorted_paths = { { path = "", type = "dir" } } + + ---We use a custom string compare because it allows us to keep children under their directories + ---For example with default string compare "x", "x/y", "x.z" would be sorted to "x", "x.z", "x/y" which then breaks + ---paths_to_positions function because it assumes children are directly after their parents. + ---@param x string + ---@param y string + local function path_compare(x, y) + local target = string.byte("/") + for i = 1, math.min(#x, #y) do + local xe, ye = x:byte(i), y:byte(i) + if xe ~= ye then + if xe == target then + return true + elseif ye == target then + return false + end + return xe < ye + end + end + return #x < #y + end + for _, elem in pairs(paths) do table.insert(sorted_paths, elem) end table.sort(sorted_paths, function(a, b) - return a.path < b.path + return path_compare(a.path, b.path) end) return Tree.from_list(parse_tree(paths_to_positions(sorted_paths)), function(pos) return pos.id diff --git a/tests/unit/lib/files/init_spec.lua b/tests/unit/lib/files/init_spec.lua new file mode 100644 index 00000000..b5e3b803 --- /dev/null +++ b/tests/unit/lib/files/init_spec.lua @@ -0,0 +1,120 @@ +local files = require("neotest.lib").files +A = function(...) + print(vim.inspect(...)) +end + +describe("files library", function() + describe("parsing directory tree from files", function() + it("places files under the root", function() + local root = "/root" + local result = files.parse_dir_from_files(root, { "/root/test_a", "/root/test_b" }) + assert.same(result:to_list(), { + { + id = "/root", + name = "root", + path = "/root", + type = "dir", + }, + { + { + id = "/root/test_a", + name = "test_a", + path = "/root/test_a", + type = "file", + }, + }, + { + { + id = "/root/test_b", + name = "test_b", + path = "/root/test_b", + type = "file", + }, + }, + }) + end) + + it("places files under the parent directory", function() + local root = "/root" + local result = files.parse_dir_from_files(root, { "/root/dir/test_a", "/root/test_b" }) + assert.same(result:to_list(), { + { + id = "/root", + name = "root", + path = "/root", + type = "dir", + }, + { + { + id = "/root/dir", + name = "dir", + path = "/root/dir", + type = "dir", + }, + { + { + id = "/root/dir/test_a", + name = "test_a", + path = "/root/dir/test_a", + type = "file", + }, + }, + }, + { + { + id = "/root/test_b", + name = "test_b", + path = "/root/test_b", + type = "file", + }, + }, + }) + end) + + it("parses directory and file with same name", function() + local root = "/root" + local result = + files.parse_dir_from_files(root, { "/root/dir.py", "/root/dir/test_a", "/root/test_b" }) + assert.same(result:to_list(), { + { + id = "/root", + name = "root", + path = "/root", + type = "dir", + }, + { + { + id = "/root/dir", + name = "dir", + path = "/root/dir", + type = "dir", + }, + { + { + id = "/root/dir/test_a", + name = "test_a", + path = "/root/dir/test_a", + type = "file", + }, + }, + }, + { + { + id = "/root/dir.py", + name = "dir.py", + path = "/root/dir.py", + type = "file", + }, + }, + { + { + id = "/root/test_b", + name = "test_b", + path = "/root/test_b", + type = "file", + }, + }, + }) + end) + end) +end)