tinymist_tests/
lib.rs

1//! Tests support for tinymist crates.
2
3pub 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/// Runs snapshot tests.
19#[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
40/// A test that runs a function with a given source string and returns the
41/// result.
42///
43/// Multiple sources can be provided, separated by `-----`. The last source
44/// is used as the entry point.
45pub 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        // find prelude
76        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}