summaryrefslogtreecommitdiff
path: root/tests/suite/introspection
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/introspection
parent72dd79210602ecc799726fc096b078afbb47f299 (diff)
Better test runner (#3922)
Diffstat (limited to 'tests/suite/introspection')
-rw-r--r--tests/suite/introspection/counter.typ78
-rw-r--r--tests/suite/introspection/here.typ3
-rw-r--r--tests/suite/introspection/locate.typ32
-rw-r--r--tests/suite/introspection/query.typ267
-rw-r--r--tests/suite/introspection/state.typ63
5 files changed, 443 insertions, 0 deletions
diff --git a/tests/suite/introspection/counter.typ b/tests/suite/introspection/counter.typ
new file mode 100644
index 00000000..8a5315f9
--- /dev/null
+++ b/tests/suite/introspection/counter.typ
@@ -0,0 +1,78 @@
+// Test counters.
+
+--- counter-basic-1 ---
+// Count with string key.
+#let mine = counter("mine!")
+
+Final: #context mine.final().at(0) \
+#mine.step()
+First: #context mine.display() \
+#mine.update(7)
+#context mine.display("1 of 1", both: true) \
+#mine.step()
+#mine.step()
+Second: #context mine.display("I")
+#mine.update(n => n * 2)
+#mine.step()
+
+--- counter-basic-2 ---
+// Test `counter`.
+#let c = counter("heading")
+#c.update(2)
+#c.update(n => n + 2)
+#context test(c.get(), (4,))
+#c.update(n => n - 3)
+#context test(c.at(here()), (1,))
+
+--- counter-label ---
+// Count labels.
+#let label = <heya>
+#let count = context counter(label).display()
+#let elem(it) = [#box(it) #label]
+
+#elem[hey, there!] #count \
+#elem[more here!] #count
+
+--- counter-heading ---
+// Count headings.
+#set heading(numbering: "1.a.")
+#show heading: set text(10pt)
+#counter(heading).step()
+
+= Alpha
+In #context counter(heading).display()
+== Beta
+
+#set heading(numbering: none)
+= Gamma
+#heading(numbering: "I.")[Delta]
+
+At Beta, it was #context {
+ let it = query(heading).find(it => it.body == [Beta])
+ numbering(it.numbering, ..counter(heading).at(it.location()))
+}
+
+--- counter-page ---
+#set page(height: 50pt, margin: (bottom: 20pt, rest: 10pt))
+#lorem(12)
+#set page(numbering: "(i)")
+#lorem(6)
+#pagebreak()
+#set page(numbering: "1 / 1")
+#counter(page).update(1)
+#lorem(20)
+
+--- counter-figure ---
+// Count figures.
+#figure(numbering: "A", caption: [Four 'A's], kind: image, supplement: "Figure")[_AAAA!_]
+#figure(numbering: none, caption: [Four 'B's], kind: image, supplement: "Figure")[_BBBB!_]
+#figure(caption: [Four 'C's], kind: image, supplement: "Figure")[_CCCC!_]
+#counter(figure.where(kind: image)).update(n => n + 3)
+#figure(caption: [Four 'D's], kind: image, supplement: "Figure")[_DDDD!_]
+
+--- counter-at-no-context ---
+// Test `counter.at` outside of context.
+// Error: 2-28 can only be used when context is known
+// Hint: 2-28 try wrapping this in a `context` expression
+// Hint: 2-28 the `context` expression should wrap everything that depends on this function
+#counter("key").at(<label>)
diff --git a/tests/suite/introspection/here.typ b/tests/suite/introspection/here.typ
new file mode 100644
index 00000000..18fff439
--- /dev/null
+++ b/tests/suite/introspection/here.typ
@@ -0,0 +1,3 @@
+--- here-position ---
+// Test `context` + `here`.
+#context test(here().position().y, 10pt)
diff --git a/tests/suite/introspection/locate.typ b/tests/suite/introspection/locate.typ
new file mode 100644
index 00000000..981f8c46
--- /dev/null
+++ b/tests/suite/introspection/locate.typ
@@ -0,0 +1,32 @@
+--- locate-position ---
+// Test `locate`.
+#v(10pt)
+= Introduction <intro>
+#context test(locate(<intro>).position().y, 20pt)
+
+--- locate-missing-label ---
+// Error: 10-25 label `<intro>` does not exist in the document
+#context locate(<intro>)
+
+--- locate-duplicate-label ---
+= Introduction <intro>
+= Introduction <intro>
+
+// Error: 10-25 label `<intro>` occurs multiple times in the document
+#context locate(<intro>)
+
+--- locate-element-selector ---
+#v(10pt)
+= Introduction <intro>
+#context test(locate(heading).position().y, 20pt)
+
+--- locate-element-selector-no-match ---
+// Error: 10-25 selector does not match any element
+#context locate(heading)
+
+--- locate-element-selector-multiple-matches ---
+= Introduction <intro>
+= Introduction <intro>
+
+// Error: 10-25 selector matches multiple elements
+#context locate(heading)
diff --git a/tests/suite/introspection/query.typ b/tests/suite/introspection/query.typ
new file mode 100644
index 00000000..3a4b4fbf
--- /dev/null
+++ b/tests/suite/introspection/query.typ
@@ -0,0 +1,267 @@
+// Test creating a header with the query function.
+
+--- query-here ---
+// Test that `here()` yields the context element's location.
+#context test(query(here()).first().func(), (context none).func())
+
+--- query-running-header ---
+#set page(
+ paper: "a8",
+ margin: (y: 1cm, x: 0.5cm),
+ header: context {
+ smallcaps[Typst Academy]
+ h(1fr)
+ let after = query(selector(heading).after(here()))
+ let before = query(selector(heading).before(here()))
+ let elem = if before.len() != 0 {
+ before.last()
+ } else if after.len() != 0 {
+ after.first()
+ }
+ emph(elem.body)
+ }
+)
+
+#outline()
+
+= Introduction
+#v(1cm)
+
+= Background
+#v(2cm)
+
+= Approach
+
+--- query-list-of-figures ---
+#set page(
+ paper: "a8",
+ numbering: "1 / 1",
+ margin: (bottom: 1cm, rest: 0.5cm),
+)
+
+#set figure(numbering: "I")
+#show figure: set image(width: 80%)
+
+= List of Figures
+#context {
+ let elements = query(selector(figure).after(here()))
+ for it in elements [
+ Figure
+ #numbering(it.numbering,
+ ..counter(figure).at(it.location())):
+ #it.caption.body
+ #box(width: 1fr, repeat[.])
+ #counter(page).at(it.location()).first() \
+ ]
+}
+
+#figure(
+ image("/assets/images/cylinder.svg", width: 50%),
+ caption: [Cylinder],
+)
+
+#figure(
+ rect[Just some stand-in text],
+ kind: image,
+ supplement: "Figure",
+ caption: [Stand-in text],
+)
+
+#figure(
+ image("/assets/images/tetrahedron.svg", width: 50%),
+ caption: [Tetrahedron],
+)
+
+--- query-before-after ---
+// LARGE
+#set page(
+ paper: "a7",
+ numbering: "1 / 1",
+ margin: (bottom: 1cm, rest: 0.5cm),
+)
+
+#show heading.where(level: 1, outlined: true): it => [
+ #it
+
+ #set text(size: 12pt, weight: "regular")
+ #outline(
+ title: "Chapter outline",
+ indent: true,
+ target: heading
+ .where(level: 1)
+ .or(heading.where(level: 2))
+ .after(it.location(), inclusive: true)
+ .before(
+ heading
+ .where(level: 1, outlined: true)
+ .after(it.location(), inclusive: false),
+ inclusive: false,
+ )
+ )
+]
+
+#set heading(outlined: true, numbering: "1.")
+
+= Section 1
+== Subsection 1
+== Subsection 2
+=== Subsubsection 1
+=== Subsubsection 2
+== Subsection 3
+
+= Section 2
+== Subsection 1
+== Subsection 2
+
+= Section 3
+== Subsection 1
+== Subsection 2
+=== Subsubsection 1
+=== Subsubsection 2
+=== Subsubsection 3
+== Subsection 3
+
+--- query-and-or ---
+#set page(
+ paper: "a7",
+ numbering: "1 / 1",
+ margin: (bottom: 1cm, rest: 0.5cm),
+)
+
+#set heading(outlined: true, numbering: "1.")
+
+#context [
+ Non-outlined elements:
+ #(query(selector(heading).and(heading.where(outlined: false)))
+ .map(it => it.body).join(", "))
+]
+
+#heading("A", outlined: false)
+#heading("B", outlined: true)
+#heading("C", outlined: true)
+#heading("D", outlined: false)
+
+--- query-complex ---
+= A
+== B
+#figure([Cat], kind: "cat", supplement: [Other])
+=== D
+= E <first>
+#figure([Frog], kind: "frog", supplement: none)
+#figure([Giraffe], kind: "giraffe", supplement: none) <second>
+#figure([GiraffeCat], kind: "cat", supplement: [Other]) <second>
+= H
+#figure([Iguana], kind: "iguana", supplement: none)
+== I
+
+#let test-selector(selector, ref) = context {
+ test(query(selector).map(e => e.body), ref)
+}
+
+// Test `or`.
+#test-selector(
+ heading.where(level: 1).or(heading.where(level: 3)),
+ ([A], [D], [E], [H]),
+)
+
+#test-selector(
+ heading.where(level: 1).or(
+ heading.where(level: 3),
+ figure.where(kind: "frog"),
+ ),
+ ([A], [D], [E], [Frog], [H]),
+)
+
+#test-selector(
+ heading.where(level: 1).or(
+ heading.where(level: 2),
+ figure.where(kind: "frog"),
+ figure.where(kind: "cat"),
+ ),
+ ([A], [B], [Cat], [E], [Frog], [GiraffeCat], [H], [I]),
+)
+
+#test-selector(
+ figure.where(kind: "dog").or(heading.where(level: 3)),
+ ([D],),
+)
+
+#test-selector(
+ figure.where(kind: "dog").or(figure.where(kind: "fish")),
+ (),
+)
+
+// Test `or` duplicates removal.
+#test-selector(
+ heading.where(level: 1).or(heading.where(level: 1)),
+ ([A], [E], [H]),
+)
+
+// Test `and`.
+#test-selector(
+ figure.where(kind: "cat").and(figure.where(kind: "frog")),
+ (),
+)
+
+// Test `or` with `before`/`after`
+#test-selector(
+ selector(heading)
+ .before(<first>)
+ .or(selector(figure).before(<first>)),
+ ([A], [B], [Cat], [D], [E]),
+)
+
+#test-selector(
+ heading.where(level: 2)
+ .after(<first>)
+ .or(selector(figure).after(<first>)),
+ ([Frog], [Giraffe], [GiraffeCat], [Iguana], [I]),
+)
+
+// Test `and` with `after`
+#test-selector(
+ figure.where(kind: "cat")
+ .and(figure.where(supplement: [Other]))
+ .after(<first>),
+ ([GiraffeCat],),
+)
+
+// Test `and` (with nested `or`)
+#test-selector(
+ heading.where(level: 2)
+ .or(heading.where(level: 3))
+ .and(heading.where(level: 2).or(heading.where(level: 1))),
+ ([B], [I]),
+)
+
+#test-selector(
+ heading.where(level: 2)
+ .or(heading.where(level: 3), heading.where(level:1))
+ .and(
+ heading.where(level: 2).or(heading.where(level: 1)),
+ heading.where(level: 3).or(heading.where(level: 1)),
+ ),
+ ([A], [E], [H]),
+)
+
+// Test `and` with `or` and `before`/`after`
+#test-selector(
+ heading.where(level: 1).before(<first>)
+ .or(heading.where(level: 3).before(<first>))
+ .and(
+ heading.where(level: 1).before(<first>)
+ .or(heading.where(level: 2).before(<first>))
+ ),
+ ([A], [E]),
+)
+
+#test-selector(
+ heading.where(level: 1).before(<first>, inclusive: false)
+ .or(selector(figure).after(<first>))
+ .and(figure.where(kind: "iguana").or(
+ figure.where(kind: "frog"),
+ figure.where(kind: "cat"),
+ heading.where(level: 1).after(<first>),
+ )),
+ ([Frog], [GiraffeCat], [Iguana])
+)
diff --git a/tests/suite/introspection/state.typ b/tests/suite/introspection/state.typ
new file mode 100644
index 00000000..208a4ea2
--- /dev/null
+++ b/tests/suite/introspection/state.typ
@@ -0,0 +1,63 @@
+// Test state.
+
+--- state-basic ---
+#let s = state("hey", "a")
+#let double(it) = 2 * it
+
+#s.update(double)
+#s.update(double)
+$ 2 + 3 $
+#s.update(double)
+
+Is: #context s.get(),
+Was: #context {
+ let it = query(math.equation).first()
+ s.at(it.location())
+}.
+
+--- state-multiple-calls-same-key ---
+// Try same key with different initial value.
+#context state("key", 2).get()
+#state("key").update(x => x + 1)
+#context state("key", 2).get()
+#context state("key", 3).get()
+#state("key").update(x => x + 1)
+#context state("key", 2).get()
+
+--- state-nested ---
+#set page(width: 200pt)
+#set text(8pt)
+
+#let ls = state("lorem", lorem(1000).split("."))
+#let loremum(count) = {
+ context ls.get().slice(0, count).join(".").trim() + "."
+ ls.update(list => list.slice(count))
+}
+
+#let fs = state("fader", red)
+#let trait(title) = block[
+ #context text(fill: fs.get())[
+ *#title:* #loremum(1)
+ ]
+ #fs.update(color => color.lighten(30%))
+]
+
+#trait[Boldness]
+#trait[Adventure]
+#trait[Fear]
+#trait[Anger]
+
+--- state-no-convergence ---
+// Make sure that a warning is produced if the layout fails to converge.
+// Warning: layout did not converge within 5 attempts
+// Hint: check if any states or queries are updating themselves
+#let s = state("s", 1)
+#context s.update(s.final() + 1)
+#context s.get()
+
+--- state-at-no-context ---
+// Test `state.at` outside of context.
+// Error: 2-26 can only be used when context is known
+// Hint: 2-26 try wrapping this in a `context` expression
+// Hint: 2-26 the `context` expression should wrap everything that depends on this function
+#state("key").at(<label>)