1pub mod mock;
4
5use std::{
6 path::{Path, PathBuf},
7 sync::{Arc, LazyLock},
8};
9
10use tinymist_project::{
11 CompileFontArgs, DynAccessModel, EntryManager, EntryState, ExportTarget, LspUniverse,
12 LspUniverseBuilder, base::ShadowApi, font::FontResolverImpl, vfs::system::SystemAccessModel,
13};
14use typst::{foundations::Bytes, syntax::VirtualPath};
15
16pub use insta::{Settings, assert_debug_snapshot, assert_snapshot, glob, with_settings};
17
18#[macro_export]
20macro_rules! snapshot_testing {
21 ($name:expr, $f:expr) => {
22 let name = $name;
23 let name = if name.is_empty() { "playground" } else { name };
24 let mut settings = $crate::Settings::new();
25 settings.set_prepend_module_to_snapshot(false);
26 settings.set_snapshot_path(format!("fixtures/{name}/snaps"));
27 settings.bind(|| {
28 let glob_path = format!("fixtures/{name}/*.typ");
29 $crate::glob!(&glob_path, |path| {
30 let contents = std::fs::read_to_string(path).unwrap();
31 #[cfg(windows)]
32 let contents = contents.replace("\r\n", "\n");
33
34 $crate::run_with_sources(&contents, $f);
35 });
36 });
37 };
38}
39
40pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut LspUniverse, PathBuf) -> T) -> T {
46 static FONT_RESOLVER: LazyLock<Arc<FontResolverImpl>> = LazyLock::new(|| {
47 Arc::new(
48 LspUniverseBuilder::resolve_fonts(CompileFontArgs {
49 ignore_system_fonts: true,
50 ..Default::default()
51 })
52 .unwrap(),
53 )
54 });
55
56 let root = if cfg!(windows) {
57 PathBuf::from("C:\\dummy-root")
58 } else {
59 PathBuf::from("/dummy-root")
60 };
61 let mut verse = LspUniverseBuilder::build(
62 EntryState::new_rooted(root.as_path().into(), None),
63 ExportTarget::Paged,
64 Default::default(),
65 Default::default(),
66 LspUniverseBuilder::resolve_package(None, None),
67 FONT_RESOLVER.clone(),
68 None,
69 DynAccessModel(Arc::new(SystemAccessModel {})),
70 );
71 let sources = source.split("-----");
72
73 let mut last_pw = None;
74 for (idx, source) in sources.enumerate() {
75 let mut source = source.trim_start();
77 let mut path = None;
78
79 if source.starts_with("//") {
80 let first_line = source.lines().next().unwrap();
81 let content = first_line.trim_start_matches("/").trim();
82
83 if let Some(path_attr) = content.strip_prefix("path:") {
84 source = source.strip_prefix(first_line).unwrap().trim();
85 path = Some(path_attr.trim().to_owned())
86 }
87 };
88
89 let path = path.unwrap_or_else(|| format!("/s{idx}.typ"));
90 let path = path.strip_prefix("/").unwrap_or(path.as_str());
91
92 let pw = root.join(Path::new(&path));
93 verse
94 .map_shadow(&pw, Bytes::from_string(source.to_owned()))
95 .unwrap();
96 last_pw = Some(pw);
97 }
98
99 let pw = last_pw.unwrap();
100 verse
101 .mutate_entry(EntryState::new_rooted(
102 root.as_path().into(),
103 Some(VirtualPath::virtualize(&root, &pw).expect("valid virtual test path")),
104 ))
105 .unwrap();
106 f(&mut verse, pw)
107}