393 lines
No EOL
14 KiB
Lua
393 lines
No EOL
14 KiB
Lua
--- JQuote Unit Tests
|
|
--- @author Jan
|
|
--- @license MIT
|
|
|
|
-- Create global state for mock
|
|
local test_state = {
|
|
current_line = "",
|
|
cursor_row = 1,
|
|
cursor_col = 0,
|
|
notifications = {},
|
|
keymaps = {}
|
|
}
|
|
|
|
-- Mock vim API for testing
|
|
local mock_vim = {
|
|
log = {
|
|
levels = {
|
|
DEBUG = 0,
|
|
INFO = 1,
|
|
WARN = 2,
|
|
ERROR = 3,
|
|
}
|
|
},
|
|
notify = function(message, level)
|
|
table.insert(test_state.notifications, { message = message, level = level })
|
|
end,
|
|
api = {
|
|
nvim_get_current_line = function()
|
|
return test_state.current_line
|
|
end,
|
|
nvim_win_get_cursor = function()
|
|
return { test_state.cursor_row, test_state.cursor_col }
|
|
end,
|
|
nvim_set_current_line = function(line)
|
|
test_state.current_line = line
|
|
return true
|
|
end,
|
|
nvim_win_set_cursor = function(win, pos)
|
|
test_state.cursor_row = pos[1]
|
|
test_state.cursor_col = pos[2]
|
|
return true
|
|
end,
|
|
},
|
|
keymap = {
|
|
set = function(mode, key, func, opts)
|
|
test_state.keymaps[key] = { mode = mode, func = func, opts = opts }
|
|
return true
|
|
end,
|
|
},
|
|
tbl_deep_extend = function(behavior, ...)
|
|
local result = {}
|
|
for _, tbl in ipairs({...}) do
|
|
for k, v in pairs(tbl) do
|
|
if type(v) == "table" and type(result[k]) == "table" then
|
|
result[k] = mock_vim.tbl_deep_extend(behavior, result[k], v)
|
|
else
|
|
result[k] = v
|
|
end
|
|
end
|
|
end
|
|
return result
|
|
end,
|
|
deepcopy = nil, -- Will be set after mock_vim is defined
|
|
-- Test utilities
|
|
_reset = function()
|
|
test_state.current_line = ""
|
|
test_state.cursor_row = 1
|
|
test_state.cursor_col = 0
|
|
test_state.notifications = {}
|
|
test_state.keymaps = {}
|
|
end,
|
|
_get_notifications = function()
|
|
return test_state.notifications
|
|
end,
|
|
_set_line_and_cursor = function(line, col)
|
|
test_state.current_line = line
|
|
test_state.cursor_col = col or 0
|
|
end,
|
|
_keymaps = test_state.keymaps
|
|
}
|
|
|
|
-- Set up deepcopy function now that mock_vim is defined
|
|
mock_vim.deepcopy = function(tbl)
|
|
if type(tbl) ~= "table" then return tbl end
|
|
local copy = {}
|
|
for k, v in pairs(tbl) do
|
|
if type(v) == "table" then
|
|
copy[k] = mock_vim.deepcopy(v)
|
|
else
|
|
copy[k] = v
|
|
end
|
|
end
|
|
return copy
|
|
end
|
|
|
|
-- Add current directory to Lua path for module loading
|
|
package.path = package.path .. ";./?.lua;./?/init.lua"
|
|
|
|
-- Inject mock vim into global scope BEFORE loading the module
|
|
_G.vim = mock_vim
|
|
|
|
-- Load the module under test
|
|
local jquote = require('lua.jquote.init')
|
|
|
|
-- Test suite
|
|
describe("JQuote Plugin", function()
|
|
|
|
before_each(function()
|
|
mock_vim._reset()
|
|
-- Reset plugin to default state
|
|
jquote.setup({})
|
|
end)
|
|
|
|
describe("Plugin Initialization", function()
|
|
|
|
it("should initialize with default options", function()
|
|
local success = jquote.setup()
|
|
assert.is_true(success)
|
|
assert.are.equal("<leader>tq", jquote.options.hotkey)
|
|
assert.are.same({ "'", '"', "`" }, jquote.options.quote_chars)
|
|
assert.are.equal("auto", jquote.options.jump_behavior)
|
|
end)
|
|
|
|
it("should merge user options with defaults", function()
|
|
local user_options = {
|
|
hotkey = "<leader>q",
|
|
quote_chars = { "'", '"' },
|
|
jump_behavior = "manual"
|
|
}
|
|
local success = jquote.setup(user_options)
|
|
assert.is_true(success)
|
|
assert.are.equal("<leader>q", jquote.options.hotkey)
|
|
assert.are.same({ "'", '"' }, jquote.options.quote_chars)
|
|
assert.are.equal("manual", jquote.options.jump_behavior)
|
|
end)
|
|
|
|
it("should validate quote_chars configuration", function()
|
|
local success = jquote.setup({ quote_chars = {} })
|
|
assert.is_false(success)
|
|
|
|
local notifications = mock_vim._get_notifications()
|
|
assert.is_true(#notifications > 0)
|
|
assert.matches("quote_chars must contain at least", notifications[1].message)
|
|
end)
|
|
|
|
it("should validate jump_behavior configuration", function()
|
|
local success = jquote.setup({ jump_behavior = "invalid" })
|
|
assert.is_false(success)
|
|
|
|
local notifications = mock_vim._get_notifications()
|
|
assert.is_true(#notifications > 0)
|
|
assert.matches("jump_behavior must be either", notifications[1].message)
|
|
end)
|
|
|
|
it("should set up key mapping", function()
|
|
jquote.setup({ hotkey = "<leader>test" })
|
|
assert.is_not_nil(mock_vim._keymaps["<leader>test"])
|
|
assert.are.equal("n", mock_vim._keymaps["<leader>test"].mode)
|
|
end)
|
|
end)
|
|
|
|
describe("Quote Detection", function()
|
|
|
|
it("should find quoted string at cursor position", function()
|
|
mock_vim._set_line_and_cursor("hello 'world' test", 8)
|
|
local success = jquote.toggle_quotes()
|
|
assert.is_true(success)
|
|
assert.are.equal('hello "world" test', test_state.current_line)
|
|
end)
|
|
|
|
it("should cycle through quote characters", function()
|
|
jquote.setup({ quote_chars = { "'", '"', "`" } })
|
|
|
|
-- First toggle: ' -> "
|
|
mock_vim._set_line_and_cursor("hello 'world'", 8)
|
|
jquote.toggle_quotes()
|
|
assert.are.equal('hello "world"', test_state.current_line)
|
|
|
|
-- Second toggle: " -> `
|
|
mock_vim._set_line_and_cursor('hello "world"', 8)
|
|
jquote.toggle_quotes()
|
|
assert.are.equal('hello `world`', test_state.current_line)
|
|
|
|
-- Third toggle: ` -> '
|
|
mock_vim._set_line_and_cursor('hello `world`', 8)
|
|
jquote.toggle_quotes()
|
|
assert.are.equal("hello 'world'", test_state.current_line)
|
|
end)
|
|
|
|
it("should handle nested quotes correctly", function()
|
|
mock_vim._set_line_and_cursor('outer "inner \'nested\' text" end', 15)
|
|
jquote.toggle_quotes()
|
|
assert.are.equal('outer "inner "nested" text" end', test_state.current_line)
|
|
end)
|
|
|
|
it("should handle multiple quote pairs on same line", function()
|
|
mock_vim._set_line_and_cursor("'first' and 'second' pair", 2)
|
|
jquote.toggle_quotes()
|
|
assert.are.equal('"first" and \'second\' pair', test_state.current_line)
|
|
end)
|
|
end)
|
|
|
|
describe("Auto Jump Functionality", function()
|
|
|
|
it("should jump to next quote pair when cursor is not inside quotes", function()
|
|
jquote.setup({ jump_behavior = "auto" })
|
|
mock_vim._set_line_and_cursor("hello 'world' and 'test'", 0)
|
|
|
|
local success = jquote.toggle_quotes()
|
|
assert.is_true(success)
|
|
assert.are.equal(6, test_state.cursor_col) -- Should jump to first quote
|
|
end)
|
|
|
|
it("should not jump when jump_behavior is manual", function()
|
|
jquote.setup({ jump_behavior = "manual" })
|
|
mock_vim._set_line_and_cursor("hello 'world' test", 0)
|
|
|
|
local success = jquote.toggle_quotes()
|
|
assert.is_false(success)
|
|
assert.are.equal(0, test_state.cursor_col) -- Cursor should not move
|
|
end)
|
|
|
|
it("should handle case with no quote pairs found", function()
|
|
jquote.setup({ jump_behavior = "auto" })
|
|
mock_vim._set_line_and_cursor("no quotes here", 0)
|
|
|
|
local success = jquote.toggle_quotes()
|
|
assert.is_false(success)
|
|
|
|
local notifications = mock_vim._get_notifications()
|
|
assert.is_true(#notifications > 0)
|
|
assert.matches("No quote pairs found", notifications[1].message)
|
|
end)
|
|
end)
|
|
|
|
describe("Error Handling", function()
|
|
|
|
it("should handle invalid line content gracefully", function()
|
|
-- Mock API failure
|
|
mock_vim.api.nvim_get_current_line = function()
|
|
error("API Error")
|
|
end
|
|
|
|
local success = jquote.toggle_quotes()
|
|
assert.is_false(success)
|
|
|
|
local notifications = mock_vim._get_notifications()
|
|
assert.is_true(#notifications > 0)
|
|
assert.matches("Failed to get current line", notifications[1].message)
|
|
end)
|
|
|
|
it("should handle cursor position API failure", function()
|
|
mock_vim.api.nvim_win_get_cursor = function()
|
|
error("Cursor API Error")
|
|
end
|
|
|
|
local success = jquote.toggle_quotes()
|
|
assert.is_false(success)
|
|
|
|
local notifications = mock_vim._get_notifications()
|
|
assert.is_true(#notifications > 0)
|
|
assert.matches("Failed to get cursor position", notifications[1].message)
|
|
end)
|
|
|
|
it("should handle line update failure", function()
|
|
mock_vim._set_line_and_cursor("'test'", 2)
|
|
mock_vim.api.nvim_set_current_line = function()
|
|
error("Set line error")
|
|
end
|
|
|
|
local success = jquote.toggle_quotes()
|
|
assert.is_false(success)
|
|
|
|
local notifications = mock_vim._get_notifications()
|
|
assert.is_true(#notifications > 0)
|
|
assert.matches("Failed to update line", notifications[1].message)
|
|
end)
|
|
|
|
it("should handle excessively long lines", function()
|
|
local long_line = string.rep("a", 15000) .. "'test'" -- Over MAX_LINE_LENGTH
|
|
mock_vim._set_line_and_cursor(long_line, 10000)
|
|
|
|
local success = jquote.toggle_quotes()
|
|
assert.is_false(success)
|
|
end)
|
|
end)
|
|
|
|
describe("Version Management", function()
|
|
|
|
it("should return version from git tag", function()
|
|
-- Mock io.popen to return a version
|
|
local original_io_popen = io.popen
|
|
io.popen = function(cmd)
|
|
return {
|
|
read = function() return "v1.2.3\n" end,
|
|
close = function() return true end
|
|
}
|
|
end
|
|
|
|
local version = jquote.get_version()
|
|
assert.are.equal("1.2.3", version)
|
|
|
|
-- Restore original function
|
|
io.popen = original_io_popen
|
|
end)
|
|
|
|
it("should return fallback version when git fails", function()
|
|
local original_io_popen = io.popen
|
|
io.popen = function(cmd)
|
|
error("Git not available")
|
|
end
|
|
|
|
local version = jquote.get_version()
|
|
assert.are.equal("unknown", version)
|
|
|
|
io.popen = original_io_popen
|
|
end)
|
|
|
|
it("should validate semantic version format", function()
|
|
local original_io_popen = io.popen
|
|
io.popen = function(cmd)
|
|
return {
|
|
read = function() return "invalid-version\n" end,
|
|
close = function() return true end
|
|
}
|
|
end
|
|
|
|
local version = jquote.get_version()
|
|
assert.are.equal("unknown", version)
|
|
|
|
io.popen = original_io_popen
|
|
end)
|
|
end)
|
|
|
|
describe("Health Check", function()
|
|
|
|
it("should return comprehensive health information", function()
|
|
jquote.setup({
|
|
hotkey = "<leader>test",
|
|
quote_chars = { "'", '"' },
|
|
jump_behavior = "auto"
|
|
})
|
|
|
|
local health = jquote.health()
|
|
|
|
assert.is_table(health.options)
|
|
assert.are.equal(2, health.quote_chars_count)
|
|
assert.is_true(health.hotkey_configured)
|
|
assert.is_true(health.jump_behavior_valid)
|
|
assert.is_string(health.plugin_version)
|
|
end)
|
|
|
|
it("should detect invalid configuration in health check", function()
|
|
-- Manually corrupt the configuration
|
|
jquote.options.jump_behavior = "invalid"
|
|
|
|
local health = jquote.health()
|
|
assert.is_false(health.jump_behavior_valid)
|
|
end)
|
|
end)
|
|
|
|
describe("Edge Cases", function()
|
|
|
|
it("should handle empty quote_chars gracefully", function()
|
|
-- Manually set invalid state to test defensive programming
|
|
jquote.options.quote_chars = {}
|
|
mock_vim._set_line_and_cursor("'test'", 2)
|
|
|
|
local success = jquote.toggle_quotes()
|
|
assert.is_false(success)
|
|
end)
|
|
|
|
it("should handle quote at line boundaries", function()
|
|
mock_vim._set_line_and_cursor("'start", 0)
|
|
local success = jquote.toggle_quotes()
|
|
assert.is_false(success) -- No closing quote
|
|
end)
|
|
|
|
it("should handle cursor at exact quote position", function()
|
|
mock_vim._set_line_and_cursor("'test'", 0) -- Cursor on opening quote
|
|
local success = jquote.toggle_quotes()
|
|
assert.is_true(success)
|
|
assert.are.equal('"test"', test_state.current_line)
|
|
end)
|
|
|
|
it("should handle single character quoted strings", function()
|
|
mock_vim._set_line_and_cursor("'a'", 1)
|
|
local success = jquote.toggle_quotes()
|
|
assert.is_true(success)
|
|
assert.are.equal('"a"', test_state.current_line)
|
|
end)
|
|
end)
|
|
end) |