diff options
Diffstat (limited to 'test/functional/lua')
-rw-r--r-- | test/functional/lua/api_spec.lua | 7 | ||||
-rw-r--r-- | test/functional/lua/buffer_updates_spec.lua | 16 | ||||
-rw-r--r-- | test/functional/lua/commands_spec.lua | 44 | ||||
-rw-r--r-- | test/functional/lua/diagnostic_spec.lua | 1147 | ||||
-rw-r--r-- | test/functional/lua/highlight_spec.lua | 25 | ||||
-rw-r--r-- | test/functional/lua/json_spec.lua | 133 | ||||
-rw-r--r-- | test/functional/lua/luaeval_spec.lua | 56 | ||||
-rw-r--r-- | test/functional/lua/mpack_spec.lua | 23 | ||||
-rw-r--r-- | test/functional/lua/overrides_spec.lua | 47 | ||||
-rw-r--r-- | test/functional/lua/ui_spec.lua | 46 | ||||
-rw-r--r-- | test/functional/lua/uri_spec.lua | 32 | ||||
-rw-r--r-- | test/functional/lua/vim_spec.lua | 122 | ||||
-rw-r--r-- | test/functional/lua/xdiff_spec.lua | 112 |
13 files changed, 1706 insertions, 104 deletions
diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua index 896554f7a3..fdf79d55b2 100644 --- a/test/functional/lua/api_spec.lua +++ b/test/functional/lua/api_spec.lua @@ -15,7 +15,7 @@ describe('luaeval(vim.api.…)', function() describe('nvim_buf_get_lines', function() it('works', function() funcs.setline(1, {"abc", "def", "a\nb", "ttt"}) - eq({{_TYPE={}, _VAL={'a\nb'}}}, + eq({'a\000b'}, funcs.luaeval('vim.api.nvim_buf_get_lines(1, 2, 3, false)')) end) end) @@ -23,7 +23,7 @@ describe('luaeval(vim.api.…)', function() it('works', function() funcs.setline(1, {"abc", "def", "a\nb", "ttt"}) eq(NIL, funcs.luaeval('vim.api.nvim_buf_set_lines(1, 1, 2, false, {"b\\0a"})')) - eq({'abc', {_TYPE={}, _VAL={'b\na'}}, {_TYPE={}, _VAL={'a\nb'}}, 'ttt'}, + eq({'abc', 'b\000a', 'a\000b', 'ttt'}, funcs.luaeval('vim.api.nvim_buf_get_lines(1, 0, 4, false)')) end) end) @@ -64,15 +64,18 @@ describe('luaeval(vim.api.…)', function() it('correctly converts from API objects', function() eq(1, funcs.luaeval('vim.api.nvim_eval("1")')) eq('1', funcs.luaeval([[vim.api.nvim_eval('"1"')]])) + eq('Blobby', funcs.luaeval('vim.api.nvim_eval("0z426c6f626279")')) eq({}, funcs.luaeval('vim.api.nvim_eval("[]")')) eq({}, funcs.luaeval('vim.api.nvim_eval("{}")')) eq(1, funcs.luaeval('vim.api.nvim_eval("1.0")')) + eq('\000', funcs.luaeval('vim.api.nvim_eval("0z00")')) eq(true, funcs.luaeval('vim.api.nvim_eval("v:true")')) eq(false, funcs.luaeval('vim.api.nvim_eval("v:false")')) eq(NIL, funcs.luaeval('vim.api.nvim_eval("v:null")')) eq(0, eval([[type(luaeval('vim.api.nvim_eval("1")'))]])) eq(1, eval([[type(luaeval('vim.api.nvim_eval("''1''")'))]])) + eq(1, eval([[type(luaeval('vim.api.nvim_eval("0zbeef")'))]])) eq(3, eval([[type(luaeval('vim.api.nvim_eval("[]")'))]])) eq(4, eval([[type(luaeval('vim.api.nvim_eval("{}")'))]])) eq(5, eval([[type(luaeval('vim.api.nvim_eval("1.0")'))]])) diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index a5b613f0b2..073927bf22 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -978,6 +978,22 @@ describe('lua: nvim_buf_attach on_bytes', function() } end) + it("visual paste", function() + local check_events= setup_eventcheck(verify, { "aaa {", "b", "}" }) + -- Setting up + feed[[jdd]] + check_events { + { "test1", "bytes", 1, 3, 1, 0, 6, 1, 0, 2, 0, 0, 0 }; + } + + -- Actually testing + feed[[v%p]] + check_events { + { "test1", "bytes", 1, 8, 0, 4, 4, 1, 1, 3, 0, 0, 0 }; + { "test1", "bytes", 1, 8, 0, 4, 4, 0, 0, 0, 2, 0, 3 }; + } + end) + it("nvim_buf_set_lines", function() local check_events = setup_eventcheck(verify, {"AAA", "BBB"}) diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua index f2a1b7dede..2e0d0ea899 100644 --- a/test/functional/lua/commands_spec.lua +++ b/test/functional/lua/commands_spec.lua @@ -15,14 +15,14 @@ local command = helpers.command local exc_exec = helpers.exc_exec local pcall_err = helpers.pcall_err local write_file = helpers.write_file -local redir_exec = helpers.redir_exec +local exec_capture = helpers.exec_capture local curbufmeths = helpers.curbufmeths before_each(clear) describe(':lua command', function() it('works', function() - eq('', redir_exec( + eq('', exec_capture( 'lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"TEST"})')) eq({'', 'TEST'}, curbufmeths.get_lines(0, 100, false)) source(dedent([[ @@ -67,18 +67,19 @@ describe(':lua command', function() eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false)) end) it('preserves global and not preserves local variables', function() - eq('', redir_exec('lua gvar = 42')) - eq('', redir_exec('lua local lvar = 100500')) + eq('', exec_capture('lua gvar = 42')) + eq('', exec_capture('lua local lvar = 100500')) eq(NIL, funcs.luaeval('lvar')) eq(42, funcs.luaeval('gvar')) end) it('works with long strings', function() local s = ('x'):rep(100500) - eq('\nE5107: Error loading lua [string ":lua"]:1: unfinished string near \'<eof>\'', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s})'):format(s))) + eq('Vim(lua):E5107: Error loading lua [string ":lua"]:0: unfinished string near \'<eof>\'', + pcall_err(command, ('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s})'):format(s))) eq({''}, curbufmeths.get_lines(0, -1, false)) - eq('', redir_exec(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s"})'):format(s))) + eq('', exec_capture(('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"%s"})'):format(s))) eq({'', s}, curbufmeths.get_lines(0, -1, false)) end) @@ -144,34 +145,34 @@ end) describe(':luado command', function() it('works', function() curbufmeths.set_lines(0, 1, false, {"ABC", "def", "gHi"}) - eq('', redir_exec('luado lines = (lines or {}) lines[#lines + 1] = {linenr, line}')) + eq('', exec_capture('luado lines = (lines or {}) lines[#lines + 1] = {linenr, line}')) eq({'ABC', 'def', 'gHi'}, curbufmeths.get_lines(0, -1, false)) eq({{1, 'ABC'}, {2, 'def'}, {3, 'gHi'}}, funcs.luaeval('lines')) -- Automatic transformation of numbers - eq('', redir_exec('luado return linenr')) + eq('', exec_capture('luado return linenr')) eq({'1', '2', '3'}, curbufmeths.get_lines(0, -1, false)) - eq('', redir_exec('luado return ("<%02x>"):format(line:byte())')) + eq('', exec_capture('luado return ("<%02x>"):format(line:byte())')) eq({'<31>', '<32>', '<33>'}, curbufmeths.get_lines(0, -1, false)) end) it('stops processing lines when suddenly out of lines', function() curbufmeths.set_lines(0, 1, false, {"ABC", "def", "gHi"}) - eq('', redir_exec('2,$luado runs = ((runs or 0) + 1) vim.api.nvim_command("%d")')) + eq('', exec_capture('2,$luado runs = ((runs or 0) + 1) vim.api.nvim_command("%d")')) eq({''}, curbufmeths.get_lines(0, -1, false)) eq(1, funcs.luaeval('runs')) end) it('works correctly when changing lines out of range', function() curbufmeths.set_lines(0, 1, false, {"ABC", "def", "gHi"}) - eq('\nE322: line number out of range: 1 past the end\nE320: Cannot find line 2', - redir_exec('2,$luado vim.api.nvim_command("%d") return linenr')) + eq('Vim(luado):E322: line number out of range: 1 past the end', + pcall_err(command, '2,$luado vim.api.nvim_command("%d") return linenr')) eq({''}, curbufmeths.get_lines(0, -1, false)) end) it('fails on errors', function() - eq([[Vim(luado):E5109: Error loading lua: [string ":luado"]:1: unexpected symbol near ')']], - exc_exec('luado ()')) - eq([[Vim(luado):E5111: Error calling lua: [string ":luado"]:1: attempt to perform arithmetic on global 'liness' (a nil value)]], - exc_exec('luado return liness + 1')) + eq([[Vim(luado):E5109: Error loading lua: [string ":luado"]:0: unexpected symbol near ')']], + pcall_err(command, 'luado ()')) + eq([[Vim(luado):E5111: Error calling lua: [string ":luado"]:0: attempt to perform arithmetic on global 'liness' (a nil value)]], + pcall_err(command, 'luado return liness + 1')) end) it('works with NULL errors', function() eq([=[Vim(luado):E5111: Error calling lua: [NULL]]=], @@ -179,17 +180,18 @@ describe(':luado command', function() end) it('fails in sandbox when needed', function() curbufmeths.set_lines(0, 1, false, {"ABC", "def", "gHi"}) - eq('\nE48: Not allowed in sandbox: sandbox luado runs = (runs or 0) + 1', - redir_exec('sandbox luado runs = (runs or 0) + 1')) + eq('Vim(luado):E48: Not allowed in sandbox', + pcall_err(command, 'sandbox luado runs = (runs or 0) + 1')) eq(NIL, funcs.luaeval('runs')) end) it('works with long strings', function() local s = ('x'):rep(100500) - eq('\nE5109: Error loading lua: [string ":luado"]:1: unfinished string near \'<eof>\'', redir_exec(('luado return "%s'):format(s))) + eq('Vim(luado):E5109: Error loading lua: [string ":luado"]:0: unfinished string near \'<eof>\'', + pcall_err(command, ('luado return "%s'):format(s))) eq({''}, curbufmeths.get_lines(0, -1, false)) - eq('', redir_exec(('luado return "%s"'):format(s))) + eq('', exec_capture(('luado return "%s"'):format(s))) eq({s}, curbufmeths.get_lines(0, -1, false)) end) end) @@ -207,7 +209,7 @@ describe(':luafile', function() vim.api.nvim_buf_set_lines(1, 2, 3, false, {"TTSE"}) vim.api.nvim_buf_set_lines(1, 3, 4, false, {"STTE"}) ]]) - eq('', redir_exec('luafile ' .. fname)) + eq('', exec_capture('luafile ' .. fname)) eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false)) end) diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua new file mode 100644 index 0000000000..9397af9d9f --- /dev/null +++ b/test/functional/lua/diagnostic_spec.lua @@ -0,0 +1,1147 @@ +local helpers = require('test.functional.helpers')(after_each) + +local NIL = helpers.NIL +local command = helpers.command +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local eq = helpers.eq +local nvim = helpers.nvim + +describe('vim.diagnostic', function() + before_each(function() + clear() + + exec_lua [[ + require('vim.diagnostic') + + function make_diagnostic(msg, x1, y1, x2, y2, severity, source) + return { + lnum = x1, + col = y1, + end_lnum = x2, + end_col = y2, + message = msg, + severity = severity, + source = source, + } + end + + function make_error(msg, x1, y1, x2, y2, source) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.ERROR, source) + end + + function make_warning(msg, x1, y1, x2, y2, source) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.WARN, source) + end + + function make_info(msg, x1, y1, x2, y2, source) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.INFO, source) + end + + function make_hint(msg, x1, y1, x2, y2, source) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.HINT, source) + end + + function count_diagnostics(bufnr, severity, namespace) + return #vim.diagnostic.get(bufnr, {severity = severity, namespace = namespace}) + end + + function count_extmarks(bufnr, namespace) + return #vim.api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, {}) + end + ]] + + exec_lua([[ + diagnostic_ns = vim.api.nvim_create_namespace("diagnostic_spec") + other_ns = vim.api.nvim_create_namespace("other_namespace") + diagnostic_bufnr = vim.api.nvim_create_buf(true, false) + local lines = {"1st line of text", "2nd line of text", "wow", "cool", "more", "lines"} + vim.fn.bufload(diagnostic_bufnr) + vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, 1, false, lines) + return diagnostic_bufnr + ]]) + end) + + after_each(function() + clear() + end) + + it('creates highlight groups', function() + command('runtime plugin/diagnostic.vim') + eq({ + 'DiagnosticError', + 'DiagnosticFloatingError', + 'DiagnosticFloatingHint', + 'DiagnosticFloatingInfo', + 'DiagnosticFloatingWarn', + 'DiagnosticHint', + 'DiagnosticInfo', + 'DiagnosticSignError', + 'DiagnosticSignHint', + 'DiagnosticSignInfo', + 'DiagnosticSignWarn', + 'DiagnosticUnderlineError', + 'DiagnosticUnderlineHint', + 'DiagnosticUnderlineInfo', + 'DiagnosticUnderlineWarn', + 'DiagnosticVirtualTextError', + 'DiagnosticVirtualTextHint', + 'DiagnosticVirtualTextInfo', + 'DiagnosticVirtualTextWarn', + 'DiagnosticWarn', + }, exec_lua([[return vim.fn.getcompletion('Diagnostic', 'highlight')]])) + end) + + it('retrieves diagnostics from all buffers and namespaces', function() + local result = exec_lua [[ + vim.diagnostic.set(diagnostic_ns, 1, { + make_error('Diagnostic #1', 1, 1, 1, 1), + make_error('Diagnostic #2', 2, 1, 2, 1), + }) + vim.diagnostic.set(other_ns, 2, { + make_error('Diagnostic #3', 3, 1, 3, 1), + }) + return vim.diagnostic.get() + ]] + eq(3, #result) + eq(2, exec_lua([[return #vim.tbl_filter(function(d) return d.bufnr == 1 end, ...)]], result)) + eq('Diagnostic #1', result[1].message) + end) + + it('saves and count a single error', function() + eq(1, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + }) + return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns) + ]]) + end) + + it('saves and count multiple errors', function() + eq(2, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + make_error('Diagnostic #2', 2, 1, 2, 1), + }) + return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns) + ]]) + end) + + it('saves and count from multiple namespaces', function() + eq({1, 1, 2}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic From Server 1', 1, 1, 1, 1), + }) + vim.diagnostic.set(other_ns, diagnostic_bufnr, { + make_error('Diagnostic From Server 2', 1, 1, 1, 1), + }) + return { + -- First namespace + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), + -- Second namespace + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, other_ns), + -- All namespaces + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR), + } + ]]) + end) + + it('saves and count from multiple namespaces with respect to severity', function() + eq({3, 0, 3}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), + make_error('Diagnostic From Server 1:2', 2, 2, 2, 2), + make_error('Diagnostic From Server 1:3', 2, 3, 3, 2), + }) + vim.diagnostic.set(other_ns, diagnostic_bufnr, { + make_warning('Warning From Server 2', 3, 3, 3, 3), + }) + return { + -- Namespace 1 + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), + -- Namespace 2 + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, other_ns), + -- All namespaces + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR), + } + ]]) + end) + + it('handles one namespace clearing highlights while the other still has highlights', function() + -- 1 Error (1) + -- 1 Warning (2) + -- 1 Warning (2) + 1 Warning (1) + -- 2 highlights and 2 underlines (since error) + -- 1 highlight + 1 underline + local all_highlights = {1, 1, 2, 4, 2} + eq(all_highlights, exec_lua [[ + local ns_1_diags = { + make_error("Error 1", 1, 1, 1, 5), + make_warning("Warning on Server 1", 2, 1, 2, 5), + } + local ns_2_diags = { + make_warning("Warning 1", 2, 1, 2, 5), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) + vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags) + + return { + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns), + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN), + count_extmarks(diagnostic_bufnr, diagnostic_ns), + count_extmarks(diagnostic_bufnr, other_ns), + } + ]]) + + -- Clear diagnostics from namespace 1, and make sure we have the right amount of stuff for namespace 2 + eq({1, 1, 2, 0, 2}, exec_lua [[ + vim.diagnostic.disable(diagnostic_bufnr, diagnostic_ns) + return { + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns), + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN), + count_extmarks(diagnostic_bufnr, diagnostic_ns), + count_extmarks(diagnostic_bufnr, other_ns), + } + ]]) + + -- Show diagnostics from namespace 1 again + eq(all_highlights, exec_lua([[ + vim.diagnostic.enable(diagnostic_bufnr, diagnostic_ns) + return { + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns), + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN), + count_extmarks(diagnostic_bufnr, diagnostic_ns), + count_extmarks(diagnostic_bufnr, other_ns), + } + ]])) + end) + + it('does not display diagnostics when disabled', function() + eq({0, 2}, exec_lua [[ + local ns_1_diags = { + make_error("Error 1", 1, 1, 1, 5), + make_warning("Warning on Server 1", 2, 1, 2, 5), + } + local ns_2_diags = { + make_warning("Warning 1", 2, 1, 2, 5), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) + vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags) + + vim.diagnostic.disable(diagnostic_bufnr, diagnostic_ns) + + return { + count_extmarks(diagnostic_bufnr, diagnostic_ns), + count_extmarks(diagnostic_bufnr, other_ns), + } + ]]) + + eq({4, 0}, exec_lua [[ + vim.diagnostic.enable(diagnostic_bufnr, diagnostic_ns) + vim.diagnostic.disable(diagnostic_bufnr, other_ns) + + return { + count_extmarks(diagnostic_bufnr, diagnostic_ns), + count_extmarks(diagnostic_bufnr, other_ns), + } + ]]) + end) + + describe('reset()', function() + it('diagnostic count is 0 and displayed diagnostics are 0 after call', function() + -- 1 Error (1) + -- 1 Warning (2) + -- 1 Warning (2) + 1 Warning (1) + -- 2 highlights and 2 underlines (since error) + -- 1 highlight + 1 underline + local all_highlights = {1, 1, 2, 4, 2} + eq(all_highlights, exec_lua [[ + local ns_1_diags = { + make_error("Error 1", 1, 1, 1, 5), + make_warning("Warning on Server 1", 2, 1, 2, 5), + } + local ns_2_diags = { + make_warning("Warning 1", 2, 1, 2, 5), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) + vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags) + + return { + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns), + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN), + count_extmarks(diagnostic_bufnr, diagnostic_ns), + count_extmarks(diagnostic_bufnr, other_ns), + } + ]]) + + -- Reset diagnostics from namespace 1 + exec_lua([[ vim.diagnostic.reset(diagnostic_ns) ]]) + + -- Make sure we have the right diagnostic count + eq({0, 1, 1, 0, 2} , exec_lua [[ + local diagnostic_count = {} + vim.wait(100, function () diagnostic_count = { + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns), + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN), + count_extmarks(diagnostic_bufnr, diagnostic_ns), + count_extmarks(diagnostic_bufnr, other_ns), + } end ) + return diagnostic_count + ]]) + + -- Reset diagnostics from namespace 2 + exec_lua([[ vim.diagnostic.reset(other_ns) ]]) + + -- Make sure we have the right diagnostic count + eq({0, 0, 0, 0, 0}, exec_lua [[ + local diagnostic_count = {} + vim.wait(100, function () diagnostic_count = { + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns), + count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN), + count_extmarks(diagnostic_bufnr, diagnostic_ns), + count_extmarks(diagnostic_bufnr, other_ns), + } end ) + return diagnostic_count + ]]) + + end) + end) + + describe('get_next_pos()', function() + it('can find the next pos with only one namespace', function() + eq({1, 1}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + }) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + return vim.diagnostic.get_next_pos() + ]]) + end) + + it('can find next pos with two errors', function() + eq({4, 4}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + make_error('Diagnostic #2', 4, 4, 4, 4), + }) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.diagnostic.get_next_pos { namespace = diagnostic_ns } + ]]) + end) + + it('can cycle when position is past error', function() + eq({1, 1}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + }) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.diagnostic.get_next_pos { namespace = diagnostic_ns } + ]]) + end) + + it('will not cycle when wrap is off', function() + eq(false, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + }) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.diagnostic.get_next_pos { namespace = diagnostic_ns, wrap = false } + ]]) + end) + + it('can cycle even from the last line', function() + eq({4, 4}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #2', 4, 4, 4, 4), + }) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {vim.api.nvim_buf_line_count(0), 1}) + return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns } + ]]) + end) + end) + + describe('get_prev_pos()', function() + it('can find the prev pos with only one namespace', function() + eq({1, 1}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + }) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.diagnostic.get_prev_pos() + ]]) + end) + + it('can find prev pos with two errors', function() + eq({1, 1}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + make_error('Diagnostic #2', 4, 4, 4, 4), + }) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns } + ]]) + end) + + it('can cycle when position is past error', function() + eq({4, 4}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #2', 4, 4, 4, 4), + }) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns } + ]]) + end) + + it('respects wrap parameter', function() + eq(false, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #2', 4, 4, 4, 4), + }) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {3, 1}) + return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns, wrap = false} + ]]) + end) + end) + + describe('get()', function() + it('returns an empty table when no diagnostics are present', function() + eq({}, exec_lua [[return vim.diagnostic.get(diagnostic_bufnr, {namespace=diagnostic_ns})]]) + end) + + it('returns all diagnostics when no severity is supplied', function() + eq(2, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error("Error 1", 1, 1, 1, 5), + make_warning("Warning on Server 1", 1, 1, 2, 5), + }) + + return #vim.diagnostic.get(diagnostic_bufnr) + ]]) + end) + + it('returns only requested diagnostics when severity is supplied', function() + eq({2, 3, 2}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error("Error 1", 1, 1, 1, 5), + make_warning("Warning on Server 1", 1, 1, 2, 5), + make_info("Ignored information", 1, 1, 2, 5), + make_hint("Here's a hint", 1, 1, 2, 5), + }) + + return { + #vim.diagnostic.get(diagnostic_bufnr, { severity = {min=vim.diagnostic.severity.WARN} }), + #vim.diagnostic.get(diagnostic_bufnr, { severity = {max=vim.diagnostic.severity.WARN} }), + #vim.diagnostic.get(diagnostic_bufnr, { + severity = { + min=vim.diagnostic.severity.INFO, + max=vim.diagnostic.severity.WARN, + } + }), + } + ]]) + end) + + it('allows filtering by line', function() + eq(1, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error("Error 1", 1, 1, 1, 5), + make_warning("Warning on Server 1", 1, 1, 2, 5), + make_info("Ignored information", 1, 1, 2, 5), + make_error("Error On Other Line", 2, 1, 1, 5), + }) + + return #vim.diagnostic.get(diagnostic_bufnr, {lnum = 2}) + ]]) + end) + end) + + describe('config()', function() + it('can use functions for config values', function() + exec_lua [[ + vim.diagnostic.config({ + virtual_text = function() return true end, + }, diagnostic_ns) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + }) + ]] + + eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]]) + eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + + -- Now, don't enable virtual text. + -- We should have one less extmark displayed. + exec_lua [[ + vim.diagnostic.config({ + virtual_text = function() return false end, + }, diagnostic_ns) + ]] + + eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]]) + eq(1, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + end) + + it('allows filtering by severity', function() + local get_extmark_count_with_severity = function(min_severity) + return exec_lua([[ + vim.diagnostic.config({ + underline = false, + virtual_text = { + severity = {min=...}, + }, + }) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_warning('Delayed Diagnostic', 4, 4, 4, 4), + }) + + return count_extmarks(diagnostic_bufnr, diagnostic_ns) + ]], min_severity) + end + + -- No messages with Error or higher + eq(0, get_extmark_count_with_severity("ERROR")) + + -- But now we don't filter it + eq(1, get_extmark_count_with_severity("WARN")) + eq(1, get_extmark_count_with_severity("HINT")) + end) + + it('allows sorting by severity', function() + exec_lua [[ + vim.diagnostic.config({ + underline = false, + signs = true, + virtual_text = true, + }) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_warning('Warning', 4, 4, 4, 4), + make_error('Error', 4, 4, 4, 4), + make_info('Info', 4, 4, 4, 4), + }) + + function get_virt_text_and_signs(severity_sort) + vim.diagnostic.config({ + severity_sort = severity_sort, + }) + + local virt_text = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true})[1][4].virt_text + + local virt_texts = {} + for i = 2, #virt_text do + table.insert(virt_texts, (string.gsub(virt_text[i][2], "DiagnosticVirtualText", ""))) + end + + local signs = {} + for _, v in ipairs(vim.fn.sign_getplaced(diagnostic_bufnr, {group = "*"})[1].signs) do + table.insert(signs, (string.gsub(v.name, "DiagnosticSign", ""))) + end + + return {virt_texts, signs} + end + ]] + + local result = exec_lua [[return get_virt_text_and_signs(false)]] + + -- Virt texts are defined lowest priority to highest, signs from + -- highest to lowest + eq({'Warn', 'Error', 'Info'}, result[1]) + eq({'Info', 'Error', 'Warn'}, result[2]) + + result = exec_lua [[return get_virt_text_and_signs(true)]] + eq({'Info', 'Warn', 'Error'}, result[1]) + eq({'Error', 'Warn', 'Info'}, result[2]) + + result = exec_lua [[return get_virt_text_and_signs({ reverse = true })]] + eq({'Error', 'Warn', 'Info'}, result[1]) + eq({'Info', 'Warn', 'Error'}, result[2]) + end) + + it('can show diagnostic sources in virtual text', function() + local result = exec_lua [[ + local diagnostics = { + make_error('Some error', 0, 0, 0, 0, 'source x'), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = '', + source = 'always', + } + }) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + local virt_text = extmarks[1][4].virt_text[2][1] + return virt_text + ]] + eq(' source x: Some error', result) + + result = exec_lua [[ + vim.diagnostic.config({ + underline = false, + virtual_text = { + prefix = '', + source = 'if_many', + } + }, diagnostic_ns) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + local virt_text = extmarks[1][4].virt_text[2][1] + return virt_text + ]] + eq(' Some error', result) + + result = exec_lua [[ + local diagnostics = { + make_error('Some error', 0, 0, 0, 0, 'source x'), + make_error('Another error', 1, 1, 1, 1, 'source y'), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = '', + source = 'if_many', + } + }) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + local virt_text = {extmarks[1][4].virt_text[2][1], extmarks[2][4].virt_text[2][1]} + return virt_text + ]] + eq(' source x: Some error', result[1]) + eq(' source y: Another error', result[2]) + end) + + it('supports a format function for diagnostic messages', function() + local result = exec_lua [[ + vim.diagnostic.config({ + underline = false, + virtual_text = { + prefix = '', + format = function(diagnostic) + if diagnostic.severity == vim.diagnostic.severity.ERROR then + return string.format("🔥 %s", diagnostic.message) + end + return string.format("👀 %s", diagnostic.message) + end, + } + }) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_warning('Warning', 0, 0, 0, 0), + make_error('Error', 1, 0, 1, 0), + }) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + return {extmarks[1][4].virt_text, extmarks[2][4].virt_text} + ]] + eq(" 👀 Warning", result[1][2][1]) + eq(" 🔥 Error", result[2][2][1]) + end) + + it('includes source for formatted diagnostics', function() + local result = exec_lua [[ + vim.diagnostic.config({ + underline = false, + virtual_text = { + prefix = '', + source = 'always', + format = function(diagnostic) + if diagnostic.severity == vim.diagnostic.severity.ERROR then + return string.format("🔥 %s", diagnostic.message) + end + return string.format("👀 %s", diagnostic.message) + end, + } + }) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_warning('Warning', 0, 0, 0, 0, 'some_linter'), + make_error('Error', 1, 0, 1, 0, 'another_linter'), + }) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + return {extmarks[1][4].virt_text, extmarks[2][4].virt_text} + ]] + eq(" some_linter: 👀 Warning", result[1][2][1]) + eq(" another_linter: 🔥 Error", result[2][2][1]) + end) + end) + + describe('set()', function() + it('can perform updates after insert_leave', function() + exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] + nvim("input", "o") + eq({mode='i', blocking=false}, nvim("get_mode")) + + -- Save the diagnostics + exec_lua [[ + vim.diagnostic.config({ + update_in_insert = false, + }) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + }) + ]] + + -- No diagnostics displayed yet. + eq({mode='i', blocking=false}, nvim("get_mode")) + eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]]) + eq(0, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + + nvim("input", "<esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + + eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]]) + eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + end) + + it('does not perform updates when not needed', function() + exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] + nvim("input", "o") + eq({mode='i', blocking=false}, nvim("get_mode")) + + -- Save the diagnostics + exec_lua [[ + vim.diagnostic.config({ + update_in_insert = false, + virtual_text = true, + }) + + -- Count how many times we call display. + SetVirtualTextOriginal = vim.diagnostic._set_virtual_text + + DisplayCount = 0 + vim.diagnostic._set_virtual_text = function(...) + DisplayCount = DisplayCount + 1 + return SetVirtualTextOriginal(...) + end + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + }) + ]] + + -- No diagnostics displayed yet. + eq({mode='i', blocking=false}, nvim("get_mode")) + eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]]) + eq(0, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + eq(0, exec_lua [[return DisplayCount]]) + + nvim("input", "<esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + + eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]]) + eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + eq(1, exec_lua [[return DisplayCount]]) + + -- Go in and out of insert mode one more time. + nvim("input", "o") + eq({mode='i', blocking=false}, nvim("get_mode")) + + nvim("input", "<esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + + -- Should not have set the virtual text again. + eq(1, exec_lua [[return DisplayCount]]) + end) + + it('never sets virtual text, in combination with insert leave', function() + exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] + nvim("input", "o") + eq({mode='i', blocking=false}, nvim("get_mode")) + + -- Save the diagnostics + exec_lua [[ + vim.diagnostic.config({ + update_in_insert = false, + virtual_text = false, + }) + + -- Count how many times we call display. + SetVirtualTextOriginal = vim.diagnostic._set_virtual_text + + DisplayCount = 0 + vim.diagnostic._set_virtual_text = function(...) + DisplayCount = DisplayCount + 1 + return SetVirtualTextOriginal(...) + end + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + }) + ]] + + -- No diagnostics displayed yet. + eq({mode='i', blocking=false}, nvim("get_mode")) + eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]]) + eq(0, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + eq(0, exec_lua [[return DisplayCount]]) + + nvim("input", "<esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + + eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]]) + eq(1, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + eq(0, exec_lua [[return DisplayCount]]) + + -- Go in and out of insert mode one more time. + nvim("input", "o") + eq({mode='i', blocking=false}, nvim("get_mode")) + + nvim("input", "<esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + + -- Should not have set the virtual text still. + eq(0, exec_lua [[return DisplayCount]]) + end) + + it('can perform updates while in insert mode, if desired', function() + exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] + nvim("input", "o") + eq({mode='i', blocking=false}, nvim("get_mode")) + + -- Save the diagnostics + exec_lua [[ + vim.diagnostic.config({ + update_in_insert = true, + }) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Delayed Diagnostic', 4, 4, 4, 4), + }) + ]] + + -- Diagnostics are displayed, because the user wanted them that way! + eq({mode='i', blocking=false}, nvim("get_mode")) + eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]]) + eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + + nvim("input", "<esc>") + eq({mode='n', blocking=false}, nvim("get_mode")) + + eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]]) + eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + end) + + it('can set diagnostics without displaying them', function() + eq(0, exec_lua [[ + vim.diagnostic.disable(diagnostic_bufnr, diagnostic_ns) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), + }) + return count_extmarks(diagnostic_bufnr, diagnostic_ns) + ]]) + + eq(2, exec_lua [[ + vim.diagnostic.enable(diagnostic_bufnr, diagnostic_ns) + return count_extmarks(diagnostic_bufnr, diagnostic_ns) + ]]) + end) + + it('can set display options', function() + eq(0, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), + }, { virtual_text = false, underline = false }) + return count_extmarks(diagnostic_bufnr, diagnostic_ns) + ]]) + + eq(1, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), + }, { virtual_text = true, underline = false }) + return count_extmarks(diagnostic_bufnr, diagnostic_ns) + ]]) + end) + end) + + describe('show_line_diagnostics()', function() + it('creates floating window and returns popup bufnr and winnr if current line contains diagnostics', function() + -- Two lines: + -- Diagnostic: + -- 1. <msg> + eq(2, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics() + return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + ]]) + end) + + it('only reports diagnostics from the current buffer when bufnr is omitted #15710', function() + eq(2, exec_lua [[ + local other_bufnr = vim.api.nvim_create_buf(true, false) + local buf_1_diagnostics = { + make_error("Syntax error", 0, 1, 0, 3), + } + local buf_2_diagnostics = { + make_warning("Some warning", 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, buf_1_diagnostics) + vim.diagnostic.set(other_ns, other_bufnr, buf_2_diagnostics) + local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics() + return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + ]]) + end) + + it('allows filtering by namespace', function() + eq(2, exec_lua [[ + local ns_1_diagnostics = { + make_error("Syntax error", 0, 1, 0, 3), + } + local ns_2_diagnostics = { + make_warning("Some warning", 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diagnostics) + vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diagnostics) + local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics({namespace = diagnostic_ns}) + return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + ]]) + end) + + it('creates floating window and returns popup bufnr and winnr without header, if requested', function() + -- One line (since no header): + -- 1. <msg> + eq(1, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics {show_header = false} + return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + ]]) + end) + + it('clamps diagnostic line numbers within the valid range', function() + eq(1, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 6, 0, 6, 0), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics({show_header = false}, diagnostic_bufnr, 5) + return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + ]]) + end) + + it('can show diagnostic source', function() + exec_lua [[vim.api.nvim_win_set_buf(0, diagnostic_bufnr)]] + + eq({"1. Syntax error"}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3, "source x"), + } + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics { + show_header = false, + source = "if_many", + } + local lines = vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + eq({"1. source x: Syntax error"}, exec_lua [[ + local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics { + show_header = false, + source = "always", + } + local lines = vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + eq({"1. source x: Syntax error", "2. source y: Another error"}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3, "source x"), + make_error("Another error", 0, 1, 0, 3, "source y"), + } + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics { + show_header = false, + source = "if_many", + } + local lines = vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + end) + end) + + describe('set_signs()', function() + -- TODO(tjdevries): Find out why signs are not displayed when set from Lua...?? + pending('sets signs by default', function() + exec_lua [[ + vim.diagnostic.config({ + update_in_insert = true, + signs = true, + }) + + local diagnostics = { + make_error('Delayed Diagnostic', 1, 1, 1, 2), + make_error('Delayed Diagnostic', 3, 3, 3, 3), + } + + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + + vim.diagnostic._set_signs(diagnostic_ns, diagnostic_bufnr, diagnostics) + -- return vim.fn.sign_getplaced() + ]] + + nvim("input", "o") + nvim("input", "<esc>") + + -- TODO(tjdevries): Find a way to get the signs to display in the test... + eq(nil, exec_lua [[ + return im.fn.sign_getplaced()[1].signs + ]]) + end) + end) + + describe('setloclist()', function() + it('sets diagnostics in lnum order', function() + local loc_list = exec_lua [[ + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Farther Diagnostic', 4, 4, 4, 4), + make_error('Lower Diagnostic', 1, 1, 1, 1), + }) + + vim.diagnostic.setloclist() + + return vim.fn.getloclist(0) + ]] + + assert(loc_list[1].lnum < loc_list[2].lnum) + end) + + it('sets diagnostics in lnum order, regardless of namespace', function() + local loc_list = exec_lua [[ + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Lower Diagnostic', 1, 1, 1, 1), + }) + + vim.diagnostic.set(other_ns, diagnostic_bufnr, { + make_warning('Farther Diagnostic', 4, 4, 4, 4), + }) + + vim.diagnostic.setloclist() + + return vim.fn.getloclist(0) + ]] + + assert(loc_list[1].lnum < loc_list[2].lnum) + end) + end) + + describe('match()', function() + it('matches a string', function() + local msg = "ERROR: george.txt:19:84:Two plus two equals five" + local diagnostic = { + severity = exec_lua [[return vim.diagnostic.severity.ERROR]], + lnum = 18, + col = 83, + end_lnum = 18, + end_col = 83, + message = "Two plus two equals five", + } + eq(diagnostic, exec_lua([[ + return vim.diagnostic.match(..., "^(%w+): [^:]+:(%d+):(%d+):(.+)$", {"severity", "lnum", "col", "message"}) + ]], msg)) + end) + + it('returns nil if the pattern fails to match', function() + eq(NIL, exec_lua [[ + local msg = "The answer to life, the universe, and everything is" + return vim.diagnostic.match(msg, "This definitely will not match", {}) + ]]) + end) + + it('respects default values', function() + local msg = "anna.txt:1:Happy families are all alike" + local diagnostic = { + severity = exec_lua [[return vim.diagnostic.severity.INFO]], + lnum = 0, + col = 0, + end_lnum = 0, + end_col = 0, + message = "Happy families are all alike", + } + eq(diagnostic, exec_lua([[ + return vim.diagnostic.match(..., "^[^:]+:(%d+):(.+)$", {"lnum", "message"}, nil, {severity = vim.diagnostic.severity.INFO}) + ]], msg)) + end) + + it('accepts a severity map', function() + local msg = "46:FATAL:Et tu, Brute?" + local diagnostic = { + severity = exec_lua [[return vim.diagnostic.severity.ERROR]], + lnum = 45, + col = 0, + end_lnum = 45, + end_col = 0, + message = "Et tu, Brute?", + } + eq(diagnostic, exec_lua([[ + return vim.diagnostic.match(..., "^(%d+):(%w+):(.+)$", {"lnum", "severity", "message"}, {FATAL = vim.diagnostic.severity.ERROR}) + ]], msg)) + end) + end) + + describe('toqflist() and fromqflist()', function() + it('works', function() + local result = exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Error 1', 0, 1, 0, 1), + make_error('Error 2', 1, 1, 1, 1), + make_warning('Warning', 2, 2, 2, 2), + }) + + local diagnostics = vim.diagnostic.get(diagnostic_bufnr) + vim.fn.setqflist(vim.diagnostic.toqflist(diagnostics)) + local list = vim.fn.getqflist() + local new_diagnostics = vim.diagnostic.fromqflist(list) + + -- Remove namespace since it isn't present in the return value of + -- fromlist() + for _, v in ipairs(diagnostics) do + v.namespace = nil + end + + return {diagnostics, new_diagnostics} + ]] + eq(result[1], result[2]) + end) + end) +end) diff --git a/test/functional/lua/highlight_spec.lua b/test/functional/lua/highlight_spec.lua new file mode 100644 index 0000000000..50eecb5d09 --- /dev/null +++ b/test/functional/lua/highlight_spec.lua @@ -0,0 +1,25 @@ +local helpers = require('test.functional.helpers')(after_each) +local exec_lua = helpers.exec_lua +local eq = helpers.eq +local eval = helpers.eval +local command = helpers.command +local clear = helpers.clear + +describe('vim.highlight.on_yank', function() + + before_each(function() + clear() + end) + + it('does not show errors even if buffer is wiped before timeout', function() + command('new') + exec_lua[[ + vim.highlight.on_yank({timeout = 10, on_macro = true, event = {operator = "y", regtype = "v"}}) + vim.cmd('bwipeout!') + ]] + helpers.sleep(10) + helpers.feed('<cr>') -- avoid hang if error message exists + eq('', eval('v:errmsg')) + end) + +end) diff --git a/test/functional/lua/json_spec.lua b/test/functional/lua/json_spec.lua new file mode 100644 index 0000000000..fbb21bfd57 --- /dev/null +++ b/test/functional/lua/json_spec.lua @@ -0,0 +1,133 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local NIL = helpers.NIL +local exec_lua = helpers.exec_lua +local eq = helpers.eq + +describe('vim.json.decode function', function() + before_each(function() + clear() + end) + + it('parses null, true, false', function() + eq(NIL, exec_lua([[return vim.json.decode('null')]])) + eq(true, exec_lua([[return vim.json.decode('true')]])) + eq(false, exec_lua([[return vim.json.decode('false')]])) + end) + + it('parses integer numbers', function() + eq(100000, exec_lua([[return vim.json.decode('100000')]])) + eq(-100000, exec_lua([[return vim.json.decode('-100000')]])) + eq(100000, exec_lua([[return vim.json.decode(' 100000 ')]])) + eq(-100000, exec_lua([[return vim.json.decode(' -100000 ')]])) + eq(0, exec_lua([[return vim.json.decode('0')]])) + eq(0, exec_lua([[return vim.json.decode('-0')]])) + end) + + it('parses floating-point numbers', function() + -- This behavior differs from vim.fn.json_decode, which return '100000.0' + eq('100000', exec_lua([[return tostring(vim.json.decode('100000.0'))]])) + eq(100000.5, exec_lua([[return vim.json.decode('100000.5')]])) + eq(-100000.5, exec_lua([[return vim.json.decode('-100000.5')]])) + eq(-100000.5e50, exec_lua([[return vim.json.decode('-100000.5e50')]])) + eq(100000.5e50, exec_lua([[return vim.json.decode('100000.5e50')]])) + eq(100000.5e50, exec_lua([[return vim.json.decode('100000.5e+50')]])) + eq(-100000.5e-50, exec_lua([[return vim.json.decode('-100000.5e-50')]])) + eq(100000.5e-50, exec_lua([[return vim.json.decode('100000.5e-50')]])) + eq(100000e-50, exec_lua([[return vim.json.decode('100000e-50')]])) + eq(0.5, exec_lua([[return vim.json.decode('0.5')]])) + eq(0.005, exec_lua([[return vim.json.decode('0.005')]])) + eq(0.005, exec_lua([[return vim.json.decode('0.00500')]])) + eq(0.5, exec_lua([[return vim.json.decode('0.00500e+002')]])) + eq(0.00005, exec_lua([[return vim.json.decode('0.00500e-002')]])) + + eq(-0.0, exec_lua([[return vim.json.decode('-0.0')]])) + eq(-0.0, exec_lua([[return vim.json.decode('-0.0e0')]])) + eq(-0.0, exec_lua([[return vim.json.decode('-0.0e+0')]])) + eq(-0.0, exec_lua([[return vim.json.decode('-0.0e-0')]])) + eq(-0.0, exec_lua([[return vim.json.decode('-0e-0')]])) + eq(-0.0, exec_lua([[return vim.json.decode('-0e-2')]])) + eq(-0.0, exec_lua([[return vim.json.decode('-0e+2')]])) + + eq(0.0, exec_lua([[return vim.json.decode('0.0')]])) + eq(0.0, exec_lua([[return vim.json.decode('0.0e0')]])) + eq(0.0, exec_lua([[return vim.json.decode('0.0e+0')]])) + eq(0.0, exec_lua([[return vim.json.decode('0.0e-0')]])) + eq(0.0, exec_lua([[return vim.json.decode('0e-0')]])) + eq(0.0, exec_lua([[return vim.json.decode('0e-2')]])) + eq(0.0, exec_lua([[return vim.json.decode('0e+2')]])) + end) + + it('parses containers', function() + eq({1}, exec_lua([[return vim.json.decode('[1]')]])) + eq({NIL, 1}, exec_lua([[return vim.json.decode('[null, 1]')]])) + eq({['1']=2}, exec_lua([[return vim.json.decode('{"1": 2}')]])) + eq({['1']=2, ['3']={{['4']={['5']={{}, 1}}}}}, + exec_lua([[return vim.json.decode('{"1": 2, "3": [{"4": {"5": [ [], 1]}}]}')]])) + end) + + it('parses strings properly', function() + eq('\n', exec_lua([=[return vim.json.decode([["\n"]])]=])) + eq('', exec_lua([=[return vim.json.decode([[""]])]=])) + eq('\\/"\t\b\n\r\f', exec_lua([=[return vim.json.decode([["\\\/\"\t\b\n\r\f"]])]=])) + eq('/a', exec_lua([=[return vim.json.decode([["\/a"]])]=])) + -- Unicode characters: 2-byte, 3-byte + eq('«',exec_lua([=[return vim.json.decode([["«"]])]=])) + eq('ફ',exec_lua([=[return vim.json.decode([["ફ"]])]=])) + end) + + it('parses surrogate pairs properly', function() + eq('\240\144\128\128', exec_lua([[return vim.json.decode('"\\uD800\\uDC00"')]])) + end) + + it('accepts all spaces in every position where space may be put', function() + local s = ' \t\n\r \t\r\n \n\t\r \n\r\t \r\t\n \r\n\t\t \n\r\t \r\n\t\n \r\t\n\r \t\r \n\t\r\n \n \t\r\n \r\t\n\t \r\n\t\r \n\r \t\n\r\t \r \t\n\r \n\t\r\t \n\r\t\n \r\n \t\r\n\t' + local str = ('%s{%s"key"%s:%s[%s"val"%s,%s"val2"%s]%s,%s"key2"%s:%s1%s}%s'):gsub('%%s', s) + eq({key={'val', 'val2'}, key2=1}, exec_lua([[return vim.json.decode(...)]], str)) + end) + +end) + +describe('vim.json.encode function', function() + before_each(function() + clear() + end) + + it('dumps strings', function() + eq('"Test"', exec_lua([[return vim.json.encode('Test')]])) + eq('""', exec_lua([[return vim.json.encode('')]])) + eq('"\\t"', exec_lua([[return vim.json.encode('\t')]])) + eq('"\\n"', exec_lua([[return vim.json.encode('\n')]])) + -- vim.fn.json_encode return \\u001B + eq('"\\u001b"', exec_lua([[return vim.json.encode('\27')]])) + eq('"þÿþ"', exec_lua([[return vim.json.encode('þÿþ')]])) + end) + + it('dumps numbers', function() + eq('0', exec_lua([[return vim.json.encode(0)]])) + eq('10', exec_lua([[return vim.json.encode(10)]])) + eq('-10', exec_lua([[return vim.json.encode(-10)]])) + end) + + it('dumps floats', function() + eq('10.5', exec_lua([[return vim.json.encode(10.5)]])) + eq('-10.5', exec_lua([[return vim.json.encode(-10.5)]])) + eq('-1e-05', exec_lua([[return vim.json.encode(-1e-5)]])) + end) + + it('dumps lists', function() + eq('[]', exec_lua([[return vim.json.encode({})]])) + eq('[[]]', exec_lua([[return vim.json.encode({{}})]])) + eq('[[],[]]', exec_lua([[return vim.json.encode({{}, {}})]])) + end) + + it('dumps dictionaries', function() + eq('{}', exec_lua([[return vim.json.encode(vim.empty_dict())]])) + eq('{"d":[]}', exec_lua([[return vim.json.encode({d={}})]])) + end) + + it('dumps vim.NIL', function() + eq('null', exec_lua([[return vim.json.encode(vim.NIL)]])) + end) + +end) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 2ec48777fd..255e99032f 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -63,11 +63,10 @@ describe('luaeval()', function() eq('\n[[...@0]]', funcs.execute('echo luaeval("l")')) end) end) - describe('strings', function() - it('are successfully converted to special dictionaries', function() + describe('strings with NULs', function() + it('are successfully converted to blobs', function() command([[let s = luaeval('"\0"')]]) - eq({_TYPE={}, _VAL={'\n'}}, meths.get_var('s')) - eq(1, funcs.eval('s._TYPE is v:msgpack_types.binary')) + eq('\000', meths.get_var('s')) end) it('are successfully converted to special dictionaries in table keys', function() @@ -76,13 +75,10 @@ describe('luaeval()', function() eq(1, funcs.eval('d._TYPE is v:msgpack_types.map')) eq(1, funcs.eval('d._VAL[0][0]._TYPE is v:msgpack_types.string')) end) - it('are successfully converted to special dictionaries from a list', + it('are successfully converted to blobs from a list', function() command([[let l = luaeval('{"abc", "a\0b", "c\0d", "def"}')]]) - eq({'abc', {_TYPE={}, _VAL={'a\nb'}}, {_TYPE={}, _VAL={'c\nd'}}, 'def'}, - meths.get_var('l')) - eq(1, funcs.eval('l[1]._TYPE is v:msgpack_types.binary')) - eq(1, funcs.eval('l[2]._TYPE is v:msgpack_types.binary')) + eq({'abc', 'a\000b', 'c\000d', 'def'}, meths.get_var('l')) end) end) @@ -100,9 +96,9 @@ describe('luaeval()', function() eq(1, eval('type(luaeval("\'test\'"))')) eq('', funcs.luaeval('""')) - eq({_TYPE={}, _VAL={'\n'}}, funcs.luaeval([['\0']])) - eq({_TYPE={}, _VAL={'\n', '\n'}}, funcs.luaeval([['\0\n\0']])) - eq(1, eval([[luaeval('"\0\n\0"')._TYPE is v:msgpack_types.binary]])) + eq('\000', funcs.luaeval([['\0']])) + eq('\000\n\000', funcs.luaeval([['\0\n\0']])) + eq(10, eval([[type(luaeval("'\\0\\n\\0'"))]])) eq(true, funcs.luaeval('true')) eq(false, funcs.luaeval('false')) @@ -122,12 +118,11 @@ describe('luaeval()', function() local level = 30 eq(nested_by_level[level].o, funcs.luaeval(nested_by_level[level].s)) - eq({_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, {_TYPE={}, _VAL={'\n', '\n\n'}}}}}, + eq({_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, '\000\n\000\000'}}}, funcs.luaeval([[{['\0\n\0']='\0\n\0\0'}]])) eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._TYPE is v:msgpack_types.map]])) eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._VAL[0][0]._TYPE is v:msgpack_types.string]])) - eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._VAL[0][1]._TYPE is v:msgpack_types.binary]])) - eq({nested={{_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, {_TYPE={}, _VAL={'\n', '\n\n'}}}}}}}, + eq({nested={{_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, '\000\n\000\000'}}}}}, funcs.luaeval([[{nested={{['\0\n\0']='\0\n\0\0'}}}]])) end) @@ -175,8 +170,8 @@ describe('luaeval()', function() end it('correctly passes special dictionaries', function() - eq({'binary', {'\n', '\n'}}, luaevalarg(sp('binary', '["\\n", "\\n"]'))) - eq({'binary', {'\n', '\n'}}, luaevalarg(sp('string', '["\\n", "\\n"]'))) + eq({0, '\000\n\000'}, luaevalarg(sp('binary', '["\\n", "\\n"]'))) + eq({0, '\000\n\000'}, luaevalarg(sp('string', '["\\n", "\\n"]'))) eq({0, true}, luaevalarg(sp('boolean', 1))) eq({0, false}, luaevalarg(sp('boolean', 0))) eq({0, NIL}, luaevalarg(sp('nil', 0))) @@ -458,6 +453,9 @@ describe('v:lua', function() function mymod.crashy() nonexistent() end + function mymod.whatis(value) + return type(value) .. ": " .. tostring(value) + end function mymod.omni(findstart, base) if findstart == 1 then return 5 @@ -476,11 +474,28 @@ describe('v:lua', function() eq(true, exec_lua([[return _G.val == vim.NIL]])) eq(NIL, eval('v:lua.mymod.noisy("eval")')) eq("hey eval", meths.get_current_line()) + eq("string: abc", eval('v:lua.mymod.whatis(0z616263)')) + eq("string: ", eval('v:lua.mymod.whatis(v:_null_blob)')) eq("Vim:E5108: Error executing lua [string \"<nvim>\"]:0: attempt to call global 'nonexistent' (a nil value)", pcall_err(eval, 'v:lua.mymod.crashy()')) end) + it('works when called as a method', function() + eq(123, eval('110->v:lua.foo(13)')) + eq(true, exec_lua([[return _G.val == nil]])) + + eq(321, eval('300->v:lua.foo(21, "boop")')) + eq("boop", exec_lua([[return _G.val]])) + + eq(NIL, eval('"there"->v:lua.mymod.noisy()')) + eq("hey there", meths.get_current_line()) + eq({5, 10, 15, 20}, eval('[[1], [2, 3], [4]]->v:lua.vim.tbl_flatten()->map({_, v -> v * 5})')) + + eq("Vim:E5108: Error executing lua [string \"<nvim>\"]:0: attempt to call global 'nonexistent' (a nil value)", + pcall_err(eval, '"huh?"->v:lua.mymod.crashy()')) + end) + it('works in :call', function() command(":call v:lua.mymod.noisy('command')") eq("hey command", meths.get_current_line()) @@ -518,8 +533,15 @@ describe('v:lua', function() eq("Vim:E15: Invalid expression: v:['lua'].foo()", pcall_err(eval, "v:['lua'].foo()")) eq("Vim(call):E461: Illegal variable name: v:['lua']", pcall_err(command, "call v:['lua'].baar()")) + eq("Vim:E117: Unknown function: v:lua", pcall_err(eval, "v:lua()")) eq("Vim(let):E46: Cannot change read-only variable \"v:['lua']\"", pcall_err(command, "let v:['lua'] = 'xx'")) eq("Vim(let):E46: Cannot change read-only variable \"v:lua\"", pcall_err(command, "let v:lua = 'xx'")) + + eq("Vim:E107: Missing parentheses: v:lua.func", pcall_err(eval, "'bad'->v:lua.func")) + eq("Vim:E274: No white space allowed before parenthesis", pcall_err(eval, "'bad'->v:lua.func ()")) + eq("Vim:E107: Missing parentheses: v:lua", pcall_err(eval, "'bad'->v:lua")) + eq("Vim:E117: Unknown function: v:lua", pcall_err(eval, "'bad'->v:lua()")) + eq("Vim:E15: Invalid expression: v:lua.()", pcall_err(eval, "'bad'->v:lua.()")) end) end) diff --git a/test/functional/lua/mpack_spec.lua b/test/functional/lua/mpack_spec.lua new file mode 100644 index 0000000000..ef693f01f3 --- /dev/null +++ b/test/functional/lua/mpack_spec.lua @@ -0,0 +1,23 @@ +-- Test suite for testing interactions with API bindings +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local exec_lua = helpers.exec_lua + +describe('lua vim.mpack', function() + before_each(clear) + it('can pack vim.NIL', function() + eq({true, true, true, true}, exec_lua [[ + local var = vim.mpack.unpack(vim.mpack.pack({33, vim.NIL, 77})) + return {var[1]==33, var[2]==vim.NIL, var[3]==77, var[4]==nil} + ]]) + end) + + it('can pack vim.empty_dict()', function() + eq({{{}, "foo", {}}, true, false}, exec_lua [[ + local var = vim.mpack.unpack(vim.mpack.pack({{}, "foo", vim.empty_dict()})) + return {var, vim.tbl_islist(var[1]), vim.tbl_islist(var[3])} + ]]) + end) +end) diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index 267c759faf..636479b81f 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -11,8 +11,9 @@ local meths = helpers.meths local iswin = helpers.iswin local command = helpers.command local write_file = helpers.write_file -local redir_exec = helpers.redir_exec +local exec_capture = helpers.exec_capture local exec_lua = helpers.exec_lua +local pcall_err = helpers.pcall_err local screen @@ -36,11 +37,11 @@ describe('print', function() write_file(fname, 'print("abc")') eq('\nabc', funcs.execute('luafile ' .. fname)) - eq('\nabc', redir_exec('lua print("abc")')) - eq('\nabc', redir_exec('luado print("abc")')) - eq('\nabc', redir_exec('call luaeval("print(\'abc\')")')) + eq('abc', exec_capture('lua print("abc")')) + eq('abc', exec_capture('luado print("abc")')) + eq('abc', exec_capture('call luaeval("print(\'abc\')")')) write_file(fname, 'print("abc")') - eq('\nabc', redir_exec('luafile ' .. fname)) + eq('abc', exec_capture('luafile ' .. fname)) end) it('handles errors in __tostring', function() write_file(fname, [[ @@ -51,30 +52,30 @@ describe('print', function() v_abcerr = setmetatable({}, meta_abcerr) v_tblout = setmetatable({}, meta_tblout) ]]) - eq('', redir_exec('luafile ' .. fname)) + eq('', exec_capture('luafile ' .. fname)) -- TODO(bfredl): these look weird, print() should not use "E5114:" style errors.. - eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: [NULL]', - redir_exec('lua print("foo", v_nilerr, "bar")')) - eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc', - redir_exec('lua print("foo", v_abcerr, "bar")')) - eq('\nE5108: Error executing lua E5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>', - redir_exec('lua print("foo", v_tblout, "bar")')) + eq('Vim(lua):E5108: Error executing lua E5114: Error while converting print argument #2: [NULL]', + pcall_err(command, 'lua print("foo", v_nilerr, "bar")')) + eq('Vim(lua):E5108: Error executing lua E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:0: abc', + pcall_err(command, 'lua print("foo", v_abcerr, "bar")')) + eq('Vim(lua):E5108: Error executing lua E5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>', + pcall_err(command, 'lua print("foo", v_tblout, "bar")')) end) it('prints strings with NULs and NLs correctly', function() meths.set_option('more', true) - eq('\nabc ^@ def\nghi^@^@^@jkl\nTEST\n\n\nT\n', - redir_exec([[lua print("abc \0 def\nghi\0\0\0jkl\nTEST\n\n\nT\n")]])) - eq('\nabc ^@ def\nghi^@^@^@jkl\nTEST\n\n\nT^@', - redir_exec([[lua print("abc \0 def\nghi\0\0\0jkl\nTEST\n\n\nT\0")]])) - eq('\nT^@', redir_exec([[lua print("T\0")]])) - eq('\nT\n', redir_exec([[lua print("T\n")]])) + eq('abc ^@ def\nghi^@^@^@jkl\nTEST\n\n\nT\n', + exec_capture([[lua print("abc \0 def\nghi\0\0\0jkl\nTEST\n\n\nT\n")]])) + eq('abc ^@ def\nghi^@^@^@jkl\nTEST\n\n\nT^@', + exec_capture([[lua print("abc \0 def\nghi\0\0\0jkl\nTEST\n\n\nT\0")]])) + eq('T^@', exec_capture([[lua print("T\0")]])) + eq('T\n', exec_capture([[lua print("T\n")]])) end) it('prints empty strings correctly', function() -- Regression: first test used to crash - eq('', redir_exec('lua print("")')) - eq('\n def', redir_exec('lua print("", "def")')) - eq('\nabc ', redir_exec('lua print("abc", "")')) - eq('\nabc def', redir_exec('lua print("abc", "", "def")')) + eq('', exec_capture('lua print("")')) + eq(' def', exec_capture('lua print("", "def")')) + eq('abc ', exec_capture('lua print("abc", "")')) + eq('abc def', exec_capture('lua print("abc", "", "def")')) end) it('defers printing in luv event handlers', function() exec_lua([[ @@ -97,7 +98,7 @@ describe('print', function() vim.api.nvim_command("sleep 1m") -- force deferred event processing end ]], (iswin() and "timeout 1") or "sleep 0.1") - eq('\nvery slow\nvery fast',redir_exec('lua test()')) + eq('very slow\nvery fast', exec_capture('lua test()')) end) end) diff --git a/test/functional/lua/ui_spec.lua b/test/functional/lua/ui_spec.lua new file mode 100644 index 0000000000..94f1b5840b --- /dev/null +++ b/test/functional/lua/ui_spec.lua @@ -0,0 +1,46 @@ +local helpers = require('test.functional.helpers')(after_each) +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local clear = helpers.clear + +describe('vim.ui', function() + before_each(function() + clear() + end) + + + describe('select', function() + it('can select an item', function() + local result = exec_lua[[ + local items = { + { name = 'Item 1' }, + { name = 'Item 2' }, + } + local opts = { + format_item = function(entry) + return entry.name + end + } + local selected + local cb = function(item) + selected = item + end + -- inputlist would require input and block the test; + local choices + vim.fn.inputlist = function(x) + choices = x + return 1 + end + vim.ui.select(items, opts, cb) + vim.wait(100, function() return selected ~= nil end) + return {selected, choices} + ]] + eq({ name = 'Item 1' }, result[1]) + eq({ + 'Select one of:', + '1: Item 1', + '2: Item 2', + }, result[2]) + end) + end) +end) diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua index f782769935..052a8a1ecd 100644 --- a/test/functional/lua/uri_spec.lua +++ b/test/functional/lua/uri_spec.lua @@ -53,12 +53,18 @@ describe('URI methods', function() describe('uri to filepath', function() describe('decode Unix file path', function() - it('file path includes only ascii charactors', function() + it('file path includes only ascii characters', function() exec_lua("uri = 'file:///Foo/Bar/Baz.txt'") eq('/Foo/Bar/Baz.txt', exec_lua("return vim.uri_to_fname(uri)")) end) + it('local file path without hostname', function() + exec_lua("uri = 'file:/Foo/Bar/Baz.txt'") + + eq('/Foo/Bar/Baz.txt', exec_lua("return vim.uri_to_fname(uri)")) + end) + it('file path including white space', function() exec_lua("uri = 'file:///Foo%20/Bar/Baz.txt'") @@ -85,6 +91,15 @@ describe('URI methods', function() eq('C:\\Foo\\Bar\\Baz.txt', exec_lua(test_case)) end) + it('local file path without hostname', function() + local test_case = [[ + local uri = 'file:/C:/Foo/Bar/Baz.txt' + return vim.uri_to_fname(uri) + ]] + + eq('C:\\Foo\\Bar\\Baz.txt', exec_lua(test_case)) + end) + it('file path includes only ascii charactors with encoded colon character', function() local test_case = [[ local uri = 'file:///C%3A/Foo/Bar/Baz.txt' @@ -125,6 +140,12 @@ describe('URI methods', function() return vim.uri_to_fname('JDT://content/%5C/') ]]) end) + + it('uri_to_fname returns non-file scheme URI without authority unchanged', function() + eq('zipfile:/path/to/archive.zip%3A%3Afilename.txt', exec_lua [[ + return vim.uri_to_fname('zipfile:/path/to/archive.zip%3A%3Afilename.txt') + ]]) + end) end) describe('decode URI without scheme', function() @@ -146,5 +167,14 @@ describe('URI methods', function() ]], uri) eq(uri, exec_lua(test_case)) end) + + it('uri_to_bufnr & uri_from_bufnr returns original uri for non-file uris without authority', function() + local uri = 'zipfile:/path/to/archive.zip%3A%3Afilename.txt' + local test_case = string.format([[ + local uri = '%s' + return vim.uri_from_bufnr(vim.uri_to_bufnr(uri)) + ]], uri) + eq(uri, exec_lua(test_case)) + end) end) end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index eff838aea3..a066cfbc10 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -6,6 +6,7 @@ local funcs = helpers.funcs local meths = helpers.meths local dedent = helpers.dedent local command = helpers.command +local insert = helpers.insert local clear = helpers.clear local eq = helpers.eq local ok = helpers.ok @@ -17,6 +18,7 @@ local matches = helpers.matches local source = helpers.source local NIL = helpers.NIL local retry = helpers.retry +local next_msg = helpers.next_msg before_each(clear) @@ -603,6 +605,31 @@ describe('lua stdlib', function() return vim.tbl_islist(c) and count == 0 ]])) + eq(exec_lua([[ + local a = { a = { b = 1 } } + local b = { a = {} } + return vim.tbl_deep_extend("force", a, b) + ]]), {a = {b = 1}}) + + eq(exec_lua([[ + local a = { a = 123 } + local b = { a = { b = 1} } + return vim.tbl_deep_extend("force", a, b) + ]]), {a = {b = 1}}) + + ok(exec_lua([[ + local a = { a = {[2] = 3} } + local b = { a = {[3] = 3} } + local c = vim.tbl_deep_extend("force", a, b) + return vim.deep_equal(c, {a = {[3] = 3}}) + ]])) + + eq(exec_lua([[ + local a = { a = { b = 1} } + local b = { a = 123 } + return vim.tbl_deep_extend("force", a, b) + ]]), {a = 123 }) + eq('Error executing lua: vim/shared.lua:0: invalid "behavior": nil', pcall_err(exec_lua, [[ return vim.tbl_deep_extend() @@ -712,7 +739,7 @@ describe('lua stdlib', function() eq({NIL, NIL}, exec_lua([[return vim.fn.Nilly()]])) -- error handling - eq({false, 'Vim:E714: List required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]])) + eq({false, 'Vim:E897: List or Blob required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]])) end) it('vim.fn should error when calling API function', function() @@ -1332,12 +1359,12 @@ describe('lua stdlib', function() it('should work for key-value pair options', function() local listchars = exec_lua [[ - vim.opt.listchars = "tab:>~,space:_" + vim.opt.listchars = "tab:> ,space:_" return vim.opt.listchars:get() ]] eq({ - tab = ">~", + tab = "> ", space = "_", }, listchars) end) @@ -1855,7 +1882,7 @@ describe('lua stdlib', function() end) it('vim.region', function() - helpers.insert(helpers.dedent( [[ + insert(helpers.dedent( [[ text tααt tααt text text tαxt txtα tex text tαxt tαxt @@ -1863,65 +1890,67 @@ describe('lua stdlib', function() eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]]) end) - describe('vim.execute_on_keystroke', function() - it('should keep track of keystrokes', function() - helpers.insert([[hello world ]]) + describe('vim.on_key', function() + it('tracks keystrokes', function() + insert([[hello world ]]) exec_lua [[ - KeysPressed = {} + keys = {} - vim.register_keystroke_callback(function(buf) + vim.on_key(function(buf) if buf:byte() == 27 then buf = "<ESC>" end - table.insert(KeysPressed, buf) + table.insert(keys, buf) end) ]] - helpers.insert([[next 🤦 lines å ]]) + insert([[next 🤦 lines å ]]) -- It has escape in the keys pressed - eq('inext 🤦 lines å <ESC>', exec_lua [[return table.concat(KeysPressed, '')]]) + eq('inext 🤦 lines å <ESC>', exec_lua [[return table.concat(keys, '')]]) end) - it('should allow removing trackers.', function() - helpers.insert([[hello world]]) + it('allows removing on_key listeners', function() + insert([[hello world]]) exec_lua [[ - KeysPressed = {} + keys = {} - return vim.register_keystroke_callback(function(buf) + return vim.on_key(function(buf) if buf:byte() == 27 then buf = "<ESC>" end - table.insert(KeysPressed, buf) + table.insert(keys, buf) end, vim.api.nvim_create_namespace("logger")) ]] - helpers.insert([[next lines]]) + insert([[next lines]]) - exec_lua("vim.register_keystroke_callback(nil, vim.api.nvim_create_namespace('logger'))") + eq(1, exec_lua('return vim.on_key()')) + exec_lua("vim.on_key(nil, vim.api.nvim_create_namespace('logger'))") + eq(0, exec_lua('return vim.on_key()')) - helpers.insert([[more lines]]) + insert([[more lines]]) -- It has escape in the keys pressed - eq('inext lines<ESC>', exec_lua [[return table.concat(KeysPressed, '')]]) + eq('inext lines<ESC>', exec_lua [[return table.concat(keys, '')]]) end) - it('should not call functions that error again.', function() - helpers.insert([[hello world]]) + it('skips any function that caused an error', function() + insert([[hello world]]) exec_lua [[ - KeysPressed = {} + keys = {} - return vim.register_keystroke_callback(function(buf) + return vim.on_key(function(buf) if buf:byte() == 27 then buf = "<ESC>" end - table.insert(KeysPressed, buf) + table.insert(keys, buf) if buf == 'l' then error("Dumb Error") @@ -1929,35 +1958,30 @@ describe('lua stdlib', function() end) ]] - helpers.insert([[next lines]]) - helpers.insert([[more lines]]) + insert([[next lines]]) + insert([[more lines]]) -- Only the first letter gets added. After that we remove the callback - eq('inext l', exec_lua [[ return table.concat(KeysPressed, '') ]]) + eq('inext l', exec_lua [[ return table.concat(keys, '') ]]) end) - it('should process mapped keys, not unmapped keys', function() + it('processes mapped keys, not unmapped keys', function() exec_lua [[ - KeysPressed = {} + keys = {} vim.cmd("inoremap hello world") - vim.register_keystroke_callback(function(buf) + vim.on_key(function(buf) if buf:byte() == 27 then buf = "<ESC>" end - table.insert(KeysPressed, buf) + table.insert(keys, buf) end) ]] + insert("hello") - helpers.insert("hello") - - local next_status = exec_lua [[ - return table.concat(KeysPressed, '') - ]] - - eq("iworld<ESC>", next_status) + eq('iworld<ESC>', exec_lua[[return table.concat(keys, '')]]) end) end) @@ -2153,6 +2177,24 @@ describe('lua stdlib', function() end) end) + describe('vim.schedule_wrap', function() + it('preserves argument lists', function() + exec_lua [[ + local fun = vim.schedule_wrap(function(kling, klang, klonk) + vim.rpcnotify(1, 'mayday_mayday', {a=kling, b=klang, c=klonk}) + end) + fun("BOB", nil, "MIKE") + ]] + eq({'notification', 'mayday_mayday', {{a='BOB', c='MIKE'}}}, next_msg()) + + -- let's gooooo + exec_lua [[ + vim.schedule_wrap(function(...) vim.rpcnotify(1, 'boogalo', select('#', ...)) end)(nil,nil,nil,nil) + ]] + eq({'notification', 'boogalo', {4}}, next_msg()) + end) + end) + describe('vim.api.nvim_buf_call', function() it('can access buf options', function() local buf1 = meths.get_current_buf() diff --git a/test/functional/lua/xdiff_spec.lua b/test/functional/lua/xdiff_spec.lua new file mode 100644 index 0000000000..4f28f84c01 --- /dev/null +++ b/test/functional/lua/xdiff_spec.lua @@ -0,0 +1,112 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local eq = helpers.eq +local pcall_err = helpers.pcall_err + +describe('xdiff bindings', function() + before_each(function() + clear() + end) + + describe('can diff text', function() + before_each(function() + exec_lua[[ + a1 = 'Hello\n' + b1 = 'Helli\n' + + a2 = 'Hello\nbye\nfoo\n' + b2 = 'Helli\nbye\nbar\nbaz\n' + ]] + end) + + it('with no callback', function() + + eq( + table.concat({ + '@@ -1 +1 @@', + '-Hello', + '+Helli', + '' + }, '\n'), + exec_lua("return vim.diff(a1, b1)") + ) + + eq( + table.concat({ + '@@ -1 +1 @@', + '-Hello', + '+Helli', + '@@ -3 +3,2 @@', + '-foo', + '+bar', + '+baz', + '' + }, '\n'), + exec_lua("return vim.diff(a2, b2)") + ) + + end) + + it('with callback', function() + exec_lua([[on_hunk = function(sa, ca, sb, cb) + exp[#exp+1] = {sa, ca, sb, cb} + end]]) + + eq({{1, 1, 1, 1}}, exec_lua[[ + exp = {} + assert(vim.diff(a1, b1, {on_hunk = on_hunk}) == nil) + return exp + ]]) + + eq({{1, 1, 1, 1}, {3, 1, 3, 2}}, exec_lua[[ + exp = {} + assert(vim.diff(a2, b2, {on_hunk = on_hunk}) == nil) + return exp + ]]) + + -- gives higher precedence to on_hunk over result_type + eq({{1, 1, 1, 1}, {3, 1, 3, 2}}, exec_lua[[ + exp = {} + assert(vim.diff(a2, b2, {on_hunk = on_hunk, result_type='indices'}) == nil) + return exp + ]]) + end) + + it('with error callback', function() + exec_lua([[on_hunk = function(sa, ca, sb, cb) + error('ERROR1') + end]]) + + eq([[Error executing lua: [string "<nvim>"]:0: error running function on_hunk: [string "<nvim>"]:0: ERROR1]], + pcall_err(exec_lua, [[vim.diff(a1, b1, {on_hunk = on_hunk})]])) + end) + + it('with hunk_lines', function() + eq({{1, 1, 1, 1}}, + exec_lua([[return vim.diff(a1, b1, {result_type = 'indices'})]])) + + eq({{1, 1, 1, 1}, {3, 1, 3, 2}}, + exec_lua([[return vim.diff(a2, b2, {result_type = 'indices'})]])) + end) + + end) + + it('can handle bad args', function() + eq([[Error executing lua: [string "<nvim>"]:0: Expected at least 2 arguments]], + pcall_err(exec_lua, [[vim.diff('a')]])) + + eq([[Error executing lua: [string "<nvim>"]:0: bad argument #1 to 'diff' (expected string)]], + pcall_err(exec_lua, [[vim.diff(1, 2)]])) + + eq([[Error executing lua: [string "<nvim>"]:0: bad argument #3 to 'diff' (expected table)]], + pcall_err(exec_lua, [[vim.diff('a', 'b', true)]])) + + eq([[Error executing lua: [string "<nvim>"]:0: unexpected key: bad_key]], + pcall_err(exec_lua, [[vim.diff('a', 'b', { bad_key = true })]])) + + eq([[Error executing lua: [string "<nvim>"]:0: on_hunk is not a function]], + pcall_err(exec_lua, [[vim.diff('a', 'b', { on_hunk = true })]])) + + end) +end) |