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:
2025-09-09 23:11:47 +03:00
parent ce620cd035
commit b54d6ed365
10 changed files with 1079 additions and 69 deletions

View File

@@ -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,

View File

@@ -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