test(changelog): the cite rule's own cases, both callers asserted
Refs #262
This commit is contained in:
parent
ef818001d0
commit
f4cb970097
3 changed files with 169 additions and 0 deletions
|
|
@ -298,6 +298,38 @@ check "fragment mode over-bound refusal names the bound and the split fix" 1 \
|
||||||
"the bound is 300: split it into multiple '- ' entries in this same fragment" \
|
"the bound is 300: split it into multiple '- ' entries in this same fragment" \
|
||||||
in_tree fragments-dev-over-bound
|
in_tree fragments-dev-over-bound
|
||||||
|
|
||||||
|
# The terminal cite (#262) reds the PR that writes the fragment, through the
|
||||||
|
# same shared predicate — which is the whole point of the rule living there
|
||||||
|
# rather than in prose a reviewer has to remember.
|
||||||
|
fragment_tree fragments-dev-uncited 1.2.4-dev <<'EOF'
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 1.2.3 — 2026-07-20
|
||||||
|
|
||||||
|
- The shipped entry.
|
||||||
|
EOF
|
||||||
|
printf '%s\n' "- An entry that never learned to cite its issue." \
|
||||||
|
>"$TMP/fragments-dev-uncited/changelog.d/115.md"
|
||||||
|
check "fragment mode refuses an uncited entry, fragment named" 1 \
|
||||||
|
"115.md' has an entry with no issue citation" \
|
||||||
|
in_tree fragments-dev-uncited
|
||||||
|
check "fragment mode uncited refusal names the shape to write" 1 \
|
||||||
|
"end it with the issue it comes from: '(#N).'" \
|
||||||
|
in_tree fragments-dev-uncited
|
||||||
|
|
||||||
|
fragment_tree fragments-dev-misplaced-cite 1.2.4-dev <<'EOF'
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 1.2.3 — 2026-07-20
|
||||||
|
|
||||||
|
- The shipped entry.
|
||||||
|
EOF
|
||||||
|
printf '%s\n' "- The citation trails the period. (#115)" \
|
||||||
|
>"$TMP/fragments-dev-misplaced-cite/changelog.d/115.md"
|
||||||
|
check "fragment mode refuses a non-terminal citation, fragment named" 1 \
|
||||||
|
"115.md' has an entry whose issue citation is not terminal" \
|
||||||
|
in_tree fragments-dev-misplaced-cite
|
||||||
|
|
||||||
fragment_tree fragments-dev-grouped 1.2.4-dev <<'EOF'
|
fragment_tree fragments-dev-grouped 1.2.4-dev <<'EOF'
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,38 @@ check "preamble-only write is exact" 0 "" \
|
||||||
assert_file "$TMP/preamble-only/CHANGELOG.md" \
|
assert_file "$TMP/preamble-only/CHANGELOG.md" \
|
||||||
$'# Changelog\n\nOnly preamble so far.\n\n## 0.1.0 — 2026-07-24\n\n- The first entry ever (#1).'
|
$'# Changelog\n\nOnly preamble so far.\n\n## 0.1.0 — 2026-07-24\n\n- The first entry ever (#1).'
|
||||||
|
|
||||||
|
# --- the fragment predicate at release time (#262) ---------------------------
|
||||||
|
|
||||||
|
# The cite rule joins changelog_fragment_problem, so it binds both callers:
|
||||||
|
# the arming guard at PR time and this assembler at release time. Asserted
|
||||||
|
# rather than assumed — a release that publishes an uncited entry is the
|
||||||
|
# failure the PR-time guard exists to have already caught.
|
||||||
|
|
||||||
|
tree uncited-release <<EOF
|
||||||
|
$BASE_CHANGELOG
|
||||||
|
EOF
|
||||||
|
frag uncited-release 60.md <<'EOF'
|
||||||
|
- An entry that never learned to cite its issue.
|
||||||
|
EOF
|
||||||
|
check "release time: an uncited fragment refuses the release, fragment named" 1 \
|
||||||
|
"changelog.d/60.md' has an entry with no issue citation" \
|
||||||
|
in_tree uncited-release 0.2.0 2026-07-24
|
||||||
|
check "release time: the uncited refusal survives --check too" 1 \
|
||||||
|
"has an entry with no issue citation" \
|
||||||
|
in_tree uncited-release 0.2.0 2026-07-24 --check
|
||||||
|
check "release time: the refused release wrote nothing" 0 "" \
|
||||||
|
test -e "$TMP/uncited-release/changelog.d/60.md"
|
||||||
|
|
||||||
|
tree misplaced-release <<EOF
|
||||||
|
$BASE_CHANGELOG
|
||||||
|
EOF
|
||||||
|
frag misplaced-release 61.md <<'EOF'
|
||||||
|
- The citation trails the period. (#61)
|
||||||
|
EOF
|
||||||
|
check "release time: a non-terminal citation refuses the release" 1 \
|
||||||
|
"changelog.d/61.md' has an entry whose issue citation is not terminal" \
|
||||||
|
in_tree misplaced-release 0.2.0 2026-07-24
|
||||||
|
|
||||||
# --- --check is provably read-only -------------------------------------------
|
# --- --check is provably read-only -------------------------------------------
|
||||||
|
|
||||||
tree check-readonly <<EOF
|
tree check-readonly <<EOF
|
||||||
|
|
|
||||||
|
|
@ -331,6 +331,111 @@ check "length bound: published sections stay unvalidated — 0.3.0's over-bound
|
||||||
check "length bound: published sections stay unvalidated — 0.2.0 reds nothing either" 0 "" \
|
check "length bound: published sections stay unvalidated — 0.2.0 reds nothing either" 0 "" \
|
||||||
changelog_section_problem "$ROOT/CHANGELOG.md" 0.2.0
|
changelog_section_problem "$ROOT/CHANGELOG.md" 0.2.0
|
||||||
|
|
||||||
|
# --- the terminal issue cite (#262) ------------------------------------------
|
||||||
|
|
||||||
|
# cite_case <number> <entry-line...> — a fragment holding exactly the given
|
||||||
|
# lines, so a case reads as the entry it is about.
|
||||||
|
cite_case() {
|
||||||
|
local num="$1"
|
||||||
|
shift
|
||||||
|
printf '%s\n' "$@" >"$PF/$num.md"
|
||||||
|
}
|
||||||
|
|
||||||
|
cite_case 40 '- Local (#262).'
|
||||||
|
check "cite: the canonical '(#N).' passes" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/40.md"
|
||||||
|
cite_case 41 '- Sibling repo (crew#309).'
|
||||||
|
check "cite: a sibling-repo reference passes" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/41.md"
|
||||||
|
cite_case 42 '- Fully qualified (heavy-duty/crew#309).'
|
||||||
|
check "cite: an owner/repo reference passes" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/42.md"
|
||||||
|
cite_case 43 '- Two issues, one entry (#236, #250).'
|
||||||
|
check "cite: one group carrying two references passes" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/43.md"
|
||||||
|
|
||||||
|
# The cite is measured on the normalized entry, so a citation that lands on
|
||||||
|
# a continuation line still closes the entry — the #167 lesson, repeated:
|
||||||
|
# wrapping alone must never red a compliant entry.
|
||||||
|
cite_case 44 '- An entry whose prose wraps onto a' ' continuation line, cite and all (#262).'
|
||||||
|
check "cite: a citation on a continuation line passes — the entry is normalized first" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/44.md"
|
||||||
|
|
||||||
|
cite_case 45 '### Added' '' '- Added one (#101).' '- Added two (#102).' '' \
|
||||||
|
'### Changed' '' '- Changed one (#103).' '' '### Fixed' '' '- Fixed one (#104).'
|
||||||
|
check "cite: a grouped fragment, three headings, every entry compliant, passes" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/45.md"
|
||||||
|
|
||||||
|
# The two diagnoses are distinct by construction (D5): a builder who reads
|
||||||
|
# one must not be told the other's fix.
|
||||||
|
cite_case 50 '- No cite here.'
|
||||||
|
check "cite: an entry with no reference at all is refused" 1 \
|
||||||
|
"50.md' has an entry with no issue citation" \
|
||||||
|
changelog_fragment_problem "$PF/50.md"
|
||||||
|
check "cite: the uncited refusal names the shape to write" 1 \
|
||||||
|
"end it with the issue it comes from: '(#N).'" \
|
||||||
|
changelog_fragment_problem "$PF/50.md"
|
||||||
|
|
||||||
|
cite_case 51 '- Cite before the period. (#262)'
|
||||||
|
check "cite: a citation trailing the period is refused — the 248.md shape" 1 \
|
||||||
|
"51.md' has an entry whose issue citation is not terminal" \
|
||||||
|
changelog_fragment_problem "$PF/51.md"
|
||||||
|
check "cite: the misplaced refusal names the shape to write" 1 \
|
||||||
|
"exactly one '(#N)' group ends the entry, the final '.' after it" \
|
||||||
|
changelog_fragment_problem "$PF/51.md"
|
||||||
|
|
||||||
|
cite_case 52 '- Trailing prose (#262) and then more.'
|
||||||
|
check "cite: a citation with prose after it is refused" 1 \
|
||||||
|
"has an entry whose issue citation is not terminal" \
|
||||||
|
changelog_fragment_problem "$PF/52.md"
|
||||||
|
|
||||||
|
cite_case 53 '- Two groups (#236) and (#250).'
|
||||||
|
check "cite: two citation groups are refused — one terminal group, or none (D2)" 1 \
|
||||||
|
"has an entry whose issue citation is not terminal" \
|
||||||
|
changelog_fragment_problem "$PF/53.md"
|
||||||
|
|
||||||
|
cite_case 54 '- Bad token (#abc).'
|
||||||
|
check "cite: a reference with no digits is no reference" 1 \
|
||||||
|
"has an entry with no issue citation" \
|
||||||
|
changelog_fragment_problem "$PF/54.md"
|
||||||
|
cite_case 55 '- Bad token (#).'
|
||||||
|
check "cite: an empty reference is no reference" 1 \
|
||||||
|
"has an entry with no issue citation" \
|
||||||
|
changelog_fragment_problem "$PF/55.md"
|
||||||
|
|
||||||
|
# The citation need not name the file's own issue (D3): the filename already
|
||||||
|
# carries the authorizing one, so an entry may cite the incident beside it.
|
||||||
|
cite_case 56 '- Cites another issue entirely (#101).'
|
||||||
|
check "cite: the reference need not match the filename" 0 "" \
|
||||||
|
changelog_fragment_problem "$PF/56.md"
|
||||||
|
|
||||||
|
# Ordering: the bound outranks the cite, so a fragment that reds today draws
|
||||||
|
# the diagnosis it drew before this rule existed.
|
||||||
|
{
|
||||||
|
printf -- '- %s\n' "$(mkchars 301)"
|
||||||
|
printf -- '- Uncited too.\n'
|
||||||
|
} >"$PF/57.md"
|
||||||
|
check "cite: an over-bound entry still reports the bound, not the cite" 1 \
|
||||||
|
"57.md' has a 301-character entry" \
|
||||||
|
changelog_fragment_problem "$PF/57.md"
|
||||||
|
|
||||||
|
# Published sections keep their pre-rule prose (D4): reddening history is a
|
||||||
|
# wall, not a guard. Every shipped section predates the cite.
|
||||||
|
check "cite: a published section with uncited entries still reds nothing" 0 "" \
|
||||||
|
changelog_section_problem "$ROOT/CHANGELOG.md" 0.3.0
|
||||||
|
|
||||||
|
# The fragments this repo carries right now are the rule's own first
|
||||||
|
# constituency — the guard is worth nothing if the tree it ships in fails it.
|
||||||
|
assert_tree_fragments() {
|
||||||
|
local f
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -n "$f" ] || continue
|
||||||
|
changelog_fragment_problem "$f" || return 1
|
||||||
|
done <<<"$(changelog_fragments "$ROOT/changelog.d")"
|
||||||
|
}
|
||||||
|
check "cite: every fragment in this tree passes the rule it ships" 0 "" \
|
||||||
|
assert_tree_fragments
|
||||||
|
|
||||||
# --- the assembler (#114) ----------------------------------------------------
|
# --- the assembler (#114) ----------------------------------------------------
|
||||||
|
|
||||||
assert_assemble() {
|
assert_assemble() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue