# 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 }