Adding autocomplete and copilot suggestions

This commit is contained in:
2026-01-14 21:43:56 -05:00
parent 5493a5ec38
commit 84c8bcf92c
55 changed files with 11823 additions and 550 deletions

View File

@@ -0,0 +1,229 @@
--- Tests for ask intent detection
local intent = require("codetyper.ask.intent")
describe("ask.intent", function()
describe("detect", function()
-- Ask/Explain intent tests
describe("ask intent", function()
it("detects 'what' questions as ask", function()
local result = intent.detect("What does this function do?")
assert.equals("ask", result.type)
assert.is_true(result.confidence > 0.3)
end)
it("detects 'why' questions as ask", function()
local result = intent.detect("Why is this variable undefined?")
assert.equals("ask", result.type)
end)
it("detects 'how does' as ask", function()
local result = intent.detect("How does this algorithm work?")
assert.is_true(result.type == "ask" or result.type == "explain")
end)
it("detects 'explain' requests as explain", function()
local result = intent.detect("Explain me the project structure")
assert.equals("explain", result.type)
assert.is_true(result.confidence > 0.4)
end)
it("detects 'walk me through' as explain", function()
local result = intent.detect("Walk me through this code")
assert.equals("explain", result.type)
end)
it("detects questions ending with ? as likely ask", function()
local result = intent.detect("Is this the right approach?")
assert.equals("ask", result.type)
end)
it("sets needs_brain_context for ask intent", function()
local result = intent.detect("What patterns are used here?")
assert.is_true(result.needs_brain_context)
end)
end)
-- Generate intent tests
describe("generate intent", function()
it("detects 'create' commands as generate", function()
local result = intent.detect("Create a function to sort arrays")
assert.equals("generate", result.type)
end)
it("detects 'write' commands as generate", function()
local result = intent.detect("Write a unit test for this module")
-- Could be generate or test
assert.is_true(result.type == "generate" or result.type == "test")
end)
it("detects 'implement' as generate", function()
local result = intent.detect("Implement a binary search")
assert.equals("generate", result.type)
assert.is_true(result.confidence > 0.4)
end)
it("detects 'add' commands as generate", function()
local result = intent.detect("Add error handling to this function")
assert.equals("generate", result.type)
end)
it("detects 'fix' as generate", function()
local result = intent.detect("Fix the bug in line 42")
assert.equals("generate", result.type)
end)
end)
-- Refactor intent tests
describe("refactor intent", function()
it("detects explicit 'refactor' as refactor", function()
local result = intent.detect("Refactor this function")
assert.equals("refactor", result.type)
end)
it("detects 'clean up' as refactor", function()
local result = intent.detect("Clean up this messy code")
assert.equals("refactor", result.type)
end)
it("detects 'simplify' as refactor", function()
local result = intent.detect("Simplify this logic")
assert.equals("refactor", result.type)
end)
end)
-- Document intent tests
describe("document intent", function()
it("detects 'document' as document", function()
local result = intent.detect("Document this function")
assert.equals("document", result.type)
end)
it("detects 'add documentation' as document", function()
local result = intent.detect("Add documentation to this class")
assert.equals("document", result.type)
end)
it("detects 'add jsdoc' as document", function()
local result = intent.detect("Add jsdoc comments")
assert.equals("document", result.type)
end)
end)
-- Test intent tests
describe("test intent", function()
it("detects 'write tests for' as test", function()
local result = intent.detect("Write tests for this module")
assert.equals("test", result.type)
end)
it("detects 'add unit tests' as test", function()
local result = intent.detect("Add unit tests for the parser")
assert.equals("test", result.type)
end)
it("detects 'generate tests' as test", function()
local result = intent.detect("Generate tests for the API")
assert.equals("test", result.type)
end)
end)
-- Project context tests
describe("project context detection", function()
it("detects 'project' as needing project context", function()
local result = intent.detect("Explain the project architecture")
assert.is_true(result.needs_project_context)
end)
it("detects 'codebase' as needing project context", function()
local result = intent.detect("How is the codebase organized?")
assert.is_true(result.needs_project_context)
end)
it("does not need project context for simple questions", function()
local result = intent.detect("What does this variable mean?")
assert.is_false(result.needs_project_context)
end)
end)
-- Exploration tests
describe("exploration detection", function()
it("detects 'explain me the project' as needing exploration", function()
local result = intent.detect("Explain me the project")
assert.is_true(result.needs_exploration)
end)
it("detects 'explain the codebase' as needing exploration", function()
local result = intent.detect("Explain the codebase structure")
assert.is_true(result.needs_exploration)
end)
it("detects 'explore project' as needing exploration", function()
local result = intent.detect("Explore this project")
assert.is_true(result.needs_exploration)
end)
it("does not need exploration for simple questions", function()
local result = intent.detect("What does this function do?")
assert.is_false(result.needs_exploration)
end)
end)
end)
describe("get_prompt_type", function()
it("maps ask to ask", function()
local result = intent.get_prompt_type({ type = "ask" })
assert.equals("ask", result)
end)
it("maps explain to ask", function()
local result = intent.get_prompt_type({ type = "explain" })
assert.equals("ask", result)
end)
it("maps generate to code_generation", function()
local result = intent.get_prompt_type({ type = "generate" })
assert.equals("code_generation", result)
end)
it("maps refactor to refactor", function()
local result = intent.get_prompt_type({ type = "refactor" })
assert.equals("refactor", result)
end)
it("maps document to document", function()
local result = intent.get_prompt_type({ type = "document" })
assert.equals("document", result)
end)
it("maps test to test", function()
local result = intent.get_prompt_type({ type = "test" })
assert.equals("test", result)
end)
end)
describe("produces_code", function()
it("returns false for ask", function()
assert.is_false(intent.produces_code({ type = "ask" }))
end)
it("returns false for explain", function()
assert.is_false(intent.produces_code({ type = "explain" }))
end)
it("returns true for generate", function()
assert.is_true(intent.produces_code({ type = "generate" }))
end)
it("returns true for refactor", function()
assert.is_true(intent.produces_code({ type = "refactor" }))
end)
it("returns true for document", function()
assert.is_true(intent.produces_code({ type = "document" }))
end)
it("returns true for test", function()
assert.is_true(intent.produces_code({ type = "test" }))
end)
end)
end)

View File

@@ -0,0 +1,252 @@
--- Tests for brain/delta modules
describe("brain.delta", function()
local diff
local commit
local storage
local types
local test_root = "/tmp/codetyper_test_" .. os.time()
before_each(function()
-- Clear module cache
package.loaded["codetyper.brain.delta.diff"] = nil
package.loaded["codetyper.brain.delta.commit"] = nil
package.loaded["codetyper.brain.storage"] = nil
package.loaded["codetyper.brain.types"] = nil
diff = require("codetyper.brain.delta.diff")
commit = require("codetyper.brain.delta.commit")
storage = require("codetyper.brain.storage")
types = require("codetyper.brain.types")
storage.clear_cache()
vim.fn.mkdir(test_root, "p")
storage.ensure_dirs(test_root)
-- Mock get_project_root
local utils = require("codetyper.utils")
utils.get_project_root = function()
return test_root
end
end)
after_each(function()
vim.fn.delete(test_root, "rf")
storage.clear_cache()
end)
describe("diff.compute", function()
it("detects added values", function()
local diffs = diff.compute(nil, { a = 1 })
assert.equals(1, #diffs)
assert.equals("add", diffs[1].op)
end)
it("detects deleted values", function()
local diffs = diff.compute({ a = 1 }, nil)
assert.equals(1, #diffs)
assert.equals("delete", diffs[1].op)
end)
it("detects replaced values", function()
local diffs = diff.compute({ a = 1 }, { a = 2 })
assert.equals(1, #diffs)
assert.equals("replace", diffs[1].op)
assert.equals(1, diffs[1].from)
assert.equals(2, diffs[1].to)
end)
it("detects nested changes", function()
local before = { a = { b = 1 } }
local after = { a = { b = 2 } }
local diffs = diff.compute(before, after)
assert.equals(1, #diffs)
assert.equals("a.b", diffs[1].path)
end)
it("returns empty for identical values", function()
local diffs = diff.compute({ a = 1 }, { a = 1 })
assert.equals(0, #diffs)
end)
end)
describe("diff.apply", function()
it("applies add operation", function()
local base = { a = 1 }
local diffs = { { op = "add", path = "b", value = 2 } }
local result = diff.apply(base, diffs)
assert.equals(2, result.b)
end)
it("applies replace operation", function()
local base = { a = 1 }
local diffs = { { op = "replace", path = "a", to = 2 } }
local result = diff.apply(base, diffs)
assert.equals(2, result.a)
end)
it("applies delete operation", function()
local base = { a = 1, b = 2 }
local diffs = { { op = "delete", path = "a" } }
local result = diff.apply(base, diffs)
assert.is_nil(result.a)
assert.equals(2, result.b)
end)
it("applies nested changes", function()
local base = { a = { b = 1 } }
local diffs = { { op = "replace", path = "a.b", to = 2 } }
local result = diff.apply(base, diffs)
assert.equals(2, result.a.b)
end)
end)
describe("diff.reverse", function()
it("reverses add to delete", function()
local diffs = { { op = "add", path = "a", value = 1 } }
local reversed = diff.reverse(diffs)
assert.equals("delete", reversed[1].op)
end)
it("reverses delete to add", function()
local diffs = { { op = "delete", path = "a", value = 1 } }
local reversed = diff.reverse(diffs)
assert.equals("add", reversed[1].op)
end)
it("reverses replace", function()
local diffs = { { op = "replace", path = "a", from = 1, to = 2 } }
local reversed = diff.reverse(diffs)
assert.equals("replace", reversed[1].op)
assert.equals(2, reversed[1].from)
assert.equals(1, reversed[1].to)
end)
end)
describe("diff.equals", function()
it("returns true for identical states", function()
assert.is_true(diff.equals({ a = 1 }, { a = 1 }))
end)
it("returns false for different states", function()
assert.is_false(diff.equals({ a = 1 }, { a = 2 }))
end)
end)
describe("commit.create", function()
it("creates a delta commit", function()
local changes = {
{ op = "add", path = "test.node1", ah = "abc123" },
}
local delta = commit.create(changes, "Test commit", "test")
assert.is_not_nil(delta)
assert.is_not_nil(delta.h)
assert.equals("Test commit", delta.m.msg)
assert.equals(1, #delta.ch)
end)
it("updates HEAD", function()
local changes = { { op = "add", path = "test.node1", ah = "abc123" } }
local delta = commit.create(changes, "Test", "test")
local head = storage.get_head(test_root)
assert.equals(delta.h, head)
end)
it("links to parent", function()
local changes1 = { { op = "add", path = "test.node1", ah = "abc123" } }
local delta1 = commit.create(changes1, "First", "test")
local changes2 = { { op = "add", path = "test.node2", ah = "def456" } }
local delta2 = commit.create(changes2, "Second", "test")
assert.equals(delta1.h, delta2.p)
end)
it("returns nil for empty changes", function()
local delta = commit.create({}, "Empty")
assert.is_nil(delta)
end)
end)
describe("commit.get", function()
it("retrieves created delta", function()
local changes = { { op = "add", path = "test.node1", ah = "abc123" } }
local created = commit.create(changes, "Test", "test")
local retrieved = commit.get(created.h)
assert.is_not_nil(retrieved)
assert.equals(created.h, retrieved.h)
end)
it("returns nil for non-existent delta", function()
local retrieved = commit.get("nonexistent")
assert.is_nil(retrieved)
end)
end)
describe("commit.get_history", function()
it("returns delta chain", function()
commit.create({ { op = "add", path = "node1", ah = "1" } }, "First", "test")
commit.create({ { op = "add", path = "node2", ah = "2" } }, "Second", "test")
commit.create({ { op = "add", path = "node3", ah = "3" } }, "Third", "test")
local history = commit.get_history(10)
assert.equals(3, #history)
assert.equals("Third", history[1].m.msg)
assert.equals("Second", history[2].m.msg)
assert.equals("First", history[3].m.msg)
end)
it("respects limit", function()
for i = 1, 5 do
commit.create({ { op = "add", path = "node" .. i, ah = tostring(i) } }, "Commit " .. i, "test")
end
local history = commit.get_history(3)
assert.equals(3, #history)
end)
end)
describe("commit.summarize", function()
it("summarizes delta statistics", function()
local changes = {
{ op = "add", path = "nodes.a" },
{ op = "add", path = "nodes.b" },
{ op = "mod", path = "nodes.c" },
{ op = "del", path = "nodes.d" },
}
local delta = commit.create(changes, "Test", "test")
local summary = commit.summarize(delta)
assert.equals(2, summary.stats.adds)
assert.equals(4, summary.stats.total)
assert.is_true(vim.tbl_contains(summary.categories, "nodes"))
end)
end)
end)

View File

@@ -0,0 +1,128 @@
--- Tests for brain/hash.lua
describe("brain.hash", function()
local hash
before_each(function()
package.loaded["codetyper.brain.hash"] = nil
hash = require("codetyper.brain.hash")
end)
describe("compute", function()
it("returns 8-character hash", function()
local result = hash.compute("test string")
assert.equals(8, #result)
end)
it("returns consistent hash for same input", function()
local result1 = hash.compute("test")
local result2 = hash.compute("test")
assert.equals(result1, result2)
end)
it("returns different hash for different input", function()
local result1 = hash.compute("test1")
local result2 = hash.compute("test2")
assert.not_equals(result1, result2)
end)
it("handles empty string", function()
local result = hash.compute("")
assert.equals("00000000", result)
end)
it("handles nil", function()
local result = hash.compute(nil)
assert.equals("00000000", result)
end)
end)
describe("compute_table", function()
it("hashes table as JSON", function()
local result = hash.compute_table({ a = 1, b = 2 })
assert.equals(8, #result)
end)
it("returns consistent hash for same table", function()
local result1 = hash.compute_table({ x = "y" })
local result2 = hash.compute_table({ x = "y" })
assert.equals(result1, result2)
end)
end)
describe("node_id", function()
it("generates ID with correct format", function()
local id = hash.node_id("pat", "test content")
assert.truthy(id:match("^n_pat_%d+_%w+$"))
end)
it("generates unique IDs", function()
local id1 = hash.node_id("pat", "test1")
local id2 = hash.node_id("pat", "test2")
assert.not_equals(id1, id2)
end)
end)
describe("edge_id", function()
it("generates ID with correct format", function()
local id = hash.edge_id("source_node", "target_node")
assert.truthy(id:match("^e_%w+_%w+$"))
end)
it("returns same ID for same source/target", function()
local id1 = hash.edge_id("s1", "t1")
local id2 = hash.edge_id("s1", "t1")
assert.equals(id1, id2)
end)
end)
describe("delta_hash", function()
it("generates 8-character hash", function()
local changes = { { op = "add", path = "test" } }
local result = hash.delta_hash(changes, "parent", 12345)
assert.equals(8, #result)
end)
it("includes parent in hash", function()
local changes = { { op = "add", path = "test" } }
local result1 = hash.delta_hash(changes, "parent1", 12345)
local result2 = hash.delta_hash(changes, "parent2", 12345)
assert.not_equals(result1, result2)
end)
end)
describe("path_hash", function()
it("returns 8-character hash", function()
local result = hash.path_hash("/path/to/file.lua")
assert.equals(8, #result)
end)
end)
describe("matches", function()
it("returns true for matching hashes", function()
assert.is_true(hash.matches("abc12345", "abc12345"))
end)
it("returns false for different hashes", function()
assert.is_false(hash.matches("abc12345", "def67890"))
end)
end)
describe("random", function()
it("returns 8-character string", function()
local result = hash.random()
assert.equals(8, #result)
end)
it("generates different values", function()
local result1 = hash.random()
local result2 = hash.random()
-- Note: There's a tiny chance these could match, but very unlikely
assert.not_equals(result1, result2)
end)
it("contains only hex characters", function()
local result = hash.random()
assert.truthy(result:match("^[0-9a-f]+$"))
end)
end)
end)

View File

@@ -0,0 +1,234 @@
--- Tests for brain/graph/node.lua
describe("brain.graph.node", function()
local node
local storage
local types
local test_root = "/tmp/codetyper_test_" .. os.time()
before_each(function()
-- Clear module cache
package.loaded["codetyper.brain.graph.node"] = nil
package.loaded["codetyper.brain.storage"] = nil
package.loaded["codetyper.brain.types"] = nil
package.loaded["codetyper.brain.hash"] = nil
storage = require("codetyper.brain.storage")
types = require("codetyper.brain.types")
node = require("codetyper.brain.graph.node")
storage.clear_cache()
vim.fn.mkdir(test_root, "p")
storage.ensure_dirs(test_root)
-- Mock get_project_root
local utils = require("codetyper.utils")
utils.get_project_root = function()
return test_root
end
end)
after_each(function()
vim.fn.delete(test_root, "rf")
storage.clear_cache()
node.pending = {}
end)
describe("create", function()
it("creates a new node with correct structure", function()
local created = node.create(types.NODE_TYPES.PATTERN, {
s = "Test pattern summary",
d = "Test pattern detail",
}, {
f = "test.lua",
})
assert.is_not_nil(created.id)
assert.equals(types.NODE_TYPES.PATTERN, created.t)
assert.equals("Test pattern summary", created.c.s)
assert.equals("test.lua", created.ctx.f)
assert.equals(0.5, created.sc.w)
assert.equals(0, created.sc.u)
end)
it("generates unique IDs", function()
local node1 = node.create(types.NODE_TYPES.PATTERN, { s = "First" }, {})
local node2 = node.create(types.NODE_TYPES.PATTERN, { s = "Second" }, {})
assert.is_not_nil(node1.id)
assert.is_not_nil(node2.id)
assert.not_equals(node1.id, node2.id)
end)
it("updates meta node count", function()
local meta_before = storage.get_meta(test_root)
local count_before = meta_before.nc
node.create(types.NODE_TYPES.PATTERN, { s = "Test" }, {})
local meta_after = storage.get_meta(test_root)
assert.equals(count_before + 1, meta_after.nc)
end)
it("tracks pending change", function()
node.pending = {}
node.create(types.NODE_TYPES.PATTERN, { s = "Test" }, {})
assert.equals(1, #node.pending)
assert.equals("add", node.pending[1].op)
end)
end)
describe("get", function()
it("retrieves created node", function()
local created = node.create(types.NODE_TYPES.PATTERN, { s = "Test" }, {})
local retrieved = node.get(created.id)
assert.is_not_nil(retrieved)
assert.equals(created.id, retrieved.id)
assert.equals("Test", retrieved.c.s)
end)
it("returns nil for non-existent node", function()
local retrieved = node.get("n_pat_0_nonexistent")
assert.is_nil(retrieved)
end)
end)
describe("update", function()
it("updates node content", function()
local created = node.create(types.NODE_TYPES.PATTERN, { s = "Original" }, {})
node.update(created.id, { c = { s = "Updated" } })
local updated = node.get(created.id)
assert.equals("Updated", updated.c.s)
end)
it("updates node scores", function()
local created = node.create(types.NODE_TYPES.PATTERN, { s = "Test" }, {})
node.update(created.id, { sc = { w = 0.9 } })
local updated = node.get(created.id)
assert.equals(0.9, updated.sc.w)
end)
it("increments version", function()
local created = node.create(types.NODE_TYPES.PATTERN, { s = "Test" }, {})
local original_version = created.m.v
node.update(created.id, { c = { s = "Updated" } })
local updated = node.get(created.id)
assert.equals(original_version + 1, updated.m.v)
end)
it("returns nil for non-existent node", function()
local result = node.update("n_pat_0_nonexistent", { c = { s = "Test" } })
assert.is_nil(result)
end)
end)
describe("delete", function()
it("removes node", function()
local created = node.create(types.NODE_TYPES.PATTERN, { s = "Test" }, {})
local result = node.delete(created.id)
assert.is_true(result)
assert.is_nil(node.get(created.id))
end)
it("decrements meta node count", function()
local created = node.create(types.NODE_TYPES.PATTERN, { s = "Test" }, {})
local meta_before = storage.get_meta(test_root)
local count_before = meta_before.nc
node.delete(created.id)
local meta_after = storage.get_meta(test_root)
assert.equals(count_before - 1, meta_after.nc)
end)
it("returns false for non-existent node", function()
local result = node.delete("n_pat_0_nonexistent")
assert.is_false(result)
end)
end)
describe("find", function()
it("finds nodes by type", function()
node.create(types.NODE_TYPES.PATTERN, { s = "Pattern 1" }, {})
node.create(types.NODE_TYPES.PATTERN, { s = "Pattern 2" }, {})
node.create(types.NODE_TYPES.CORRECTION, { s = "Correction 1" }, {})
local patterns = node.find({ types = { types.NODE_TYPES.PATTERN } })
assert.equals(2, #patterns)
end)
it("finds nodes by file", function()
node.create(types.NODE_TYPES.PATTERN, { s = "Test 1" }, { f = "file1.lua" })
node.create(types.NODE_TYPES.PATTERN, { s = "Test 2" }, { f = "file2.lua" })
node.create(types.NODE_TYPES.PATTERN, { s = "Test 3" }, { f = "file1.lua" })
local found = node.find({ file = "file1.lua" })
assert.equals(2, #found)
end)
it("finds nodes by query", function()
node.create(types.NODE_TYPES.PATTERN, { s = "Foo bar baz" }, {})
node.create(types.NODE_TYPES.PATTERN, { s = "Something else" }, {})
local found = node.find({ query = "foo" })
assert.equals(1, #found)
assert.equals("Foo bar baz", found[1].c.s)
end)
it("respects limit", function()
for i = 1, 10 do
node.create(types.NODE_TYPES.PATTERN, { s = "Node " .. i }, {})
end
local found = node.find({ limit = 5 })
assert.equals(5, #found)
end)
end)
describe("record_usage", function()
it("increments usage count", function()
local created = node.create(types.NODE_TYPES.PATTERN, { s = "Test" }, {})
node.record_usage(created.id, true)
local updated = node.get(created.id)
assert.equals(1, updated.sc.u)
end)
it("updates success rate", function()
local created = node.create(types.NODE_TYPES.PATTERN, { s = "Test" }, {})
node.record_usage(created.id, true)
node.record_usage(created.id, false)
local updated = node.get(created.id)
assert.equals(0.5, updated.sc.sr)
end)
end)
describe("get_and_clear_pending", function()
it("returns and clears pending changes", function()
node.pending = {}
node.create(types.NODE_TYPES.PATTERN, { s = "Test" }, {})
local pending = node.get_and_clear_pending()
assert.equals(1, #pending)
assert.equals(0, #node.pending)
end)
end)
end)

View File

@@ -0,0 +1,173 @@
--- Tests for brain/storage.lua
describe("brain.storage", function()
local storage
local test_root = "/tmp/codetyper_test_" .. os.time()
before_each(function()
-- Clear module cache to get fresh state
package.loaded["codetyper.brain.storage"] = nil
package.loaded["codetyper.brain.types"] = nil
storage = require("codetyper.brain.storage")
-- Clear cache before each test
storage.clear_cache()
-- Create test directory
vim.fn.mkdir(test_root, "p")
end)
after_each(function()
-- Clean up test directory
vim.fn.delete(test_root, "rf")
storage.clear_cache()
end)
describe("get_brain_dir", function()
it("returns correct path", function()
local dir = storage.get_brain_dir(test_root)
assert.equals(test_root .. "/.coder/brain", dir)
end)
end)
describe("ensure_dirs", function()
it("creates required directories", function()
local result = storage.ensure_dirs(test_root)
assert.is_true(result)
-- Check directories exist
assert.equals(1, vim.fn.isdirectory(test_root .. "/.coder/brain"))
assert.equals(1, vim.fn.isdirectory(test_root .. "/.coder/brain/nodes"))
assert.equals(1, vim.fn.isdirectory(test_root .. "/.coder/brain/indices"))
assert.equals(1, vim.fn.isdirectory(test_root .. "/.coder/brain/deltas"))
assert.equals(1, vim.fn.isdirectory(test_root .. "/.coder/brain/deltas/objects"))
end)
end)
describe("get_path", function()
it("returns correct path for simple key", function()
local path = storage.get_path("meta", test_root)
assert.equals(test_root .. "/.coder/brain/meta.json", path)
end)
it("returns correct path for nested key", function()
local path = storage.get_path("nodes.patterns", test_root)
assert.equals(test_root .. "/.coder/brain/nodes/patterns.json", path)
end)
it("returns correct path for deeply nested key", function()
local path = storage.get_path("deltas.objects.abc123", test_root)
assert.equals(test_root .. "/.coder/brain/deltas/objects/abc123.json", path)
end)
end)
describe("save and load", function()
it("saves and loads data correctly", function()
storage.ensure_dirs(test_root)
local data = { test = "value", count = 42 }
storage.save("meta", data, test_root, true) -- immediate
-- Clear cache and reload
storage.clear_cache()
local loaded = storage.load("meta", test_root)
assert.equals("value", loaded.test)
assert.equals(42, loaded.count)
end)
it("returns empty table for missing files", function()
storage.ensure_dirs(test_root)
local loaded = storage.load("nonexistent", test_root)
assert.same({}, loaded)
end)
end)
describe("get_meta", function()
it("creates default meta if not exists", function()
storage.ensure_dirs(test_root)
local meta = storage.get_meta(test_root)
assert.is_not_nil(meta.v)
assert.equals(0, meta.nc)
assert.equals(0, meta.ec)
assert.equals(0, meta.dc)
end)
end)
describe("update_meta", function()
it("updates meta values", function()
storage.ensure_dirs(test_root)
storage.update_meta({ nc = 5 }, test_root)
local meta = storage.get_meta(test_root)
assert.equals(5, meta.nc)
end)
end)
describe("get/save_nodes", function()
it("saves and retrieves nodes by type", function()
storage.ensure_dirs(test_root)
local nodes = {
["n_pat_123_abc"] = { id = "n_pat_123_abc", t = "pat" },
["n_pat_456_def"] = { id = "n_pat_456_def", t = "pat" },
}
storage.save_nodes("patterns", nodes, test_root)
storage.flush("nodes.patterns", test_root)
storage.clear_cache()
local loaded = storage.get_nodes("patterns", test_root)
assert.equals(2, vim.tbl_count(loaded))
assert.equals("n_pat_123_abc", loaded["n_pat_123_abc"].id)
end)
end)
describe("get/save_graph", function()
it("saves and retrieves graph", function()
storage.ensure_dirs(test_root)
local graph = {
adj = { node1 = { sem = { "node2" } } },
radj = { node2 = { sem = { "node1" } } },
}
storage.save_graph(graph, test_root)
storage.flush("graph", test_root)
storage.clear_cache()
local loaded = storage.get_graph(test_root)
assert.same({ "node2" }, loaded.adj.node1.sem)
end)
end)
describe("get/set_head", function()
it("stores and retrieves HEAD", function()
storage.ensure_dirs(test_root)
storage.set_head("abc12345", test_root)
storage.flush("meta", test_root) -- Ensure written to disk
storage.clear_cache()
local head = storage.get_head(test_root)
assert.equals("abc12345", head)
end)
end)
describe("exists", function()
it("returns false for non-existent brain", function()
assert.is_false(storage.exists(test_root))
end)
it("returns true after ensure_dirs", function()
storage.ensure_dirs(test_root)
assert.is_true(storage.exists(test_root))
end)
end)
end)

345
tests/spec/indexer_spec.lua Normal file
View File

@@ -0,0 +1,345 @@
---@diagnostic disable: undefined-global
-- Tests for lua/codetyper/indexer/init.lua
describe("indexer", function()
local indexer
local utils
-- Mock cwd for testing
local test_cwd = "/tmp/codetyper_test_indexer"
before_each(function()
-- Reset modules
package.loaded["codetyper.indexer"] = nil
package.loaded["codetyper.indexer.scanner"] = nil
package.loaded["codetyper.indexer.analyzer"] = nil
package.loaded["codetyper.indexer.memory"] = nil
package.loaded["codetyper.utils"] = nil
indexer = require("codetyper.indexer")
utils = require("codetyper.utils")
-- Create test directory structure
vim.fn.mkdir(test_cwd, "p")
vim.fn.mkdir(test_cwd .. "/.coder", "p")
vim.fn.mkdir(test_cwd .. "/src", "p")
-- Mock getcwd to return test directory
vim.fn.getcwd = function()
return test_cwd
end
-- Mock get_project_root
package.loaded["codetyper.utils"].get_project_root = function()
return test_cwd
end
end)
after_each(function()
-- Clean up test directory
vim.fn.delete(test_cwd, "rf")
end)
describe("setup", function()
it("should accept configuration options", function()
indexer.setup({
enabled = true,
auto_index = false,
})
local config = indexer.get_config()
assert.is_false(config.auto_index)
end)
it("should use default configuration when no options provided", function()
indexer.setup()
local config = indexer.get_config()
assert.is_true(config.enabled)
end)
end)
describe("load_index", function()
it("should return nil when no index exists", function()
local index = indexer.load_index()
assert.is_nil(index)
end)
it("should load existing index from file", function()
-- Create a mock index file
local mock_index = {
version = 1,
project_root = test_cwd,
project_name = "test",
project_type = "node",
dependencies = {},
dev_dependencies = {},
files = {},
symbols = {},
last_indexed = os.time(),
stats = { files = 0, functions = 0, classes = 0, exports = 0 },
}
utils.write_file(test_cwd .. "/.coder/index.json", vim.json.encode(mock_index))
local index = indexer.load_index()
assert.is_table(index)
assert.equals("test", index.project_name)
assert.equals("node", index.project_type)
end)
it("should cache loaded index", function()
local mock_index = {
version = 1,
project_root = test_cwd,
project_name = "cached_test",
project_type = "lua",
dependencies = {},
dev_dependencies = {},
files = {},
symbols = {},
last_indexed = os.time(),
stats = { files = 0, functions = 0, classes = 0, exports = 0 },
}
utils.write_file(test_cwd .. "/.coder/index.json", vim.json.encode(mock_index))
local index1 = indexer.load_index()
local index2 = indexer.load_index()
assert.equals(index1.project_name, index2.project_name)
end)
end)
describe("save_index", function()
it("should save index to file", function()
local index = {
version = 1,
project_root = test_cwd,
project_name = "save_test",
project_type = "node",
dependencies = { express = "^4.18.0" },
dev_dependencies = {},
files = {},
symbols = {},
last_indexed = os.time(),
stats = { files = 0, functions = 0, classes = 0, exports = 0 },
}
local result = indexer.save_index(index)
assert.is_true(result)
-- Verify file was created
local content = utils.read_file(test_cwd .. "/.coder/index.json")
assert.is_truthy(content)
local decoded = vim.json.decode(content)
assert.equals("save_test", decoded.project_name)
end)
it("should create .coder directory if it does not exist", function()
vim.fn.delete(test_cwd .. "/.coder", "rf")
local index = {
version = 1,
project_root = test_cwd,
project_name = "test",
project_type = "unknown",
dependencies = {},
dev_dependencies = {},
files = {},
symbols = {},
last_indexed = os.time(),
stats = { files = 0, functions = 0, classes = 0, exports = 0 },
}
indexer.save_index(index)
assert.equals(1, vim.fn.isdirectory(test_cwd .. "/.coder"))
end)
end)
describe("index_project", function()
it("should create an index for the project", function()
-- Create some test files
utils.write_file(test_cwd .. "/package.json", '{"name":"test","dependencies":{}}')
utils.write_file(test_cwd .. "/src/main.lua", [[
local M = {}
function M.hello()
return "world"
end
return M
]])
indexer.setup({ index_extensions = { "lua" } })
local index = indexer.index_project()
assert.is_table(index)
assert.equals("node", index.project_type)
assert.is_truthy(index.stats.files >= 0)
end)
it("should detect project dependencies", function()
utils.write_file(test_cwd .. "/package.json", [[{
"name": "test",
"dependencies": {
"express": "^4.18.0",
"lodash": "^4.17.0"
}
}]])
indexer.setup()
local index = indexer.index_project()
assert.is_table(index.dependencies)
assert.equals("^4.18.0", index.dependencies.express)
end)
it("should call callback when complete", function()
local callback_called = false
local callback_index = nil
indexer.setup()
indexer.index_project(function(index)
callback_called = true
callback_index = index
end)
assert.is_true(callback_called)
assert.is_table(callback_index)
end)
end)
describe("index_file", function()
it("should index a single file", function()
utils.write_file(test_cwd .. "/src/test.lua", [[
local M = {}
function M.add(a, b)
return a + b
end
function M.subtract(a, b)
return a - b
end
return M
]])
indexer.setup({ index_extensions = { "lua" } })
-- First create an initial index
indexer.index_project()
local file_index = indexer.index_file(test_cwd .. "/src/test.lua")
assert.is_table(file_index)
assert.equals("src/test.lua", file_index.path)
end)
it("should update symbols in the main index", function()
utils.write_file(test_cwd .. "/src/utils.lua", [[
local M = {}
function M.format_string(str)
return string.upper(str)
end
return M
]])
indexer.setup({ index_extensions = { "lua" } })
indexer.index_project()
indexer.index_file(test_cwd .. "/src/utils.lua")
local index = indexer.load_index()
assert.is_table(index.files)
end)
end)
describe("get_status", function()
it("should return indexed: false when no index exists", function()
local status = indexer.get_status()
assert.is_false(status.indexed)
assert.is_nil(status.stats)
end)
it("should return status when index exists", function()
indexer.setup()
indexer.index_project()
local status = indexer.get_status()
assert.is_true(status.indexed)
assert.is_table(status.stats)
assert.is_truthy(status.last_indexed)
end)
end)
describe("get_context_for", function()
it("should return context with project type", function()
utils.write_file(test_cwd .. "/package.json", '{"name":"test"}')
indexer.setup()
indexer.index_project()
local context = indexer.get_context_for({
file = test_cwd .. "/src/main.lua",
prompt = "add a function",
})
assert.is_table(context)
assert.equals("node", context.project_type)
end)
it("should find relevant symbols", function()
utils.write_file(test_cwd .. "/src/utils.lua", [[
local M = {}
function M.calculate_total(items)
return 0
end
return M
]])
indexer.setup({ index_extensions = { "lua" } })
indexer.index_project()
local context = indexer.get_context_for({
file = test_cwd .. "/src/main.lua",
prompt = "use calculate_total function",
})
assert.is_table(context)
-- Should find the calculate symbol
if context.relevant_symbols and context.relevant_symbols.calculate then
assert.is_table(context.relevant_symbols.calculate)
end
end)
end)
describe("clear", function()
it("should remove the index file", function()
indexer.setup()
indexer.index_project()
-- Verify index exists
assert.is_true(indexer.get_status().indexed)
indexer.clear()
-- Verify index is gone
local status = indexer.get_status()
assert.is_false(status.indexed)
end)
end)
describe("schedule_index_file", function()
it("should not index when disabled", function()
indexer.setup({ enabled = false })
-- This should not throw or cause issues
indexer.schedule_index_file(test_cwd .. "/src/test.lua")
end)
it("should not index when auto_index is false", function()
indexer.setup({ enabled = true, auto_index = false })
-- This should not throw or cause issues
indexer.schedule_index_file(test_cwd .. "/src/test.lua")
end)
end)
end)

341
tests/spec/memory_spec.lua Normal file
View File

@@ -0,0 +1,341 @@
---@diagnostic disable: undefined-global
-- Tests for lua/codetyper/indexer/memory.lua
describe("indexer.memory", function()
local memory
local utils
-- Mock cwd for testing
local test_cwd = "/tmp/codetyper_test_memory"
before_each(function()
-- Reset modules
package.loaded["codetyper.indexer.memory"] = nil
package.loaded["codetyper.utils"] = nil
memory = require("codetyper.indexer.memory")
utils = require("codetyper.utils")
-- Create test directory structure
vim.fn.mkdir(test_cwd, "p")
vim.fn.mkdir(test_cwd .. "/.coder", "p")
vim.fn.mkdir(test_cwd .. "/.coder/memories", "p")
vim.fn.mkdir(test_cwd .. "/.coder/memories/files", "p")
vim.fn.mkdir(test_cwd .. "/.coder/sessions", "p")
-- Mock getcwd to return test directory
vim.fn.getcwd = function()
return test_cwd
end
-- Mock get_project_root
package.loaded["codetyper.utils"].get_project_root = function()
return test_cwd
end
end)
after_each(function()
-- Clean up test directory
vim.fn.delete(test_cwd, "rf")
end)
describe("store_memory", function()
it("should store a pattern memory", function()
local mem = {
type = "pattern",
content = "Use snake_case for function names",
weight = 0.8,
}
local result = memory.store_memory(mem)
assert.is_true(result)
end)
it("should store a convention memory", function()
local mem = {
type = "convention",
content = "Project uses TypeScript",
weight = 0.9,
}
local result = memory.store_memory(mem)
assert.is_true(result)
end)
it("should assign an ID to the memory", function()
local mem = {
type = "pattern",
content = "Test memory",
}
memory.store_memory(mem)
assert.is_truthy(mem.id)
assert.is_true(mem.id:match("^mem_") ~= nil)
end)
it("should set timestamps", function()
local mem = {
type = "pattern",
content = "Test memory",
}
memory.store_memory(mem)
assert.is_truthy(mem.created_at)
assert.is_truthy(mem.updated_at)
end)
end)
describe("load_patterns", function()
it("should return empty table when no patterns exist", function()
local patterns = memory.load_patterns()
assert.is_table(patterns)
end)
it("should load stored patterns", function()
-- Store a pattern first
memory.store_memory({
type = "pattern",
content = "Test pattern",
weight = 0.5,
})
-- Force reload
package.loaded["codetyper.indexer.memory"] = nil
memory = require("codetyper.indexer.memory")
local patterns = memory.load_patterns()
assert.is_table(patterns)
local count = 0
for _ in pairs(patterns) do
count = count + 1
end
assert.is_true(count >= 1)
end)
end)
describe("load_conventions", function()
it("should return empty table when no conventions exist", function()
local conventions = memory.load_conventions()
assert.is_table(conventions)
end)
end)
describe("store_file_memory", function()
it("should store file-specific memory", function()
local file_index = {
functions = {
{ name = "test_func", line = 10, end_line = 20 },
},
classes = {},
exports = {},
imports = {},
}
local result = memory.store_file_memory("src/main.lua", file_index)
assert.is_true(result)
end)
end)
describe("load_file_memory", function()
it("should return nil when file memory does not exist", function()
local result = memory.load_file_memory("nonexistent.lua")
assert.is_nil(result)
end)
it("should load stored file memory", function()
local file_index = {
functions = {
{ name = "my_function", line = 5, end_line = 15 },
},
classes = {},
exports = {},
imports = {},
}
memory.store_file_memory("src/test.lua", file_index)
local loaded = memory.load_file_memory("src/test.lua")
assert.is_table(loaded)
assert.equals("src/test.lua", loaded.path)
assert.equals(1, #loaded.functions)
assert.equals("my_function", loaded.functions[1].name)
end)
end)
describe("get_relevant", function()
it("should return empty table when no memories exist", function()
local results = memory.get_relevant("test query", 10)
assert.is_table(results)
assert.equals(0, #results)
end)
it("should find relevant memories by keyword", function()
memory.store_memory({
type = "pattern",
content = "Use TypeScript for type safety",
weight = 0.8,
})
memory.store_memory({
type = "pattern",
content = "Use Python for data processing",
weight = 0.7,
})
local results = memory.get_relevant("TypeScript", 10)
assert.is_true(#results >= 1)
-- First result should contain TypeScript
local found = false
for _, r in ipairs(results) do
if r.content:find("TypeScript") then
found = true
break
end
end
assert.is_true(found)
end)
it("should limit results", function()
-- Store multiple memories
for i = 1, 20 do
memory.store_memory({
type = "pattern",
content = "Pattern number " .. i .. " about testing",
weight = 0.5,
})
end
local results = memory.get_relevant("testing", 5)
assert.is_true(#results <= 5)
end)
end)
describe("update_usage", function()
it("should increment used_count", function()
local mem = {
type = "pattern",
content = "Test pattern for usage tracking",
weight = 0.5,
}
memory.store_memory(mem)
memory.update_usage(mem.id)
-- Reload and check
package.loaded["codetyper.indexer.memory"] = nil
memory = require("codetyper.indexer.memory")
local patterns = memory.load_patterns()
if patterns[mem.id] then
assert.equals(1, patterns[mem.id].used_count)
end
end)
end)
describe("get_all", function()
it("should return all memory types", function()
memory.store_memory({ type = "pattern", content = "A pattern" })
memory.store_memory({ type = "convention", content = "A convention" })
local all = memory.get_all()
assert.is_table(all.patterns)
assert.is_table(all.conventions)
assert.is_table(all.symbols)
end)
end)
describe("clear", function()
it("should clear all memories when no pattern provided", function()
memory.store_memory({ type = "pattern", content = "Pattern 1" })
memory.store_memory({ type = "convention", content = "Convention 1" })
memory.clear()
local all = memory.get_all()
assert.equals(0, vim.tbl_count(all.patterns))
assert.equals(0, vim.tbl_count(all.conventions))
end)
it("should clear only matching memories when pattern provided", function()
local mem1 = { type = "pattern", content = "Pattern 1" }
local mem2 = { type = "pattern", content = "Pattern 2" }
memory.store_memory(mem1)
memory.store_memory(mem2)
-- Clear memories matching the first ID
memory.clear(mem1.id)
local patterns = memory.load_patterns()
assert.is_nil(patterns[mem1.id])
end)
end)
describe("prune", function()
it("should remove low-weight unused memories", function()
-- Store some low-weight memories
memory.store_memory({
type = "pattern",
content = "Low weight pattern",
weight = 0.05,
used_count = 0,
})
memory.store_memory({
type = "pattern",
content = "High weight pattern",
weight = 0.9,
used_count = 0,
})
local pruned = memory.prune(0.1)
-- Should have pruned at least one
assert.is_true(pruned >= 0)
end)
it("should not remove frequently used memories", function()
local mem = {
type = "pattern",
content = "Frequently used but low weight",
weight = 0.05,
used_count = 10,
}
memory.store_memory(mem)
memory.prune(0.1)
-- Memory should still exist because used_count > 0
local patterns = memory.load_patterns()
-- Note: prune only removes if used_count == 0 AND weight < threshold
if patterns[mem.id] then
assert.is_truthy(patterns[mem.id])
end
end)
end)
describe("get_stats", function()
it("should return memory statistics", function()
memory.store_memory({ type = "pattern", content = "P1" })
memory.store_memory({ type = "pattern", content = "P2" })
memory.store_memory({ type = "convention", content = "C1" })
local stats = memory.get_stats()
assert.is_table(stats)
assert.equals(2, stats.patterns)
assert.equals(1, stats.conventions)
assert.equals(3, stats.total)
end)
end)
end)

285
tests/spec/scanner_spec.lua Normal file
View File

@@ -0,0 +1,285 @@
---@diagnostic disable: undefined-global
-- Tests for lua/codetyper/indexer/scanner.lua
describe("indexer.scanner", function()
local scanner
local utils
-- Mock cwd for testing
local test_cwd = "/tmp/codetyper_test_scanner"
before_each(function()
-- Reset modules
package.loaded["codetyper.indexer.scanner"] = nil
package.loaded["codetyper.utils"] = nil
scanner = require("codetyper.indexer.scanner")
utils = require("codetyper.utils")
-- Create test directory
vim.fn.mkdir(test_cwd, "p")
-- Mock getcwd to return test directory
vim.fn.getcwd = function()
return test_cwd
end
end)
after_each(function()
-- Clean up test directory
vim.fn.delete(test_cwd, "rf")
end)
describe("detect_project_type", function()
it("should detect node project from package.json", function()
utils.write_file(test_cwd .. "/package.json", '{"name":"test"}')
local project_type = scanner.detect_project_type(test_cwd)
assert.equals("node", project_type)
end)
it("should detect rust project from Cargo.toml", function()
utils.write_file(test_cwd .. "/Cargo.toml", '[package]\nname = "test"')
local project_type = scanner.detect_project_type(test_cwd)
assert.equals("rust", project_type)
end)
it("should detect go project from go.mod", function()
utils.write_file(test_cwd .. "/go.mod", "module example.com/test")
local project_type = scanner.detect_project_type(test_cwd)
assert.equals("go", project_type)
end)
it("should detect python project from pyproject.toml", function()
utils.write_file(test_cwd .. "/pyproject.toml", '[project]\nname = "test"')
local project_type = scanner.detect_project_type(test_cwd)
assert.equals("python", project_type)
end)
it("should return unknown for unrecognized project", function()
-- Empty directory
local project_type = scanner.detect_project_type(test_cwd)
assert.equals("unknown", project_type)
end)
end)
describe("parse_package_json", function()
it("should parse dependencies from package.json", function()
local pkg_content = [[{
"name": "test",
"dependencies": {
"express": "^4.18.0",
"lodash": "^4.17.0"
},
"devDependencies": {
"jest": "^29.0.0"
}
}]]
utils.write_file(test_cwd .. "/package.json", pkg_content)
local result = scanner.parse_package_json(test_cwd)
assert.is_table(result.dependencies)
assert.is_table(result.dev_dependencies)
assert.equals("^4.18.0", result.dependencies.express)
assert.equals("^4.17.0", result.dependencies.lodash)
assert.equals("^29.0.0", result.dev_dependencies.jest)
end)
it("should return empty tables when package.json does not exist", function()
local result = scanner.parse_package_json(test_cwd)
assert.is_table(result.dependencies)
assert.is_table(result.dev_dependencies)
assert.equals(0, vim.tbl_count(result.dependencies))
end)
it("should handle malformed JSON gracefully", function()
utils.write_file(test_cwd .. "/package.json", "not valid json")
local result = scanner.parse_package_json(test_cwd)
assert.is_table(result.dependencies)
assert.equals(0, vim.tbl_count(result.dependencies))
end)
end)
describe("parse_cargo_toml", function()
it("should parse dependencies from Cargo.toml", function()
local cargo_content = [[
[package]
name = "test"
[dependencies]
serde = "1.0"
tokio = "1.28"
[dev-dependencies]
tempfile = "3.5"
]]
utils.write_file(test_cwd .. "/Cargo.toml", cargo_content)
local result = scanner.parse_cargo_toml(test_cwd)
assert.is_table(result.dependencies)
assert.equals("1.0", result.dependencies.serde)
assert.equals("1.28", result.dependencies.tokio)
assert.equals("3.5", result.dev_dependencies.tempfile)
end)
it("should return empty tables when Cargo.toml does not exist", function()
local result = scanner.parse_cargo_toml(test_cwd)
assert.equals(0, vim.tbl_count(result.dependencies))
end)
end)
describe("parse_go_mod", function()
it("should parse dependencies from go.mod", function()
local go_mod_content = [[
module example.com/test
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
github.com/stretchr/testify v1.8.4
)
]]
utils.write_file(test_cwd .. "/go.mod", go_mod_content)
local result = scanner.parse_go_mod(test_cwd)
assert.is_table(result.dependencies)
assert.equals("v1.9.1", result.dependencies["github.com/gin-gonic/gin"])
assert.equals("v1.8.4", result.dependencies["github.com/stretchr/testify"])
end)
end)
describe("should_ignore", function()
it("should ignore hidden files", function()
local config = { excluded_dirs = {} }
assert.is_true(scanner.should_ignore(".hidden", config))
assert.is_true(scanner.should_ignore(".git", config))
end)
it("should ignore node_modules", function()
local config = { excluded_dirs = {} }
assert.is_true(scanner.should_ignore("node_modules", config))
end)
it("should ignore configured directories", function()
local config = { excluded_dirs = { "custom_ignore" } }
assert.is_true(scanner.should_ignore("custom_ignore", config))
end)
it("should not ignore regular files", function()
local config = { excluded_dirs = {} }
assert.is_false(scanner.should_ignore("main.lua", config))
assert.is_false(scanner.should_ignore("src", config))
end)
end)
describe("should_index", function()
it("should index files with allowed extensions", function()
vim.fn.mkdir(test_cwd .. "/src", "p")
utils.write_file(test_cwd .. "/src/main.lua", "-- test")
local config = {
index_extensions = { "lua", "ts", "js" },
max_file_size = 100000,
excluded_dirs = {},
}
assert.is_true(scanner.should_index(test_cwd .. "/src/main.lua", config))
end)
it("should not index coder files", function()
utils.write_file(test_cwd .. "/main.coder.lua", "-- test")
local config = {
index_extensions = { "lua" },
max_file_size = 100000,
excluded_dirs = {},
}
assert.is_false(scanner.should_index(test_cwd .. "/main.coder.lua", config))
end)
it("should not index files with disallowed extensions", function()
utils.write_file(test_cwd .. "/image.png", "binary")
local config = {
index_extensions = { "lua", "ts", "js" },
max_file_size = 100000,
excluded_dirs = {},
}
assert.is_false(scanner.should_index(test_cwd .. "/image.png", config))
end)
end)
describe("get_indexable_files", function()
it("should return list of indexable files", function()
vim.fn.mkdir(test_cwd .. "/src", "p")
utils.write_file(test_cwd .. "/src/main.lua", "-- main")
utils.write_file(test_cwd .. "/src/utils.lua", "-- utils")
utils.write_file(test_cwd .. "/README.md", "# Readme")
local config = {
index_extensions = { "lua" },
max_file_size = 100000,
excluded_dirs = { "node_modules" },
}
local files = scanner.get_indexable_files(test_cwd, config)
assert.equals(2, #files)
end)
it("should skip ignored directories", function()
vim.fn.mkdir(test_cwd .. "/src", "p")
vim.fn.mkdir(test_cwd .. "/node_modules", "p")
utils.write_file(test_cwd .. "/src/main.lua", "-- main")
utils.write_file(test_cwd .. "/node_modules/package.lua", "-- ignore")
local config = {
index_extensions = { "lua" },
max_file_size = 100000,
excluded_dirs = { "node_modules" },
}
local files = scanner.get_indexable_files(test_cwd, config)
-- Should only include src/main.lua
assert.equals(1, #files)
end)
end)
describe("get_language", function()
it("should return correct language for extensions", function()
assert.equals("lua", scanner.get_language("test.lua"))
assert.equals("typescript", scanner.get_language("test.ts"))
assert.equals("javascript", scanner.get_language("test.js"))
assert.equals("python", scanner.get_language("test.py"))
assert.equals("go", scanner.get_language("test.go"))
assert.equals("rust", scanner.get_language("test.rs"))
end)
it("should return extension as fallback", function()
assert.equals("unknown", scanner.get_language("test.unknown"))
end)
end)
end)