aboutsummaryrefslogtreecommitdiff
path: root/test/functional/vimscript
diff options
context:
space:
mode:
authorJustin M. Keyes <justinkz@gmail.com>2021-09-17 09:16:40 -0700
committerGitHub <noreply@github.com>2021-09-17 09:16:40 -0700
commitd8de4eb685e35646c7d541e9a75bdc296127b7e2 (patch)
tree4bb05ec713856715ac9ba57e5d116eed344511b9 /test/functional/vimscript
parentd56002f7b722facd97b0958e141c8ed2d01495f7 (diff)
downloadrneovim-d8de4eb685e35646c7d541e9a75bdc296127b7e2.tar.gz
rneovim-d8de4eb685e35646c7d541e9a75bdc296127b7e2.tar.bz2
rneovim-d8de4eb685e35646c7d541e9a75bdc296127b7e2.zip
test: reorg #15698
Problem: Subdirectories like "visual", "insert", "normal" encourage people to separate *related* tests for no good reason. Typically the _mode_ is not the relevant topic of a test (and when it is, _then_ create an appropriate describe() or it()). Solution: - Delete the various `test/functional/<mode>/` subdirectories, move their tests to more meaningful topics. - Rename `…/normal/` to `…/editor/`. - Move or merge `…/visual/*` and `…/insert/*` tests into here where appropriate. - Rename `…/eval/` to `…/vimscript/`. - Move `…/viml/*` into here also. * test(reorg): insert/* => editor/mode_insert_spec.lua * test(reorg): cmdline/* => editor/mode_cmdline_spec.lua * test(reorg): eval core tests => eval_spec.lua
Diffstat (limited to 'test/functional/vimscript')
-rw-r--r--test/functional/vimscript/api_functions_spec.lua167
-rw-r--r--test/functional/vimscript/buf_functions_spec.lua306
-rw-r--r--test/functional/vimscript/changedtick_spec.lua142
-rw-r--r--test/functional/vimscript/container_functions_spec.lua24
-rw-r--r--test/functional/vimscript/ctx_functions_spec.lua406
-rw-r--r--test/functional/vimscript/environ_spec.lua80
-rw-r--r--test/functional/vimscript/errorlist_spec.lua84
-rw-r--r--test/functional/vimscript/eval_spec.lua146
-rw-r--r--test/functional/vimscript/executable_spec.lua218
-rw-r--r--test/functional/vimscript/execute_spec.lua337
-rw-r--r--test/functional/vimscript/exepath_spec.lua40
-rw-r--r--test/functional/vimscript/fnamemodify_spec.lua156
-rw-r--r--test/functional/vimscript/functions_spec.lua20
-rw-r--r--test/functional/vimscript/getline_spec.lua39
-rw-r--r--test/functional/vimscript/glob_spec.lua28
-rw-r--r--test/functional/vimscript/has_spec.lua66
-rw-r--r--test/functional/vimscript/hostname_spec.lua20
-rw-r--r--test/functional/vimscript/input_spec.lua483
-rw-r--r--test/functional/vimscript/json_functions_spec.lua795
-rw-r--r--test/functional/vimscript/lang_spec.lua30
-rw-r--r--test/functional/vimscript/let_spec.lua93
-rw-r--r--test/functional/vimscript/map_functions_spec.lua163
-rw-r--r--test/functional/vimscript/match_functions_spec.lua157
-rw-r--r--test/functional/vimscript/minmax_functions_spec.lua51
-rw-r--r--test/functional/vimscript/modeline_spec.lua19
-rw-r--r--test/functional/vimscript/msgpack_functions_spec.lua755
-rw-r--r--test/functional/vimscript/null_spec.lua170
-rw-r--r--test/functional/vimscript/operators_spec.lua28
-rw-r--r--test/functional/vimscript/printf_spec.lua92
-rw-r--r--test/functional/vimscript/reltime_spec.lua53
-rw-r--r--test/functional/vimscript/server_spec.lua156
-rw-r--r--test/functional/vimscript/setpos_spec.lua64
-rw-r--r--test/functional/vimscript/sort_spec.lua57
-rw-r--r--test/functional/vimscript/special_vars_spec.lua190
-rw-r--r--test/functional/vimscript/string_spec.lua277
-rw-r--r--test/functional/vimscript/system_spec.lua589
-rw-r--r--test/functional/vimscript/timer_spec.lua265
-rw-r--r--test/functional/vimscript/uniq_spec.lua31
-rw-r--r--test/functional/vimscript/vvar_event_spec.lua15
-rw-r--r--test/functional/vimscript/wait_spec.lua78
-rw-r--r--test/functional/vimscript/writefile_spec.lua156
41 files changed, 7046 insertions, 0 deletions
diff --git a/test/functional/vimscript/api_functions_spec.lua b/test/functional/vimscript/api_functions_spec.lua
new file mode 100644
index 0000000000..d07e74d40e
--- /dev/null
+++ b/test/functional/vimscript/api_functions_spec.lua
@@ -0,0 +1,167 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local lfs = require('lfs')
+local neq, eq, command = helpers.neq, helpers.eq, helpers.command
+local clear, curbufmeths = helpers.clear, helpers.curbufmeths
+local exc_exec, expect, eval = helpers.exc_exec, helpers.expect, helpers.eval
+local insert, pcall_err = helpers.insert, helpers.pcall_err
+local meths = helpers.meths
+
+describe('eval-API', function()
+ before_each(clear)
+
+ it("work", function()
+ command("call nvim_command('let g:test = 1')")
+ eq(1, eval("nvim_get_var('test')"))
+
+ local buf = eval("nvim_get_current_buf()")
+ command("call nvim_buf_set_lines("..buf..", 0, -1, v:true, ['aa', 'bb'])")
+ expect([[
+ aa
+ bb]])
+
+ command("call nvim_win_set_cursor(0, [1, 1])")
+ command("call nvim_input('ax<esc>')")
+ expect([[
+ aax
+ bb]])
+ end)
+
+ it("throw errors for invalid arguments", function()
+ local err = exc_exec('call nvim_get_current_buf("foo")')
+ eq('Vim(call):E118: Too many arguments for function: nvim_get_current_buf', err)
+
+ err = exc_exec('call nvim_set_option("hlsearch")')
+ eq('Vim(call):E119: Not enough arguments for function: nvim_set_option', err)
+
+ err = exc_exec('call nvim_buf_set_lines(1, 0, -1, [], ["list"])')
+ eq('Vim(call):E5555: API call: Wrong type for argument 4 when calling nvim_buf_set_lines, expecting Boolean', err)
+
+ err = exc_exec('call nvim_buf_set_lines(0, 0, -1, v:true, "string")')
+ eq('Vim(call):E5555: API call: Wrong type for argument 5 when calling nvim_buf_set_lines, expecting ArrayOf(String)', err)
+
+ err = exc_exec('call nvim_buf_get_number("0")')
+ eq('Vim(call):E5555: API call: Wrong type for argument 1 when calling nvim_buf_get_number, expecting Buffer', err)
+
+ err = exc_exec('call nvim_buf_line_count(17)')
+ eq('Vim(call):E5555: API call: Invalid buffer id: 17', err)
+ end)
+
+ it('cannot change texts if textlocked', function()
+ command("autocmd TextYankPost <buffer> ++once call nvim_buf_set_lines(0, 0, -1, v:false, [])")
+ eq('Vim(call):E5555: API call: E523: Not allowed here', pcall_err(command, "normal! yy"))
+ end)
+
+ it("use buffer numbers and windows ids as handles", function()
+ local screen = Screen.new(40, 8)
+ screen:attach()
+ local bnr = eval("bufnr('')")
+ local bhnd = eval("nvim_get_current_buf()")
+ local wid = eval("win_getid()")
+ local whnd = eval("nvim_get_current_win()")
+ eq(bnr, bhnd)
+ eq(wid, whnd)
+
+ command("new") -- creates new buffer and new window
+ local bnr2 = eval("bufnr('')")
+ local bhnd2 = eval("nvim_get_current_buf()")
+ local wid2 = eval("win_getid()")
+ local whnd2 = eval("nvim_get_current_win()")
+ eq(bnr2, bhnd2)
+ eq(wid2, whnd2)
+ neq(bnr, bnr2)
+ neq(wid, wid2)
+ -- 0 is synonymous to the current buffer
+ eq(bnr2, eval("nvim_buf_get_number(0)"))
+
+ command("bn") -- show old buffer in new window
+ eq(bnr, eval("nvim_get_current_buf()"))
+ eq(bnr, eval("bufnr('')"))
+ eq(bnr, eval("nvim_buf_get_number(0)"))
+ eq(wid2, eval("win_getid()"))
+ eq(whnd2, eval("nvim_get_current_win()"))
+ end)
+
+ it("get_lines and set_lines use NL to represent NUL", function()
+ curbufmeths.set_lines(0, -1, true, {"aa\0", "b\0b"})
+ eq({'aa\n', 'b\nb'}, eval("nvim_buf_get_lines(0, 0, -1, 1)"))
+
+ command('call nvim_buf_set_lines(0, 1, 2, v:true, ["xx", "\\nyy"])')
+ eq({'aa\0', 'xx', '\0yy'}, curbufmeths.get_lines(0, -1, 1))
+ end)
+
+ it("that are FUNC_ATTR_NOEVAL cannot be called", function()
+ -- Deprecated vim_ prefix is not exported.
+ local err = exc_exec('call vim_get_current_buffer("foo")')
+ eq('Vim(call):E117: Unknown function: vim_get_current_buffer', err)
+
+ -- Deprecated buffer_ prefix is not exported.
+ err = exc_exec('call buffer_line_count(0)')
+ eq('Vim(call):E117: Unknown function: buffer_line_count', err)
+
+ -- Functions deprecated before the api functions became available
+ -- in vimscript are not exported.
+ err = exc_exec('call buffer_get_line(0, 1)')
+ eq('Vim(call):E117: Unknown function: buffer_get_line', err)
+
+ -- some api functions are only useful from a msgpack-rpc channel
+ err = exc_exec('call nvim_subscribe("fancyevent")')
+ eq('Vim(call):E117: Unknown function: nvim_subscribe', err)
+ end)
+
+ it('have metadata accessible with api_info()', function()
+ local api_keys = eval("sort(keys(api_info()))")
+ eq({'error_types', 'functions', 'types',
+ 'ui_events', 'ui_options', 'version'}, api_keys)
+ end)
+
+ it('are highlighted by vim.vim syntax file', function()
+ if lfs.attributes("build/runtime/syntax/vim/generated.vim",'uid') == nil then
+ pending("runtime was not built, skipping test")
+ return
+ end
+ local screen = Screen.new(40, 8)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Brown},
+ [2] = {foreground = Screen.colors.DarkCyan},
+ [3] = {foreground = Screen.colors.SlateBlue},
+ [4] = {foreground = Screen.colors.Fuchsia},
+ [5] = {bold = true, foreground = Screen.colors.Blue},
+ })
+
+ command("set ft=vim")
+ command("let &rtp='build/runtime/,'.&rtp")
+ command("syntax on")
+ insert([[
+ call bufnr('%')
+ call nvim_input('typing...')
+ call not_a_function(42)]])
+
+ screen:expect([[
+ {1:call} {2:bufnr}{3:(}{4:'%'}{3:)} |
+ {1:call} {2:nvim_input}{3:(}{4:'typing...'}{3:)} |
+ {1:call} not_a_function{3:(}{4:42}{3:^)} |
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ {5:~ }|
+ |
+ ]])
+ end)
+
+ it('cannot be called from sandbox', function()
+ eq('Vim(call):E48: Not allowed in sandbox',
+ pcall_err(command, "sandbox call nvim_input('ievil')"))
+ eq({''}, meths.buf_get_lines(0, 0, -1, true))
+ end)
+
+ it('converts blobs to API strings', function()
+ command('let g:v1 = nvim__id(0z68656c6c6f)')
+ command('let g:v2 = nvim__id(v:_null_blob)')
+ eq(1, eval('type(g:v1)'))
+ eq(1, eval('type(g:v2)'))
+ eq('hello', eval('g:v1'))
+ eq('', eval('g:v2'))
+ end)
+end)
diff --git a/test/functional/vimscript/buf_functions_spec.lua b/test/functional/vimscript/buf_functions_spec.lua
new file mode 100644
index 0000000000..e957e5f5af
--- /dev/null
+++ b/test/functional/vimscript/buf_functions_spec.lua
@@ -0,0 +1,306 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local lfs = require('lfs')
+
+local eq = helpers.eq
+local clear = helpers.clear
+local funcs = helpers.funcs
+local meths = helpers.meths
+local command = helpers.command
+local exc_exec = helpers.exc_exec
+local bufmeths = helpers.bufmeths
+local winmeths = helpers.winmeths
+local curbufmeths = helpers.curbufmeths
+local curwinmeths = helpers.curwinmeths
+local curtabmeths = helpers.curtabmeths
+local get_pathsep = helpers.get_pathsep
+local rmdir = helpers.rmdir
+local pcall_err = helpers.pcall_err
+
+local fname = 'Xtest-functional-eval-buf_functions'
+local fname2 = fname .. '.2'
+local dirname = fname .. '.d'
+
+before_each(clear)
+
+for _, func in ipairs({'bufname(%s)', 'bufnr(%s)', 'bufwinnr(%s)',
+ 'getbufline(%s, 1)', 'getbufvar(%s, "changedtick")',
+ 'setbufvar(%s, "f", 0)'}) do
+ local funcname = func:match('%w+')
+ describe(funcname .. '() function', function()
+ it('errors out when receives v:true/v:false/v:null', function()
+ -- Not compatible with Vim: in Vim it always results in buffer not found
+ -- without any error messages.
+ for _, var in ipairs({'v:true', 'v:false'}) do
+ eq('Vim(call):E5299: Expected a Number or a String, Boolean found',
+ exc_exec('call ' .. func:format(var)))
+ end
+ eq('Vim(call):E5300: Expected a Number or a String',
+ exc_exec('call ' .. func:format('v:null')))
+ end)
+ it('errors out when receives invalid argument', function()
+ eq('Vim(call):E745: Expected a Number or a String, List found',
+ exc_exec('call ' .. func:format('[]')))
+ eq('Vim(call):E728: Expected a Number or a String, Dictionary found',
+ exc_exec('call ' .. func:format('{}')))
+ eq('Vim(call):E805: Expected a Number or a String, Float found',
+ exc_exec('call ' .. func:format('0.0')))
+ eq('Vim(call):E703: Expected a Number or a String, Funcref found',
+ exc_exec('call ' .. func:format('function("tr")')))
+ end)
+ end)
+end
+
+describe('bufname() function', function()
+ it('returns empty string when buffer was not found', function()
+ command('file ' .. fname)
+ eq('', funcs.bufname(2))
+ eq('', funcs.bufname('non-existent-buffer'))
+ eq('', funcs.bufname('#'))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq('', funcs.bufname('X'))
+ end)
+ before_each(function()
+ lfs.mkdir(dirname)
+ end)
+ after_each(function()
+ rmdir(dirname)
+ end)
+ it('returns expected buffer name', function()
+ eq('', funcs.bufname('%')) -- Buffer has no name yet
+ command('file ' .. fname)
+ local wd = lfs.currentdir()
+ local sep = get_pathsep()
+ local curdirname = funcs.fnamemodify(wd, ':t')
+ for _, arg in ipairs({'%', 1, 'X', wd}) do
+ eq(fname, funcs.bufname(arg))
+ meths.set_current_dir('..')
+ eq(curdirname .. sep .. fname, funcs.bufname(arg))
+ meths.set_current_dir(curdirname)
+ meths.set_current_dir(dirname)
+ eq(wd .. sep .. fname, funcs.bufname(arg))
+ meths.set_current_dir('..')
+ eq(fname, funcs.bufname(arg))
+ command('enew')
+ end
+ eq('', funcs.bufname('%'))
+ eq('', funcs.bufname('$'))
+ eq(2, funcs.bufnr('%'))
+ end)
+end)
+
+describe('bufnr() function', function()
+ it('returns -1 when buffer was not found', function()
+ command('file ' .. fname)
+ eq(-1, funcs.bufnr(2))
+ eq(-1, funcs.bufnr('non-existent-buffer'))
+ eq(-1, funcs.bufnr('#'))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq(-1, funcs.bufnr('X'))
+ end)
+ it('returns expected buffer number', function()
+ eq(1, funcs.bufnr('%'))
+ command('file ' .. fname)
+ local wd = lfs.currentdir()
+ local curdirname = funcs.fnamemodify(wd, ':t')
+ eq(1, funcs.bufnr(fname))
+ eq(1, funcs.bufnr(wd))
+ eq(1, funcs.bufnr(curdirname))
+ eq(1, funcs.bufnr('X'))
+ end)
+ it('returns number of last buffer with "$"', function()
+ eq(1, funcs.bufnr('$'))
+ command('new')
+ eq(2, funcs.bufnr('$'))
+ command('new')
+ eq(3, funcs.bufnr('$'))
+ command('only')
+ eq(3, funcs.bufnr('$'))
+ eq(3, funcs.bufnr('%'))
+ command('buffer 1')
+ eq(3, funcs.bufnr('$'))
+ eq(1, funcs.bufnr('%'))
+ command('bwipeout 2')
+ eq(3, funcs.bufnr('$'))
+ eq(1, funcs.bufnr('%'))
+ command('bwipeout 3')
+ eq(1, funcs.bufnr('$'))
+ eq(1, funcs.bufnr('%'))
+ command('new')
+ eq(4, funcs.bufnr('$'))
+ end)
+end)
+
+describe('bufwinnr() function', function()
+ it('returns -1 when buffer was not found', function()
+ command('file ' .. fname)
+ eq(-1, funcs.bufwinnr(2))
+ eq(-1, funcs.bufwinnr('non-existent-buffer'))
+ eq(-1, funcs.bufwinnr('#'))
+ command('split ' .. fname2) -- It would be OK if there was one window
+ eq(2, funcs.bufnr('%'))
+ eq(-1, funcs.bufwinnr('X'))
+ end)
+ before_each(function()
+ lfs.mkdir(dirname)
+ end)
+ after_each(function()
+ rmdir(dirname)
+ end)
+ it('returns expected window number', function()
+ eq(1, funcs.bufwinnr('%'))
+ command('file ' .. fname)
+ command('vsplit')
+ command('split ' .. fname2)
+ eq(2, funcs.bufwinnr(fname))
+ eq(1, funcs.bufwinnr(fname2))
+ eq(-1, funcs.bufwinnr(fname:sub(1, #fname - 1)))
+ meths.set_current_dir(dirname)
+ eq(2, funcs.bufwinnr(fname))
+ eq(1, funcs.bufwinnr(fname2))
+ eq(-1, funcs.bufwinnr(fname:sub(1, #fname - 1)))
+ eq(1, funcs.bufwinnr('%'))
+ eq(2, funcs.bufwinnr(1))
+ eq(1, funcs.bufwinnr(2))
+ eq(-1, funcs.bufwinnr(3))
+ eq(1, funcs.bufwinnr('$'))
+ end)
+end)
+
+describe('getbufline() function', function()
+ it('returns empty list when buffer was not found', function()
+ command('file ' .. fname)
+ eq({}, funcs.getbufline(2, 1))
+ eq({}, funcs.getbufline('non-existent-buffer', 1))
+ eq({}, funcs.getbufline('#', 1))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq({}, funcs.getbufline('X', 1))
+ end)
+ it('returns empty list when range is invalid', function()
+ eq({}, funcs.getbufline(1, 0))
+ curbufmeths.set_lines(0, 1, false, {'foo', 'bar', 'baz'})
+ eq({}, funcs.getbufline(1, 2, 1))
+ eq({}, funcs.getbufline(1, -10, -20))
+ eq({}, funcs.getbufline(1, -2, -1))
+ eq({}, funcs.getbufline(1, -1, 9999))
+ end)
+ it('returns expected lines', function()
+ meths.set_option('hidden', true)
+ command('file ' .. fname)
+ curbufmeths.set_lines(0, 1, false, {'foo\0', '\0bar', 'baz'})
+ command('edit ' .. fname2)
+ curbufmeths.set_lines(0, 1, false, {'abc\0', '\0def', 'ghi'})
+ eq({'foo\n', '\nbar', 'baz'}, funcs.getbufline(1, 1, 9999))
+ eq({'abc\n', '\ndef', 'ghi'}, funcs.getbufline(2, 1, 9999))
+ eq({'foo\n', '\nbar', 'baz'}, funcs.getbufline(1, 1, '$'))
+ eq({'baz'}, funcs.getbufline(1, '$', '$'))
+ eq({'baz'}, funcs.getbufline(1, '$', 9999))
+ end)
+end)
+
+describe('getbufvar() function', function()
+ it('returns empty list when buffer was not found', function()
+ command('file ' .. fname)
+ eq('', funcs.getbufvar(2, '&autoindent'))
+ eq('', funcs.getbufvar('non-existent-buffer', '&autoindent'))
+ eq('', funcs.getbufvar('#', '&autoindent'))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq('', funcs.getbufvar('X', '&autoindent'))
+ end)
+ it('returns empty list when variable/option/etc was not found', function()
+ command('file ' .. fname)
+ eq('', funcs.getbufvar(1, '&autondent'))
+ eq('', funcs.getbufvar(1, 'changedtic'))
+ end)
+ it('returns expected option value', function()
+ eq(0, funcs.getbufvar(1, '&autoindent'))
+ eq(0, funcs.getbufvar(1, '&l:autoindent'))
+ eq(0, funcs.getbufvar(1, '&g:autoindent'))
+ -- Also works with global-only options
+ eq(1, funcs.getbufvar(1, '&hidden'))
+ eq(1, funcs.getbufvar(1, '&l:hidden'))
+ eq(1, funcs.getbufvar(1, '&g:hidden'))
+ -- Also works with window-local options
+ eq(0, funcs.getbufvar(1, '&number'))
+ eq(0, funcs.getbufvar(1, '&l:number'))
+ eq(0, funcs.getbufvar(1, '&g:number'))
+ command('new')
+ -- But with window-local options it probably does not what you expect
+ command("setl number")
+ -- (note that current window’s buffer is 2, but getbufvar() receives 1)
+ eq({id=2}, curwinmeths.get_buf())
+ eq(1, funcs.getbufvar(1, '&number'))
+ eq(1, funcs.getbufvar(1, '&l:number'))
+ -- You can get global value though, if you find this useful.
+ eq(0, funcs.getbufvar(1, '&g:number'))
+ end)
+ it('returns expected variable value', function()
+ eq(2, funcs.getbufvar(1, 'changedtick'))
+ curbufmeths.set_lines(0, 1, false, {'abc\0', '\0def', 'ghi'})
+ eq(3, funcs.getbufvar(1, 'changedtick'))
+ curbufmeths.set_var('test', true)
+ eq(true, funcs.getbufvar(1, 'test'))
+ eq({test=true, changedtick=3}, funcs.getbufvar(1, ''))
+ command('new')
+ eq(3, funcs.getbufvar(1, 'changedtick'))
+ eq(true, funcs.getbufvar(1, 'test'))
+ eq({test=true, changedtick=3}, funcs.getbufvar(1, ''))
+ end)
+end)
+
+describe('setbufvar() function', function()
+ it('throws the error or ignores the input when buffer was not found', function()
+ command('file ' .. fname)
+ eq(0,
+ exc_exec('call setbufvar(2, "&autoindent", 0)'))
+ eq('Vim(call):E94: No matching buffer for non-existent-buffer',
+ exc_exec('call setbufvar("non-existent-buffer", "&autoindent", 0)'))
+ eq(0,
+ exc_exec('call setbufvar("#", "&autoindent", 0)'))
+ command('edit ' .. fname2)
+ eq(2, funcs.bufnr('%'))
+ eq('Vim(call):E93: More than one match for X',
+ exc_exec('call setbufvar("X", "&autoindent", 0)'))
+ end)
+ it('may set options, including window-local and global values', function()
+ local buf1 = meths.get_current_buf()
+ eq(false, curwinmeths.get_option('number'))
+ command('split')
+ command('new')
+ eq(2, bufmeths.get_number(curwinmeths.get_buf()))
+ funcs.setbufvar(1, '&number', true)
+ local windows = curtabmeths.list_wins()
+ eq(false, winmeths.get_option(windows[1], 'number'))
+ eq(true, winmeths.get_option(windows[2], 'number'))
+ eq(false, winmeths.get_option(windows[3], 'number'))
+ eq(false, winmeths.get_option(meths.get_current_win(), 'number'))
+
+ eq(true, meths.get_option('hidden'))
+ funcs.setbufvar(1, '&hidden', 0)
+ eq(false, meths.get_option('hidden'))
+
+ eq(false, bufmeths.get_option(buf1, 'autoindent'))
+ funcs.setbufvar(1, '&autoindent', true)
+ eq(true, bufmeths.get_option(buf1, 'autoindent'))
+ eq('Vim(call):E355: Unknown option: xxx',
+ exc_exec('call setbufvar(1, "&xxx", 0)'))
+ end)
+ it('may set variables', function()
+ local buf1 = meths.get_current_buf()
+ command('split')
+ command('new')
+ eq(2, curbufmeths.get_number())
+ funcs.setbufvar(1, 'number', true)
+ eq(true, bufmeths.get_var(buf1, 'number'))
+ eq('Vim(call):E461: Illegal variable name: b:',
+ exc_exec('call setbufvar(1, "", 0)'))
+ eq(true, bufmeths.get_var(buf1, 'number'))
+ eq('Vim:E46: Cannot change read-only variable "b:changedtick"',
+ pcall_err(funcs.setbufvar, 1, 'changedtick', true))
+ eq(2, funcs.getbufvar(1, 'changedtick'))
+ end)
+end)
diff --git a/test/functional/vimscript/changedtick_spec.lua b/test/functional/vimscript/changedtick_spec.lua
new file mode 100644
index 0000000000..99406d9d7a
--- /dev/null
+++ b/test/functional/vimscript/changedtick_spec.lua
@@ -0,0 +1,142 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local eval = helpers.eval
+local feed = helpers.feed
+local clear = helpers.clear
+local funcs = helpers.funcs
+local meths = helpers.meths
+local command = helpers.command
+local exc_exec = helpers.exc_exec
+local redir_exec = helpers.redir_exec
+local pcall_err = helpers.pcall_err
+local curbufmeths = helpers.curbufmeths
+
+before_each(clear)
+
+local function changedtick()
+ local ct = curbufmeths.get_changedtick()
+ eq(ct, curbufmeths.get_var('changedtick'))
+ eq(ct, curbufmeths.get_var('changedtick'))
+ eq(ct, eval('b:changedtick'))
+ eq(ct, eval('b:["changedtick"]'))
+ eq(ct, eval('b:.changedtick'))
+ eq(ct, funcs.getbufvar('%', 'changedtick'))
+ eq(ct, funcs.getbufvar('%', '').changedtick)
+ eq(ct, eval('b:').changedtick)
+ return ct
+end
+
+describe('b:changedtick', function()
+ -- Ported tests from Vim-8.0.333
+ it('increments', function() -- Test_changedtick_increments
+ -- New buffer has an empty line, tick starts at 2
+ eq(2, changedtick())
+ funcs.setline(1, 'hello')
+ eq(3, changedtick())
+ eq(0, exc_exec('undo'))
+ -- Somehow undo counts as two changes
+ eq(5, changedtick())
+ end)
+ it('is present in b: dictionary', function()
+ eq(2, changedtick())
+ command('let d = b:')
+ eq(2, meths.get_var('d').changedtick)
+ end)
+ it('increments at bdel', function()
+ command('new')
+ eq(2, changedtick())
+ local bnr = curbufmeths.get_number()
+ eq(2, bnr)
+ command('bdel')
+ eq(3, funcs.getbufvar(bnr, 'changedtick'))
+ eq(1, curbufmeths.get_number())
+ end)
+ it('fails to be changed by user', function()
+ local ct = changedtick()
+ local ctn = ct + 100500
+ eq(0, exc_exec('let d = b:'))
+ eq('\nE46: Cannot change read-only variable "b:changedtick"',
+ redir_exec('let b:changedtick = ' .. ctn))
+ eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
+ redir_exec('let b:["changedtick"] = ' .. ctn))
+ eq('\nE46: Cannot change read-only variable "b:.changedtick"',
+ redir_exec('let b:.changedtick = ' .. ctn))
+ eq('\nE46: Cannot change read-only variable "d.changedtick"',
+ redir_exec('let d.changedtick = ' .. ctn))
+ eq('Key is read-only: changedtick',
+ pcall_err(curbufmeths.set_var, 'changedtick', ctn))
+
+ eq('\nE795: Cannot delete variable b:changedtick',
+ redir_exec('unlet b:changedtick'))
+ eq('\nE46: Cannot change read-only variable "b:.changedtick"',
+ redir_exec('unlet b:.changedtick'))
+ eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
+ redir_exec('unlet b:["changedtick"]'))
+ eq('\nE46: Cannot change read-only variable "d.changedtick"',
+ redir_exec('unlet d.changedtick'))
+ eq('Key is read-only: changedtick',
+ pcall_err(curbufmeths.del_var, 'changedtick'))
+ eq(ct, changedtick())
+
+ eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
+ redir_exec('let b:["changedtick"] += ' .. ctn))
+ eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
+ redir_exec('let b:["changedtick"] -= ' .. ctn))
+ eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
+ redir_exec('let b:["changedtick"] .= ' .. ctn))
+
+ eq(ct, changedtick())
+
+ funcs.setline(1, 'hello')
+
+ eq(ct + 1, changedtick())
+ end)
+ it('is listed in :let output', function()
+ eq('\nb:changedtick #2',
+ redir_exec(':let b:'))
+ end)
+ it('fails to unlock b:changedtick', function()
+ eq(0, exc_exec('let d = b:'))
+ eq(0, funcs.islocked('b:changedtick'))
+ eq(0, funcs.islocked('d.changedtick'))
+ eq('\nE940: Cannot lock or unlock variable b:changedtick',
+ redir_exec('unlockvar b:changedtick'))
+ eq('\nE46: Cannot change read-only variable "d.changedtick"',
+ redir_exec('unlockvar d.changedtick'))
+ eq(0, funcs.islocked('b:changedtick'))
+ eq(0, funcs.islocked('d.changedtick'))
+ eq('\nE940: Cannot lock or unlock variable b:changedtick',
+ redir_exec('lockvar b:changedtick'))
+ eq('\nE46: Cannot change read-only variable "d.changedtick"',
+ redir_exec('lockvar d.changedtick'))
+ eq(0, funcs.islocked('b:changedtick'))
+ eq(0, funcs.islocked('d.changedtick'))
+ end)
+ it('is being completed', function()
+ feed(':echo b:<Tab><Home>let cmdline="<End>"<CR>')
+ eq('echo b:changedtick', meths.get_var('cmdline'))
+ end)
+ it('cannot be changed by filter() or map()', function()
+ eq(2, changedtick())
+ eq('\nE795: Cannot delete variable filter() argument',
+ redir_exec('call filter(b:, 0)'))
+ eq('\nE742: Cannot change value of map() argument',
+ redir_exec('call map(b:, 0)'))
+ eq('\nE742: Cannot change value of map() argument',
+ redir_exec('call map(b:, "v:val")'))
+ eq(2, changedtick())
+ end)
+ it('cannot be remove()d', function()
+ eq(2, changedtick())
+ eq('\nE795: Cannot delete variable remove() argument',
+ redir_exec('call remove(b:, "changedtick")'))
+ eq(2, changedtick())
+ end)
+ it('does not inherit VAR_FIXED when copying dictionary over', function()
+ eq(2, changedtick())
+ eq('', redir_exec('let d1 = copy(b:)|let d1.changedtick = 42'))
+ eq('', redir_exec('let d2 = copy(b:)|unlet d2.changedtick'))
+ eq(2, changedtick())
+ end)
+end)
diff --git a/test/functional/vimscript/container_functions_spec.lua b/test/functional/vimscript/container_functions_spec.lua
new file mode 100644
index 0000000000..04a3248c49
--- /dev/null
+++ b/test/functional/vimscript/container_functi