diff options
Diffstat (limited to 'test/functional/api/autocmd_spec.lua')
-rw-r--r-- | test/functional/api/autocmd_spec.lua | 1249 |
1 files changed, 1249 insertions, 0 deletions
diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua new file mode 100644 index 0000000000..a923f5df0e --- /dev/null +++ b/test/functional/api/autocmd_spec.lua @@ -0,0 +1,1249 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local neq = helpers.neq +local exec_lua = helpers.exec_lua +local matches = helpers.matches +local meths = helpers.meths +local source = helpers.source +local pcall_err = helpers.pcall_err + +before_each(clear) + +describe('autocmd api', function() + describe('nvim_create_autocmd', function() + it('does not allow "command" and "callback" in the same autocmd', function() + local ok, _ = pcall(meths.create_autocmd, "BufReadPost", { + pattern = "*.py,*.pyi", + command = "echo 'Should Have Errored", + callback = "not allowed", + }) + + eq(false, ok) + end) + + it('doesnt leak when you use ++once', function() + eq(1, exec_lua([[ + local count = 0 + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = function() count = count + 1 end, + once = true + }) + + vim.cmd "set filetype=txt" + vim.cmd "set filetype=python" + + return count + ]], {})) + end) + + it('allows passing buffer by key', function() + meths.set_var('called', 0) + + meths.create_autocmd("FileType", { + command = "let g:called = g:called + 1", + buffer = 0, + }) + + meths.command "set filetype=txt" + eq(1, meths.get_var('called')) + + -- switch to a new buffer + meths.command "new" + meths.command "set filetype=python" + + eq(1, meths.get_var('called')) + end) + + it('does not allow passing buffer and patterns', function() + local ok = pcall(meths.create_autocmd, "Filetype", { + command = "let g:called = g:called + 1", + buffer = 0, + pattern = "*.py", + }) + + eq(false, ok) + end) + + it('does not allow passing invalid buffers', function() + local ok, msg = pcall(meths.create_autocmd, "Filetype", { + command = "let g:called = g:called + 1", + buffer = -1, + }) + + eq(false, ok) + matches('Invalid buffer id', msg) + end) + + it('errors on non-functions for cb', function() + eq(false, pcall(exec_lua, [[ + vim.api.nvim_create_autocmd("BufReadPost", { + pattern = "*.py,*.pyi", + callback = 5, + }) + ]])) + end) + + it('allow passing pattern and <buffer> in same pattern', function() + local ok = pcall(meths.create_autocmd, "BufReadPost", { + pattern = "*.py,<buffer>", + command = "echo 'Should Not Error'" + }) + + eq(true, ok) + end) + + it('should handle multiple values as comma separated list', function() + meths.create_autocmd("BufReadPost", { + pattern = "*.py,*.pyi", + command = "echo 'Should Not Have Errored'" + }) + + -- We should have one autocmd for *.py and one for *.pyi + eq(2, #meths.get_autocmds { event = "BufReadPost" }) + end) + + it('should handle multiple values as array', function() + meths.create_autocmd("BufReadPost", { + pattern = { "*.py", "*.pyi", }, + command = "echo 'Should Not Have Errored'" + }) + + -- We should have one autocmd for *.py and one for *.pyi + eq(2, #meths.get_autocmds { event = "BufReadPost" }) + end) + + describe('desc', function() + it('can add description to one autocmd', function() + local cmd = "echo 'Should Not Have Errored'" + local desc = "Can show description" + meths.create_autocmd("BufReadPost", { + pattern = "*.py", + command = cmd, + desc = desc, + }) + + eq(desc, meths.get_autocmds { event = "BufReadPost" }[1].desc) + eq(cmd, meths.get_autocmds { event = "BufReadPost" }[1].command) + end) + + it('can add description to one autocmd that uses a callback', function() + local desc = 'Can show description' + meths.set_var('desc', desc) + + local result = exec_lua([[ + local callback = function() print 'Should Not Have Errored' end + vim.api.nvim_create_autocmd("BufReadPost", { + pattern = "*.py", + callback = callback, + desc = vim.g.desc, + }) + local aus = vim.api.nvim_get_autocmds({ event = 'BufReadPost' }) + local first = aus[1] + return { + desc = first.desc, + cbtype = type(first.callback) + } + ]]) + + eq({ desc = desc, cbtype = 'function' }, result) + end) + + it('will not add a description unless it was provided', function() + exec_lua([[ + local callback = function() print 'Should Not Have Errored' end + vim.api.nvim_create_autocmd("BufReadPost", { + pattern = "*.py", + callback = callback, + }) + ]]) + + eq(nil, meths.get_autocmds({ event = 'BufReadPost' })[1].desc) + end) + + it('can add description to multiple autocmd', function() + meths.create_autocmd("BufReadPost", { + pattern = {"*.py", "*.pyi"}, + command = "echo 'Should Not Have Errored'", + desc = "Can show description", + }) + + local aus = meths.get_autocmds { event = "BufReadPost" } + eq(2, #aus) + eq("Can show description", aus[1].desc) + eq("Can show description", aus[2].desc) + end) + end) + + pending('script and verbose settings', function() + it('marks API client', function() + meths.create_autocmd("BufReadPost", { + pattern = "*.py", + command = "echo 'Should Not Have Errored'", + desc = "Can show description", + }) + + local aus = meths.get_autocmds { event = "BufReadPost" } + eq(1, #aus, aus) + end) + end) + + it('removes an autocommand if the callback returns true', function() + meths.set_var("some_condition", false) + + exec_lua [[ + vim.api.nvim_create_autocmd("User", { + pattern = "Test", + desc = "A test autocommand", + callback = function() + return vim.g.some_condition + end, + }) + ]] + + meths.exec_autocmds("User", {pattern = "Test"}) + + local aus = meths.get_autocmds({ event = 'User', pattern = 'Test' }) + local first = aus[1] + eq(first.id, 1) + + meths.set_var("some_condition", true) + meths.exec_autocmds("User", {pattern = "Test"}) + eq({}, meths.get_autocmds({event = "User", pattern = "Test"})) + end) + + it('receives an args table', function() + local res = exec_lua [[ + local group_id = vim.api.nvim_create_augroup("TestGroup", {}) + local autocmd_id = vim.api.nvim_create_autocmd("User", { + group = "TestGroup", + pattern = "Te*", + callback = function(args) + vim.g.autocmd_args = args + end, + }) + + return {group_id, autocmd_id} + ]] + + meths.exec_autocmds("User", {pattern = "Test pattern"}) + eq({ + id = res[2], + group = res[1], + event = "User", + match = "Test pattern", + file = "Test pattern", + buf = 1, + }, meths.get_var("autocmd_args")) + + -- Test without a group + res = exec_lua [[ + local autocmd_id = vim.api.nvim_create_autocmd("User", { + pattern = "*", + callback = function(args) + vim.g.autocmd_args = args + end, + }) + + return {autocmd_id} + ]] + + meths.exec_autocmds("User", {pattern = "some_pat"}) + eq({ + id = res[1], + group = nil, + event = "User", + match = "some_pat", + file = "some_pat", + buf = 1, + }, meths.get_var("autocmd_args")) + + end) + + it('can receive arbitrary data', function() + local function test(data) + eq(data, exec_lua([[ + local input = ... + local output + vim.api.nvim_create_autocmd("User", { + pattern = "Test", + callback = function(args) + output = args.data + end, + }) + + vim.api.nvim_exec_autocmds("User", { + pattern = "Test", + data = input, + }) + + return output + ]], data)) + end + + test("Hello") + test(42) + test(true) + test({ "list" }) + test({ foo = "bar" }) + end) + end) + + describe('nvim_get_autocmds', function() + describe('events', function() + it('should return one autocmd when there is only one for an event', function() + command [[au! InsertEnter]] + command [[au InsertEnter * :echo "1"]] + + local aus = meths.get_autocmds { event = "InsertEnter" } + eq(1, #aus) + end) + + it('should return two autocmds when there are two for an event', function() + command [[au! InsertEnter]] + command [[au InsertEnter * :echo "1"]] + command [[au InsertEnter * :echo "2"]] + + local aus = meths.get_autocmds { event = "InsertEnter" } + eq(2, #aus) + end) + + it('should return the same thing if you use string or list', function() + command [[au! InsertEnter]] + command [[au InsertEnter * :echo "1"]] + command [[au InsertEnter * :echo "2"]] + + local string_aus = meths.get_autocmds { event = "InsertEnter" } + local array_aus = meths.get_autocmds { event = { "InsertEnter" } } + eq(string_aus, array_aus) + end) + + it('should return two autocmds when there are two for an event', function() + command [[au! InsertEnter]] + command [[au! InsertLeave]] + command [[au InsertEnter * :echo "1"]] + command [[au InsertEnter * :echo "2"]] + + local aus = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + eq(2, #aus) + end) + + it('should return different IDs for different autocmds', function() + command [[au! InsertEnter]] + command [[au! InsertLeave]] + command [[au InsertEnter * :echo "1"]] + source [[ + call nvim_create_autocmd("InsertLeave", #{ + \ command: ":echo 2", + \ }) + ]] + + local aus = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + local first = aus[1] + eq(first.id, nil) + + -- TODO: Maybe don't have this number, just assert it's not nil + local second = aus[2] + neq(second.id, nil) + + meths.del_autocmd(second.id) + local new_aus = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + eq(1, #new_aus) + eq(first, new_aus[1]) + end) + + it('should return event name', function() + command [[au! InsertEnter]] + command [[au InsertEnter * :echo "1"]] + + local aus = meths.get_autocmds { event = "InsertEnter" } + eq({ { buflocal = false, command = ':echo "1"', event = "InsertEnter", once = false, pattern = "*" } }, aus) + end) + + it('should work with buffer numbers', function() + command [[new]] + command [[au! InsertEnter]] + command [[au InsertEnter <buffer=1> :echo "1"]] + command [[au InsertEnter <buffer=2> :echo "2"]] + + local aus = meths.get_autocmds { event = "InsertEnter", buffer = 0 } + eq({{ + buffer = 2, + buflocal = true, + command = ':echo "2"', + event = 'InsertEnter', + once = false, + pattern = '<buffer=2>', + }}, aus) + + aus = meths.get_autocmds { event = "InsertEnter", buffer = 1 } + eq({{ + buffer = 1, + buflocal = true, + command = ':echo "1"', + event = "InsertEnter", + once = false, + pattern = "<buffer=1>", + }}, aus) + + aus = meths.get_autocmds { event = "InsertEnter", buffer = { 1, 2 } } + eq({{ + buffer = 1, + buflocal = true, + command = ':echo "1"', + event = "InsertEnter", + once = false, + pattern = "<buffer=1>", + }, { + buffer = 2, + buflocal = true, + command = ':echo "2"', + event = "InsertEnter", + once = false, + pattern = "<buffer=2>", + }}, aus) + + eq("Invalid value for 'buffer': must be an integer or array of integers", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = "foo" })) + eq("Invalid value for 'buffer': must be an integer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { "foo", 42 } })) + eq("Invalid buffer id: 42", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { 42 } })) + + local bufs = {} + for _ = 1, 257 do + table.insert(bufs, meths.create_buf(true, false)) + end + + eq("Too many buffers. Please limit yourself to 256 or fewer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = bufs })) + end) + + it('should return autocmds when group is specified by id', function() + local auid = meths.create_augroup("nvim_test_augroup", { clear = true }) + meths.create_autocmd("FileType", { group = auid, command = 'echo "1"' }) + meths.create_autocmd("FileType", { group = auid, command = 'echo "2"' }) + + local aus = meths.get_autocmds { group = auid } + eq(2, #aus) + + local aus2 = meths.get_autocmds { group = auid, event = "InsertEnter" } + eq(0, #aus2) + end) + + it('should return autocmds when group is specified by name', function() + local auname = "nvim_test_augroup" + meths.create_augroup(auname, { clear = true }) + meths.create_autocmd("FileType", { group = auname, command = 'echo "1"' }) + meths.create_autocmd("FileType", { group = auname, command = 'echo "2"' }) + + local aus = meths.get_autocmds { group = auname } + eq(2, #aus) + + local aus2 = meths.get_autocmds { group = auname, event = "InsertEnter" } + eq(0, #aus2) + end) + + it('should respect nested', function() + local bufs = exec_lua [[ + local count = 0 + vim.api.nvim_create_autocmd("BufNew", { + once = false, + nested = true, + callback = function() + count = count + 1 + if count > 5 then + return true + end + + vim.cmd(string.format("new README_%s.md", count)) + end + }) + + vim.cmd "new First.md" + + return vim.api.nvim_list_bufs() + ]] + + -- 1 for the first buffer + -- 2 for First.md + -- 3-7 for the 5 we make in the autocmd + eq({1, 2, 3, 4, 5, 6, 7}, bufs) + end) + + it('can retrieve a callback from an autocmd', function() + local content = 'I Am A Callback' + meths.set_var('content', content) + + local result = exec_lua([[ + local cb = function() return vim.g.content end + vim.api.nvim_create_autocmd("User", { + pattern = "TestTrigger", + desc = "A test autocommand with a callback", + callback = cb, + }) + local aus = vim.api.nvim_get_autocmds({ event = 'User', pattern = 'TestTrigger'}) + local first = aus[1] + return { + cb = { + type = type(first.callback), + can_retrieve = first.callback() == vim.g.content + } + } + ]]) + + eq("function", result.cb.type) + eq(true, result.cb.can_retrieve) + end) + + it('will return an empty string as the command for an autocmd that uses a callback', function() + local result = exec_lua([[ + local callback = function() print 'I Am A Callback' end + vim.api.nvim_create_autocmd("BufWritePost", { + pattern = "*.py", + callback = callback, + }) + local aus = vim.api.nvim_get_autocmds({ event = 'BufWritePost' }) + local first = aus[1] + return { + command = first.command, + cbtype = type(first.callback) + } + ]]) + + eq({ command = "", cbtype = 'function' }, result) + end) + end) + + describe('groups', function() + before_each(function() + command [[au! InsertEnter]] + + command [[au InsertEnter * :echo "No Group"]] + + command [[augroup GroupOne]] + command [[ au InsertEnter * :echo "GroupOne:1"]] + command [[augroup END]] + + command [[augroup GroupTwo]] + command [[ au InsertEnter * :echo "GroupTwo:2"]] + command [[ au InsertEnter * :echo "GroupTwo:3"]] + command [[augroup END]] + end) + + it('should return all groups if no group is specified', function() + local aus = meths.get_autocmds { event = "InsertEnter" } + if #aus ~= 4 then + eq({}, aus) + end + + eq(4, #aus) + end) + + it('should return only the group specified', function() + local aus = meths.get_autocmds { + event = "InsertEnter", + group = "GroupOne", + } + + eq(1, #aus) + eq([[:echo "GroupOne:1"]], aus[1].command) + eq("GroupOne", aus[1].group_name) + end) + + it('should return only the group specified, multiple values', function() + local aus = meths.get_autocmds { + event = "InsertEnter", + group = "GroupTwo", + } + + eq(2, #aus) + eq([[:echo "GroupTwo:2"]], aus[1].command) + eq("GroupTwo", aus[1].group_name) + eq([[:echo "GroupTwo:3"]], aus[2].command) + eq("GroupTwo", aus[2].group_name) + end) + end) + + describe('groups: 2', function() + it('raises error for undefined augroup name', function() + local success, code = unpack(meths.exec_lua([[ + return {pcall(function() + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + group = "NotDefined", + command = "echo 'hello'", + }) + end)} + ]], {})) + + eq(false, success) + matches('invalid augroup: NotDefined', code) + end) + + it('raises error for undefined augroup id', function() + local success, code = unpack(meths.exec_lua([[ + return {pcall(function() + -- Make sure the augroup is deleted + vim.api.nvim_del_augroup_by_id(1) + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + group = 1, + command = "echo 'hello'", + }) + end)} + ]], {})) + + eq(false, success) + matches('invalid augroup: 1', code) + end) + + it('raises error for invalid group type', function() + local success, code = unpack(meths.exec_lua([[ + return {pcall(function() + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + group = true, + command = "echo 'hello'", + }) + end)} + ]], {})) + + eq(false, success) + matches("'group' must be a string or an integer", code) + end) + end) + + describe('patterns', function() + before_each(function() + command [[au! InsertEnter]] + + command [[au InsertEnter * :echo "No Group"]] + command [[au InsertEnter *.one :echo "GroupOne:1"]] + command [[au InsertEnter *.two :echo "GroupTwo:2"]] + command [[au InsertEnter *.two :echo "GroupTwo:3"]] + command [[au InsertEnter <buffer> :echo "Buffer"]] + end) + + it('should should return for literal match', function() + local aus = meths.get_autocmds { + event = "InsertEnter", + pattern = "*" + } + + eq(1, #aus) + eq([[:echo "No Group"]], aus[1].command) + end) + + it('should return for multiple matches', function() + -- vim.api.nvim_get_autocmds + local aus = meths.get_autocmds { + event = "InsertEnter", + pattern = { "*.one", "*.two" }, + } + + eq(3, #aus) + eq([[:echo "GroupOne:1"]], aus[1].command) + eq([[:echo "GroupTwo:2"]], aus[2].command) + eq([[:echo "GroupTwo:3"]], aus[3].command) + end) + + it('should work for buffer autocmds', function() + local normalized_aus = meths.get_autocmds { + event = "InsertEnter", + pattern = "<buffer=1>", + } + + local raw_aus = meths.get_autocmds { + event = "InsertEnter", + pattern = "<buffer>", + } + + local zero_aus = meths.get_autocmds { + event = "InsertEnter", + pattern = "<buffer=0>", + } + + eq(normalized_aus, raw_aus) + eq(normalized_aus, zero_aus) + eq([[:echo "Buffer"]], normalized_aus[1].command) + end) + end) + end) + + describe('nvim_exec_autocmds', function() + it("can trigger builtin autocmds", function() + meths.set_var("autocmd_executed", false) + + meths.create_autocmd("BufReadPost", { + pattern = "*", + command = "let g:autocmd_executed = v:true", + }) + + eq(false, meths.get_var("autocmd_executed")) + meths.exec_autocmds("BufReadPost", {}) + eq(true, meths.get_var("autocmd_executed")) + end) + + it("can trigger multiple patterns", function() + meths.set_var("autocmd_executed", 0) + + meths.create_autocmd("BufReadPost", { + pattern = "*", + command = "let g:autocmd_executed += 1", + }) + + meths.exec_autocmds("BufReadPost", { pattern = { "*.lua", "*.vim" } }) + eq(2, meths.get_var("autocmd_executed")) + + meths.create_autocmd("BufReadPre", { + pattern = { "bar", "foo" }, + command = "let g:autocmd_executed += 10", + }) + + meths.exec_autocmds("BufReadPre", { pattern = { "foo", "bar", "baz", "frederick" }}) + eq(22, meths.get_var("autocmd_executed")) + end) + + it("can pass the buffer", function() + meths.set_var("buffer_executed", -1) + eq(-1, meths.get_var("buffer_executed")) + + meths.create_autocmd("BufLeave", { + pattern = "*", + command = 'let g:buffer_executed = +expand("<abuf>")', + }) + + -- Doesn't execute for other non-matching events + meths.exec_autocmds("CursorHold", { buffer = 1 }) + eq(-1, meths.get_var("buffer_executed")) + + meths.exec_autocmds("BufLeave", { buffer = 1 }) + eq(1, meths.get_var("buffer_executed")) + end) + + it("can pass the filename, pattern match", function() + meths.set_var("filename_executed", 'none') + eq('none', meths.get_var("filename_executed")) + + meths.create_autocmd("BufEnter", { + pattern = "*.py", + command = 'let g:filename_executed = expand("<afile>")', + }) + + -- Doesn't execute for other non-matching events + meths.exec_autocmds("CursorHold", { buffer = 1 }) + eq('none', meths.get_var("filename_executed")) + + meths.command('edit __init__.py') + eq('__init__.py', meths.get_var("filename_executed")) + end) + + it('cannot pass buf and fname', function() + local ok = pcall(meths.exec_autocmds, "BufReadPre", { pattern = "literally_cannot_error.rs", buffer = 1 }) + eq(false, ok) + end) + + it("can pass the filename, exact match", function() + meths.set_var("filename_executed", 'none') + eq('none', meths.get_var("filename_executed")) + + meths.command('edit other_file.txt') + meths.command('edit __init__.py') + eq('none', meths.get_var("filename_executed")) + + meths.create_autocmd("CursorHoldI", { + pattern = "__init__.py", + command = 'let g:filename_executed = expand("<afile>")', + }) + + -- Doesn't execute for other non-matching events + meths.exec_autocmds("CursorHoldI", { buffer = 1 }) + eq('none', meths.get_var("filename_executed")) + + meths.exec_autocmds("CursorHoldI", { buffer = meths.get_current_buf() }) + eq('__init__.py', meths.get_var("filename_executed")) + + -- Reset filename + meths.set_var("filename_executed", 'none') + + meths.exec_autocmds("CursorHoldI", { pattern = '__init__.py' }) + eq('__init__.py', meths.get_var("filename_executed")) + end) + + it("works with user autocmds", function() + meths.set_var("matched", 'none') + + meths.create_autocmd("User", { + pattern = "TestCommand", + command = 'let g:matched = "matched"' + }) + + meths.exec_autocmds("User", { pattern = "OtherCommand" }) + eq('none', meths.get_var('matched')) + meths.exec_autocmds("User", { pattern = "TestCommand" }) + eq('matched', meths.get_var('matched')) + end) + + it('can pass group by id', function() + meths.set_var("group_executed", false) + + local auid = meths.create_augroup("nvim_test_augroup", { clear = true }) + meths.create_autocmd("FileType", { + group = auid, + command = 'let g:group_executed = v:true', + }) + + eq(false, meths.get_var("group_executed")) + meths.exec_autocmds("FileType", { group = auid }) + eq(true, meths.get_var("group_executed")) + end) + + it('can pass group by name', function() + meths.set_var("group_executed", false) + + local auname = "nvim_test_augroup" + meths.create_augroup(auname, { clear = true }) + meths.create_autocmd("FileType", { + group = auname, + command = 'let g:group_executed = v:true', + }) + + eq(false, meths.get_var("group_executed")) + meths.exec_autocmds("FileType", { group = auname }) + eq(true, meths.get_var("group_executed")) + end) + end) + + describe('nvim_create_augroup', function() + before_each(function() + clear() + + meths.set_var('executed', 0) + end) + + local make_counting_autocmd = function(opts) + opts = opts or {} + + local resulting = { + pattern = "*", + command = "let g:executed = g:executed + 1", + } + + resulting.group = opts.group + resulting.once = opts.once + + meths.create_autocmd("FileType", resulting) + end + + local set_ft = function(ft) + ft = ft or "txt" + source(string.format("set filetype=%s", ft)) + end + + local get_executed_count = function() + return meths.get_var('executed') + end + + it('can be added in a group', function() + local augroup = "TestGroup" + meths.create_augroup(augroup, { clear = true }) + make_counting_autocmd { group = augroup } + + set_ft("txt") + set_ft("python") + + eq(2, get_executed_count()) + end) + + it('works getting called multiple times', function() + make_counting_autocmd() + set_ft() + set_ft() + set_ft() + + eq(3, get_executed_count()) + end) + + it('handles ++once', function() + make_counting_autocmd {once = true} + set_ft('txt') + set_ft('help') + set_ft('txt') + set_ft('help') + + eq(1, get_executed_count()) + end) + + it('errors on unexpected keys', function() + local success, code = pcall(meths.create_autocmd, "FileType", { + pattern = "*", + not_a_valid_key = "NotDefined", + }) + + eq(false, success) + matches('not_a_valid_key', code) + end) + + it('can execute simple callback', function() + exec_lua([[ + vim.g.executed = false + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = function() vim.g.executed = true end, + }) + ]], {}) + + + eq(true, exec_lua([[ + vim.cmd "set filetype=txt" + return vim.g.executed + ]], {})) + end) + + it('calls multiple lua callbacks for the same autocmd execution', function() + eq(4, exec_lua([[ + local count = 0 + local counter = function() + count = count + 1 + end + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = counter, + }) + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = counter, + }) + + vim.cmd "set filetype=txt" + vim.cmd "set filetype=txt" + + return count + ]], {})) + end) + + it('properly releases functions with ++once', function() + exec_lua([[ + WeakTable = setmetatable({}, { __mode = "k" }) + + OnceCount = 0 + + MyVal = {} + WeakTable[MyVal] = true + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = function() + OnceCount = OnceCount + 1 + MyVal = {} + end, + once = true + }) + ]]) + + command [[set filetype=txt]] + eq(1, exec_lua([[return OnceCount]], {})) + + exec_lua([[collectgarbage()]], {}) + + command [[set filetype=txt]] + eq(1, exec_lua([[return OnceCount]], {})) + + eq(0, exec_lua([[ + local count = 0 + for _ in pairs(WeakTable) do + count = count + 1 + end + + return count + ]]), "Should have no keys remaining") + end) + + it('groups can be cleared', function() + local augroup = "TestGroup" + meths.create_augroup(augroup, { clear = true }) + meths.create_autocmd("FileType", { + group = augroup, + command = "let g:executed = g:executed + 1" + }) + + set_ft("txt") + set_ft("txt") + eq(2, get_executed_count(), "should only count twice") + + meths.create_augroup(augroup, { clear = true }) + eq({}, meths.get_autocmds { group = augroup }) + + set_ft("txt") + set_ft("txt") + eq(2, get_executed_count(), "No additional counts") + end) + + it('can delete non-existent groups with pcall', function() + eq(false, exec_lua[[return pcall(vim.api.nvim_del_augroup_by_name, 'noexist')]]) + eq('Vim:E367: No such group: "noexist"', pcall_err(meths.del_augroup_by_name, 'noexist')) + + eq(false, exec_lua[[return pcall(vim.api.nvim_del_augroup_by_id, -12342)]]) + eq('Vim:E367: No such group: "--Deleted--"', pcall_err(meths.del_augroup_by_id, -12312)) + end) + + it('groups work with once', function() + local augroup = "TestGroup" + + meths.create_augroup(augroup, { clear = true }) + make_counting_autocmd { group = augroup, once = true } + + set_ft("txt") + set_ft("python") + + eq(1, get_executed_count()) + end) + + it('autocmds can be registered multiple times.', function() + local augroup = "TestGroup" + + meths.create_augroup(augroup, { clear = true }) + make_counting_autocmd { group = augroup, once = false } + make_counting_autocmd { group = augroup, once = false } + make_counting_autocmd { group = augroup, once = false } + + set_ft("txt") + set_ft("python") + + eq(3 * 2, get_executed_count()) + end) + + it('can be deleted', function() + local augroup = "WillBeDeleted" + + meths.create_augroup(augroup, { clear = true }) + meths.create_autocmd({"Filetype"}, { + pattern = "*", + command = "echo 'does not matter'", + }) + + -- Clears the augroup from before, which erases the autocmd + meths.create_augroup(augroup, { clear = true }) + + local result = #meths.get_autocmds { group = augroup } + + eq(0, result) + end) + + it('can be used for buffer local autocmds', function() + local augroup = "WillBeDeleted" + + meths.set_var("value_set", false) + + meths.create_augroup(augroup, { clear = true }) + meths.create_autocmd("Filetype", { + pattern = "<buffer>", + command = "let g:value_set = v:true", + }) + + command "new" + command "set filetype=python" + + eq(false, meths.get_var("value_set")) + end) + + it('can accept vimscript functions', function() + source [[ + let g:vimscript_executed = 0 + + function! MyVimscriptFunction() abort + let g:vimscript_executed = g:vimscript_executed + 1 + endfunction + + call nvim_create_autocmd("FileType", #{ + \ pattern: ["python", "javascript"], + \ callback: "MyVimscriptFunction", + \ }) + + set filetype=txt + set filetype=python + set filetype=txt + set filetype=javascript + set filetype=txt + ]] + + eq(2, meths.get_var("vimscript_executed")) + end) + end) + + describe('augroup!', function() + it('legacy: should clear and not return any autocmds for delete groups', function() + command('augroup TEMP_A') + command(' autocmd! BufReadPost *.py :echo "Hello"') + command('augroup END') + + command('augroup! TEMP_A') + + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_A' })) + + -- For some reason, augroup! doesn't clear the autocmds themselves, which is just wild + -- but we managed to keep this behavior. + eq(1, #meths.get_autocmds { event = 'BufReadPost' }) + end) + + it('legacy: remove augroups that have no autocmds', function() + command('augroup TEMP_AB') + command('augroup END') + + command('augroup! TEMP_AB') + + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_AB' })) + eq(0, #meths.get_autocmds { event = 'BufReadPost' }) + end) + + it('legacy: multiple remove and add augroup', function() + command('augroup TEMP_ABC') + command(' au!') + command(' autocmd BufReadPost *.py echo "Hello"') + command('augroup END') + + command('augroup! TEMP_ABC') + + -- Should still have one autocmd :'( + local aus = meths.get_autocmds { event = 'BufReadPost' } + eq(1, #aus, aus) + + command('augroup TEMP_ABC') + command(' au!') + command(' autocmd BufReadPost *.py echo "Hello"') + command('augroup END') + + -- Should now have two autocmds :'( + aus = meths.get_autocmds { event = 'BufReadPost' } + eq(2, #aus, aus) + + command('augroup! TEMP_ABC') + + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_ABC' })) + eq(2, #meths.get_autocmds { event = 'BufReadPost' }) + end) + + it('api: should clear and not return any autocmds for delete groups by id', function() + command('augroup TEMP_ABCD') + command('autocmd! BufReadPost *.py :echo "Hello"') + command('augroup END') + + local augroup_id = meths.create_augroup("TEMP_ABCD", { clear = false }) + meths.del_augroup_by_id(augroup_id) + + -- For good reason, we kill all the autocmds from del_augroup, + -- so now this works as expected + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_ABCD' })) + eq(0, #meths.get_autocmds { event = 'BufReadPost' }) + end) + + it('api: should clear and not return any autocmds for delete groups by name', function() + command('augroup TEMP_ABCDE') + command('autocmd! BufReadPost *.py :echo "Hello"') + command('augroup END') + + meths.del_augroup_by_name("TEMP_ABCDE") + + -- For good reason, we kill all the autocmds from del_augroup, + -- so now this works as expected + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_ABCDE' })) + eq(0, #meths.get_autocmds { event = 'BufReadPost' }) + end) + end) + + describe('nvim_clear_autocmds', function() + it('should clear based on event + pattern', function() + command('autocmd InsertEnter *.py :echo "Python can be cool sometimes"') + command('autocmd InsertEnter *.txt :echo "Text Files Are Cool"') + + local search = { event = "InsertEnter", pattern = "*.txt" } + local before_delete = meths.get_autocmds(search) + eq(1, #before_delete) + + local before_delete_all = meths.get_autocmds { event = search.event } + eq(2, #before_delete_all) + + meths.clear_autocmds(search) + local after_delete = meths.get_autocmds(search) + eq(0, #after_delete) + + local after_delete_all = meths.get_autocmds { event = search.event } + eq(1, #after_delete_all) + end) + + it('should clear based on event', function() + command('autocmd InsertEnter *.py :echo "Python can be cool sometimes"') + command('autocmd InsertEnter *.txt :echo "Text Files Are Cool"') + + local search = { event = "InsertEnter"} + local before_delete = meths.get_autocmds(search) + eq(2, #before_delete) + + meths.clear_autocmds(search) + local after_delete = meths.get_autocmds(search) + eq(0, #after_delete) + end) + + it('should clear based on pattern', function() + command('autocmd InsertEnter *.TestPat1 :echo "Enter 1"') + command('autocmd InsertLeave *.TestPat1 :echo "Leave 1"') + command('autocmd InsertEnter *.TestPat2 :echo "Enter 2"') + command('autocmd InsertLeave *.TestPat2 :echo "Leave 2"') + + local search = { pattern = "*.TestPat1"} + local before_delete = meths.get_autocmds(search) + eq(2, #before_delete) + local before_delete_events = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + eq(4, #before_delete_events) + + meths.clear_autocmds(search) + local after_delete = meths.get_autocmds(search) + eq(0, #after_delete) + + local after_delete_events = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + eq(2, #after_delete_events) + end) + + it('should allow clearing by buffer', function() + command('autocmd! InsertEnter') + command('autocmd InsertEnter <buffer> :echo "Enter Buffer"') + command('autocmd InsertEnter *.TestPat1 :echo "Enter Pattern"') + + local search = { event = "InsertEnter" } + local before_delete = meths.get_autocmds(search) + eq(2, #before_delete) + + meths.clear_autocmds { buffer = 0 } + local after_delete = meths.get_autocmds(search) + eq(1, #after_delete) + eq("*.TestPat1", after_delete[1].pattern) + end) + + it('should allow clearing by buffer and group', function() + command('augroup TestNvimClearAutocmds') + command(' au!') + command(' autocmd InsertEnter <buffer> :echo "Enter Buffer"') + command(' autocmd InsertEnter *.TestPat1 :echo "Enter Pattern"') + command('augroup END') + + local search = { event = "InsertEnter", group = "TestNvimClearAutocmds" } + local before_delete = meths.get_autocmds(search) + eq(2, #before_delete) + + -- Doesn't clear without passing group. + meths.clear_autocmds { buffer = 0 } + local without_group = meths.get_autocmds(search) + eq(2, #without_group) + + -- Doest clear with passing group. + meths.clear_autocmds { buffer = 0, group = search.group } + local with_group = meths.get_autocmds(search) + eq(1, #with_group) + end) + end) +end) |