1use comemo::Tracked;
22use ecow::EcoString;
23use typst::diag::{bail, SourceResult};
24use typst::engine::Engine;
25use typst::foundations::{func, Context, Func, Module, Repr, Scope, Str, Value};
26use typst::{Library, LibraryBuilder};
27
28pub fn define_prelude(scope: &mut Scope) {
31 scope.define_func::<catch>();
32 scope.define_func::<assert_panic>();
33}
34
35pub fn define_test_module(scope: &mut Scope) {
37 define_prelude(scope)
38}
39
40pub fn test_module() -> Module {
42 let mut scope = Scope::new();
43 define_test_module(&mut scope);
44 Module::new("test", scope)
45}
46
47pub fn augmented_default_library() -> Library {
49 augmented_library(|x| x)
50}
51
52pub fn augmented_library(builder: impl FnOnce(LibraryBuilder) -> LibraryBuilder) -> Library {
58 let mut lib = builder(LibraryBuilder::default()).build();
59 let scope = lib.global.scope_mut();
60
61 scope.define("test", test_module());
62 define_prelude(scope);
63
64 lib
65}
66
67#[func]
68fn catch(engine: &mut Engine, context: Tracked<Context>, func: Func) -> Value {
69 func.call::<[Value; 0]>(engine, context, [])
70 .map(|_| Value::None)
71 .unwrap_or_else(|errors| {
72 Value::Str(Str::from(
73 errors
74 .first()
75 .expect("should contain at least one diagnostic")
76 .message
77 .clone(),
78 ))
79 })
80}
81
82#[func]
83fn assert_panic(
84 engine: &mut Engine,
85 context: Tracked<Context>,
86 func: Func,
87 #[named] message: Option<EcoString>,
88) -> SourceResult<()> {
89 let result = func.call::<[Value; 0]>(engine, context, []);
90 let span = func.span();
91 if let Ok(val) = result {
92 match message {
93 Some(message) => bail!(span, "{}", message),
94 None => match val {
95 Value::None => bail!(
96 span,
97 "Expected panic, closure returned successfully with {}",
98 val.repr(),
99 ),
100 _ => bail!(span, "Expected panic, closure returned successfully"),
101 },
102 }
103 }
104
105 Ok(())
106}
107
108#[cfg(test)]
109mod tests {
110 use typst::syntax::Source;
111
112 use super::*;
113 use crate::_dev::VirtualWorld;
114 use crate::doc::compile::{self, Warnings};
115
116 #[test]
117 fn test_catch() {
118 let world = VirtualWorld::new(augmented_default_library());
119 let source = Source::detached(
120 r#"
121 #let errors = catch(() => {
122 panic()
123 })
124 #assert.eq(errors, "panicked")
125 "#,
126 );
127
128 compile::compile(source, &world, Warnings::Emit)
129 .output
130 .unwrap();
131 }
132
133 #[test]
134 fn test_assert_panic() {
135 let world = VirtualWorld::new(augmented_default_library());
136 let source = Source::detached(
137 r#"
138 #assert-panic(() => {
139 panic()
140 })
141 "#,
142 );
143
144 compile::compile(source, &world, Warnings::Emit)
145 .output
146 .unwrap();
147 }
148}