local user_command = require('std.user_command') describe('std.user_command', function() before_each(function() package.loaded['std.user_command'] = nil user_command = require('std.user_command') end) after_each(function() pcall(vim.api.nvim_del_user_command, 'TestCommand') pcall(vim.api.nvim_del_user_command, 'AnotherCommand') pcall(vim.api.nvim_del_user_command, 'NoDescCommand') pcall(vim.api.nvim_del_user_command, 'NoOptsCommand') pcall(vim.api.nvim_del_user_command, 'FullChainCommand') pcall(vim.api.nvim_buf_del_user_command, 0, 'TestBufCmd') pcall(vim.api.nvim_buf_del_user_command, 0, 'TestBufCmd2') pcall(vim.api.nvim_buf_del_user_command, 0, 'AnotherBufCmd') pcall(vim.api.nvim_buf_del_user_command, 0, 'OptsBufCmd') pcall(vim.api.nvim_buf_del_user_command, 0, 'ExecBufCmd') end) local function get_user_command(name) local commands = vim.api.nvim_get_commands {} return commands[name] end describe('full chain: name -> description -> options -> command', function() it('should create a user command with all parameters', function() local deleter = user_command.FullChainCommand 'A test command' { nargs = '*', complete = 'file', } (function() return 'executed' end) assert.is_function(deleter) local info = get_user_command('FullChainCommand') assert.is_not_nil(info) assert.equal('FullChainCommand', info.name) assert.equal('*', info.nargs) assert.equal('file', info.complete) end) it('should return a deleter function that removes the command', function() local deleter = user_command.TestCommand 'Description' { nargs = '?' } (function() end) assert.is_function(deleter) local info_before = get_user_command('TestCommand') assert.is_not_nil(info_before) deleter() local info_after = get_user_command('TestCommand') assert.is_nil(info_after) end) end) describe('chain: name -> description -> command (no options)', function() it('should create a user command with just description and command', function() local deleter = user_command.TestCommand 'Just a description' (function() return 'simple' end) assert.is_function(deleter) local info = get_user_command('TestCommand') assert.is_not_nil(info) assert.equal('TestCommand', info.name) end) end) describe('command execution', function() it('should execute the command function when called', function() local executed = false local deleter = user_command.TestCommand 'Execute test' (function() executed = true return 'done' end) vim.cmd('TestCommand') assert.is_true(executed) end) end) describe('error handling', function() it('should error on invalid command_name (non-string)', function() local ok, err = pcall(function() return user_command[123] end) assert.is_false(ok) assert.is_not_nil(err:match('command_name: expected string')) end) it('should error on invalid description (non-string)', function() local desc_stage = user_command.TestCommand local ok, err = pcall(function() return desc_stage(123) end) assert.is_false(ok) assert.is_not_nil(err:match('description: expected string')) end) it('should error on invalid command (non-function when expected)', function() local ok, err = pcall(function() return user_command.TestCommand('description')({ nargs = '?' })(123) end) assert.is_false(ok) assert.is_not_nil(err:match('command: expected function')) end) it('should error on invalid options_or_command (non-table and non-function)', function() local ok, err = pcall(function() return user_command.TestCommand('description')(123) end) assert.is_false(ok) assert.is_not_nil(err:match('options_or_command: expected')) end) end) describe('metatable chaining behavior', function() it('should return DESCRIPTION_SETTER metatable after setting name', function() local mt = getmetatable(user_command.TestCommand) assert.is_function(mt.__call) end) it('should return OPTIONS_OR_COMMAND_SETTER metatable after setting description', function() local desc_stage = user_command.TestCommand 'description' local mt = getmetatable(desc_stage) assert.is_function(mt.__call) end) it('should return COMMAND_SETTER metatable after setting options', function() local opts_stage = user_command.TestCommand 'description' { nargs = '?' } local mt = getmetatable(opts_stage) assert.is_function(mt.__call) end) it('should return deleter function after setting command', function() local result = user_command.TestCommand 'description' (function() end) assert.is_function(result) end) end) describe('options merging', function() it('should create command with correct options', function() local deleter = user_command.TestCommand 'My description' (function() end) local info = get_user_command('TestCommand') assert.is_not_nil(info) end) it('should allow custom options to override defaults', function() local deleter = user_command.TestCommand 'Custom desc' { nargs = '*', bang = true, } (function() end) local info = get_user_command('TestCommand') assert.is_not_nil(info) assert.equal('*', info.nargs) assert.is_true(info.bang == 1 or info.bang == true) end) end) describe('multiple commands', function() it('should create multiple independent commands', function() local deleter1 = user_command.TestCommand 'First' (function() end) local deleter2 = user_command.AnotherCommand 'Second' (function() end) local info1 = get_user_command('TestCommand') local info2 = get_user_command('AnotherCommand') assert.is_not_nil(info1) assert.is_not_nil(info2) assert.equal('TestCommand', info1.name) assert.equal('AnotherCommand', info2.name) deleter1() deleter2() local after1 = get_user_command('TestCommand') local after2 = get_user_command('AnotherCommand') assert.is_nil(after1) assert.is_nil(after2) end) end) describe('buffer commands', function() local function get_buf_commands(bufnr) return vim.api.nvim_buf_get_commands(bufnr, {}) end it('should create a buffer-local command', function() local deleter = user_command.buffer(0).TestBufCmd 'A buffer command' (function() end) local buf_cmds = get_buf_commands(0) assert.is_not_nil(buf_cmds['TestBufCmd']) end) it('should NOT appear in global command namespace', function() local deleter = user_command.buffer(0).TestBufCmd 'A buffer command' (function() end) local global_cmds = vim.api.nvim_get_commands {} assert.is_nil(global_cmds['TestBufCmd']) end) it('should appear in buffer-local command namespace', function() local deleter = user_command.buffer(0).TestBufCmd 'A buffer command' (function() end) local buf_cmds = get_buf_commands(0) assert.is_table(buf_cmds['TestBufCmd']) assert.equal('TestBufCmd', buf_cmds['TestBufCmd'].name) end) it('should return a deleter that removes the buffer command', function() local deleter = user_command.buffer(0).TestBufCmd 'To be deleted' (function() end) local buf_cmds_before = get_buf_commands(0) assert.is_not_nil(buf_cmds_before['TestBufCmd']) deleter() local buf_cmds_after = get_buf_commands(0) assert.is_nil(buf_cmds_after['TestBufCmd']) end) it('should create multiple independent buffer commands', function() local deleter1 = user_command.buffer(0).TestBufCmd 'First' (function() end) local deleter2 = user_command.buffer(0).TestBufCmd2 'Second' (function() end) local buf_cmds = get_buf_commands(0) assert.is_not_nil(buf_cmds['TestBufCmd']) assert.is_not_nil(buf_cmds['TestBufCmd2']) deleter1() deleter2() local buf_cmds_after = get_buf_commands(0) assert.is_nil(buf_cmds_after['TestBufCmd']) assert.is_nil(buf_cmds_after['TestBufCmd2']) end) it('should error on invalid buffer argument', function() local ok = pcall(function() return user_command.buffer('not_a_number') end) assert.is_false(ok) end) it('should accept an options table before command', function() local deleter = user_command.buffer(0).OptsBufCmd 'With options' { nargs = '*', bang = true, } (function() end) local buf_cmds = get_buf_commands(0) assert.is_not_nil(buf_cmds['OptsBufCmd']) assert.equal('*', buf_cmds['OptsBufCmd'].nargs) deleter() end) it('should execute the command function when called', function() local executed = false local deleter = user_command.buffer(0).ExecBufCmd 'Execute test' (function() executed = true end) vim.cmd('ExecBufCmd') assert.is_true(executed) deleter() end) it('should pass opts to command function', function() ---@type vim.api.keyset.create_user_command.command_args? local received_opts local deleter = user_command.buffer(0).TestBufCmd 'Opts test' (function(opts) received_opts = opts end) vim.cmd('TestBufCmd') assert.is_table(received_opts) assert.equal('TestBufCmd', assert(received_opts).name) deleter() end) it('should be scoped to the specified buffer only', function() local bufnr1 = vim.api.nvim_create_buf(false, true) local bufnr2 = vim.api.nvim_create_buf(false, true) local deleter1 = user_command.buffer(bufnr1).TestBufCmd 'On buffer 1' (function() end) local deleter2 = user_command.buffer(bufnr2).AnotherBufCmd 'On buffer 2' (function() end) local cmds1 = get_buf_commands(bufnr1) local cmds2 = get_buf_commands(bufnr2) assert.is_not_nil(cmds1['TestBufCmd']) assert.is_nil(cmds1['AnotherBufCmd']) assert.is_not_nil(cmds2['AnotherBufCmd']) assert.is_nil(cmds2['TestBufCmd']) deleter1() deleter2() vim.api.nvim_buf_delete(bufnr1, { force = true }) vim.api.nvim_buf_delete(bufnr2, { force = true }) end) end) end)