|
#!/usr/bin/env bash |
|
# Unit tests for helpers.sh functions in isolation. |
|
|
|
source "$(dirname "${BASH_SOURCE[0]}")/lib/assert.bash" |
|
|
|
TMPROOT=$(mktemp -d -t jenny-units.XXXXXX) |
|
trap 'rm -rf "$TMPROOT"' EXIT |
|
|
|
# helpers.sh expects the util indirection to be resolved. |
|
SED=$(command -v gsed || command -v sed) |
|
AWK=$(command -v gawk || command -v awk) |
|
DATE=$(command -v gdate || command -v date) |
|
GREP=$(command -v ggrep || command -v grep) |
|
SUM=$(command -v md5sum || command -v md5) |
|
export SED AWK DATE GREP SUM |
|
PUBLISH_PATTERN="^[0-9]{4}-[0-9]{2}-[0-9]{2}(.*)" |
|
|
|
source "$REPO_ROOT/share/jenny/lib/helpers.sh" |
|
|
|
suite "filename helpers" |
|
|
|
it "get_full_filename strips the directory" |
|
assert_eq "$(get_full_filename '/a/b/2024-01-01 post.md')" "2024-01-01 post.md" |
|
|
|
it "get_filename strips the extension" |
|
assert_eq "$(get_filename '2024-01-01 post.md')" "2024-01-01 post" |
|
|
|
it "get_timestamp extracts the date" |
|
assert_eq "$(get_timestamp '2024-01-01 post.md')" "2024-01-01" |
|
|
|
it "get_post_date formats as MM/DD" |
|
assert_eq "$(get_post_date '2024-03-05 post.md')" "03/05" |
|
|
|
it "get_post_date_int formats as YYYYMMDD" |
|
assert_eq "$(get_post_date_int '2024-03-05 post.md')" "20240305" |
|
|
|
suite "draft and schedule detection" |
|
|
|
it "an undated file is a draft" |
|
if is_draft "notes.md"; then pass; else fail "expected notes.md to be a draft"; fi |
|
|
|
it "a dated file is not a draft" |
|
if is_draft "2024-01-01 post.md"; then fail "dated file wrongly a draft"; else pass; fi |
|
|
|
it "a future date is scheduled" |
|
future=$(( $(date +%Y) + 1 )) |
|
if is_scheduled "$future-01-01 post.md"; then pass; else fail "future post not scheduled"; fi |
|
|
|
it "a past date is not scheduled" |
|
if is_scheduled "2000-01-01 post.md"; then fail "past post wrongly scheduled"; else pass; fi |
|
|
|
suite "get_title" |
|
|
|
it "reads the first heading" |
|
printf '# Hello World\n\nBody\n' > "$TMPROOT/a.md" |
|
assert_eq "$(get_title "$TMPROOT/a.md")" "Hello World" |
|
|
|
it "ignores headings inside a backtick fence" |
|
printf '# Real Title\n\n```\n# not a title\n```\n' > "$TMPROOT/b.md" |
|
assert_eq "$(get_title "$TMPROOT/b.md")" "Real Title" |
|
|
|
it "ignores headings inside a tilde fence" |
|
printf '# Real Title\n\n~~~\n# not a title\n~~~\n' > "$TMPROOT/c.md" |
|
assert_eq "$(get_title "$TMPROOT/c.md")" "Real Title" |
|
|
|
it "takes only the first of several headings" |
|
printf '# First\n\n# Second\n' > "$TMPROOT/d.md" |
|
assert_eq "$(get_title "$TMPROOT/d.md")" "First" |
|
|
|
it "keeps a title containing shell metacharacters intact" |
|
printf '# Back`tick $(whoami) "quoted" & <angle>\n' > "$TMPROOT/e.md" |
|
assert_eq "$(get_title "$TMPROOT/e.md")" 'Back`tick $(whoami) "quoted" & <angle>' |
|
|
|
it "returns empty for a file with no heading" |
|
printf 'no heading here\n' > "$TMPROOT/f.md" |
|
assert_eq "$(get_title "$TMPROOT/f.md")" "" |
|
|
|
it "does not treat a #hashtag as a heading" |
|
printf '#nospace is not a heading\n\n# Real\n' > "$TMPROOT/g.md" |
|
assert_eq "$(get_title "$TMPROOT/g.md")" "Real" |
|
|
|
suite "escaping" |
|
|
|
it "html_escape escapes the five entities" |
|
assert_eq "$(html_escape 'a & b < c > d " e '"'"' f')" 'a & b < c > d " e ' f' |
|
|
|
it "html_escape escapes ampersands first" |
|
assert_eq "$(html_escape '<')" '&lt;' |
|
|
|
it "html_escape passes plain text through" |
|
assert_eq "$(html_escape 'Hello World')" 'Hello World' |
|
|
|
it "xml_escape escapes the five entities" |
|
assert_eq "$(xml_escape 'a & b < c > d " e '"'"' f')" 'a & b < c > d " e ' f' |
|
|
|
it "escaping is safe on an empty string" |
|
assert_eq "$(html_escape '')" "" |
|
|
|
suite "record join and split" |
|
|
|
it "join_by uses the given delimiter" |
|
assert_eq "$(join_by '|' a b c)" "a|b|c" |
|
|
|
it "join_by on a single element returns it unchanged" |
|
assert_eq "$(join_by '|' solo)" "solo" |
|
|
|
it "split_list round-trips a joined list" |
|
rec=$(join_by "$JENNY_FS" one two three) |
|
split_list "$rec" out |
|
assert_eq "${#out[@]}" "3" |
|
|
|
it "split_list preserves empty trailing fields" |
|
rec=$(join_by "$JENNY_FS" one '' three) |
|
split_list "$rec" out2 |
|
assert_eq "${out2[1]}" "" |
|
|
|
it "split_list handles a one-element list" |
|
split_list "solo" out3 |
|
assert_eq "${#out3[@]}" "1" |
|
|
|
suite "tag names" |
|
|
|
it "a plain tag is valid" |
|
if is_valid_tag "alpha"; then pass; else fail "alpha rejected"; fi |
|
|
|
it "hyphens and underscores are valid" |
|
if is_valid_tag "my_tag-2"; then pass; else fail "my_tag-2 rejected"; fi |
|
|
|
it "a traversing tag is rejected" |
|
if is_valid_tag "../evil"; then fail "../evil accepted"; else pass; fi |
|
|
|
it "a slash is rejected" |
|
if is_valid_tag "a/b"; then fail "a/b accepted"; else pass; fi |
|
|
|
it "a dot-only tag is rejected" |
|
if is_valid_tag ".."; then fail ".. accepted"; else pass; fi |
|
|
|
it "an empty tag is rejected" |
|
if is_valid_tag ""; then fail "empty tag accepted"; else pass; fi |
|
|
|
suite "pagination arithmetic" |
|
|
|
it "get_total_page_count rounds up" |
|
assert_eq "$(get_total_page_count 10 3)" "4" |
|
|
|
it "an exact multiple needs no extra page" |
|
assert_eq "$(get_total_page_count 9 3)" "3" |
|
|
|
it "a single post is one page" |
|
assert_eq "$(get_total_page_count 1 3)" "1" |
|
|
|
suite "path prefixes" |
|
|
|
it "the site root needs no prefix" |
|
assert_eq "$(get_url_prefix 0)" "." |
|
|
|
it "one level deep goes up once" |
|
assert_eq "$(get_url_prefix 1)" ".." |
|
|
|
it "three levels deep goes up three times" |
|
assert_eq "$(get_url_prefix 3)" "../../.." |
|
|
|
|
|
suite "pagination decisions" |
|
|
|
it "a post lands on the right page" |
|
assert_eq "$(page_for_index 1 3)/$(page_for_index 3 3)/$(page_for_index 4 3)/$(page_for_index 10 3)" "1/1/2/4" |
|
|
|
it "a full page is a boundary" |
|
if is_page_boundary 3 3 10; then pass; else fail "post 3 of 10 at 3/page should close a page"; fi |
|
|
|
it "a partial page mid-run is not a boundary" |
|
if is_page_boundary 2 3 10; then fail "post 2 should not close a page"; else pass; fi |
|
|
|
it "the last post is always a boundary" |
|
if is_page_boundary 10 3 10; then pass; else fail "the final post must close a page"; fi |
|
|
|
it "the final page is the main index" |
|
if is_main_index 4 4; then pass; else fail "page 4 of 4 is the main index"; fi |
|
|
|
it "an earlier page is not the main index" |
|
if is_main_index 2 4; then fail "page 2 of 4 is not the main index"; else pass; fi |
|
|
|
suite "main index filling" |
|
|
|
it "a short main index needs filling" |
|
if needs_filling 1 3 4; then pass; else fail "1 post at 3/page over 4 pages should fill"; fi |
|
|
|
it "a full main index does not" |
|
if needs_filling 3 3 4; then fail "a full page needs no filling"; else pass; fi |
|
|
|
it "a single-page blog never fills" |
|
if needs_filling 1 3 1; then fail "one page means nothing to fill from"; else pass; fi |
|
|
|
it "fill_count asks for the shortfall" |
|
assert_eq "$(fill_count 1 3 10)" "2" |
|
|
|
it "fill_count is capped by what is available" |
|
assert_eq "$(fill_count 1 3 1)" "1" |
|
|
|
it "fill_count never goes negative" |
|
assert_eq "$(fill_count 5 3 10)" "0" |
|
|
|
it "take_last takes from the tail" |
|
arr=(a b c d e) |
|
assert_eq "$(take_last 2 arr | tr '\n' ' ')" "d e " |
|
|
|
it "take_last clamps to the array length" |
|
assert_eq "$(take_last 99 arr | tr '\n' ' ')" "a b c d e " |
|
|
|
it "take_last of zero yields nothing" |
|
assert_eq "$(take_last 0 arr)" "" |
|
|
|
suite "feed selection" |
|
|
|
it "feed_slice reverses to newest-first" |
|
feed=(oldest middle newest) |
|
assert_eq "$(feed_slice 10 feed | tr '\n' ' ')" "newest middle oldest " |
|
|
|
it "feed_slice honours the cap" |
|
assert_eq "$(feed_slice 2 feed | tr '\n' ' ')" "newest middle " |
|
|
|
it "feed_slice on an empty list yields nothing" |
|
empty=() |
|
assert_eq "$(feed_slice 10 empty)" "" |
|
|
|
it "feed_slice of zero yields nothing" |
|
assert_eq "$(feed_slice 0 feed)" "" |
|
|
|
suite "records" |
|
|
|
it "a post record round-trips its fields" |
|
rec=$(make_record "./post/x.html" "/post/x.html" "My Title" "01/02" "RFC822" "") |
|
assert_eq "$(record_field "$rec" 1)|$(record_field "$rec" 3)|$(record_field "$rec" 7)" \ |
|
"./post/x.html|My Title|post" |
|
|
|
it "a title with a delimiter-adjacent character survives" |
|
rec=$(make_record "u" "c" 'Quote " and $(cmd) and `tick`' "d" "r" "") |
|
assert_eq "$(record_field "$rec" 3)" 'Quote " and $(cmd) and `tick`' |
|
|
|
it "a break record carries its page number" |
|
rec=$(make_break_record "" "3") |
|
assert_eq "$(record_field "$rec" 7)|$(record_field "$rec" 8)" "break|3" |
|
|
|
it "a tag record carries the tag name" |
|
rec=$(make_record "u" "c" "t" "d" "r" "alpha") |
|
assert_eq "$(record_field "$rec" 6)" "alpha" |
|
|
|
suite "output depth" |
|
|
|
it "the main index sits at the root" |
|
assert_eq "$(index_depth_for "")" "0" |
|
|
|
it "a tag index sits two levels down" |
|
assert_eq "$(index_depth_for "alpha")" "2" |
|
|
|
it "a paginated page sits one level down" |
|
assert_eq "$(page_depth_for "")" "1" |
|
|
|
it "a tag's paginated page sits three levels down" |
|
assert_eq "$(page_depth_for "alpha")" "3" |
|
|
|
it "depths compose into the right prefixes" |
|
assert_eq "$(get_url_prefix "$(index_depth_for "")")|$(get_url_prefix "$(page_depth_for "alpha")")" \ |
|
".|../../.." |
|
|
|
suite "URL_STYLE" |
|
|
|
it "the default style keeps the .html extension" |
|
URL_STYLE="" |
|
assert_eq "$(post_href post-01)" "/post/post-01.html" |
|
|
|
it "an explicit 'html' style keeps it too" |
|
URL_STYLE=html |
|
assert_eq "$(post_href post-01)" "/post/post-01.html" |
|
|
|
it "the extensionless style drops it" |
|
URL_STYLE=extensionless |
|
assert_eq "$(post_href post-01)" "/post/post-01" |
|
|
|
it "a slug containing a dot is not truncated" |
|
URL_STYLE=extensionless |
|
assert_eq "$(post_href my.post.v2)" "/post/my.post.v2" |
|
|
|
it "the html style leaves a dotted slug alone" |
|
URL_STYLE=html |
|
assert_eq "$(post_href my.post.v2)" "/post/my.post.v2.html" |
|
|
|
it "an unknown style is rejected rather than guessed at" |
|
URL_STYLE=bogus |
|
out=$(post_href post-01 2>&1); st=$? |
|
assert_failure "$st" "$out" |
|
|
|
it "the rejection names the offending value" |
|
assert_contains "$out" "bogus" |
|
|
|
it "the output filename always keeps .html" |
|
URL_STYLE=extensionless |
|
assert_eq "$(post_filename post-01)" "post-01.html" |
|
|
|
it "the filename is unaffected by the html style too" |
|
URL_STYLE=html |
|
assert_eq "$(post_filename post-01)" "post-01.html" |
|
|
|
URL_STYLE="" |
|
|
|
suite "markdown: intraword underscores" |
|
|
|
# The deep-link id is stripped so these assertions can state the markup they |
|
# are about. Each is checking how a span of text is parsed, not that a |
|
# paragraph carries an anchor -- that has tests of its own, and pinning a |
|
# content hash in every one of these would make them unreadable and would |
|
# break them all again on any wording change. |
|
md() { printf '%s\n' "$1" | "$AWK" -f "$REPO_ROOT/share/jenny/lib/markdown.awk/markdown.awk" \ |
|
| "$SED" 's/<p id="p-[0-9a-f]*\(-[0-9]*\)\{0,1\}">/<p>/g'; } |
|
|
|
it "an underscore inside a word is literal" |
|
assert_eq "$(md 'node_modules here')" "<p>node_modules here</p>" |
|
|
|
it "snake_case survives intact" |
|
assert_eq "$(md 'snake_case_name')" "<p>snake_case_name</p>" |
|
|
|
it "a leading underscore still opens emphasis" |
|
assert_eq "$(md 'a _real_ emphasis')" "<p>a <em>real</em> emphasis</p>" |
|
|
|
it "intraword and real emphasis coexist on one line" |
|
assert_eq "$(md 'x_y and _z_')" "<p>x_y and <em>z</em></p>" |
|
|
|
it "asterisk emphasis is unaffected" |
|
assert_eq "$(md 'a *starred* word')" "<p>a <em>starred</em> word</p>" |
|
|
|
it "intraword asterisks still emphasise, as in CommonMark" |
|
assert_eq "$(md 'a*b*c')" "<p>a<em>b</em>c</p>" |
|
|
|
it "bold with underscores still works" |
|
assert_eq "$(md 'a __bold__ word')" "<p>a <strong>bold</strong> word</p>" |
|
|
|
it "no stray em is emitted for an identifier" |
|
assert_not_contains "$(md 'multi_word_ident text')" "<em>" |
|
|
|
suite "markdown: fenced code blocks" |
|
|
|
# Whitespace is significant inside <pre>, so the tags are joined to the first |
|
# and last content lines. A newline after the opening tag would render as a |
|
# blank first line in the block. |
|
it "a plain fence produces a code block" |
|
assert_eq "$(md '``` |
|
echo hi |
|
```')" "<pre><code>echo hi</code></pre>" |
|
|
|
it "no blank line is introduced at the top of a block" |
|
assert_not_contains "$(md '``` |
|
echo hi |
|
```')" "<code> |
|
" |
|
|
|
it "no blank line at the bottom either" |
|
assert_not_contains "$(md '``` |
|
echo hi |
|
```')" " |
|
</code>" |
|
|
|
it "interior blank lines are preserved" |
|
assert_contains "$(md '``` |
|
one |
|
|
|
two |
|
```')" "one |
|
|
|
two" |
|
|
|
it "the fence markers are not in the output" |
|
assert_not_contains "$(md '``` |
|
echo hi |
|
```')" '`' |
|
|
|
it "a language tag becomes a class" |
|
assert_contains "$(md '```bash |
|
echo hi |
|
```')" 'class="language-bash"' |
|
|
|
it "multiple lines are preserved separately" |
|
out=$(md '``` |
|
line one |
|
line two |
|
```') |
|
assert_contains "$out" "line one |
|
line two" |
|
|
|
it "HTML inside a fence is escaped" |
|
assert_contains "$(md '``` |
|
<b>x</b> & y |
|
```')" "<b>x</b> & y" |
|
|
|
it "markdown inside a fence is left alone" |
|
assert_contains "$(md '``` |
|
*not emphasis* and _not_ either |
|
```')" "*not emphasis* and _not_ either" |
|
|
|
it "smart quotes do not touch fenced content" |
|
assert_contains "$(md '``` |
|
say "quoted" here |
|
```')" 'say "quoted" here' |
|
|
|
it "text around a fence still renders" |
|
out=$(md 'Before. |
|
|
|
``` |
|
code |
|
``` |
|
|
|
After.') |
|
assert_contains "$out" "<p>Before.</p>" |
|
|
|
it "text after a fence is a separate paragraph" |
|
assert_contains "$out" "<p>After.</p>" |
|
|
|
it "a tilde fence works too" |
|
assert_contains "$(md '~~~ |
|
echo hi |
|
~~~')" "<pre><code>" |
|
|
|
it "an empty fence is handled" |
|
assert_contains "$(md '``` |
|
```')" "<pre><code>" |
|
|
|
it "indented code blocks still work" |
|
assert_contains "$(md 'Text: |
|
|
|
indented')" "<pre><code>" |
|
|
|
suite "markdown: reference links" |
|
|
|
it "an explicit label resolves" |
|
assert_contains "$(md 'See [the docs][d]. |
|
|
|
[d]: http://example.com')" '<a href="http://example.com">the docs</a>' |
|
|
|
it "the definition does not appear in the output" |
|
assert_not_contains "$(md 'See [the docs][d]. |
|
|
|
[d]: http://example.com')" 'http://example.com<' |
|
|
|
it "an implicit label reuses the link text" |
|
assert_contains "$(md 'See [docs][]. |
|
|
|
[docs]: http://example.com')" '<a href="http://example.com">docs</a>' |
|
|
|
it "a shortcut reference resolves" |
|
assert_contains "$(md 'See [docs]. |
|
|
|
[docs]: http://example.com')" '<a href="http://example.com">docs</a>' |
|
|
|
it "labels are case-insensitive" |
|
assert_contains "$(md 'See [Docs][D]. |
|
|
|
[d]: http://example.com')" 'href="http://example.com"' |
|
|
|
it "a title is carried through" |
|
assert_contains "$(md 'See [x][d]. |
|
|
|
[d]: http://example.com "The Title"')" 'title="The Title"' |
|
|
|
it "a definition before its use resolves" |
|
assert_contains "$(md '[d]: http://example.com |
|
|
|
See [x][d].')" 'href="http://example.com"' |
|
|
|
it "an unresolved reference is left as literal text" |
|
out=$(md 'See [x][nope].') |
|
assert_not_contains "$out" '<a href="">' |
|
|
|
it "inline links are unaffected" |
|
assert_contains "$(md 'See [x](http://example.com).')" '<a href="http://example.com">x</a>' |
|
|
|
it "footnotes still work alongside references" |
|
out=$(md 'Text[^1] and [x][d]. |
|
|
|
[^1]: note |
|
[d]: http://example.com') |
|
assert_contains "$out" 'href="http://example.com"' |
|
|
|
it "the footnote still renders" |
|
assert_contains "$out" 'fn-item' |
|
|
|
it "a definition inside a fence is literal" |
|
assert_contains "$(md '``` |
|
[d]: http://example.com |
|
```')" '[d]: http://example.com' |
|
|
|
suite "markdown: MMD metadata block" |
|
|
|
it "a metadata pair is wrapped, not stripped" |
|
assert_contains "$(md 'Series: Web Typography |
|
|
|
# Title')" 'data-key="Series"' |
|
|
|
it "the value is exposed as an attribute" |
|
assert_contains "$(md 'Series: Web Typography |
|
|
|
# Title')" 'data-value="Web Typography"' |
|
|
|
it "the original text is preserved verbatim" |
|
assert_contains "$(md 'Series: Web Typography |
|
|
|
# Title')" '>Series: Web Typography</p>' |
|
|
|
it "the block carries a class the theme can target" |
|
assert_contains "$(md 'Series: Web Typography |
|
|
|
# Title')" 'class="meta"' |
|
|
|
it "the body still renders" |
|
# Headings carry an id now, so this asserts the text rather than the exact tag. |
|
assert_contains "$(md 'Series: Web Typography |
|
|
|
# Title')" ">Title</h1>" |
|
|
|
it "multiple keys each get their own element" |
|
out=$(md 'Series: Web Typography |
|
Author: Pat |
|
|
|
# Title') |
|
assert_contains "$out" 'data-key="Series"' |
|
|
|
it "the second key is wrapped too" |
|
assert_contains "$out" 'data-key="Author"' |
|
|
|
it "tags are wrapped like any other key" |
|
assert_contains "$(md 'tags: alpha beta |
|
|
|
# Title')" 'data-key="tags"' |
|
|
|
it "a multi-word key is preserved verbatim" |
|
assert_contains "$(md 'Base Header Level: 2 |
|
|
|
# Title')" 'data-key="Base Header Level"' |
|
|
|
# MultiMarkdown makes no attempt to tell metadata from prose that happens to |
|
# start Key: value -- position is the whole rule. Verified against |
|
# multimarkdown 6.6.0, which renders "Note: this is prose." at the top of a |
|
# file as <meta name="note">. Matching that rather than inventing a smarter |
|
# heuristic keeps jenny predictable for anyone who knows MMD. |
|
it "a leading Key: value line is metadata even if it reads as prose" |
|
assert_contains "$(md 'Note: this is ordinary prose. |
|
|
|
# Title')" 'data-key="Note"' |
|
|
|
it "such a line keeps its text, so nothing is lost either way" |
|
assert_contains "$(md 'Note: this is ordinary prose. |
|
|
|
# Title')" '>Note: this is ordinary prose.</p>' |
|
|
|
it "a block below the top of the file is prose" |
|
assert_not_contains "$(md '# Title |
|
|
|
Series: Web Typography')" 'data-key="Series"' |
|
|
|
it "a document with no metadata emits no meta element" |
|
assert_not_contains "$(md '# Title |
|
|
|
Body.')" 'class="meta"' |
|
|
|
it "attribute values are escaped" |
|
assert_contains "$(md 'Series: A & B <x> |
|
|
|
# Title')" 'data-value="A & B <x>"' |
|
|
|
suite "markdown: tables" |
|
|
|
it "a basic table renders as a table" |
|
assert_contains "$(md '| A | B | |
|
|---|---| |
|
| 1 | 2 |')" "<table>" |
|
|
|
it "the header row becomes thead" |
|
assert_contains "$(md '| A | B | |
|
|---|---| |
|
| 1 | 2 |')" "<thead>" |
|
|
|
it "header cells are th" |
|
assert_contains "$(md '| A | B | |
|
|---|---| |
|
| 1 | 2 |')" "<th" |
|
|
|
it "body cells are td" |
|
assert_contains "$(md '| A | B | |
|
|---|---| |
|
| 1 | 2 |')" "<td" |
|
|
|
it "the separator row is not rendered" |
|
assert_not_contains "$(md '| A | B | |
|
|---|---| |
|
| 1 | 2 |')" "---" |
|
|
|
it "cell text is preserved" |
|
out=$(md '| A | B | |
|
|---|---| |
|
| 1 | 2 |') |
|
assert_contains "$out" "A" |
|
|
|
it "left alignment is applied" |
|
assert_contains "$(md '| L | |
|
|:--| |
|
| 1 |')" "text-align:left" |
|
|
|
it "centre alignment is applied" |
|
assert_contains "$(md '| C | |
|
|:-:| |
|
| 1 |')" "text-align:center" |
|
|
|
it "right alignment is applied" |
|
assert_contains "$(md '| R | |
|
|--:| |
|
| 1 |')" "text-align:right" |
|
|
|
it "pipes at the row edges are optional" |
|
assert_contains "$(md 'A | B |
|
--- | --- |
|
1 | 2')" "<table>" |
|
|
|
it "inline markdown inside a cell is parsed" |
|
assert_contains "$(md '| **bold** | |
|
|---| |
|
| a |')" "<strong>bold</strong>" |
|
|
|
it "a link inside a cell is parsed" |
|
assert_contains "$(md '| x | |
|
|---| |
|
| [y](http://e.com) |')" '<a href="http://e.com">y</a>' |
|
|
|
it "multiple body rows all render" |
|
out=$(md '| A | |
|
|---| |
|
| 1 | |
|
| 2 |') |
|
assert_eq "$(printf '%s' "$out" | grep -c '<td')" "2" |
|
|
|
it "the table closes" |
|
assert_contains "$(md '| A | |
|
|---| |
|
| 1 |')" "</table>" |
|
|
|
it "text after a table is a separate paragraph" |
|
assert_contains "$(md '| A | |
|
|---| |
|
| 1 | |
|
|
|
After.')" "<p>After.</p>" |
|
|
|
it "a pipe in prose is not a table" |
|
assert_not_contains "$(md 'a | b in ordinary prose')" "<table>" |
|
|
|
# A cell must handle raw HTML exactly as prose does. That used to mean both |
|
# passed it through; it now means both escape it unless the caller asks for |
|
# passthrough with UNSAFE_HTML=1. What matters is that a cell is not a hole in |
|
# whichever rule is in force. |
|
it "raw html in a cell is escaped, as in prose" |
|
assert_contains "$(md '| <b>x</b> | |
|
|---| |
|
| a |')" "<b>x</b>" |
|
|
|
it "a table inside a fence is literal" |
|
assert_contains "$(md '``` |
|
| A | |
|
|---| |
|
```')" "| A |" |
|
|
|
suite "markdown: citations" |
|
|
|
it "a citation renders an inline reference" |
|
assert_contains "$(md 'Shown[#smith]. |
|
|
|
[#smith]: John Smith. 2011.')" 'class="citation"' |
|
|
|
it "the citation is numbered" |
|
assert_contains "$(md 'Shown[#smith]. |
|
|
|
[#smith]: John Smith. 2011.')" '(1)' |
|
|
|
it "a locator is carried into the reference" |
|
assert_contains "$(md 'Shown[p. 42][#smith]. |
|
|
|
[#smith]: John Smith. 2011.')" '(p. 42, 1)' |
|
|
|
it "the definition does not render as body text" |
|
assert_not_contains "$(md 'Shown[#smith]. |
|
|
|
[#smith]: John Smith. 2011.')" '<p>[#smith]:' |
|
|
|
it "a citation list is emitted" |
|
assert_contains "$(md 'Shown[#smith]. |
|
|
|
[#smith]: John Smith. 2011.')" 'class="citations"' |
|
|
|
it "the citation body appears in the list" |
|
assert_contains "$(md 'Shown[#smith]. |
|
|
|
[#smith]: John Smith. 2011.')" 'John Smith. 2011.' |
|
|
|
it "markdown inside a citation body is parsed" |
|
assert_contains "$(md 'Shown[#smith]. |
|
|
|
[#smith]: John Smith. *A Book*. 2011.')" '<em>A Book</em>' |
|
|
|
it "the inline reference links to the entry" |
|
assert_contains "$(md 'Shown[#smith]. |
|
|
|
[#smith]: John Smith. 2011.')" 'href="#cn:1"' |
|
|
|
it "the entry links back to the reference" |
|
assert_contains "$(md 'Shown[#smith]. |
|
|
|
[#smith]: John Smith. 2011.')" 'href="#cnref:1"' |
|
|
|
it "numbering follows first use" |
|
out=$(md 'A[#x] and B[#y]. |
|
|
|
[#x]: First. |
|
[#y]: Second.') |
|
assert_contains "$out" '(1)' |
|
|
|
it "the second citation gets the next number" |
|
assert_contains "$out" '(2)' |
|
|
|
it "a repeated citation reuses its number" |
|
out=$(md 'A[#x], B[#y], C[#x]. |
|
|
|
[#x]: First. |
|
[#y]: Second.') |
|
assert_eq "$(printf '%s' "$out" | grep -o 'class="citation">(1)' | wc -l | tr -d ' ')" "2" |
|
|
|
it "footnotes still work alongside citations" |
|
out=$(md 'A[^1] and B[#x]. |
|
|
|
[^1]: a note |
|
[#x]: a citation') |
|
assert_contains "$out" 'fn-item' |
|
|
|
it "the citation still renders too" |
|
assert_contains "$out" 'class="citations"' |
|
|
|
it "a citation inside a fence is literal" |
|
assert_contains "$(md '``` |
|
[#x]: literal |
|
```')" '[#x]: literal' |
|
|
|
summary |