Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions content-guards/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ claude plugins add jacobpevans-cc-plugins/content-guards
- `cspell` - Spell checking
- `gh` - GitHub CLI

## Testing

The markdown-validator has automated tests using [bats-core](https://github.com/bats-core/bats-core):

```bash
# Install bats-core (if not already installed)
brew install bats-core

# Run the test suite from repo root
bats tests/content-guards/markdown-validator/validate-markdown.bats
```

Test coverage includes:
- File type filtering (non-markdown, missing, dotfiles)
- Config resolution (project vs fallback)
- Cross-repo editing scenarios
- Unbound variable regression prevention (PR #39, #40)

## License

Apache-2.0
13 changes: 10 additions & 3 deletions content-guards/scripts/validate-markdown.sh
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,16 @@ EOF
fi
fi

if ! markdownlint_output=$(markdownlint-cli2 "${config_flag[@]+"${config_flag[@]}"}" "$file_path" 2>&1); then
errors+=("markdownlint-cli2 failed:")
errors+=("$markdownlint_output")
if [[ ${#config_flag[@]} -gt 0 ]]; then
if ! markdownlint_output=$(markdownlint-cli2 "${config_flag[@]}" "$file_path" 2>&1); then
errors+=("markdownlint-cli2 failed:")
errors+=("$markdownlint_output")
fi
else
if ! markdownlint_output=$(markdownlint-cli2 "$file_path" 2>&1); then
errors+=("markdownlint-cli2 failed:")
errors+=("$markdownlint_output")
fi
fi
fi

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Cross-Repo Feature

This file simulates a cross-repo editing scenario.

It has no ancestor `.markdownlint*` config, so the validator should use the fallback config.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"default": true,
"MD013": {
"line_length": 120
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Project Documentation

This markdown file lives in a directory with a `.markdownlint.json` config.

It should be validated using that project config.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Valid Basic Markdown

This is a minimal valid markdown file for testing.

- Item 1
- Item 2
81 changes: 81 additions & 0 deletions tests/content-guards/markdown-validator/validate-markdown.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bats
# Test suite for content-guards/scripts/validate-markdown.sh
#
# Tests the markdown validator hook behavior including:
# - File type filtering (non-markdown, missing, dotfiles)
# - Config resolution (project vs fallback)
# - Cross-repo editing scenarios
# - Unbound variable regression (PR #39, #40)
#
# Run with: bats tests/content-guards/markdown-validator/validate-markdown.bats

setup() {
# Path to the script under test (relative to repo root)
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../.." && pwd)"
SCRIPT="$REPO_ROOT/content-guards/scripts/validate-markdown.sh"
FIXTURES="$(dirname "$BATS_TEST_FILENAME")/fixtures"

# Verify script exists
if [[ ! -f "$SCRIPT" ]]; then
echo "ERROR: Script not found at $SCRIPT" >&2
return 1
fi
}

@test "TC1: non-markdown file is skipped" {
run bash -c 'echo "{\"tool_input\":{\"file_path\":\"test.py\"}}" | /bin/bash "$1"' _ "$SCRIPT"
[ "$status" -eq 0 ]
[ -z "$output" ]
}

@test "TC2: missing file is skipped" {
run bash -c 'echo "{\"tool_input\":{\"file_path\":\"/nonexistent/file.md\"}}" | /bin/bash "$1"' _ "$SCRIPT"
[ "$status" -eq 0 ]
[ -z "$output" ]
}

@test "TC3: home dotfile is skipped" {
run bash -c 'echo "{\"tool_input\":{\"file_path\":\"~/.config/foo.md\"}}" | /bin/bash "$1"' _ "$SCRIPT"
[ "$status" -eq 0 ]
[ -z "$output" ]
}

@test "TC4: .claude directory file is skipped" {
run bash -c 'echo "{\"tool_input\":{\"file_path\":\"/path/to/.claude/foo.md\"}}" | /bin/bash "$1"' _ "$SCRIPT"
[ "$status" -eq 0 ]
[ -z "$output" ]
}

@test "TC5: empty config_flag with project config does not cause unbound variable" {
run bash -c 'echo "{\"tool_input\":{\"file_path\":\"'"$FIXTURES"'/project-with-config/doc.md\"}}" | /bin/bash "$1"' _ "$SCRIPT"
[ "$status" -eq 0 ]
[[ ! "$output" =~ "unbound variable" ]]
}

@test "TC6: non-empty config_flag with fallback config" {
# File without ancestor config should use fallback (temp config or plugin default)
run bash -c 'echo "{\"tool_input\":{\"file_path\":\"'"$FIXTURES"'/cross-repo-sim/feat/new-feature/README.md\"}}" | /bin/bash "$1"' _ "$SCRIPT"
[ "$status" -eq 0 ]
[[ ! "$output" =~ "unbound variable" ]]
}

@test "TC7: cross-repo editing finds config from file directory not CWD" {
# Simulate being in a completely different directory (like ~/git/repo1)
# while editing a file in ~/git/repo2/feat/branch/
original_dir=$(pwd)
cd /tmp

run bash -c 'echo "{\"tool_input\":{\"file_path\":\"'"$FIXTURES"'/project-with-config/doc.md\"}}" | /bin/bash "$1"' _ "$SCRIPT"

# Restore original directory
cd "$original_dir"

[ "$status" -eq 0 ]
[[ ! "$output" =~ "unbound variable" ]]
}

@test "TC8: empty JSON input is handled gracefully" {
run bash -c 'echo "{}" | /bin/bash "$1"' _ "$SCRIPT"
[ "$status" -eq 0 ]
[ -z "$output" ]
}
Loading