diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f9b3f93 --- /dev/null +++ b/Makefile @@ -0,0 +1,290 @@ +# Makefile for nvim-shellspec +# Provides help, linting, testing, and release functionality + +# Colors for output +RED := \033[0;31m +GREEN := \033[0;32m +YELLOW := \033[1;33m +BLUE := \033[0;34m +NC := \033[0m # No Color + +# Version files +VERSION_LUA := lua/shellspec/init.lua +VERSION_VIM := plugin/shellspec.vim +VERSION_BIN := bin/shellspec-format + +# Commands +MAKE := make +PRE_COMMIT := pre-commit +TEST_RUNNER := ./tests/run_tests.sh + +# Default target +.PHONY: help +help: ## Display this help message + @echo "$(BLUE)nvim-shellspec Makefile$(NC)" + @echo "$(BLUE)==========================================$(NC)" + @echo "" + @echo "$(GREEN)Available targets:$(NC)" + @echo "" + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " $(YELLOW)%-20s$(NC) %s\n", $$1, $$2}' $(MAKEFILE_LIST) + @echo "" + @echo "$(GREEN)Current versions:$(NC)" + @$(MAKE) --no-print-directory version-check + @echo "" + @echo "$(GREEN)Usage examples:$(NC)" + @echo " $(YELLOW)make test$(NC) # Run all tests" + @echo " $(YELLOW)make lint$(NC) # Run all linters" + @echo " $(YELLOW)make release-patch$(NC) # Bump patch version and create tag" + @echo "" + +.PHONY: check +check: ## Quick health check (verify tools and version consistency) + @echo "$(BLUE)Running health check...$(NC)" + @echo "" + @echo "$(GREEN)Checking required tools:$(NC)" + @which pre-commit >/dev/null 2>&1 && echo " ✓ pre-commit found" || echo " $(RED)✗ pre-commit not found$(NC)" + @which git >/dev/null 2>&1 && echo " ✓ git found" || echo " $(RED)✗ git not found$(NC)" + @which bash >/dev/null 2>&1 && echo " ✓ bash found" || echo " $(RED)✗ bash not found$(NC)" + @test -f $(TEST_RUNNER) && echo " ✓ test runner found" || echo " $(RED)✗ test runner not found$(NC)" + @echo "" + @echo "$(GREEN)Version consistency:$(NC)" + @$(MAKE) --no-print-directory version-check + @echo "" + +.PHONY: version +version: version-check ## Display current versions + +.PHONY: version-check +version-check: ## Check version consistency across files + @echo "$(GREEN)Version information:$(NC)" + @lua_version=$$(grep '_VERSION = ' $(VERSION_LUA) | sed 's/.*"\(.*\)".*/\1/'); \ + vim_version=$$(grep "g:shellspec_version = " $(VERSION_VIM) | sed "s/.*'\(.*\)'.*/\1/"); \ + bin_version=$$(grep 'echo "shellspec-format ' $(VERSION_BIN) | sed 's/.*shellspec-format \([0-9.]*\).*/\1/'); \ + echo " Lua module: $$lua_version"; \ + echo " VimScript: $$vim_version"; \ + echo " Binary script: $$bin_version"; \ + if [ "$$lua_version" = "$$vim_version" ] && [ "$$vim_version" = "$$bin_version" ]; then \ + echo " $(GREEN)✓ All versions match$(NC)"; \ + else \ + echo " $(RED)✗ Version mismatch detected$(NC)"; \ + exit 1; \ + fi + +# Linting targets +.PHONY: lint +lint: ## Run all linters + @echo "$(BLUE)Running all linters...$(NC)" + $(PRE_COMMIT) run --all-files + +.PHONY: lint-fix +lint-fix: format ## Run linters with auto-fix (alias for format) + +.PHONY: format +format: ## Format all code (auto-fix where possible) + @echo "$(BLUE)Formatting all code...$(NC)" + $(PRE_COMMIT) run --all-files + +.PHONY: lint-lua +lint-lua: ## Format Lua code with StyLua + @echo "$(BLUE)Formatting Lua code...$(NC)" + $(PRE_COMMIT) run stylua-github --all-files + +.PHONY: lint-shell +lint-shell: ## Lint shell scripts with ShellCheck and format with shfmt + @echo "$(BLUE)Linting shell scripts...$(NC)" + $(PRE_COMMIT) run shellcheck --all-files + $(PRE_COMMIT) run shfmt --all-files + +.PHONY: lint-markdown +lint-markdown: ## Lint and format Markdown files + @echo "$(BLUE)Linting Markdown files...$(NC)" + $(PRE_COMMIT) run markdownlint --all-files + +.PHONY: lint-yaml +lint-yaml: ## Lint YAML files + @echo "$(BLUE)Linting YAML files...$(NC)" + $(PRE_COMMIT) run yamllint --all-files + +# Testing targets +.PHONY: test +test: ## Run complete test suite + @echo "$(BLUE)Running complete test suite...$(NC)" + $(TEST_RUNNER) + +.PHONY: test-unit +test-unit: ## Run only Lua unit tests + @echo "$(BLUE)Running unit tests...$(NC)" + cd tests && timeout 30 nvim --headless -u NONE -c "set rtp+=.." -c "luafile format_spec.lua" -c "quit" + +.PHONY: test-integration +test-integration: ## Run integration tests + @echo "$(BLUE)Running integration tests...$(NC)" + cd tests && timeout 30 ./integration_test.sh + +.PHONY: test-golden +test-golden: ## Run golden master tests + @echo "$(BLUE)Running golden master tests...$(NC)" + cd tests && timeout 30 ./golden_master_test.sh + +.PHONY: test-bin +test-bin: ## Run standalone formatter tests + @echo "$(BLUE)Running standalone formatter tests...$(NC)" + cd tests && ./bin_format_spec.sh + +# Release targets +.PHONY: release +release: ## Interactive release (prompts for version type) + @echo "$(BLUE)Interactive Release$(NC)" + @echo "" + @echo "Select release type:" + @echo " 1) $(GREEN)patch$(NC) (2.0.0 → 2.0.1) - Bug fixes" + @echo " 2) $(YELLOW)minor$(NC) (2.0.0 → 2.1.0) - New features" + @echo " 3) $(RED)major$(NC) (2.0.0 → 3.0.0) - Breaking changes" + @echo "" + @read -p "Enter choice (1-3): " choice; \ + case $$choice in \ + 1) $(MAKE) release-patch ;; \ + 2) $(MAKE) release-minor ;; \ + 3) $(MAKE) release-major ;; \ + *) echo "$(RED)Invalid choice$(NC)"; exit 1 ;; \ + esac + +.PHONY: release-patch +release-patch: ## Bump patch version (X.Y.Z → X.Y.Z+1) + @$(MAKE) --no-print-directory _release TYPE=patch + +.PHONY: release-minor +release-minor: ## Bump minor version (X.Y.Z → X.Y+1.0) + @$(MAKE) --no-print-directory _release TYPE=minor + +.PHONY: release-major +release-major: ## Bump major version (X.Y.Z → X+1.0.0) + @$(MAKE) --no-print-directory _release TYPE=major + +.PHONY: _release +_release: ## Internal release target (use release-* targets instead) + @if [ "$(TYPE)" = "" ]; then echo "$(RED)Error: TYPE not specified$(NC)"; exit 1; fi + @echo "$(BLUE)Starting $(TYPE) release...$(NC)" + @echo "" + + # Check git status + @echo "$(GREEN)Checking git status...$(NC)" + @if [ -n "$$(git status --porcelain)" ]; then \ + echo "$(RED)Error: Working directory not clean$(NC)"; \ + git status --short; \ + exit 1; \ + fi + @echo " ✓ Working directory is clean" + + # Check version consistency + @echo "" + @echo "$(GREEN)Checking version consistency...$(NC)" + @$(MAKE) --no-print-directory version-check + + # Run tests + @echo "" + @echo "$(GREEN)Running tests...$(NC)" + @$(MAKE) --no-print-directory test + + # Run linters + @echo "" + @echo "$(GREEN)Running linters...$(NC)" + @$(MAKE) --no-print-directory lint + + # Calculate new version + @echo "" + @echo "$(GREEN)Calculating new version...$(NC)" + @current_version=$$(grep '_VERSION = ' $(VERSION_LUA) | sed 's/.*"\(.*\)".*/\1/'); \ + echo " Current version: $$current_version"; \ + new_version=$$(echo "$$current_version" | awk -F. -v type=$(TYPE) '{ \ + if (type == "major") printf "%d.0.0", $$1+1; \ + else if (type == "minor") printf "%d.%d.0", $$1, $$2+1; \ + else if (type == "patch") printf "%d.%d.%d", $$1, $$2, $$3+1; \ + }'); \ + echo " New version: $$new_version"; \ + echo ""; \ + read -p "Continue with release? (y/N): " confirm; \ + if [ "$$confirm" != "y" ] && [ "$$confirm" != "Y" ]; then \ + echo "$(YELLOW)Release cancelled$(NC)"; \ + exit 1; \ + fi; \ + echo ""; \ + echo "$(GREEN)Updating version in files...$(NC)"; \ + sed -i.bak "s/M._VERSION = \".*\"/M._VERSION = \"$$new_version\"/" $(VERSION_LUA) && rm $(VERSION_LUA).bak; \ + sed -i.bak "s/let g:shellspec_version = '.*'/let g:shellspec_version = '$$new_version'/" $(VERSION_VIM) && rm $(VERSION_VIM).bak; \ + sed -i.bak "s/shellspec-format [0-9.]*/shellspec-format $$new_version/" $(VERSION_BIN) && rm $(VERSION_BIN).bak; \ + echo " ✓ Updated $(VERSION_LUA)"; \ + echo " ✓ Updated $(VERSION_VIM)"; \ + echo " ✓ Updated $(VERSION_BIN)"; \ + echo ""; \ + echo "$(GREEN)Creating git commit...$(NC)"; \ + git add $(VERSION_LUA) $(VERSION_VIM) $(VERSION_BIN); \ + git commit -m "chore: bump version to $$new_version"; \ + echo " ✓ Created commit"; \ + echo ""; \ + echo "$(GREEN)Creating git tag...$(NC)"; \ + git tag -a "v$$new_version" -m "Release version $$new_version"; \ + echo " ✓ Created tag v$$new_version"; \ + echo ""; \ + echo "$(GREEN)$(TYPE) release completed successfully!$(NC)"; \ + echo ""; \ + echo "$(BLUE)Next steps:$(NC)"; \ + echo " 1. Review the changes: $(YELLOW)git show$(NC)"; \ + echo " 2. Push the release: $(YELLOW)git push origin main --tags$(NC)"; \ + echo " 3. Create GitHub release from tag v$$new_version"; \ + echo "" + +# Utility targets +.PHONY: clean +clean: ## Remove temporary files and test artifacts + @echo "$(BLUE)Cleaning temporary files...$(NC)" + find . -name "*.bak" -delete + find . -name "*.tmp" -delete + find /tmp -name "*shellspec*" -delete 2>/dev/null || true + find /var/folders -name "*shellspec*" -delete 2>/dev/null || true + @echo " ✓ Cleaned temporary files" + +.PHONY: install +install: ## Install pre-commit hooks + @echo "$(BLUE)Installing pre-commit hooks...$(NC)" + $(PRE_COMMIT) install + @echo " ✓ Pre-commit hooks installed" + +# Development convenience targets +.PHONY: dev-setup +dev-setup: install ## Set up development environment + @echo "$(BLUE)Setting up development environment...$(NC)" + @$(MAKE) --no-print-directory check + @echo "" + @echo "$(GREEN)Development environment ready!$(NC)" + +.PHONY: ci +ci: check test lint ## Run CI pipeline (check, test, lint) + @echo "" + @echo "$(GREEN)CI pipeline completed successfully!$(NC)" + +# Debug targets +.PHONY: debug +debug: ## Show debug information + @echo "$(BLUE)Debug Information$(NC)" + @echo "$(BLUE)==================$(NC)" + @echo "" + @echo "$(GREEN)Environment:$(NC)" + @echo " PWD: $(PWD)" + @echo " SHELL: $(SHELL)" + @echo " MAKE: $(MAKE)" + @echo "" + @echo "$(GREEN)Git status:$(NC)" + @git status --short || echo " Not in git repository" + @echo "" + @echo "$(GREEN)Tools:$(NC)" + @echo " pre-commit: $$(which pre-commit || echo 'not found')" + @echo " git: $$(which git || echo 'not found')" + @echo " nvim: $$(which nvim || echo 'not found')" + @echo "" + @$(MAKE) --no-print-directory version-check + +# Ensure all targets are PHONY (no file dependencies) +.PHONY: _release help check version version-check lint lint-fix format lint-lua lint-shell lint-markdown lint-yaml +.PHONY: test test-unit test-integration test-golden test-bin release release-patch release-minor release-major +.PHONY: clean install dev-setup ci debug diff --git a/bin/shellspec-format b/bin/shellspec-format index 52b29e6..70762d2 100755 --- a/bin/shellspec-format +++ b/bin/shellspec-format @@ -1,46 +1,305 @@ #!/bin/bash -# Standalone ShellSpec DSL formatter +# Enhanced ShellSpec DSL formatter with HEREDOC and comment support +# Matches functionality from the nvim-shellspec plugin +set -e + +# Default configuration +INDENT_SIZE=2 +USE_SPACES=1 +INDENT_COMMENTS=1 +DEBUG=0 + +# State constants +STATE_NORMAL=1 +STATE_HEREDOC=2 + +# Usage information +usage() { + cat <<'EOF' +Usage: shellspec-format [OPTIONS] [FILE...] + +Enhanced ShellSpec DSL formatter with HEREDOC preservation and smart comment indentation. + +OPTIONS: + -h, --help Show this help message + -s, --indent-size SIZE Set indentation size (default: 2) + -t, --tabs Use tabs instead of spaces + -n, --no-comment-indent Don't indent comments + -d, --debug Enable debug output + -v, --version Show version information + +If no files are specified, reads from stdin and writes to stdout. +If files are specified, formats them in place. + +EXAMPLES: + shellspec-format < input.spec.sh > output.spec.sh + shellspec-format file1.spec.sh file2.spec.sh + cat file.spec.sh | shellspec-format --indent-size 4 --tabs + +EOF +} + +version() { + echo "shellspec-format 2.0.0" + echo "Part of nvim-shellspec plugin" +} + +# Debug logging +debug_log() { + if [[ $DEBUG -eq 1 ]]; then + echo "DEBUG: $*" >&2 + fi +} + +# Detect HEREDOC start and return delimiter +detect_heredoc_start() { + local line="$1" + local trimmed + trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + # Check for various HEREDOC patterns + local patterns=( + "<<([A-Z_][A-Z0-9_]*)" + "<<'([^']*)'" + "<<\"([^\"]*)\"" + "<<-([A-Z_][A-Z0-9_]*)" + ) + + for pattern in "${patterns[@]}"; do + if [[ $trimmed =~ $pattern ]]; then + echo "${BASH_REMATCH[1]}" + return 0 + fi + done + + return 1 +} + +# Check if line ends a HEREDOC +is_heredoc_end() { + local line="$1" + local delimiter="$2" + local trimmed + trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + [[ -n "$delimiter" && "$trimmed" == "$delimiter" ]] +} + +# Check if line is a ShellSpec block keyword +is_block_keyword() { + local line="$1" + local trimmed + trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + debug_log "Checking if block keyword: '$trimmed'" + + # Standard block keywords + if [[ $trimmed =~ ^(Describe|Context|ExampleGroup|It|Specify|Example)[[:space:]] ]]; then + debug_log "Matched standard block keyword: '$trimmed'" + return 0 + fi + + # Prefixed block keywords (x for skip, f for focus) + if [[ $trimmed =~ ^[xf](Describe|Context|ExampleGroup|It|Specify|Example)[[:space:]] ]]; then + debug_log "Matched prefixed block keyword: '$trimmed'" + return 0 + fi + + # Data and Parameters blocks + if [[ $trimmed =~ ^(Data|Parameters)[[:space:]]*$ ]]; then + debug_log "Matched data/parameters block: '$trimmed'" + return 0 + fi + + # Hook keywords that create blocks (can be standalone) + if [[ $trimmed =~ ^(BeforeEach|AfterEach|BeforeAll|AfterAll|Before|After)[[:space:]]*$ ]]; then + debug_log "Matched hook keyword: '$trimmed'" + return 0 + fi + + # Additional hook keywords (can be standalone) + if [[ $trimmed =~ ^(BeforeCall|AfterCall|BeforeRun|AfterRun)[[:space:]]*$ ]]; then + debug_log "Matched additional hook keyword: '$trimmed'" + return 0 + fi + + debug_log "Not a block keyword: '$trimmed'" + return 1 +} + +# Check if line is an End keyword +is_end_keyword() { + local line="$1" + local trimmed + trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + [[ $trimmed =~ ^End[[:space:]]*$ ]] +} + +# Check if line is a comment +is_comment() { + local line="$1" + local trimmed + trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + [[ $trimmed =~ ^# ]] +} + +# Generate indentation string +make_indent() { + local level="$1" + local total_indent=$((level * INDENT_SIZE)) + + if [[ $USE_SPACES -eq 1 ]]; then + printf "%*s" $total_indent "" + else + printf "%*s" $level "" | tr ' ' '\t' + fi +} + +# Main formatting function format_shellspec() { - local indent=0 + local indent_level=0 + local state=$STATE_NORMAL + local heredoc_delimiter="" local line while IFS= read -r line; do local trimmed trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - # Skip empty lines and comments - if [[ -z "$trimmed" || "$trimmed" =~ ^# ]]; then + # Handle empty lines + if [[ -z "$trimmed" ]]; then echo "$line" continue fi - # Decrease indent for End - if [[ "$trimmed" =~ ^End[[:space:]]*$ ]]; then - ((indent > 0)) && ((indent--)) - fi + # State machine for HEREDOC handling + case $state in + "$STATE_NORMAL") + # Check for HEREDOC start + if heredoc_delimiter=$(detect_heredoc_start "$line"); then + state=$STATE_HEREDOC + debug_log "HEREDOC start detected: '$heredoc_delimiter'" + # Apply current indentation to HEREDOC start line + printf "%s%s\n" "$(make_indent $indent_level)" "$trimmed" + continue + fi - # Apply indentation - printf "%*s%s\n" $((indent * 2)) "" "$trimmed" + # Handle End keyword (decrease indent first) + if is_end_keyword "$line"; then + ((indent_level > 0)) && ((indent_level--)) + printf "%s%s\n" "$(make_indent $indent_level)" "$trimmed" + continue + fi - # Increase indent after block keywords - if [[ "$trimmed" =~ ^(Describe|Context|ExampleGroup|It|Specify|Example) ]] || - [[ "$trimmed" =~ ^[xf](Describe|Context|ExampleGroup|It|Specify|Example) ]] || - [[ "$trimmed" =~ ^(Data|Parameters)[[:space:]]*$ ]]; then - ((indent++)) - fi + # Handle comments + if is_comment "$line"; then + if [[ $INDENT_COMMENTS -eq 1 ]]; then + printf "%s%s\n" "$(make_indent $indent_level)" "$trimmed" + else + # Preserve original comment formatting + echo "$line" + fi + continue + fi + + # Handle non-comment lines (ShellSpec commands, etc.) + printf "%s%s\n" "$(make_indent $indent_level)" "$trimmed" + + # Increase indent after block keywords + if is_block_keyword "$line"; then + ((indent_level++)) + debug_log "Block keyword detected: '$trimmed', new indent: $indent_level" + fi + ;; + + "$STATE_HEREDOC") + # Check for HEREDOC end + if is_heredoc_end "$line" "$heredoc_delimiter"; then + state=$STATE_NORMAL + debug_log "HEREDOC end detected: '$heredoc_delimiter'" + # Apply current indentation to HEREDOC end line + printf "%s%s\n" "$(make_indent $indent_level)" "$trimmed" + heredoc_delimiter="" + else + # Preserve original indentation within HEREDOC + echo "$line" + fi + ;; + esac done } +# Parse command line options +while [[ $# -gt 0 ]]; do + case $1 in + -h | --help) + usage + exit 0 + ;; + -v | --version) + version + exit 0 + ;; + -s | --indent-size) + INDENT_SIZE="$2" + if ! [[ $INDENT_SIZE =~ ^[0-9]+$ ]] || [[ $INDENT_SIZE -lt 1 ]]; then + echo "Error: indent-size must be a positive integer" >&2 + exit 1 + fi + shift 2 + ;; + -t | --tabs) + USE_SPACES=0 + shift + ;; + -n | --no-comment-indent) + INDENT_COMMENTS=0 + shift + ;; + -d | --debug) + DEBUG=1 + shift + ;; + --) + shift + break + ;; + -*) + echo "Error: Unknown option $1" >&2 + echo "Use --help for usage information" >&2 + exit 1 + ;; + *) + break + ;; + esac +done + # Main execution if [[ $# -eq 0 ]]; then + # Read from stdin, write to stdout + debug_log "Reading from stdin" format_shellspec else + # Process files in place for file in "$@"; do if [[ -f "$file" ]]; then - format_shellspec <"$file" >"${file}.tmp" && mv "${file}.tmp" "$file" + debug_log "Processing file: $file" + temp_file=$(mktemp) + if format_shellspec <"$file" >"$temp_file"; then + mv "$temp_file" "$file" + debug_log "Successfully formatted: $file" + else + rm -f "$temp_file" + echo "Error: Failed to format $file" >&2 + exit 1 + fi else echo "Error: File not found: $file" >&2 + exit 1 fi done fi diff --git a/plugin/shellspec.vim b/plugin/shellspec.vim index 3f3e922..5f03140 100644 --- a/plugin/shellspec.vim +++ b/plugin/shellspec.vim @@ -8,6 +8,9 @@ if exists('g:loaded_shellspec') endif let g:loaded_shellspec = 1 +" Version information +let g:shellspec_version = '2.0.0' + " Detect Neovim and use appropriate implementation if has('nvim-0.7') " Use modern Neovim Lua implementation diff --git a/tests/bin_format_spec.sh b/tests/bin_format_spec.sh new file mode 100755 index 0000000..6b10de8 --- /dev/null +++ b/tests/bin_format_spec.sh @@ -0,0 +1,323 @@ +#!/bin/bash +# Unit tests for bin/shellspec-format standalone formatter +# Tests the CLI formatter against the same test cases used for Lua implementation + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Test counters +TESTS_PASSED=0 +TESTS_FAILED=0 + +# Get the script directory and project root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +FORMATTER="$PROJECT_ROOT/bin/shellspec-format" + +# Helper functions +print_test() { + echo -e "${YELLOW}[BIN-TEST]${NC} $1" + # Force flush + exec 1>&1 +} + +print_pass() { + echo -e "${GREEN}[PASS]${NC} $1" + ((TESTS_PASSED++)) + # Force flush + exec 1>&1 +} + +print_fail() { + echo -e "${RED}[FAIL]${NC} $1" + ((TESTS_FAILED++)) + # Force flush + exec 1>&1 +} + +print_summary() { + echo "" + echo "Standalone Formatter Test Results:" + echo " Passed: $TESTS_PASSED" + echo " Failed: $TESTS_FAILED" + echo " Total: $((TESTS_PASSED + TESTS_FAILED))" + + if [ $TESTS_FAILED -gt 0 ]; then + echo -e "${RED}Some standalone formatter tests failed!${NC}" + exit 1 + else + echo -e "${GREEN}All standalone formatter tests passed!${NC}" + fi +} + +# Function to run a formatting test +run_format_test() { + local test_name="$1" + local input_content="$2" + local expected_content="$3" + + print_test "Testing $test_name" + + # Create temporary files + local input_file + local expected_file + local actual_file + input_file=$(mktemp -t "bin_format_input_XXXXXX.spec.sh") + expected_file=$(mktemp -t "bin_format_expected_XXXXXX.spec.sh") + actual_file=$(mktemp -t "bin_format_actual_XXXXXX.spec.sh") + + # Debug: Show what we're testing + if [[ -n "${DEBUG:-}" ]]; then + echo " Input file: $input_file" + echo " Expected file: $expected_file" + echo " Actual file: $actual_file" + fi + + # Write test data to files + printf "%s\n" "$input_content" >"$input_file" + printf "%s\n" "$expected_content" >"$expected_file" + + # Format using the standalone formatter + if timeout 10 "$FORMATTER" <"$input_file" >"$actual_file" 2>/dev/null; then + # Compare with expected output + if diff -u "$expected_file" "$actual_file" >/dev/null; then + print_pass "$test_name formatting matches expected output" + else + print_fail "$test_name formatting does not match expected output" + echo "Expected:" + cat "$expected_file" + echo "" + echo "Actual:" + cat "$actual_file" + echo "" + echo "Diff:" + diff -u "$expected_file" "$actual_file" || true + echo "" + fi + else + print_fail "$test_name formatting command failed" + fi + + # Clean up + rm -f "$input_file" "$expected_file" "$actual_file" +} + +# Function to test CLI options +test_cli_options() { + local test_name="$1" + local options="$2" + local input_content="$3" + local expected_content="$4" + + print_test "Testing $test_name" + + # Create temporary files + local input_file + local expected_file + local actual_file + input_file=$(mktemp -t "bin_format_cli_input_XXXXXX.spec.sh") + expected_file=$(mktemp -t "bin_format_cli_expected_XXXXXX.spec.sh") + actual_file=$(mktemp -t "bin_format_cli_actual_XXXXXX.spec.sh") + + # Write test data to files + printf "%s\n" "$input_content" >"$input_file" + printf "%s\n" "$expected_content" >"$expected_file" + + # Format using the standalone formatter with options + if timeout 10 bash -c "$FORMATTER $options < '$input_file' > '$actual_file'" 2>/dev/null; then + # Compare with expected output + if diff -u "$expected_file" "$actual_file" >/dev/null; then + print_pass "$test_name formatting with options matches expected output" + else + print_fail "$test_name formatting with options does not match expected output" + echo "Options: $options" + echo "Expected:" + cat "$expected_file" + echo "" + echo "Actual:" + cat "$actual_file" + echo "" + echo "Diff:" + diff -u "$expected_file" "$actual_file" || true + echo "" + fi + else + print_fail "$test_name formatting command with options failed" + fi + + # Clean up + rm -f "$input_file" "$expected_file" "$actual_file" +} + +echo "Running bin/shellspec-format standalone formatter tests..." +echo "Project root: $PROJECT_ROOT" +echo "Formatter: $FORMATTER" +echo "" + +# Verify formatter exists and is executable +if [[ ! -x "$FORMATTER" ]]; then + echo -e "${RED}Error: Formatter not found or not executable: $FORMATTER${NC}" + exit 1 +fi + +# Test 1: Basic block indentation (ported from format_spec.lua) +input1='Describe "test" +It "should work" +End +End' +expected1='Describe "test" + It "should work" + End +End' +run_format_test "Basic block indentation" "$input1" "$expected1" + +# Test 2: Comment indentation (ported from format_spec.lua) +input2='Describe "test" +# Comment at Describe level +It "should work" +# Comment at It level +When call echo "test" +End +End' +expected2='Describe "test" + # Comment at Describe level + It "should work" + # Comment at It level + When call echo "test" + End +End' +run_format_test "Comment indentation" "$input2" "$expected2" + +# Test 3: HEREDOC preservation (ported from format_spec.lua) +run_format_test \ + "HEREDOC preservation" \ + 'Describe "test" +It "handles heredoc" +When call cat </dev/null 2>&1; then + print_fail "Should have failed with invalid indent size" +else + print_pass "Correctly rejected invalid indent size" +fi + +print_test "Testing error handling - unknown option" +if timeout 5 echo 'test' | "$FORMATTER" --unknown-option >/dev/null 2>&1; then + print_fail "Should have failed with unknown option" +else + print_pass "Correctly rejected unknown option" +fi + +print_summary diff --git a/tests/run_tests.sh b/tests/run_tests.sh index facc4f6..7f02fe0 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -15,6 +15,7 @@ NC='\033[0m' # No Color UNIT_PASSED=false INTEGRATION_PASSED=false GOLDEN_PASSED=false +BIN_FORMAT_PASSED=false # Get the script directory and project root SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -110,6 +111,9 @@ echo "" echo -e "${BLUE}----------------------------------------${NC}" echo "" +# Run bin formatter tests +run_test_suite "Standalone Formatter Tests" "script" "./tests/bin_format_spec.sh" BIN_FORMAT_PASSED + # Summary echo -e "${BLUE}========================================${NC}" echo -e "${BLUE} Test Results Summary ${NC}" @@ -134,10 +138,16 @@ else echo -e "${RED}✗ Golden Master Tests: FAILED${NC}" fi +if [ "$BIN_FORMAT_PASSED" = true ]; then + echo -e "${GREEN}✓ Standalone Formatter Tests: PASSED${NC}" +else + echo -e "${RED}✗ Standalone Formatter Tests: FAILED${NC}" +fi + echo "" # Overall result -if [ "$UNIT_PASSED" = true ] && [ "$INTEGRATION_PASSED" = true ] && [ "$GOLDEN_PASSED" = true ]; then +if [ "$UNIT_PASSED" = true ] && [ "$INTEGRATION_PASSED" = true ] && [ "$GOLDEN_PASSED" = true ] && [ "$BIN_FORMAT_PASSED" = true ]; then echo -e "${GREEN}🎉 ALL TESTS COMPLETED SUCCESSFULLY! 🎉${NC}" echo "" echo -e "${GREEN}The nvim-shellspec plugin is ready for use!${NC}"