From 020294fca9a7065d4b9cf4e677f606ebaaa29b00 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 13 Apr 2024 10:39:45 +0200 Subject: Better test runner (#3922) --- tests/suite/foundations/array.typ | 494 ++++++++++++++++++++++++ tests/suite/foundations/assert.typ | 40 ++ tests/suite/foundations/bytes.typ | 31 ++ tests/suite/foundations/calc.typ | 261 +++++++++++++ tests/suite/foundations/content.typ | 120 ++++++ tests/suite/foundations/context.typ | 65 ++++ tests/suite/foundations/datetime.typ | 93 +++++ tests/suite/foundations/dict.typ | 266 +++++++++++++ tests/suite/foundations/duration.typ | 103 +++++ tests/suite/foundations/eval.typ | 54 +++ tests/suite/foundations/float.typ | 66 ++++ tests/suite/foundations/int.typ | 73 ++++ tests/suite/foundations/label.typ | 70 ++++ tests/suite/foundations/panic.typ | 14 + tests/suite/foundations/plugin.typ | 47 +++ tests/suite/foundations/repr.typ | 57 +++ tests/suite/foundations/str.typ | 315 +++++++++++++++ tests/suite/foundations/type.typ | 25 ++ tests/suite/foundations/version.typ | 47 +++ tests/suite/introspection/counter.typ | 78 ++++ tests/suite/introspection/here.typ | 3 + tests/suite/introspection/locate.typ | 32 ++ tests/suite/introspection/query.typ | 267 +++++++++++++ tests/suite/introspection/state.typ | 63 +++ tests/suite/layout/align.typ | 142 +++++++ tests/suite/layout/angle.typ | 8 + tests/suite/layout/clip.typ | 0 tests/suite/layout/columns.typ | 124 ++++++ tests/suite/layout/container.typ | 183 +++++++++ tests/suite/layout/dir.typ | 24 ++ tests/suite/layout/flow/flow.typ | 67 ++++ tests/suite/layout/flow/invisibles.typ | 61 +++ tests/suite/layout/flow/orphan.typ | 31 ++ tests/suite/layout/grid/cell.typ | 132 +++++++ tests/suite/layout/grid/colspan.typ | 142 +++++++ tests/suite/layout/grid/footers.typ | 404 ++++++++++++++++++++ tests/suite/layout/grid/grid.typ | 276 ++++++++++++++ tests/suite/layout/grid/headers.typ | 368 ++++++++++++++++++ tests/suite/layout/grid/positioning.typ | 203 ++++++++++ tests/suite/layout/grid/rowspan.typ | 490 ++++++++++++++++++++++++ tests/suite/layout/grid/rtl.typ | 195 ++++++++++ tests/suite/layout/grid/stroke.typ | 435 +++++++++++++++++++++ tests/suite/layout/grid/styling.typ | 160 ++++++++ tests/suite/layout/hide.typ | 104 +++++ tests/suite/layout/inline/baseline.typ | 17 + tests/suite/layout/inline/bidi.typ | 77 ++++ tests/suite/layout/inline/cjk.typ | 90 +++++ tests/suite/layout/inline/hyphenate.typ | 51 +++ tests/suite/layout/inline/justify.typ | 170 +++++++++ tests/suite/layout/inline/linebreak.typ | 109 ++++++ tests/suite/layout/inline/overhang.typ | 24 ++ tests/suite/layout/inline/shaping.typ | 65 ++++ tests/suite/layout/inline/text.typ | 89 +++++ tests/suite/layout/layout.typ | 14 + tests/suite/layout/length.typ | 69 ++++ tests/suite/layout/limits.typ | 32 ++ tests/suite/layout/measure.typ | 9 + tests/suite/layout/pad.typ | 30 ++ tests/suite/layout/page.typ | 231 +++++++++++ tests/suite/layout/pagebreak.typ | 143 +++++++ tests/suite/layout/place.typ | 226 +++++++++++ tests/suite/layout/relative.typ | 7 + tests/suite/layout/repeat.typ | 44 +++ tests/suite/layout/spacing.typ | 38 ++ tests/suite/layout/stack.typ | 82 ++++ tests/suite/layout/table.typ | 284 ++++++++++++++ tests/suite/layout/transform.typ | 106 ++++++ tests/suite/loading/csv.typ | 27 ++ tests/suite/loading/json.typ | 16 + tests/suite/loading/read.typ | 12 + tests/suite/loading/toml.typ | 41 ++ tests/suite/loading/xml.typ | 28 ++ tests/suite/loading/yaml.typ | 17 + tests/suite/math/accent.typ | 33 ++ tests/suite/math/alignment.typ | 34 ++ tests/suite/math/attach.typ | 130 +++++++ tests/suite/math/cancel.typ | 38 ++ tests/suite/math/cases.typ | 13 + tests/suite/math/class.typ | 47 +++ tests/suite/math/delimited.typ | 64 ++++ tests/suite/math/equation.typ | 212 +++++++++++ tests/suite/math/frac.typ | 43 +++ tests/suite/math/interactions.typ | 95 +++++ tests/suite/math/mat.typ | 163 ++++++++ tests/suite/math/multiline.typ | 109 ++++++ tests/suite/math/op.typ | 30 ++ tests/suite/math/primes.typ | 50 +++ tests/suite/math/root.typ | 45 +++ tests/suite/math/size.typ | 9 + tests/suite/math/spacing.typ | 59 +++ tests/suite/math/style.typ | 34 ++ tests/suite/math/syntax.typ | 34 ++ tests/suite/math/text.typ | 45 +++ tests/suite/math/underover.typ | 21 + tests/suite/math/vec.typ | 27 ++ tests/suite/model/bibliography.typ | 55 +++ tests/suite/model/cite.typ | 92 +++++ tests/suite/model/document.typ | 36 ++ tests/suite/model/emph-strong.typ | 74 ++++ tests/suite/model/enum.typ | 156 ++++++++ tests/suite/model/figure.typ | 220 +++++++++++ tests/suite/model/footnote.typ | 182 +++++++++ tests/suite/model/heading.typ | 80 ++++ tests/suite/model/link.typ | 77 ++++ tests/suite/model/list.typ | 147 +++++++ tests/suite/model/numbering.typ | 103 +++++ tests/suite/model/outline.typ | 176 +++++++++ tests/suite/model/par.typ | 78 ++++ tests/suite/model/quote.typ | 86 +++++ tests/suite/model/ref.typ | 56 +++ tests/suite/model/terms.typ | 77 ++++ tests/suite/playground.typ | 1 + tests/suite/scripting/blocks.typ | 143 +++++++ tests/suite/scripting/call.typ | 200 ++++++++++ tests/suite/scripting/closure.typ | 223 +++++++++++ tests/suite/scripting/destructuring.typ | 357 +++++++++++++++++ tests/suite/scripting/field.typ | 76 ++++ tests/suite/scripting/for.typ | 135 +++++++ tests/suite/scripting/get-rule.typ | 67 ++++ tests/suite/scripting/if.typ | 134 +++++++ tests/suite/scripting/import.typ | 334 ++++++++++++++++ tests/suite/scripting/include.typ | 32 ++ tests/suite/scripting/let.typ | 143 +++++++ tests/suite/scripting/loop.typ | 142 +++++++ tests/suite/scripting/methods.typ | 51 +++ tests/suite/scripting/module.typ | 13 + tests/suite/scripting/modules/chap1.typ | 8 + tests/suite/scripting/modules/chap2.typ | 10 + tests/suite/scripting/modules/cycle1.typ | 5 + tests/suite/scripting/modules/cycle2.typ | 5 + tests/suite/scripting/ops.typ | 465 +++++++++++++++++++++++ tests/suite/scripting/params.typ | 69 ++++ tests/suite/scripting/recursion.typ | 55 +++ tests/suite/scripting/return.typ | 87 +++++ tests/suite/scripting/while.typ | 59 +++ tests/suite/styling/fold.typ | 19 + tests/suite/styling/set.typ | 96 +++++ tests/suite/styling/show-set.typ | 70 ++++ tests/suite/styling/show-text.typ | 133 +++++++ tests/suite/styling/show-where.typ | 89 +++++ tests/suite/styling/show.typ | 262 +++++++++++++ tests/suite/symbols/symbol.typ | 38 ++ tests/suite/syntax/backtracking.typ | 32 ++ tests/suite/syntax/comment.typ | 43 +++ tests/suite/syntax/embedded.typ | 9 + tests/suite/syntax/escape.typ | 36 ++ tests/suite/syntax/newlines.typ | 77 ++++ tests/suite/syntax/numbers.typ | 32 ++ tests/suite/syntax/shorthand.typ | 61 +++ tests/suite/text/case.typ | 11 + tests/suite/text/coma.typ | 26 ++ tests/suite/text/copy-paste.typ | 8 + tests/suite/text/deco.typ | 85 +++++ tests/suite/text/edge.typ | 39 ++ tests/suite/text/em.typ | 33 ++ tests/suite/text/font.typ | 66 ++++ tests/suite/text/lang.typ | 74 ++++ tests/suite/text/lorem.typ | 32 ++ tests/suite/text/raw.typ | 630 ++++++++++++++++++++++++++++++ tests/suite/text/shift.typ | 19 + tests/suite/text/smallcaps.typ | 3 + tests/suite/text/smartquote.typ | 122 ++++++ tests/suite/text/space.typ | 60 +++ tests/suite/visualize/circle.typ | 69 ++++ tests/suite/visualize/color.typ | 331 ++++++++++++++++ tests/suite/visualize/ellipse.typ | 31 ++ tests/suite/visualize/gradient.typ | 631 +++++++++++++++++++++++++++++++ tests/suite/visualize/image.typ | 122 ++++++ tests/suite/visualize/line.typ | 92 +++++ tests/suite/visualize/path.typ | 52 +++ tests/suite/visualize/pattern.typ | 131 +++++++ tests/suite/visualize/polygon.typ | 51 +++ tests/suite/visualize/rect.typ | 107 ++++++ tests/suite/visualize/square.typ | 146 +++++++ tests/suite/visualize/stroke.typ | 171 +++++++++ 175 files changed, 18364 insertions(+) create mode 100644 tests/suite/foundations/array.typ create mode 100644 tests/suite/foundations/assert.typ create mode 100644 tests/suite/foundations/bytes.typ create mode 100644 tests/suite/foundations/calc.typ create mode 100644 tests/suite/foundations/content.typ create mode 100644 tests/suite/foundations/context.typ create mode 100644 tests/suite/foundations/datetime.typ create mode 100644 tests/suite/foundations/dict.typ create mode 100644 tests/suite/foundations/duration.typ create mode 100644 tests/suite/foundations/eval.typ create mode 100644 tests/suite/foundations/float.typ create mode 100644 tests/suite/foundations/int.typ create mode 100644 tests/suite/foundations/label.typ create mode 100644 tests/suite/foundations/panic.typ create mode 100644 tests/suite/foundations/plugin.typ create mode 100644 tests/suite/foundations/repr.typ create mode 100644 tests/suite/foundations/str.typ create mode 100644 tests/suite/foundations/type.typ create mode 100644 tests/suite/foundations/version.typ create mode 100644 tests/suite/introspection/counter.typ create mode 100644 tests/suite/introspection/here.typ create mode 100644 tests/suite/introspection/locate.typ create mode 100644 tests/suite/introspection/query.typ create mode 100644 tests/suite/introspection/state.typ create mode 100644 tests/suite/layout/align.typ create mode 100644 tests/suite/layout/angle.typ create mode 100644 tests/suite/layout/clip.typ create mode 100644 tests/suite/layout/columns.typ create mode 100644 tests/suite/layout/container.typ create mode 100644 tests/suite/layout/dir.typ create mode 100644 tests/suite/layout/flow/flow.typ create mode 100644 tests/suite/layout/flow/invisibles.typ create mode 100644 tests/suite/layout/flow/orphan.typ create mode 100644 tests/suite/layout/grid/cell.typ create mode 100644 tests/suite/layout/grid/colspan.typ create mode 100644 tests/suite/layout/grid/footers.typ create mode 100644 tests/suite/layout/grid/grid.typ create mode 100644 tests/suite/layout/grid/headers.typ create mode 100644 tests/suite/layout/grid/positioning.typ create mode 100644 tests/suite/layout/grid/rowspan.typ create mode 100644 tests/suite/layout/grid/rtl.typ create mode 100644 tests/suite/layout/grid/stroke.typ create mode 100644 tests/suite/layout/grid/styling.typ create mode 100644 tests/suite/layout/hide.typ create mode 100644 tests/suite/layout/inline/baseline.typ create mode 100644 tests/suite/layout/inline/bidi.typ create mode 100644 tests/suite/layout/inline/cjk.typ create mode 100644 tests/suite/layout/inline/hyphenate.typ create mode 100644 tests/suite/layout/inline/justify.typ create mode 100644 tests/suite/layout/inline/linebreak.typ create mode 100644 tests/suite/layout/inline/overhang.typ create mode 100644 tests/suite/layout/inline/shaping.typ create mode 100644 tests/suite/layout/inline/text.typ create mode 100644 tests/suite/layout/layout.typ create mode 100644 tests/suite/layout/length.typ create mode 100644 tests/suite/layout/limits.typ create mode 100644 tests/suite/layout/measure.typ create mode 100644 tests/suite/layout/pad.typ create mode 100644 tests/suite/layout/page.typ create mode 100644 tests/suite/layout/pagebreak.typ create mode 100644 tests/suite/layout/place.typ create mode 100644 tests/suite/layout/relative.typ create mode 100644 tests/suite/layout/repeat.typ create mode 100644 tests/suite/layout/spacing.typ create mode 100644 tests/suite/layout/stack.typ create mode 100644 tests/suite/layout/table.typ create mode 100644 tests/suite/layout/transform.typ create mode 100644 tests/suite/loading/csv.typ create mode 100644 tests/suite/loading/json.typ create mode 100644 tests/suite/loading/read.typ create mode 100644 tests/suite/loading/toml.typ create mode 100644 tests/suite/loading/xml.typ create mode 100644 tests/suite/loading/yaml.typ create mode 100644 tests/suite/math/accent.typ create mode 100644 tests/suite/math/alignment.typ create mode 100644 tests/suite/math/attach.typ create mode 100644 tests/suite/math/cancel.typ create mode 100644 tests/suite/math/cases.typ create mode 100644 tests/suite/math/class.typ create mode 100644 tests/suite/math/delimited.typ create mode 100644 tests/suite/math/equation.typ create mode 100644 tests/suite/math/frac.typ create mode 100644 tests/suite/math/interactions.typ create mode 100644 tests/suite/math/mat.typ create mode 100644 tests/suite/math/multiline.typ create mode 100644 tests/suite/math/op.typ create mode 100644 tests/suite/math/primes.typ create mode 100644 tests/suite/math/root.typ create mode 100644 tests/suite/math/size.typ create mode 100644 tests/suite/math/spacing.typ create mode 100644 tests/suite/math/style.typ create mode 100644 tests/suite/math/syntax.typ create mode 100644 tests/suite/math/text.typ create mode 100644 tests/suite/math/underover.typ create mode 100644 tests/suite/math/vec.typ create mode 100644 tests/suite/model/bibliography.typ create mode 100644 tests/suite/model/cite.typ create mode 100644 tests/suite/model/document.typ create mode 100644 tests/suite/model/emph-strong.typ create mode 100644 tests/suite/model/enum.typ create mode 100644 tests/suite/model/figure.typ create mode 100644 tests/suite/model/footnote.typ create mode 100644 tests/suite/model/heading.typ create mode 100644 tests/suite/model/link.typ create mode 100644 tests/suite/model/list.typ create mode 100644 tests/suite/model/numbering.typ create mode 100644 tests/suite/model/outline.typ create mode 100644 tests/suite/model/par.typ create mode 100644 tests/suite/model/quote.typ create mode 100644 tests/suite/model/ref.typ create mode 100644 tests/suite/model/terms.typ create mode 100644 tests/suite/playground.typ create mode 100644 tests/suite/scripting/blocks.typ create mode 100644 tests/suite/scripting/call.typ create mode 100644 tests/suite/scripting/closure.typ create mode 100644 tests/suite/scripting/destructuring.typ create mode 100644 tests/suite/scripting/field.typ create mode 100644 tests/suite/scripting/for.typ create mode 100644 tests/suite/scripting/get-rule.typ create mode 100644 tests/suite/scripting/if.typ create mode 100644 tests/suite/scripting/import.typ create mode 100644 tests/suite/scripting/include.typ create mode 100644 tests/suite/scripting/let.typ create mode 100644 tests/suite/scripting/loop.typ create mode 100644 tests/suite/scripting/methods.typ create mode 100644 tests/suite/scripting/module.typ create mode 100644 tests/suite/scripting/modules/chap1.typ create mode 100644 tests/suite/scripting/modules/chap2.typ create mode 100644 tests/suite/scripting/modules/cycle1.typ create mode 100644 tests/suite/scripting/modules/cycle2.typ create mode 100644 tests/suite/scripting/ops.typ create mode 100644 tests/suite/scripting/params.typ create mode 100644 tests/suite/scripting/recursion.typ create mode 100644 tests/suite/scripting/return.typ create mode 100644 tests/suite/scripting/while.typ create mode 100644 tests/suite/styling/fold.typ create mode 100644 tests/suite/styling/set.typ create mode 100644 tests/suite/styling/show-set.typ create mode 100644 tests/suite/styling/show-text.typ create mode 100644 tests/suite/styling/show-where.typ create mode 100644 tests/suite/styling/show.typ create mode 100644 tests/suite/symbols/symbol.typ create mode 100644 tests/suite/syntax/backtracking.typ create mode 100644 tests/suite/syntax/comment.typ create mode 100644 tests/suite/syntax/embedded.typ create mode 100644 tests/suite/syntax/escape.typ create mode 100644 tests/suite/syntax/newlines.typ create mode 100644 tests/suite/syntax/numbers.typ create mode 100644 tests/suite/syntax/shorthand.typ create mode 100644 tests/suite/text/case.typ create mode 100644 tests/suite/text/coma.typ create mode 100644 tests/suite/text/copy-paste.typ create mode 100644 tests/suite/text/deco.typ create mode 100644 tests/suite/text/edge.typ create mode 100644 tests/suite/text/em.typ create mode 100644 tests/suite/text/font.typ create mode 100644 tests/suite/text/lang.typ create mode 100644 tests/suite/text/lorem.typ create mode 100644 tests/suite/text/raw.typ create mode 100644 tests/suite/text/shift.typ create mode 100644 tests/suite/text/smallcaps.typ create mode 100644 tests/suite/text/smartquote.typ create mode 100644 tests/suite/text/space.typ create mode 100644 tests/suite/visualize/circle.typ create mode 100644 tests/suite/visualize/color.typ create mode 100644 tests/suite/visualize/ellipse.typ create mode 100644 tests/suite/visualize/gradient.typ create mode 100644 tests/suite/visualize/image.typ create mode 100644 tests/suite/visualize/line.typ create mode 100644 tests/suite/visualize/path.typ create mode 100644 tests/suite/visualize/pattern.typ create mode 100644 tests/suite/visualize/polygon.typ create mode 100644 tests/suite/visualize/rect.typ create mode 100644 tests/suite/visualize/square.typ create mode 100644 tests/suite/visualize/stroke.typ (limited to 'tests/suite') diff --git a/tests/suite/foundations/array.typ b/tests/suite/foundations/array.typ new file mode 100644 index 00000000..3992d75e --- /dev/null +++ b/tests/suite/foundations/array.typ @@ -0,0 +1,494 @@ +// Test arrays. + +--- array-basic-syntax --- +#set page(width: 150pt) + +// Empty. +#() + +// Not an array, just a parenthesized expression. +#(1) + +// One item and trailing comma. +#(-1,) + +// No trailing comma. +#(true, false) + +// Multiple lines and items and trailing comma. +#("1" + , rgb("002") + ,) + +--- array-bad-token --- +// Error: 4-6 unexpected end of block comment +#(1*/2) + +--- array-bad-number-suffix --- +// Error: 6-8 invalid number suffix: u +#(1, 1u 2) + +--- array-leading-comma --- +// Error: 3-4 unexpected comma +#(,1) + +--- array-incomplete-pair --- +// Missing expression makes named pair incomplete, making this an empty array. +// Error: 5 expected expression +#(a:) + +--- array-named-pair --- +// Named pair after this is already identified as an array. +// Error: 6-10 expected expression, found named pair +#(1, b: 2) + +--- array-keyed-pair --- +// Keyed pair after this is already identified as an array. +// Error: 6-14 expected expression, found keyed pair +#(1, "key": 2) + +--- array-bad-conversion-from-string --- +// Error: 8-15 expected array, bytes, or version, found string +#array("hello") + +--- spread-into-array --- +// Test spreading into array and dictionary. +#{ + let l = (1, 2, 3) + let r = (5, 6, 7) + test((..l, 4, ..r), range(1, 8)) + test((..none), ()) +} + +--- spread-dict-into-array --- +// Error: 9-17 cannot spread dictionary into array +#(1, 2, ..(a: 1)) + +--- array-len --- +// Test the `len` method. +#test(().len(), 0) +#test(("A", "B", "C").len(), 3) + +--- array-at-lvalue --- +// Test lvalue and rvalue access. +#{ + let array = (1, 2) + array.at(1) += 5 + array.at(0) + test(array, (1, 8)) +} + +--- array-first-and-at-lvalue --- +// Test different lvalue method. +#{ + let array = (1, 2, 3) + array.first() = 7 + array.at(1) *= 8 + test(array, (7, 16, 3)) +} + +--- array-at-out-of-bounds --- +// Test rvalue out of bounds. +// Error: 2-17 array index out of bounds (index: 5, len: 3) and no default value was specified +#(1, 2, 3).at(5) + +--- array-at-out-of-bounds-negative --- +// Error: 2-18 array index out of bounds (index: -4, len: 3) and no default value was specified +#(1, 2, 3).at(-4) + +--- array-at-out-of-bounds-lvalue --- +// Test lvalue out of bounds. +#{ + let array = (1, 2, 3) + // Error: 3-14 array index out of bounds (index: 3, len: 3) + array.at(3) = 5 +} + +--- array-at-with-default --- +// Test default value. +#test((1, 2, 3).at(2, default: 5), 3) +#test((1, 2, 3).at(3, default: 5), 5) + +--- array-remove-with-default --- +// Test remove with default value. + +#{ + let array = (1, 2, 3) + test(array.remove(2, default: 5), 3) +} + +#{ + let array = (1, 2, 3) + test(array.remove(3, default: 5), 5) +} + +--- array-range --- +// Test the `range` function. +#test(range(4), (0, 1, 2, 3)) +#test(range(1, 4), (1, 2, 3)) +#test(range(-4, 2), (-4, -3, -2, -1, 0, 1)) +#test(range(10, 5), ()) +#test(range(10, step: 3), (0, 3, 6, 9)) +#test(range(1, 4, step: 1), (1, 2, 3)) +#test(range(1, 8, step: 2), (1, 3, 5, 7)) +#test(range(5, 2, step: -1), (5, 4, 3)) +#test(range(10, 0, step: -3), (10, 7, 4, 1)) + +--- array-range-end-missing --- +// Error: 2-9 missing argument: end +#range() + +--- array-range-float-invalid --- +// Error: 11-14 expected integer, found float +#range(1, 2.0) + +--- array-range-bad-step-type --- +// Error: 17-22 expected integer, found string +#range(4, step: "one") + +--- array-range-step-zero --- +// Error: 18-19 number must not be zero +#range(10, step: 0) + +--- array-bad-method-lvalue --- +// Test bad lvalue. +// Error: 2:3-2:14 cannot mutate a temporary value +#let array = (1, 2, 3) +#(array.len() = 4) + +--- array-unknown-method-lvalue --- +// Test bad lvalue. +// Error: 2:9-2:13 type array has no method `yolo` +#let array = (1, 2, 3) +#(array.yolo() = 4) + +--- array-negative-indices --- +// Test negative indices. +#{ + let array = (1, 2, 3, 4) + test(array.at(0), 1) + test(array.at(-1), 4) + test(array.at(-2), 3) + test(array.at(-3), 2) + test(array.at(-4), 1) +} + +--- array-first-and-last --- +// The the `first` and `last` methods. +#test((1,).first(), 1) +#test((2,).last(), 2) +#test((1, 2, 3).first(), 1) +#test((1, 2, 3).last(), 3) + +--- array-first-empty --- +// Error: 2-12 array is empty +#().first() + +--- array-last-empty --- +// Error: 2-11 array is empty +#().last() + +--- array-push-and-pop --- +// Test the `push` and `pop` methods. +#{ + let tasks = (a: (1, 2, 3), b: (4, 5, 6)) + test(tasks.at("a").pop(), 3) + tasks.b.push(7) + test(tasks.a, (1, 2)) + test(tasks.at("b"), (4, 5, 6, 7)) +} + +--- array-insert-and-remove --- +// Test the `insert` and `remove` methods. +#{ + let array = (0, 1, 2, 4, 5) + array.insert(3, 3) + test(array, range(6)) + array.remove(1) + test(array, (0, 2, 3, 4, 5)) +} + +--- array-insert-missing-index --- +// Error: 2:2-2:18 missing argument: index +#let numbers = () +#numbers.insert() + +--- array-slice --- +// Test the `slice` method. +#test((1, 2, 3, 4).slice(2), (3, 4)) +#test(range(10).slice(2, 6), (2, 3, 4, 5)) +#test(range(10).slice(4, count: 3), (4, 5, 6)) +#test(range(10).slice(-5, count: 2), (5, 6)) +#test((1, 2, 3).slice(2, -2), ()) +#test((1, 2, 3).slice(-2, 2), (2,)) +#test((1, 2, 3).slice(-3, 2), (1, 2)) +#test("ABCD".split("").slice(1, -1).join("-"), "A-B-C-D") + +--- array-slice-out-of-bounds --- +// Error: 2-30 array index out of bounds (index: 12, len: 10) +#range(10).slice(9, count: 3) + +--- array-slice-out-of-bounds-negative --- +// Error: 2-24 array index out of bounds (index: -4, len: 3) +#(1, 2, 3).slice(0, -4) + +--- array-position --- +// Test the `position` method. +#test(("Hi", "❤️", "Love").position(s => s == "❤️"), 1) +#test(("Bye", "💘", "Apart").position(s => s == "❤️"), none) +#test(("A", "B", "CDEF", "G").position(v => v.len() > 2), 2) + +--- array-filter --- +// Test the `filter` method. +#test(().filter(calc.even), ()) +#test((1, 2, 3, 4).filter(calc.even), (2, 4)) +#test((7, 3, 2, 5, 1).filter(x => x < 5), (3, 2, 1)) + +--- array-map --- +// Test the `map` method. +#test(().map(x => x * 2), ()) +#test((2, 3).map(x => x * 2), (4, 6)) + +--- array-fold --- +// Test the `fold` method. +#test(().fold("hi", grid), "hi") +#test((1, 2, 3, 4).fold(0, (s, x) => s + x), 10) + +--- array-fold-closure-without-params --- +// Error: 20-22 unexpected argument +#(1, 2, 3).fold(0, () => none) + +--- array-sum --- +// Test the `sum` method. +#test(().sum(default: 0), 0) +#test(().sum(default: []), []) +#test((1, 2, 3).sum(), 6) + +--- array-sum-empty --- +// Error: 2-10 cannot calculate sum of empty array with no default +#().sum() + +--- array-product --- +// Test the `product` method. +#test(().product(default: 0), 0) +#test(().product(default: []), []) +#test(([ab], 3).product(), [ab]*3) +#test((1, 2, 3).product(), 6) + +--- array-product-empty --- +// Error: 2-14 cannot calculate product of empty array with no default +#().product() + +--- array-rev --- +// Test the `rev` method. +#test(range(3).rev(), (2, 1, 0)) + +--- array-join --- +// Test the `join` method. +#test(().join(), none) +#test((1,).join(), 1) +#test(("a", "b", "c").join(), "abc") +#test("(" + ("a", "b", "c").join(", ") + ")", "(a, b, c)") + +--- array-join-bad-values --- +// Error: 2-22 cannot join boolean with boolean +#(true, false).join() + +--- array-join-bad-separator --- +// Error: 2-20 cannot join string with integer +#("a", "b").join(1) + +--- array-join-content --- +// Test joining content. +#([One], [Two], [Three]).join([, ], last: [ and ]). + +--- array-intersperse --- +// Test the `intersperse` method +#test(().intersperse("a"), ()) +#test((1,).intersperse("a"), (1,)) +#test((1, 2).intersperse("a"), (1, "a", 2)) +#test((1, 2, "b").intersperse("a"), (1, "a", 2, "a", "b")) + +--- array-chunks --- +// Test the `chunks` method. +#test(().chunks(10), ()) +#test((1, 2, 3).chunks(10), ((1, 2, 3),)) +#test((1, 2, 3, 4, 5, 6).chunks(3), ((1, 2, 3), (4, 5, 6))) +#test((1, 2, 3, 4, 5, 6, 7, 8).chunks(3), ((1, 2, 3), (4, 5, 6), (7, 8))) + +#test(().chunks(10, exact: true), ()) +#test((1, 2, 3).chunks(10, exact: true), ()) +#test((1, 2, 3, 4, 5, 6).chunks(3, exact: true), ((1, 2, 3), (4, 5, 6))) +#test((1, 2, 3, 4, 5, 6, 7, 8).chunks(3, exact: true), ((1, 2, 3), (4, 5, 6))) + +--- array-chunks-size-zero --- +// Error: 19-20 number must be positive +#(1, 2, 3).chunks(0) + +--- array-chunks-size-negative --- +// Error: 19-21 number must be positive +#(1, 2, 3).chunks(-5) + +--- array-sorted --- +// Test the `sorted` method. +#test(().sorted(), ()) +#test(().sorted(key: x => x), ()) +#test(((true, false) * 10).sorted(), (false,) * 10 + (true,) * 10) +#test(("it", "the", "hi", "text").sorted(), ("hi", "it", "text", "the")) +#test(("I", "the", "hi", "text").sorted(key: x => x), ("I", "hi", "text", "the")) +#test(("I", "the", "hi", "text").sorted(key: x => x.len()), ("I", "hi", "the", "text")) +#test((2, 1, 3, 10, 5, 8, 6, -7, 2).sorted(), (-7, 1, 2, 2, 3, 5, 6, 8, 10)) +#test((2, 1, 3, -10, -5, 8, 6, -7, 2).sorted(key: x => x), (-10, -7, -5, 1, 2, 2, 3, 6, 8)) +#test((2, 1, 3, -10, -5, 8, 6, -7, 2).sorted(key: x => x * x), (1, 2, 2, 3, -5, 6, -7, 8, -10)) + +--- array-sorted-key-function-positional-1 --- +// Error: 12-18 unexpected argument +#().sorted(x => x) + +--- array-zip --- +// Test the `zip` method. +#test(().zip(()), ()) +#test((1,).zip(()), ()) +#test((1,).zip((2,)), ((1, 2),)) +#test((1, 2).zip((3, 4)), ((1, 3), (2, 4))) +#test((1, 2, 3, 4).zip((5, 6)), ((1, 5), (2, 6))) +#test(((1, 2), 3).zip((4, 5)), (((1, 2), 4), (3, 5))) +#test((1, "hi").zip((true, false)), ((1, true), ("hi", false))) +#test((1, 2, 3).zip((3, 4, 5), (6, 7, 8)), ((1, 3, 6), (2, 4, 7), (3, 5, 8))) +#test(().zip((), ()), ()) +#test((1,).zip((2,), (3,)), ((1, 2, 3),)) +#test((1, 2, 3).zip(), ((1,), (2,), (3,))) +#test(array.zip(()), ()) + +--- array-enumerate --- +// Test the `enumerate` method. +#test(().enumerate(), ()) +#test(().enumerate(start: 5), ()) +#test(("a", "b", "c").enumerate(), ((0, "a"), (1, "b"), (2, "c"))) +#test(("a", "b", "c").enumerate(start: 1), ((1, "a"), (2, "b"), (3, "c"))) +#test(("a", "b", "c").enumerate(start: 42), ((42, "a"), (43, "b"), (44, "c"))) +#test(("a", "b", "c").enumerate(start: -7), ((-7, "a"), (-6, "b"), (-5, "c"))) + +--- array-dedup --- +// Test the `dedup` method. +#test(().dedup(), ()) +#test((1,).dedup(), (1,)) +#test((1, 1).dedup(), (1,)) +#test((1, 2, 1).dedup(), (1, 2)) +#test(("Jane", "John", "Eric").dedup(), ("Jane", "John", "Eric")) +#test(("Jane", "John", "Eric", "John").dedup(), ("Jane", "John", "Eric")) + +--- array-dedup-key --- +// Test the `dedup` method with the `key` argument. +#test((1, 2, 3, 4, 5, 6).dedup(key: x => calc.rem(x, 2)), (1, 2)) +#test((1, 2, 3, 4, 5, 6).dedup(key: x => calc.rem(x, 3)), (1, 2, 3)) +#test(("Hello", "World", "Hi", "There").dedup(key: x => x.len()), ("Hello", "Hi")) +#test(("Hello", "World", "Hi", "There").dedup(key: x => x.at(0)), ("Hello", "World", "There")) + +--- array-to-dict --- +// Test the `to-dict` method. +#test(().to-dict(), (:)) +#test((("a", 1), ("b", 2), ("c", 3)).to-dict(), (a: 1, b: 2, c: 3)) +#test((("a", 1), ("b", 2), ("c", 3), ("b", 4)).to-dict(), (a: 1, b: 4, c: 3)) + +--- array-to-dict-bad-item-type --- +// Error: 2-16 expected (str, any) pairs, found integer +#(1,).to-dict() + +--- array-to-dict-bad-pair-length-1 --- +// Error: 2-19 expected pairs of length 2, found length 1 +#((1,),).to-dict() + +--- array-to-dict-bad-pair-length-3 --- +// Error: 2-26 expected pairs of length 2, found length 3 +#(("key",1,2),).to-dict() + +--- array-to-dict-bad-key-type --- +// Error: 2-21 expected key of type str, found integer +#((1, 2),).to-dict() + +--- array-zip-positional-and-named-argument --- +// Error: 13-30 unexpected argument: val +#().zip((), val: "applicable") + +--- array-sorted-bad-key --- +// Error: 32-37 cannot divide by zero +#(1, 2, 0, 3).sorted(key: x => 5 / x) + +--- array-sorted-uncomparable --- +// Error: 2-26 cannot compare content and content +#([Hi], [There]).sorted() + +--- array-sorted-uncomparable-lengths --- +// Error: 2-26 cannot compare 3em with 2pt +#(1pt, 2pt, 3em).sorted() + +--- array-sorted-key-function-positional-2 --- +// Error: 42-52 unexpected argument +#((k: "a", v: 2), (k: "b", v: 1)).sorted(it => it.v) + +--- issue-3014-mix-array-dictionary --- +// Error: 8-17 expected expression, found named pair +#(box, fill: red) + +--- issue-3154-array-first-empty --- +#{ + let array = () + // Error: 3-16 array is empty + array.first() +} + +--- issue-3154-array-first-mutable-empty --- +#{ + let array = () + // Error: 3-16 array is empty + array.first() = 9 +} + +--- issue-3154-array-last-empty --- +#{ + let array = () + // Error: 3-15 array is empty + array.last() +} + +--- issue-3154-array-last-mutable-empty --- +#{ + let array = () + // Error: 3-15 array is empty + array.last() = 9 +} + +--- issue-3154-array-at-out-of-bounds --- +#{ + let array = (1,) + // Error: 3-14 array index out of bounds (index: 1, len: 1) and no default value was specified + array.at(1) +} + +--- issue-3154-array-at-out-of-bounds-default --- +#{ + let array = (1,) + test(array.at(1, default: 0), 0) +} + +--- issue-3154-array-at-out-of-bounds-mutable --- +#{ + let array = (1,) + // Error: 3-14 array index out of bounds (index: 1, len: 1) + array.at(1) = 9 +} + +--- issue-3154-array-at-out-of-bounds-mutable-default --- +#{ + let array = (1,) + // Error: 3-26 array index out of bounds (index: 1, len: 1) + array.at(1, default: 0) = 9 +} + +--- array-unopened --- +// Error: 2-3 unclosed delimiter +#{)} + +--- array-unclosed --- +// Error: 3-4 unclosed delimiter +#{(} diff --git a/tests/suite/foundations/assert.typ b/tests/suite/foundations/assert.typ new file mode 100644 index 00000000..5de0f387 --- /dev/null +++ b/tests/suite/foundations/assert.typ @@ -0,0 +1,40 @@ +--- assert-fail --- +// Test failing assertions. +// Error: 2-16 assertion failed +#assert(1 == 2) + +--- assert-fail-message --- +// Test failing assertions. +// Error: 2-51 assertion failed: two is smaller than one +#assert(2 < 1, message: "two is smaller than one") + +--- assert-bad-type --- +// Test failing assertions. +// Error: 9-15 expected boolean, found string +#assert("true") + +--- assert-eq-fail --- +// Test failing assertions. +// Error: 2-19 equality assertion failed: value 10 was not equal to 11 +#assert.eq(10, 11) + +--- assert-eq-fail-message --- +// Test failing assertions. +// Error: 2-55 equality assertion failed: 10 and 12 are not equal +#assert.eq(10, 12, message: "10 and 12 are not equal") + +--- assert-ne-fail --- +// Test failing assertions. +// Error: 2-19 inequality assertion failed: value 11 was equal to 11 +#assert.ne(11, 11) + +--- assert-ne-fail-message --- +// Test failing assertions. +// Error: 2-57 inequality assertion failed: must be different from 11 +#assert.ne(11, 11, message: "must be different from 11") + +--- assert-success --- +// Test successful assertions. +#assert(5 > 3) +#assert.eq(15, 15) +#assert.ne(10, 12) diff --git a/tests/suite/foundations/bytes.typ b/tests/suite/foundations/bytes.typ new file mode 100644 index 00000000..c7089278 --- /dev/null +++ b/tests/suite/foundations/bytes.typ @@ -0,0 +1,31 @@ +// Test the bytes type. + +--- bytes-basic --- +#let data = read("/assets/images/rhino.png", encoding: none) +#test(data.len(), 232243) +#test(data.slice(0, count: 5), bytes((137, 80, 78, 71, 13))) +#test(str(data.slice(1, 4)), "PNG") +#test(repr(data), "bytes(232243)") + +--- bytes-string-conversion --- +#test(str(bytes(range(0x41, 0x50))), "ABCDEFGHIJKLMNO") + +--- bytes-array-conversion --- +#test(array(bytes("Hello")), (0x48, 0x65, 0x6C, 0x6C, 0x6F)) + +--- bytes-addition --- +// Test addition and joining. +#test(bytes((1, 2)) + bytes(()), bytes((1, 2))) +#test(bytes((1, 2)) + bytes((3, 4)), bytes((1, 2, 3, 4))) +#test(bytes(()) + bytes((3, 4)), bytes((3, 4))) + +--- bytes-joining --- +#test(str({ + bytes("Hello") + bytes((0x20,)) + bytes("World") +}), "Hello World") + +--- bytes-bad-conversion-from-dict --- +// Error: 8-14 expected string, array, or bytes, found dictionary +#bytes((a: 1)) diff --git a/tests/suite/foundations/calc.typ b/tests/suite/foundations/calc.typ new file mode 100644 index 00000000..e702be9f --- /dev/null +++ b/tests/suite/foundations/calc.typ @@ -0,0 +1,261 @@ +--- calc-round --- +#test(calc.round(calc.e, digits: 2), 2.72) +#test(calc.round(calc.pi, digits: 2), 3.14) + +--- calc-abs --- +// Test the `abs` function. +#test(calc.abs(-3), 3) +#test(calc.abs(3), 3) +#test(calc.abs(-0.0), 0.0) +#test(calc.abs(0.0), -0.0) +#test(calc.abs(-3.14), 3.14) +#test(calc.abs(50%), 50%) +#test(calc.abs(-25%), 25%) + +--- cals-abs-bad-type --- +// Error: 11-22 expected integer, float, length, angle, ratio, or fraction, found string +#calc.abs("no number") + +--- calc-even-and-odd --- +// Test the `even` and `odd` functions. +#test(calc.even(2), true) +#test(calc.odd(2), false) +#test(calc.odd(-1), true) +#test(calc.even(-11), false) + +--- calc-rem --- +// Test the `rem` function. +#test(calc.rem(1, 1), 0) +#test(calc.rem(5, 3), 2) +#test(calc.rem(5, -3), 2) +#test(calc.rem(22.5, 10), 2.5) +#test(calc.rem(9, 4.5), 0) + +--- calc-rem-divisor-zero-1 --- +// Error: 14-15 divisor must not be zero +#calc.rem(5, 0) + +--- calc-rem-divisor-zero-2 --- +// Error: 16-19 divisor must not be zero +#calc.rem(3.0, 0.0) + +--- calc-div-euclid --- +// Test the `div-euclid` function. +#test(calc.div-euclid(7, 3), 2) +#test(calc.div-euclid(7, -3), -2) +#test(calc.div-euclid(-7, 3), -3) +#test(calc.div-euclid(-7, -3), 3) +#test(calc.div-euclid(2.5, 2), 1) + +--- calc-div-euclid-divisor-zero-1 --- +// Error: 21-22 divisor must not be zero +#calc.div-euclid(5, 0) + +--- calc-div-euclid-divisor-zero-2 --- +// Error: 23-26 divisor must not be zero +#calc.div-euclid(3.0, 0.0) + +--- calc-rem-euclid --- +// Test the `rem-euclid` function. +#test(calc.rem-euclid(7, 3), 1) +#test(calc.rem-euclid(7, -3), 1) +#test(calc.rem-euclid(-7, 3), 2) +#test(calc.rem-euclid(-7, -3), 2) +#test(calc.rem-euclid(2.5, 2), 0.5) + +--- calc-rem-euclid-divisor-zero-1 --- +// Error: 21-22 divisor must not be zero +#calc.rem-euclid(5, 0) + +--- calc-rem-euclid-divisor-zero-2 --- +// Error: 23-26 divisor must not be zero +#calc.rem-euclid(3.0, 0.0) + +--- calc-quo --- +// Test the `quo` function. +#test(calc.quo(1, 1), 1) +#test(calc.quo(5, 3), 1) +#test(calc.quo(5, -3), -1) +#test(calc.quo(22.5, 10), 2) +#test(calc.quo(9, 4.5), 2) + +--- calc-quo-divisor-zero-1 --- +// Error: 14-15 divisor must not be zero +#calc.quo(5, 0) + +--- calc-quo-divisor-zero-2 --- +// Error: 16-19 divisor must not be zero +#calc.quo(3.0, 0.0) + +--- calc-min-and-max --- +// Test the `min` and `max` functions. +#test(calc.min(2, -4), -4) +#test(calc.min(3.5, 1e2, -0.1, 3), -0.1) +#test(calc.max(-3, 11), 11) +#test(calc.min("hi"), "hi") + +--- calc-pow-log-exp-ln --- +// Test the `pow`, `log`, `exp`, and `ln` functions. +#test(calc.pow(10, 0), 1) +#test(calc.pow(2, 4), 16) +#test(calc.exp(2), calc.pow(calc.e, 2)) +#test(calc.ln(10), calc.log(10, base: calc.e)) + +--- calc-bit-logical --- +// Test the `bit-not`, `bit-and`, `bit-or` and `bit-xor` functions. +#test(64.bit-not(), -65) +#test(0.bit-not(), -1) +#test((-56).bit-not(), 55) +#test(128.bit-and(192), 128) +#test(192.bit-and(224), 192) +#test((-1).bit-and(325532), 325532) +#test(0.bit-and(-53), 0) +#test(0.bit-or(-1), -1) +#test(5.bit-or(3), 7) +#test((-50).bit-or(3), -49) +#test(64.bit-or(32), 96) +#test((-1).bit-xor(1), -2) +#test(64.bit-xor(96), 32) +#test((-1).bit-xor(-7), 6) +#test(0.bit-xor(492), 492) + +--- calc-bit-shift --- +// Test the `bit-lshift` and `bit-rshift` functions. +#test(32.bit-lshift(2), 128) +#test(694.bit-lshift(0), 694) +#test(128.bit-rshift(2), 32) +#test(128.bit-rshift(12345), 0) +#test((-7).bit-rshift(2), -2) +#test((-7).bit-rshift(12345), -1) +#test(128.bit-rshift(2, logical: true), 32) +#test((-7).bit-rshift(61, logical: true), 7) +#test(128.bit-rshift(12345, logical: true), 0) +#test((-7).bit-rshift(12345, logical: true), 0) + +--- calc-bit-shift-too-large --- +// Error: 2-18 the result is too large +#1.bit-lshift(64) + +--- calc-bit-lshift-negative --- +// Error: 15-17 number must be at least zero +#1.bit-lshift(-1) + +--- calc-bit-rshift-negative --- +// Error: 15-17 number must be at least zero +#1.bit-rshift(-1) + +--- calc-pow-zero-to-power-of-zero --- +// Error: 2-16 zero to the power of zero is undefined +#calc.pow(0, 0) + +--- calc-pow-exponent-too-large --- +// Error: 14-31 exponent is too large +#calc.pow(2, 10000000000000000) + +--- calc-pow-too-large --- +// Error: 2-25 the result is too large +#calc.pow(2, 2147483647) + +--- calc-pow-bad-exponent --- +// Error: 14-36 exponent may not be infinite, subnormal, or NaN +#calc.pow(2, calc.pow(2.0, 10000.0)) + +--- calc-pow-not-real --- +// Error: 2-19 the result is not a real number +#calc.pow(-1, 0.5) + +--- calc-sqrt-not-real --- +// Error: 12-14 cannot take square root of negative number +#calc.sqrt(-1) + +--- calc-root --- +#test(calc.root(12.0, 1), 12.0) +#test(calc.root(9.0, 2), 3.0) +#test(calc.root(27.0, 3), 3.0) +#test(calc.root(-27.0, 3), -3.0) +// 100^(-1/2) = (100^(1/2))^-1 = 1/sqrt(100) +#test(calc.root(100.0, -2), 0.1) + +--- calc-root-zeroth --- +// Error: 17-18 cannot take the 0th root of a number +#calc.root(1.0, 0) + +--- calc-root-negative-even --- +// Error: 24-25 negative numbers do not have a real nth root when n is even +#test(calc.root(-27.0, 4), -3.0) + +--- calc-log-negative --- +// Error: 11-13 value must be strictly positive +#calc.log(-1) + +--- calc-log-bad-base --- +// Error: 20-21 base may not be zero, NaN, infinite, or subnormal +#calc.log(1, base: 0) + +--- calc-log-not-real --- +// Error: 2-24 the result is not a real number +#calc.log(10, base: -1) + +--- calc-fact --- +// Test the `fact` function. +#test(calc.fact(0), 1) +#test(calc.fact(5), 120) + +--- calc-fact-too-large --- +// Error: 2-15 the result is too large +#calc.fact(21) + +--- calc-perm --- +// Test the `perm` function. +#test(calc.perm(0, 0), 1) +#test(calc.perm(5, 3), 60) +#test(calc.perm(5, 5), 120) +#test(calc.perm(5, 6), 0) + +--- calc-perm-too-large --- +// Error: 2-19 the result is too large +#calc.perm(21, 21) + +--- calc-binom --- +// Test the `binom` function. +#test(calc.binom(0, 0), 1) +#test(calc.binom(5, 3), 10) +#test(calc.binom(5, 5), 1) +#test(calc.binom(5, 6), 0) +#test(calc.binom(6, 2), 15) + +--- calc-gcd --- +// Test the `gcd` function. +#test(calc.gcd(112, 77), 7) +#test(calc.gcd(12, 96), 12) +#test(calc.gcd(13, 9), 1) +#test(calc.gcd(13, -9), 1) +#test(calc.gcd(272557, 272557), 272557) +#test(calc.gcd(0, 0), 0) +#test(calc.gcd(7, 0), 7) + +--- calc-lcm --- +// Test the `lcm` function. +#test(calc.lcm(112, 77), 1232) +#test(calc.lcm(12, 96), 96) +#test(calc.lcm(13, 9), 117) +#test(calc.lcm(13, -9), 117) +#test(calc.lcm(272557, 272557), 272557) +#test(calc.lcm(0, 0), 0) +#test(calc.lcm(8, 0), 0) + +--- calc-lcm-too-large --- +// Error: 2-41 the result is too large +#calc.lcm(15486487489457, 4874879896543) + +--- calc-min-nothing --- +// Error: 2-12 expected at least one value +#calc.min() + +--- calc-min-uncomparable --- +// Error: 14-18 cannot compare string and integer +#calc.min(1, "hi") + +--- calc-max-uncomparable --- +// Error: 16-19 cannot compare 1pt with 1em +#calc.max(1em, 1pt) diff --git a/tests/suite/foundations/content.typ b/tests/suite/foundations/content.typ new file mode 100644 index 00000000..afecc124 --- /dev/null +++ b/tests/suite/foundations/content.typ @@ -0,0 +1,120 @@ +--- content-at-default --- +// Test .at() default values for content. +#test(auto, [a].at("doesn't exist", default: auto)) + +--- content-field-syntax --- +// Test fields on elements. +#show list: it => { + test(it.children.len(), 3) +} + +- A +- B +- C + +--- content-field-missing --- +// Error: 25-28 content does not contain field "fun" +#show heading: it => it.fun += A + +--- content-fields --- +// Test content fields method. +#test([a].fields(), (text: "a")) +#test([a *b*].fields(), (children: ([a], [ ], strong[b]))) + +--- content-fields-mutable-invalid --- +#{ + let object = [hi] + // Error: 3-9 cannot mutate fields on content + object.property = "value" +} + +--- content-field-materialized-table --- +// Ensure that fields from set rules are materialized into the element before +// a show rule runs. +#set table(columns: (10pt, auto)) +#show table: it => it.columns +#table[A][B][C][D] + +--- content-field-materialized-heading --- +// Test it again with a different element. +#set heading(numbering: "(I)") +#show heading: set text(size: 11pt, weight: "regular") +#show heading: it => it.numbering += Heading + +--- content-field-materialized-query --- +// Test it with query. +#set raw(lang: "rust") +#context query().first().lang +`raw` + +--- content-fields-complex --- +// Integrated test for content fields. +#let compute(equation, ..vars) = { + let vars = vars.named() + let f(elem) = { + let func = elem.func() + if func == text { + let text = elem.text + if regex("^\d+$") in text { + int(text) + } else if text in vars { + int(vars.at(text)) + } else { + panic("unknown math variable: " + text) + } + } else if func == math.attach { + let value = f(elem.base) + if elem.has("t") { + value = calc.pow(value, f(elem.t)) + } + value + } else if elem.has("children") { + elem + .children + .filter(v => v != [ ]) + .split[+] + .map(xs => xs.fold(1, (prod, v) => prod * f(v))) + .fold(0, (sum, v) => sum + v) + } + } + let result = f(equation.body) + [With ] + vars + .pairs() + .map(p => $#p.first() = #p.last()$) + .join(", ", last: " and ") + [ we have:] + $ equation = result $ +} + +#compute($x y + y^2$, x: 2, y: 3) + +--- content-label-has-method --- +// Test whether the label is accessible through the `has` method. +#show heading: it => { + assert(it.has("label")) + it +} + += Hello, world! + +--- content-label-field-access --- +// Test whether the label is accessible through field syntax. +#show heading: it => { + assert(str(it.label) == "my-label") + it +} + += Hello, world! + +--- content-label-fields-method --- +// Test whether the label is accessible through the fields method. +#show heading: it => { + assert("label" in it.fields()) + assert(str(it.fields().label) == "my-label") + it +} + += Hello, world! diff --git a/tests/suite/foundations/context.typ b/tests/suite/foundations/context.typ new file mode 100644 index 00000000..fea9f544 --- /dev/null +++ b/tests/suite/foundations/context.typ @@ -0,0 +1,65 @@ +// Test context expressions. + +--- context-body-atomic-in-markup --- +// Test that context body is parsed as atomic expression. +#let c = [#context "hello".] +#test(c.children.first().func(), (context none).func()) +#test(c.children.last(), [.]) + +--- context-element-constructor-forbidden --- +// Test that manual construction is forbidden. +// Error: 2-25 cannot be constructed manually +#(context none).func()() + +--- context-in-show-rule --- +// Test that show rule establishes context. +#set heading(numbering: "1.") +#show heading: it => test( + counter(heading).get(), + (intro: (1,), back: (2,)).at(str(it.label)), +) + += Introduction += Background + +--- context-in-show-rule-query --- +// Test that show rule on non-locatable element allows `query`. +// Error: 18-47 Assertion failed: 2 != 3 +#show emph: _ => test(query(heading).len(), 3) +#show strong: _ => test(query(heading).len(), 2) += Introduction += Background +*Hi* _there_ + +--- context-assign-to-captured-variable --- +// Test error when captured variable is assigned to. +#let i = 0 +// Error: 11-12 variables from outside the context expression are read-only and cannot be modified +#context (i = 1) + +--- context-compatibility-locate --- +#let s = state("x", 0) +#let compute(expr) = [ + #s.update(x => + eval(expr.replace("x", str(x))) + ) + New value is #s.display(). +] + +#locate(loc => { + let elem = query(, loc).first() + test(s.at(elem.location()), 13) +}) + +#compute("10") \ +#compute("x + 3") \ +*Here.* \ +#compute("x * 2") \ +#compute("x - 5") + +--- context-compatibility-styling --- +#style(styles => measure([it], styles).width < 20pt) + +--- context-compatibility-counter-display --- +#counter(heading).update(10) +#counter(heading).display(n => test(n, 10)) diff --git a/tests/suite/foundations/datetime.typ b/tests/suite/foundations/datetime.typ new file mode 100644 index 00000000..b54c11f3 --- /dev/null +++ b/tests/suite/foundations/datetime.typ @@ -0,0 +1,93 @@ +--- datetime-constructor-empty --- +// Error: 2-12 at least one of date or time must be fully specified +#datetime() + +--- datetime-constructor-time-invalid --- +// Error: 2-42 time is invalid +#datetime(hour: 25, minute: 0, second: 0) + +--- datetime-constructor-date-invalid --- +// Error: 2-41 date is invalid +#datetime(year: 2000, month: 2, day: 30) + +--- datetime-display --- +// Test displaying of dates. +#test(datetime(year: 2023, month: 4, day: 29).display(), "2023-04-29") +#test(datetime(year: 2023, month: 4, day: 29).display("[year]"), "2023") +#test( + datetime(year: 2023, month: 4, day: 29) + .display("[year repr:last_two]"), + "23", +) +#test( + datetime(year: 2023, month: 4, day: 29) + .display("[year] [month repr:long] [day] [week_number] [weekday]"), + "2023 April 29 17 Saturday", +) + +// Test displaying of times +#test(datetime(hour: 14, minute: 26, second: 50).display(), "14:26:50") +#test(datetime(hour: 14, minute: 26, second: 50).display("[hour]"), "14") +#test( + datetime(hour: 14, minute: 26, second: 50) + .display("[hour repr:12 padding:none]"), + "2", +) +#test( + datetime(hour: 14, minute: 26, second: 50) + .display("[hour], [minute], [second]"), "14, 26, 50", +) + +// Test displaying of datetimes +#test( + datetime(year: 2023, month: 4, day: 29, hour: 14, minute: 26, second: 50).display(), + "2023-04-29 14:26:50", +) + +// Test getting the year/month/day etc. of a datetime +#let d = datetime(year: 2023, month: 4, day: 29, hour: 14, minute: 26, second: 50) +#test(d.year(), 2023) +#test(d.month(), 4) +#test(d.weekday(), 6) +#test(d.day(), 29) +#test(d.hour(), 14) +#test(d.minute(), 26) +#test(d.second(), 50) + +#let e = datetime(year: 2023, month: 4, day: 29) +#test(e.hour(), none) +#test(e.minute(), none) +#test(e.second(), none) + +// Test today +#test(datetime.today().display(), "1970-01-01") +#test(datetime.today(offset: auto).display(), "1970-01-01") +#test(datetime.today(offset: 2).display(), "1970-01-01") + +--- datetime-ordinal --- +// Test date methods. +#test(datetime(day: 1, month: 1, year: 2000).ordinal(), 1); +#test(datetime(day: 1, month: 3, year: 2000).ordinal(), 31 + 29 + 1); +#test(datetime(day: 31, month: 12, year: 2000).ordinal(), 366); +#test(datetime(day: 1, month: 3, year: 2001).ordinal(), 31 + 28 + 1); +#test(datetime(day: 31, month: 12, year: 2001).ordinal(), 365); + +--- datetime-display-missing-closing-bracket --- +// Error: 27-34 missing closing bracket for bracket at index 0 +#datetime.today().display("[year") + +--- datetime-display-invalid-component --- +// Error: 27-38 invalid component name 'nothing' at index 1 +#datetime.today().display("[nothing]") + +--- datetime-display-invalid-modifier --- +// Error: 27-50 invalid modifier 'wrong' at index 6 +#datetime.today().display("[year wrong:last_two]") + +--- datetime-display-expected-component --- +// Error: 27-33 expected component name at index 2 +#datetime.today().display(" []") + +--- datetime-display-insufficient-information --- +// Error: 2-36 failed to format datetime (insufficient information) +#datetime.today().display("[hour]") diff --git a/tests/suite/foundations/dict.typ b/tests/suite/foundations/dict.typ new file mode 100644 index 00000000..2c2d2a41 --- /dev/null +++ b/tests/suite/foundations/dict.typ @@ -0,0 +1,266 @@ +// Test dictionaries. + +--- dict-basic-syntax --- + +// Empty +#(:) + +// Two pairs and string key. +#let dict = (normal: 1, "spacy key": 2) +#dict + +#test(dict.normal, 1) +#test(dict.at("spacy key"), 2) + +--- dict-fields --- +// Test field on dictionary. +#let dict = (nothing: "ness", hello: "world") +#test(dict.nothing, "ness") +#{ + let world = dict + .hello + + test(world, "world") +} + +--- dict-missing-field --- +// Error: 6-13 dictionary does not contain key "invalid" +#(:).invalid + +--- dict-bad-key --- +// Error: 3-7 expected string, found boolean +// Error: 16-18 expected string, found integer +#(true: false, 42: 3) + +--- dict-duplicate-key --- +// Error: 24-29 duplicate key: first +#(first: 1, second: 2, first: 3) + +--- dict-duplicate-key-stringy --- +// Error: 17-20 duplicate key: a +#(a: 1, "b": 2, "a": 3) + +--- dict-bad-expression --- +// Simple expression after already being identified as a dictionary. +// Error: 9-10 expected named or keyed pair, found identifier +#(a: 1, b) + +--- dict-leading-colon --- +// Identified as dictionary due to initial colon. +// The boolean key is allowed for now since it will only cause an error at the evaluation stage. +// Error: 4-5 expected named or keyed pair, found integer +// Error: 17 expected expression +#(:1 b:"", true:) + +--- spread-into-dict --- +#{ + let x = (a: 1) + let y = (b: 2) + let z = (a: 3) + test((:..x, ..y, ..z), (a: 3, b: 2)) + test((..(a: 1), b: 2), (a: 1, b: 2)) +} + +--- spread-array-into-dict --- +// Error: 3-11 cannot spread array into dictionary +#(..(1, 2), a: 1) + +--- dict-at-lvalue --- +// Test lvalue and rvalue access. +#{ + let dict = (a: 1, "b b": 1) + dict.at("b b") += 1 + dict.state = (ok: true, err: false) + test(dict, (a: 1, "b b": 2, state: (ok: true, err: false))) + test(dict.state.ok, true) + dict.at("state").ok = false + test(dict.state.ok, false) + test(dict.state.err, false) +} + +--- dict-at-missing-key --- +// Test rvalue missing key. +#{ + let dict = (a: 1, b: 2) + // Error: 11-23 dictionary does not contain key "c" and no default value was specified + let x = dict.at("c") +} + +--- dict-at-default --- +// Test default value. +#test((a: 1, b: 2).at("b", default: 3), 2) +#test((a: 1, b: 2).at("c", default: 3), 3) + +--- dict-insert --- +// Test insert. +#{ + let dict = (a: 1, b: 2) + dict.insert("b", 3) + test(dict, (a: 1, b: 3)) + dict.insert("c", 5) + test(dict, (a: 1, b: 3, c: 5)) +} + +--- dict-remove-with-default --- +// Test remove with default value. +#{ + let dict = (a: 1, b: 2) + test(dict.remove("b", default: 3), 2) +} + +#{ + let dict = (a: 1, b: 2) + test(dict.remove("c", default: 3), 3) +} + +--- dict-missing-lvalue --- +// Missing lvalue is not automatically none-initialized. +#{ + let dict = (:) + // Error: 3-9 dictionary does not contain key "b" + // Hint: 3-9 use `insert` to add or update values + dict.b += 1 +} + +--- dict-basic-methods --- +// Test dictionary methods. +#let dict = (a: 3, c: 2, b: 1) +#test("c" in dict, true) +#test(dict.len(), 3) +#test(dict.values(), (3, 2, 1)) +#test(dict.pairs().map(p => p.first() + str(p.last())).join(), "a3c2b1") + +#dict.remove("c") +#test("c" in dict, false) +#test(dict, (a: 3, b: 1)) + +--- dict-from-module --- +// Test dictionary constructor +#dictionary(sys).at("version") +#dictionary(sys).at("no-crash", default: none) + +--- dict-remove-order --- +// Test that removal keeps order. +#let dict = (a: 1, b: 2, c: 3, d: 4) +#dict.remove("b") +#test(dict.keys(), ("a", "c", "d")) + +--- dict-temporary-lvalue --- +// Error: 3-15 cannot mutate a temporary value +#((key: "val").other = "some") + +--- dict-function-item-not-a-method --- +#{ + let dict = ( + call-me: () => 1, + ) + // Error: 8-15 type dictionary has no method `call-me` + // Hint: 8-15 to call the function stored in the dictionary, surround the field access with parentheses, e.g. `(dict.call-me)(..)` + dict.call-me() +} + +--- dict-item-missing-method --- +#{ + let dict = ( + nonfunc: 1 + ) + + // Error: 8-15 type dictionary has no method `nonfunc` + // Hint: 8-15 did you mean to access the field `nonfunc`? + dict.nonfunc() +} + +--- dict-dynamic-uplicate-key --- +#let a = "hello" +#let b = "world" +#let c = "value" +#let d = "conflict" + +#assert.eq(((a): b), ("hello": "world")) +#assert.eq(((a): 1, (a): 2), ("hello": 2)) +#assert.eq((hello: 1, (a): 2), ("hello": 2)) +#assert.eq((a + b: c, (a + b): d, (a): "value2", a: "value3"), ("helloworld": "conflict", "hello": "value2", "a": "value3")) + +--- issue-1338-dictionary-underscore --- +#let foo = "foo" +#let bar = "bar" +// Error: 8-9 expected expression, found underscore +// Error: 16-17 expected expression, found underscore +#(foo: _, bar: _) + +--- issue-1342-dictionary-bare-expressions --- +// Error: 5-8 expected named or keyed pair, found identifier +// Error: 10-13 expected named or keyed pair, found identifier +#(: foo, bar) + +--- issue-3154-dict-at-not-contained --- +#{ + let dict = (a: 1) + // Error: 3-15 dictionary does not contain key "b" and no default value was specified + dict.at("b") +} + +--- issue-3154-dict-at-missing-default --- +#{ + let dict = (a: 1) + test(dict.at("b", default: 0), 0) +} + +--- issue-3154-dict-at-missing-mutable --- +#{ + let dict = (a: 1) + // Error: 3-15 dictionary does not contain key "b" + // Hint: 3-15 use `insert` to add or update values + dict.at("b") = 9 +} + +--- issue-3154-dict-at-missing-mutable-default --- +#{ + let dict = (a: 1) + // Error: 3-27 dictionary does not contain key "b" + // Hint: 3-27 use `insert` to add or update values + dict.at("b", default: 0) = 9 +} + +--- issue-3154-dict-syntax-missing --- +#{ + let dict = (a: 1) + // Error: 8-9 dictionary does not contain key "b" + dict.b +} + +--- issue-3154-dict-syntax-missing-mutable --- +#{ + let dict = (a: 1) + dict.b = 9 + test(dict, (a: 1, b: 9)) +} + +--- issue-3154-dict-syntax-missing-add-assign --- +#{ + let dict = (a: 1) + // Error: 3-9 dictionary does not contain key "b" + // Hint: 3-9 use `insert` to add or update values + dict.b += 9 +} + +--- issue-3232-dict-unexpected-keys-sides --- +// Confusing "expected relative length or dictionary, found dictionary" +// Error: 16-58 unexpected keys "unexpected" and "unexpected-too" +#block(outset: (unexpected: 0.5em, unexpected-too: 0.2em), [Hi]) + +--- issue-3232-dict-unexpected-keys-corners --- +// Error: 14-56 unexpected keys "unexpected" and "unexpected-too" +#box(radius: (unexpected: 0.5em, unexpected-too: 0.5em), [Hi]) + +--- issue-3232-dict-unexpected-key-sides --- +// Error: 16-49 unexpected key "unexpected", valid keys are "left", "top", "right", "bottom", "x", "y", and "rest" +#block(outset: (unexpected: 0.2em, right: 0.5em), [Hi]) // The 1st key is unexpected + +--- issue-3232-dict-unexpected-key-corners --- +// Error: 14-50 unexpected key "unexpected", valid keys are "top-left", "top-right", "bottom-right", "bottom-left", "left", "top", "right", "bottom", and "rest" +#box(radius: (top-left: 0.5em, unexpected: 0.5em), [Hi]) // The 2nd key is unexpected + +--- issue-3232-dict-empty --- +#block(outset: (:), [Hi]) // Ok +#box(radius: (:), [Hi]) // Ok diff --git a/tests/suite/foundations/duration.typ b/tests/suite/foundations/duration.typ new file mode 100644 index 00000000..7e53f703 --- /dev/null +++ b/tests/suite/foundations/duration.typ @@ -0,0 +1,103 @@ +// Test durations. + +--- duration-negate --- +// Test negating durations. +#test(-duration(hours: 2), duration(hours: -2)) + +--- duration-add-and-subtract --- +// Test adding and subtracting durations. +#test(duration(weeks: 1, hours: 1), duration(weeks: 1) + duration(hours: 1)) +#test(duration(weeks: 1, hours: -1), duration(weeks: 1) - duration(hours: 1)) +#test(duration(days: 6, hours: 23), duration(weeks: 1) - duration(hours: 1)) + +--- duration-add-and-subtract-dates --- +// Test adding and subtracting durations and dates. +#let d = datetime(day: 1, month: 1, year: 2000) +#let d2 = datetime(day: 1, month: 2, year: 2000) +#test(d + duration(weeks: 2), datetime(day: 15, month: 1, year: 2000)) +#test(d + duration(days: 3), datetime(day: 4, month: 1, year: 2000)) +#test(d + duration(weeks: 1, days: 3), datetime(day: 11, month: 1, year: 2000)) +#test(d2 + duration(days: -1), datetime(day: 31, month: 1, year: 2000)) +#test(d2 + duration(days: -3), datetime(day: 29, month: 1, year: 2000)) +#test(d2 + duration(weeks: -1), datetime(day: 25, month: 1, year: 2000)) +#test(d + duration(days: -1), datetime(day: 31, month: 12, year: 1999)) +#test(d + duration(weeks: 1, days: -7), datetime(day: 1, month: 1, year: 2000)) +#test(d2 - duration(days: 1), datetime(day: 31, month: 1, year: 2000)) +#test(d2 - duration(days: 3), datetime(day: 29, month: 1, year: 2000)) +#test(d2 - duration(weeks: 1), datetime(day: 25, month: 1, year: 2000)) +#test(d - duration(days: 1), datetime(day: 31, month: 12, year: 1999)) +#test(datetime(day: 31, month: 1, year: 2000) + duration(days: 1), d2) +#test( + datetime(day: 31, month: 12, year: 2000) + duration(days: 1), + datetime(day: 1, month: 1, year: 2001), +) + +--- duration-add-and-subtract-times --- +// Test adding and subtracting durations and times. +#let a = datetime(hour: 12, minute: 0, second: 0) +#test(a + duration(hours: 1, minutes: -60), datetime(hour: 12, minute: 0, second: 0)) +#test(a + duration(hours: 2), datetime(hour: 14, minute: 0, second: 0)) +#test(a + duration(minutes: 10), datetime(hour: 12, minute: 10, second: 0)) +#test(a + duration(seconds: 30), datetime(hour: 12, minute: 0, second: 30)) +#test(a + duration(hours: -2), datetime(hour: 10, minute: 0, second: 0)) +#test(a - duration(hours: 2), datetime(hour: 10, minute: 0, second: 0)) +#test(a + duration(minutes: -10), datetime(hour: 11, minute: 50, second: 0)) +#test(a - duration(minutes: 10), datetime(hour: 11, minute: 50, second: 0)) +#test(a + duration(seconds: -30), datetime(hour: 11, minute: 59, second: 30)) +#test(a - duration(seconds: 30), datetime(hour: 11, minute: 59, second: 30)) +#test( + a + duration(hours: 1, minutes: 13, seconds: 13), + datetime(hour: 13, minute: 13, second: 13), +) + +--- duration-add-and-subtract-datetimes --- +// Test adding and subtracting durations and datetimes. +#test( + datetime(day: 1, month: 1, year: 2000, hour: 12, minute: 0, second: 0) + + duration(weeks: 1, days: 3, hours: -13, minutes: 10, seconds: -10 ), + datetime(day: 10, month: 1, year: 2000, hour: 23, minute: 9, second: 50), +) +#test( + datetime(day: 1, month: 1, year: 2000, hour: 12, minute: 0, second: 0) + + duration(weeks: 1, days: 3, minutes: 10) + - duration(hours: 13, seconds: 10), + datetime(day: 10, month: 1, year: 2000, hour: 23, minute: 9, second: 50), +) + +--- duration-from-date-subtraction --- +// Test subtracting dates. +#let a = datetime(hour: 12, minute: 0, second: 0) +#let b = datetime(day: 1, month: 1, year: 2000) +#test(datetime(hour: 14, minute: 0, second: 0) - a, duration(hours: 2)) +#test(datetime(hour: 14, minute: 0, second: 0) - a, duration(minutes: 120)) +#test(datetime(hour: 13, minute: 0, second: 0) - a, duration(seconds: 3600)) +#test(datetime(day: 1, month: 2, year: 2000) - b, duration(days: 31)) +#test(datetime(day: 15, month: 1, year: 2000) - b, duration(weeks: 2)) + +--- duration-multiply-with-number --- +// Test multiplying and dividing durations with numbers. +#test(duration(minutes: 10) * 6, duration(hours: 1)) +#test(duration(minutes: 10) * 2, duration(minutes: 20)) +#test(duration(minutes: 10) * 2.5, duration(minutes: 25)) +#test(duration(minutes: 10) / 2, duration(minutes: 5)) +#test(duration(minutes: 10) / 2.5, duration(minutes: 4)) + +--- duration-divide --- +// Test dividing durations with durations +#test(duration(minutes: 20) / duration(hours: 1), 1 / 3) +#test(duration(minutes: 20) / duration(minutes: 10), 2) +#test(duration(minutes: 20) / duration(minutes: 8), 2.5) + +--- duration-compare --- +// Test comparing durations +#test(duration(minutes: 20) > duration(minutes: 10), true) +#test(duration(minutes: 20) >= duration(minutes: 10), true) +#test(duration(minutes: 10) < duration(minutes: 20), true) +#test(duration(minutes: 10) <= duration(minutes: 20), true) +#test(duration(minutes: 10) == duration(minutes: 10), true) +#test(duration(minutes: 10) != duration(minutes: 20), true) +#test(duration(minutes: 10) <= duration(minutes: 10), true) +#test(duration(minutes: 10) >= duration(minutes: 10), true) +#test(duration(minutes: 20) < duration(minutes: 10), false) +#test(duration(minutes: 20) <= duration(minutes: 10), false) +#test(duration(minutes: 20) == duration(minutes: 10), false) diff --git a/tests/suite/foundations/eval.typ b/tests/suite/foundations/eval.typ new file mode 100644 index 00000000..f85146b2 --- /dev/null +++ b/tests/suite/foundations/eval.typ @@ -0,0 +1,54 @@ +--- eval --- +// Test the eval function. +#test(eval("1 + 2"), 3) +#test(eval("1 + x", scope: (x: 3)), 4) +#test(eval("let x = x + 1; x + 1", scope: (x: 1)), 3) + +--- eval-mode --- +// Test evaluation in other modes. +#eval("[_Hello" + " World!_]") \ +#eval("_Hello" + " World!_", mode: "markup") \ +#eval("RR_1^NN", mode: "math", scope: (RR: math.NN, NN: math.RR)) + +--- eval-syntax-error-1 --- +// Error: 7-12 expected pattern +#eval("let") + +--- eval-in-show-rule --- +#show raw: it => text(font: "PT Sans", eval("[" + it.text + "]")) + +Interacting +``` +#set text(blue) +Blue #move(dy: -0.15em)[🌊] +``` + +--- eval-runtime-error --- +// Error: 7-17 cannot continue outside of loop +#eval("continue") + +--- eval-syntax-error-2 --- +// Error: 7-12 expected semicolon or line break +#eval("1 2") + +--- eval-path-resolve --- +// Test absolute path. +#eval("image(\"/assets/images/tiger.jpg\", width: 50%)") + +--- eval-path-resolve-in-show-rule --- +#show raw: it => eval(it.text, mode: "markup") + +``` +#show emph: image("/assets/images/tiger.jpg", width: 50%) +_Tiger!_ +``` + +--- eval-path-resolve-relative --- +// Test relative path. +#test(eval(`"HELLO" in read("./eval.typ")`.text), true) + +--- issue-2055-math-eval --- +// Evaluating a math expr should renders the same as an equation +#eval(mode: "math", "f(a) = cases(a + b\, space space x >= 3,a + b\, space space x = 5)") + +$f(a) = cases(a + b\, space space x >= 3,a + b\, space space x = 5)$ diff --git a/tests/suite/foundations/float.typ b/tests/suite/foundations/float.typ new file mode 100644 index 00000000..770533b9 --- /dev/null +++ b/tests/suite/foundations/float.typ @@ -0,0 +1,66 @@ +--- float-constructor --- +#test(float(10), 10.0) +#test(float(50% * 30%), 0.15) +#test(float("31.4e-1"), 3.14) +#test(float("31.4e\u{2212}1"), 3.14) +#test(float("3.1415"), 3.1415) +#test(float("-7654.321"), -7654.321) +#test(float("\u{2212}7654.321"), -7654.321) +#test(type(float(10)), float) + +--- float-constructor-bad-type --- +// Error: 8-13 expected float, boolean, integer, ratio, or string, found type +#float(float) + +--- float-constructor-bad-value --- +// Error: 8-15 invalid float: 1.2.3 +#float("1.2.3") + +--- float-is-nan --- +// Test float `is-nan()`. +#test(float(calc.nan).is-nan(), true) +#test(float(10).is-nan(), false) + +--- float-is-infinite --- +// Test float `is-infinite()`. +#test(float(calc.inf).is-infinite(), true) +#test(float(-calc.inf).is-infinite(), true) +#test(float(10).is-infinite(), false) +#test(float(-10).is-infinite(), false) + +--- float-signum --- +// Test float `signum()` +#test(float(0.0).signum(), 1.0) +#test(float(1.0).signum(), 1.0) +#test(float(-1.0).signum(), -1.0) +#test(float(10.0).signum(), 1.0) +#test(float(-10.0).signum(), -1.0) +#test(float(calc.nan).signum().is-nan(), true) + +--- float-repr --- +// Test the `repr` function with floats. +#repr(12.0) \ +#repr(3.14) \ +#repr(1234567890.0) \ +#repr(0123456789.0) \ +#repr(0.0) \ +#repr(-0.0) \ +#repr(-1.0) \ +#repr(-9876543210.0) \ +#repr(-0987654321.0) \ +#repr(-3.14) \ +#repr(4.0 - 8.0) + +--- float-display --- +// Test floats. +#12.0 \ +#3.14 \ +#1234567890.0 \ +#0123456789.0 \ +#0.0 \ +#(-0.0) \ +#(-1.0) \ +#(-9876543210.0) \ +#(-0987654321.0) \ +#(-3.14) \ +#(4.0 - 8.0) diff --git a/tests/suite/foundations/int.typ b/tests/suite/foundations/int.typ new file mode 100644 index 00000000..0c85dcab --- /dev/null +++ b/tests/suite/foundations/int.typ @@ -0,0 +1,73 @@ +--- int-base-alternative --- +// Test numbers with alternative bases. +#test(0x10, 16) +#test(0b1101, 13) +#test(0xA + 0xa, 0x14) + +--- int-base-binary-invalid --- +// Error: 2-7 invalid binary number: 0b123 +#0b123 + +--- int-base-hex-invalid --- +// Error: 2-8 invalid hexadecimal number: 0x123z +#0x123z + +--- int-constructor --- +// Test conversion to numbers. +#test(int(false), 0) +#test(int(true), 1) +#test(int(10), 10) +#test(int("150"), 150) +#test(int("-834"), -834) +#test(int("\u{2212}79"), -79) +#test(int(10 / 3), 3) + +--- int-constructor-bad-type --- +// Error: 6-10 expected integer, boolean, float, or string, found length +#int(10pt) + +--- int-constructor-bad-value --- +// Error: 6-12 invalid integer: nope +#int("nope") + +--- int-signum --- +// Test int `signum()` +#test(int(0).signum(), 0) +#test(int(1.0).signum(), 1) +#test(int(-1.0).signum(), -1) +#test(int(10.0).signum(), 1) +#test(int(-10.0).signum(), -1) + +--- int-repr --- +// Test the `repr` function with integers. +#repr(12) \ +#repr(1234567890) \ +#repr(0123456789) \ +#repr(0) \ +#repr(-0) \ +#repr(-1) \ +#repr(-9876543210) \ +#repr(-0987654321) \ +#repr(4 - 8) + +--- int-display --- +// Test integers. +#12 \ +#1234567890 \ +#0123456789 \ +#0 \ +#(-0) \ +#(-1) \ +#(-9876543210) \ +#(-0987654321) \ +#(4 - 8) + +--- issue-int-constructor --- +// Test that integer -> integer conversion doesn't do a roundtrip through float. +#let x = 9223372036854775800 +#test(type(x), int) +#test(int(x), x) + +--- number-invalid-suffix --- +// Error: 2-4 invalid number suffix: u +#1u diff --git a/tests/suite/foundations/label.typ b/tests/suite/foundations/label.typ new file mode 100644 index 00000000..2cde102c --- /dev/null +++ b/tests/suite/foundations/label.typ @@ -0,0 +1,70 @@ +// Test labels. + +--- label-show-where-selector --- +// Test labelled headings. +#show heading: set text(10pt) +#show heading.where(label: ): underline + += Introduction +The beginning. + += Conclusion +The end. + +--- label-after-expression --- +// Test label after expression. +#show strong.where(label: ): set text(red) + +#let a = [*A*] +#let b = [*B*] +#a #b + +--- label-on-text --- +// Test labelled text. +#show "t": it => { + set text(blue) if it.has("label") and it.label == + it +} + +This is a thing #[that ] happened. + +--- label-dynamic-show-set --- +// Test abusing dynamic labels for styling. +#show : set text(red) +#show : set text(blue) + +*A* *B* *C* #label("bl" + "ue") *D* + +--- label-after-parbreak --- +// Test that label ignores parbreak. +#show : none + +_Hidden_ + + +_Hidden_ + + +_Visible_ + +--- label-in-block --- +// Test that label only works within one content block. +#show : strike +*This is* #[] *protected.* +*This is not.* + +--- label-unclosed-is-text --- +// Test that incomplete label is text. +1 < 2 is #if 1 < 2 [not] a label. + +--- label-text-styled-and-sequence --- +// Test label on text, styled, and sequence. +#test([Hello].label, ) +#test([#[A *B* C]].label, ) +#test([#text(red)[Hello]].label, ) + +--- label-string-conversion --- +// Test getting the name of a label. +#test(str(), "hey") +#test(str(label("hey")), "hey") +#test(str([Hmm].label), "hey") diff --git a/tests/suite/foundations/panic.typ b/tests/suite/foundations/panic.typ new file mode 100644 index 00000000..5d9d4046 --- /dev/null +++ b/tests/suite/foundations/panic.typ @@ -0,0 +1,14 @@ +--- panic --- +// Test panic. +// Error: 2-9 panicked +#panic() + +--- panic-with-int --- +// Test panic. +// Error: 2-12 panicked with: 123 +#panic(123) + +--- panic-with-str --- +// Test panic. +// Error: 2-24 panicked with: "this is wrong" +#panic("this is wrong") diff --git a/tests/suite/foundations/plugin.typ b/tests/suite/foundations/plugin.typ new file mode 100644 index 00000000..0842980e --- /dev/null +++ b/tests/suite/foundations/plugin.typ @@ -0,0 +1,47 @@ +// Test WebAssembly plugins. + +--- plugin-basic --- +#let p = plugin("/assets/plugins/hello.wasm") +#test(p.hello(), bytes("Hello from wasm!!!")) +#test(p.double_it(bytes("hey!")), bytes("hey!.hey!")) +#test( + p.shuffle(bytes("value1"), bytes("value2"), bytes("value3")), + bytes("value3-value1-value2"), +) + +--- plugin-wrong-number-of-arguments --- +#let p = plugin("/assets/plugins/hello.wasm") + +// Error: 2-20 plugin function takes 0 arguments, but 1 was given +#p.hello(bytes("")) + +--- plugin-wrong-argument-type --- +#let p = plugin("/assets/plugins/hello.wasm") + +// Error: 10-14 expected bytes, found boolean +// Error: 27-29 expected bytes, found integer +#p.hello(true, bytes(()), 10) + +--- plugin-error --- +#let p = plugin("/assets/plugins/hello.wasm") + +// Error: 2-17 plugin errored with: This is an `Err` +#p.returns_err() + +--- plugin-panic --- +#let p = plugin("/assets/plugins/hello.wasm") + +// Error: 2-16 plugin panicked: wasm `unreachable` instruction executed +#p.will_panic() + +--- plugin-out-of-bounds-read --- +#let p = plugin("/assets/plugins/plugin-oob.wasm") + +// Error: 2-14 plugin tried to read out of bounds: pointer 0x40000000 is out of bounds for read of length 1 +#p.read_oob() + +--- plugin-out-of-bounds-write --- +#let p = plugin("/assets/plugins/plugin-oob.wasm") + +// Error: 2-27 plugin tried to write out of bounds: pointer 0x40000000 is out of bounds for write of length 3 +#p.write_oob(bytes("xyz")) diff --git a/tests/suite/foundations/repr.typ b/tests/suite/foundations/repr.typ new file mode 100644 index 00000000..7f03209b --- /dev/null +++ b/tests/suite/foundations/repr.typ @@ -0,0 +1,57 @@ +--- repr --- +#test(repr(ltr), "ltr") +#test(repr((1, 2, false, )), "(1, 2, false)") + +--- repr-literals --- +// Literal values. +#auto \ +#none (empty) \ +#true \ +#false + +--- repr-numerical --- +// Numerical values. +#1 \ +#1.0e-4 \ +#3.15 \ +#1e-10 \ +#50.368% \ +#0.0000012345pt \ +#4.5cm \ +#12e1pt \ +#2.5rad \ +#45deg \ +#1.7em \ +#(1cm + 0em) \ +#(2em + 10pt) \ +#(100% + (2em + 2pt)) \ +#(100% + 0pt) \ +#(100% - 2em + 2pt) \ +#(100% - 2pt) \ +#2.3fr + +--- repr-misc --- +// Colors and strokes. +#set text(0.8em) +#rgb("f7a205") \ +#(2pt + rgb("f7a205")) + +// Strings and escaping. +#raw(repr("hi"), lang: "typc") +#repr("a\n[]\"\u{1F680}string") + +// Content. +#raw(lang: "typc", repr[*Hey*]) \ +#raw(lang: "typc", repr[A _sequence_]) \ +#raw(lang: "typc", repr[A _longer_ *sequence*!]) + +// Functions. +#let f(x) = x +#f \ +#rect \ +#(() => none) + +// Types. +#int \ +#type("hi") \ +#type((a: 1)) diff --git a/tests/suite/foundations/str.typ b/tests/suite/foundations/str.typ new file mode 100644 index 00000000..025ec53d --- /dev/null +++ b/tests/suite/foundations/str.typ @@ -0,0 +1,315 @@ +// Test the string methods. + +--- str-constructor --- +// Test conversion to string. +#test(str(123), "123") +#test(str(123, base: 3), "11120") +#test(str(-123, base: 16), "−7b") +#test(str(9223372036854775807, base: 36), "1y2p0ij32e8e7") +#test(str(50.14), "50.14") +#test(str(10 / 3).len() > 10, true) + +--- str-from-float --- +// Test the `str` function with floats. +#test(str(12.0), "12") +#test(str(3.14), "3.14") +#test(str(1234567890.0), "1234567890") +#test(str(0123456789.0), "123456789") +#test(str(0.0), "0") +#test(str(-0.0), "0") +#test(str(-1.0), "−1") +#test(str(-9876543210.0), "−9876543210") +#test(str(-0987654321.0), "−987654321") +#test(str(-3.14), "−3.14") +#test(str(4.0 - 8.0), "−4") + +--- str-from-int --- +// Test the `str` function with integers. +#test(str(12), "12") +#test(str(1234567890), "1234567890") +#test(str(0123456789), "123456789") +#test(str(0), "0") +#test(str(-0), "0") +#test(str(-1), "−1") +#test(str(-9876543210), "−9876543210") +#test(str(-0987654321), "−987654321") +#test(str(4 - 8), "−4") + +--- str-constructor-bad-type --- +// Error: 6-8 expected integer, float, version, bytes, label, type, or string, found content +#str([]) + +--- str-constructor-bad-base --- +// Error: 17-19 base must be between 2 and 36 +#str(123, base: 99) + +--- str-constructor-unsupported-base --- +// Error: 18-19 base is only supported for integers +#str(1.23, base: 2) + +--- str-from-and-to-unicode --- +// Test the unicode function. +#test(str.from-unicode(97), "a") +#test(str.to-unicode("a"), 97) + +--- str-from-unicode-bad-type --- +// Error: 19-22 expected integer, found content +#str.from-unicode([a]) + +--- str-to-unicode-bad-type --- +// Error: 17-21 expected exactly one character +#str.to-unicode("ab") + +--- str-from-unicode-negative --- +// Error: 19-21 number must be at least zero +#str.from-unicode(-1) + +--- str-from-unicode-bad-value --- +// Error: 2-28 0x110000 is not a valid codepoint +#str.from-unicode(0x110000) // 0x10ffff is the highest valid code point + +--- string-len --- +// Test the `len` method. +#test("Hello World!".len(), 12) + +--- string-first-and-last --- +// Test the `first` and `last` methods. +#test("Hello".first(), "H") +#test("Hello".last(), "o") +#test("🏳️‍🌈A🏳️‍⚧️".first(), "🏳️‍🌈") +#test("🏳️‍🌈A🏳️‍⚧️".last(), "🏳️‍⚧️") + +--- string-first-empty --- +// Error: 2-12 string is empty +#"".first() + +--- string-last-empty --- +// Error: 2-11 string is empty +#"".last() + +--- string-at --- +// Test the `at` method. +#test("Hello".at(1), "e") +#test("Hello".at(4), "o") +#test("Hello".at(-1), "o") +#test("Hello".at(-2), "l") +#test("Hey: 🏳️‍🌈 there!".at(5), "🏳️‍🌈") + +--- string-at-default --- +// Test `at`'s 'default' parameter. +#test("z", "Hello".at(5, default: "z")) + +--- string-at-not-a-char-boundary --- +// Error: 2-14 string index 2 is not a character boundary +#"🏳️‍🌈".at(2) + +--- string-at-out-of-bounds --- +// Error: 2-15 no default value was specified and string index out of bounds (index: 5, len: 5) +#"Hello".at(5) + +--- string-at-at-default-other-type --- +#test("Hello".at(5, default: (a: 10)), (a: 10)) + +--- string-slice --- +// Test the `slice` method. +#test("abc".slice(1, 2), "b") +#test("abc🏡def".slice(2, 7), "c🏡") +#test("abc🏡def".slice(2, -2), "c🏡d") +#test("abc🏡def".slice(-3, -1), "de") + +--- string-slice-not-a-char-boundary --- +// Error: 2-21 string index -1 is not a character boundary +#"🏳️‍🌈".slice(0, -1) + +--- string-clusters --- +// Test the `clusters` and `codepoints` methods. +#test("abc".clusters(), ("a", "b", "c")) +#test("abc".clusters(), ("a", "b", "c")) +#test("🏳️‍🌈!".clusters(), ("🏳️‍🌈", "!")) + +--- string-codepoints --- +#test("🏳️‍🌈!".codepoints(), ("🏳", "\u{fe0f}", "\u{200d}", "🌈", "!")) + +--- string-contains --- +// Test the `contains` method. +#test("abc".contains("b"), true) +#test("b" in "abc", true) +#test("1234f".contains(regex("\d")), true) +#test(regex("\d") in "1234f", true) +#test("abc".contains("d"), false) +#test("1234g" in "1234f", false) +#test("abc".contains(regex("^[abc]$")), false) +#test("abc".contains(regex("^[abc]+$")), true) + +--- string-starts-with --- +// Test the `starts-with` and `ends-with` methods. +#test("Typst".starts-with("Ty"), true) +#test("Typst".starts-with(regex("[Tt]ys")), false) +#test("Typst".starts-with("st"), false) + +--- string-ends-with --- +#test("Typst".ends-with("st"), true) +#test("Typst".ends-with(regex("\d*")), true) +#test("Typst".ends-with(regex("\d+")), false) +#test("Typ12".ends-with(regex("\d+")), true) +#test("typst13".ends-with(regex("1[0-9]")), true) +#test("typst113".ends-with(regex("1[0-9]")), true) +#test("typst23".ends-with(regex("1[0-9]")), false) + +--- string-find-and-position --- +// Test the `find` and `position` methods. +#let date = regex("\d{2}:\d{2}") +#test("Hello World".find("World"), "World") +#test("Hello World".position("World"), 6) +#test("It's 12:13 now".find(date), "12:13") +#test("It's 12:13 now".position(date), 5) + +--- string-match --- +// Test the `match` method. +#test("Is there a".match("for this?"), none) +#test( + "The time of my life.".match(regex("[mit]+e")), + (start: 4, end: 8, text: "time", captures: ()), +) + +--- string-matches --- +// Test the `matches` method. +#test("Hello there".matches("\d"), ()) +#test("Day by Day.".matches("Day"), ( + (start: 0, end: 3, text: "Day", captures: ()), + (start: 7, end: 10, text: "Day", captures: ()), +)) + +// Compute the sum of all timestamps in the text. +#let timesum(text) = { + let time = 0 + for match in text.matches(regex("(\d+):(\d+)")) { + let caps = match.captures + time += 60 * int(caps.at(0)) + int(caps.at(1)) + } + str(int(time / 60)) + ":" + str(calc.rem(time, 60)) +} + +#test(timesum(""), "0:0") +#test(timesum("2:70"), "3:10") +#test(timesum("1:20, 2:10, 0:40"), "4:10") + +--- stgring-replace --- +// Test the `replace` method with `Str` replacements. +#test("ABC".replace("", "-"), "-A-B-C-") +#test("Ok".replace("Ok", "Nope", count: 0), "Ok") +#test("to add?".replace("", "How ", count: 1), "How to add?") +#test("AB C DEF GH J".replace(" ", ",", count: 2), "AB,C,DEF GH J") +#test("Walcemo" + .replace("o", "k") + .replace("e", "o") + .replace("k", "e") + .replace("a", "e"), + "Welcome" +) +#test("123".replace(regex("\d$"), "_"), "12_") +#test("123".replace(regex("\d{1,2}$"), "__"), "1__") + +--- string-replace-function --- +// Test the `replace` method with `Func` replacements. + +#test("abc".replace(regex("[a-z]"), m => { + str(m.start) + m.text + str(m.end) +}), "0a11b22c3") +#test("abcd, efgh".replace(regex("\w+"), m => { + upper(m.text) +}), "ABCD, EFGH") +#test("hello : world".replace(regex("^(.+)\s*(:)\s*(.+)$"), m => { + upper(m.captures.at(0)) + m.captures.at(1) + " " + upper(m.captures.at(2)) +}), "HELLO : WORLD") +#test("hello world, lorem ipsum".replace(regex("(\w+) (\w+)"), m => { + m.captures.at(1) + " " + m.captures.at(0) +}), "world hello, ipsum lorem") +#test("hello world, lorem ipsum".replace(regex("(\w+) (\w+)"), count: 1, m => { + m.captures.at(1) + " " + m.captures.at(0) +}), "world hello, lorem ipsum") +#test("123 456".replace(regex("[a-z]+"), "a"), "123 456") + +#test("abc".replace("", m => "-"), "-a-b-c-") +#test("abc".replace("", m => "-", count: 1), "-abc") +#test("123".replace("abc", m => ""), "123") +#test("123".replace("abc", m => "", count: 2), "123") +#test("a123b123c".replace("123", m => { + str(m.start) + "-" + str(m.end) +}), "a1-4b5-8c") +#test("halla warld".replace("a", m => { + if m.start == 1 { "e" } + else if m.start == 4 or m.start == 7 { "o" } +}), "hello world") +#test("aaa".replace("a", m => str(m.captures.len())), "000") + +--- string-replace-function-bad-type --- +// Error: 23-24 expected string, found integer +#"123".replace("123", m => 1) + +--- string-replace-bad-type --- +// Error: 23-32 expected string or function, found array +#"123".replace("123", (1, 2, 3)) + +--- string-trim-basic --- +// Test the `trim` method; the pattern is not provided. +#let str = "Typst, LaTeX, Word, InDesign" +#let array = ("Typst", "LaTeX", "Word", "InDesign") +#test(str.split(",").map(s => s.trim()), array) +#test("".trim(), "") +#test(" ".trim(), "") +#test("\t".trim(), "") +#test("\n".trim(), "") +#test("\t \n".trim(), "") +#test(" abc ".trim(at: start), "abc ") +#test("\tabc ".trim(at: start), "abc ") +#test("abc\n".trim(at: end), "abc") +#test(" abc ".trim(at: end, repeat: true), " abc") +#test(" abc".trim(at: start, repeat: false), "abc") + +--- string-trim-pattern-str --- +// Test the `trim` method; the pattern is a string. +#test("aabcaa".trim("a", repeat: false), "abca") +#test("aabca".trim("a", at: start), "bca") +#test("aabcaa".trim("a", at: end, repeat: false), "aabca") +#test(" abc\n".trim("\n"), " abc") +#test("whole".trim("whole", at: start), "") + +--- string-trim-pattern-regex --- +// Test the `trim` method; the pattern is a regex. +#test("".trim(regex(".")), "") +#test("123abc456".trim(regex("\d")), "abc") +#test("123abc456".trim(regex("\d"), repeat: false), "23abc45") +#test("123a4b5c678".trim(regex("\d"), repeat: true), "a4b5c") +#test("123a4b5c678".trim(regex("\d"), repeat: false), "23a4b5c67") +#test("123abc456".trim(regex("\d"), at: start), "abc456") +#test("123abc456".trim(regex("\d"), at: end), "123abc") +#test("123abc456".trim(regex("\d+"), at: end, repeat: false), "123abc") +#test("123abc456".trim(regex("\d{1,2}$"), repeat: false), "123abc4") +#test("hello world".trim(regex(".")), "") +#test("12306".trim(regex("\d"), at: start), "") +#test("12306abc".trim(regex("\d"), at: start), "abc") +#test("whole".trim(regex("whole"), at: start), "") +#test("12306".trim(regex("\d"), at: end), "") +#test("abc12306".trim(regex("\d"), at: end), "abc") +#test("whole".trim(regex("whole"), at: end), "") + +--- string-trim-at-bad-alignment --- +// Error: 17-21 expected either `start` or `end` +#"abc".trim(at: left) + +--- string-split --- +// Test the `split` method. +#test("abc".split(""), ("", "a", "b", "c", "")) +#test("abc".split("b"), ("a", "c")) +#test("a123c".split(regex("\d")), ("a", "", "", "c")) +#test("a123c".split(regex("\d+")), ("a", "c")) + +--- string-rev --- +// Test the `rev` method. +#test("abc".rev(), "cba") +#test("ax̂e".rev(), "ex̂a") + +--- string-unclosed --- +// Error: 2-2:1 unclosed string +#"hello\" diff --git a/tests/suite/foundations/type.typ b/tests/suite/foundations/type.typ new file mode 100644 index 00000000..f2a98845 --- /dev/null +++ b/tests/suite/foundations/type.typ @@ -0,0 +1,25 @@ +--- type --- +#test(type(1), int) +#test(type(ltr), direction) +#test(type(10 / 3), float) + +--- type-string-compatibility --- +#test(type(10), int) +#test(type(10), "integer") +#test("is " + type(10), "is integer") +#test(int in ("integer", "string"), true) +#test(int in "integers or strings", true) +#test(str in "integers or strings", true) + +--- issue-3110-type-constructor --- +// Let the error message report the type name. +// Error: 2-9 type content does not have a constructor +#content() + +--- issue-3110-associated-field --- +// Error: 6-12 type integer does not contain field `MAXVAL` +#int.MAXVAL + +--- issue-3110-associated-function --- +// Error: 6-18 type string does not contain field `from-unïcode` +#str.from-unïcode(97) diff --git a/tests/suite/foundations/version.typ b/tests/suite/foundations/version.typ new file mode 100644 index 00000000..bf2cadb1 --- /dev/null +++ b/tests/suite/foundations/version.typ @@ -0,0 +1,47 @@ +// Test versions. + +--- version-constructor --- +// Test version constructor. + +// Empty. +#version() + +// Plain. +#test(version(1, 2).major, 1) + +// Single Array argument. +#test(version((1, 2)).minor, 2) + +// Mixed arguments. +#test(version(1, (2, 3), 4, (5, 6), 7).at(5), 6) + +--- version-equality --- +// Test equality of different-length versions +#test(version(), version(0)) +#test(version(0), version(0, 0)) +#test(version(1, 2), version(1, 2, 0, 0, 0, 0)) + +--- version-at --- +// Test `version.at`. + +// Non-negative index in bounds +#test(version(1, 2).at(1), 2) + +// Non-negative index out of bounds +#test(version(1, 2).at(4), 0) + +// Negative index in bounds +#test(version(1, 2).at(-2), 1) + +// Error: 2-22 component index out of bounds (index: -3, len: 2) +#version(1, 2).at(-3) + +--- version-fields --- +// Test version fields. +#test(version(1, 2, 3).major, 1) +#test(version(1, 2, 3).minor, 2) +#test(version(1, 2, 3).patch, 3) + +--- version-type --- +// Test the type of `sys.version` +#test(type(sys.version), version) 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 = +#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(