diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..be7deaf --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,28 @@ +name: Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + test: + name: Run ZSH Configuration Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run tests + run: | + bash tests/run_tests.sh + + - name: Test Summary + if: always() + run: | + echo "## Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Tests completed. Check the logs above for details." >> $GITHUB_STEP_SUMMARY diff --git a/README.md b/README.md index bff53b3..520d758 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,34 @@ zdotdir/ export ZDOTDIR="$HOME/.config/zsh" ``` +## Testing + +This repository includes a comprehensive test suite to verify that the configuration is properly structured and that important tools are correctly integrated. + +### Running Tests + +Run all tests: +```bash +./tests/run_tests.sh +``` + +Run specific test files: +```bash +./tests/run_tests.sh "test_core_tools.sh" +``` + +### Test Coverage + +The test suite includes: + +- **Core Tools Integration**: Tests for mise, fzf, and zoxide configuration +- **Configuration Files**: Tests for .zshrc, .zshenv, .zprofile structure +- **Custom Functions**: Tests for autoloaded functions in the `functions/` directory +- **Aliases**: Tests for alias definitions +- **Custom Commands**: Tests for shell functions in rc.d/06-commands.zsh + +For more details, see [tests/README.md](tests/README.md). + ## Aliases Documentation This document provides a comprehensive list of all available aliases organized by category. diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..b15822a --- /dev/null +++ b/tests/README.md @@ -0,0 +1,156 @@ +# ZSH Configuration Tests + +This directory contains tests for the ZSH configuration. The tests verify that the configuration files are properly structured and that important tools like mise, fzf, and zoxide are correctly integrated. + +## Test Structure + +``` +tests/ +├── run_tests.sh # Main test runner +├── test_helpers.sh # Test assertion functions and utilities +├── integration/ # Integration tests +│ └── test_core_tools.sh # Tests for mise, fzf, zoxide integration +└── unit/ # Unit tests + ├── test_aliases.sh # Tests for alias definitions + ├── test_commands.sh # Tests for custom shell functions + ├── test_config.sh # Tests for ZSH configuration files + └── test_functions.sh # Tests for autoloaded functions +``` + +## Running Tests + +### Run All Tests + +```bash +./tests/run_tests.sh +``` + +### Run Specific Test Files + +You can run specific test files by passing a pattern: + +```bash +./tests/run_tests.sh "test_core_tools.sh" +``` + +Or run tests from a specific directory: + +```bash +bash tests/unit/test_config.sh +``` + +## Test Categories + +### Integration Tests + +**Core Tools Integration** (`integration/test_core_tools.sh`) +- Verifies mise configuration file exists and has proper content +- Verifies fzf configuration file exists and has proper content +- Verifies zoxide configuration file exists and has proper content +- Tests command check patterns (returns 1 when command is missing) +- Ensures configuration files are minimal and focused + +### Unit Tests + +**Custom Functions** (`unit/test_functions.sh`) +- Tests that all function files exist in the `functions/` directory +- Verifies functions have proper shebangs +- Tests specific function content (bench-startup, grecent, etc.) +- Checks that functions use expected tools (fzf, git, jq, etc.) + +**ZSH Configuration Files** (`unit/test_config.sh`) +- Tests main configuration files exist (.zshrc, .zshenv, .zprofile) +- Verifies configuration content (antidote loading, rc.d sourcing) +- Tests rc.d file structure and naming conventions +- Verifies environment variables are set correctly + +**Aliases** (`unit/test_aliases.sh`) +- Tests that alias file exists and has content +- Verifies common aliases are defined +- Checks alias syntax is correct + +**Custom Commands** (`unit/test_commands.sh`) +- Tests that command functions are defined +- Verifies integration with fzf and jq +- Tests PostgreSQL functions use mise +- Checks function documentation + +## Test Helpers + +The `test_helpers.sh` file provides several assertion functions: + +- `assert_success(description, command)` - Asserts command succeeds +- `assert_failure(description, command)` - Asserts command fails +- `assert_equals(description, expected, actual)` - Asserts equality +- `assert_contains(description, haystack, needle)` - Asserts substring match +- `assert_file_exists(description, file)` - Asserts file exists +- `assert_command_exists(description, command)` - Asserts command is available + +## Test Output + +Tests produce colored output: +- ✓ Green for passed tests +- ✗ Red for failed tests +- ℹ Yellow for informational messages + +Example output: +``` +Running test suite: Core Tools Integration +================================ +✓ mise.zsh configuration file exists +✓ fzf.zsh configuration file exists +✓ zoxide.zsh configuration file exists +... + +================================ +Test Summary +================================ +Total: 15 +Passed: 15 +Failed: 0 +``` + +## Adding New Tests + +To add new tests: + +1. Create a new test file in `tests/unit/` or `tests/integration/` +2. Name it `test_*.sh` (e.g., `test_my_feature.sh`) +3. Make it executable: `chmod +x tests/unit/test_my_feature.sh` +4. Source the test helpers: `source "$SCRIPT_DIR/../test_helpers.sh"` +5. Use `run_test_suite "Your Suite Name"` to start the suite +6. Add your test assertions using the helper functions +7. End with `print_summary` to show results + +Example test file: + +```bash +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +source "$SCRIPT_DIR/../test_helpers.sh" + +run_test_suite "My Feature Tests" + +assert_file_exists "my config exists" "$REPO_ROOT/my_config.zsh" +assert_contains "config has setting" "$(cat $REPO_ROOT/my_config.zsh)" "my_setting" + +print_summary +``` + +## Continuous Integration + +These tests can be run in CI environments. The test runner exits with: +- Exit code 0 if all tests pass +- Exit code 1 if any tests fail + +This makes it easy to integrate with GitHub Actions or other CI systems. + +## Requirements + +The tests are written in bash and use standard Unix tools: +- bash (for running tests) +- grep, awk, sed (for text processing) +- Standard file operations (cat, ls, wc) + +No ZSH is required to run the tests - they verify the configuration files themselves, not their runtime behavior. diff --git a/tests/integration/test_core_tools.sh b/tests/integration/test_core_tools.sh new file mode 100755 index 0000000..99d2323 --- /dev/null +++ b/tests/integration/test_core_tools.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# Integration tests for core tools: mise, fzf, zoxide +# These tests verify that the tool integration files work correctly + +# Get the directory of this script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Source test helpers +source "$SCRIPT_DIR/../test_helpers.sh" + +run_test_suite "Core Tools Integration" + +# Test mise configuration file +assert_file_exists "mise.zsh configuration file exists" "$REPO_ROOT/rc.d/mise.zsh" + +# Test fzf configuration file +assert_file_exists "fzf.zsh configuration file exists" "$REPO_ROOT/rc.d/fzf.zsh" + +# Test zoxide configuration file +assert_file_exists "zoixide.zsh configuration file exists" "$REPO_ROOT/rc.d/zoixide.zsh" + +# Test mise configuration content +test_mise_config() { + local content=$(cat "$REPO_ROOT/rc.d/mise.zsh") + + assert_contains "mise.zsh checks for mise command" "$content" '\$+commands\[mise\]' + assert_contains "mise.zsh activates mise" "$content" "mise activate zsh" +} + +test_mise_config + +# Test fzf configuration content +test_fzf_config() { + local content=$(cat "$REPO_ROOT/rc.d/fzf.zsh") + + assert_contains "fzf.zsh checks for fzf command" "$content" '\$+commands\[fzf\]' + assert_contains "fzf.zsh sources fzf integration" "$content" "fzf --zsh" +} + +test_fzf_config + +# Test zoxide configuration content +test_zoxide_config() { + local content=$(cat "$REPO_ROOT/rc.d/zoixide.zsh") + + assert_contains "zoixide.zsh checks for zoxide command" "$content" '\$+commands\[zoxide\]' + assert_contains "zoixide.zsh initializes zoxide" "$content" "zoxide init zsh" +} + +test_zoxide_config + +# Test that config files return 1 when command is missing +test_command_check_pattern() { + # All three files should use the same pattern for command checking + local mise_first_line=$(head -n 1 "$REPO_ROOT/rc.d/mise.zsh") + local fzf_first_line=$(head -n 1 "$REPO_ROOT/rc.d/fzf.zsh") + local zoxide_first_line=$(head -n 1 "$REPO_ROOT/rc.d/zoixide.zsh") + + # Check that they all follow the pattern: (($+commands[tool])) || return 1 + assert_contains "mise.zsh has proper command check" "$mise_first_line" "return 1" + assert_contains "fzf.zsh has proper command check" "$fzf_first_line" "return 1" + assert_contains "zoixide.zsh has proper command check" "$zoxide_first_line" "return 1" +} + +test_command_check_pattern + +# Test that configuration files are minimal +test_config_minimalism() { + local mise_lines=$(wc -l < "$REPO_ROOT/rc.d/mise.zsh") + local fzf_lines=$(wc -l < "$REPO_ROOT/rc.d/fzf.zsh") + local zoxide_lines=$(wc -l < "$REPO_ROOT/rc.d/zoixide.zsh") + + # Each file should be very minimal (less than 10 lines) + if [ "$mise_lines" -le 10 ]; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ mise.zsh is minimal ($mise_lines lines)" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ mise.zsh should be minimal (has $mise_lines lines)" + fi + + if [ "$fzf_lines" -le 10 ]; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ fzf.zsh is minimal ($fzf_lines lines)" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ fzf.zsh should be minimal (has $fzf_lines lines)" + fi + + if [ "$zoxide_lines" -le 10 ]; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ zoixide.zsh is minimal ($zoxide_lines lines)" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ zoixide.zsh should be minimal (has $zoxide_lines lines)" + fi +} + +test_config_minimalism + +print_summary diff --git a/tests/run_tests.sh b/tests/run_tests.sh new file mode 100755 index 0000000..dddaad3 --- /dev/null +++ b/tests/run_tests.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# Main test runner for ZSH configuration tests +# Usage: ./tests/run_tests.sh [test_file_pattern] + +set -e + +# Get the directory of this script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Source test helpers +source "$SCRIPT_DIR/test_helpers.sh" + +# Run all test files +run_all_tests() { + local pattern="${1:-*.sh}" + local test_files=() + local exit_code=0 + + # Find all test files + while IFS= read -r -d '' file; do + if [[ "$(basename "$file")" != "test_helpers.sh" ]] && \ + [[ "$(basename "$file")" != "run_tests.sh" ]]; then + test_files+=("$file") + fi + done < <(find "$SCRIPT_DIR" -type f -name "$pattern" -print0) + + # If no pattern match, try finding test files in subdirectories + if [ ${#test_files[@]} -eq 0 ]; then + while IFS= read -r -d '' file; do + if [[ "$(basename "$file")" != "test_helpers.sh" ]] && \ + [[ "$(basename "$file")" != "run_tests.sh" ]]; then + test_files+=("$file") + fi + done < <(find "$SCRIPT_DIR" -type f -name "test_*.sh" -print0) + fi + + if [ ${#test_files[@]} -eq 0 ]; then + print_yellow "No test files found matching pattern: $pattern" + return 0 + fi + + print_yellow "Found ${#test_files[@]} test file(s)" + echo "" + + # Run each test file + for test_file in "${test_files[@]}"; do + print_yellow "Running: $(basename "$test_file")" + echo "================================" + + if bash "$test_file"; then + echo "" + else + exit_code=1 + echo "" + fi + done + + return $exit_code +} + +# Main execution +main() { + print_yellow "ZSH Configuration Test Suite" + print_yellow "Repository: $REPO_ROOT" + echo "" + + if run_all_tests "$@"; then + print_summary + exit_code=$? + echo "" + if [ $exit_code -eq 0 ]; then + print_green "All tests passed!" + else + print_red "Some tests failed!" + fi + exit $exit_code + else + print_summary + echo "" + print_red "Test execution failed!" + exit 1 + fi +} + +main "$@" diff --git a/tests/test_helpers.sh b/tests/test_helpers.sh new file mode 100644 index 0000000..893b92d --- /dev/null +++ b/tests/test_helpers.sh @@ -0,0 +1,164 @@ +#!/usr/bin/env bash +# Test helper functions for ZSH configuration tests + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Test counters +TESTS_RUN=0 +TESTS_PASSED=0 +TESTS_FAILED=0 + +# Print colored output +print_red() { + printf "${RED}%s${NC}\n" "$1" +} + +print_green() { + printf "${GREEN}%s${NC}\n" "$1" +} + +print_yellow() { + printf "${YELLOW}%s${NC}\n" "$1" +} + +# Test assertion functions +assert_success() { + local description="$1" + local command="$2" + + TESTS_RUN=$((TESTS_RUN + 1)) + + if eval "$command" > /dev/null 2>&1; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ $description" + return 0 + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ $description" + print_red " Command failed: $command" + return 1 + fi +} + +assert_failure() { + local description="$1" + local command="$2" + + TESTS_RUN=$((TESTS_RUN + 1)) + + if ! eval "$command" > /dev/null 2>&1; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ $description" + return 0 + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ $description" + print_red " Command should have failed: $command" + return 1 + fi +} + +assert_equals() { + local description="$1" + local expected="$2" + local actual="$3" + + TESTS_RUN=$((TESTS_RUN + 1)) + + if [ "$expected" = "$actual" ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ $description" + return 0 + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ $description" + print_red " Expected: $expected" + print_red " Actual: $actual" + return 1 + fi +} + +assert_contains() { + local description="$1" + local haystack="$2" + local needle="$3" + + TESTS_RUN=$((TESTS_RUN + 1)) + + if echo "$haystack" | grep -q "$needle"; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ $description" + return 0 + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ $description" + print_red " Expected to find: $needle" + print_red " In: $haystack" + return 1 + fi +} + +assert_file_exists() { + local description="$1" + local file="$2" + + TESTS_RUN=$((TESTS_RUN + 1)) + + if [ -f "$file" ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ $description" + return 0 + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ $description" + print_red " File does not exist: $file" + return 1 + fi +} + +assert_command_exists() { + local description="$1" + local command="$2" + + TESTS_RUN=$((TESTS_RUN + 1)) + + if command -v "$command" >/dev/null 2>&1; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ $description" + return 0 + else + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ $description" + print_red " Command not found: $command" + return 1 + fi +} + +# Print test summary +print_summary() { + echo "" + echo "================================" + echo "Test Summary" + echo "================================" + echo "Total: $TESTS_RUN" + print_green "Passed: $TESTS_PASSED" + if [ $TESTS_FAILED -gt 0 ]; then + print_red "Failed: $TESTS_FAILED" + return 1 + else + echo "Failed: $TESTS_FAILED" + return 0 + fi +} + +# Run a test suite +run_test_suite() { + local suite_name="$1" + echo "" + print_yellow "Running test suite: $suite_name" + echo "================================" +} diff --git a/tests/unit/test_aliases.sh b/tests/unit/test_aliases.sh new file mode 100755 index 0000000..2165c95 --- /dev/null +++ b/tests/unit/test_aliases.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Unit tests for aliases +# These tests verify that aliases are properly defined + +# Get the directory of this script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Source test helpers +source "$SCRIPT_DIR/../test_helpers.sh" + +run_test_suite "Aliases Configuration" + +# Test aliases file exists +assert_file_exists "05-aliases.zsh exists" "$REPO_ROOT/rc.d/05-aliases.zsh" + +# Test aliases content +test_aliases_content() { + local content=$(cat "$REPO_ROOT/rc.d/05-aliases.zsh") + + # Test navigation aliases + assert_contains "aliases has .. alias" "$content" ".." + assert_contains "aliases has ... alias" "$content" "..." + + # Test git aliases (very common ones) + if echo "$content" | grep -qE "(alias g=|g='git'|g=\"git\")"; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ aliases has git shortcuts" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ aliases should have git shortcuts" + fi +} + +test_aliases_content + +# Test that aliases file is substantial +test_aliases_size() { + local lines=$(wc -l < "$REPO_ROOT/rc.d/05-aliases.zsh") + + # Should have a reasonable number of aliases (at least 10 lines) + if [ "$lines" -ge 10 ]; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ aliases file has substantial content ($lines lines)" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ aliases file seems too small ($lines lines)" + fi +} + +test_aliases_size + +# Test that aliases follow ZSH syntax +test_aliases_syntax() { + local content=$(cat "$REPO_ROOT/rc.d/05-aliases.zsh") + + # Check for alias command usage + if echo "$content" | grep -q "alias"; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ aliases file uses alias command" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ aliases file should use alias command" + fi +} + +test_aliases_syntax + +print_summary diff --git a/tests/unit/test_commands.sh b/tests/unit/test_commands.sh new file mode 100755 index 0000000..eee82b3 --- /dev/null +++ b/tests/unit/test_commands.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# Unit tests for custom commands/functions in rc.d/06-commands.zsh +# These tests verify that custom shell functions are properly defined + +# Get the directory of this script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Source test helpers +source "$SCRIPT_DIR/../test_helpers.sh" + +run_test_suite "Custom Commands" + +# Test commands file exists +assert_file_exists "06-commands.zsh exists" "$REPO_ROOT/rc.d/06-commands.zsh" + +# Test commands content +test_commands_content() { + local content=$(cat "$REPO_ROOT/rc.d/06-commands.zsh") + + # Test for specific functions mentioned in the repository context + assert_contains "commands has pg_switch function" "$content" "function pg_switch" + assert_contains "commands has pg_stop function" "$content" "function pg_stop" + assert_contains "commands has install_casks function" "$content" "function install_casks" + assert_contains "commands has view_defaults function" "$content" "function view_defaults" + assert_contains "commands has print_path function" "$content" "function print_path" +} + +test_commands_content + +# Test that commands use fzf integration +test_fzf_integration() { + local content=$(cat "$REPO_ROOT/rc.d/06-commands.zsh") + + # Several functions should use fzf + if echo "$content" | grep -q "fzf"; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ commands integrate with fzf" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ commands should integrate with fzf" + fi +} + +test_fzf_integration + +# Test that commands use jq for JSON processing +test_jq_usage() { + local content=$(cat "$REPO_ROOT/rc.d/06-commands.zsh") + + if echo "$content" | grep -q "jq"; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ commands use jq for JSON processing" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ commands should use jq for JSON processing" + fi +} + +test_jq_usage + +# Test PostgreSQL functions +test_postgresql_functions() { + local content=$(cat "$REPO_ROOT/rc.d/06-commands.zsh") + + # Check for mise integration in PostgreSQL functions + assert_contains "pg functions use mise" "$content" "mise/installs/postgres" +} + +test_postgresql_functions + +# Test that commands file is substantial +test_commands_size() { + local lines=$(wc -l < "$REPO_ROOT/rc.d/06-commands.zsh") + + # Should have a reasonable number of functions (at least 20 lines) + if [ "$lines" -ge 20 ]; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ commands file has substantial content ($lines lines)" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ commands file seems too small ($lines lines)" + fi +} + +test_commands_size + +# Test function documentation +test_function_documentation() { + local content=$(cat "$REPO_ROOT/rc.d/06-commands.zsh") + + # Check if functions have documentation comments + if echo "$content" | grep -q "^#"; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ commands file has documentation comments" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ commands file should have documentation comments" + fi +} + +test_function_documentation + +print_summary diff --git a/tests/unit/test_config.sh b/tests/unit/test_config.sh new file mode 100755 index 0000000..c6dbbb9 --- /dev/null +++ b/tests/unit/test_config.sh @@ -0,0 +1,165 @@ +#!/usr/bin/env bash +# Unit tests for ZSH configuration files +# These tests verify the structure and content of configuration files + +# Get the directory of this script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Source test helpers +source "$SCRIPT_DIR/../test_helpers.sh" + +run_test_suite "ZSH Configuration Files" + +# Test main configuration files exist +assert_file_exists ".zshrc exists" "$REPO_ROOT/.zshrc" +assert_file_exists ".zshenv exists" "$REPO_ROOT/.zshenv" +assert_file_exists ".zprofile exists" "$REPO_ROOT/.zprofile" +assert_file_exists ".zstyles exists" "$REPO_ROOT/.zstyles" +assert_file_exists "antidote_plugins.conf exists" "$REPO_ROOT/antidote_plugins.conf" + +# Test rc.d directory and files +if [ -d "$REPO_ROOT/rc.d" ]; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ rc.d directory exists" +else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ rc.d directory does not exist" +fi +assert_file_exists "01-hist.zsh exists" "$REPO_ROOT/rc.d/01-hist.zsh" +assert_file_exists "02_dirs.zsh exists" "$REPO_ROOT/rc.d/02_dirs.zsh" +assert_file_exists "04-opts.zsh exists" "$REPO_ROOT/rc.d/04-opts.zsh" +assert_file_exists "05-aliases.zsh exists" "$REPO_ROOT/rc.d/05-aliases.zsh" +assert_file_exists "06-commands.zsh exists" "$REPO_ROOT/rc.d/06-commands.zsh" +assert_file_exists "history-substring-search.zsh exists" "$REPO_ROOT/rc.d/history-substring-search.zsh" +assert_file_exists "sharship.zsh exists" "$REPO_ROOT/rc.d/sharship.zsh" + +# Test .zshrc configuration +test_zshrc() { + local content=$(cat "$REPO_ROOT/.zshrc") + + assert_contains ".zshrc has shebang" "$content" "#!/bin/zsh" + assert_contains ".zshrc loads antidote" "$content" "antidote" + assert_contains ".zshrc sources rc.d files" "$content" "rc.d/\*.zsh" + assert_contains ".zshrc sets up fpath" "$content" "fpath" + assert_contains ".zshrc autoloads functions" "$content" "autoload" +} + +test_zshrc + +# Test .zprofile configuration +test_zprofile() { + local content=$(cat "$REPO_ROOT/.zprofile") + + assert_contains ".zprofile has shebang" "$content" "#!/bin/zsh" + assert_contains ".zprofile sets HOMEBREW_PREFIX" "$content" "HOMEBREW_PREFIX" + assert_contains ".zprofile sets EDITOR" "$content" "EDITOR" + assert_contains ".zprofile sets VISUAL" "$content" "VISUAL" + assert_contains ".zprofile sets FZF_DEFAULT_COMMAND" "$content" "FZF_DEFAULT_COMMAND" + assert_contains ".zprofile sets FZF_DEFAULT_OPTS" "$content" "FZF_DEFAULT_OPTS" +} + +test_zprofile + +# Test .zshenv configuration +test_zshenv() { + local content=$(cat "$REPO_ROOT/.zshenv") + + # .zshenv may not have a shebang since it's always sourced, not executed + # Just check that it sets XDG variables + assert_contains ".zshenv sets XDG_CONFIG_HOME" "$content" "XDG_CONFIG_HOME" + assert_contains ".zshenv sets XDG_DATA_HOME" "$content" "XDG_DATA_HOME" + assert_contains ".zshenv sets XDG_CACHE_HOME" "$content" "XDG_CACHE_HOME" +} + +test_zshenv + +# Test rc.d file naming convention +test_rcd_naming() { + local files=$(ls "$REPO_ROOT/rc.d/"*.zsh 2>/dev/null | wc -l) + + if [ "$files" -gt 0 ]; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ rc.d contains $files .zsh files" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ rc.d should contain .zsh files" + fi +} + +test_rcd_naming + +# Test that rc.d files are properly structured +test_rcd_structure() { + local rcd_dir="$REPO_ROOT/rc.d" + + # Check that numbered files exist (for load order) + if ls "$rcd_dir"/[0-9]*.zsh >/dev/null 2>&1; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ rc.d contains numbered files for load order" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ rc.d should contain numbered files" + fi +} + +test_rcd_structure + +# Test antidote_plugins.conf +test_antidote_plugins() { + local content=$(cat "$REPO_ROOT/antidote_plugins.conf") + + # Check for any of the common plugin sources (github, path to plugins, etc) + if echo "$content" | grep -qE "(zsh-users|mattmc3|aloxaf|belak)"; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ antidote_plugins.conf has plugin references" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ antidote_plugins.conf should have plugin references" + fi + + # Check for some expected plugins + if echo "$content" | grep -q "zsh-users/zsh-completions" || \ + echo "$content" | grep -q "zsh-users/zsh-syntax-highlighting" || \ + echo "$content" | grep -q "zsh-users/zsh-autosuggestions"; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ antidote_plugins.conf contains expected plugins" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ antidote_plugins.conf should contain zsh-users plugins" + fi +} + +test_antidote_plugins + +# Test .gitignore for ZSH artifacts +test_gitignore() { + if [ -f "$REPO_ROOT/.gitignore" ]; then + local content=$(cat "$REPO_ROOT/.gitignore") + + # Check that common ZSH artifacts are ignored + assert_contains ".gitignore ignores .zsh_plugins.zsh" "$content" ".zsh_plugins.zsh" + + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ .gitignore exists and contains ZSH artifacts" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ .gitignore should exist" + fi +} + +test_gitignore + +print_summary diff --git a/tests/unit/test_functions.sh b/tests/unit/test_functions.sh new file mode 100755 index 0000000..e5a8e8b --- /dev/null +++ b/tests/unit/test_functions.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash +# Unit tests for custom ZSH functions +# These tests verify that custom functions are properly defined + +# Get the directory of this script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Source test helpers +source "$SCRIPT_DIR/../test_helpers.sh" + +run_test_suite "Custom Functions" + +# Test that functions directory exists (it's a directory, not a file) +if [ -d "$REPO_ROOT/functions" ]; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ functions directory exists" +else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ functions directory does not exist" +fi + +# Test individual function files exist +assert_file_exists "bench-startup function exists" "$REPO_ROOT/functions/bench-startup" +assert_file_exists "calculate_actions_stats function exists" "$REPO_ROOT/functions/calculate_actions_stats" +assert_file_exists "fetch_action_stats function exists" "$REPO_ROOT/functions/fetch_action_stats" +assert_file_exists "grecent function exists" "$REPO_ROOT/functions/grecent" +assert_file_exists "is-macos function exists" "$REPO_ROOT/functions/is-macos" +assert_file_exists "os function exists" "$REPO_ROOT/functions/os" + +# Test bench-startup function +test_bench_startup() { + local content=$(cat "$REPO_ROOT/functions/bench-startup") + + assert_contains "bench-startup has shebang" "$content" "#!/usr/bin/env zsh" + assert_contains "bench-startup has measure_basic function" "$content" "measure_basic" + assert_contains "bench-startup has measure_hyperfine function" "$content" "measure_hyperfine" + assert_contains "bench-startup uses time command" "$content" "time" +} + +test_bench_startup + +# Test fetch_action_stats function +test_fetch_action_stats() { + local content=$(cat "$REPO_ROOT/functions/fetch_action_stats") + + assert_contains "fetch_action_stats has shebang" "$content" "#!/bin/zsh" + assert_contains "fetch_action_stats uses gh command" "$content" "gh run list" + assert_contains "fetch_action_stats uses jq" "$content" "jq" + assert_contains "fetch_action_stats has main function" "$content" "main" +} + +test_fetch_action_stats + +# Test calculate_actions_stats function +test_calculate_actions_stats() { + local content=$(cat "$REPO_ROOT/functions/calculate_actions_stats") + + assert_contains "calculate_actions_stats has shebang" "$content" "#!/bin/zsh" + # This function uses awk for calculations + assert_contains "calculate_actions_stats uses awk" "$content" "awk" +} + +test_calculate_actions_stats + +# Test grecent function +test_grecent() { + local content=$(cat "$REPO_ROOT/functions/grecent") + + # Accept both #!/bin/zsh and #!/usr/bin/env zsh + if echo "$content" | head -n 1 | grep -q "^#!/.*zsh"; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ grecent has zsh shebang" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ grecent does not have zsh shebang" + fi + + assert_contains "grecent uses git" "$content" "git" + assert_contains "grecent uses fzf" "$content" "fzf" +} + +test_grecent + +# Test is-macos function +test_is_macos() { + local content=$(cat "$REPO_ROOT/functions/is-macos") + + assert_contains "is-macos has shebang" "$content" "#!/bin/zsh" + # Check for darwin OS detection (case-insensitive) + if echo "$content" | grep -qi "darwin"; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ is-macos checks for darwin OS" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ is-macos should check for darwin OS" + fi +} + +test_is_macos + +# Test os function +test_os() { + local content=$(cat "$REPO_ROOT/functions/os") + + # Accept both #!/bin/zsh and #!/usr/bin/env zsh + if echo "$content" | head -n 1 | grep -q "^#!/.*zsh"; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ os has zsh shebang" + else + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ os does not have zsh shebang" + fi + + # The 'os' function is for Overmind start, not for OS detection + assert_contains "os function uses overmind" "$content" "overmind" +} + +test_os + +# Test that all functions have executable permissions (or note it as info) +test_function_permissions() { + local functions_dir="$REPO_ROOT/functions" + local non_executable=0 + + for func_file in "$functions_dir"/*; do + if [ -f "$func_file" ]; then + if [ ! -x "$func_file" ]; then + non_executable=$((non_executable + 1)) + fi + fi + done + + TESTS_RUN=$((TESTS_RUN + 1)) + if [ "$non_executable" -eq 0 ]; then + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ All function files are executable" + else + # This is informational - not all systems require functions to be executable + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_yellow "ℹ $non_executable function files are not executable (this is optional)" + fi +} + +test_function_permissions + +# Test that function files have shebangs +test_function_shebangs() { + local functions_dir="$REPO_ROOT/functions" + local all_have_shebang=true + + for func_file in "$functions_dir"/*; do + if [ -f "$func_file" ]; then + local first_line=$(head -n 1 "$func_file") + if [[ ! "$first_line" =~ ^#! ]]; then + all_have_shebang=false + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_FAILED=$((TESTS_FAILED + 1)) + print_red "✗ $(basename "$func_file") does not have a shebang" + fi + fi + done + + if [ "$all_have_shebang" = true ]; then + TESTS_RUN=$((TESTS_RUN + 1)) + TESTS_PASSED=$((TESTS_PASSED + 1)) + print_green "✓ All function files have shebangs" + fi +} + +test_function_shebangs + +print_summary