#!/bin/sh
set -eu
SKILL_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
NODE=${NODE:-node}
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/gongwen-test.XXXXXX")
trap 'rm -rf "$TMP_DIR"' EXIT
GEN="$SKILL_DIR/scripts/generate_gongwen_docx.mjs"
VERIFY="$SKILL_DIR/scripts/verify_gongwen_docx.mjs"
FIXTURE="$SKILL_DIR/tests/fixture.md"
"$NODE" --check "$GEN"
"$NODE" --check "$VERIFY"
"$NODE" --check "$SKILL_DIR/tests/measure-header-coordinates.mjs"
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/layout.docx" --format ordinary --org "不应变红头的单位" --title "公文格式回归测试" --page-number center
"$NODE" "$VERIFY" --input "$TMP_DIR/layout.docx" --profile ordinary
unzip -t "$TMP_DIR/layout.docx" >/dev/null
! unzip -p "$TMP_DIR/layout.docx" word/document.xml | grep -q 'FF0000'
unzip -p "$TMP_DIR/layout.docx" word/styles.xml | grep -q 'styleId="Heading4"'
unzip -p "$TMP_DIR/layout.docx" word/document.xml | grep -q 'w:pStyle w:val="Heading4"'
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/standard.docx" --format formal --letterhead preprinted --letterhead-reserve-mm 72 --org "示例单位文件" --doc-no "示例发〔2026〕1号" --title "公文格式回归测试" --page-number standard
"$NODE" "$VERIFY" --input "$TMP_DIR/standard.docx" --profile formal --letterhead preprinted
! unzip -p "$TMP_DIR/standard.docx" word/document.xml | grep -q '示例单位文件'
unzip -p "$TMP_DIR/standard.docx" word/document.xml | grep -q '示例发〔2026〕1号'
unzip -p "$TMP_DIR/standard.docx" word/document.xml | grep -q 'w:snapToGrid w:val="false"'
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/digital.docx" --format formal --letterhead digital --org "示例单位文件" --doc-no "示例发〔2026〕1号" --title "电子红头测试" --page-number standard
"$NODE" "$VERIFY" --input "$TMP_DIR/digital.docx" --profile formal --letterhead digital
unzip -p "$TMP_DIR/digital.docx" word/document.xml | grep -q '示例单位文件'
unzip -p "$TMP_DIR/digital.docx" word/document.xml | grep -q 'FF0000'
unzip -p "$TMP_DIR/digital.docx" word/document.xml | grep -q 'height:0.5mm'
unzip -p "$TMP_DIR/digital.docx" word/document.xml | grep -q 'w:framePr.*w:hAnchor="margin".*w:vAnchor="margin".*w:xAlign="center".*w:y="1984"'
unzip -p "$TMP_DIR/digital.docx" word/document.xml | grep -q 'w:before="0"'
unzip -p "$TMP_DIR/digital.docx" word/document.xml | grep -q 'w:snapToGrid w:val="false"'
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/digital-fields.docx" --format formal --letterhead digital --copy-no 7 --secret "机密★3年" --urgent "特急" --org "示例单位文件" --doc-no "示例发〔2026〕3号" --title "版头字段定位测试"
"$NODE" "$VERIFY" --input "$TMP_DIR/digital-fields.docx" --profile formal --letterhead digital
unzip -p "$TMP_DIR/digital-fields.docx" word/document.xml | grep -q 'w:framePr.*w:hAnchor="margin".*w:vAnchor="margin".*w:xAlign="left".*w:y="0"'
unzip -p "$TMP_DIR/digital-fields.docx" word/document.xml | grep -q 'w:framePr.*w:hAnchor="margin".*w:vAnchor="margin".*w:xAlign="left".*w:y="560"'
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/secret-only.docx" --format formal --letterhead digital --secret "机密★3年" --org "示例单位文件" --doc-no "示例发〔2026〕5号" --title "密级首行留空测试"
"$NODE" "$VERIFY" --input "$TMP_DIR/secret-only.docx" --profile formal --letterhead digital
unzip -p "$TMP_DIR/secret-only.docx" word/document.xml | grep -q 'w:framePr.*w:vAnchor="margin".*w:y="560"'
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/preprinted-fields.docx" --format formal --letterhead preprinted --letterhead-reserve-mm 72 --copy-no 7 --secret "机密★3年" --urgent "特急" --doc-no "示例发〔2026〕4号" --title "预印字段定位测试"
"$NODE" "$VERIFY" --input "$TMP_DIR/preprinted-fields.docx" --profile formal --letterhead preprinted
unzip -p "$TMP_DIR/preprinted-fields.docx" word/document.xml | grep -q 'w:before="1984"'
cat >"$TMP_DIR/attachment.md" <<'EOF'
# 附件一:测试附件
附件正文。
EOF
cat >"$TMP_DIR/main-send.md" <<'EOF'
各主送机关:
主送机关只应在输出中出现一次。
EOF
"$NODE" "$GEN" --input "$TMP_DIR/main-send.md" --output "$TMP_DIR/main-send.docx" --format formal --letterhead digital --org "示例单位文件" --doc-no "示例发〔2026〕2号" --title "主送机关匹配测试" --to "各主送机关"
test "$(unzip -p "$TMP_DIR/main-send.docx" word/document.xml | grep -o '各主送机关' | wc -l | tr -d ' ')" -eq 1
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/upward.docx" --format formal --letterhead digital --org "示例单位文件" --doc-no "示例发〔2026〕1号" --title "上行文测试" --upward true --signer "张三"
"$NODE" "$VERIFY" --input "$TMP_DIR/upward.docx" --profile formal --letterhead digital --upward true
unzip -p "$TMP_DIR/upward.docx" word/document.xml | grep -q '签发人:'
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/letter.docx" --format letter --org "示例机关" --doc-no "示例〔2026〕1号" --title "信函测试"
"$NODE" "$VERIFY" --input "$TMP_DIR/letter.docx" --profile letter
unzip -l "$TMP_DIR/letter.docx" | grep -q 'word/footerLetter.xml'
unzip -p "$TMP_DIR/letter.docx" word/footerLetter.xml | grep -q 'w:fill="FF0000"'
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/with-attachment.docx" --format formal --letterhead preprinted --letterhead-reserve-mm 72 --doc-no "示例发〔2026〕1号" --title "附件测试" --attachment-note "1. 测试附件" --attachment-file "$TMP_DIR/attachment.md"
unzip -p "$TMP_DIR/with-attachment.docx" word/document.xml | grep -q '附件:1. 测试附件'
unzip -p "$TMP_DIR/with-attachment.docx" word/document.xml | grep -q 'w:pageBreakBefore'
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/detached-attachment.docx" --format formal --letterhead preprinted --letterhead-reserve-mm 72 --doc-no "示例发〔2026〕1号" --title "分离附件测试" --attachment-note "1. 测试附件" --attachment-file "$TMP_DIR/attachment.md" --attachment-detached
unzip -p "$TMP_DIR/detached-attachment.docx" word/document.xml | grep -q '示例发〔2026〕1号 附件1'
if "$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/bad-attachment.docx" --format formal --letterhead preprinted --letterhead-reserve-mm 72 --doc-no "示例发〔2026〕6号" --title "附件一致性测试" --attachment-note "1. 另一份附件" --attachment-file "$TMP_DIR/attachment.md" >"$TMP_DIR/bad-attachment.out" 2>&1; then
echo "mismatched attachment note unexpectedly succeeded" >&2
exit 1
fi
grep -q 'GENERATOR ERROR:' "$TMP_DIR/bad-attachment.out"
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/command.docx" --format command --org "示例机关命令" --doc-no "第1号" --title "命令正文"
"$NODE" "$VERIFY" --input "$TMP_DIR/command.docx" --profile command
if "$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/bad-command.docx" --format command --org "示例机关" --doc-no "第1号" --title "命令正文" >"$TMP_DIR/bad-command.out" 2>&1; then
echo "command without 命令/令 unexpectedly succeeded" >&2
exit 1
fi
grep -q 'GENERATOR ERROR:' "$TMP_DIR/bad-command.out"
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/minutes.docx" --format minutes --org "示例机关纪要" --title "纪要正文" --attendees "张三、李四" --absent "王五"
"$NODE" "$VERIFY" --input "$TMP_DIR/minutes.docx" --profile minutes
minutes_xml=$(unzip -p "$TMP_DIR/minutes.docx" word/document.xml)
test "${minutes_xml%%出席:*}" != "$minutes_xml"
test "${minutes_xml%%w:sectPr*}" != "$minutes_xml"
test "${minutes_xml%%出席:*}" != "${minutes_xml%%w:sectPr*}"
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/seal-layout.docx" --format formal --letterhead preprinted --letterhead-reserve-mm 72 --title "签章位置测试" --sender "示例机关" --date "2026年9月10日" --seal-mode seal
unzip -p "$TMP_DIR/seal-layout.docx" word/document.xml | grep -q 'w:right="1280"'
if "$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/bad-doc-no.docx" --format formal --letterhead digital --org "示例单位文件" --doc-no "示例发〔2026〕01号" --title "非法文号" >"$TMP_DIR/bad-doc-no.out" 2>&1; then
echo "invalid document number unexpectedly succeeded" >&2
exit 1
fi
grep -q 'GENERATOR ERROR:' "$TMP_DIR/bad-doc-no.out"
if "$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/bad-date.docx" --format formal --letterhead preprinted --date "2026年09月10日" --title "非法日期" >"$TMP_DIR/bad-date.out" 2>&1; then
echo "invalid date unexpectedly succeeded" >&2
exit 1
fi
grep -q 'GENERATOR ERROR:' "$TMP_DIR/bad-date.out"
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/letter-fields.docx" --format letter --org "示例机关" --copy-no 7 --secret "机密★3年" --urgent "特急" --doc-no "示例〔2026〕2号" --title "信函字段边界"
"$NODE" "$VERIFY" --input "$TMP_DIR/letter-fields.docx" --profile letter
unzip -p "$TMP_DIR/letter-fields.docx" word/document.xml | grep -q '000007'
unzip -p "$TMP_DIR/letter-fields.docx" word/document.xml | grep -q '机密★3年'
"$NODE" "$GEN" --input "$SKILL_DIR/tests/fixtures/visual-horizontal.md" --output "$TMP_DIR/horizontal-table.docx" --format horizontal-table --title "横排表格测试" --page-number standard
"$NODE" "$VERIFY" --input "$TMP_DIR/horizontal-table.docx" --profile horizontal-table
unzip -p "$TMP_DIR/horizontal-table.docx" word/document.xml | grep -q 'w:orient="landscape"'
unzip -p "$TMP_DIR/horizontal-table.docx" word/document.xml | grep -q 'w:tblW w:w="12756"'
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/joint.docx" --format formal --letterhead digital --org "主办机关文件" --joint-org "协办机关" --doc-no "示例发〔2026〕3号" --title "联合行文测试"
"$NODE" "$VERIFY" --input "$TMP_DIR/joint.docx" --profile formal --letterhead digital
unzip -p "$TMP_DIR/joint.docx" word/document.xml | grep -q '主办机关'
unzip -p "$TMP_DIR/joint.docx" word/document.xml | grep -q '协办机关'
unzip -p "$TMP_DIR/joint.docx" word/document.xml | grep -q '文件'
unzip -p "$TMP_DIR/joint.docx" word/document.xml | grep -q 'w:sz w:val="48"'
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/joint-four-agencies.docx" --format formal --letterhead digital --org "主办机关文件" --joint-org "协办机关一" --joint-org "协办机关二" --joint-org "协办机关三" --doc-no "示例发〔2026〕4号" --title "四机关联合行文测试"
"$NODE" "$VERIFY" --input "$TMP_DIR/joint-four-agencies.docx" --profile formal --letterhead digital
"$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/joint-without-file-suffix.docx" --format formal --letterhead digital --org "主办机关" --joint-org "协办机关" --doc-no "示例发〔2026〕5号" --title "无文件后缀联合行文测试"
"$NODE" "$VERIFY" --input "$TMP_DIR/joint-without-file-suffix.docx" --profile formal --letterhead digital
python3 - "$TMP_DIR/joint.docx" "$TMP_DIR/joint-four-agencies.docx" <<'PY'
import re
import sys
import zipfile
for filename, expected_count in ((sys.argv[1], 2), (sys.argv[2], 4)):
with zipfile.ZipFile(filename) as archive:
xml = archive.read("word/document.xml").decode()
tables = re.findall(r'[\s\S]*?]*w:tblpY="1984"[\s\S]*?', xml)
assert len(tables) == 1, filename
table = tables[0]
assert table.count('') >= 2, filename
assert table.count('') == 2, filename
assert table.count('') == expected_count + 1, filename
assert '文件' in table, filename
PY
python3 - "$TMP_DIR/joint-without-file-suffix.docx" <<'PY'
import re
import sys
import zipfile
with zipfile.ZipFile(sys.argv[1]) as archive:
xml = archive.read("word/document.xml").decode()
tables = re.findall(r'[\s\S]*?]*w:tblpY="1984"[\s\S]*?', xml)
assert len(tables) == 1
table = tables[0]
assert table.count('') >= 1
assert '文件' not in table
PY
if "$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/missing-joint-name.docx" --format formal --letterhead digital --org "主办机关文件" --joint-org --doc-no "示例发〔2026〕4号" --title "缺少联署机关名称" >"$TMP_DIR/missing-joint-name.out" 2>&1; then
echo "missing joint agency name unexpectedly succeeded" >&2
exit 1
fi
grep -q 'GENERATOR ERROR: --joint-org requires a value' "$TMP_DIR/missing-joint-name.out"
if "$NODE" "$GEN" --input "$FIXTURE" --output "$TMP_DIR/strict.docx" --format formal --letterhead digital --org "示例单位文件" --title "严格字体测试" --require-standard-fonts >"$TMP_DIR/strict.out" 2>&1; then
if ! fc-list -f '%{family}\n' | grep -Eq '方正小标宋简体|方正小标宋_GBK|FZXiaoBiaoSong'; then
echo "strict font mode unexpectedly succeeded without a small-standard-title font" >&2
exit 1
fi
elif ! grep -q 'FONT ERROR:' "$TMP_DIR/strict.out"; then
cat "$TMP_DIR/strict.out" >&2
echo "strict font mode failed for an unexpected reason" >&2
exit 1
fi
if command -v soffice >/dev/null 2>&1; then
mkdir -p "$TMP_DIR/pdf"
soffice --headless --convert-to pdf --outdir "$TMP_DIR/pdf" "$TMP_DIR/layout.docx" >/dev/null 2>&1
test -s "$TMP_DIR/pdf/layout.pdf"
fi
printf '%s\n' "gongwen regression tests passed"