diff --git a/Makefile b/Makefile index 9abb50a3b..5695572df 100644 --- a/Makefile +++ b/Makefile @@ -137,6 +137,9 @@ setup-dev: nix-setup git-submodule-sync shell-install ## Set up local developmen .PHONY: switch switch: nix-switch launchctl ## Apply Nix configuration and restart launchd agents. +.PHONY: test +test: neovim-test + .PHONY: update update: nix-update shell-update neovim-update ## Update Nix flake and configurations. @@ -494,7 +497,7 @@ neovim-sync: ## Sync Neovim plugins. .PHONY: neovim-test neovim-test: ## Run Neovim tests using plenary.nvim. @echo "🧪 Running Neovim tests..." - @$(PWD)/home-manager/programs/neovim/tests/run_tests.sh + @$(PWD)/home-manager/programs/neovim/run_tests.sh @echo "✅ Neovim tests completed" .PHONY: neovim-test-dev diff --git a/home-manager/programs/neovim/tests/run_tests.sh b/home-manager/programs/neovim/run_tests.sh similarity index 97% rename from home-manager/programs/neovim/tests/run_tests.sh rename to home-manager/programs/neovim/run_tests.sh index f45daae96..b9ef82181 100755 --- a/home-manager/programs/neovim/tests/run_tests.sh +++ b/home-manager/programs/neovim/run_tests.sh @@ -5,7 +5,7 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -NVIM_DIR="$(dirname "$SCRIPT_DIR")" +NVIM_DIR="$SCRIPT_DIR" PLENARY_DIR="${PLENARY_DIR:-/tmp/plenary.nvim}" # Colors for output diff --git a/home-manager/programs/neovim/tests/ai_spec.lua b/home-manager/programs/neovim/tests/ai_spec.lua new file mode 100644 index 000000000..7edee352d --- /dev/null +++ b/home-manager/programs/neovim/tests/ai_spec.lua @@ -0,0 +1,22 @@ +-- Tests for lua/ai.lua +-- Tests AI/sidekick integration (plugin not loaded in minimal test env) + +describe("ai", function() + describe("sidekick API", function() + it("should have sidekick module structure expected", function() + -- In full config, sidekick would be loaded + -- Here we just verify the expected API pattern + assert.is_true(true) + end) + end) + + describe("AI keymaps expected", function() + it("should support leader-based AI keymaps pattern", function() + -- Verify keymap API works for AI-style mappings + vim.keymap.set("n", "ai_test", function() end, { noremap = true }) + local keymap = vim.fn.maparg("ai_test", "n") + assert.is_true(keymap ~= "") + vim.keymap.del("n", "ai_test") + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/api/buffer_spec.lua b/home-manager/programs/neovim/tests/api/buffer_spec.lua new file mode 100644 index 000000000..49f0b5ca5 --- /dev/null +++ b/home-manager/programs/neovim/tests/api/buffer_spec.lua @@ -0,0 +1,129 @@ +-- Tests for Neovim buffer API +-- Tests common buffer manipulation patterns + +describe("buffer", function() + describe("creation and deletion", function() + it("should create scratch buffer", function() + local buf = vim.api.nvim_create_buf(false, true) + assert.is_true(vim.api.nvim_buf_is_valid(buf)) + assert.is_true(vim.bo[buf].buftype == "nofile" or vim.bo[buf].buftype == "") + vim.api.nvim_buf_delete(buf, { force = true }) + end) + + it("should create listed buffer", function() + local buf = vim.api.nvim_create_buf(true, false) + assert.is_true(vim.api.nvim_buf_is_valid(buf)) + assert.is_true(vim.bo[buf].buflisted) + vim.api.nvim_buf_delete(buf, { force = true }) + end) + + it("should delete buffer with force", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "modified content" }) + vim.api.nvim_buf_delete(buf, { force = true }) + assert.is_false(vim.api.nvim_buf_is_valid(buf)) + end) + end) + + describe("content manipulation", function() + it("should set and get buffer lines", function() + local buf = vim.api.nvim_create_buf(false, true) + local lines = { "line 1", "line 2", "line 3" } + vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) + + local result = vim.api.nvim_buf_get_lines(buf, 0, -1, false) + assert.are.same(lines, result) + + vim.api.nvim_buf_delete(buf, { force = true }) + end) + + it("should append lines to buffer", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "first" }) + vim.api.nvim_buf_set_lines(buf, -1, -1, false, { "second" }) + + local result = vim.api.nvim_buf_get_lines(buf, 0, -1, false) + assert.are.same({ "first", "second" }, result) + + vim.api.nvim_buf_delete(buf, { force = true }) + end) + + it("should replace specific lines", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "a", "b", "c" }) + vim.api.nvim_buf_set_lines(buf, 1, 2, false, { "replaced" }) + + local result = vim.api.nvim_buf_get_lines(buf, 0, -1, false) + assert.are.same({ "a", "replaced", "c" }, result) + + vim.api.nvim_buf_delete(buf, { force = true }) + end) + end) + + describe("buffer options", function() + it("should set buffer-local options", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.bo[buf].filetype = "lua" + assert.equals("lua", vim.bo[buf].filetype) + vim.api.nvim_buf_delete(buf, { force = true }) + end) + + it("should set modifiable option", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.bo[buf].modifiable = false + assert.is_false(vim.bo[buf].modifiable) + vim.bo[buf].modifiable = true + vim.api.nvim_buf_delete(buf, { force = true }) + end) + + it("should set readonly option", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.bo[buf].readonly = true + assert.is_true(vim.bo[buf].readonly) + vim.api.nvim_buf_delete(buf, { force = true }) + end) + end) + + describe("buffer listing", function() + it("should list all buffers", function() + local bufs = vim.api.nvim_list_bufs() + assert.is_table(bufs) + assert.is_true(#bufs >= 1) + end) + + it("should check if buffer is loaded", function() + local buf = vim.api.nvim_create_buf(false, true) + assert.is_true(vim.api.nvim_buf_is_loaded(buf)) + vim.api.nvim_buf_delete(buf, { force = true }) + end) + end) + + describe("buffer name", function() + it("should set and get buffer name", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_name(buf, "test_buffer_name") + local name = vim.api.nvim_buf_get_name(buf) + assert.is_true(name:match("test_buffer_name") ~= nil) + vim.api.nvim_buf_delete(buf, { force = true }) + end) + end) + + describe("current buffer", function() + it("should get current buffer", function() + local buf = vim.api.nvim_get_current_buf() + assert.is_number(buf) + assert.is_true(vim.api.nvim_buf_is_valid(buf)) + end) + + it("should set current buffer", function() + local new_buf = vim.api.nvim_create_buf(false, true) + local original_buf = vim.api.nvim_get_current_buf() + + vim.api.nvim_set_current_buf(new_buf) + assert.equals(new_buf, vim.api.nvim_get_current_buf()) + + vim.api.nvim_set_current_buf(original_buf) + vim.api.nvim_buf_delete(new_buf, { force = true }) + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/config_spec.lua b/home-manager/programs/neovim/tests/api/init_spec.lua similarity index 84% rename from home-manager/programs/neovim/tests/config_spec.lua rename to home-manager/programs/neovim/tests/api/init_spec.lua index 620c58c13..b6dfd374b 100644 --- a/home-manager/programs/neovim/tests/config_spec.lua +++ b/home-manager/programs/neovim/tests/api/init_spec.lua @@ -1,7 +1,7 @@ --- Tests for Neovim configuration loading --- Verifies that the config modules can be loaded without errors +-- Tests for Neovim init/core API +-- Tests basic configuration loading and core Neovim functionality -describe("config", function() +describe("init", function() describe("settings", function() it("should load without errors", function() assert.has_no.errors(function() @@ -31,32 +31,26 @@ describe("config", function() end) it("should be able to create and manipulate buffers", function() - -- Create a new buffer local buf = vim.api.nvim_create_buf(false, true) assert.is_true(vim.api.nvim_buf_is_valid(buf)) - -- Set lines vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "hello", "world" }) local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) assert.are.same({ "hello", "world" }, lines) - -- Clean up vim.api.nvim_buf_delete(buf, { force = true }) end) it("should handle feedkeys for keymap testing", function() - -- Create a scratch buffer for testing local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_set_current_buf(buf) - -- Enter insert mode and type vim.api.nvim_feedkeys("itest", "x", false) vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("", true, false, true), "x", false) local line = vim.api.nvim_get_current_line() assert.equals("test", line) - -- Clean up vim.api.nvim_buf_delete(buf, { force = true }) end) end) diff --git a/home-manager/programs/neovim/tests/api/window_spec.lua b/home-manager/programs/neovim/tests/api/window_spec.lua new file mode 100644 index 000000000..28d109614 --- /dev/null +++ b/home-manager/programs/neovim/tests/api/window_spec.lua @@ -0,0 +1,142 @@ +-- Tests for Neovim window API +-- Tests common window manipulation patterns + +describe("window", function() + describe("creation and deletion", function() + it("should create new window with split", function() + local original_win = vim.api.nvim_get_current_win() + vim.cmd("split") + local new_win = vim.api.nvim_get_current_win() + + assert.is_not.equals(original_win, new_win) + assert.is_true(vim.api.nvim_win_is_valid(new_win)) + + vim.api.nvim_win_close(new_win, true) + end) + + it("should create new window with vsplit", function() + local original_win = vim.api.nvim_get_current_win() + vim.cmd("vsplit") + local new_win = vim.api.nvim_get_current_win() + + assert.is_not.equals(original_win, new_win) + assert.is_true(vim.api.nvim_win_is_valid(new_win)) + + vim.api.nvim_win_close(new_win, true) + end) + + it("should close window", function() + vim.cmd("split") + local win_to_close = vim.api.nvim_get_current_win() + vim.api.nvim_win_close(win_to_close, true) + + assert.is_false(vim.api.nvim_win_is_valid(win_to_close)) + end) + end) + + describe("window listing", function() + it("should list all windows", function() + local wins = vim.api.nvim_list_wins() + assert.is_table(wins) + assert.is_true(#wins >= 1) + end) + + it("should list windows in current tabpage", function() + local wins = vim.api.nvim_tabpage_list_wins(0) + assert.is_table(wins) + assert.is_true(#wins >= 1) + end) + end) + + describe("window buffer", function() + it("should get buffer in window", function() + local win = vim.api.nvim_get_current_win() + local buf = vim.api.nvim_win_get_buf(win) + assert.is_number(buf) + assert.is_true(vim.api.nvim_buf_is_valid(buf)) + end) + + it("should set buffer in window", function() + local win = vim.api.nvim_get_current_win() + local new_buf = vim.api.nvim_create_buf(false, true) + local original_buf = vim.api.nvim_win_get_buf(win) + + vim.api.nvim_win_set_buf(win, new_buf) + assert.equals(new_buf, vim.api.nvim_win_get_buf(win)) + + vim.api.nvim_win_set_buf(win, original_buf) + vim.api.nvim_buf_delete(new_buf, { force = true }) + end) + end) + + describe("window options", function() + it("should set window-local options", function() + local win = vim.api.nvim_get_current_win() + local original_number = vim.wo[win].number + + vim.wo[win].number = not original_number + assert.equals(not original_number, vim.wo[win].number) + + vim.wo[win].number = original_number + end) + + it("should set cursorline option", function() + local win = vim.api.nvim_get_current_win() + vim.wo[win].cursorline = true + assert.is_true(vim.wo[win].cursorline) + end) + end) + + describe("window dimensions", function() + it("should get window height", function() + local win = vim.api.nvim_get_current_win() + local height = vim.api.nvim_win_get_height(win) + assert.is_number(height) + assert.is_true(height > 0) + end) + + it("should get window width", function() + local win = vim.api.nvim_get_current_win() + local width = vim.api.nvim_win_get_width(win) + assert.is_number(width) + assert.is_true(width > 0) + end) + + it("should set window height", function() + vim.cmd("split") + local win = vim.api.nvim_get_current_win() + local target_height = 10 + + vim.api.nvim_win_set_height(win, target_height) + assert.equals(target_height, vim.api.nvim_win_get_height(win)) + + vim.api.nvim_win_close(win, true) + end) + end) + + describe("window cursor", function() + it("should get cursor position", function() + local win = vim.api.nvim_get_current_win() + local pos = vim.api.nvim_win_get_cursor(win) + assert.is_table(pos) + assert.equals(2, #pos) + assert.is_number(pos[1]) + assert.is_number(pos[2]) + end) + + it("should set cursor position", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "line 1", "line 2", "line 3" }) + vim.api.nvim_set_current_buf(buf) + + local win = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_cursor(win, { 2, 3 }) + + local pos = vim.api.nvim_win_get_cursor(win) + assert.equals(2, pos[1]) + assert.equals(3, pos[2]) + + vim.api.nvim_buf_delete(buf, { force = true }) + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/autocmds_spec.lua b/home-manager/programs/neovim/tests/autocmds_spec.lua new file mode 100644 index 000000000..e5bc733e3 --- /dev/null +++ b/home-manager/programs/neovim/tests/autocmds_spec.lua @@ -0,0 +1,163 @@ +-- Tests for lua/autocmds.lua +-- Verifies that autocommands are correctly configured + +describe("autocmds", function() + before_each(function() + package.loaded["autocmds"] = nil + require("autocmds") + end) + + describe("augroup creation", function() + local function augroup_exists(name) + local ok, id = pcall(vim.api.nvim_get_autocmds, { group = name }) + return ok and id ~= nil + end + + it("should create HighlightYank augroup", function() + assert.is_true(augroup_exists("HighlightYank")) + end) + + it("should create ResizeSplits augroup", function() + assert.is_true(augroup_exists("ResizeSplits")) + end) + + it("should create CheckTime augroup", function() + assert.is_true(augroup_exists("CheckTime")) + end) + + it("should create GitCommit augroup", function() + assert.is_true(augroup_exists("GitCommit")) + end) + + it("should create NewFile augroup", function() + assert.is_true(augroup_exists("NewFile")) + end) + + it("should create Help augroup", function() + assert.is_true(augroup_exists("Help")) + end) + + it("should create Git augroup", function() + assert.is_true(augroup_exists("Git")) + end) + + it("should create Fugitive augroup", function() + assert.is_true(augroup_exists("Fugitive")) + end) + + it("should create QuickfixHelp augroup", function() + assert.is_true(augroup_exists("QuickfixHelp")) + end) + + it("should create Markdown augroup", function() + assert.is_true(augroup_exists("Markdown")) + end) + + it("should create Terminal augroup", function() + assert.is_true(augroup_exists("Terminal")) + end) + end) + + describe("autocmd events", function() + local function has_autocmd_for_event(group, event) + local autocmds = vim.api.nvim_get_autocmds({ group = group }) + for _, autocmd in ipairs(autocmds) do + if autocmd.event == event then + return true + end + end + return false + end + + it("should have TextYankPost autocmd for highlighting", function() + assert.is_true(has_autocmd_for_event("HighlightYank", "TextYankPost")) + end) + + it("should have VimResized autocmd for resize handling", function() + assert.is_true(has_autocmd_for_event("ResizeSplits", "VimResized")) + end) + + it("should have BufEnter autocmd for checktime", function() + assert.is_true(has_autocmd_for_event("CheckTime", "BufEnter")) + end) + + it("should have FileType autocmd for gitcommit", function() + assert.is_true(has_autocmd_for_event("GitCommit", "FileType")) + end) + + it("should have BufNewFile autocmd for parent dir creation", function() + assert.is_true(has_autocmd_for_event("NewFile", "BufNewFile")) + end) + + it("should have FileType autocmd for markdown", function() + assert.is_true(has_autocmd_for_event("Markdown", "FileType")) + end) + + it("should have TermOpen autocmd for terminal", function() + assert.is_true(has_autocmd_for_event("Terminal", "TermOpen")) + end) + end) + + describe("highlight on yank", function() + it("should trigger highlight.on_yank without error", function() + assert.has_no.errors(function() + vim.highlight.on_yank({ timeout = 1 }) + end) + end) + end) + + describe("nvim_create_autocmd API", function() + it("should be able to create autocmds", function() + local called = false + local test_group = vim.api.nvim_create_augroup("TestAutocmd", { clear = true }) + vim.api.nvim_create_autocmd("BufEnter", { + group = test_group, + pattern = "*", + callback = function() + called = true + end, + }) + + local autocmds = vim.api.nvim_get_autocmds({ group = test_group }) + assert.is_true(#autocmds > 0) + + vim.api.nvim_del_augroup_by_id(test_group) + end) + + it("should be able to delete augroups", function() + local test_group = vim.api.nvim_create_augroup("TestDelete", { clear = true }) + vim.api.nvim_del_augroup_by_id(test_group) + + local ok = pcall(vim.api.nvim_get_autocmds, { group = "TestDelete" }) + assert.is_false(ok) + end) + end) + + describe("filetype-specific settings", function() + it("should set markdown options correctly when filetype is set", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(buf) + vim.bo[buf].filetype = "markdown" + + vim.api.nvim_exec_autocmds("FileType", { pattern = "markdown" }) + + assert.is_true(vim.opt_local.spell:get()) + assert.equals(80, vim.opt_local.textwidth:get()) + + vim.api.nvim_buf_delete(buf, { force = true }) + end) + + it("should set gitcommit options correctly", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(buf) + vim.bo[buf].filetype = "gitcommit" + + vim.api.nvim_exec_autocmds("FileType", { pattern = "gitcommit" }) + + assert.is_true(vim.opt_local.spell:get()) + assert.equals(72, vim.opt_local.textwidth:get()) + + vim.api.nvim_buf_delete(buf, { force = true }) + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/completion_spec.lua b/home-manager/programs/neovim/tests/completion_spec.lua new file mode 100644 index 000000000..3a0ee412f --- /dev/null +++ b/home-manager/programs/neovim/tests/completion_spec.lua @@ -0,0 +1,92 @@ +-- Tests for lua/completion.lua +-- Tests completion-related APIs (nvim-cmp not loaded in minimal test env) + +describe("completion", function() + describe("vim.fn completion", function() + it("should have complete function", function() + assert.is_function(vim.fn.complete) + end) + + it("should have complete_info function", function() + assert.is_function(vim.fn.complete_info) + end) + + it("should have pumvisible function", function() + assert.is_function(vim.fn.pumvisible) + end) + end) + + describe("completeopt", function() + it("should be configurable", function() + local completeopt = vim.opt.completeopt:get() + assert.is_table(completeopt) + end) + + it("should support menu option", function() + vim.opt.completeopt:append("menu") + local completeopt = vim.opt.completeopt:get() + assert.is_true(vim.tbl_contains(completeopt, "menu")) + end) + + it("should support menuone option", function() + vim.opt.completeopt:append("menuone") + local completeopt = vim.opt.completeopt:get() + assert.is_true(vim.tbl_contains(completeopt, "menuone")) + end) + + it("should support noselect option", function() + vim.opt.completeopt:append("noselect") + local completeopt = vim.opt.completeopt:get() + assert.is_true(vim.tbl_contains(completeopt, "noselect")) + end) + end) + + describe("snippet expansion", function() + it("should have snippet API or be nil in older nvim", function() + -- vim.snippet is available in Neovim 0.10+ + local has_snippet = vim.snippet ~= nil + assert.is_boolean(has_snippet) + end) + + it("should have expand function if snippet API exists", function() + if vim.snippet then + assert.is_function(vim.snippet.expand) + else + assert.is_true(true) -- Skip on older Neovim + end + end) + + it("should have active function if snippet API exists", function() + if vim.snippet then + assert.is_function(vim.snippet.active) + else + assert.is_true(true) -- Skip on older Neovim + end + end) + end) + + describe("insert mode mappings", function() + it("should support insert mode keymaps", function() + vim.keymap.set("i", "", "", { noremap = true }) + local keymap = vim.fn.maparg("", "i") + assert.is_true(keymap ~= "") + vim.keymap.del("i", "") + end) + + it("should support expr mappings for completion", function() + vim.keymap.set("i", "", function() + return "" + end, { expr = true }) + local keymaps = vim.api.nvim_get_keymap("i") + local found = false + for _, km in ipairs(keymaps) do + if km.lhs:match("test%-expr") then + found = true + break + end + end + assert.is_true(found) + vim.keymap.del("i", "") + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/keymaps_spec.lua b/home-manager/programs/neovim/tests/keymaps_spec.lua new file mode 100644 index 000000000..b75889686 --- /dev/null +++ b/home-manager/programs/neovim/tests/keymaps_spec.lua @@ -0,0 +1,139 @@ +-- Tests for keymap functionality +-- Note: keymaps.lua requires plugins, so we test keymap API behavior instead + +describe("keymaps", function() + describe("leader key", function() + it("should set leader key to space", function() + assert.equals(" ", vim.g.mapleader) + end) + end) + + describe("vim.keymap API", function() + it("should be able to set keymaps", function() + local test_called = false + vim.keymap.set("n", "test_km1", function() + test_called = true + end, { noremap = true, silent = true }) + + local keymap_info = vim.fn.maparg("test_km1", "n") + assert.is_true(keymap_info ~= "") + + vim.keymap.del("n", "test_km1") + end) + + it("should be able to delete keymaps", function() + vim.keymap.set("n", "test_km2", ":echo 'test'", { noremap = true }) + vim.keymap.del("n", "test_km2") + + local keymap_info = vim.fn.maparg("test_km2", "n") + assert.equals("", keymap_info) + end) + + it("should support silent option", function() + vim.keymap.set("n", "test_km3", ":echo 'test'", { silent = true }) + + local keymaps = vim.api.nvim_get_keymap("n") + local found = false + for _, km in ipairs(keymaps) do + if km.lhs:match("test_km3") then + found = true + assert.equals(1, km.silent) + break + end + end + assert.is_true(found) + + vim.keymap.del("n", "test_km3") + end) + + it("should support noremap option", function() + vim.keymap.set("n", "test_km4", ":echo 'test'", { noremap = true }) + + local keymaps = vim.api.nvim_get_keymap("n") + local found = false + for _, km in ipairs(keymaps) do + if km.lhs:match("test_km4") then + found = true + assert.equals(1, km.noremap) + break + end + end + assert.is_true(found) + + vim.keymap.del("n", "test_km4") + end) + + it("should be able to create buffer-local keymaps", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(buf) + + vim.keymap.set("n", "test_buf_km", ":echo 'test'", { + buffer = buf, + noremap = true, + }) + + local keymaps = vim.api.nvim_buf_get_keymap(buf, "n") + local found = false + for _, km in ipairs(keymaps) do + if km.lhs:match("test_buf_km") then + found = true + break + end + end + + assert.is_true(found) + + vim.api.nvim_buf_delete(buf, { force = true }) + end) + + it("should support function callbacks", function() + local callback_executed = false + vim.keymap.set("n", "test_fn", function() + callback_executed = true + end, { noremap = true }) + + local keymaps = vim.api.nvim_get_keymap("n") + local found = false + for _, km in ipairs(keymaps) do + if km.lhs:match("test_fn") then + found = true + assert.is_function(km.callback) + break + end + end + assert.is_true(found) + + vim.keymap.del("n", "test_fn") + end) + + it("should support multiple modes", function() + vim.keymap.set({ "n", "v" }, "test_multi", ":echo 'test'", { noremap = true }) + + local n_keymap = vim.fn.maparg("test_multi", "n") + local v_keymap = vim.fn.maparg("test_multi", "v") + + assert.is_true(n_keymap ~= "") + assert.is_true(v_keymap ~= "") + + vim.keymap.del({ "n", "v" }, "test_multi") + end) + end) + + describe("keymap execution", function() + it("should execute keymap action via feedkeys", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(buf) + + vim.keymap.set("n", "test_exec", function() + vim.api.nvim_buf_set_lines(buf, 0, -1, false, { "executed" }) + end, { buffer = buf }) + + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("test_exec", true, false, true), "x", false) + + local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) + assert.are.same({ "executed" }, lines) + + vim.api.nvim_buf_delete(buf, { force = true }) + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/lsp_spec.lua b/home-manager/programs/neovim/tests/lsp_spec.lua new file mode 100644 index 000000000..fc02085e0 --- /dev/null +++ b/home-manager/programs/neovim/tests/lsp_spec.lua @@ -0,0 +1,104 @@ +-- Tests for lua/lsp.lua +-- Tests LSP configuration and diagnostics + +describe("lsp", function() + describe("vim.lsp API", function() + it("should have vim.lsp.buf available", function() + assert.is_table(vim.lsp.buf) + end) + + it("should have hover function", function() + assert.is_function(vim.lsp.buf.hover) + end) + + it("should have definition function", function() + assert.is_function(vim.lsp.buf.definition) + end) + + it("should have declaration function", function() + assert.is_function(vim.lsp.buf.declaration) + end) + + it("should have implementation function", function() + assert.is_function(vim.lsp.buf.implementation) + end) + + it("should have references function", function() + assert.is_function(vim.lsp.buf.references) + end) + + it("should have type_definition function", function() + assert.is_function(vim.lsp.buf.type_definition) + end) + + it("should have code_action function", function() + assert.is_function(vim.lsp.buf.code_action) + end) + + it("should have rename function", function() + assert.is_function(vim.lsp.buf.rename) + end) + + it("should have signature_help function", function() + assert.is_function(vim.lsp.buf.signature_help) + end) + end) + + describe("diagnostics", function() + it("should have vim.diagnostic available", function() + assert.is_table(vim.diagnostic) + end) + + it("should have goto_next function", function() + assert.is_function(vim.diagnostic.goto_next) + end) + + it("should have goto_prev function", function() + assert.is_function(vim.diagnostic.goto_prev) + end) + + it("should have open_float function", function() + assert.is_function(vim.diagnostic.open_float) + end) + + it("should have get function", function() + assert.is_function(vim.diagnostic.get) + end) + + it("should have set function", function() + assert.is_function(vim.diagnostic.set) + end) + + it("should be able to get diagnostics for current buffer", function() + local diags = vim.diagnostic.get(0) + assert.is_table(diags) + end) + + it("should have config function", function() + assert.is_function(vim.diagnostic.config) + end) + + it("should have diagnostic config set", function() + local config = vim.diagnostic.config() + assert.is_table(config) + end) + end) + + describe("diagnostic severity", function() + it("should have ERROR severity", function() + assert.is_number(vim.diagnostic.severity.ERROR) + end) + + it("should have WARN severity", function() + assert.is_number(vim.diagnostic.severity.WARN) + end) + + it("should have INFO severity", function() + assert.is_number(vim.diagnostic.severity.INFO) + end) + + it("should have HINT severity", function() + assert.is_number(vim.diagnostic.severity.HINT) + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/minimal_init.lua b/home-manager/programs/neovim/tests/minimal_init.lua index 5da0e0232..74a422071 100644 --- a/home-manager/programs/neovim/tests/minimal_init.lua +++ b/home-manager/programs/neovim/tests/minimal_init.lua @@ -19,7 +19,9 @@ vim.opt.runtimepath:append(".") vim.opt.runtimepath:append(nvim_dir) -- Add plenary to runtimepath if installed via vim.pack or available +local plenary_dir = os.getenv("PLENARY_DIR") local plenary_paths = { + plenary_dir, -- Environment variable takes precedence vim.fn.stdpath("data") .. "/site/pack/plugins/start/plenary.nvim", vim.fn.stdpath("data") .. "/site/pack/plugins/opt/plenary.nvim", vim.fn.expand("~/.local/share/nvim/site/pack/plugins/start/plenary.nvim"), diff --git a/home-manager/programs/neovim/tests/plugins_spec.lua b/home-manager/programs/neovim/tests/plugins_spec.lua new file mode 100644 index 000000000..84071b3a9 --- /dev/null +++ b/home-manager/programs/neovim/tests/plugins_spec.lua @@ -0,0 +1,81 @@ +-- Tests for lua/plugins.lua +-- Tests plugin management and setup patterns + +describe("plugins", function() + describe("vim.pack API", function() + it("should have vim.pack table or be nil in older nvim", function() + -- vim.pack is available in Neovim 0.10+ + local has_pack = vim.pack ~= nil + assert.is_boolean(has_pack) + end) + + it("should have add function if pack API exists", function() + if vim.pack then + assert.is_function(vim.pack.add) + else + assert.is_true(true) -- Skip on older Neovim + end + end) + end) + + describe("plugin autocommands", function() + it("should support BufWritePost for lint", function() + local autocmds = vim.api.nvim_get_autocmds({ event = "BufWritePost" }) + assert.is_table(autocmds) + end) + end) + + describe("formatters_by_ft pattern", function() + it("should be able to create filetype-based config", function() + local formatters = { + lua = { "stylua" }, + python = { "black" }, + go = { "gofmt" }, + } + assert.is_table(formatters) + assert.are.same({ "stylua" }, formatters.lua) + assert.are.same({ "black" }, formatters.python) + assert.are.same({ "gofmt" }, formatters.go) + end) + end) + + describe("plugin setup pattern", function() + it("should support setup with empty table", function() + -- Common pattern: require("plugin").setup({}) + -- We test the pattern works + local mock_setup_called = false + local mock_plugin = { + setup = function(opts) + mock_setup_called = true + assert.is_table(opts) + end, + } + mock_plugin.setup({}) + assert.is_true(mock_setup_called) + end) + + it("should support setup with options", function() + local received_opts = nil + local mock_plugin = { + setup = function(opts) + received_opts = opts + end, + } + mock_plugin.setup({ enabled = true, timeout = 500 }) + assert.equals(true, received_opts.enabled) + assert.equals(500, received_opts.timeout) + end) + end) + + describe("gitsigns pattern", function() + it("should support signs configuration", function() + local signs_config = { + add = { text = "+" }, + change = { text = "~" }, + delete = { text = "_" }, + } + assert.equals("+", signs_config.add.text) + assert.equals("~", signs_config.change.text) + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/settings_spec.lua b/home-manager/programs/neovim/tests/settings_spec.lua new file mode 100644 index 000000000..8c0e3e290 --- /dev/null +++ b/home-manager/programs/neovim/tests/settings_spec.lua @@ -0,0 +1,170 @@ +-- Tests for lua/settings.lua +-- Verifies that Neovim settings are configured correctly + +describe("settings", function() + before_each(function() + package.loaded["settings"] = nil + require("settings") + end) + + describe("basic options", function() + it("should disable compatible mode", function() + assert.is_false(vim.opt.compatible:get()) + end) + + it("should enable hidden buffers", function() + assert.is_true(vim.opt.hidden:get()) + end) + + it("should set updatetime to 300", function() + assert.equals(300, vim.opt.updatetime:get()) + end) + + it("should enable mouse support for all modes", function() + local mouse = vim.opt.mouse:get() + assert.is_true(mouse.a == true or mouse == "a") + end) + end) + + describe("split behavior", function() + it("should split below by default", function() + assert.is_true(vim.opt.splitbelow:get()) + end) + + it("should split right by default", function() + assert.is_true(vim.opt.splitright:get()) + end) + end) + + describe("indentation", function() + it("should use spaces instead of tabs", function() + assert.is_true(vim.opt.expandtab:get()) + end) + + it("should enable smart indentation", function() + assert.is_true(vim.opt.smartindent:get()) + end) + + it("should set shiftwidth to 2", function() + assert.equals(2, vim.opt.shiftwidth:get()) + end) + + it("should set softtabstop to 2", function() + assert.equals(2, vim.opt.softtabstop:get()) + end) + + it("should set tabstop to 2", function() + assert.equals(2, vim.opt.tabstop:get()) + end) + end) + + describe("line numbers", function() + it("should enable line numbers", function() + assert.is_true(vim.opt.number:get()) + end) + + it("should enable relative line numbers", function() + assert.is_true(vim.opt.relativenumber:get()) + end) + end) + + describe("scrolling", function() + it("should set scrolloff to 10", function() + assert.equals(10, vim.opt.scrolloff:get()) + end) + + it("should set sidescrolloff to 10", function() + assert.equals(10, vim.opt.sidescrolloff:get()) + end) + + it("should enable smooth scrolling", function() + assert.is_true(vim.opt.smoothscroll:get()) + end) + end) + + describe("search options", function() + it("should disable highlight search", function() + assert.is_false(vim.opt.hlsearch:get()) + end) + + it("should enable case insensitive search", function() + assert.is_true(vim.opt.ignorecase:get()) + end) + + it("should enable incremental search", function() + assert.is_true(vim.opt.incsearch:get()) + end) + end) + + describe("backup and undo", function() + it("should disable swapfile", function() + assert.is_false(vim.opt.swapfile:get()) + end) + + it("should enable backup", function() + assert.is_true(vim.opt.backup:get()) + end) + + it("should enable persistent undo", function() + assert.is_true(vim.opt.undofile:get()) + end) + + it("should set backup directory", function() + local backupdir = vim.opt.backupdir:get() + assert.is_true(#backupdir > 0) + end) + + it("should set undo directory", function() + local undodir = vim.opt.undodir:get() + assert.is_true(type(undodir) == "string" or type(undodir) == "table") + end) + end) + + describe("visual settings", function() + it("should enable signcolumn", function() + assert.equals("yes", vim.opt.signcolumn:get()) + end) + + it("should enable cursor line", function() + assert.is_true(vim.opt.cursorline:get()) + end) + + it("should set colorcolumn to 80", function() + local colorcolumn = vim.opt.colorcolumn:get() + assert.are.same({ "80" }, colorcolumn) + end) + + it("should enable termguicolors", function() + assert.is_true(vim.opt.termguicolors:get()) + end) + end) + + describe("completion", function() + it("should set completeopt correctly", function() + local completeopt = vim.opt.completeopt:get() + assert.is_true(vim.tbl_contains(completeopt, "menu")) + assert.is_true(vim.tbl_contains(completeopt, "menuone")) + assert.is_true(vim.tbl_contains(completeopt, "noselect")) + end) + end) + + describe("grep program", function() + it("should use ripgrep for grepping", function() + local grepprg = vim.opt.grepprg:get() + assert.is_true(grepprg:match("rg") ~= nil) + end) + end) + + describe("timeout", function() + it("should set timeoutlen to 300", function() + assert.equals(300, vim.opt.timeoutlen:get()) + end) + end) + + describe("spell checking", function() + it("should set spelllang to en_us", function() + local spelllang = vim.opt.spelllang:get() + assert.is_true(vim.tbl_contains(spelllang, "en_us")) + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/telescope_spec.lua b/home-manager/programs/neovim/tests/telescope_spec.lua new file mode 100644 index 000000000..7d6b0b1df --- /dev/null +++ b/home-manager/programs/neovim/tests/telescope_spec.lua @@ -0,0 +1,99 @@ +-- Tests for lua/telescope.lua +-- Tests telescope-related APIs (telescope not loaded in minimal test env) + +describe("telescope", function() + describe("picker keymaps", function() + it("should have C-p mapped or mappable", function() + -- Verify the keymap pattern works + vim.keymap.set("n", "", function() end, { noremap = true }) + local keymap = vim.fn.maparg("", "n") + assert.is_true(keymap ~= "") + vim.keymap.del("n", "") + end) + + it("should support leader keymaps for pickers", function() + local leader_maps = { + "of", + "lg", + "fb", + "fh", + "fc", + "fr", + "fq", + "/", + } + for _, lhs in ipairs(leader_maps) do + vim.keymap.set("n", lhs, function() end, { noremap = true }) + local keymap = vim.fn.maparg(lhs, "n") + assert.is_true(keymap ~= "", "Failed for " .. lhs) + vim.keymap.del("n", lhs) + end + end) + end) + + describe("vimgrep_arguments pattern", function() + it("should support rg arguments", function() + local vimgrep_args = { + "rg", + "--color=never", + "--no-heading", + "--with-filename", + "--line-number", + "--column", + "--smart-case", + } + assert.equals("rg", vimgrep_args[1]) + assert.is_true(vim.tbl_contains(vimgrep_args, "--smart-case")) + end) + end) + + describe("themes pattern", function() + it("should support ivy theme config", function() + local ivy_config = { + theme = "ivy", + layout_config = { + height = 0.4, + }, + } + assert.equals("ivy", ivy_config.theme) + end) + + it("should support dropdown theme config", function() + local dropdown_config = { + theme = "dropdown", + layout_config = { + width = 0.8, + }, + } + assert.equals("dropdown", dropdown_config.theme) + end) + end) + + describe("extensions pattern", function() + it("should support extension configuration", function() + local extensions = { + fzf = { + fuzzy = true, + override_generic_sorter = true, + }, + ["ui-select"] = {}, + } + assert.is_true(extensions.fzf.fuzzy) + assert.is_table(extensions["ui-select"]) + end) + end) + + describe("find_command pattern", function() + it("should support fd command arguments", function() + local find_command = { + "fd", + "--type", + "f", + "--strip-cwd-prefix", + "--hidden", + } + assert.equals("fd", find_command[1]) + assert.is_true(vim.tbl_contains(find_command, "--hidden")) + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/terminal_spec.lua b/home-manager/programs/neovim/tests/terminal_spec.lua new file mode 100644 index 000000000..eb239d405 --- /dev/null +++ b/home-manager/programs/neovim/tests/terminal_spec.lua @@ -0,0 +1,46 @@ +-- Tests for terminal functionality +-- Tests terminal API basics (plugins not loaded in minimal test env) + +describe("terminal", function() + describe("terminal API basics", function() + it("should have termopen function available", function() + assert.is_function(vim.fn.termopen) + end) + + it("should be able to create terminal buffers", function() + local buf = vim.api.nvim_create_buf(false, true) + assert.is_true(vim.api.nvim_buf_is_valid(buf)) + vim.api.nvim_buf_delete(buf, { force = true }) + end) + + it("should have TermOpen event", function() + local autocmds = vim.api.nvim_get_autocmds({ event = "TermOpen" }) + assert.is_table(autocmds) + end) + + it("should recognize terminal buftype", function() + -- buftype=terminal can only be set by termopen, but we can check the option exists + local buf = vim.api.nvim_create_buf(false, true) + local buftype = vim.bo[buf].buftype + assert.is_string(buftype) + vim.api.nvim_buf_delete(buf, { force = true }) + end) + end) + + describe("terminal window options", function() + it("should be able to create floating window", function() + local buf = vim.api.nvim_create_buf(false, true) + local win = vim.api.nvim_open_win(buf, true, { + relative = "editor", + width = 80, + height = 20, + row = 5, + col = 5, + style = "minimal", + }) + assert.is_true(vim.api.nvim_win_is_valid(win)) + vim.api.nvim_win_close(win, true) + vim.api.nvim_buf_delete(buf, { force = true }) + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/treesitter_spec.lua b/home-manager/programs/neovim/tests/treesitter_spec.lua new file mode 100644 index 000000000..587c1e84c --- /dev/null +++ b/home-manager/programs/neovim/tests/treesitter_spec.lua @@ -0,0 +1,62 @@ +-- Tests for lua/treesitter.lua +-- Tests treesitter configuration + +describe("treesitter", function() + describe("vim.treesitter API", function() + it("should have vim.treesitter available", function() + assert.is_table(vim.treesitter) + end) + + it("should have get_parser function", function() + assert.is_function(vim.treesitter.get_parser) + end) + + it("should have get_node function", function() + assert.is_function(vim.treesitter.get_node) + end) + + it("should have start function for highlighting", function() + assert.is_function(vim.treesitter.start) + end) + + it("should have stop function", function() + assert.is_function(vim.treesitter.stop) + end) + end) + + describe("treesitter language", function() + it("should have language module", function() + assert.is_table(vim.treesitter.language) + end) + + it("should be able to check if language is available", function() + assert.is_function(vim.treesitter.language.get_lang) + end) + end) + + describe("treesitter query", function() + it("should have query module", function() + assert.is_table(vim.treesitter.query) + end) + + it("should have get or get_query function", function() + -- API changed in newer Neovim versions + local has_get = vim.treesitter.query.get ~= nil or vim.treesitter.query.get_query ~= nil + assert.is_true(has_get) + end) + + it("should have parse or parse_query function", function() + -- API changed in newer Neovim versions + local has_parse = vim.treesitter.query.parse ~= nil or vim.treesitter.query.parse_query ~= nil + assert.is_true(has_parse) + end) + end) + + describe("incremental selection keymaps", function() + it("should have C-space mapped for init_selection", function() + local keymap = vim.fn.maparg("", "n") + -- Just verify it doesn't error, may not be set in test env + assert.is_string(keymap) + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/ui_spec.lua b/home-manager/programs/neovim/tests/ui_spec.lua new file mode 100644 index 000000000..c70df1700 --- /dev/null +++ b/home-manager/programs/neovim/tests/ui_spec.lua @@ -0,0 +1,62 @@ +-- Tests for lua/ui.lua +-- Tests UI-related configurations + +describe("ui", function() + describe("vim.notify", function() + it("should be overridden with nvim-notify", function() + assert.is_function(vim.notify) + end) + + it("should not error when calling notify", function() + assert.has_no.errors(function() + vim.notify("Test notification", vim.log.levels.INFO) + end) + end) + + it("should support different log levels", function() + assert.has_no.errors(function() + vim.notify("Debug message", vim.log.levels.DEBUG) + vim.notify("Info message", vim.log.levels.INFO) + vim.notify("Warn message", vim.log.levels.WARN) + vim.notify("Error message", vim.log.levels.ERROR) + end) + end) + end) + + describe("colorscheme", function() + it("should support colorscheme API", function() + -- Dracula plugin may not be loaded in minimal test env + -- Just verify colorscheme API works + local colors = vim.fn.getcompletion("", "color") + assert.is_table(colors) + end) + + it("should have background option set", function() + local bg = vim.opt.background:get() + assert.is_true(bg == "dark" or bg == "light") + end) + + it("should support termguicolors option", function() + -- In test env, termguicolors may not be set + local tgc = vim.opt.termguicolors:get() + assert.is_boolean(tgc) + end) + end) + + describe("vim.ui", function() + it("should have vim.ui.select available", function() + assert.is_function(vim.ui.select) + end) + + it("should have vim.ui.input available", function() + assert.is_function(vim.ui.input) + end) + end) + + describe("statusline", function() + it("should have laststatus set", function() + local laststatus = vim.opt.laststatus:get() + assert.is_true(laststatus >= 0) + end) + end) +end) diff --git a/home-manager/programs/neovim/tests/workspace_spec.lua b/home-manager/programs/neovim/tests/workspace_spec.lua new file mode 100644 index 000000000..75c587ce4 --- /dev/null +++ b/home-manager/programs/neovim/tests/workspace_spec.lua @@ -0,0 +1,56 @@ +-- Tests for lua/workspace.lua +-- Tests workspace-specific settings and commands + +describe("workspace", function() + before_each(function() + package.loaded["workspace"] = nil + require("workspace") + end) + + describe("user commands", function() + it("should define WorkspaceRoot command", function() + local commands = vim.api.nvim_get_commands({}) + assert.is_not_nil(commands.WorkspaceRoot) + end) + + it("WorkspaceRoot command should not error", function() + assert.has_no.errors(function() + vim.cmd("WorkspaceRoot") + end) + end) + end) + + describe("indentation autocmd", function() + it("should set shiftwidth to 2 for new buffers", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(buf) + vim.bo[buf].filetype = "lua" + + vim.api.nvim_exec_autocmds("FileType", { pattern = "lua" }) + + assert.equals(2, vim.opt_local.shiftwidth:get()) + + vim.api.nvim_buf_delete(buf, { force = true }) + end) + + it("should set tabstop to 2 for new buffers", function() + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(buf) + vim.bo[buf].filetype = "javascript" + + vim.api.nvim_exec_autocmds("FileType", { pattern = "javascript" }) + + assert.equals(2, vim.opt_local.tabstop:get()) + + vim.api.nvim_buf_delete(buf, { force = true }) + end) + end) + + describe("getcwd", function() + it("should return current working directory", function() + local cwd = vim.fn.getcwd() + assert.is_string(cwd) + assert.is_true(#cwd > 0) + end) + end) +end)