tinymist_tests/
lib.rs

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