api_spec.lua (14361B)
1 -- Test suite for testing interactions with API bindings 2 local t = require('test.testutil') 3 local n = require('test.functional.testnvim')() 4 5 local exc_exec = n.exc_exec 6 local remove_trace = t.remove_trace 7 local fn = n.fn 8 local clear = n.clear 9 local eval = n.eval 10 local NIL = vim.NIL 11 local eq = t.eq 12 local exec_lua = n.exec_lua 13 local pcall_err = t.pcall_err 14 15 before_each(clear) 16 17 describe('luaeval(vim.api.…)', function() 18 describe('with channel_id and buffer handle', function() 19 describe('nvim_buf_get_lines', function() 20 it('works', function() 21 fn.setline(1, { 'abc', 'def', 'a\nb', 'ttt' }) 22 eq({ 'a\000b' }, fn.luaeval('vim.api.nvim_buf_get_lines(1, 2, 3, false)')) 23 end) 24 end) 25 describe('nvim_buf_set_lines', function() 26 it('works', function() 27 fn.setline(1, { 'abc', 'def', 'a\nb', 'ttt' }) 28 eq(NIL, fn.luaeval('vim.api.nvim_buf_set_lines(1, 1, 2, false, {"b\\0a"})')) 29 eq( 30 { 'abc', 'b\000a', 'a\000b', 'ttt' }, 31 fn.luaeval('vim.api.nvim_buf_get_lines(1, 0, 4, false)') 32 ) 33 end) 34 end) 35 end) 36 describe('with errors', function() 37 it('transforms API error from nvim_buf_set_lines into lua error', function() 38 fn.setline(1, { 'abc', 'def', 'a\nb', 'ttt' }) 39 eq( 40 { false, "'replacement string' item contains newlines" }, 41 fn.luaeval('{pcall(vim.api.nvim_buf_set_lines, 1, 1, 2, false, {"b\\na"})}') 42 ) 43 end) 44 45 it('transforms API error from nvim_win_set_cursor into lua error', function() 46 eq( 47 { false, 'Argument "pos" must be a [row, col] array' }, 48 fn.luaeval('{pcall(vim.api.nvim_win_set_cursor, 0, {1, 2, 3})}') 49 ) 50 -- Used to produce a memory leak due to a bug in nvim_win_set_cursor 51 eq( 52 { false, 'Invalid window id: -1' }, 53 fn.luaeval('{pcall(vim.api.nvim_win_set_cursor, -1, {1, 2, 3})}') 54 ) 55 end) 56 57 it( 58 'transforms API error from nvim_win_set_cursor + same array as in first test into lua error', 59 function() 60 eq( 61 { false, 'Argument "pos" must be a [row, col] array' }, 62 fn.luaeval('{pcall(vim.api.nvim_win_set_cursor, 0, {"b\\na"})}') 63 ) 64 end 65 ) 66 end) 67 68 it('correctly evaluates API code which calls luaeval', function() 69 local str = ( 70 ([===[vim.api.nvim_eval([==[ 71 luaeval('vim.api.nvim_eval([=[ 72 luaeval("vim.api.nvim_eval([[ 73 luaeval(1) 74 ]])") 75 ]=])') 76 ]==])]===]):gsub('\n', ' ') 77 ) 78 eq(1, fn.luaeval(str)) 79 end) 80 81 it('correctly converts from API objects', function() 82 eq(1, fn.luaeval('vim.api.nvim_eval("1")')) 83 eq('1', fn.luaeval([[vim.api.nvim_eval('"1"')]])) 84 eq('Blobby', fn.luaeval('vim.api.nvim_eval("0z426c6f626279")')) 85 eq({}, fn.luaeval('vim.api.nvim_eval("[]")')) 86 eq({}, fn.luaeval('vim.api.nvim_eval("{}")')) 87 eq(1, fn.luaeval('vim.api.nvim_eval("1.0")')) 88 eq('\000', fn.luaeval('vim.api.nvim_eval("0z00")')) 89 eq(true, fn.luaeval('vim.api.nvim_eval("v:true")')) 90 eq(false, fn.luaeval('vim.api.nvim_eval("v:false")')) 91 eq(NIL, fn.luaeval('vim.api.nvim_eval("v:null")')) 92 93 eq(0, eval([[type(luaeval('vim.api.nvim_eval("1")'))]])) 94 eq(1, eval([[type(luaeval('vim.api.nvim_eval("''1''")'))]])) 95 eq(1, eval([[type(luaeval('vim.api.nvim_eval("0zbeef")'))]])) 96 eq(3, eval([[type(luaeval('vim.api.nvim_eval("[]")'))]])) 97 eq(4, eval([[type(luaeval('vim.api.nvim_eval("{}")'))]])) 98 eq(5, eval([[type(luaeval('vim.api.nvim_eval("1.0")'))]])) 99 eq(6, eval([[type(luaeval('vim.api.nvim_eval("v:true")'))]])) 100 eq(6, eval([[type(luaeval('vim.api.nvim_eval("v:false")'))]])) 101 eq(7, eval([[type(luaeval('vim.api.nvim_eval("v:null")'))]])) 102 103 eq({ foo = 42 }, fn.luaeval([[vim.api.nvim_eval('{"foo": 42}')]])) 104 eq({ 42 }, fn.luaeval([[vim.api.nvim_eval('[42]')]])) 105 106 eq( 107 { foo = { bar = 42 }, baz = 50 }, 108 fn.luaeval([[vim.api.nvim_eval('{"foo": {"bar": 42}, "baz": 50}')]]) 109 ) 110 eq({ { 42 }, {} }, fn.luaeval([=[vim.api.nvim_eval('[[42], []]')]=])) 111 end) 112 113 it('correctly converts to API objects', function() 114 eq(1, fn.luaeval('vim.api.nvim__id(1)')) 115 eq('1', fn.luaeval('vim.api.nvim__id("1")')) 116 eq({ 1 }, fn.luaeval('vim.api.nvim__id({1})')) 117 eq({ foo = 1 }, fn.luaeval('vim.api.nvim__id({foo=1})')) 118 eq(1.5, fn.luaeval('vim.api.nvim__id(1.5)')) 119 eq(true, fn.luaeval('vim.api.nvim__id(true)')) 120 eq(false, fn.luaeval('vim.api.nvim__id(false)')) 121 eq(NIL, fn.luaeval('vim.api.nvim__id(nil)')) 122 123 -- API strings from Blobs can work as NUL-terminated C strings 124 eq( 125 'Vim(call):E5555: API call: Vim:E15: Invalid expression: ""', 126 exc_exec('call nvim_eval(v:_null_blob)') 127 ) 128 eq('Vim(call):E5555: API call: Vim:E15: Invalid expression: ""', exc_exec('call nvim_eval(0z)')) 129 eq(1, eval('nvim_eval(0z31)')) 130 131 eq(0, eval([[type(luaeval('vim.api.nvim__id(1)'))]])) 132 eq(1, eval([[type(luaeval('vim.api.nvim__id("1")'))]])) 133 eq(3, eval([[type(luaeval('vim.api.nvim__id({1})'))]])) 134 eq(4, eval([[type(luaeval('vim.api.nvim__id({foo=1})'))]])) 135 eq(5, eval([[type(luaeval('vim.api.nvim__id(1.5)'))]])) 136 eq(6, eval([[type(luaeval('vim.api.nvim__id(true)'))]])) 137 eq(6, eval([[type(luaeval('vim.api.nvim__id(false)'))]])) 138 eq(7, eval([[type(luaeval('vim.api.nvim__id(nil)'))]])) 139 140 eq( 141 { foo = 1, bar = { 42, { { baz = true }, 5 } } }, 142 fn.luaeval('vim.api.nvim__id({foo=1, bar={42, {{baz=true}, 5}}})') 143 ) 144 145 eq(true, fn.luaeval('vim.api.nvim__id(vim.api.nvim__id)(true)')) 146 eq( 147 42, 148 exec_lua(function() 149 local f = vim.api.nvim__id({ 42, vim.api.nvim__id }) 150 return f[2](f[1]) 151 end) 152 ) 153 end) 154 155 it('correctly converts container objects with type_idx to API objects', function() 156 eq( 157 5, 158 eval('type(luaeval("vim.api.nvim__id({[vim.type_idx]=vim.types.float, [vim.val_idx]=0})"))') 159 ) 160 eq(4, eval([[type(luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.dictionary})'))]])) 161 eq(3, eval([[type(luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.array})'))]])) 162 163 eq({}, fn.luaeval('vim.api.nvim__id({[vim.type_idx]=vim.types.array})')) 164 165 -- Presence of type_idx makes Vim ignore some keys 166 eq( 167 { 42 }, 168 fn.luaeval( 169 'vim.api.nvim__id({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})' 170 ) 171 ) 172 eq( 173 { foo = 2 }, 174 fn.luaeval( 175 'vim.api.nvim__id({[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})' 176 ) 177 ) 178 eq( 179 10, 180 fn.luaeval( 181 'vim.api.nvim__id({[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})' 182 ) 183 ) 184 eq( 185 {}, 186 fn.luaeval( 187 'vim.api.nvim__id({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2})' 188 ) 189 ) 190 end) 191 192 it('correctly converts arrays with type_idx to API objects', function() 193 eq(3, eval([[type(luaeval('vim.api.nvim__id_array({[vim.type_idx]=vim.types.array})'))]])) 194 195 eq({}, fn.luaeval('vim.api.nvim__id_array({[vim.type_idx]=vim.types.array})')) 196 197 eq( 198 { 42 }, 199 fn.luaeval( 200 'vim.api.nvim__id_array({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})' 201 ) 202 ) 203 eq( 204 { { foo = 2 } }, 205 fn.luaeval( 206 'vim.api.nvim__id_array({{[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})' 207 ) 208 ) 209 eq( 210 { 10 }, 211 fn.luaeval( 212 'vim.api.nvim__id_array({{[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})' 213 ) 214 ) 215 eq( 216 {}, 217 fn.luaeval( 218 'vim.api.nvim__id_array({[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2})' 219 ) 220 ) 221 222 eq({}, fn.luaeval('vim.api.nvim__id_array({})')) 223 eq(3, eval([[type(luaeval('vim.api.nvim__id_array({})'))]])) 224 end) 225 226 it('correctly converts dictionaries with type_idx to API objects', function() 227 eq(4, eval([[type(luaeval('vim.api.nvim__id_dict({[vim.type_idx]=vim.types.dictionary})'))]])) 228 229 eq({}, fn.luaeval('vim.api.nvim__id_dict({[vim.type_idx]=vim.types.dictionary})')) 230 231 eq( 232 { v = { 42 } }, 233 fn.luaeval( 234 'vim.api.nvim__id_dict({v={[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})' 235 ) 236 ) 237 eq( 238 { foo = 2 }, 239 fn.luaeval( 240 'vim.api.nvim__id_dict({[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})' 241 ) 242 ) 243 eq( 244 { v = 10 }, 245 fn.luaeval( 246 'vim.api.nvim__id_dict({v={[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})' 247 ) 248 ) 249 eq( 250 { v = {} }, 251 fn.luaeval( 252 'vim.api.nvim__id_dict({v={[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2}})' 253 ) 254 ) 255 256 -- If API requests dict, then empty table will be the one. This is not 257 -- the case normally because empty table is an empty array. 258 eq({}, fn.luaeval('vim.api.nvim__id_dict({})')) 259 eq(4, eval([[type(luaeval('vim.api.nvim__id_dict({})'))]])) 260 end) 261 262 it('converts booleans in positional args', function() 263 eq({ '' }, exec_lua [[ return vim.api.nvim_buf_get_lines(0, 0, 10, false) ]]) 264 eq({ '' }, exec_lua [[ return vim.api.nvim_buf_get_lines(0, 0, 10, nil) ]]) 265 eq( 266 'Index out of bounds', 267 pcall_err(exec_lua, [[ return vim.api.nvim_buf_get_lines(0, 0, 10, true) ]]) 268 ) 269 eq( 270 'Index out of bounds', 271 pcall_err(exec_lua, [[ return vim.api.nvim_buf_get_lines(0, 0, 10, 1) ]]) 272 ) 273 274 -- this follows lua conventions for bools (not api convention for Boolean) 275 eq( 276 'Index out of bounds', 277 pcall_err(exec_lua, [[ return vim.api.nvim_buf_get_lines(0, 0, 10, 0) ]]) 278 ) 279 eq( 280 'Index out of bounds', 281 pcall_err(exec_lua, [[ return vim.api.nvim_buf_get_lines(0, 0, 10, {}) ]]) 282 ) 283 end) 284 285 it('converts booleans in optional args', function() 286 eq({}, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {output=false}) ]]) 287 eq({}, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {}) ]]) -- same as {output=nil} 288 289 -- API conventions (not lua conventions): zero is falsy 290 eq({}, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {output=0}) ]]) 291 292 eq( 293 { output = 'foobar' }, 294 exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {output=true}) ]] 295 ) 296 eq({ output = 'foobar' }, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {output=1}) ]]) 297 eq( 298 [[Invalid 'output': not a boolean]], 299 pcall_err(exec_lua, [[ return vim.api.nvim_exec2("echo 'foobar'", {output={}}) ]]) 300 ) 301 end) 302 303 it('errors out correctly when working with API', function() 304 -- Conversion errors 305 eq( 306 [[Vim(call):E5108: Lua: [string "luaeval()"]:1: Invalid 'obj': Cannot convert given Lua table]], 307 remove_trace(exc_exec([[call luaeval("vim.api.nvim__id({1, foo=42})")]])) 308 ) 309 -- Errors in number of arguments 310 eq( 311 'Vim(call):E5108: Lua: [string "luaeval()"]:1: Expected 1 argument', 312 remove_trace(exc_exec([[call luaeval("vim.api.nvim__id()")]])) 313 ) 314 eq( 315 'Vim(call):E5108: Lua: [string "luaeval()"]:1: Expected 1 argument', 316 remove_trace(exc_exec([[call luaeval("vim.api.nvim__id(1, 2)")]])) 317 ) 318 eq( 319 'Vim(call):E5108: Lua: [string "luaeval()"]:1: Expected 2 arguments', 320 remove_trace(exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2, 3)")]])) 321 ) 322 -- Error in argument types 323 eq( 324 [[Vim(call):E5108: Lua: [string "luaeval()"]:1: Invalid 'name': Expected Lua string]], 325 remove_trace(exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2)")]])) 326 ) 327 328 eq( 329 [[Vim(call):E5108: Lua: [string "luaeval()"]:1: Invalid 'start': Expected Lua number]], 330 remove_trace(exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 'test', 1, false)")]])) 331 ) 332 eq( 333 [[Vim(call):E5108: Lua: [string "luaeval()"]:1: Invalid 'start': Number is not integral]], 334 remove_trace(exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 1.5, 1, false)")]])) 335 ) 336 eq( 337 [[Vim(call):E5108: Lua: [string "luaeval()"]:1: Invalid 'window': Expected Lua number]], 338 remove_trace(exc_exec([[call luaeval("vim.api.nvim_win_is_valid(nil)")]])) 339 ) 340 341 eq( 342 [[Vim(call):E5108: Lua: [string "luaeval()"]:1: Invalid 'flt': Expected Lua number]], 343 remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_float('test')")]])) 344 ) 345 eq( 346 [[Vim(call):E5108: Lua: [string "luaeval()"]:1: Invalid 'flt': Expected Float-like Lua table]], 347 remove_trace( 348 exc_exec([[call luaeval("vim.api.nvim__id_float({[vim.type_idx]=vim.types.dictionary})")]]) 349 ) 350 ) 351 352 eq( 353 [[Vim(call):E5108: Lua: [string "luaeval()"]:1: Invalid 'arr': Expected Lua table]], 354 remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_array(1)")]])) 355 ) 356 eq( 357 [[Vim(call):E5108: Lua: [string "luaeval()"]:1: Invalid 'arr': Expected Array-like Lua table]], 358 remove_trace( 359 exc_exec([[call luaeval("vim.api.nvim__id_array({[vim.type_idx]=vim.types.dictionary})")]]) 360 ) 361 ) 362 363 eq( 364 [[Vim(call):E5108: Lua: [string "luaeval()"]:1: Invalid 'dct': Expected Lua table]], 365 remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_dict(1)")]])) 366 ) 367 eq( 368 [[Vim(call):E5108: Lua: [string "luaeval()"]:1: Invalid 'dct': Expected Dict-like Lua table]], 369 remove_trace( 370 exc_exec([[call luaeval("vim.api.nvim__id_dict({[vim.type_idx]=vim.types.array})")]]) 371 ) 372 ) 373 374 eq( 375 [[Vim(call):E5108: Lua: [string "luaeval()"]:1: Expected Lua table]], 376 remove_trace(exc_exec([[call luaeval("vim.api.nvim_set_keymap('', '', '', '')")]])) 377 ) 378 379 -- TODO: check for errors with Tabpage argument 380 -- TODO: check for errors with Window argument 381 -- TODO: check for errors with Buffer argument 382 end) 383 384 it('accepts any value as API Boolean', function() 385 eq('', fn.luaeval('vim.api.nvim_replace_termcodes("", vim, false, nil)')) 386 eq('', fn.luaeval('vim.api.nvim_replace_termcodes("", 0, 1.5, "test")')) 387 eq( 388 '', 389 fn.luaeval('vim.api.nvim_replace_termcodes("", true, {}, {[vim.type_idx]=vim.types.array})') 390 ) 391 end) 392 393 it('serializes sparse arrays in Lua', function() 394 eq({ [1] = vim.NIL, [2] = 2 }, exec_lua [[ return { [2] = 2 } ]]) 395 end) 396 end)