-- Test suite for testing interactions with API bindings local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local funcs = helpers.funcs local clear = helpers.clear local eq = helpers.eq local eval = helpers.eval local feed = helpers.feed local pcall_err = helpers.pcall_err local exec_lua = helpers.exec_lua local matches = helpers.matches local source = helpers.source before_each(clear) describe('lua stdlib', function() -- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has -- length 2 (in bytes). -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has -- length 3 (in bytes). -- -- Note: 'i' !=? 'İ' and 'ⱥ' !=? 'Ⱥ' on some systems. -- Note: Built-in Nvim comparison (on systems lacking `strcasecmp`) works -- only on ASCII characters. it('vim.stricmp', function() eq(0, funcs.luaeval('vim.stricmp("a", "A")')) eq(0, funcs.luaeval('vim.stricmp("A", "a")')) eq(0, funcs.luaeval('vim.stricmp("a", "a")')) eq(0, funcs.luaeval('vim.stricmp("A", "A")')) eq(0, funcs.luaeval('vim.stricmp("", "")')) eq(0, funcs.luaeval('vim.stricmp("\\0", "\\0")')) eq(0, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0")')) eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0\\0")')) eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0a")')) eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0A")')) eq(0, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0a")')) eq(0, funcs.luaeval('vim.stricmp("a\\0", "A\\0")')) eq(0, funcs.luaeval('vim.stricmp("A\\0", "a\\0")')) eq(0, funcs.luaeval('vim.stricmp("a\\0", "a\\0")')) eq(0, funcs.luaeval('vim.stricmp("A\\0", "A\\0")')) eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0A")')) eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0a")')) eq(0, funcs.luaeval('vim.stricmp("\\0a", "\\0a")')) eq(0, funcs.luaeval('vim.stricmp("\\0A", "\\0A")')) eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0A\\0")')) eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0a\\0")')) eq(0, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0a\\0")')) eq(0, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0A\\0")')) eq(-1, funcs.luaeval('vim.stricmp("a", "B")')) eq(-1, funcs.luaeval('vim.stricmp("A", "b")')) eq(-1, funcs.luaeval('vim.stricmp("a", "b")')) eq(-1, funcs.luaeval('vim.stricmp("A", "B")')) eq(-1, funcs.luaeval('vim.stricmp("", "\\0")')) eq(-1, funcs.luaeval('vim.stricmp("\\0", "\\0\\0")')) eq(-1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0\\0\\0")')) eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0A", "\\0\\0\\0b")')) eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0B")')) eq(-1, funcs.luaeval('vim.stricmp("\\0\\0\\0a", "\\0\\0\\0b")')) eq(-1, funcs.luaeval('vim.stricmp("a\\0", "B\\0")')) eq(-1, funcs.luaeval('vim.stricmp("A\\0", "b\\0")')) eq(-1, funcs.luaeval('vim.stricmp("a\\0", "b\\0")')) eq(-1, funcs.luaeval('vim.stricmp("A\\0", "B\\0")')) eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0B")')) eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0b")')) eq(-1, funcs.luaeval('vim.stricmp("\\0a", "\\0b")')) eq(-1, funcs.luaeval('vim.stricmp("\\0A", "\\0B")')) eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0B\\0")')) eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0b\\0")')) eq(-1, funcs.luaeval('vim.stricmp("\\0a\\0", "\\0b\\0")')) eq(-1, funcs.luaeval('vim.stricmp("\\0A\\0", "\\0B\\0")')) eq(1, funcs.luaeval('vim.stricmp("c", "B")')) eq(1, funcs.luaeval('vim.stricmp("C", "b")')) eq(1, funcs.luaeval('vim.stricmp("c", "b")')) eq(1, funcs.luaeval('vim.stricmp("C", "B")')) eq(1, funcs.luaeval('vim.stricmp("\\0", "")')) eq(1, funcs.luaeval('vim.stricmp("\\0\\0", "\\0")')) eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0", "\\0\\0")')) eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0\\0", "\\0\\0\\0")')) eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0C", "\\0\\0\\0b")')) eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0B")')) eq(1, funcs.luaeval('vim.stricmp("\\0\\0\\0c", "\\0\\0\\0b")')) eq(1, funcs.luaeval('vim.stricmp("c\\0", "B\\0")')) eq(1, funcs.luaeval('vim.stricmp("C\\0", "b\\0")')) eq(1, funcs.luaeval('vim.stricmp("c\\0", "b\\0")')) eq(1, funcs.luaeval('vim.stricmp("C\\0", "B\\0")')) eq(1, funcs.luaeval('vim.stricmp("c\\0", "B")')) eq(1, funcs.luaeval('vim.stricmp("C\\0", "b")')) eq(1, funcs.luaeval('vim.stricmp("c\\0", "b")')) eq(1, funcs.luaeval('vim.stricmp("C\\0", "B")')) eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0B")')) eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0b")')) eq(1, funcs.luaeval('vim.stricmp("\\0c", "\\0b")')) eq(1, funcs.luaeval('vim.stricmp("\\0C", "\\0B")')) eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0B\\0")')) eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0b\\0")')) eq(1, funcs.luaeval('vim.stricmp("\\0c\\0", "\\0b\\0")')) eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")')) end) it("vim.str_utfindex/str_byteindex", function() exec_lua([[_G.test_text = "xy åäö ɧ 汉语 ↥ 🤦x🦄 å بِيَّ"]]) local indicies32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48} local indicies16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48} for i,k in pairs(indicies32) do eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ...)", i), i) end for i,k in pairs(indicies16) do eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ..., true)", i), i) end local i32, i16 = 0, 0 for k = 0,48 do if indicies32[i32] < k then i32 = i32 + 1 end if indicies16[i16] < k then i16 = i16 + 1 if indicies16[i16+1] == indicies16[i16] then i16 = i16 + 1 end end eq({i32, i16}, exec_lua("return {vim.str_utfindex(_G.test_text, ...)}", k), k) end end) it("vim.schedule", function() exec_lua([[ test_table = {} vim.schedule(function() table.insert(test_table, "xx") end) table.insert(test_table, "yy") ]]) eq({"yy","xx"}, exec_lua("return test_table")) -- Validates args. eq('Error executing lua: vim.schedule: expected function', pcall_err(exec_lua, "vim.schedule('stringly')")) eq('Error executing lua: vim.schedule: expected function', pcall_err(exec_lua, "vim.schedule()")) exec_lua([[ vim.schedule(function() error("big failure\nvery async") end) ]]) feed("") eq('Error executing vim.schedule lua callback: [string ""]:2: big failure\nvery async', eval("v:errmsg")) local screen = Screen.new(60,5) screen:set_default_attr_ids({ [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {bold = true, reverse = true}, [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, [4] = {bold = true, foreground = Screen.colors.SeaGreen4}, }) screen:attach() screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | ]]} -- nvim_command causes a vimL exception, check that it is properly caught -- and propagated as an error message in async contexts.. #10809 exec_lua([[ vim.schedule(function() vim.api.nvim_command(":echo 'err") end) ]]) screen:expect{grid=[[ | {2: }| {3:Error executing vim.schedule lua callback: [string ""]}| {3::2: Vim(echo):E115: Missing quote: 'err} | {4:Press ENTER or type command to continue}^ | ]]} end) it("vim.split", function() local split = function(str, sep, plain) return exec_lua('return vim.split(...)', str, sep, plain) end local tests = { { "a,b", ",", false, { 'a', 'b' } }, { ":aa::bb:", ":", false, { '', 'aa', '', 'bb', '' } }, { "::ee::ff:", ":", false, { '', '', 'ee', '', 'ff', '' } }, { "ab", ".", false, { '', '', '' } }, { "a1b2c", "[0-9]", false, { 'a', 'b', 'c' } }, { "xy", "", false, { 'x', 'y' } }, { "here be dragons", " ", false, { "here", "be", "dragons"} }, { "axaby", "ab?", false, { '', 'x', 'y' } }, { "f v2v v3v w2w ", "([vw])2%1", false, { 'f ', ' v3v ', ' ' } }, { "x*yz*oo*l", "*", true, { 'x', 'yz', 'oo', 'l' } }, } for _, t in ipairs(tests) do eq(t[4], split(t[1], t[2], t[3])) end local loops = { { "abc", ".-" }, } for _, t in ipairs(loops) do matches(".*Infinite loop detected", pcall_err(split, t[1], t[2])) end -- Validates args. eq(true, pcall(split, 'string', 'string', nil)) eq('Error executing lua: .../shared.lua: Expected string, got number', pcall_err(split, 1, 'string', nil)) eq('Error executing lua: .../shared.lua: Expected string, got number', pcall_err(split, 'string', 1, nil)) eq('Error executing lua: .../shared.lua: Expected boolean or nil, got number', pcall_err(split, 'string', 'string', 1)) end) it('vim.trim', function() local trim = function(s) return exec_lua('return vim.trim(...)', s) end local trims = { { " a", "a" }, { " b ", "b" }, { "\tc" , "c" }, { "r\n", "r" }, } for _, t in ipairs(trims) do assert(t[2], trim(t[1])) end -- Validates args. eq('Error executing lua: .../shared.lua: Expected string, got number', pcall_err(trim, 2)) end) it('vim.inspect', function() -- just make sure it basically works, it has its own test suite local inspect = function(t, opts) return exec_lua('return vim.inspect(...)', t, opts) end eq('2', inspect(2)) eq('{+a = {+b = 1+}+}', inspect({ a = { b = 1 } }, { newline = '+', indent = '' })) -- special value vim.inspect.KEY works eq('{ KEY_a = "x", KEY_b = "y"}', exec_lua([[ return vim.inspect({a="x", b="y"}, {newline = '', process = function(item, path) if path[#path] == vim.inspect.KEY then return 'KEY_'..item end return item end}) ]])) end) it("vim.deepcopy", function() local is_dc = exec_lua([[ local a = { x = { 1, 2 }, y = 5} local b = vim.deepcopy(a) local count = 0 for _ in pairs(b) do count = count + 1 end return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and count == 2 and tostring(a) ~= tostring(b) ]]) assert(is_dc) end) it('vim.pesc', function() eq('foo%-bar', exec_lua([[return vim.pesc('foo-bar')]])) eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]])) -- Validates args. eq("Error executing lua: .../shared.lua: Expected string, got number", pcall_err(exec_lua, [[return vim.pesc(2)]])) end) it('vim.call, vim.fn', function() eq(true, exec_lua([[return vim.call('sin', 0.0) == 0.0 ]])) eq(true, exec_lua([[return vim.fn.sin(0.0) == 0.0 ]])) -- compat: nvim_call_function uses "special" value for vimL float eq(false, exec_lua([[return vim.api.nvim_call_function('sin', {0.0}) == 0.0 ]])) source([[ func! FooFunc(test) let g:test = a:test return {} endfunc func! VarArg(...) return a:000 endfunc ]]) eq(true, exec_lua([[return next(vim.fn.FooFunc(3)) == nil ]])) eq(3, eval("g:test")) -- compat: nvim_call_function uses "special" value for empty dict eq(true, exec_lua([[return next(vim.api.nvim_call_function("FooFunc", {5})) == true ]])) eq(5, eval("g:test")) eq({2, "foo", true}, exec_lua([[return vim.fn.VarArg(2, "foo", true)]])) -- error handling eq({false, 'Vim:E714: List required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]])) end) end)