# tests/helpers/common.bash
# Sourced by every bats test file via `load`.
# Provides: workspace isolation, PATH stubs, assertion helpers.
# ── workspace setup/teardown ────────────────────────────────────────────────
# Call in setup(): creates an isolated temp directory and cds into it.
# A stub for md2html is pre-installed in a fake bin/ on PATH.
setup_workspace() {
TEST_TMPDIR="$(mktemp -d)"
# trap 'rm -rf $TEST_TMPDIR' EXIT
# Fake bin injected before the real PATH so stubs take priority
STUB_BIN="$TEST_TMPDIR/bin"
mkdir -p "$STUB_BIN"
install_stub_md2html "$STUB_BIN"
ORIG_DIR="$(pwd)"
export PATH="$STUB_BIN:$PATH"
# Work inside the temp dir so .trblcache is isolated
pushd "$TEST_TMPDIR" >/dev/null
}
# Call in teardown(): removes the temp directory.
teardown_workspace() {
popd >/dev/null 2>&1 || true
rm -rf "$TEST_TMPDIR"
}
# ── stubs ───────────────────────────────────────────────────────────────────
# md2html stub: echoes a predictable HTML marker for easy assertion.
# Accepts any flags; always succeeds.
install_stub_md2html() {
local bin_dir="$1"
cat > "$bin_dir/md2html" <<'EOF'
#!/usr/bin/env bash
# Stub: find the last non-flag argument as the input file, or read stdin.
# Flags (args starting with -) from the md alias are ignored.
input=""
for arg in "$@"; do
[[ "$arg" == -* ]] || input="$arg"
done
if [[ -n "$input" ]]; then
content="$(cat "$input")"
else
content="$(cat)"
fi
echo "
$content
"
EOF
chmod +x "$bin_dir/md2html"
}
# ── AWK program builders ─────────────────────────────────────────────────────
# These mirror the comment_form patterns from trbldoc.sh so extraction
# logic can be unit-tested without sourcing the whole script.
CACHE_DIR=".trblcache"
# Returns the AWK program string for C-style /* */ comments.
awk_prog_c() {
local cache="$1"
cat < \$chunkfile << \"EOF\"\\n@file " name ":" NR ;
if(\$2) {\$1=""; print "@exec " \$0}
in_comment=1;
}
EOF
}
# Returns the AWK program string for Lua
awk_prog_lua() {
local cache="$1"
cat < \$chunkfile << \"EOF\"\\n@file " name ":" NR ;
if(\$2) {\$1=""; print "@exec " \$0}
in_comment=1;
}
EOF
}
# Returns the AWK program string for Python.
# Handles '''renderer format (renderer glued directly to ''', no space required).
awk_prog_py() {
local cache="$1"
sed "s|__PY_CACHE__|${cache}|g" << 'PYEOF'
BEGIN{}
/'''/ {
if(in_comment) {print "EOF"}
in_comment=0
}
in_comment {print}
/'''.+/ && !/'''$/ {
print "chunkfile=$(mktemp -p __PY_CACHE__/chunks)"
print "cat > $chunkfile << \"EOF\"\n@file " name ":" NR ;
renderer = $0
sub(/'''/, "", renderer)
sub(/^[[:space:]]+/, "", renderer)
sub(/[[:space:]]+$/, "", renderer)
if (renderer != "") {print "@exec " renderer}
in_comment=1;
}
PYEOF
}
# Returns the AWK program string for .md files
awk_prog_md() {
local cache="$1"
cat < \$chunkfile << \"EOF\"\\n@file " name ":1\\n@exec md" ;
}
{print \$0}
END{print "EOF"}
EOF
}
# ── extraction helper ────────────────────────────────────────────────────────
# run_extraction FILE AWK_PROG CACHE_DIR
# Runs AWK against FILE, evals the output shell script, and populates CACHE_DIR/chunks.
run_extraction() {
local file="$1"
local awk_prog="$2"
local cache="$3"
mkdir -p "$cache/chunks"
local chunk_script
chunk_script=$(awk -v name="$file" "$awk_prog" < "$file")
eval "$chunk_script"
}
# chunk_count CACHE_DIR
# Prints the number of chunk files produced.
chunk_count() {
find "$1/chunks" -type f 2>/dev/null | wc -l | tr -d ' '
}
# chunk_contains CACHE_DIR PATTERN
# Succeeds if any chunk file contains a line matching PATTERN (grep -q).
chunk_contains() {
grep -rl "$2" "$1/chunks" >/dev/null 2>&1
}
# ── assertion helpers ────────────────────────────────────────────────────────
# assert_file_exists PATH
assert_file_exists() {
if [[ ! -f "$1" ]]; then
echo "ASSERTION FAILED: expected file to exist: $1" >&2
return 1
fi
}
# assert_dir_exists PATH
assert_dir_exists() {
if [[ ! -d "$1" ]]; then
echo "ASSERTION FAILED: expected directory to exist: $1" >&2
return 1
fi
}
# assert_contains FILE PATTERN
assert_contains() {
if ! grep -q "$2" "$1" 2>/dev/null; then
echo "ASSERTION FAILED: '$2' not found in $1" >&2
echo "--- file contents ---" >&2
cat "$1" >&2
return 1
fi
}
# assert_not_contains FILE PATTERN
assert_not_contains() {
if grep -q "$2" "$1" 2>/dev/null; then
echo "ASSERTION FAILED: '$2' unexpectedly found in $1" >&2
return 1
fi
}
# assert_equal ACTUAL EXPECTED
assert_equal() {
if [[ "$1" != "$2" ]]; then
echo "ASSERTION FAILED: expected '$2', got '$1'" >&2
return 1
fi
}
# ── HTML validation helpers ──────────────────────────────────────────────────
# assert_valid_html FILE
# Validates FILE as a complete HTML5 document using tidy.
# Mustache template markers ({{...}}, {{{...}}}) are substituted with
# structurally valid HTML before linting to avoid false positives from
# template syntax that tidy cannot parse.
# Skips automatically if tidy is not installed.
# tidy exit codes: 0 = clean, 1 = warnings only, 2 = errors.
# This helper fails only on exit code 2 (actual errors).
assert_valid_html() {
local file="$1"
if ! command -v tidy >/dev/null 2>&1; then
skip "tidy not installed (apt install tidy)"
fi
local tmp
tmp=$(mktemp --suffix=.html)
# Replace {{ var }} template markers with valid HTML equivalents so tidy
# sees a structurally correct document.
sed \
-e 's/{{[^}]*}}/placeholder/g' \
"$file" > "$tmp"
local tidy_out rc
tidy_out=$(tidy -errors -quiet --show-warnings no -utf8 "$tmp" 2>&1)
rc=$?
rm -f "$tmp"
if [ "$rc" -ge 2 ]; then
echo "HTML VALIDATION FAILED: $file" >&2
echo "$tidy_out" >&2
return 1
fi
}
# assert_valid_html_fragment FILE
# Wraps FILE in a minimal HTML5 document shell, then validates with tidy.
# Use for HTML fragment files (e.g. rendered chunk index.html output) that
# are not complete documents on their own.
# Skips automatically if tidy is not installed.
assert_valid_html_fragment() {
local file="$1"
if ! command -v tidy >/dev/null 2>&1; then
skip "tidy not installed (apt install tidy)"
fi
local tmp
tmp=$(mktemp --suffix=.html)
{
echo 'fragment'
cat "$file"
echo ''
} > "$tmp"
local tidy_out rc
tidy_out=$(tidy -errors -quiet --show-warnings no -utf8 "$tmp" 2>&1)
rc=$?
rm -f "$tmp"
if [ "$rc" -ge 2 ]; then
echo "HTML VALIDATION FAILED: $file" >&2
echo "$tidy_out" >&2
return 1
fi
}