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