neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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)