diff --git a/.github/workflows/test-smokes.yml b/.github/workflows/test-smokes.yml
index 074b84ecb39..652807eddd7 100644
--- a/.github/workflows/test-smokes.yml
+++ b/.github/workflows/test-smokes.yml
@@ -317,7 +317,7 @@ jobs:
echo ">>> ./run-tests.sh ${file}"
# Run tests without -e so we don't exit on first failure
set +e
- shopt -s globstar && ./run-tests.sh "$file"
+ ./run-tests.sh "$file"
status=$?
set -e
echo "::endgroup::"
diff --git a/dev-docs/feature-format-matrix/.gitignore b/dev-docs/feature-format-matrix/.gitignore
index 58d8cfd09bf..36cda2a8fe4 100644
--- a/dev-docs/feature-format-matrix/.gitignore
+++ b/dev-docs/feature-format-matrix/.gitignore
@@ -5,5 +5,8 @@ qmd-files/**/*.html
qmd-files/**/*.pdf
qmd-files/**/*.md
qmd-files/**/*.mdx
+qmd-files/**/*.typ
/.quarto/
+
+**/*.quarto_ipynb
diff --git a/dev-docs/feature-format-matrix/qmd-files/css-properties/font-family/document.qmd b/dev-docs/feature-format-matrix/qmd-files/css-properties/font-family/document.qmd
index c068b1c0420..b21d3aa0247 100644
--- a/dev-docs/feature-format-matrix/qmd-files/css-properties/font-family/document.qmd
+++ b/dev-docs/feature-format-matrix/qmd-files/css-properties/font-family/document.qmd
@@ -19,12 +19,12 @@ _quarto:
typst:
ensureTypstFileRegexMatches:
-
- - '#{set text\(font: \("Georgia", "serif"\)\); table\('
+ - '#{set text\(font: \("Libertinus Serif", "serif"\)\); table\('
- []
---
```{=html}
-
+
```
diff --git a/tests/run-tests-multiple-file-args.test.sh b/tests/run-tests-multiple-file-args.test.sh
new file mode 100644
index 00000000000..29617ea7572
--- /dev/null
+++ b/tests/run-tests-multiple-file-args.test.sh
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+# Regression test for run-tests.sh collapsing multiple .test.ts file
+# arguments into a single glued argv token.
+#
+# tests/README.md documents passing several .ts files at once, e.g.:
+# ./run-tests.sh smoke/extensions/extension-render-doc.test.ts smoke/smoke-all.test.ts
+#
+# run-tests.sh built TESTS_TO_RUN by looping `for file in "$*"; do`, which
+# joins all positional args into one string before iterating, so the loop
+# only ever runs once. That single glued string used to reach deno unquoted,
+# where bash's own word-splitting happened to reconstruct separate args -
+# but that's incidental, not something quoting the resulting array should be
+# allowed to depend on. This test runs the real run-tests.sh with two file
+# arguments and confirms each survives as its own deno argv token.
+set -uo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/.." >/dev/null 2>&1 && pwd)"
+
+TMP="$(mktemp -d)"
+trap 'rm -rf "$TMP"' EXIT
+
+fail() {
+ echo "FAIL: $1"
+ exit 1
+}
+
+# --- Isolated fake repo skeleton, just enough for run-tests.sh to run ---
+mkdir -p "$TMP/tests"
+mkdir -p "$TMP/package/scripts/common"
+mkdir -p "$TMP/package/dist/bin/tools/stub"
+mkdir -p "$TMP/src/resources"
+
+# Strip CR in case this checkout has core.autocrlf=true (Windows dev boxes);
+# CI checkouts on Linux/macOS never have CRLF here, this is a local-copy
+# normalization only, not part of the fix under test.
+tr -d '\r' < "$REPO_ROOT/tests/run-tests.sh" > "$TMP/tests/run-tests.sh"
+tr -d '\r' < "$REPO_ROOT/package/scripts/common/utils.sh" > "$TMP/package/scripts/common/utils.sh"
+chmod +x "$TMP/tests/run-tests.sh"
+
+RECORD_FILE="$TMP/recorded-argv.txt"
+cat > "$TMP/package/dist/bin/tools/stub/deno" <<'EOF'
+#!/usr/bin/env bash
+printf '%s\n' "$@" > "$RECORD_FILE"
+exit 0
+EOF
+chmod +x "$TMP/package/dist/bin/tools/stub/deno"
+
+# --- Invoke the real script with two separate .test.ts file arguments ---
+(
+ cd "$TMP/tests" || exit 1
+ export QUARTO_TESTS_NO_CONFIG=true
+ export QUARTO_TESTS_FORCE_NO_VENV=true
+ export DENO_DIR=stub
+ export RECORD_FILE
+ ./run-tests.sh "foo.test.ts" "bar.test.ts"
+)
+
+[ -f "$RECORD_FILE" ] || fail "deno stub was never invoked - run-tests.sh did not reach the final deno call"
+
+mapfile -t recorded < "$RECORD_FILE"
+
+found_foo=0
+found_bar=0
+found_glued=0
+for tok in "${recorded[@]}"; do
+ [ "$tok" = "foo.test.ts" ] && found_foo=1
+ [ "$tok" = "bar.test.ts" ] && found_bar=1
+ [ "$tok" = "foo.test.ts bar.test.ts" ] && found_glued=1
+done
+
+[ "$found_glued" -eq 0 ] || fail "foo.test.ts and bar.test.ts were glued into a single argv token - multiple file arguments were collapsed"
+[ "$found_foo" -eq 1 ] || fail "foo.test.ts missing from deno argv (got: ${recorded[*]})"
+[ "$found_bar" -eq 1 ] || fail "bar.test.ts missing from deno argv (got: ${recorded[*]})"
+
+echo "PASS: multiple .test.ts file arguments reached deno as separate argv tokens"
diff --git a/tests/run-tests-recursive-glob.test.sh b/tests/run-tests-recursive-glob.test.sh
new file mode 100644
index 00000000000..4370468a24b
--- /dev/null
+++ b/tests/run-tests-recursive-glob.test.sh
@@ -0,0 +1,95 @@
+#!/usr/bin/env bash
+# Regression test for run-tests.sh silently truncating recursive ** glob
+# buckets on Linux/macOS (quarto-cli-jho3).
+#
+# Runs the real run-tests.sh against a nested qmd fixture, with the deno
+# binary swapped for a recorder stub (via the DENO_DIR override run-tests.sh
+# already supports), and checks that a file nested two directories deep
+# under a `**` bucket still reaches the resolved test-file set. The
+# assertion re-expands any still-glob-shaped token itself (mirroring what
+# smoke-all.test.ts's expandGlobSync does), so it passes regardless of
+# whether the fix quotes the pattern through to deno or expands it in bash.
+set -uo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/.." >/dev/null 2>&1 && pwd)"
+
+TMP="$(mktemp -d)"
+trap 'rm -rf "$TMP"' EXIT
+
+fail() {
+ echo "FAIL: $1"
+ exit 1
+}
+
+# --- Isolated fake repo skeleton, just enough for run-tests.sh to run ---
+mkdir -p "$TMP/tests"
+mkdir -p "$TMP/package/scripts/common"
+mkdir -p "$TMP/package/dist/bin/tools/stub"
+mkdir -p "$TMP/src/resources"
+
+# Strip CR in case this checkout has core.autocrlf=true (Windows dev boxes);
+# CI checkouts on Linux/macOS never have CRLF here, this is a local-copy
+# normalization only, not part of the fix under test.
+tr -d '\r' < "$REPO_ROOT/tests/run-tests.sh" > "$TMP/tests/run-tests.sh"
+tr -d '\r' < "$REPO_ROOT/package/scripts/common/utils.sh" > "$TMP/package/scripts/common/utils.sh"
+chmod +x "$TMP/tests/run-tests.sh"
+
+RECORD_FILE="$TMP/recorded-argv.txt"
+cat > "$TMP/package/dist/bin/tools/stub/deno" <<'EOF'
+#!/usr/bin/env bash
+printf '%s\n' "$@" > "$RECORD_FILE"
+exit 0
+EOF
+chmod +x "$TMP/package/dist/bin/tools/stub/deno"
+
+# --- Nested fixture: one .qmd at depth 1, one at depth 2 under qmd-files/ ---
+mkdir -p "$TMP/qmd-files/a/b"
+: > "$TMP/qmd-files/a/one.qmd"
+: > "$TMP/qmd-files/a/b/two.qmd"
+
+# --- Invoke the real script exactly as test-smokes.yml's bucket runner does ---
+(
+ cd "$TMP/tests" || exit 1
+ export QUARTO_TESTS_NO_CONFIG=true
+ export QUARTO_TESTS_FORCE_NO_VENV=true
+ export DENO_DIR=stub
+ export RECORD_FILE
+ ./run-tests.sh "../qmd-files/**/*.qmd"
+)
+
+[ -f "$RECORD_FILE" ] || fail "deno stub was never invoked - run-tests.sh did not reach the final deno call"
+
+mapfile -t recorded < "$RECORD_FILE"
+
+dashdash_index=-1
+for i in "${!recorded[@]}"; do
+ if [ "${recorded[$i]}" = "--" ]; then
+ dashdash_index=$i
+ break
+ fi
+done
+[ "$dashdash_index" -ge 0 ] || fail "no isolated '--' token in deno argv (got: ${recorded[*]})"
+
+# Resolve the file tokens after '--' into an actual file set, expanding any
+# token that still looks like a glob pattern (mirrors expandGlobSync).
+resolved=()
+(
+ cd "$TMP/tests" || exit 1
+ shopt -s globstar nullglob
+ for ((i = dashdash_index + 1; i < ${#recorded[@]}; i++)); do
+ tok="${recorded[$i]}"
+ if [[ "$tok" == *"*"* ]]; then
+ for f in $tok; do
+ echo "$f"
+ done
+ else
+ echo "$tok"
+ fi
+ done
+) > "$TMP/resolved-files.txt"
+
+grep -q 'a/one\.qmd$' "$TMP/resolved-files.txt" || fail "depth-1 fixture file (qmd-files/a/one.qmd) missing from resolved test set"
+grep -q 'a/b/two\.qmd$' "$TMP/resolved-files.txt" || fail "depth-2 fixture file (qmd-files/a/b/two.qmd) missing from resolved test set - recursive ** bucket was silently truncated"
+
+echo "PASS: recursive ** bucket resolved both depth-1 and depth-2 fixture files"
diff --git a/tests/run-tests.sh b/tests/run-tests.sh
index 702b70b8414..a2c134fd392 100755
--- a/tests/run-tests.sh
+++ b/tests/run-tests.sh
@@ -130,19 +130,19 @@ else
## Short version syntax to run smoke-all.test.ts
## Only use if different than ./run-test.sh ./smoke/smoke-all.test.ts
if [[ "$1" =~ smoke-all\.test\.ts ]]; then
- TESTS_TO_RUN=$@
+ TESTS_TO_RUN=("$@")
else
# Check file argument
- SMOKE_ALL_FILES=""
- TESTS_TO_RUN=""
+ SMOKE_ALL_FILES=()
+ TESTS_TO_RUN=()
if [[ ! -z "$*" ]]; then
- for file in "$*"; do
+ for file in "$@"; do
filename=$(basename "$file")
# smoke-all.test.ts works with .qmd, .md and .ipynb but will ignored file starting with _
if [[ $filename =~ ^[^_].*[.]qmd$ ]] || [[ $filename =~ ^[^_].*[.]ipynb$ ]] || [[ $filename =~ ^[^_].*[.]md$ ]]; then
- SMOKE_ALL_FILES="${SMOKE_ALL_FILES} ${file}"
+ SMOKE_ALL_FILES+=("${file}")
elif [[ $file =~ .*[.]ts$ ]]; then
- TESTS_TO_RUN="${TESTS_TO_RUN} ${file}"
+ TESTS_TO_RUN+=("${file}")
else
echo "#### WARNING"
echo "Only .ts, or .qmd, .md and .ipynb passed to smoke-all.test.ts are accepted (file starting with _ are ignored)."
@@ -151,17 +151,22 @@ else
fi
done
fi
- if [ "$SMOKE_ALL_FILES" != "" ]; then
- if [ "$TESTS_TO_RUN" != "" ]; then
+ if [ "${#SMOKE_ALL_FILES[@]}" -ne 0 ]; then
+ if [ "${#TESTS_TO_RUN[@]}" -ne 0 ]; then
echo "#### WARNING"
echo "When passing .qmd, .md and/or .ipynb, only ./smoke/smoke-all.test.ts will be run. Other tests files are ignored."
- echo "Ignoring ${TESTS_TO_RUN}."
+ echo "Ignoring ${TESTS_TO_RUN[*]}."
echo "####"
fi
- TESTS_TO_RUN="${SMOKE_ALL_TEST_FILE} -- ${SMOKE_ALL_FILES}"
+ TESTS_TO_RUN=("${SMOKE_ALL_TEST_FILE}" "--" "${SMOKE_ALL_FILES[@]}")
fi
fi
- "${QUARTO_BIN_PATH}/tools/${DENO_ARCH_DIR}/deno" test ${QUARTO_DENO_OPTIONS} --check ${QUARTO_DENO_EXTRA_OPTIONS} "${QUARTO_IMPORT_MAP_ARG}" $TESTS_TO_RUN
+ # TESTS_TO_RUN is an array and quoted here on purpose: a bucket can be a
+ # literal, unexpanded ** glob pattern (e.g. from the ff-matrix CI bucket),
+ # and smoke-all.test.ts expands it itself via expandGlobSync. Expanding it
+ # here instead would depend on bash's own (non-recursive by default) glob
+ # semantics and could silently drop deeply nested matches.
+ "${QUARTO_BIN_PATH}/tools/${DENO_ARCH_DIR}/deno" test ${QUARTO_DENO_OPTIONS} --check ${QUARTO_DENO_EXTRA_OPTIONS} "${QUARTO_IMPORT_MAP_ARG}" "${TESTS_TO_RUN[@]}"
SUCCESS=$?
fi