summaryrefslogtreecommitdiff
path: root/tests/suite/model
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-04-13 10:39:45 +0200
committerGitHub <noreply@github.com>2024-04-13 08:39:45 +0000
commit020294fca9a7065d4b9cf4e677f606ebaaa29b00 (patch)
treec0027ad22046e2726c22298461327823d6b88d53 /tests/suite/model
parent72dd79210602ecc799726fc096b078afbb47f299 (diff)
Better test runner (#3922)
Diffstat (limited to 'tests/suite/model')
-rw-r--r--tests/suite/model/bibliography.typ55
-rw-r--r--tests/suite/model/cite.typ92
-rw-r--r--tests/suite/model/document.typ36
-rw-r--r--tests/suite/model/emph-strong.typ74
-rw-r--r--tests/suite/model/enum.typ156
-rw-r--r--tests/suite/model/figure.typ220
-rw-r--r--tests/suite/model/footnote.typ182
-rw-r--r--tests/suite/model/heading.typ80
-rw-r--r--tests/suite/model/link.typ77
-rw-r--r--tests/suite/model/list.typ147
-rw-r--r--tests/suite/model/numbering.typ103
-rw-r--r--tests/suite/model/outline.typ176
-rw-r--r--tests/suite/model/par.typ78
-rw-r--r--tests/suite/model/quote.typ86
-rw-r--r--tests/suite/model/ref.typ56
-rw-r--r--tests/suite/model/terms.typ77
16 files changed, 1695 insertions, 0 deletions
diff --git a/tests/suite/model/bibliography.typ b/tests/suite/model/bibliography.typ
new file mode 100644
index 00000000..7197082f
--- /dev/null
+++ b/tests/suite/model/bibliography.typ
@@ -0,0 +1,55 @@
+// Test citations and bibliographies.
+
+--- bibliography-basic ---
+#set page(width: 200pt)
+
+= Details
+See also @arrgh #cite(<distress>, supplement: [p.~22]), @arrgh[p.~4], and @distress[p.~5].
+#bibliography("/assets/bib/works.bib")
+
+--- bibliography-before-content ---
+// Test unconventional order.
+#set page(width: 200pt)
+#bibliography(
+ "/assets/bib/works.bib",
+ title: [Works to be cited],
+ style: "chicago-author-date",
+)
+#line(length: 100%)
+
+As described by #cite(<netwok>, form: "prose"),
+the net-work is a creature of its own.
+This is close to piratery! @arrgh
+And quark! @quark
+
+--- bibliography-multiple-files ---
+#set page(width: 200pt)
+#set heading(numbering: "1.")
+#show bibliography: set heading(numbering: "1.")
+
+= Multiple Bibs
+Now we have multiple bibliographies containing @glacier-melt @keshav2007read
+#bibliography(("/assets/bib/works.bib", "/assets/bib/works_too.bib"))
+
+--- bibliography-duplicate-key ---
+// Error: 15-65 duplicate bibliography keys: netwok, issue201, arrgh, quark, distress, glacier-melt, tolkien54, DBLP:books/lib/Knuth86a, sharing, restful, mcintosh_anxiety, psychology25
+#bibliography(("/assets/bib/works.bib", "/assets/bib/works.bib"))
+
+--- bibliography-ordering ---
+#set page(width: 300pt)
+
+@mcintosh_anxiety
+@psychology25
+
+#bibliography("/assets/bib/works.bib")
+
+--- bibliography-full ---
+// LARGE
+#set page(paper: "a6", height: 170mm)
+#bibliography("/assets/bib/works.bib", full: true)
+
+--- bibliography-math ---
+#set page(width: 200pt)
+
+@Zee04
+#bibliography("/assets/bib/works_too.bib", style: "mla")
diff --git a/tests/suite/model/cite.typ b/tests/suite/model/cite.typ
new file mode 100644
index 00000000..c6bdccb1
--- /dev/null
+++ b/tests/suite/model/cite.typ
@@ -0,0 +1,92 @@
+--- cite-footnote ---
+Hello @netwok
+And again: @netwok
+
+#pagebreak()
+#bibliography("/assets/bib/works.bib", style: "chicago-notes")
+
+--- cite-form ---
+#set page(width: 200pt)
+
+Nothing: #cite(<arrgh>, form: none)
+
+#cite(<netwok>, form: "prose") say stuff.
+
+#bibliography("/assets/bib/works.bib", style: "apa")
+
+--- cite-group ---
+A#[@netwok@arrgh]B \
+A@netwok@arrgh B \
+A@netwok @arrgh B \
+A@netwok @arrgh. B \
+
+A @netwok#[@arrgh]B \
+A @netwok@arrgh, B \
+A @netwok @arrgh, B \
+A @netwok @arrgh. B \
+
+A#[@netwok @arrgh @quark]B. \
+A @netwok @arrgh @quark B. \
+A @netwok @arrgh @quark, B.
+
+#set text(0pt)
+#bibliography("/assets/bib/works.bib")
+
+--- cite-grouping-and-ordering ---
+@mcintosh_anxiety
+@psychology25
+@netwok
+@issue201
+@arrgh
+@quark
+@distress,
+@glacier-melt
+@issue201
+@tolkien54
+@sharing
+@restful
+
+#show bibliography: none
+#bibliography("/assets/bib/works.bib")
+
+--- issue-785-cite-locate ---
+// Test citation in other introspection.
+#set page(width: 180pt)
+#set heading(numbering: "1")
+
+#outline(
+ title: [List of Figures],
+ target: figure.where(kind: image),
+)
+
+#pagebreak()
+
+= Introduction <intro>
+#figure(
+ rect[-- PIRATE --],
+ caption: [A pirate @arrgh in @intro],
+)
+
+#context [Citation @distress on page #here().page()]
+
+#pagebreak()
+#bibliography("/assets/bib/works.bib", style: "chicago-notes")
+
+--- issue-1597-cite-footnote ---
+// Tests that when a citation footnote is pushed to next page, things still
+// work as expected.
+#set page(height: 60pt)
+#lorem(4)
+
+#footnote[@netwok]
+#show bibliography: none
+#bibliography("/assets/bib/works.bib")
+
+--- issue-2531-cite-show-set ---
+// Test show set rules on citations.
+#show cite: set text(red)
+A @netwok @arrgh.
+B #cite(<netwok>) #cite(<arrgh>).
+
+#show bibliography: none
+#bibliography("/assets/bib/works.bib")
diff --git a/tests/suite/model/document.typ b/tests/suite/model/document.typ
new file mode 100644
index 00000000..6f8e7131
--- /dev/null
+++ b/tests/suite/model/document.typ
@@ -0,0 +1,36 @@
+// Test document and page-level styles.
+
+--- document-set-title ---
+// This is okay.
+#set document(title: [Hello])
+What's up?
+
+--- document-set-author-date ---
+// This, too.
+#set document(author: ("A", "B"), date: datetime.today())
+
+--- document-date-bad ---
+// Error: 21-28 expected datetime, none, or auto, found string
+#set document(date: "today")
+
+--- document-author-bad ---
+// This, too.
+// Error: 23-29 expected string, found integer
+#set document(author: (123,))
+What's up?
+
+--- document-set-after-content ---
+Hello
+
+// Error: 2-30 document set rules must appear before any content
+#set document(title: [Hello])
+
+--- document-constructor ---
+// Error: 2-12 can only be used in set rules
+#document()
+
+--- document-set-in-container ---
+#box[
+ // Error: 4-32 document set rules are not allowed inside of containers
+ #set document(title: [Hello])
+]
diff --git a/tests/suite/model/emph-strong.typ b/tests/suite/model/emph-strong.typ
new file mode 100644
index 00000000..2af8bb16
--- /dev/null
+++ b/tests/suite/model/emph-strong.typ
@@ -0,0 +1,74 @@
+// Test emph and strong.
+
+--- emph-syntax ---
+// Basic.
+_Emphasized and *strong* words!_
+
+// Inside of a word it's a normal underscore or star.
+hello_world Nutzer*innen
+
+// CJK characters will not need spaces.
+中文一般使用*粗体*或者_楷体_来表示强调。
+
+日本語では、*太字*や_斜体_を使って強調します。
+
+中文中混有*Strong*和_Empasis_。
+
+// Can contain paragraph in nested content block.
+_Still #[
+
+] emphasized._
+
+--- emph-and-strong-call-in-word ---
+// Inside of words can still use the functions.
+P#strong[art]ly em#emph[phas]ized.
+
+--- emph-empty-hint ---
+// Warning: 1-3 no text within underscores
+// Hint: 1-3 using multiple consecutive underscores (e.g. __) has no additional effect
+__
+
+--- emph-double-underscore-empty-hint ---
+// Warning: 1-3 no text within underscores
+// Hint: 1-3 using multiple consecutive underscores (e.g. __) has no additional effect
+// Warning: 13-15 no text within underscores
+// Hint: 13-15 using multiple consecutive underscores (e.g. __) has no additional effect
+__not italic__
+
+--- emph-unclosed ---
+// Error: 6-7 unclosed delimiter
+#box[_Scoped] to body.
+
+--- emph-ends-at-parbreak ---
+// Ends at paragraph break.
+// Error: 1-2 unclosed delimiter
+_Hello
+
+World
+
+--- emph-strong-unclosed-nested ---
+// Error: 11-12 unclosed delimiter
+// Error: 3-4 unclosed delimiter
+#[_Cannot *be interleaved]
+
+--- strong-delta ---
+// Adjusting the delta that strong applies on the weight.
+Normal
+
+#set strong(delta: 300)
+*Bold*
+
+#set strong(delta: 150)
+*Medium* and *#[*Bold*]*
+
+--- strong-empty-hint ---
+// Warning: 1-3 no text within stars
+// Hint: 1-3 using multiple consecutive stars (e.g. **) has no additional effect
+**
+
+--- strong-double-star-empty-hint ---
+// Warning: 1-3 no text within stars
+// Hint: 1-3 using multiple consecutive stars (e.g. **) has no additional effect
+// Warning: 11-13 no text within stars
+// Hint: 11-13 using multiple consecutive stars (e.g. **) has no additional effect
+**not bold**
diff --git a/tests/suite/model/enum.typ b/tests/suite/model/enum.typ
new file mode 100644
index 00000000..57a4d7a6
--- /dev/null
+++ b/tests/suite/model/enum.typ
@@ -0,0 +1,156 @@
+// Test enumerations.
+
+--- enum-function-call ---
+#enum[Embrace][Extend][Extinguish]
+
+--- enum-number-override-nested ---
+0. Before first!
+1. First.
+ 2. Indented
+
++ Second
+
+--- enum-built-in-loop ---
+// Test automatic numbering in summed content.
+#for i in range(5) {
+ [+ #numbering("I", 1 + i)]
+}
+
+--- list-mix ---
+// Mix of different lists
+- Bullet List
++ Numbered List
+/ Term: List
+
+--- enum-syntax-at-start ---
+// In the line.
+1.2 \
+This is 0. \
+See 0.3. \
+
+--- enum-syntax-edge-cases ---
+// Edge cases.
++
+Empty \
++Nope \
+a + 0.
+
+--- enum-number-override ---
+// Test item number overriding.
+1. first
++ second
+5. fifth
+
+#enum(
+ enum.item(1)[First],
+ [Second],
+ enum.item(5)[Fifth]
+)
+
+--- enum-numbering-pattern ---
+// Test numbering pattern.
+#set enum(numbering: "(1.a.*)")
++ First
++ Second
+ 2. Nested
+ + Deep
++ Normal
+
+--- enum-numbering-full ---
+// Test full numbering.
+#set enum(numbering: "1.a.", full: true)
++ First
+ + Nested
+
+--- enum-numbering-closure ---
+// Test numbering with closure.
+#enum(
+ start: 3,
+ spacing: 0.65em - 3pt,
+ tight: false,
+ numbering: n => text(
+ fill: (red, green, blue).at(calc.rem(n, 3)),
+ numbering("A", n),
+ ),
+ [Red], [Green], [Blue], [Red],
+)
+
+--- enum-numbering-closure-nested ---
+// Test numbering with closure and nested lists.
+#set enum(numbering: n => super[#n])
++ A
+ + B
++ C
+
+--- enum-numbering-closure-nested-complex ---
+// Test numbering with closure and nested lists.
+#set text(font: "New Computer Modern")
+#set enum(numbering: (..args) => math.mat(args.pos()), full: true)
++ A
+ + B
+ + C
+ + D
++ E
++ F
+
+--- enum-numbering-pattern-empty ---
+// Error: 22-24 invalid numbering pattern
+#set enum(numbering: "")
+
+--- enum-numbering-pattern-invalid ---
+// Error: 22-28 invalid numbering pattern
+#set enum(numbering: "(())")
+
+--- enum-number-align-unaffected ---
+// Alignment shouldn't affect number
+#set align(horizon)
+
++ ABCDEF\ GHIJKL\ MNOPQR
+ + INNER\ INNER\ INNER
++ BACK\ HERE
+
+--- enum-number-align-default ---
+// Enum number alignment should be 'end' by default
+1. a
+10. b
+100. c
+
+--- enum-number-align-specified ---
+#set enum(number-align: start)
+1. a
+8. b
+16. c
+
+--- enum-number-align-2d ---
+#set enum(number-align: center + horizon)
+1. #box(fill: teal, inset: 10pt )[a]
+8. #box(fill: teal, inset: 10pt )[b]
+16. #box(fill: teal,inset: 10pt )[c]
+
+--- enum-number-align-unfolded ---
+// Number align option should not be affected by the context.
+#set align(center)
+#set enum(number-align: start)
+
+4. c
+8. d
+16. e\ f
+ 2. f\ g
+ 32. g
+ 64. h
+
+--- enum-number-align-values ---
+// Test valid number align values (horizontal and vertical)
+#set enum(number-align: start)
+#set enum(number-align: end)
+#set enum(number-align: left)
+#set enum(number-align: center)
+#set enum(number-align: right)
+#set enum(number-align: top)
+#set enum(number-align: horizon)
+#set enum(number-align: bottom)
+
+--- issue-2530-enum-item-panic ---
+// Enum item (pre-emptive)
+#enum.item(none)[Hello]
+#enum.item(17)[Hello]
diff --git a/tests/suite/model/figure.typ b/tests/suite/model/figure.typ
new file mode 100644
index 00000000..6846760f
--- /dev/null
+++ b/tests/suite/model/figure.typ
@@ -0,0 +1,220 @@
+// Test figures.
+
+--- figure-basic ---
+#set page(width: 150pt)
+#set figure(numbering: "I")
+
+We can clearly see that @fig-cylinder and
+@tab-complex are relevant in this context.
+
+#figure(
+ table(columns: 2)[a][b],
+ caption: [The basic table.],
+) <tab-basic>
+
+#figure(
+ pad(y: -6pt, image("/assets/images/cylinder.svg", height: 2cm)),
+ caption: [The basic shapes.],
+ numbering: "I",
+) <fig-cylinder>
+
+#figure(
+ table(columns: 3)[a][b][c][d][e][f],
+ caption: [The complex table.],
+) <tab-complex>
+
+--- figure-table ---
+// Testing figures with tables.
+#figure(
+ table(
+ columns: 2,
+ [Second cylinder],
+ image("/assets/images/cylinder.svg"),
+ ),
+ caption: "A table containing images."
+) <fig-image-in-table>
+
+--- figure-theorem ---
+// Testing show rules with figures with a simple theorem display
+#show figure.where(kind: "theorem"): it => {
+ let name = none
+ if not it.caption == none {
+ name = [ #emph(it.caption.body)]
+ } else {
+ name = []
+ }
+
+ let title = none
+ if not it.numbering == none {
+ title = it.supplement
+ if not it.numbering == none {
+ title += " " + it.counter.display(it.numbering)
+ }
+ }
+ title = strong(title)
+ pad(
+ top: 0em, bottom: 0em,
+ block(
+ fill: green.lighten(90%),
+ stroke: 1pt + green,
+ inset: 10pt,
+ width: 100%,
+ radius: 5pt,
+ breakable: false,
+ [#title#name#h(0.1em):#h(0.2em)#it.body#v(0.5em)]
+ )
+ )
+}
+
+#set page(width: 150pt)
+#figure(
+ $a^2 + b^2 = c^2$,
+ supplement: "Theorem",
+ kind: "theorem",
+ caption: "Pythagoras' theorem.",
+ numbering: "1",
+) <fig-formula>
+
+#figure(
+ $a^2 + b^2 = c^2$,
+ supplement: "Theorem",
+ kind: "theorem",
+ caption: "Another Pythagoras' theorem.",
+ numbering: none,
+) <fig-formula>
+
+#figure(
+ ```rust
+ fn main() {
+ println!("Hello!");
+ }
+ ```,
+ caption: [Hello world in _rust_],
+)
+
+--- figure-breakable ---
+// Test breakable figures
+#set page(height: 6em)
+#show figure: set block(breakable: true)
+
+#figure(table[a][b][c][d][e], caption: [A table])
+
+--- figure-caption-separator ---
+// Test custom separator for figure caption
+#set figure.caption(separator: [ --- ])
+
+#figure(
+ table(columns: 2)[a][b],
+ caption: [The table with custom separator.],
+)
+
+--- figure-caption-show ---
+// Test figure.caption element
+#show figure.caption: emph
+
+#figure(
+ [Not italicized],
+ caption: [Italicized],
+)
+
+--- figure-caption-where-selector ---
+// Test figure.caption element for specific figure kinds
+#show figure.caption.where(kind: table): underline
+
+#figure(
+ [Not a table],
+ caption: [Not underlined],
+)
+
+#figure(
+ table[A table],
+ caption: [Underlined],
+)
+
+--- figure-and-caption-show ---
+// Test creating custom figure and custom caption
+
+#let gap = 0.7em
+#show figure.where(kind: "custom"): it => rect(inset: gap, {
+ align(center, it.body)
+ v(gap, weak: true)
+ line(length: 100%)
+ v(gap, weak: true)
+ align(center, it.caption)
+})
+
+#figure(
+ [A figure],
+ kind: "custom",
+ caption: [Hi],
+ supplement: [A],
+)
+
+#show figure.caption: it => emph[
+ #it.body
+ (#it.supplement
+ #context it.counter.display(it.numbering))
+]
+
+#figure(
+ [Another figure],
+ kind: "custom",
+ caption: [Hi],
+ supplement: [B],
+)
+
+--- figure-caption-position ---
+#set figure.caption(position: top)
+
+--- figure-caption-position-bad ---
+// Error: 31-38 expected `top` or `bottom`, found horizon
+#set figure.caption(position: horizon)
+
+--- figure-localization-fr ---
+// Test French
+#set text(lang: "fr")
+#figure(
+ circle(),
+ caption: [Un cercle.],
+)
+
+--- figure-localization-zh ---
+// Test Chinese
+#set text(lang: "zh")
+#figure(
+ rect(),
+ caption: [一个矩形],
+)
+
+--- figure-localization-ru ---
+// Test Russian
+#set text(lang: "ru")
+
+#figure(
+ polygon.regular(size: 1cm, vertices: 8),
+ caption: [Пятиугольник],
+)
+
+--- figure-localization-gr ---
+// Test Greek
+#set text(lang: "gr")
+#figure(
+ circle(),
+ caption: [Ένας κύκλος.],
+)
+
+--- issue-2165-figure-caption-panic ---
+#figure.caption[]
+
+--- issue-2328-figure-entry-panic ---
+// Error: 4-43 footnote entry must have a location
+// Hint: 4-43 try using a query or a show rule to customize the footnote instead
+HI#footnote.entry(clearance: 2.5em)[There]
+
+--- issue-2530-figure-caption-panic ---
+#figure(caption: [test])[].caption
+
+--- issue-3586-figure-caption-separator ---
+// Test that figure caption separator is synthesized correctly.
+#show figure.caption: c => test(c.separator, [#": "])
+#figure(table[], caption: [This is a test caption])
diff --git a/tests/suite/model/footnote.typ b/tests/suite/model/footnote.typ
new file mode 100644
index 00000000..c41db577
--- /dev/null
+++ b/tests/suite/model/footnote.typ
@@ -0,0 +1,182 @@
+// Test footnotes.
+
+--- footnote-basic ---
+#footnote[Hi]
+
+--- footnote-space-collapsing ---
+// Test space collapsing before footnote.
+A#footnote[A] \
+A #footnote[A]
+
+--- footnote-nested ---
+// Test nested footnotes.
+First \
+Second #footnote[A, #footnote[B, #footnote[C]]] \
+Third #footnote[D, #footnote[E]] \
+Fourth
+
+--- footnote-nested-same-frame ---
+// Currently, numbers a bit out of order if a nested footnote ends up in the
+// same frame as another one. :(
+#footnote[A, #footnote[B]], #footnote[C]
+
+--- footnote-entry ---
+// Test customization.
+#show footnote: set text(red)
+#show footnote.entry: set text(8pt, style: "italic")
+#set footnote.entry(
+ indent: 0pt,
+ gap: 0.6em,
+ clearance: 0.3em,
+ separator: repeat[.],
+)
+
+Beautiful footnotes. #footnote[Wonderful, aren't they?]
+
+--- footnote-break-across-pages ---
+// LARGE
+#set page(height: 200pt)
+
+#lorem(5)
+#footnote[ // 1
+ A simple footnote.
+ #footnote[Well, not that simple ...] // 2
+]
+#lorem(15)
+#footnote[Another footnote: #lorem(30)] // 3
+#lorem(15)
+#footnote[My fourth footnote: #lorem(50)] // 4
+#lorem(15)
+#footnote[And a final footnote.] // 5
+
+--- footnote-in-columns ---
+// Test footnotes in columns, even those that are not enabled via `set page`.
+#set page(height: 120pt)
+#align(center, strong[Title])
+#show: columns.with(2)
+#lorem(3) #footnote(lorem(6))
+Hello there #footnote(lorem(2))
+
+--- footnote-in-caption ---
+// Test footnote in caption.
+Read the docs #footnote[https://typst.app/docs]!
+#figure(
+ image("/assets/images/graph.png", width: 70%),
+ caption: [
+ A graph #footnote[A _graph_ is a structure with nodes and edges.]
+ ]
+)
+More #footnote[just for ...] footnotes #footnote[... testing. :)]
+
+--- footnote-duplicate ---
+// Test duplicate footnotes.
+#let lang = footnote[Languages.]
+#let nums = footnote[Numbers.]
+
+/ "Hello": A word #lang
+/ "123": A number #nums
+
+- "Hello" #lang
+- "123" #nums
+
++ "Hello" #lang
++ "123" #nums
+
+#table(
+ columns: 2,
+ [Hello], [A word #lang],
+ [123], [A number #nums],
+)
+
+--- footnote-invariant ---
+// Ensure that a footnote and the first line of its entry
+// always end up on the same page.
+#set page(height: 120pt)
+
+#lorem(13)
+
+There #footnote(lorem(20))
+
+--- footnote-ref ---
+// Test references to footnotes.
+A footnote #footnote[Hi]<fn> \
+A reference to it @fn
+
+--- footnote-ref-multiple ---
+// Multiple footnotes are refs
+First #footnote[A]<fn1> \
+Second #footnote[B]<fn2> \
+First ref @fn1 \
+Third #footnote[C] \
+Fourth #footnote[D]<fn4> \
+Fourth ref @fn4 \
+Second ref @fn2 \
+Second ref again @fn2
+
+--- footnote-ref-forward ---
+// Forward reference
+Usage @fn \
+Definition #footnote[Hi]<fn>
+
+--- footnote-ref-in-footnote ---
+// Footnote ref in footnote
+#footnote[Reference to next @fn]
+#footnote[Reference to myself @fn]<fn>
+#footnote[Reference to previous @fn]
+
+--- footnote-styling ---
+// Styling
+#show footnote: text.with(fill: red)
+Real #footnote[...]<fn> \
+Ref @fn
+
+--- footnote-ref-call ---
+// Footnote call with label
+#footnote(<fn>)
+#footnote[Hi]<fn>
+#ref(<fn>)
+#footnote(<fn>)
+
+--- footnote-in-table ---
+// Test footnotes in tables. When the table spans multiple pages, the footnotes
+// will all be after the table, but it shouldn't create any empty pages.
+#set page(height: 100pt)
+
+= Tables
+#table(
+ columns: 2,
+ [Hello footnote #footnote[This is a footnote.]],
+ [This is more text],
+ [This cell
+ #footnote[This footnote is not on the same page]
+ breaks over multiple pages.],
+ image("/assets/images/tiger.jpg"),
+)
+
+#table(
+ columns: 3,
+ ..range(1, 10)
+ .map(numbering.with("a"))
+ .map(v => upper(v) + footnote(v))
+)
+
+--- issue-multiple-footnote-in-one-line ---
+// Test that the logic that keeps footnote entry together with
+// their markers also works for multiple footnotes in a single
+// line or frame (here, there are two lines, but they are one
+// unit due to orphan prevention).
+#set page(height: 100pt)
+#v(40pt)
+A #footnote[a] \
+B #footnote[b]
+
+--- issue-1433-footnote-in-list ---
+// Test that footnotes in lists do not produce extraneous page breaks. The list
+// layout itself does not currently react to the footnotes layout, weakening the
+// "footnote and its entry are on the same page" invariant somewhat, but at
+// least there shouldn't be extra page breaks.
+#set page(height: 100pt)
+#block(height: 50pt, width: 100%, fill: aqua)
+
+- #footnote[1]
+- #footnote[2]
diff --git a/tests/suite/model/heading.typ b/tests/suite/model/heading.typ
new file mode 100644
index 00000000..5d50eeee
--- /dev/null
+++ b/tests/suite/model/heading.typ
@@ -0,0 +1,80 @@
+// Test headings.
+
+--- heading-basic ---
+// Different number of equals signs.
+
+= Level 1
+== Level 2
+=== Level 3
+
+// After three, it stops shrinking.
+=========== Level 11
+
+--- heading-syntax-at-start ---
+// Heading vs. no heading.
+
+// Parsed as headings if at start of the context.
+/**/ = Level 1
+#[== Level 2]
+#box[=== Level 3]
+
+// Not at the start of the context.
+No = heading
+
+// Escaped.
+\= No heading
+
+--- heading-block ---
+// Blocks can continue the heading.
+
+= #[This
+is
+multiline.
+]
+
+= This
+ is not.
+
+--- heading-show-where ---
+// Test styling.
+#show heading.where(level: 5): it => block(
+ text(font: "Roboto", fill: eastern, it.body + [!])
+)
+
+= Heading
+===== Heading 🌍
+#heading(level: 5)[Heading]
+
+--- heading-offset ---
+// Test setting the starting offset.
+#set heading(numbering: "1.1")
+#show heading.where(level: 2): set text(blue)
+= Level 1
+
+#heading(depth: 1)[We're twins]
+#heading(level: 1)[We're twins]
+
+== Real level 2
+
+#set heading(offset: 1)
+= Fake level 2
+== Fake level 3
+
+--- heading-offset-and-level ---
+// Passing level directly still overrides all other set values
+#set heading(numbering: "1.1", offset: 1)
+#heading(level: 1)[Still level 1]
+
+--- heading-syntax-edge-cases ---
+// Edge cases.
+#set heading(numbering: "1.")
+=
+Not in heading
+=Nope
+
+--- heading-numbering-hint ---
+= Heading <intro>
+
+// Error: 1:20-1:26 cannot reference heading without numbering
+// Hint: 1:20-1:26 you can enable heading numbering with `#set heading(numbering: "1.")`
+Can not be used as @intro
diff --git a/tests/suite/model/link.typ b/tests/suite/model/link.typ
new file mode 100644
index 00000000..27afd53c
--- /dev/null
+++ b/tests/suite/model/link.typ
@@ -0,0 +1,77 @@
+// Test hyperlinking.
+
+--- link-basic ---
+// Link syntax.
+https://example.com/
+
+// Link with body.
+#link("https://typst.org/")[Some text text text]
+
+// With line break.
+This link appears #link("https://google.com/")[in the middle of] a paragraph.
+
+// Certain prefixes are trimmed when using the `link` function.
+Contact #link("mailto:hi@typst.app") or
+call #link("tel:123") for more information.
+
+--- link-trailing-period ---
+// Test that the period is trimmed.
+#show link: underline
+https://a.b.?q=%10#. \
+Wahttp://link \
+Nohttps:\//link \
+Nohttp\://comment
+
+--- link-bracket-balanced ---
+// Verify that brackets are included in links.
+https://[::1]:8080/ \
+https://example.com/(paren) \
+https://example.com/#(((nested))) \
+
+--- link-bracket-unbalanced-closing ---
+// Check that unbalanced brackets are not included in links.
+#[https://example.com/] \
+https://example.com/)
+
+--- link-bracket-unbalanced-opening ---
+// Verify that opening brackets without closing brackets throw an error.
+// Error: 1-22 automatic links cannot contain unbalanced brackets, use the `link` function instead
+https://exam(ple.com/
+
+--- link-show ---
+// Styled with underline and color.
+#show link: it => underline(text(fill: rgb("283663"), it))
+You could also make the
+#link("https://html5zombo.com/")[link look way more typical.]
+
+--- link-transformed ---
+// Transformed link.
+#set page(height: 60pt)
+#let mylink = link("https://typst.org/")[LINK]
+My cool #box(move(dx: 0.7cm, dy: 0.7cm, rotate(10deg, scale(200%, mylink))))
+
+--- link-on-block ---
+// Link containing a block.
+#link("https://example.com/", block[
+ My cool rhino
+ #box(move(dx: 10pt, image("/assets/images/rhino.png", width: 1cm)))
+])
+
+--- link-to-page ---
+// Link to page one.
+#link((page: 1, x: 10pt, y: 20pt))[Back to the start]
+
+--- link-to-label ---
+// Test link to label.
+Text <hey>
+#link(<hey>)[Go to text.]
+
+--- link-to-label-missing ---
+// Error: 2-20 label `<hey>` does not exist in the document
+#link(<hey>)[Nope.]
+
+--- link-to-label-duplicate ---
+Text <hey>
+Text <hey>
+// Error: 2-20 label `<hey>` occurs multiple times in the document
+#link(<hey>)[Nope.]
diff --git a/tests/suite/model/list.typ b/tests/suite/model/list.typ
new file mode 100644
index 00000000..e37fa65d
--- /dev/null
+++ b/tests/suite/model/list.typ
@@ -0,0 +1,147 @@
+// Test bullet lists.
+
+--- list-basic ---
+_Shopping list_
+#list[Apples][Potatoes][Juice]
+
+--- list-nested ---
+- First level.
+
+ - Second level.
+ There are multiple paragraphs.
+
+ - Third level.
+
+ Still the same bullet point.
+
+ - Still level 2.
+
+- At the top.
+
+--- list-content-block ---
+- Level 1
+ - Level #[
+2 through content block
+]
+
+--- list-top-level-indent ---
+ - Top-level indent
+- is fine.
+
+--- list-indent-specifics ---
+ - A
+ - B
+ - C
+- D
+
+--- list-tabs ---
+// This works because tabs are used consistently.
+ - A with 1 tab
+ - B with 2 tabs
+
+--- list-mixed-tabs-and-spaces ---
+// This doesn't work because of mixed tabs and spaces.
+ - A with 2 spaces
+ - B with 2 tabs
+
+--- list-syntax-edge-cases ---
+// Edge cases.
+-
+Not in list
+-Nope
+
+--- list-marker-align-unaffected ---
+// Alignment shouldn't affect marker
+#set align(horizon)
+
+- ABCDEF\ GHIJKL\ MNOPQR
+
+--- list-marker-dash ---
+// Test en-dash.
+#set list(marker: [--])
+- A
+- B
+
+--- list-marker-cycle ---
+// Test that items are cycled.
+#set list(marker: ([--], [•]))
+- A
+ - B
+ - C
+
+--- list-marker-closure ---
+// Test function.
+#set list(marker: n => if n == 1 [--] else [•])
+- A
+- B
+ - C
+ - D
+ - E
+- F
+
+--- list-marker-bare-hyphen ---
+// Test that bare hyphen doesn't lead to cycles and crashes.
+#set list(marker: [-])
+- Bare hyphen is
+- a bad marker
+
+--- list-marker-array-empty ---
+// Error: 19-21 array must contain at least one marker
+#set list(marker: ())
+
+--- list-attached ---
+// Test basic attached list.
+Attached to:
+- the bottom
+- of the paragraph
+
+Next paragraph.
+
+--- list-attached-above-spacing ---
+// Test that attached list isn't affected by block spacing.
+#show list: set block(above: 100pt)
+Hello
+- A
+World
+- B
+
+--- list-non-attached-followed-by-attached ---
+// Test non-attached list followed by attached list,
+// separated by only word.
+Hello
+
+- A
+
+World
+- B
+
+--- list-tight-non-attached-tight ---
+// Test non-attached tight list.
+#set block(spacing: 15pt)
+Hello
+- A
+World
+
+- B
+- C
+
+More.
+
+--- list-wide-cannot-attach ---
+// Test that wide lists cannot be ...
+#set block(spacing: 15pt)
+Hello
+- A
+
+- B
+World
+
+--- list-wide-really-cannot-attach ---
+// ... even if forced to.
+Hello
+#list(tight: false)[A][B]
+World
+
+--- issue-2530-list-item-panic ---
+// List item (pre-emptive)
+#list.item[Hello]
diff --git a/tests/suite/model/numbering.typ b/tests/suite/model/numbering.typ
new file mode 100644
index 00000000..c2de1e05
--- /dev/null
+++ b/tests/suite/model/numbering.typ
@@ -0,0 +1,103 @@
+// Test integrated numbering patterns.
+
+--- numbering-symbol-and-roman ---
+#for i in range(0, 9) {
+ numbering("*", i)
+ [ and ]
+ numbering("I.a", i, i)
+ [ for #i \ ]
+}
+
+--- numbering-latin ---
+#for i in range(0, 4) {
+ numbering("A", i)
+ [ for #i \ ]
+}
+... \
+#for i in range(26, 30) {
+ numbering("A", i)
+ [ for #i \ ]
+}
+... \
+#for i in range(702, 706) {
+ numbering("A", i)
+ [ for #i \ ]
+}
+
+--- numbering-hebrew ---
+#set text(lang: "he")
+#for i in range(9, 21, step: 2) {
+ numbering("א.", i)
+ [ עבור #i \ ]
+}
+
+--- numbering-chinese ---
+#set text(lang: "zh", font: ("Linux Libertine", "Noto Serif CJK SC"))
+#for i in range(9, 21, step: 2){
+ numbering("一", i)
+ [ and ]
+ numbering("壹", i)
+ [ for #i \ ]
+}
+
+--- numbering-japanese-iroha ---
+#set text(lang: "ja", font: ("Linux Libertine", "Noto Serif CJK JP"))
+#for i in range(0, 4) {
+ numbering("イ", i)
+ [ (or ]
+ numbering("い", i)
+ [) for #i \ ]
+}
+... \
+#for i in range(47, 51) {
+ numbering("イ", i)
+ [ (or ]
+ numbering("い", i)
+ [) for #i \ ]
+}
+... \
+#for i in range(2256, 2260) {
+ numbering("イ", i)
+ [ for #i \ ]
+}
+
+--- numbering-korean ---
+#set text(lang: "ko", font: ("Linux Libertine", "Noto Serif CJK KR"))
+#for i in range(0, 4) {
+ numbering("가", i)
+ [ (or ]
+ numbering("ㄱ", i)
+ [) for #i \ ]
+}
+... \
+#for i in range(47, 51) {
+ numbering("가", i)
+ [ (or ]
+ numbering("ㄱ", i)
+ [) for #i \ ]
+}
+... \
+#for i in range(2256, 2260) {
+ numbering("ㄱ", i)
+ [ for #i \ ]
+}
+
+--- numbering-japanese-aiueo ---
+#set text(lang: "jp", font: ("Linux Libertine", "Noto Serif CJK JP"))
+#for i in range(0, 9) {
+ numbering("あ", i)
+ [ and ]
+ numbering("I.あ", i, i)
+ [ for #i \ ]
+}
+
+#for i in range(0, 9) {
+ numbering("ア", i)
+ [ and ]
+ numbering("I.ア", i, i)
+ [ for #i \ ]
+}
+
+--- numbering-negative ---
+// Error: 17-19 number must be at least zero
+#numbering("1", -1)
diff --git a/tests/suite/model/outline.typ b/tests/suite/model/outline.typ
new file mode 100644
index 00000000..d8fc1a43
--- /dev/null
+++ b/tests/suite/model/outline.typ
@@ -0,0 +1,176 @@
+--- outline ---
+// LARGE
+#set page("a7", margin: 20pt, numbering: "1")
+#set heading(numbering: "(1/a)")
+#show heading.where(level: 1): set text(12pt)
+#show heading.where(level: 2): set text(10pt)
+#set math.equation(numbering: "1")
+
+#outline()
+#outline(title: [Figures], target: figure)
+#outline(title: [Equations], target: math.equation)
+
+= Introduction
+#lorem(12)
+
+= Analysis
+#lorem(10)
+
+#[
+ #set heading(outlined: false)
+ == Methodology
+ #lorem(6)
+]
+
+== Math
+$x$ is a very useful constant. See it in action:
+$ x = x $
+
+== Interesting figures
+#figure(rect[CENSORED], kind: image, caption: [A picture showing a programmer at work.])
+#figure(table[1x1], caption: [A very small table.])
+
+== Programming
+```rust
+fn main() {
+ panic!("in the disco");
+}
+```
+
+==== Deep Stuff
+Ok ...
+
+// Ensure 'bookmarked' option doesn't affect the outline
+#set heading(numbering: "(I)", bookmarked: false)
+
+= #text(blue)[Sum]mary
+#lorem(10)
+
+--- outline-indent-numbering ---
+// LARGE
+// With heading numbering
+#set page(width: 200pt)
+#set heading(numbering: "1.a.")
+#show heading: none
+#set outline(fill: none)
+
+#context test(outline.indent, none)
+#outline(indent: false)
+#outline(indent: true)
+#outline(indent: none)
+#outline(indent: auto)
+#outline(indent: 2em)
+#outline(indent: n => ([-], [], [==], [====]).at(n))
+
+= About ACME Corp.
+== History
+== Products
+=== Categories
+==== General
+
+--- outline-indent-no-numbering ---
+// Without heading numbering
+#set page(width: 200pt)
+#show heading: none
+#set outline(fill: none)
+
+#outline(indent: false)
+#outline(indent: true)
+#outline(indent: none)
+#outline(indent: auto)
+#outline(indent: n => 2em * n)
+
+= About
+== History
+
+--- outline-indent-bad-type ---
+// Error: 2-35 expected relative length or content, found dictionary
+#outline(indent: n => (a: "dict"))
+
+= Heading
+
+--- outline-first-line-indent ---
+#set par(first-line-indent: 1.5em)
+#set heading(numbering: "1.1.a.")
+#show outline.entry.where(level: 1): it => {
+ v(0.5em, weak: true)
+ strong(it)
+}
+
+#outline()
+
+= Introduction
+= Background
+== History
+== State of the Art
+= Analysis
+== Setup
+
+--- outline-entry ---
+#set page(width: 150pt)
+#set heading(numbering: "1.")
+
+#show outline.entry.where(
+ level: 1
+): it => {
+ v(12pt, weak: true)
+ strong(it)
+}
+
+#outline(indent: auto)
+
+#set text(8pt)
+#show heading: set block(spacing: 0.65em)
+
+= Introduction
+= Background
+== History
+== State of the Art
+= Analysis
+== Setup
+
+--- outline-entry-complex ---
+#set page(width: 150pt, numbering: "I", margin: (bottom: 20pt))
+#set heading(numbering: "1.")
+#show outline.entry.where(level: 1): it => [
+ #let loc = it.element.location()
+ #let num = numbering(loc.page-numbering(), ..counter(page).at(loc))
+ #emph(link(loc, it.body))
+ #text(luma(100), box(width: 1fr, repeat[#it.fill.body;·]))
+ #link(loc, num)
+]
+
+#counter(page).update(3)
+#outline(indent: auto, fill: repeat[--])
+
+#set text(8pt)
+#show heading: set block(spacing: 0.65em)
+
+= Top heading
+== Not top heading
+=== Lower heading
+=== Lower too
+== Also not top
+
+#pagebreak()
+#set page(numbering: "1")
+
+= Another top heading
+== Middle heading
+=== Lower heading
+
+--- outline-bad-element ---
+// Error: 2-27 cannot outline metadata
+#outline(target: metadata)
+#metadata("hello")
+
+--- issue-2530-outline-entry-panic-text ---
+// Outline entry (pre-emptive)
+// Error: 2-48 cannot outline text
+#outline.entry(1, [Hello], [World!], none, [1])
+
+--- issue-2530-outline-entry-panic-heading ---
+// Outline entry (pre-emptive, improved error)
+// Error: 2-55 heading must have a location
+// Hint: 2-55 try using a query or a show rule to customize the outline.entry instead
+#outline.entry(1, heading[Hello], [World!], none, [1])
diff --git a/tests/suite/model/par.typ b/tests/suite/model/par.typ
new file mode 100644
index 00000000..65779f6a
--- /dev/null
+++ b/tests/suite/model/par.typ
@@ -0,0 +1,78 @@
+// Test configuring paragraph properties.
+
+--- par-basic ---
+#set page(width: 250pt, height: 120pt)
+
+But, soft! what light through yonder window breaks? It is the east, and Juliet
+is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and
+pale with grief, That thou her maid art far more fair than she: Be not her maid,
+since she is envious; Her vestal livery is but sick and green And none but fools
+do wear it; cast it off. It is my lady, O, it is my love! O, that she knew she
+were! She speaks yet she says nothing: what of that? Her eye discourses; I will
+answer it.
+
+I am too bold, 'tis not to me she speaks: Two of the fairest stars in all the
+heaven, Having some business, do entreat her eyes To twinkle in their spheres
+till they return. What if her eyes were there, they in her head? The brightness
+of her cheek would shame those stars, As daylight doth a lamp; her eyes in
+heaven Would through the airy region stream so bright That birds would sing and
+think it were not night. See, how she leans her cheek upon her hand! O, that I
+were a glove upon that hand, That I might touch that cheek!
+
+--- par-leading-and-block-spacing ---
+// Test changing leading and spacing.
+#set block(spacing: 1em)
+#set par(leading: 2pt)
+But, soft! what light through yonder window breaks?
+
+It is the east, and Juliet is the sun.
+
+--- par-first-line-indent ---
+#set par(first-line-indent: 12pt, leading: 5pt)
+#set block(spacing: 5pt)
+#show heading: set text(size: 10pt)
+
+The first paragraph has no indent.
+
+But the second one does.
+
+#box(image("/assets/images/tiger.jpg", height: 6pt))
+starts a paragraph, also with indent.
+
+#align(center, image("/assets/images/rhino.png", width: 1cm))
+
+= Headings
+- And lists.
+- Have no indent.
+
+ Except if you have another paragraph in them.
+
+#set text(8pt, lang: "ar", font: ("Noto Sans Arabic", "Linux Libertine"))
+#set par(leading: 8pt)
+
+= Arabic
+دع النص يمطر عليك
+
+ثم يصبح النص رطبًا وقابل للطرق ويبدو المستند رائعًا.
+
+--- par-spacing-and-first-line-indent ---
+// This is madness.
+#set par(first-line-indent: 12pt)
+Why would anybody ever ...
+
+... want spacing and indent?
+
+--- par-hanging-indent ---
+// Test hanging indent.
+#set par(hanging-indent: 15pt, justify: true)
+#lorem(10)
+
+--- par-hanging-indent-manual-linebreak ---
+#set par(hanging-indent: 1em)
+Welcome \ here. Does this work well?
+
+--- par-hanging-indent-rtl ---
+#set par(hanging-indent: 2em)
+#set text(dir: rtl)
+لآن وقد أظلم الليل وبدأت النجوم
+تنضخ وجه الطبيعة التي أعْيَتْ من طول ما انبعثت في النهار
diff --git a/tests/suite/model/quote.typ b/tests/suite/model/quote.typ
new file mode 100644
index 00000000..446784ee
--- /dev/null
+++ b/tests/suite/model/quote.typ
@@ -0,0 +1,86 @@
+// Test the quote element.
+
+--- quote-dir-author-pos ---
+// Text direction affects author positioning
+And I quote: #quote(attribution: [René Descartes])[cogito, ergo sum].
+
+#set text(lang: "ar")
+#quote(attribution: [عالم])[مرحبًا]
+
+--- quote-dir-align ---
+// Text direction affects block alignment
+#set quote(block: true)
+#quote(attribution: [René Descartes])[cogito, ergo sum]
+
+#set text(lang: "ar")
+#quote(attribution: [عالم])[مرحبًا]
+
+--- quote-block-spacing ---
+// Spacing with other blocks
+#set quote(block: true)
+#set text(8pt)
+
+#lorem(10)
+#quote(lorem(10))
+#lorem(10)
+
+--- quote-inline ---
+// Inline citation
+#set text(8pt)
+#quote(attribution: <tolkien54>)[In a hole in the ground there lived a hobbit.]
+
+#set text(0pt)
+#bibliography("/assets/bib/works.bib")
+
+--- quote-cite-format-label-or-numeric ---
+// Citation-format: label or numeric
+#set text(8pt)
+#set quote(block: true)
+#quote(attribution: <tolkien54>)[In a hole in the ground there lived a hobbit.]
+
+#set text(0pt)
+#bibliography("/assets/bib/works.bib", style: "ieee")
+
+--- quote-cite-format-note ---
+// Citation-format: note
+#set text(8pt)
+#set quote(block: true)
+#quote(attribution: <tolkien54>)[In a hole in the ground there lived a hobbit.]
+
+#set text(0pt)
+#bibliography("/assets/bib/works.bib", style: "chicago-notes")
+
+--- quote-cite-format-author-date ---
+// Citation-format: author-date or author
+#set text(8pt)
+#set quote(block: true)
+#quote(attribution: <tolkien54>)[In a hole in the ground there lived a hobbit.]
+
+#set text(0pt)
+#bibliography("/assets/bib/works.bib", style: "apa")
+
+--- quote-nesting ---
+// Test quote selection.
+#set page(width: auto)
+#set text(lang: "en")
+=== EN
+#quote[An apostroph'] \
+#quote[A #quote[nested] quote] \
+#quote[A #quote[very #quote[nested]] quote]
+
+#set text(lang: "de")
+=== DE
+#quote[Satz mit Apostroph'] \
+#quote[Satz mit #quote[Zitat]] \
+#quote[A #quote[very #quote[nested]] quote]
+
+#set smartquote(alternative: true)
+=== DE Alternative
+#quote[Satz mit Apostroph'] \
+#quote[Satz mit #quote[Zitat]] \
+#quote[A #quote[very #quote[nested]] quote]
+
+--- quote-nesting-custom ---
+// With custom quotes.
+#set smartquote(quotes: (single: ("<", ">"), double: ("(", ")")))
+#quote[A #quote[nested] quote]
diff --git a/tests/suite/model/ref.typ b/tests/suite/model/ref.typ
new file mode 100644
index 00000000..200f40aa
--- /dev/null
+++ b/tests/suite/model/ref.typ
@@ -0,0 +1,56 @@
+// Test references.
+
+--- ref-basic ---
+#set heading(numbering: "1.")
+
+= Introduction <intro>
+See @setup.
+
+== Setup <setup>
+As seen in @intro, we proceed.
+
+--- ref-label-missing ---
+// Error: 1-5 label `<foo>` does not exist in the document
+@foo
+
+--- ref-label-duplicate ---
+= First <foo>
+= Second <foo>
+
+// Error: 1-5 label `<foo>` occurs multiple times in the document
+@foo
+
+--- ref-supplements ---
+#set heading(numbering: "1.", supplement: [Chapter])
+#set math.equation(numbering: "(1)", supplement: [Eq.])
+
+= Intro
+#figure(
+ image("/assets/images/cylinder.svg", height: 1cm),
+ caption: [A cylinder.],
+ supplement: "Fig",
+) <fig1>
+
+#figure(
+ image("/assets/images/tiger.jpg", height: 1cm),
+ caption: [A tiger.],
+ supplement: "Tig",
+) <fig2>
+
+$ A = 1 $ <eq1>
+
+#set math.equation(supplement: none)
+$ A = 1 $ <eq2>
+
+@fig1, @fig2, @eq1, (@eq2)
+
+#set ref(supplement: none)
+@fig1, @fig2, @eq1, @eq2
+
+--- ref-ambigious ---
+// Test ambiguous reference.
+= Introduction <arrgh>
+
+// Error: 1-7 label occurs in the document and its bibliography
+@arrgh
+#bibliography("/assets/bib/works.bib")
diff --git a/tests/suite/model/terms.typ b/tests/suite/model/terms.typ
new file mode 100644
index 00000000..6a08b923
--- /dev/null
+++ b/tests/suite/model/terms.typ
@@ -0,0 +1,77 @@
+// Test term list.
+
+--- terms-constructor ---
+// Test with constructor.
+#terms(
+ ([One], [First]),
+ ([Two], [Second]),
+)
+
+--- terms-built-in-loop ---
+// Test joining.
+#for word in lorem(4).split().map(s => s.trim(".")) [
+ / #word: Latin stuff.
+]
+
+--- terms-multiline ---
+// Test multiline.
+#set text(8pt)
+
+/ Fruit: A tasty, edible thing.
+/ Veggie:
+ An important energy source
+ for vegetarians.
+
+ And healthy!
+
+--- terms-style-change-interrupted ---
+// Test style change.
+#set text(8pt)
+/ First list: #lorem(6)
+
+#set terms(hanging-indent: 30pt)
+/ Second list: #lorem(5)
+
+--- terms-rtl ---
+// Test RTL.
+#set text(8pt, dir: rtl)
+
+/ פרי: דבר טעים, אכיל. ומקור אנרגיה חשוב לצמחונים.
+
+--- terms-grid ---
+// Test grid like show rule.
+#show terms: it => table(
+ columns: 2,
+ inset: 3pt,
+ ..it.children.map(v => (emph(v.term), v.description)).flatten(),
+)
+
+/ A: One letter
+/ BB: Two letters
+/ CCC: Three letters
+
+--- terms-syntax-edge-cases ---
+/ Term:
+Not in list
+/Nope
+
+--- terms-missing-colon ---
+// Error: 8 expected colon
+/ Hello
+
+--- issue-1050-terms-indent ---
+#set page(width: 200pt)
+#set par(first-line-indent: 0.5cm)
+
+- #lorem(10)
+- #lorem(10)
+
++ #lorem(10)
++ #lorem(10)
+
+/ Term 1: #lorem(10)
+/ Term 2: #lorem(10)
+
+--- issue-2530-term-item-panic ---
+// Term item (pre-emptive)
+#terms.item[Hello][World!]