refactor: Refactor everything
BREAKING CHANGE: Actually move to the next pair of quotes
This commit is contained in:
parent
9622219679
commit
6d065862b0
6 changed files with 1044 additions and 27 deletions
393
tests/init_spec.lua
Normal file
393
tests/init_spec.lua
Normal file
|
@ -0,0 +1,393 @@
|
|||
--- 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)
|
Loading…
Add table
Add a link
Reference in a new issue