+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)