mirror of
https://github.com/ivuorinen/nvim-shellspec.git
synced 2026-01-26 03:24:00 +00:00
feat: implement dynamic test generation and resolve pre-commit conflicts
- Replace static test fixture files with dynamic test generation - Implement comprehensive test suite with unit, integration, and golden master tests - Add vim API mocking for standalone Lua test execution - Fix pre-commit hook interference by eliminating external fixture files - Add StyLua formatting for consistent Lua code style - Enhance ShellSpec formatting with improved HEREDOC and comment handling - Update documentation with new test architecture details This resolves issues where pre-commit hooks (shfmt, end-of-file-fixer) were modifying test fixture files and breaking golden master tests. The new dynamic approach generates test data programmatically, making tests immune to formatting tools while maintaining comprehensive coverage.
This commit is contained in:
@@ -10,11 +10,13 @@ local augroup = vim.api.nvim_create_augroup("ShellSpec", { clear = true })
|
||||
local function setup_buffer(bufnr)
|
||||
-- Set buffer options
|
||||
vim.api.nvim_set_option_value("commentstring", "# %s", { buf = bufnr })
|
||||
vim.api.nvim_set_option_value("foldmethod", "indent", { buf = bufnr })
|
||||
vim.api.nvim_set_option_value("shiftwidth", config.get("indent_size"), { buf = bufnr })
|
||||
vim.api.nvim_set_option_value("tabstop", config.get("indent_size"), { buf = bufnr })
|
||||
vim.api.nvim_set_option_value("expandtab", config.get("use_spaces"), { buf = bufnr })
|
||||
|
||||
-- Set window-local options (foldmethod is window-local)
|
||||
vim.api.nvim_set_option_value("foldmethod", "indent", { win = 0 })
|
||||
|
||||
-- Buffer-local commands
|
||||
vim.api.nvim_buf_create_user_command(bufnr, "ShellSpecFormat", function()
|
||||
format.format_buffer(bufnr)
|
||||
@@ -35,6 +37,18 @@ end
|
||||
|
||||
-- Create all autocommands
|
||||
function M.setup()
|
||||
-- Create global commands first
|
||||
vim.api.nvim_create_user_command("ShellSpecFormat", function()
|
||||
format.format_buffer()
|
||||
end, { desc = "Format current ShellSpec buffer" })
|
||||
|
||||
vim.api.nvim_create_user_command("ShellSpecFormatRange", function(cmd_opts)
|
||||
format.format_selection(0, cmd_opts.line1, cmd_opts.line2)
|
||||
end, {
|
||||
range = true,
|
||||
desc = "Format ShellSpec selection",
|
||||
})
|
||||
|
||||
-- FileType detection and setup
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
group = augroup,
|
||||
|
||||
@@ -17,14 +17,18 @@ end
|
||||
-- Check if line starts a HEREDOC
|
||||
local function detect_heredoc_start(line)
|
||||
local trimmed = vim.trim(line)
|
||||
for _, pattern in ipairs(get_heredoc_patterns()) do
|
||||
local match = string.match(trimmed, pattern)
|
||||
if match then
|
||||
-- Extract the delimiter
|
||||
local delimiter = string.match(match, "<<-?['\"]?([A-Z_][A-Z0-9_]*)['\"]?")
|
||||
return delimiter
|
||||
end
|
||||
|
||||
-- Check each pattern and extract delimiter directly
|
||||
if string.match(trimmed, "<<[A-Z_][A-Z0-9_]*") then
|
||||
return string.match(trimmed, "<<([A-Z_][A-Z0-9_]*)")
|
||||
elseif string.match(trimmed, "<<'[^']*'") then
|
||||
return string.match(trimmed, "<<'([^']*)'")
|
||||
elseif string.match(trimmed, '<<"[^"]*"') then
|
||||
return string.match(trimmed, '<<"([^"]*)"')
|
||||
elseif string.match(trimmed, "<<-[A-Z_][A-Z0-9_]*") then
|
||||
return string.match(trimmed, "<<-([A-Z_][A-Z0-9_]*)")
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -41,21 +45,81 @@ end
|
||||
local function is_block_keyword(line)
|
||||
local trimmed = vim.trim(line)
|
||||
|
||||
-- Standard block keywords
|
||||
if string.match(trimmed, "^(Describe|Context|ExampleGroup|It|Specify|Example)") then
|
||||
-- Debug logging
|
||||
if vim.g.shellspec_debug then
|
||||
vim.notify('ShellSpec: Checking if block keyword: "' .. trimmed .. '"', vim.log.levels.DEBUG)
|
||||
end
|
||||
|
||||
-- Standard block keywords - check each one individually
|
||||
if
|
||||
string.match(trimmed, "^Describe%s")
|
||||
or string.match(trimmed, "^Context%s")
|
||||
or string.match(trimmed, "^ExampleGroup%s")
|
||||
or string.match(trimmed, "^It%s")
|
||||
or string.match(trimmed, "^Specify%s")
|
||||
or string.match(trimmed, "^Example%s")
|
||||
then
|
||||
if vim.g.shellspec_debug then
|
||||
vim.notify('ShellSpec: Matched standard block keyword: "' .. trimmed .. '"', vim.log.levels.DEBUG)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Prefixed block keywords (x for skip, f for focus)
|
||||
if string.match(trimmed, "^[xf](Describe|Context|ExampleGroup|It|Specify|Example)") then
|
||||
if
|
||||
string.match(trimmed, "^[xf]Describe%s")
|
||||
or string.match(trimmed, "^[xf]Context%s")
|
||||
or string.match(trimmed, "^[xf]ExampleGroup%s")
|
||||
or string.match(trimmed, "^[xf]It%s")
|
||||
or string.match(trimmed, "^[xf]Specify%s")
|
||||
or string.match(trimmed, "^[xf]Example%s")
|
||||
then
|
||||
if vim.g.shellspec_debug then
|
||||
vim.notify('ShellSpec: Matched prefixed block keyword: "' .. trimmed .. '"', vim.log.levels.DEBUG)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Data and Parameters blocks
|
||||
if string.match(trimmed, "^(Data|Parameters)%s*$") then
|
||||
if string.match(trimmed, "^Data%s*$") or string.match(trimmed, "^Parameters%s*$") then
|
||||
if vim.g.shellspec_debug then
|
||||
vim.notify('ShellSpec: Matched data/parameters block: "' .. trimmed .. '"', vim.log.levels.DEBUG)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Hook keywords that create blocks (can be standalone)
|
||||
if
|
||||
string.match(trimmed, "^BeforeEach%s*$")
|
||||
or string.match(trimmed, "^AfterEach%s*$")
|
||||
or string.match(trimmed, "^BeforeAll%s*$")
|
||||
or string.match(trimmed, "^AfterAll%s*$")
|
||||
or string.match(trimmed, "^Before%s*$")
|
||||
or string.match(trimmed, "^After%s*$")
|
||||
then
|
||||
if vim.g.shellspec_debug then
|
||||
vim.notify('ShellSpec: Matched hook keyword: "' .. trimmed .. '"', vim.log.levels.DEBUG)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Additional hook keywords (can be standalone)
|
||||
if
|
||||
string.match(trimmed, "^BeforeCall%s*$")
|
||||
or string.match(trimmed, "^AfterCall%s*$")
|
||||
or string.match(trimmed, "^BeforeRun%s*$")
|
||||
or string.match(trimmed, "^AfterRun%s*$")
|
||||
then
|
||||
if vim.g.shellspec_debug then
|
||||
vim.notify('ShellSpec: Matched additional hook keyword: "' .. trimmed .. '"', vim.log.levels.DEBUG)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
if vim.g.shellspec_debug then
|
||||
vim.notify('ShellSpec: Not a block keyword: "' .. trimmed .. '"', vim.log.levels.DEBUG)
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -113,13 +177,6 @@ function M.format_lines(lines)
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- Handle comments with proper indentation
|
||||
if is_comment(line) and indent_comments then
|
||||
local formatted_line = make_indent(indent_level) .. trimmed
|
||||
table.insert(result, formatted_line)
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- Handle End keyword (decrease indent first)
|
||||
if is_end_keyword(line) then
|
||||
indent_level = math.max(0, indent_level - 1)
|
||||
@@ -128,18 +185,30 @@ function M.format_lines(lines)
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- Apply normal indentation for other lines
|
||||
if not is_comment(line) or not indent_comments then
|
||||
local formatted_line = make_indent(indent_level) .. trimmed
|
||||
table.insert(result, formatted_line)
|
||||
|
||||
-- Increase indent after block keywords
|
||||
if is_block_keyword(line) then
|
||||
indent_level = indent_level + 1
|
||||
-- Handle comments
|
||||
if is_comment(line) then
|
||||
if indent_comments then
|
||||
local formatted_line = make_indent(indent_level) .. trimmed
|
||||
table.insert(result, formatted_line)
|
||||
else
|
||||
-- Preserve original comment formatting
|
||||
table.insert(result, line)
|
||||
end
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- Handle non-comment lines (ShellSpec commands, etc.)
|
||||
local formatted_line = make_indent(indent_level) .. trimmed
|
||||
table.insert(result, formatted_line)
|
||||
|
||||
-- Increase indent after block keywords
|
||||
if is_block_keyword(line) then
|
||||
indent_level = indent_level + 1
|
||||
|
||||
-- Debug logging
|
||||
if vim.g.shellspec_debug then
|
||||
vim.notify('ShellSpec: Block keyword detected: "' .. trimmed .. '", new indent: ' .. indent_level, vim.log.levels.DEBUG)
|
||||
end
|
||||
else
|
||||
-- Preserve original comment formatting if indent_comments is false
|
||||
table.insert(result, line)
|
||||
end
|
||||
elseif state == State.IN_HEREDOC then
|
||||
-- Check for HEREDOC end
|
||||
@@ -164,17 +233,28 @@ end
|
||||
-- Format entire buffer
|
||||
function M.format_buffer(bufnr)
|
||||
bufnr = bufnr or 0
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local formatted = M.format_lines(lines)
|
||||
|
||||
-- Store cursor position
|
||||
local cursor_pos = vim.api.nvim_win_get_cursor(0)
|
||||
local ok, err = pcall(function()
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local formatted = M.format_lines(lines)
|
||||
|
||||
-- Replace buffer content
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, formatted)
|
||||
-- Store cursor position
|
||||
local cursor_pos = vim.api.nvim_win_get_cursor(0)
|
||||
|
||||
-- Restore cursor position
|
||||
pcall(vim.api.nvim_win_set_cursor, 0, cursor_pos)
|
||||
-- Replace buffer content
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, formatted)
|
||||
|
||||
-- Restore cursor position
|
||||
pcall(vim.api.nvim_win_set_cursor, 0, cursor_pos)
|
||||
|
||||
if vim.g.shellspec_debug then
|
||||
vim.notify("ShellSpec: Formatted " .. #lines .. " lines", vim.log.levels.INFO)
|
||||
end
|
||||
end)
|
||||
|
||||
if not ok then
|
||||
vim.notify("ShellSpec: Format buffer failed - " .. tostring(err), vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
-- Format selection
|
||||
|
||||
Reference in New Issue
Block a user