From fa7fbb82749f6e8a150fb81c1cdd0efeb80aba14 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 30 May 2024 14:06:36 +0200 Subject: [PATCH] Tests for jump from click/cursor (#4297) --- crates/typst-ide/Cargo.toml | 2 +- crates/typst-ide/src/jump.rs | 76 +++++++++++++++++++++++++++++++++--- crates/typst-ide/src/lib.rs | 29 ++++++++++++-- 3 files changed, 98 insertions(+), 9 deletions(-) diff --git a/crates/typst-ide/Cargo.toml b/crates/typst-ide/Cargo.toml index 01f7a1062..4e87f99b1 100644 --- a/crates/typst-ide/Cargo.toml +++ b/crates/typst-ide/Cargo.toml @@ -22,7 +22,7 @@ serde = { workspace = true } unscanny = { workspace = true } [dev-dependencies] -typst-assets = { workspace = true } +typst-assets = { workspace = true, features = ["fonts"] } typst-dev-assets = { workspace = true } once_cell = { workspace = true } diff --git a/crates/typst-ide/src/jump.rs b/crates/typst-ide/src/jump.rs index 45186d5ba..3427a0c59 100644 --- a/crates/typst-ide/src/jump.rs +++ b/crates/typst-ide/src/jump.rs @@ -126,11 +126,8 @@ pub fn jump_from_cursor( let span = node.span(); for (i, page) in document.pages.iter().enumerate() { - if let Some(pos) = find_in_frame(&page.frame, span) { - return Some(Position { - page: NonZeroUsize::new(i + 1).unwrap(), - point: pos, - }); + if let Some(point) = find_in_frame(&page.frame, span) { + return Some(Position { page: NonZeroUsize::new(i + 1).unwrap(), point }); } } @@ -168,3 +165,72 @@ fn is_in_rect(pos: Point, size: Size, click: Point) -> bool { && pos.y <= click.y && pos.y + size.y >= click.y } + +#[cfg(test)] +mod tests { + use std::num::NonZeroUsize; + + use typst::eval::Tracer; + use typst::layout::{Abs, Point, Position}; + + use super::{jump_from_click, jump_from_cursor, Jump}; + use crate::tests::TestWorld; + + fn point(x: f64, y: f64) -> Point { + Point::new(Abs::pt(x), Abs::pt(y)) + } + + fn cursor(cursor: usize) -> Option { + Some(Jump::Source(TestWorld::main_id(), cursor)) + } + + fn pos(page: usize, x: f64, y: f64) -> Option { + Some(Position { + page: NonZeroUsize::new(page).unwrap(), + point: point(x, y), + }) + } + + macro_rules! assert_approx_eq { + ($l:expr, $r:expr) => { + assert!(($l.to_raw() - $r.to_raw()).abs() < 0.1, "{:?} ≉ {:?}", $l, $r); + }; + } + + #[track_caller] + fn test_click(text: &str, click: Point, expected: Option) { + let world = TestWorld::new(text); + let doc = typst::compile(&world, &mut Tracer::new()).unwrap(); + assert_eq!(jump_from_click(&world, &doc, &doc.pages[0].frame, click), expected); + } + + #[track_caller] + fn test_cursor(text: &str, cursor: usize, expected: Option) { + let world = TestWorld::new(text); + let doc = typst::compile(&world, &mut Tracer::new()).unwrap(); + let pos = jump_from_cursor(&doc, &world.main, cursor); + assert_eq!(pos.is_some(), expected.is_some()); + if let (Some(pos), Some(expected)) = (pos, expected) { + assert_eq!(pos.page, expected.page); + assert_approx_eq!(pos.point.x, expected.point.x); + assert_approx_eq!(pos.point.y, expected.point.y); + } + } + + #[test] + fn test_jump_from_click() { + let s = "*Hello* #box[ABC] World"; + test_click(s, point(0.0, 0.0), None); + test_click(s, point(70.0, 5.0), None); + test_click(s, point(45.0, 15.0), cursor(14)); + test_click(s, point(48.0, 15.0), cursor(15)); + test_click(s, point(72.0, 10.0), cursor(20)); + } + + #[test] + fn test_jump_from_cursor() { + let s = "*Hello* #box[ABC] World"; + test_cursor(s, 12, None); + test_cursor(s, 14, pos(1, 37.55, 16.58)); + } +} diff --git a/crates/typst-ide/src/lib.rs b/crates/typst-ide/src/lib.rs index 117e75ab6..1f8562fd2 100644 --- a/crates/typst-ide/src/lib.rs +++ b/crates/typst-ide/src/lib.rs @@ -95,9 +95,10 @@ fn summarize_font_family<'a>(variants: impl Iterator) -> Ec mod tests { use once_cell::sync::Lazy; use typst::diag::{FileError, FileResult}; - use typst::foundations::{Bytes, Datetime}; + use typst::foundations::{Bytes, Datetime, Smart}; + use typst::layout::{Abs, Margin, PageElem}; use typst::syntax::{FileId, Source}; - use typst::text::{Font, FontBook}; + use typst::text::{Font, FontBook, TextElem, TextSize}; use typst::utils::LazyHash; use typst::{Library, World}; @@ -117,6 +118,12 @@ mod tests { let main = Source::detached(text); Self { main, base: &*BASE } } + + /// The ID of the main file in a `TestWorld`. + pub fn main_id() -> FileId { + static ID: Lazy = Lazy::new(|| Source::detached("").id()); + *ID + } } impl World for TestWorld { @@ -168,10 +175,26 @@ mod tests { .collect(); Self { - library: LazyHash::new(Library::default()), + library: LazyHash::new(library()), book: LazyHash::new(FontBook::from_fonts(&fonts)), fonts, } } } + + /// The extended standard library for testing. + fn library() -> Library { + // Set page width to 120pt with 10pt margins, so that the inner page is + // exactly 100pt wide. Page height is unbounded and font size is 10pt so + // that it multiplies to nice round numbers. + let mut lib = Library::default(); + lib.styles + .set(PageElem::set_width(Smart::Custom(Abs::pt(120.0).into()))); + lib.styles.set(PageElem::set_height(Smart::Auto)); + lib.styles.set(PageElem::set_margin(Margin::splat(Some(Smart::Custom( + Abs::pt(10.0).into(), + ))))); + lib.styles.set(TextElem::set_size(TextSize(Abs::pt(10.0).into()))); + lib + } }