summaryrefslogtreecommitdiff
path: root/lua/99/test/files_spec.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/99/test/files_spec.lua')
-rw-r--r--lua/99/test/files_spec.lua323
1 files changed, 321 insertions, 2 deletions
diff --git a/lua/99/test/files_spec.lua b/lua/99/test/files_spec.lua
index e033abe..53d9458 100644
--- a/lua/99/test/files_spec.lua
+++ b/lua/99/test/files_spec.lua
@@ -33,13 +33,11 @@ describe("files", function()
paths[f.path] = f
end
- -- known fixture files must be present
assert.is_not_nil(paths["scratch/refresh.lua"])
assert.is_not_nil(paths["scratch/test.ts"])
eq("refresh.lua", paths["scratch/refresh.lua"].name)
eq("test.ts", paths["scratch/test.ts"].name)
- -- .git must be excluded
for path, _ in pairs(paths) do
assert.is_nil(
path:match("^%.git/"),
@@ -199,3 +197,324 @@ describe("files", function()
)
end)
end)
+
+describe("files git integration", function()
+ -- Mock storage
+ local _mocks = {
+ system_output = "",
+ system_exit = 0,
+ stat_type = nil,
+ stat_exists = false,
+ orig_system = nil,
+ orig_stat = nil,
+ orig_shell_error = nil,
+ system_calls = {},
+ }
+
+ before_each(function()
+ _mocks.orig_system = vim.fn.system
+ _mocks.orig_stat = vim.uv.fs_stat
+
+ _mocks.system_output = ""
+ _mocks.system_exit = 0
+ _mocks.stat_type = nil
+ _mocks.stat_exists = false
+ _mocks.system_calls = {}
+
+ vim.fn.system = function(cmd)
+ table.insert(_mocks.system_calls, cmd)
+
+ pcall(function()
+ rawset(vim.v, "shell_error", _mocks.system_exit)
+ end)
+ return _mocks.system_output
+ end
+
+ vim.uv.fs_stat = function(_path)
+ if _mocks.stat_exists then
+ return { type = _mocks.stat_type }
+ end
+ return nil
+ end
+
+ Files.set_project_root("/test/repo")
+ end)
+
+ after_each(function()
+ vim.fn.system = _mocks.orig_system
+ vim.uv.fs_stat = _mocks.orig_stat
+
+ pcall(function()
+ rawset(vim.v, "shell_error", 0)
+ end)
+ Files.set_project_root("")
+ end)
+
+ it(
+ "detects git repo with .git directory and uses git-based discovery",
+ function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = "README.md\n"
+ _mocks.system_exit = 0
+
+ local files = Files.discover_files()
+
+ assert.is_true(
+ #_mocks.system_calls > 0,
+ "git command should have been called"
+ )
+ local git_called = false
+ for _, cmd in ipairs(_mocks.system_calls) do
+ if cmd:match("git.*ls%-files") then
+ git_called = true
+ break
+ end
+ end
+ assert.is_true(git_called, "git ls-files should have been executed")
+
+ assert.is_true(#files > 0, "should return files from git")
+ eq("README.md", files[1].path)
+ end
+ )
+
+ it(
+ "detects git repo with .git file (worktree) and uses git-based discovery",
+ function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "file"
+ _mocks.system_output = "src/main.lua\ntest/file.lua\n"
+ _mocks.system_exit = 0
+
+ local files = Files.discover_files()
+
+ assert.is_true(
+ #_mocks.system_calls > 0,
+ "git command should have been called for worktree"
+ )
+ local git_called = false
+ for _, cmd in ipairs(_mocks.system_calls) do
+ if cmd:match("git.*ls%-files") then
+ git_called = true
+ break
+ end
+ end
+ assert.is_true(git_called, "should use git for worktree .git file")
+
+ assert.are.equal(2, #files)
+ eq("src/main.lua", files[1].path)
+ eq("test/file.lua", files[2].path)
+ end
+ )
+
+ it("returns files when git command succeeds", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = "README.md\nsrc/init.lua\nsrc/utils.lua\n"
+ _mocks.system_exit = 0
+
+ local files = Files.discover_files()
+
+ assert.are.equal(3, #files)
+ eq("README.md", files[1].path)
+ eq("src/init.lua", files[2].path)
+ eq("src/utils.lua", files[3].path)
+
+ for _, f in ipairs(files) do
+ assert.is_not_nil(f.path, "file should have path")
+ assert.is_not_nil(f.name, "file should have name")
+ assert.is_not_nil(f.absolute_path, "file should have absolute_path")
+ end
+ end)
+
+ it("returns empty table (not nil) for empty repo", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = ""
+ _mocks.system_exit = 0
+
+ local files = Files.discover_files()
+
+ assert.is_not_nil(files, "should return table, not nil")
+ assert.are.equal(0, #files, "should return empty table for empty repo")
+
+ for _, cmd in ipairs(_mocks.system_calls) do
+ assert.is_true(
+ cmd:match("git") ~= nil,
+ "should only call git, not fs commands"
+ )
+ end
+ end)
+
+ it("returns nil on git command failure", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = "fatal: not a git repository"
+ _mocks.system_exit = 128 -- Git failure
+
+ local orig_scandir = vim.uv.fs_scandir
+ vim.uv.fs_scandir = function(_dir)
+ return nil -- Empty directory
+ end
+
+ local files = Files.discover_files()
+
+ vim.uv.fs_scandir = orig_scandir
+
+ local git_failed = false
+ for _, cmd in ipairs(_mocks.system_calls) do
+ if cmd:match("git") and vim.v.shell_error ~= 0 then
+ git_failed = true
+ break
+ end
+ end
+ assert.is_true(git_failed, "git should have been called and failed")
+ end)
+
+ it("applies manual excludes on top of git output", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output =
+ "README.md\n.env\n.env.local\nnode_modules/package.json\nsrc/main.lua\n"
+ _mocks.system_exit = 0
+
+ Files.setup({
+ enabled = true,
+ exclude = { ".env", ".env.*", "node_modules" },
+ }, {})
+
+ local files = Files.discover_files()
+
+ assert.are.equal(2, #files)
+ eq("README.md", files[1].path)
+ eq("src/main.lua", files[2].path)
+
+ for _, f in ipairs(files) do
+ assert.is_nil(f.path:match("%.env"), ".env files should be excluded")
+ assert.is_nil(
+ f.path:match("node_modules"),
+ "node_modules should be excluded"
+ )
+ end
+ end)
+
+ it(
+ "excludes files by filename (not just path) consistent with fs scanner",
+ function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = "README.md\nsrc/builder.js\nsrc/build/config.lua\n"
+ _mocks.system_exit = 0
+
+ Files.setup({
+ enabled = true,
+ exclude = { "build" },
+ }, {})
+
+ local files = Files.discover_files()
+
+ assert.are.equal(1, #files)
+ eq("README.md", files[1].path)
+
+ for _, f in ipairs(files) do
+ assert.is_nil(
+ f.name:match("^build"),
+ "files starting with 'build' should be excluded"
+ )
+ end
+ end
+ )
+
+ it("uses --deduplicate flag to handle merge conflict duplicates", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+
+ _mocks.system_output = "README.md\nREADME.md\nREADME.md\n"
+ _mocks.system_exit = 0
+
+ local files = Files.discover_files()
+
+ local has_deduplicate = false
+ for _, cmd in ipairs(_mocks.system_calls) do
+ if cmd:match("%-%-deduplicate") then
+ has_deduplicate = true
+ break
+ end
+ end
+ assert.is_true(
+ has_deduplicate,
+ "git command should include --deduplicate flag"
+ )
+ end)
+
+ it("uses git-based discovery in git repo", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = "tracked.txt\n"
+ _mocks.system_exit = 0
+
+ Files.discover_files()
+
+ local git_called = false
+ for _, cmd in ipairs(_mocks.system_calls) do
+ if cmd:match("git") then
+ git_called = true
+ break
+ end
+ end
+ assert.is_true(git_called, "should use git ls-files in git repo")
+ end)
+
+ it("falls back to filesystem when not in git repo", function()
+ _mocks.stat_exists = false
+ local orig_scandir = vim.uv.fs_scandir
+ local fs_called = false
+ vim.uv.fs_scandir = function(_dir)
+ fs_called = true
+ return nil -- Empty
+ end
+
+ Files.discover_files()
+
+ vim.uv.fs_scandir = orig_scandir
+
+ for _, cmd in ipairs(_mocks.system_calls) do
+ assert.is_nil(
+ cmd:match("git"),
+ "git should not be called in non-git repo"
+ )
+ end
+
+ assert.is_true(fs_called, "filesystem fallback should be used")
+ end)
+
+ it("returns cached files on subsequent calls", function()
+ _mocks.stat_exists = true
+ _mocks.stat_type = "directory"
+ _mocks.system_output = "file.txt\n"
+ _mocks.system_exit = 0
+
+ local first = Files.get_files()
+ local first_call_count = #_mocks.system_calls
+
+ local second = Files.get_files()
+ local second_call_count = #_mocks.system_calls
+
+ eq(first, second)
+
+ assert.are.equal(
+ first_call_count,
+ second_call_count,
+ "should not re-scan, use cache"
+ )
+ end)
+
+ it("handles empty root gracefully", function()
+ Files.set_project_root("")
+
+ local files = Files.discover_files()
+
+ assert.is_not_nil(files, "should return table")
+ assert.are.equal(0, #files, "should return empty for empty root")
+ end)
+end)