diff options
author | James McCoy <jamessan@jamessan.com> | 2017-04-05 22:39:40 -0400 |
---|---|---|
committer | James McCoy <jamessan@jamessan.com> | 2017-04-05 22:39:40 -0400 |
commit | bb54d921aaf85f0393c1ba10585560056f7f4ec8 (patch) | |
tree | 316ff7424337b3572b58340383eeabb3a0d36e80 /test/functional | |
parent | 4f69a8fb8854698adb2de8956ad0d86ff35a6f68 (diff) | |
parent | 210b013ce75b5ea8a897071e32decc1e1f88189e (diff) | |
download | rneovim-bb54d921aaf85f0393c1ba10585560056f7f4ec8.tar.gz rneovim-bb54d921aaf85f0393c1ba10585560056f7f4ec8.tar.bz2 rneovim-bb54d921aaf85f0393c1ba10585560056f7f4ec8.zip |
Merge remote-tracking branch 'origin/master' into vim-7.4.2170
Diffstat (limited to 'test/functional')
33 files changed, 1655 insertions, 214 deletions
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 3348368a36..8f9f155110 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -153,6 +153,28 @@ describe('api', function() nvim('set_option', 'equalalways', false) ok(not nvim('get_option', 'equalalways')) end) + + it('works to get global value of local options', function() + eq(false, nvim('get_option', 'lisp')) + eq(8, nvim('get_option', 'shiftwidth')) + end) + + it('works to set global value of local options', function() + nvim('set_option', 'lisp', true) + eq(true, nvim('get_option', 'lisp')) + eq(false, helpers.curbuf('get_option', 'lisp')) + eq(nil, nvim('command_output', 'setglobal lisp?'):match('nolisp')) + eq('nolisp', nvim('command_output', 'setlocal lisp?'):match('nolisp')) + nvim('set_option', 'shiftwidth', 20) + eq('20', nvim('command_output', 'setglobal shiftwidth?'):match('%d+')) + eq('8', nvim('command_output', 'setlocal shiftwidth?'):match('%d+')) + end) + + it('most window-local options have no global value', function() + local status, err = pcall(nvim, 'get_option', 'foldcolumn') + eq(false, status) + ok(err:match('Invalid option name') ~= nil) + end) end) describe('nvim_{get,set}_current_buf, nvim_list_bufs', function() diff --git a/test/functional/autocmd/termclose_spec.lua b/test/functional/autocmd/termclose_spec.lua index ecda1bffb7..d4beab22e4 100644 --- a/test/functional/autocmd/termclose_spec.lua +++ b/test/functional/autocmd/termclose_spec.lua @@ -14,17 +14,11 @@ describe('TermClose event', function() nvim('set_option', 'shellcmdflag', 'EXE') end) - local function eq_err(expected, actual) - if expected ~= actual then - error('expected: '..tostring(expected)..', actual: '..tostring(actual)) - end - end - it('triggers when terminal job ends', function() command('autocmd TermClose * let g:test_termclose = 23') command('terminal') command('call jobstop(b:terminal_job_id)') - retry(nil, nil, function() eq_err(23, eval('g:test_termclose')) end) + retry(nil, nil, function() eq(23, eval('g:test_termclose')) end) end) it('reports the correct <abuf>', function() @@ -35,12 +29,12 @@ describe('TermClose event', function() eq(2, eval('bufnr("%")')) command('terminal') - retry(nil, nil, function() eq_err(3, eval('bufnr("%")')) end) + retry(nil, nil, function() eq(3, eval('bufnr("%")')) end) command('buffer 1') - retry(nil, nil, function() eq_err(1, eval('bufnr("%")')) end) + retry(nil, nil, function() eq(1, eval('bufnr("%")')) end) command('3bdelete!') - retry(nil, nil, function() eq_err('3', eval('g:abuf')) end) + retry(nil, nil, function() eq('3', eval('g:abuf')) end) end) end) diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index e442c2a317..9ee91f2fe9 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -8,6 +8,7 @@ local clear, eq, eval, exc_exec, execute, feed, insert, neq, next_msg, nvim, local command = helpers.command local wait = helpers.wait local iswin = helpers.iswin +local get_pathsep = helpers.get_pathsep local Screen = require('test.functional.ui.screen') describe('jobs', function() @@ -65,7 +66,7 @@ describe('jobs', function() end) it('changes to given `cwd` directory', function() - local dir = eval('resolve(tempname())') + local dir = eval("resolve(tempname())"):gsub("/", get_pathsep()) mkdir(dir) nvim('command', "let g:job_opts.cwd = '" .. dir .. "'") if iswin() then diff --git a/test/functional/eval/buf_functions_spec.lua b/test/functional/eval/buf_functions_spec.lua new file mode 100644 index 0000000000..db50874c53 --- /dev/null +++ b/test/functional/eval/buf_functions_spec.lua @@ -0,0 +1,302 @@ +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 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', 'v:null'}) do + eq('Vim(call):E5300: Expected a Number or a String', + exc_exec('call ' .. func:format(var))) + end + 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() + lfs.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() + lfs.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(0, funcs.getbufvar(1, '&hidden')) + eq(0, funcs.getbufvar(1, '&l:hidden')) + eq(0, 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 + curwinmeths.set_option('number', true) + -- (note that current window’s buffer is 2, but getbufvar() receives 1) + eq(2, bufmeths.get_number(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(false, meths.get_option('hidden')) + funcs.setbufvar(1, '&hidden', true) + eq(true, 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')) + funcs.setbufvar(1, 'changedtick', true) + -- eq(true, bufmeths.get_var(buf1, 'changedtick')) + eq(2, funcs.getbufvar(1, 'changedtick')) + end) +end) diff --git a/test/functional/eval/container_functions_spec.lua b/test/functional/eval/container_functions_spec.lua new file mode 100644 index 0000000000..04a3248c49 --- /dev/null +++ b/test/functional/eval/container_functions_spec.lua @@ -0,0 +1,24 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local eval = helpers.eval +local meths = helpers.meths +local clear = helpers.clear + +before_each(clear) + +describe('extend()', function() + it('suceeds to extend list with itself', function() + meths.set_var('l', {1, {}}) + eq({1, {}, 1, {}}, eval('extend(l, l)')) + eq({1, {}, 1, {}}, meths.get_var('l')) + + meths.set_var('l', {1, {}}) + eq({1, {}, 1, {}}, eval('extend(l, l, 0)')) + eq({1, {}, 1, {}}, meths.get_var('l')) + + meths.set_var('l', {1, {}}) + eq({1, 1, {}, {}}, eval('extend(l, l, 1)')) + eq({1, 1, {}, {}}, meths.get_var('l')) + end) +end) diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua new file mode 100644 index 0000000000..393fc10175 --- /dev/null +++ b/test/functional/eval/input_spec.lua @@ -0,0 +1,38 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local feed = helpers.feed +local clear = helpers.clear +local command = helpers.command + +local screen + +before_each(function() + clear() + screen = Screen.new(25, 5) + screen:attach() +end) + +describe('input()', function() + it('works correctly with multiline prompts', function() + feed([[:call input("Test\nFoo")<CR>]]) + screen:expect([[ + {1:~ }| + {1:~ }| + {1:~ }| + Test | + Foo^ | + ]], {{bold=true, foreground=Screen.colors.Blue}}) + end) + it('works correctly with multiline prompts and :echohl', function() + command('hi Test ctermfg=Red guifg=Red term=bold') + feed([[:echohl Test | call input("Test\nFoo")<CR>]]) + screen:expect([[ + {1:~ }| + {1:~ }| + {1:~ }| + {2:Test} | + {2:Foo}^ | + ]], {{bold=true, foreground=Screen.colors.Blue}, {foreground=Screen.colors.Red}}) + end) +end) diff --git a/test/functional/eval/match_functions_spec.lua b/test/functional/eval/match_functions_spec.lua new file mode 100644 index 0000000000..3150d89f62 --- /dev/null +++ b/test/functional/eval/match_functions_spec.lua @@ -0,0 +1,61 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local clear = helpers.clear +local funcs = helpers.funcs +local command = helpers.command + +before_each(clear) + +describe('setmatches()', function() + it('correctly handles case when both group and pattern entries are numbers', + function() + command('hi def link 1 PreProc') + eq(0, funcs.setmatches({{group=1, pattern=2, id=3, priority=4}})) + eq({{ + group='1', + pattern='2', + id=3, + priority=4, + }}, funcs.getmatches()) + eq(0, funcs.setmatches({{group=1, pattern=2, id=3, priority=4, conceal=5}})) + eq({{ + group='1', + pattern='2', + id=3, + priority=4, + conceal='5', + }}, funcs.getmatches()) + eq(0, funcs.setmatches({{group=1, pos1={2}, pos2={6}, id=3, priority=4, conceal=5}})) + eq({{ + group='1', + pos1={2}, + pos2={6}, + id=3, + priority=4, + conceal='5', + }}, funcs.getmatches()) + end) + + it('fails with -1 if highlight group is not defined', function() + eq(-1, funcs.setmatches({{group=1, pattern=2, id=3, priority=4}})) + eq({}, funcs.getmatches()) + eq(-1, funcs.setmatches({{group=1, pos1={2}, pos2={6}, id=3, priority=4, conceal=5}})) + eq({}, funcs.getmatches()) + end) +end) + +describe('matchadd()', function() + it('correctly works when first two arguments and conceal are numbers at once', + function() + command('hi def link 1 PreProc') + eq(4, funcs.matchadd(1, 2, 3, 4, {conceal=5})) + eq({{ + group='1', + pattern='2', + priority=3, + id=4, + conceal='5', + }}, funcs.getmatches()) + end) +end) diff --git a/test/functional/eval/minmax_functions_spec.lua b/test/functional/eval/minmax_functions_spec.lua new file mode 100644 index 0000000000..c6eb754f91 --- /dev/null +++ b/test/functional/eval/minmax_functions_spec.lua @@ -0,0 +1,51 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local eval = helpers.eval +local clear = helpers.clear +local funcs = helpers.funcs +local redir_exec = helpers.redir_exec + +before_each(clear) +for _, func in ipairs({'min', 'max'}) do + describe(func .. '()', function() + it('gives a single error message when multiple values failed conversions', + function() + eq('\nE745: Using a List as a Number\n0', + redir_exec('echo ' .. func .. '([-5, [], [], [], 5])')) + eq('\nE745: Using a List as a Number\n0', + redir_exec('echo ' .. func .. '({1:-5, 2:[], 3:[], 4:[], 5:5})')) + for errmsg, errinput in pairs({ + ['E745: Using a List as a Number'] = '[]', + ['E805: Using a Float as a Number'] = '0.0', + ['E703: Using a Funcref as a Number'] = 'function("tr")', + ['E728: Using a Dictionary as a Number'] = '{}', + }) do + eq('\n' .. errmsg .. '\n0', + redir_exec('echo ' .. func .. '([' .. errinput .. '])')) + eq('\n' .. errmsg .. '\n0', + redir_exec('echo ' .. func .. '({1:' .. errinput .. '})')) + end + end) + it('works with arrays/dictionaries with zero items', function() + eq(0, funcs[func]({})) + eq(0, eval(func .. '({})')) + end) + it('works with arrays/dictionaries with one item', function() + eq(5, funcs[func]({5})) + eq(5, funcs[func]({test=5})) + end) + it('works with NULL arrays/dictionaries', function() + eq(0, eval(func .. '(v:_null_list)')) + eq(0, eval(func .. '(v:_null_dict)')) + end) + it('errors out for invalid types', function() + for _, errinput in ipairs({'1', 'v:true', 'v:false', 'v:null', + 'function("tr")', '""'}) do + eq(('\nE712: Argument of %s() must be a List or Dictionary\n0'):format( + func), + redir_exec('echo ' .. func .. '(' .. errinput .. ')')) + end + end) + end) +end diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua new file mode 100644 index 0000000000..6fd30caec9 --- /dev/null +++ b/test/functional/eval/null_spec.lua @@ -0,0 +1,138 @@ +local helpers = require('test.functional.helpers')(after_each) + +local curbufmeths = helpers.curbufmeths +local redir_exec = helpers.redir_exec +local exc_exec = helpers.exc_exec +local command = helpers.command +local clear = helpers.clear +local meths = helpers.meths +local funcs = helpers.funcs +local eq = helpers.eq + +describe('NULL', function() + before_each(function() + clear() + command('let L = v:_null_list') + command('let D = v:_null_dict') + command('let S = $XXX_NONEXISTENT_VAR_XXX') + end) + local tmpfname = 'Xtest-functional-viml-null' + after_each(function() + os.remove(tmpfname) + end) + local null_test = function(name, cmd, err) + it(name, function() + eq(err, exc_exec(cmd)) + end) + end + local null_expr_test = function(name, expr, err, val, after) + it(name, function() + eq((err == 0) and ('') or ('\n' .. err), + redir_exec('let g:_var = ' .. expr)) + if val == nil then + eq(0, funcs.exists('g:_var')) + else + eq(val, meths.get_var('_var')) + end + if after ~= nil then + after() + end + end) + end + describe('list', function() + -- Incorrect behaviour + + -- FIXME map() should not return 0 without error + null_expr_test('does not crash map()', 'map(L, "v:val")', 0, 0) + -- FIXME map() should not return 0 without error + null_expr_test('does not crash filter()', 'filter(L, "1")', 0, 0) + -- FIXME map() should at least return L + null_expr_test('makes map() return v:_null_list', 'map(L, "v:val") is# L', 0, 0) + -- FIXME filter() should at least return L + null_expr_test('makes filter() return v:_null_list', 'map(L, "1") is# L', 0, 0) + -- FIXME add() should not return 1 at all + null_expr_test('does not crash add()', 'add(L, 0)', 0, 1) + null_expr_test('does not crash extend()', 'extend(L, [1])', 'E742: Cannot change value of extend() argument', 0) + null_expr_test('does not crash extend() (second position)', 'extend([1], L)', 0, {1}) + -- FIXME should be accepted by inputlist() + null_expr_test('is accepted as an empty list by inputlist()', + '[feedkeys("\\n"), inputlist(L)]', 'E686: Argument of inputlist() must be a List', {0, 0}) + -- FIXME should be accepted by writefile(), return {0, {}} + null_expr_test('is accepted as an empty list by writefile()', + ('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname), + 'E484: Can\'t open file ' .. tmpfname, {0, {}}) + -- FIXME should give error message + null_expr_test('does not crash remove()', 'remove(L, 0)', 0, 0) + -- FIXME should return 0 + null_expr_test('is accepted by setqflist()', 'setqflist(L)', 0, -1) + -- FIXME should return 0 + null_expr_test('is accepted by setloclist()', 'setloclist(1, L)', 0, -1) + -- FIXME should return 0 + null_expr_test('is accepted by setmatches()', 'setmatches(L)', 0, -1) + -- FIXME should return empty list or error out + null_expr_test('is accepted by sort()', 'sort(L)', 0, 0) + -- FIXME Should return 1 + null_expr_test('is accepted by sort()', 'sort(L) is L', 0, 0) + -- FIXME should not error out + null_test('is accepted by :cexpr', 'cexpr L', 'Vim(cexpr):E777: String or List expected') + -- FIXME should not error out + null_test('is accepted by :lexpr', 'lexpr L', 'Vim(lexpr):E777: String or List expected') + null_test('is accepted by :for', 'for x in L|throw x|endfor', 0) + + -- Subjectable behaviour + + -- FIXME Should return 1 + null_expr_test('is equal to empty list', 'L == []', 0, 0) + -- FIXME Should return 1 + null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 0) + -- FIXME Should return 1 + null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) + + -- Crashes + + -- null_expr_test('does not crash setreg', 'setreg("x", L)', 0, 0) + -- null_expr_test('does not crash setline', 'setline(1, L)', 0, 0) + -- null_expr_test('does not crash system()', 'system("cat", L)', 0, '') + -- null_expr_test('does not crash systemlist()', 'systemlist("cat", L)', 0, {}) + + -- Correct behaviour + null_expr_test('does not crash append()', 'append(1, L)', 0, 0, function() + eq({''}, curbufmeths.get_lines(0, -1, false)) + end) + null_expr_test('is identical to itself', 'L is L', 0, 1) + null_expr_test('can be sliced', 'L[:]', 0, {}) + null_expr_test('can be copied', 'copy(L)', 0, {}) + null_expr_test('can be deepcopied', 'deepcopy(L)', 0, {}) + null_expr_test('does not crash when indexed', 'L[1]', + 'E684: list index out of range: 1\nE15: Invalid expression: L[1]', nil) + null_expr_test('does not crash call()', 'call("arglistid", L)', 0, 0) + null_expr_test('does not crash col()', 'col(L)', 0, 0) + null_expr_test('does not crash virtcol()', 'virtcol(L)', 0, 0) + null_expr_test('does not crash line()', 'line(L)', 0, 0) + null_expr_test('does not crash count()', 'count(L, 1)', 0, 0) + null_expr_test('does not crash cursor()', 'cursor(L)', 'E474: Invalid argument', -1) + null_expr_test('is empty', 'empty(L)', 0, 1) + null_expr_test('does not crash get()', 'get(L, 1, 10)', 0, 10) + null_expr_test('has zero length', 'len(L)', 0, 0) + null_expr_test('is accepted as an empty list by max()', 'max(L)', 0, 0) + null_expr_test('is accepted as an empty list by min()', 'min(L)', 0, 0) + null_expr_test('is stringified correctly', 'string(L)', 0, '[]') + null_expr_test('is JSON encoded correctly', 'json_encode(L)', 0, '[]') + null_test('does not crash lockvar', 'lockvar! L', 0) + null_expr_test('can be added to itself', '(L + L)', 0, {}) + null_expr_test('can be added to itself', '(L + L) is L', 0, 1) + null_expr_test('can be added to non-empty list', '([1] + L)', 0, {1}) + null_expr_test('can be added to non-empty list (reversed)', '(L + [1])', 0, {1}) + null_expr_test('is equal to itself', 'L == L', 0, 1) + null_expr_test('is not not equal to itself', 'L != L', 0, 0) + null_expr_test('counts correctly', 'count([L], L)', 0, 1) + end) + describe('dict', function() + it('does not crash when indexing NULL dict', function() + eq('\nE716: Key not present in Dictionary: test\nE15: Invalid expression: v:_null_dict.test', + redir_exec('echo v:_null_dict.test')) + end) + null_expr_test('makes extend error out', 'extend(D, {})', 'E742: Cannot change value of extend() argument', 0) + null_expr_test('makes extend do nothing', 'extend({1: 2}, D)', 0, {['1']=2}) + end) +end) diff --git a/test/functional/eval/sort_spec.lua b/test/functional/eval/sort_spec.lua new file mode 100644 index 0000000000..4e5a0afba4 --- /dev/null +++ b/test/functional/eval/sort_spec.lua @@ -0,0 +1,41 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local NIL = helpers.NIL +local eval = helpers.eval +local clear = helpers.clear +local meths = helpers.meths +local funcs = helpers.funcs +local command = helpers.command +local exc_exec = helpers.exc_exec + +before_each(clear) + +describe('sort()', function() + it('errors out when sorting special values', function() + eq('Vim(call):E907: Using a special value as a Float', + exc_exec('call sort([v:true, v:false], "f")')) + end) + + it('sorts “wrong” values between -0.0001 and 0.0001, preserving order', + function() + meths.set_var('list', {true, false, NIL, {}, {a=42}, 'check', + 0.0001, -0.0001}) + command('call insert(g:list, function("tr"))') + local error_lines = funcs.split( + funcs.execute('silent! call sort(g:list, "f")'), '\n') + local errors = {} + for _, err in ipairs(error_lines) do + errors[err] = true + end + eq({ + ['E891: Using a Funcref as a Float']=true, + ['E892: Using a String as a Float']=true, + ['E893: Using a List as a Float']=true, + ['E894: Using a Dictionary as a Float']=true, + ['E907: Using a special value as a Float']=true, + }, errors) + eq('[-1.0e-4, function(\'tr\'), v:true, v:false, v:null, [], {\'a\': 42}, \'check\', 1.0e-4]', + eval('string(g:list)')) + end) +end) diff --git a/test/functional/eval/string_spec.lua b/test/functional/eval/string_spec.lua index f6279e85e8..adc1af9b8e 100644 --- a/test/functional/eval/string_spec.lua +++ b/test/functional/eval/string_spec.lua @@ -7,7 +7,6 @@ local eval = helpers.eval local exc_exec = helpers.exc_exec local redir_exec = helpers.redir_exec local funcs = helpers.funcs -local write_file = helpers.write_file local NIL = helpers.NIL local source = helpers.source local dedent = helpers.dedent @@ -105,10 +104,8 @@ describe('string() function', function() end) describe('used to represent funcrefs', function() - local fname = 'Xtest-functional-eval-string_spec-fref-script.vim' - before_each(function() - write_file(fname, [[ + source([[ function Test1() endfunction @@ -120,11 +117,6 @@ describe('string() function', function() let g:Test2_f = function('s:Test2') ]]) - command('source ' .. fname) - end) - - after_each(function() - os.remove(fname) end) it('dumps references to built-in functions', function() diff --git a/test/functional/eval/timer_spec.lua b/test/functional/eval/timer_spec.lua index 4353619ff0..b3c4cd07eb 100644 --- a/test/functional/eval/timer_spec.lua +++ b/test/functional/eval/timer_spec.lua @@ -49,7 +49,7 @@ describe('timers', function() it('works with zero timeout', function() -- timer_start does still not invoke the callback immediately eq(0,eval("[timer_start(0, 'MyHandler', {'repeat': 1000}), g:val][1]")) - run(nil, nil, nil, 300) + run(nil, nil, nil, 400) eq(1000,eval("g:val")) end) diff --git a/test/functional/eval/writefile_spec.lua b/test/functional/eval/writefile_spec.lua index 3052c616e0..2f84114b9b 100644 --- a/test/functional/eval/writefile_spec.lua +++ b/test/functional/eval/writefile_spec.lua @@ -80,6 +80,13 @@ describe('writefile()', function() eq('a\0\0\0b', read_file(fname)) end) + it('writes with s and S', function() + eq(0, funcs.writefile({'\na\nb\n'}, fname, 'bs')) + eq('\0a\0b\0', read_file(fname)) + eq(0, funcs.writefile({'a\n\n\nb'}, fname, 'bS')) + eq('a\0\0\0b', read_file(fname)) + end) + it('correctly overwrites file', function() eq(0, funcs.writefile({'\na\nb\n'}, fname, 'b')) eq('\0a\0b\0', read_file(fname)) @@ -115,6 +122,8 @@ describe('writefile()', function() eq('\nE729: using Funcref as a String', redir_exec(('call writefile(%s)'):format(args:format('function("tr")')))) end + eq('\nE5060: Unknown flag: «»', + redir_exec(('call writefile([], "%s", "bs«»")'):format(fname))) eq('TEST', read_file(fname)) end) diff --git a/test/functional/ex_cmds/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua index 30753c34ac..e3b4a1c504 100644 --- a/test/functional/ex_cmds/dict_notifications_spec.lua +++ b/test/functional/ex_cmds/dict_notifications_spec.lua @@ -9,7 +9,7 @@ local eval = helpers.eval describe('dictionary change notifications', function() local channel - setup(function() + before_each(function() clear() channel = nvim('get_api_info')[1] nvim('set_var', 'channel', channel) @@ -18,19 +18,15 @@ describe('dictionary change notifications', function() -- the same set of tests are applied to top-level dictionaries(g:, b:, w: and -- t:) and a dictionary variable, so we generate them in the following -- function. - local function gentests(dict_expr, dict_expr_suffix, dict_init) - if not dict_expr_suffix then - dict_expr_suffix = '' - end - + local function gentests(dict_expr, dict_init) local function update(opval, key) if not key then key = 'watched' end if opval == '' then - nvim('command', "unlet "..dict_expr..dict_expr_suffix..key) + command(('unlet %s[\'%s\']'):format(dict_expr, key)) else - nvim('command', "let "..dict_expr..dict_expr_suffix..key.." "..opval) + command(('let %s[\'%s\'] %s'):format(dict_expr, key, opval)) end end @@ -48,9 +44,9 @@ describe('dictionary change notifications', function() eq({'notification', 'values', {key, vals}}, next_msg()) end - describe('watcher', function() + describe(dict_expr .. ' watcher', function() if dict_init then - setup(function() + before_each(function() source(dict_init) end) end @@ -58,7 +54,7 @@ describe('dictionary change notifications', function() before_each(function() source([[ function! g:Changed(dict, key, value) - if a:dict != ]]..dict_expr..[[ | + if a:dict isnot ]]..dict_expr..[[ | throw 'invalid dict' endif call rpcnotify(g:channel, 'values', a:key, a:value) @@ -143,6 +139,32 @@ describe('dictionary change notifications', function() ]]) end) + it('is triggered for empty keys', function() + command([[ + call dictwatcheradd(]]..dict_expr..[[, "", "g:Changed") + ]]) + update('= 1', '') + verify_value({new = 1}, '') + update('= 2', '') + verify_value({old = 1, new = 2}, '') + command([[ + call dictwatcherdel(]]..dict_expr..[[, "", "g:Changed") + ]]) + end) + + it('is triggered for empty keys when using catch-all *', function() + command([[ + call dictwatcheradd(]]..dict_expr..[[, "*", "g:Changed") + ]]) + update('= 1', '') + verify_value({new = 1}, '') + update('= 2', '') + verify_value({old = 1, new = 2}, '') + command([[ + call dictwatcherdel(]]..dict_expr..[[, "*", "g:Changed") + ]]) + end) + -- test a sequence of updates of different types to ensure proper memory -- management(with ASAN) local function test_updates(tests) @@ -190,10 +212,10 @@ describe('dictionary change notifications', function() gentests('b:') gentests('w:') gentests('t:') - gentests('g:dict_var', '.', 'let g:dict_var = {}') + gentests('g:dict_var', 'let g:dict_var = {}') describe('multiple watchers on the same dict/key', function() - setup(function() + before_each(function() source([[ function! g:Watcher1(dict, key, value) call rpcnotify(g:channel, '1', a:key, a:value) @@ -213,13 +235,37 @@ describe('dictionary change notifications', function() end) it('only removes watchers that fully match dict, key and callback', function() + nvim('command', 'let g:key = "value"') + eq({'notification', '1', {'key', {new = 'value'}}}, next_msg()) + eq({'notification', '2', {'key', {new = 'value'}}}, next_msg()) nvim('command', 'call dictwatcherdel(g:, "key", "g:Watcher1")') nvim('command', 'let g:key = "v2"') eq({'notification', '2', {'key', {old = 'value', new = 'v2'}}}, next_msg()) end) end) + it('errors out when adding to v:_null_dict', function() + command([[ + function! g:Watcher1(dict, key, value) + call rpcnotify(g:channel, '1', a:key, a:value) + endfunction + ]]) + eq('Vim(call):E46: Cannot change read-only variable "dictwatcheradd() argument"', + exc_exec('call dictwatcheradd(v:_null_dict, "x", "g:Watcher1")')) + end) + describe('errors', function() + before_each(function() + source([[ + function! g:Watcher1(dict, key, value) + call rpcnotify(g:channel, '1', a:key, a:value) + endfunction + function! g:Watcher2(dict, key, value) + call rpcnotify(g:channel, '2', a:key, a:value) + endfunction + ]]) + end) + -- WARNING: This suite depends on the above tests it('fails to remove if no watcher with matching callback is found', function() eq("Vim(call):Couldn't find a watcher matching key and callback", @@ -236,15 +282,24 @@ describe('dictionary change notifications', function() command('call dictwatcherdel(g:, "key", "g:InvalidCb")') end) - it('fails with empty keys', function() - eq("Vim(call):E713: Cannot use empty key for Dictionary", - exc_exec('call dictwatcheradd(g:, "", "g:Watcher1")')) - eq("Vim(call):E713: Cannot use empty key for Dictionary", - exc_exec('call dictwatcherdel(g:, "", "g:Watcher1")')) + it('fails to remove watcher from v:_null_dict', function() + eq("Vim(call):Couldn't find a watcher matching key and callback", + exc_exec('call dictwatcherdel(v:_null_dict, "x", "g:Watcher2")')) end) + --[[ + [ it("fails to add/remove if the callback doesn't exist", function() + [ eq("Vim(call):Function g:InvalidCb doesn't exist", + [ exc_exec('call dictwatcheradd(g:, "key", "g:InvalidCb")')) + [ eq("Vim(call):Function g:InvalidCb doesn't exist", + [ exc_exec('call dictwatcherdel(g:, "key", "g:InvalidCb")')) + [ end) + ]] + it('does not fail to replace a watcher function', function() source([[ + let g:key = 'v2' + call dictwatcheradd(g:, "key", "g:Watcher2") function! g:ReplaceWatcher2() function! g:Watcher2(dict, key, value) call rpcnotify(g:channel, '2b', a:key, a:value) diff --git a/test/functional/ex_cmds/quickfix_commands_spec.lua b/test/functional/ex_cmds/quickfix_commands_spec.lua new file mode 100644 index 0000000000..5ab34db3fb --- /dev/null +++ b/test/functional/ex_cmds/quickfix_commands_spec.lua @@ -0,0 +1,83 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local clear = helpers.clear +local funcs = helpers.funcs +local command = helpers.command +local exc_exec = helpers.exc_exec +local write_file = helpers.write_file +local curbufmeths = helpers.curbufmeths + +local file_base = 'Xtest-functional-ex_cmds-quickfix_commands' + +before_each(clear) + +for _, c in ipairs({'l', 'c'}) do + local file = ('%s.%s'):format(file_base, c) + local filecmd = c .. 'file' + local getfcmd = c .. 'getfile' + local addfcmd = c .. 'addfile' + local getlist = (c == 'c') and funcs.getqflist or ( + function() return funcs.getloclist(0) end) + + describe((':%s*file commands'):format(c), function() + before_each(function() + write_file(file, ([[ + %s-1.res:700:10:Line 700 + %s-2.res:800:15:Line 800 + ]]):format(file, file)) + end) + after_each(function() + os.remove(file) + end) + + it('work', function() + command(('%s %s'):format(filecmd, file)) + -- Second line of each entry (i.e. `nr=-1, …`) was obtained from actual + -- results. First line (i.e. `{lnum=…`) was obtained from legacy test. + local list = { + {lnum=700, col=10, text='Line 700', + nr=-1, bufnr=2, valid=1, pattern='', vcol=0, ['type']=''}, + {lnum=800, col=15, text='Line 800', + nr=-1, bufnr=3, valid=1, pattern='', vcol=0, ['type']=''}, + } + eq(list, getlist()) + eq(('%s-1.res'):format(file), funcs.bufname(list[1].bufnr)) + eq(('%s-2.res'):format(file), funcs.bufname(list[2].bufnr)) + + -- Run cfile/lfile from a modified buffer + command('enew!') + curbufmeths.set_lines(1, 1, true, {'Quickfix'}) + eq(('Vim(%s):E37: No write since last change (add ! to override)'):format( + filecmd), + exc_exec(('%s %s'):format(filecmd, file))) + + write_file(file, ([[ + %s-3.res:900:30:Line 900 + ]]):format(file)) + command(('%s %s'):format(addfcmd, file)) + list[#list + 1] = { + lnum=900, col=30, text='Line 900', + nr=-1, bufnr=5, valid=1, pattern='', vcol=0, ['type']='', + } + eq(list, getlist()) + eq(('%s-3.res'):format(file), funcs.bufname(list[3].bufnr)) + + write_file(file, ([[ + %s-1.res:222:77:Line 222 + %s-2.res:333:88:Line 333 + ]]):format(file, file)) + command('enew!') + command(('%s %s'):format(getfcmd, file)) + list = { + {lnum=222, col=77, text='Line 222', + nr=-1, bufnr=2, valid=1, pattern='', vcol=0, ['type']=''}, + {lnum=333, col=88, text='Line 333', + nr=-1, bufnr=3, valid=1, pattern='', vcol=0, ['type']=''}, + } + eq(list, getlist()) + eq(('%s-1.res'):format(file), funcs.bufname(list[1].bufnr)) + eq(('%s-2.res'):format(file), funcs.bufname(list[2].bufnr)) + end) + end) +end diff --git a/test/functional/ex_cmds/write_spec.lua b/test/functional/ex_cmds/write_spec.lua index 4ac9f312ef..9c2687971e 100644 --- a/test/functional/ex_cmds/write_spec.lua +++ b/test/functional/ex_cmds/write_spec.lua @@ -1,15 +1,28 @@ local helpers = require('test.functional.helpers')(after_each) +local lfs = require('lfs') local eq, eval, clear, write_file, execute, source, insert = helpers.eq, helpers.eval, helpers.clear, helpers.write_file, helpers.execute, helpers.source, helpers.insert +local redir_exec = helpers.redir_exec +local exc_exec = helpers.exc_exec +local command = helpers.command +local funcs = helpers.funcs +local meths = helpers.meths if helpers.pending_win32(pending) then return end +local fname = 'Xtest-functional-ex_cmds-write' +local fname_bak = fname .. '~' +local fname_broken = fname_bak .. 'broken' + describe(':write', function() local function cleanup() os.remove('test_bkc_file.txt') os.remove('test_bkc_link.txt') os.remove('test_fifo') + os.remove(fname) + os.remove(fname_bak) + os.remove(fname_broken) end before_each(function() clear() @@ -63,4 +76,34 @@ describe(':write', function() eq(text.."\n", fifo:read("*all")) fifo:close() end) + + it('errors out correctly', function() + command('let $HOME=""') + eq(funcs.fnamemodify('.', ':p:h'), funcs.fnamemodify('.', ':p:h:~')) + -- Message from check_overwrite + eq(('\nE17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'), + redir_exec('write .')) + meths.set_option('writeany', true) + -- Message from buf_write + eq(('\nE502: "." is a directory'), + redir_exec('write .')) + funcs.mkdir(fname_bak) + meths.set_option('backupdir', '.') + meths.set_option('backup', true) + write_file(fname, 'content0') + eq(0, exc_exec('edit ' .. fname)) + funcs.setline(1, 'TTY') + eq('Vim(write):E510: Can\'t make backup file (add ! to override)', + exc_exec('write')) + meths.set_option('backup', false) + funcs.setfperm(fname, 'r--------') + eq('Vim(write):E505: "Xtest-functional-ex_cmds-write" is read-only (add ! to override)', + exc_exec('write')) + os.remove(fname) + os.remove(fname_bak) + write_file(fname_bak, 'TTYX') + lfs.link(fname_bak .. ('/xxxxx'):rep(20), fname, true) + eq('Vim(write):E166: Can\'t open linked file for writing', + exc_exec('write!')) + end) end) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 13a0cff137..335cf3c3ff 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -16,6 +16,7 @@ local eq = global_helpers.eq local ok = global_helpers.ok local map = global_helpers.map local filter = global_helpers.filter +local dedent = global_helpers.dedent local start_dir = lfs.currentdir() -- XXX: NVIM_PROG takes precedence, QuickBuild sets it. @@ -23,7 +24,7 @@ local nvim_prog = os.getenv('NVIM_PROG') or os.getenv('NVIM_PRG') or 'build/bin/ -- Default settings for the test session. local nvim_set = 'set shortmess+=I background=light noswapfile noautoindent' ..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.' - ..' belloff= noshowcmd noruler' + ..' belloff= noshowcmd noruler nomore' local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N', '--cmd', nvim_set, '--embed'} @@ -191,28 +192,6 @@ local function nvim_feed(input) end end -local function dedent(str) - -- find minimum common indent across lines - local indent = nil - for line in str:gmatch('[^\n]+') do - local line_indent = line:match('^%s+') or '' - if indent == nil or #line_indent < #indent then - indent = line_indent - end - end - if indent == nil or #indent == 0 then - -- no minimum common indent - return str - end - -- create a pattern for the indent - indent = indent:gsub('%s', '[ \t]') - -- strip it from the first line - str = str:gsub('^'..indent, '') - -- strip it from the remaining lines - str = str:gsub('[\n]'..indent, '\n') - return str -end - local function feed(...) for _, v in ipairs({...}) do nvim_feed(dedent(v)) @@ -570,6 +549,10 @@ local curbufmeths = create_callindex(curbuf) local curwinmeths = create_callindex(curwin) local curtabmeths = create_callindex(curtab) +local function get_pathsep() + return funcs.fnamemodify('.', ':p'):sub(-1) +end + local M = { prepend_argv = prepend_argv, clear = clear, @@ -635,6 +618,7 @@ local M = { tmpname = tmpname, meth_pcall = meth_pcall, NIL = mpack.NIL, + get_pathsep = get_pathsep, } return function(after_each) diff --git a/test/functional/legacy/051_highlight_spec.lua b/test/functional/legacy/051_highlight_spec.lua index ef392d8c67..d4d9b7d997 100644 --- a/test/functional/legacy/051_highlight_spec.lua +++ b/test/functional/legacy/051_highlight_spec.lua @@ -16,7 +16,7 @@ describe(':highlight', function() local screen = Screen.new(35, 10) screen:attach() -- Basic test if ":highlight" doesn't crash - execute('highlight') + execute('set more', 'highlight') -- FIXME(tarruda): We need to be sure the prompt is displayed before -- continuing, or risk a race condition where some of the following input -- is discarded resulting in test failure diff --git a/test/functional/legacy/062_tab_pages_spec.lua b/test/functional/legacy/062_tab_pages_spec.lua index d5b10b160e..71a0a77354 100644 --- a/test/functional/legacy/062_tab_pages_spec.lua +++ b/test/functional/legacy/062_tab_pages_spec.lua @@ -99,10 +99,6 @@ describe('tab pages', function() eq(7, eval('tabpagenr()')) execute('tabmove') eq(10, eval('tabpagenr()')) - execute('tabmove -20') - eq(1, eval('tabpagenr()')) - execute('tabmove +20') - eq(10, eval('tabpagenr()')) execute('0tabmove') eq(1, eval('tabpagenr()')) execute('$tabmove') @@ -172,7 +168,7 @@ describe('tab pages', function() C tabnext 1 autocmd TabDestructive TabEnter * nested \ :C tabnext 2 | C tabclose 3 - C tabnext 3 + C tabnext 2 let g:r+=[tabpagenr().'/'.tabpagenr('$')] endfunction call Test() @@ -233,22 +229,14 @@ describe('tab pages', function() WinEnter TabEnter BufEnter - === tabnext 3 === - BufLeave - WinLeave - TabLeave - WinEnter - TabEnter === tabnext 2 === - BufLeave WinLeave TabLeave WinEnter TabEnter === tabnext 2 === === tabclose 3 === - BufEnter - === tabclose 3 === 2/2]]) + eq(2, eval("tabpagenr('$')")) end) end) diff --git a/test/functional/legacy/063_match_and_matchadd_spec.lua b/test/functional/legacy/063_match_and_matchadd_spec.lua index 298e0a31ea..5818bb6b3a 100644 --- a/test/functional/legacy/063_match_and_matchadd_spec.lua +++ b/test/functional/legacy/063_match_and_matchadd_spec.lua @@ -97,11 +97,10 @@ describe('063: Test for ":match", "matchadd()" and related functions', function( -- Check that "setmatches()" will not add two matches with the same ID. The -- expected behaviour (for now) is to add the first match but not the - -- second and to return 0 (even though it is a matter of debate whether - -- this can be considered successful behaviour). + -- second and to return -1. execute("let r1 = setmatches([{'group': 'MyGroup1', 'pattern': 'TODO', 'priority': 10, 'id': 1}, {'group': 'MyGroup2', 'pattern': 'FIXME', 'priority': 10, 'id': 1}])") feed("<cr>") - eq(0, eval("r1")) + eq(-1, eval("r1")) eq({{group = 'MyGroup1', pattern = 'TODO', priority = 10, id = 1}}, eval('getmatches()')) -- Check that "setmatches()" returns 0 if successful and otherwise -1. diff --git a/test/functional/legacy/arglist_spec.lua b/test/functional/legacy/arglist_spec.lua index f5e3522972..b9075620dc 100644 --- a/test/functional/legacy/arglist_spec.lua +++ b/test/functional/legacy/arglist_spec.lua @@ -269,4 +269,33 @@ describe('argument list commands', function() eq(0, eval('argidx()')) execute('%argd') end) + + + it('test for autocommand that redefines the argument list, when doing ":all"', function() + execute('autocmd BufReadPost Xxx2 next Xxx2 Xxx1') + execute("call writefile(['test file Xxx1'], 'Xxx1')") + execute("call writefile(['test file Xxx2'], 'Xxx2')") + execute("call writefile(['test file Xxx3'], 'Xxx3')") + + execute('new') + -- redefine arglist; go to Xxx1 + execute('next! Xxx1 Xxx2 Xxx3') + -- open window for all args + execute('all') + eq('test file Xxx1', eval('getline(1)')) + execute('wincmd w') + execute('wincmd w') + eq('test file Xxx1', eval('getline(1)')) + -- should now be in Xxx2 + execute('rewind') + eq('test file Xxx2', eval('getline(1)')) + + execute('autocmd! BufReadPost Xxx2') + execute('enew! | only') + execute("call delete('Xxx1')") + execute("call delete('Xxx2')") + execute("call delete('Xxx3')") + execute('argdelete Xxx*') + execute('bwipe! Xxx1 Xxx2 Xxx3') + end) end) diff --git a/test/functional/normal/fold_spec.lua b/test/functional/normal/fold_spec.lua index a2a2a35a8b..fc055c4e7a 100644 --- a/test/functional/normal/fold_spec.lua +++ b/test/functional/normal/fold_spec.lua @@ -5,9 +5,16 @@ local insert = helpers.insert local feed = helpers.feed local expect = helpers.expect local execute = helpers.execute +local funcs = helpers.funcs +local foldlevel = funcs.foldlevel +local foldclosedend = funcs.foldclosedend +local eq = helpers.eq describe('Folds', function() + local tempfname = 'Xtest-fold.txt' clear() + before_each(function() execute('enew!') end) + after_each(function() os.remove(tempfname) end) it('manual folding adjusts with filter', function() insert([[ 1 @@ -44,4 +51,293 @@ describe('Folds', function() 8 9]]) end) + describe('adjusting folds after :move', function() + local function manually_fold_indent() + -- setting foldmethod twice is a trick to get vim to set the folds for me + execute('set foldmethod=indent', 'set foldmethod=manual') + -- Ensure that all folds will get closed (makes it easier to test the + -- length of folds). + execute('set foldminlines=0') + -- Start with all folds open (so :move ranges aren't affected by closed + -- folds). + execute('%foldopen!') + end + + local function get_folds() + local rettab = {} + for i = 1, funcs.line('$') do + table.insert(rettab, foldlevel(i)) + end + return rettab + end + + local function test_move_indent(insert_string, move_command) + -- This test is easy because we just need to ensure that the resulting + -- fold is the same as calculated when creating folds from scratch. + insert(insert_string) + execute(move_command) + local after_move_folds = get_folds() + -- Doesn't change anything, but does call foldUpdateAll() + execute('set foldminlines=0') + eq(after_move_folds, get_folds()) + -- Set up the buffer with insert_string for the manual fold testing. + execute('enew!') + insert(insert_string) + manually_fold_indent() + execute(move_command) + end + + it('neither closes nor corrupts folds', function() + test_move_indent([[ +a + a + a + a + a + a +a + a + a + a + a + a +a + a + a + a + a + a]], '7,12m0') + expect([[ +a + a + a + a + a + a +a + a + a + a + a + a +a + a + a + a + a + a]]) + -- lines are not closed, folds are correct + for i = 1,funcs.line('$') do + eq(-1, funcs.foldclosed(i)) + if i == 1 or i == 7 or i == 13 then + eq(0, foldlevel(i)) + elseif i == 4 then + eq(2, foldlevel(i)) + else + eq(1, foldlevel(i)) + end + end + -- folds are not corrupted + feed('zM') + eq(6, foldclosedend(2)) + eq(12, foldclosedend(8)) + eq(18, foldclosedend(14)) + end) + it("doesn't split a fold when the move is within it", function() + test_move_indent([[ +a + a + a + a + a + a + a + a + a +a]], '5m6') + eq({0, 1, 1, 2, 2, 2, 2, 1, 1, 0}, get_folds()) + end) + it('truncates folds that end in the moved range', function() + test_move_indent([[ +a + a + a + a + a +a +a]], '4,5m6') + eq({0, 1, 2, 0, 0, 0, 0}, get_folds()) + end) + it('moves folds that start between moved range and destination', function() + test_move_indent([[ +a + a + a + a + a +a +a + a + a + a +a +a + a]], '3,4m$') + eq({0, 1, 1, 0, 0, 1, 2, 1, 0, 0, 1, 0, 0}, get_folds()) + end) + it('does not affect folds outside changed lines', function() + test_move_indent([[ + a + a + a +a +a +a + a + a + a]], '4m5') + eq({1, 1, 1, 0, 0, 0, 1, 1, 1}, get_folds()) + end) + it('moves and truncates folds that start in moved range', function() + test_move_indent([[ +a + a + a + a + a +a +a +a +a +a]], '1,3m7') + eq({0, 0, 0, 0, 0, 1, 2, 0, 0, 0}, get_folds()) + end) + it('breaks a fold when moving text into it', function() + test_move_indent([[ +a + a + a + a + a +a +a]], '$m4') + eq({0, 1, 2, 2, 0, 0, 0}, get_folds()) + end) + it('adjusts correctly when moving a range backwards', function() + test_move_indent([[ +a + a + a + a +a]], '2,3m0') + eq({1, 2, 0, 0, 0}, get_folds()) + end) + end) + it('updates correctly on :read', function() + -- luacheck: ignore 621 + helpers.write_file(tempfname, [[ + a + + + a]]) + insert([[ + a + a + a + a + ]]) + execute('set foldmethod=indent', '2', '%foldopen') + execute('read ' .. tempfname) + -- Just to check we have the correct file text. + expect([[ + a + a + a + + + a + a + a + ]]) + for i = 1,2 do + eq(1, funcs.foldlevel(i)) + end + for i = 3,5 do + eq(0, funcs.foldlevel(i)) + end + for i = 6,8 do + eq(1, funcs.foldlevel(i)) + end + end) + it('combines folds when removing separating space', function() + -- luacheck: ignore 621 + insert([[ + a + a + a + a + a + a + a + a + ]]) + execute('set foldmethod=indent', '3,5d') + eq(5, funcs.foldclosedend(1)) + end) + it("doesn't combine folds that have a specified end", function() + insert([[ + {{{ + }}} + + + + {{{ + + }}} + ]]) + execute('set foldmethod=marker', '3,5d', '%foldclose') + eq(2, funcs.foldclosedend(1)) + end) + it('splits folds according to >N and <N with foldexpr', function() + helpers.source([[ + function TestFoldExpr(lnum) + let thisline = getline(a:lnum) + if thisline == 'a' + return 1 + elseif thisline == 'b' + return 0 + elseif thisline == 'c' + return '<1' + elseif thisline == 'd' + return '>1' + endif + return 0 + endfunction + ]]) + helpers.write_file(tempfname, [[ + b + b + a + a + d + a + a + c]]) + insert([[ + a + a + a + a + a + a + ]]) + execute('set foldmethod=expr', 'set foldexpr=TestFoldExpr(v:lnum)', '2', 'foldopen') + execute('read ' .. tempfname, '%foldclose') + eq(2, funcs.foldclosedend(1)) + eq(0, funcs.foldlevel(3)) + eq(0, funcs.foldlevel(4)) + eq(6, funcs.foldclosedend(5)) + eq(10, funcs.foldclosedend(7)) + eq(14, funcs.foldclosedend(11)) + end) end) diff --git a/test/functional/options/pastetoggle_spec.lua b/test/functional/options/pastetoggle_spec.lua new file mode 100644 index 0000000000..e449df31f5 --- /dev/null +++ b/test/functional/options/pastetoggle_spec.lua @@ -0,0 +1,37 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local feed = helpers.feed +local execute = helpers.execute +local eq = helpers.eq +local eval = helpers.eval +local sleep = helpers.sleep + +describe("'pastetoggle' option", function() + before_each(function() + clear() + execute('set nopaste') + execute('set pastetoggle=a') + end) + it("toggles 'paste'", function() + eq(eval('&paste'), 0) + feed('a') + -- Need another key so that the vgetorpeek() function returns. + feed('j') + eq(eval('&paste'), 1) + end) + it("multiple key 'pastetoggle' is waited for", function() + eq(eval('&paste'), 0) + local pastetoggle = 'lllll' + execute('set pastetoggle=' .. pastetoggle) + execute('set timeoutlen=1', 'set ttimoutlen=10000') + feed(pastetoggle:sub(0, 2)) + -- sleep() for long enough that vgetorpeek() is gotten into, but short + -- enough that ttimeoutlen is not reached. + sleep(200) + feed(pastetoggle:sub(3, -1)) + -- Need another key so that the vgetorpeek() function returns. + feed('j') + eq(eval('&paste'), 1) + end) +end) diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index 32598fc399..ca44026852 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -180,8 +180,7 @@ describe('ShaDa support code', function() nvim_command('undo') nvim_command('set shada+=%') nvim_command('wshada! ' .. shada_fname) - local readme_fname = paths.test_source_path .. '/README.md' - readme_fname = helpers.eval( 'resolve("' .. readme_fname .. '")' ) + local readme_fname = funcs.resolve(paths.test_source_path) .. '/README.md' eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) nvim_command('set shada+=r~') nvim_command('wshada! ' .. shada_fname) @@ -189,7 +188,8 @@ describe('ShaDa support code', function() nvim_command('set shada-=r~') nvim_command('wshada! ' .. shada_fname) eq({[7]=1, [8]=2, [9]=1, [10]=4, [11]=1}, find_file(readme_fname)) - nvim_command('set shada+=r' .. paths.test_source_path) + nvim_command('set shada+=r' .. funcs.escape( + funcs.escape(paths.test_source_path, '$~'), ' "\\,')) nvim_command('wshada! ' .. shada_fname) eq({}, find_file(readme_fname)) end) diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index d990f92c3a..84f14585fa 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -60,16 +60,17 @@ describe('terminal cursor', function() ]]) end) - pending('is positioned correctly when focused', function() + it('is positioned correctly when focused', function() feed('i') + helpers.wait() screen:expect([[ - 1 tty ready | - 2 {1: } | - 3 | - 4 | - 5 | - 6 | - -- TERMINAL -- | + {7: 1 }tty ready | + {7: 2 }{1: } | + {7: 3 } | + {7: 4 } | + {7: 5 } | + {7: 6 } | + {3:-- TERMINAL --} | ]]) end) end) diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 81649f2bde..4ead288a19 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -8,6 +8,7 @@ local command = helpers.command local wait = helpers.wait local retry = helpers.retry local curbufmeths = helpers.curbufmeths +local nvim = helpers.nvim local feed_data = thelpers.feed_data if helpers.pending_win32(pending) then return end @@ -368,6 +369,12 @@ describe("'scrollback' option", function() clear() end) + local function set_fake_shell() + -- shell-test.c is a fake shell that prints its arguments and exits. + nvim('set_option', 'shell', nvim_dir..'/shell-test') + nvim('set_option', 'shellcmdflag', 'EXE') + end + local function expect_lines(expected, epsilon) local ep = epsilon and epsilon or 0 local actual = eval("line('$')") @@ -421,12 +428,13 @@ describe("'scrollback' option", function() screen:detach() end) - it('defaults to 1000', function() - execute('terminal') + it('defaults to 1000 in terminal buffers', function() + set_fake_shell() + command('terminal') eq(1000, curbufmeths.get_option('scrollback')) end) - it('error if set to invalid values', function() + it('error if set to invalid value', function() local status, rv = pcall(command, 'set scrollback=-2') eq(false, status) -- assert failure eq('E474:', string.match(rv, "E%d*:")) @@ -437,15 +445,32 @@ describe("'scrollback' option", function() end) it('defaults to -1 on normal buffers', function() - execute('new') + command('new') eq(-1, curbufmeths.get_option('scrollback')) end) - it('error if set on a normal buffer', function() + it(':setlocal in a normal buffer is an error', function() command('new') - execute('set scrollback=42') + execute('setlocal scrollback=42') feed('<CR>') eq('E474:', string.match(eval("v:errmsg"), "E%d*:")) + eq(-1, curbufmeths.get_option('scrollback')) + end) + + it(':set updates local value and global default', function() + set_fake_shell() + command('set scrollback=42') -- set global and (attempt) local + eq(-1, curbufmeths.get_option('scrollback')) -- normal buffer: -1 + command('terminal') + eq(42, curbufmeths.get_option('scrollback')) -- inherits global default + command('setlocal scrollback=99') + eq(99, curbufmeths.get_option('scrollback')) + command('set scrollback<') -- reset to global default + eq(42, curbufmeths.get_option('scrollback')) + command('setglobal scrollback=734') -- new global default + eq(42, curbufmeths.get_option('scrollback')) -- local value did not change + command('terminal') + eq(734, curbufmeths.get_option('scrollback')) end) end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 0e5c437c28..90051b8cd5 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -322,6 +322,7 @@ describe("tui 't_Co' (terminal colors)", function() helpers.nvim_prog)) thelpers.feed_data(":echo &t_Co\n") + helpers.wait() local tline if maxcolors == 8 then tline = "~ " diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua new file mode 100644 index 0000000000..02e9422781 --- /dev/null +++ b/test/functional/ui/cursor_spec.lua @@ -0,0 +1,192 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, meths = helpers.clear, helpers.meths +local eq = helpers.eq +local command = helpers.command + +describe('ui/cursor', function() + local screen + + before_each(function() + clear() + screen = Screen.new(25, 5) + screen:attach() + end) + + after_each(function() + screen:detach() + end) + + it("'guicursor' is published as a UI event", function() + local expected_cursor_style = { + cmdline_hover = { + mouse_shape = 0, + short_name = 'e' }, + cmdline_insert = { + blinkoff = 250, + blinkon = 400, + blinkwait = 700, + cell_percentage = 25, + cursor_shape = 'vertical', + hl_id = 46, + id_lm = 47, + mouse_shape = 0, + short_name = 'ci' }, + cmdline_normal = { + blinkoff = 250, + blinkon = 400, + blinkwait = 700, + cell_percentage = 0, + cursor_shape = 'block', + hl_id = 46, + id_lm = 47, + mouse_shape = 0, + short_name = 'c' }, + cmdline_replace = { + blinkoff = 250, + blinkon = 400, + blinkwait = 700, + cell_percentage = 20, + cursor_shape = 'horizontal', + hl_id = 46, + id_lm = 47, + mouse_shape = 0, + short_name = 'cr' }, + insert = { + blinkoff = 250, + blinkon = 400, + blinkwait = 700, + cell_percentage = 25, + cursor_shape = 'vertical', + hl_id = 46, + id_lm = 47, + mouse_shape = 0, + short_name = 'i' }, + more = { + mouse_shape = 0, + short_name = 'm' }, + more_lastline = { + mouse_shape = 0, + short_name = 'ml' }, + normal = { + blinkoff = 250, + blinkon = 400, + blinkwait = 700, + cell_percentage = 0, + cursor_shape = 'block', + hl_id = 46, + id_lm = 47, + mouse_shape = 0, + short_name = 'n' }, + operator = { + blinkoff = 250, + blinkon = 400, + blinkwait = 700, + cell_percentage = 50, + cursor_shape = 'horizontal', + hl_id = 46, + id_lm = 46, + mouse_shape = 0, + short_name = 'o' }, + replace = { + blinkoff = 250, + blinkon = 400, + blinkwait = 700, + cell_percentage = 20, + cursor_shape = 'horizontal', + hl_id = 46, + id_lm = 47, + mouse_shape = 0, + short_name = 'r' }, + showmatch = { + blinkoff = 150, + blinkon = 175, + blinkwait = 175, + cell_percentage = 0, + cursor_shape = 'block', + hl_id = 46, + id_lm = 46, + short_name = 'sm' }, + statusline_drag = { + mouse_shape = 0, + short_name = 'sd' }, + statusline_hover = { + mouse_shape = 0, + short_name = 's' }, + visual = { + blinkoff = 250, + blinkon = 400, + blinkwait = 700, + cell_percentage = 0, + cursor_shape = 'block', + hl_id = 46, + id_lm = 47, + mouse_shape = 0, + short_name = 'v' }, + visual_select = { + blinkoff = 250, + blinkon = 400, + blinkwait = 700, + cell_percentage = 35, + cursor_shape = 'vertical', + hl_id = 46, + id_lm = 46, + mouse_shape = 0, + short_name = 've' }, + vsep_drag = { + mouse_shape = 0, + short_name = 'vd' }, + vsep_hover = { + mouse_shape = 0, + short_name = 'vs' } + } + + screen:expect(function() + -- Default 'guicursor' published on startup. + eq(expected_cursor_style, screen._cursor_style) + eq(true, screen._cursor_style_enabled) + eq('normal', screen.mode) + end) + + -- Event is published ONLY if the cursor style changed. + screen._cursor_style = nil + command("echo 'test'") + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + test | + ]], nil, nil, function() + eq(nil, screen._cursor_style) + end) + + -- Change the cursor style. + meths.set_option('guicursor', 'n-v-c:ver35-blinkwait171-blinkoff172-blinkon173,ve:hor35,o:ver50,i-ci:block,r-cr:hor90,sm:ver42') + screen:expect(function() + eq('vertical', screen._cursor_style.normal.cursor_shape) + eq('horizontal', screen._cursor_style.visual_select.cursor_shape) + eq('vertical', screen._cursor_style.operator.cursor_shape) + eq('block', screen._cursor_style.insert.cursor_shape) + eq('vertical', screen._cursor_style.showmatch.cursor_shape) + eq(171, screen._cursor_style.normal.blinkwait) + eq(172, screen._cursor_style.normal.blinkoff) + eq(173, screen._cursor_style.normal.blinkon) + end) + end) + + it("empty 'guicursor' sets cursor_shape=block in all modes", function() + meths.set_option('guicursor', '') + screen:expect(function() + -- Empty 'guicursor' sets enabled=false. + eq(false, screen._cursor_style_enabled) + for _, m in ipairs({ 'cmdline_insert', 'cmdline_normal', 'cmdline_replace', 'insert', + 'showmatch', 'normal', 'replace', 'visual', + 'visual_select', }) do + eq('block', screen._cursor_style[m].cursor_shape) + eq(0, screen._cursor_style[m].blinkon) + end + end) + end) + +end) diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 945b16ef92..05cf3231ea 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -200,58 +200,31 @@ describe('Default highlight groups', function() it('insert mode text', function() feed('i') + screen:try_resize(53, 4) screen:expect([[ ^ | {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| {1:-- INSERT --} | ]], {[0] = {bold=true, foreground=Screen.colors.Blue}, [1] = {bold = true}}) end) it('end of file markers', function() + screen:try_resize(53, 4) screen:expect([[ ^ | {1:~ }| {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| | ]], {[1] = {bold = true, foreground = Screen.colors.Blue}}) end) it('"wait return" text', function() + screen:try_resize(53, 4) feed(':ls<cr>') screen:expect([[ {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| :ls | 1 %a "[No Name]" line 1 | {1:Press ENTER or type command to continue}^ | @@ -259,23 +232,15 @@ describe('Default highlight groups', function() [1] = {bold = true, foreground = Screen.colors.SeaGreen}}) feed('<cr>') -- skip the "Press ENTER..." state or tests will hang end) + it('can be cleared and linked to other highlight groups', function() + screen:try_resize(53, 4) execute('highlight clear ModeMsg') feed('i') screen:expect([[ ^ | {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| -- INSERT -- | ]], {[0] = {bold=true, foreground=Screen.colors.Blue}, [1] = {bold=true}}) @@ -287,21 +252,13 @@ describe('Default highlight groups', function() ^ | {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| {1:-- INSERT --} | ]], {[0] = {bold=true, foreground=Screen.colors.Blue}, [1] = {foreground = Screen.colors.Red, background = Screen.colors.Green}}) end) + it('can be cleared by assigning NONE', function() + screen:try_resize(53, 4) execute('syn keyword TmpKeyword neovim') execute('hi link TmpKeyword ErrorMsg') insert('neovim') @@ -309,16 +266,6 @@ describe('Default highlight groups', function() {1:neovi^m} | {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| | ]], { [0] = {bold=true, foreground=Screen.colors.Blue}, @@ -330,18 +277,34 @@ describe('Default highlight groups', function() neovi^m | {0:~ }| {0:~ }| + | + ]], {[0] = {bold=true, foreground=Screen.colors.Blue}}) + end) + + it('Whitespace highlight', function() + screen:try_resize(53, 4) + execute('highlight NonText gui=NONE guifg=#FF0000') + execute('set listchars=space:.,tab:>-,trail:*,eol:¬ list') + insert(' ne \t o\tv im ') + screen:expect([[ + ne{0:.>----.}o{0:>-----}v{0:..}im{0:*^*¬} | {0:~ }| {0:~ }| + | + ]], { + [0] = {foreground=Screen.colors.Red}, + [1] = {foreground=Screen.colors.Blue}, + }) + execute('highlight Whitespace gui=NONE guifg=#0000FF') + screen:expect([[ + ne{1:.>----.}o{1:>-----}v{1:..}im{1:*^*}{0:¬} | {0:~ }| {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - {0:~ }| - | - ]], {[0] = {bold=true, foreground=Screen.colors.Blue}}) + :highlight Whitespace gui=NONE guifg=#0000FF | + ]], { + [0] = {foreground=Screen.colors.Red}, + [1] = {foreground=Screen.colors.Blue}, + }) end) end) @@ -401,7 +364,7 @@ describe('guisp (special/undercurl)', function() end) end) -describe("'cursorline' with 'listchars'", function() +describe("'listchars' highlight", function() local screen before_each(function() @@ -510,7 +473,7 @@ describe("'cursorline' with 'listchars'", function() }, }) execute('highlight clear ModeMsg') - execute('highlight SpecialKey guifg=#FF0000') + execute('highlight Whitespace guifg=#FF0000') execute('set cursorline') execute('set tabstop=8') execute('set listchars=space:.,eol:¬,tab:>-,extends:>,precedes:<,trail:* list') @@ -606,7 +569,7 @@ describe("'cursorline' with 'listchars'", function() }, }) execute('highlight clear ModeMsg') - execute('highlight SpecialKey guifg=#FF0000') + execute('highlight Whitespace guifg=#FF0000') execute('set cursorline') execute('set tabstop=8') execute('set nowrap') @@ -644,4 +607,41 @@ describe("'cursorline' with 'listchars'", function() | ]]) end) + + it("'cursorline' with :match", function() + screen:set_default_attr_ids({ + [0] = {bold=true, foreground=Screen.colors.Blue}, + [1] = {background=Screen.colors.Grey90}, + [2] = {foreground=Screen.colors.Red}, + [3] = {foreground=Screen.colors.Green1}, + }) + execute('highlight clear ModeMsg') + execute('highlight Whitespace guifg=#FF0000') + execute('highlight Error guifg=#00FF00') + execute('set nowrap') + feed('ia \t bc \t <esc>') + screen:expect([[ + a bc ^ | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + execute('set listchars=space:.,eol:¬,tab:>-,extends:>,precedes:<,trail:* list') + screen:expect([[ + a{2:.>-----.}bc{2:*>---*^*}{0:¬} | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + execute('match Error /\\s\\+$/') + screen:expect([[ + a{2:.>-----.}bc{3:*>---*^*}{0:¬} | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) end) diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index b2fbedfb5e..ecbd5642d1 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -6,7 +6,7 @@ local eq, funcs = helpers.eq, helpers.funcs if helpers.pending_win32(pending) then return end -describe('Mouse input', function() +describe('ui/mouse/input', function() local screen before_each(function() diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 54f43387dc..2f2cc85dab 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -181,6 +181,7 @@ end -- expected: Expected screen state (string). Each line represents a screen -- row. Last character of each row (typically "|") is stripped. -- Common indentation is stripped. +-- Used as `condition` if NOT a string; must be the ONLY arg then. -- attr_ids: Expected text attributes. Screen rows are transformed according -- to this table, as follows: each substring S composed of -- characters having the same attributes will be substituted by @@ -191,18 +192,23 @@ end -- any: true: Succeed if `expected` matches ANY screen line(s). -- false (default): `expected` must match screen exactly. function Screen:expect(expected, attr_ids, attr_ignore, condition, any) - -- remove the last line and dedent - expected = dedent(expected:gsub('\n[ ]+$', '')) local expected_rows = {} - for row in expected:gmatch('[^\n]+') do - -- the last character should be the screen delimiter - row = row:sub(1, #row - 1) - table.insert(expected_rows, row) - end - if not any then - assert(self._height == #expected_rows, - "Expected screen state's row count(" .. #expected_rows - .. ') differs from configured height(' .. self._height .. ') of Screen.') + if type(expected) ~= "string" then + assert(not (attr_ids or attr_ignore or condition or any)) + condition = expected + expected = nil + else + -- Remove the last line and dedent. + expected = dedent(expected:gsub('\n[ ]+$', '')) + for row in expected:gmatch('[^\n]+') do + row = row:sub(1, #row - 1) -- Last char must be the screen delimiter. + table.insert(expected_rows, row) + end + if not any then + assert(self._height == #expected_rows, + "Expected screen state's row count(" .. #expected_rows + .. ') differs from configured height(' .. self._height .. ') of Screen.') + end end local ids = attr_ids or self._default_attr_ids local ignore = attr_ignore or self._default_attr_ignore @@ -218,7 +224,9 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any) actual_rows[i] = self:_row_repr(self._rows[i], ids, ignore) end - if any then + if expected == nil then + return + elseif any then -- Search for `expected` anywhere in the screen lines. local actual_screen_str = table.concat(actual_rows, '\n') if nil == string.find(actual_screen_str, expected) then @@ -313,6 +321,8 @@ function Screen:_redraw(updates) if handler ~= nil then handler(self, unpack(update[i])) else + assert(self._on_event, + "Add Screen:_handle_XXX method or call Screen:set_on_event_handler") self._on_event(method, update[i]) end end @@ -343,6 +353,11 @@ function Screen:_handle_resize(width, height) } end +function Screen:_handle_cursor_style_set(enabled, style) + self._cursor_style_enabled = enabled + self._cursor_style = style +end + function Screen:_handle_clear() self:_clear_block(self._scroll_region.top, self._scroll_region.bot, self._scroll_region.left, self._scroll_region.right) diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index e511234e5e..21953ba294 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -73,33 +73,29 @@ describe('Screen', function() describe(':suspend', function() it('is forwarded to the UI', function() local function check() - if not screen.suspended then - return 'Screen was not suspended' - end + eq(true, screen.suspended) end execute('suspend') - screen:wait(check) + screen:expect(check) screen.suspended = false feed('<c-z>') - screen:wait(check) + screen:expect(check) end) end) describe('bell/visual bell', function() it('is forwarded to the UI', function() feed('<left>') - screen:wait(function() - if not screen.bell or screen.visual_bell then - return 'Bell was not sent' - end + screen:expect(function() + eq(true, screen.bell) + eq(false, screen.visual_bell) end) screen.bell = false execute('set visualbell') feed('<left>') - screen:wait(function() - if not screen.visual_bell or screen.bell then - return 'Visual bell was not sent' - end + screen:expect(function() + eq(true, screen.visual_bell) + eq(false, screen.bell) end) end) end) @@ -109,22 +105,16 @@ describe('Screen', function() local expected = 'test-title' execute('set titlestring='..expected) execute('set title') - screen:wait(function() - local actual = screen.title - if actual ~= expected then - return 'Expected title to be "'..expected..'" but was "'..actual..'"' - end + screen:expect(function() + eq(expected, screen.title) end) end) it('has correct default title with unnamed file', function() local expected = '[No Name] - NVIM' execute('set title') - screen:wait(function() - local actual = screen.title - if actual ~= expected then - return 'Expected title to be "'..expected..'" but was "'..actual..'"' - end + screen:expect(function() + eq(expected, screen.title) end) end) @@ -132,11 +122,8 @@ describe('Screen', function() local expected = 'myfile (/mydir) - NVIM' execute('set title') execute('file /mydir/myfile') - screen:wait(function() - local actual = screen.title - if actual ~= expected then - return 'Expected title to be "'..expected..'" but was "'..actual..'"' - end + screen:expect(function() + eq(expected, screen.title) end) end) end) @@ -146,11 +133,8 @@ describe('Screen', function() local expected = 'test-icon' execute('set iconstring='..expected) execute('set icon') - screen:wait(function() - local actual = screen.icon - if actual ~= expected then - return 'Expected title to be "'..expected..'" but was "'..actual..'"' - end + screen:expect(function() + eq(expected, screen.icon) end) end) end) diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua index cd5f4260e0..3c09d71eb7 100644 --- a/test/functional/viml/completion_spec.lua +++ b/test/functional/viml/completion_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local clear, feed = helpers.clear, helpers.feed local eval, eq, neq = helpers.eval, helpers.eq, helpers.neq local execute, source, expect = helpers.execute, helpers.source, helpers.expect +local meths = helpers.meths if helpers.pending_win32(pending) then return end @@ -814,6 +815,41 @@ describe('completion', function() end) end) + describe('with numeric items', function() + before_each(function() + source([[ + function! TestComplete() abort + call complete(1, g:_complist) + return '' + endfunction + ]]) + meths.set_option('completeopt', 'menuone,noselect') + meths.set_var('_complist', {{ + word=0, + abbr=1, + menu=2, + kind=3, + info=4, + icase=5, + dup=6, + empty=7, + }}) + end) + + it('shows correct variant as word', function() + feed('i<C-r>=TestComplete()<CR>') + screen:expect([[ + ^ | + {1:1 3 2 }{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {3:-- INSERT --} | + ]]) + end) + end) end) describe('External completion popupmenu', function() |