local eq = assert.are.same local lsp = require("ale.lsp") describe("ale.lsp.start", function() local start_calls local rpc_connect_calls local vim_fn_calls local defer_calls local nvim_default_capabilities setup(function() _G.vim = { defer_fn = function(func, delay) table.insert(defer_calls, {func, delay}) end, fn = setmetatable({}, { __index = function(_, key) return function(...) table.insert(vim_fn_calls, {key, ...}) if key == "ale#lsp#GetLanguage" then return "python" end if key ~= "ale#lsp_linter#HandleLSPDiagnostics" and key ~= "ale#lsp#UpdateCapabilities" and key ~= "ale#lsp#CallInitCallbacks" then assert(false, "Invalid ALE function: " .. key) end return nil end end, }), lsp = { rpc = { connect = function(host, port) return function(dispatch) table.insert(rpc_connect_calls, { host = host, port = port, dispatch = dispatch, }) end end, }, start = function(...) table.insert(start_calls, {...}) return 42 end, protocol = { make_client_capabilities = function() return nvim_default_capabilities end, }, }, } end) teardown(function() _G.vim = nil end) before_each(function() start_calls = {} rpc_connect_calls = {} vim_fn_calls = {} defer_calls = {} nvim_default_capabilities = { textDocument = {}, } end) it("should start lsp programs with the correct arguments", function() lsp.start({ name = "server:/code", cmd = "server", root_dir = "/code", -- This Boolean value somehow ends up in Dictionaries from -- Vim for init_options, and we need to remove it. init_options = {[true] = 123}, }) -- Remove arguments with functions we can't apply equality checks -- for easily. for _, args in pairs(start_calls) do args[1].handlers = nil args[1].on_init = nil args[1].get_language_id = nil end eq({ { { cmd = "server", name = "server:/code", root_dir = "/code", init_options = {}, }, {attach = false, silent = true} } }, start_calls) eq({}, vim_fn_calls) end) it("should start lsp socket connections with the correct arguments", function() lsp.start({ name = "localhost:1234:/code", host = "localhost", port = 1234, root_dir = "/code", init_options = {foo = "bar"}, }) local cmd -- Remove arguments with functions we can't apply equality checks -- for easily. for _, args in pairs(start_calls) do cmd = args[1].cmd args[1].cmd = nil args[1].handlers = nil args[1].on_init = nil args[1].get_language_id = nil end eq({ { { name = "localhost:1234:/code", root_dir = "/code", init_options = {foo = "bar"}, }, {attach = false, silent = true} } }, start_calls) cmd("dispatch_value") eq({ {dispatch = "dispatch_value", host = "localhost", port = 1234}, }, rpc_connect_calls) eq({}, vim_fn_calls) end) it("should return the client_id value from vim.lsp.start", function() eq(42, lsp.start({})) end) it("should implement get_language_id correctly", function() lsp.start({name = "server:/code"}) eq(1, #start_calls) eq("python", start_calls[1][1].get_language_id(347, "ftype")) eq({{"ale#lsp#GetLanguage", "server:/code", 347}}, vim_fn_calls) end) it("should enable dynamicRegistration for the pull model", function() nvim_default_capabilities = {textDocument = {diagnostic = {}}} lsp.start({name = "server:/code"}) eq(1, #start_calls) eq( { textDocument = { diagnostic = { dynamicRegistration = true, }, }, }, start_calls[1][1].capabilities ) end) it("should initialize clients with ALE correctly", function() lsp.start({name = "server:/code"}) eq(1, #start_calls) start_calls[1][1].on_init({server_capabilities = {cap = 1}}) eq({ {"ale#lsp#UpdateCapabilities", "server:/code", {cap = 1}}, }, vim_fn_calls) eq(1, #defer_calls) eq(2, #defer_calls[1]) eq("function", type(defer_calls[1][1])) eq(0, defer_calls[1][2]) defer_calls[1][1]() eq({ {"ale#lsp#UpdateCapabilities", "server:/code", {cap = 1}}, {"ale#lsp#CallInitCallbacks", "server:/code"}, }, vim_fn_calls) end) it("should configure handlers correctly", function() lsp.start({name = "server:/code"}) eq(1, #start_calls) local handlers = start_calls[1][1].handlers local handler_names = {} -- get keys from handlers for key, _ in pairs(handlers) do -- add key to handler_names mapping handler_names[key] = true end eq({ ["textDocument/publishDiagnostics"] = true, ["textDocument/diagnostic"] = true, ["workspace/diagnostic/refresh"] = true, }, handler_names) end) it("should handle push model published diagnostics", function() lsp.start({name = "server:/code"}) eq(1, #start_calls) local handlers = start_calls[1][1].handlers eq("function", type(handlers["textDocument/publishDiagnostics"])) handlers["textDocument/publishDiagnostics"](nil, { uri = "file://code/foo.py", diagnostics = { { lnum = 1, end_lnum = 2, col = 3, end_col = 5, severity = 1, code = "123", message = "Warning message", } }, }) eq({ { "ale#lsp_linter#HandleLSPDiagnostics", "server:/code", "file://code/foo.py", { { lnum = 1, end_lnum = 2, col = 3, end_col = 5, severity = 1, code = "123", message = "Warning message", }, }, }, }, vim_fn_calls) end) it("should respond to workspace diagnostic refresh requests", function() lsp.start({name = "server:/code"}) eq(1, #start_calls) local handlers = start_calls[1][1].handlers eq("function", type(handlers["workspace/diagnostic/refresh"])) eq({}, handlers["workspace/diagnostic/refresh"]()) end) it("should handle pull model diagnostics", function() lsp.start({name = "server:/code"}) eq(1, #start_calls) local handlers = start_calls[1][1].handlers eq("function", type(handlers["textDocument/diagnostic"])) handlers["textDocument/diagnostic"]( nil, { kind = "full", items = { { lnum = 1, end_lnum = 2, col = 3, end_col = 5, severity = 1, code = "123", message = "Warning message", } }, }, { params = { textDocument = { uri = "file://code/foo.py", }, }, } ) eq({ { "ale#lsp_linter#HandleLSPDiagnostics", "server:/code", "file://code/foo.py", { { lnum = 1, end_lnum = 2, col = 3, end_col = 5, severity = 1, code = "123", message = "Warning message", }, }, }, }, vim_fn_calls) end) it("should handle unchanged pull model diagnostics", function() lsp.start({name = "server:/code"}) eq(1, #start_calls) local handlers = start_calls[1][1].handlers eq("function", type(handlers["textDocument/diagnostic"])) handlers["textDocument/diagnostic"]( nil, {kind = "unchanged"}, { params = { textDocument = { uri = "file://code/foo.py", }, }, } ) eq({ { "ale#lsp_linter#HandleLSPDiagnostics", "server:/code", "file://code/foo.py", "unchanged", }, }, vim_fn_calls) end) end)