From 80c6c1839d6022ea6806c460f772bce281e201d1 Mon Sep 17 00:00:00 2001 From: Tim Heuett Date: Tue, 26 Aug 2025 15:39:52 -0700 Subject: [PATCH 1/6] add tests coverage for nodejs module --- tests/test_nodejs.lua | 147 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 tests/test_nodejs.lua diff --git a/tests/test_nodejs.lua b/tests/test_nodejs.lua new file mode 100644 index 00000000..c9f8a5a3 --- /dev/null +++ b/tests/test_nodejs.lua @@ -0,0 +1,147 @@ +local eq = MiniTest.expect.equality + +local T = MiniTest.new_set({ + hooks = { + pre_once = function() end, + pre_case = function() + -- Reset the module state before each test + package.loaded["copilot.lsp.nodejs"] = nil + end, + }, +}) + +T["get_node_version()"] = MiniTest.new_set() + +local function stub_process(stdout, code, fail, callback) + local captured_args = nil + local original_vim_system = vim.system + vim.system = function(cmd, opts) + captured_args = cmd + if fail then + error("Command failed") + end + return { + wait = function() + return { + stdout = stdout .. "\n", + code = code + } + end + } + end + callback() + vim.system = original_vim_system + return captured_args +end + +T["get_node_version()"]["default node command"] = function() + captured_args = stub_process("v20.10.0", 0, false, function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup() + + local version, error = nodejs.get_node_version() + + eq(version, "20.10.0") + eq(error, nil) + end) + eq(captured_args, { "node", "--version" }) +end + +T["get_node_version()"]["custom node command as string"] = function() + local captured_args = stub_process("v20.10.0", 0, false, function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup("/usr/local/bin/node") + + local version, error = nodejs.get_node_version() + + eq(version, "20.10.0") + eq(error, nil) + end) + eq(captured_args, { "/usr/local/bin/node", "--version" }) +end + +T["get_node_version()"]["custom node command as string with spaces"] = function() + local captured_args = stub_process("v20.10.0", 0, false, function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup("/path to/node") + + local version, error = nodejs.get_node_version() + + eq(version, "20.10.0") + eq(error, nil) + end) + eq(captured_args, { "/path to/node", "--version" }) +end + +T["get_node_version()"]["handles vim.system failure"] = function() + local captured_args = stub_process("", -1, true, function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup("node") + + local version, error = nodejs.get_node_version() + + eq(version, "") + -- Error should contain failure information + local expected_error_pattern = "Could not determine Node%.js version" + eq(type(error), "string") + eq(error:find(expected_error_pattern) ~= nil, true) + end) +end + +T["get_node_version()"]["handles process with non-zero exit code"] = function() + local captured_args = stub_process("", 127, false, function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup("nonexistent-node") + + local version, error = nodejs.get_node_version() + + eq(version, "") + -- Error should contain failure information with exit code + local expected_error_pattern = "Could not determine Node%.js version" + eq(type(error), "string") + eq(error:find(expected_error_pattern) ~= nil, true) + eq(error:find("127") ~= nil, true) + end) + eq(captured_args, { "nonexistent-node", "--version" }) +end + +T["get_node_version()"]["validates node version requirement"] = function() + local captured_args = stub_process("v18.17.0", 0, false, function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup("node") + + local version, error = nodejs.get_node_version() + + eq(version, "18.17.0") + -- Error should indicate version requirement not met + eq(type(error), "string") + eq(error:find("Node%.js version 20 or newer required") ~= nil, true) + eq(error:find("18%.17%.0") ~= nil, true) + end) + eq(captured_args, { "node", "--version" }) +end + +T["get_execute_command()"] = MiniTest.new_set() + +T["get_execute_command()"]["default node command"] = function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup() + local cmd = nodejs.get_execute_command() + eq(cmd, { "node", nodejs.server_path, "--stdio" }) +end + +T["get_execute_command()"]["custom node command as string"] = function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup("/usr/local/bin/node") + local cmd = nodejs.get_execute_command() + eq(cmd, { "/usr/local/bin/node", nodejs.server_path, "--stdio" }) +end + +T["get_execute_command()"]["custom node command as string with spaces"] = function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup("/path to/node") + local cmd = nodejs.get_execute_command() + eq(cmd, { "/path to/node", nodejs.server_path, "--stdio" }) +end + +return T From 5d898b3ade3ad34296cb3c1d1ff680fadcf193c9 Mon Sep 17 00:00:00 2001 From: Tim Heuett Date: Tue, 26 Aug 2025 15:42:06 -0700 Subject: [PATCH 2/6] revert to original get_execute_command --- lua/copilot/lsp/nodejs.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lua/copilot/lsp/nodejs.lua b/lua/copilot/lsp/nodejs.lua index 47a56834..33d9d7a6 100644 --- a/lua/copilot/lsp/nodejs.lua +++ b/lua/copilot/lsp/nodejs.lua @@ -95,10 +95,11 @@ end ---@return table function M.get_execute_command() - local cmd = { M.node_command } - table.insert(cmd, M.server_path or M.get_server_path()) - table.insert(cmd, "--stdio") - return cmd + return { + M.node_command, + M.server_path or M.get_server_path(), + "--stdio", + } end ---@param node_command? string From c4e8a3ef7c841114a0998f38740b02f73db815b7 Mon Sep 17 00:00:00 2001 From: Tim Heuett Date: Tue, 26 Aug 2025 15:48:36 -0700 Subject: [PATCH 3/6] add support for table copilot node command --- lua/copilot/config/init.lua | 4 ++-- lua/copilot/lsp/nodejs.lua | 24 ++++++++++++++++-------- lua/copilot/util.lua | 27 +++++++++++++++++++++++++++ tests/test_nodejs.lua | 20 ++++++++++++++++++++ 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/lua/copilot/config/init.lua b/lua/copilot/config/init.lua index a15ecf61..fedc6b19 100644 --- a/lua/copilot/config/init.lua +++ b/lua/copilot/config/init.lua @@ -12,7 +12,7 @@ local logger = require("copilot.logger") ---@field copilot_model string|nil Model to use for Copilot, LSP server dictates the default ---@field root_dir RootDirFuncOrString Root directory for the project, defaults to the nearest .git directory ---@field should_attach ShouldAttachFunc Function to determine if Copilot should attach to the buffer ----@field copilot_node_command string Path to the Node.js executable, defaults to "node" +---@field copilot_node_command string|string[] Path to the Node.js executable, defaults to "node" ---@field disable_limit_reached_message boolean Disable the limit reached message, defaults to false local initialized = false @@ -68,7 +68,7 @@ function M.validate(config) vim.validate("copilot_model", config.copilot_model, { "string", "nil" }) vim.validate("root_dir", config.root_dir, { "string", "function" }) vim.validate("should_attach", config.should_attach, "function") - vim.validate("copilot_node_command", config.copilot_node_command, "string") + vim.validate("copilot_node_command", config.copilot_node_command, {"string", "table"}) require("copilot.config.panel").validate(config.panel) require("copilot.config.suggestion").validate(config.suggestion) diff --git a/lua/copilot/lsp/nodejs.lua b/lua/copilot/lsp/nodejs.lua index 33d9d7a6..0c34cdc3 100644 --- a/lua/copilot/lsp/nodejs.lua +++ b/lua/copilot/lsp/nodejs.lua @@ -1,19 +1,21 @@ local logger = require("copilot.logger") +local util = require("copilot.util") local M = { ---@class copilot_nodejs_server_info - ---@type string + ---@type string|string[] node_command = nil, ---@type string server_path = nil, initialization_failed = false, } + ---@return string node_version ---@return nil|string node_version_error function M.get_node_version() if not M.node_version then - local version_cmd = { M.node_command, "--version" } + local version_cmd = util.append_command(M.node_command, "--version") local node_version_major = 0 local node_version = "" @@ -95,14 +97,20 @@ end ---@return table function M.get_execute_command() - return { - M.node_command, - M.server_path or M.get_server_path(), - "--stdio", - } + if type(M.node_command) == "string" then + return { + M.node_command, + M.server_path or M.get_server_path(), + "--stdio", + } + elseif type(M.node_command) == "table" then + return util.append_command(M.node_command, { M.server_path or M.get_server_path(), "--stdio" }) + else + error(string.format("failed to build node command from %s (type %s)", M.node_command, type(M.node_command))) + end end ----@param node_command? string +---@param node_command? string|string[] ---@param custom_server_path? string ---@return boolean function M.setup(node_command, custom_server_path) diff --git a/lua/copilot/util.lua b/lua/copilot/util.lua index 35ea94b9..a1eb2e34 100644 --- a/lua/copilot/util.lua +++ b/lua/copilot/util.lua @@ -186,4 +186,31 @@ function M.get_buffer_previous_ft(bufnr) return (ok and result) or "" end +---@param cmd string|string[] +---@param append string|string[] +---@return string[] +M.append_command = function(cmd, append) + local full_cmd = {} + + -- first append the base command + if type(cmd) == "string" then + table.insert(full_cmd, cmd) + elseif type(cmd) == "table" then + for _, part in ipairs(cmd) do + table.insert(full_cmd, part) + end + end + + -- then append the additional parts + if type(append) == "string" then + table.insert(full_cmd, append) + elseif type(append) == "table" then + for _, part in ipairs(append) do + table.insert(full_cmd, part) + end + end + + return full_cmd +end + return M diff --git a/tests/test_nodejs.lua b/tests/test_nodejs.lua index c9f8a5a3..4dcbc443 100644 --- a/tests/test_nodejs.lua +++ b/tests/test_nodejs.lua @@ -73,6 +73,19 @@ T["get_node_version()"]["custom node command as string with spaces"] = function( eq(captured_args, { "/path to/node", "--version" }) end +T["get_node_version()"]["custom node command as table"] = function() + local captured_args = stub_process("v20.10.0", 0, false, function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup({ "mise", "x", "node@lts", "--", "node" }) + + local version, error = nodejs.get_node_version() + + eq(version, "20.10.0") + eq(error, nil) + end) + eq(captured_args, { "mise", "x", "node@lts", "--", "node", "--version" }) +end + T["get_node_version()"]["handles vim.system failure"] = function() local captured_args = stub_process("", -1, true, function() local nodejs = require("copilot.lsp.nodejs") @@ -144,4 +157,11 @@ T["get_execute_command()"]["custom node command as string with spaces"] = functi eq(cmd, { "/path to/node", nodejs.server_path, "--stdio" }) end +T["get_execute_command()"]["custom node command as table"] = function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup({ "mise", "x", "node@lts", "--", "node" }) + local cmd = nodejs.get_execute_command() + eq(cmd, { "mise", "x", "node@lts", "--", "node", nodejs.server_path, "--stdio" }) +end + return T From b7365f34c18a8fdca39411e262e39a88fc57b6ec Mon Sep 17 00:00:00 2001 From: Tim Heuett Date: Wed, 27 Aug 2025 22:24:27 -0700 Subject: [PATCH 4/6] rethink system stubs for nodejs tests --- lua/copilot/lsp/nodejs.lua | 12 +-- lua/copilot/util.lua | 8 +- tests/test_nodejs.lua | 159 ++++++++++++++++++++---------------- tests/test_nodejs_stubs.lua | 79 ++++++++++++++++++ 4 files changed, 173 insertions(+), 85 deletions(-) create mode 100644 tests/test_nodejs_stubs.lua diff --git a/lua/copilot/lsp/nodejs.lua b/lua/copilot/lsp/nodejs.lua index 0c34cdc3..d402d613 100644 --- a/lua/copilot/lsp/nodejs.lua +++ b/lua/copilot/lsp/nodejs.lua @@ -97,17 +97,7 @@ end ---@return table function M.get_execute_command() - if type(M.node_command) == "string" then - return { - M.node_command, - M.server_path or M.get_server_path(), - "--stdio", - } - elseif type(M.node_command) == "table" then - return util.append_command(M.node_command, { M.server_path or M.get_server_path(), "--stdio" }) - else - error(string.format("failed to build node command from %s (type %s)", M.node_command, type(M.node_command))) - end + return util.append_command(M.node_command, { M.server_path or M.get_server_path(), "--stdio" }) end ---@param node_command? string|string[] diff --git a/lua/copilot/util.lua b/lua/copilot/util.lua index a1eb2e34..dd6392e3 100644 --- a/lua/copilot/util.lua +++ b/lua/copilot/util.lua @@ -197,7 +197,9 @@ M.append_command = function(cmd, append) table.insert(full_cmd, cmd) elseif type(cmd) == "table" then for _, part in ipairs(cmd) do - table.insert(full_cmd, part) + if part ~= nil then + table.insert(full_cmd, part) + end end end @@ -206,7 +208,9 @@ M.append_command = function(cmd, append) table.insert(full_cmd, append) elseif type(append) == "table" then for _, part in ipairs(append) do - table.insert(full_cmd, part) + if part ~= nil then + table.insert(full_cmd, part) + end end end diff --git a/tests/test_nodejs.lua b/tests/test_nodejs.lua index 4dcbc443..a5289aa4 100644 --- a/tests/test_nodejs.lua +++ b/tests/test_nodejs.lua @@ -1,4 +1,5 @@ local eq = MiniTest.expect.equality +local stub = require("tests.test_nodejs_stubs") local T = MiniTest.new_set({ hooks = { @@ -12,156 +13,170 @@ local T = MiniTest.new_set({ T["get_node_version()"] = MiniTest.new_set() -local function stub_process(stdout, code, fail, callback) - local captured_args = nil - local original_vim_system = vim.system - vim.system = function(cmd, opts) - captured_args = cmd - if fail then - error("Command failed") - end - return { - wait = function() - return { - stdout = stdout .. "\n", - code = code - } - end - } - end - callback() - vim.system = original_vim_system - return captured_args -end - T["get_node_version()"]["default node command"] = function() - captured_args = stub_process("v20.10.0", 0, false, function() + local captured_args = stub.valid_node(function() local nodejs = require("copilot.lsp.nodejs") nodejs.setup() local version, error = nodejs.get_node_version() - eq(version, "20.10.0") + eq(version, stub.valid_node_version) eq(error, nil) end) eq(captured_args, { "node", "--version" }) end T["get_node_version()"]["custom node command as string"] = function() - local captured_args = stub_process("v20.10.0", 0, false, function() + local captured_args = stub.valid_node(function() local nodejs = require("copilot.lsp.nodejs") nodejs.setup("/usr/local/bin/node") local version, error = nodejs.get_node_version() - eq(version, "20.10.0") + eq(version, stub.valid_node_version) eq(error, nil) end) eq(captured_args, { "/usr/local/bin/node", "--version" }) end T["get_node_version()"]["custom node command as string with spaces"] = function() - local captured_args = stub_process("v20.10.0", 0, false, function() + local captured_args = stub.valid_node(function() local nodejs = require("copilot.lsp.nodejs") nodejs.setup("/path to/node") local version, error = nodejs.get_node_version() - eq(version, "20.10.0") + eq(version, stub.valid_node_version) eq(error, nil) end) eq(captured_args, { "/path to/node", "--version" }) end T["get_node_version()"]["custom node command as table"] = function() - local captured_args = stub_process("v20.10.0", 0, false, function() + local captured_args = stub.valid_node(function() local nodejs = require("copilot.lsp.nodejs") nodejs.setup({ "mise", "x", "node@lts", "--", "node" }) local version, error = nodejs.get_node_version() - eq(version, "20.10.0") + eq(version, stub.valid_node_version) eq(error, nil) end) eq(captured_args, { "mise", "x", "node@lts", "--", "node", "--version" }) end T["get_node_version()"]["handles vim.system failure"] = function() - local captured_args = stub_process("", -1, true, function() + local captured_args = stub.process("", -1, true, function() local nodejs = require("copilot.lsp.nodejs") nodejs.setup("node") - local version, error = nodejs.get_node_version() + local _, error = nodejs.get_node_version() - eq(version, "") - -- Error should contain failure information - local expected_error_pattern = "Could not determine Node%.js version" - eq(type(error), "string") - eq(error:find(expected_error_pattern) ~= nil, true) + eq(error:find("Could not determine Node.js version") ~= nil, true) end) + eq(captured_args, { "node", "--version" }) end T["get_node_version()"]["handles process with non-zero exit code"] = function() - local captured_args = stub_process("", 127, false, function() + local captured_args = stub.process("", 127, false, function() local nodejs = require("copilot.lsp.nodejs") nodejs.setup("nonexistent-node") - local version, error = nodejs.get_node_version() + local _, error = nodejs.get_node_version() - eq(version, "") - -- Error should contain failure information with exit code - local expected_error_pattern = "Could not determine Node%.js version" - eq(type(error), "string") - eq(error:find(expected_error_pattern) ~= nil, true) - eq(error:find("127") ~= nil, true) + eq(error:find("Could not determine Node.js version") ~= nil, true) end) eq(captured_args, { "nonexistent-node", "--version" }) end T["get_node_version()"]["validates node version requirement"] = function() - local captured_args = stub_process("v18.17.0", 0, false, function() + local captured_args = stub.invalid_node(function() local nodejs = require("copilot.lsp.nodejs") nodejs.setup("node") - local version, error = nodejs.get_node_version() + local _, error = nodejs.get_node_version() - eq(version, "18.17.0") - -- Error should indicate version requirement not met - eq(type(error), "string") - eq(error:find("Node%.js version 20 or newer required") ~= nil, true) - eq(error:find("18%.17%.0") ~= nil, true) + eq(error:find("Node.js version 20 or newer required") ~= nil, true) end) eq(captured_args, { "node", "--version" }) end T["get_execute_command()"] = MiniTest.new_set() -T["get_execute_command()"]["default node command"] = function() - local nodejs = require("copilot.lsp.nodejs") - nodejs.setup() - local cmd = nodejs.get_execute_command() - eq(cmd, { "node", nodejs.server_path, "--stdio" }) +T["get_execute_command()"]["default node command, default server path"] = function() + local captured_path = stub.get_runtime_server_path(function() + local nodejs = require("copilot.lsp.nodejs") + eq(nodejs.setup(), true) + local cmd = nodejs.get_execute_command() + eq(cmd, { "node", stub.default_server_path, "--stdio" }) + end) + eq(captured_path, stub.default_server_path) +end + +T["get_execute_command()"]["default node command, custom server path"] = function() + stub.get_runtime_server_path(function() + local nodejs = require("copilot.lsp.nodejs") + eq(nodejs.setup(nil, stub.custom_server_path), true) + local cmd = nodejs.get_execute_command() + eq(cmd, { "node", stub.custom_server_path, "--stdio" }) + end) +end + +T["get_execute_command()"]["custom node command as string, default server path"] = function() + local captured_path = stub.get_runtime_server_path(function() + local nodejs = require("copilot.lsp.nodejs") + eq(nodejs.setup("/usr/local/bin/node"), true) + local cmd = nodejs.get_execute_command() + eq(cmd, { "/usr/local/bin/node", stub.default_server_path, "--stdio" }) + end) + eq(captured_path, stub.default_server_path) +end + +T["get_execute_command()"]["custom node command as string, custom server path"] = function() + stub.get_runtime_server_path(function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup("/usr/local/bin/node", stub.custom_server_path) + local cmd = nodejs.get_execute_command() + eq(cmd, { "/usr/local/bin/node", stub.custom_server_path, "--stdio" }) + end) +end + +T["get_execute_command()"]["custom node command as string with spaces, default server path"] = function() + local captured_path = stub.get_runtime_server_path(function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup("/path to/node") + local cmd = nodejs.get_execute_command() + eq(cmd, { "/path to/node", stub.default_server_path, "--stdio" }) + end) + eq(captured_path, stub.default_server_path) end -T["get_execute_command()"]["custom node command as string"] = function() - local nodejs = require("copilot.lsp.nodejs") - nodejs.setup("/usr/local/bin/node") - local cmd = nodejs.get_execute_command() - eq(cmd, { "/usr/local/bin/node", nodejs.server_path, "--stdio" }) +T["get_execute_command()"]["custom node command as string with spaces, custom server path"] = function() + stub.get_runtime_server_path(function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup("/path to/node", stub.custom_server_path) + local cmd = nodejs.get_execute_command() + eq(cmd, { "/path to/node", stub.custom_server_path, "--stdio" }) + end) end -T["get_execute_command()"]["custom node command as string with spaces"] = function() - local nodejs = require("copilot.lsp.nodejs") - nodejs.setup("/path to/node") - local cmd = nodejs.get_execute_command() - eq(cmd, { "/path to/node", nodejs.server_path, "--stdio" }) +T["get_execute_command()"]["custom node command as table, default server path"] = function() + local captured_path = stub.get_runtime_server_path(function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup({ "mise", "x", "node@lts", "--", "node" }) + local cmd = nodejs.get_execute_command() + eq(cmd, { "mise", "x", "node@lts", "--", "node", stub.default_server_path, "--stdio" }) + end) + eq(captured_path, stub.default_server_path) end -T["get_execute_command()"]["custom node command as table"] = function() - local nodejs = require("copilot.lsp.nodejs") - nodejs.setup({ "mise", "x", "node@lts", "--", "node" }) - local cmd = nodejs.get_execute_command() - eq(cmd, { "mise", "x", "node@lts", "--", "node", nodejs.server_path, "--stdio" }) +T["get_execute_command()"]["custom node command as table, custom server path"] = function() + stub.get_runtime_server_path(function() + local nodejs = require("copilot.lsp.nodejs") + nodejs.setup({ "mise", "x", "node@lts", "--", "node" }, stub.custom_server_path) + local cmd = nodejs.get_execute_command() + eq(cmd, { "mise", "x", "node@lts", "--", "node", stub.custom_server_path, "--stdio" }) + end) end return T diff --git a/tests/test_nodejs_stubs.lua b/tests/test_nodejs_stubs.lua new file mode 100644 index 00000000..0d3e097a --- /dev/null +++ b/tests/test_nodejs_stubs.lua @@ -0,0 +1,79 @@ +-- these are helper functions for testing the nodejs LSP integration +-- they stub out system interactions for verifying access to node and the LSP script + +Stub = {} + +Stub.default_server_path = "copilot/js/language-server.js" +Stub.custom_server_path = "custom/path/to/language-server.js" + +---@param stdout string the stdout that will be returned by the stubbed vim.system +---@param code integer the exit code that will be returned by the stubbed vim.system +---@param fail boolean if true, vim.system will error when called +---@param callback function the function to call while vim.system is stubbed +---@return table|nil captured_args -- the arguments vim.system was called with +Stub.process = function(stdout, code, fail, callback) + local captured_args = nil + local original_vim_system = vim.system + vim.system = function(cmd) + captured_args = cmd + if fail then + error("Command failed") + end + return { + wait = function() + return { + stdout = stdout .. "\n", + code = code + } + end + } + end + -- wrap callback in pcall to ensure vim.system is restored if callback errors + local ok, err = pcall(callback) + vim.system = original_vim_system + if not ok then error(err) end + return captured_args +end + +Stub.valid_node_version = "20.0.0" +Stub.invalid_node_version = "10.0.0" + +---Convenience wrapper for Stub.process for a valid Node.js version (>= 20) +Stub.valid_node = function(callback) + return Stub.process("v"..Stub.valid_node_version, 0, false, callback) +end + +---Convenience wrapper for Stub.process for an invalid Node.js version (< 20) +Stub.invalid_node = function(callback) + return Stub.process("v"..Stub.invalid_node_version, 0, false, callback) +end + +---@param callback function the function to call while vim.api.nvim_get_runtime_file is stubbed +---@return string|nil captured_path -- the path vim.api.nvim_get_runtime_file was called with +Stub.get_runtime_server_path = function(callback) + local captured_path = nil + + local original_get_file = vim.api.nvim_get_runtime_file + vim.api.nvim_get_runtime_file = function(path) + captured_path = path + return { vim.fn.expand(Stub.default_server_path) } + end + + local original_filereadable = vim.fn.filereadable + vim.fn.filereadable = function() + return 1 + end + + -- stub valid node version for callback so setup() succeeds + Stub.valid_node(function() + -- wrap callback in pcall to ensure vim.api.nvim_get_runtime_file is restored if callback errors + local ok, err = pcall(callback) + vim.api.nvim_get_runtime_file = original_get_file + vim.fn.filereadable = original_filereadable + if not ok then error(err) end + end) + + return captured_path +end + +return Stub From be62c56f46cff54aa4e61c9aad17f3a9410e035b Mon Sep 17 00:00:00 2001 From: Antoine Gaudreau Simard Date: Thu, 28 Aug 2025 15:55:05 -0400 Subject: [PATCH 5/6] refactor: linting, naming --- lua/copilot/auth/init.lua | 2 +- lua/copilot/util.lua | 4 +- .../nodejs.lua} | 44 +++++++++++-------- tests/test_nodejs.lua | 5 ++- 4 files changed, 33 insertions(+), 22 deletions(-) rename tests/{test_nodejs_stubs.lua => stubs/nodejs.lua} (69%) diff --git a/lua/copilot/auth/init.lua b/lua/copilot/auth/init.lua index 747ad5d4..ff091479 100644 --- a/lua/copilot/auth/init.lua +++ b/lua/copilot/auth/init.lua @@ -153,7 +153,7 @@ function M.find_config_path() end ---@return table|nil -M.get_creds = function() +function M.get_creds() local filename = M.find_config_path() .. "/github-copilot/apps.json" if vim.fn.filereadable(filename) == 0 then diff --git a/lua/copilot/util.lua b/lua/copilot/util.lua index 639edea6..ebbacebd 100644 --- a/lua/copilot/util.lua +++ b/lua/copilot/util.lua @@ -110,7 +110,7 @@ function M.get_doc_params(overrides) return params end -M.get_plugin_path = function() +function M.get_plugin_path() local copilot_path = vim.api.nvim_get_runtime_file("lua/copilot/init.lua", false)[1] if vim.fn.filereadable(copilot_path) ~= 0 then return vim.fn.fnamemodify(copilot_path, ":h:h:h") @@ -189,7 +189,7 @@ end ---@param cmd string|string[] ---@param append string|string[] ---@return string[] -M.append_command = function(cmd, append) +function M.append_command(cmd, append) local full_cmd = {} -- first append the base command diff --git a/tests/test_nodejs_stubs.lua b/tests/stubs/nodejs.lua similarity index 69% rename from tests/test_nodejs_stubs.lua rename to tests/stubs/nodejs.lua index 0d3e097a..5be355e8 100644 --- a/tests/test_nodejs_stubs.lua +++ b/tests/stubs/nodejs.lua @@ -1,19 +1,21 @@ -- these are helper functions for testing the nodejs LSP integration -- they stub out system interactions for verifying access to node and the LSP script -Stub = {} +M = {} -Stub.default_server_path = "copilot/js/language-server.js" -Stub.custom_server_path = "custom/path/to/language-server.js" +M.default_server_path = "copilot/js/language-server.js" +M.custom_server_path = "custom/path/to/language-server.js" ---@param stdout string the stdout that will be returned by the stubbed vim.system ---@param code integer the exit code that will be returned by the stubbed vim.system ---@param fail boolean if true, vim.system will error when called ---@param callback function the function to call while vim.system is stubbed ---@return table|nil captured_args -- the arguments vim.system was called with -Stub.process = function(stdout, code, fail, callback) +function M.process(stdout, code, fail, callback) local captured_args = nil local original_vim_system = vim.system + + ---@diagnostic disable-next-line: duplicate-set-field vim.system = function(cmd) captured_args = cmd if fail then @@ -23,57 +25,63 @@ Stub.process = function(stdout, code, fail, callback) wait = function() return { stdout = stdout .. "\n", - code = code + code = code, } - end + end, } end -- wrap callback in pcall to ensure vim.system is restored if callback errors local ok, err = pcall(callback) vim.system = original_vim_system - if not ok then error(err) end + if not ok then + error(err) + end return captured_args end -Stub.valid_node_version = "20.0.0" -Stub.invalid_node_version = "10.0.0" +M.valid_node_version = "20.0.0" +M.invalid_node_version = "10.0.0" ---Convenience wrapper for Stub.process for a valid Node.js version (>= 20) -Stub.valid_node = function(callback) - return Stub.process("v"..Stub.valid_node_version, 0, false, callback) +function M.valid_node(callback) + return M.process("v" .. M.valid_node_version, 0, false, callback) end ---Convenience wrapper for Stub.process for an invalid Node.js version (< 20) -Stub.invalid_node = function(callback) - return Stub.process("v"..Stub.invalid_node_version, 0, false, callback) +function M.invalid_node(callback) + return M.process("v" .. M.invalid_node_version, 0, false, callback) end ---@param callback function the function to call while vim.api.nvim_get_runtime_file is stubbed ---@return string|nil captured_path -- the path vim.api.nvim_get_runtime_file was called with -Stub.get_runtime_server_path = function(callback) +function M.get_runtime_server_path(callback) local captured_path = nil local original_get_file = vim.api.nvim_get_runtime_file + ---@diagnostic disable-next-line: duplicate-set-field vim.api.nvim_get_runtime_file = function(path) captured_path = path - return { vim.fn.expand(Stub.default_server_path) } + return { vim.fn.expand(M.default_server_path) } end local original_filereadable = vim.fn.filereadable + ---@diagnostic disable-next-line: duplicate-set-field vim.fn.filereadable = function() return 1 end -- stub valid node version for callback so setup() succeeds - Stub.valid_node(function() + M.valid_node(function() -- wrap callback in pcall to ensure vim.api.nvim_get_runtime_file is restored if callback errors local ok, err = pcall(callback) vim.api.nvim_get_runtime_file = original_get_file vim.fn.filereadable = original_filereadable - if not ok then error(err) end + if not ok then + error(err) + end end) return captured_path end -return Stub +return M diff --git a/tests/test_nodejs.lua b/tests/test_nodejs.lua index a5289aa4..2872398b 100644 --- a/tests/test_nodejs.lua +++ b/tests/test_nodejs.lua @@ -1,5 +1,5 @@ local eq = MiniTest.expect.equality -local stub = require("tests.test_nodejs_stubs") +local stub = require("tests.stubs.nodejs") local T = MiniTest.new_set({ hooks = { @@ -71,6 +71,7 @@ T["get_node_version()"]["handles vim.system failure"] = function() nodejs.setup("node") local _, error = nodejs.get_node_version() + error = error or "" eq(error:find("Could not determine Node.js version") ~= nil, true) end) @@ -83,6 +84,7 @@ T["get_node_version()"]["handles process with non-zero exit code"] = function() nodejs.setup("nonexistent-node") local _, error = nodejs.get_node_version() + error = error or "" eq(error:find("Could not determine Node.js version") ~= nil, true) end) @@ -95,6 +97,7 @@ T["get_node_version()"]["validates node version requirement"] = function() nodejs.setup("node") local _, error = nodejs.get_node_version() + error = error or "" eq(error:find("Node.js version 20 or newer required") ~= nil, true) end) From 0c6663c517befac2b63b22f766a477ea58ab9c13 Mon Sep 17 00:00:00 2001 From: Antoine Gaudreau Simard Date: Thu, 28 Aug 2025 15:55:28 -0400 Subject: [PATCH 6/6] test: failing tests on win32 environments --- tests/test_nodejs.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_nodejs.lua b/tests/test_nodejs.lua index 2872398b..909e9072 100644 --- a/tests/test_nodejs.lua +++ b/tests/test_nodejs.lua @@ -111,7 +111,7 @@ T["get_execute_command()"]["default node command, default server path"] = functi local nodejs = require("copilot.lsp.nodejs") eq(nodejs.setup(), true) local cmd = nodejs.get_execute_command() - eq(cmd, { "node", stub.default_server_path, "--stdio" }) + eq(cmd, { "node", vim.fn.expand(stub.default_server_path), "--stdio" }) end) eq(captured_path, stub.default_server_path) end @@ -119,9 +119,9 @@ end T["get_execute_command()"]["default node command, custom server path"] = function() stub.get_runtime_server_path(function() local nodejs = require("copilot.lsp.nodejs") - eq(nodejs.setup(nil, stub.custom_server_path), true) + eq(nodejs.setup(nil, vim.fn.expand(stub.custom_server_path)), true) local cmd = nodejs.get_execute_command() - eq(cmd, { "node", stub.custom_server_path, "--stdio" }) + eq(cmd, { "node", vim.fn.expand(stub.custom_server_path), "--stdio" }) end) end @@ -130,7 +130,7 @@ T["get_execute_command()"]["custom node command as string, default server path"] local nodejs = require("copilot.lsp.nodejs") eq(nodejs.setup("/usr/local/bin/node"), true) local cmd = nodejs.get_execute_command() - eq(cmd, { "/usr/local/bin/node", stub.default_server_path, "--stdio" }) + eq(cmd, { "/usr/local/bin/node", vim.fn.expand(stub.default_server_path), "--stdio" }) end) eq(captured_path, stub.default_server_path) end @@ -149,7 +149,7 @@ T["get_execute_command()"]["custom node command as string with spaces, default s local nodejs = require("copilot.lsp.nodejs") nodejs.setup("/path to/node") local cmd = nodejs.get_execute_command() - eq(cmd, { "/path to/node", stub.default_server_path, "--stdio" }) + eq(cmd, { "/path to/node", vim.fn.expand(stub.default_server_path), "--stdio" }) end) eq(captured_path, stub.default_server_path) end @@ -168,7 +168,7 @@ T["get_execute_command()"]["custom node command as table, default server path"] local nodejs = require("copilot.lsp.nodejs") nodejs.setup({ "mise", "x", "node@lts", "--", "node" }) local cmd = nodejs.get_execute_command() - eq(cmd, { "mise", "x", "node@lts", "--", "node", stub.default_server_path, "--stdio" }) + eq(cmd, { "mise", "x", "node@lts", "--", "node", vim.fn.expand(stub.default_server_path), "--stdio" }) end) eq(captured_path, stub.default_server_path) end