summaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-11-09 11:37:01 +0100
committerLaurenz <laurmaedje@gmail.com>2024-11-13 10:21:40 +0100
commit6f294eac567bd15510cf5e766c0f7cacec60f7a3 (patch)
treedc175ae85ef30cb4ecc19c0ef9b1178aca0ebccf /crates
parent8d4f01d2847e116c2156f02a869b526859364953 (diff)
Simplify autocompletion tests
A A
Diffstat (limited to 'crates')
-rw-r--r--crates/typst-ide/src/complete.rs113
-rw-r--r--crates/typst-ide/src/lib.rs6
2 files changed, 86 insertions, 33 deletions
diff --git a/crates/typst-ide/src/complete.rs b/crates/typst-ide/src/complete.rs
index cde4d1e3..699ce573 100644
--- a/crates/typst-ide/src/complete.rs
+++ b/crates/typst-ide/src/complete.rs
@@ -1353,53 +1353,106 @@ impl<'a> CompletionContext<'a> {
#[cfg(test)]
mod tests {
- use super::autocomplete;
+ use std::collections::BTreeSet;
+
+ use typst::model::Document;
+
+ use super::{autocomplete, Completion};
use crate::tests::TestWorld;
+ type Response = Option<(usize, Vec<Completion>)>;
+
+ trait ResponseExt {
+ fn labels(&self) -> BTreeSet<&str>;
+ fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self;
+ fn must_exclude<'a>(&self, excludes: impl IntoIterator<Item = &'a str>) -> &Self;
+ }
+
+ impl ResponseExt for Response {
+ fn labels(&self) -> BTreeSet<&str> {
+ match self {
+ None => BTreeSet::new(),
+ Some((_, completions)) => {
+ completions.iter().map(|c| c.label.as_str()).collect()
+ }
+ }
+ }
+
+ #[track_caller]
+ fn must_include<'a>(&self, includes: impl IntoIterator<Item = &'a str>) -> &Self {
+ let labels = self.labels();
+ for item in includes {
+ assert!(
+ labels.contains(item),
+ "{item:?} was not contained in {labels:?}",
+ );
+ }
+ self
+ }
+
+ #[track_caller]
+ fn must_exclude<'a>(&self, excludes: impl IntoIterator<Item = &'a str>) -> &Self {
+ let labels = self.labels();
+ for item in excludes {
+ assert!(
+ !labels.contains(item),
+ "{item:?} was wrongly contained in {labels:?}",
+ );
+ }
+ self
+ }
+ }
+
#[track_caller]
- fn test(text: &str, cursor: usize, contains: &[&str], excludes: &[&str]) {
+ fn test(text: &str, cursor: isize) -> Response {
let world = TestWorld::new(text);
+ test_with_world(&world, cursor)
+ }
+
+ #[track_caller]
+ fn test_with_world(world: &TestWorld, cursor: isize) -> Response {
let doc = typst::compile(&world).output.ok();
- let (_, completions) =
- autocomplete(&world, doc.as_ref(), &world.main, cursor, true)
- .unwrap_or_default();
+ test_with_world_and_doc(world, doc.as_ref(), cursor)
+ }
- let labels: Vec<_> = completions.iter().map(|c| c.label.as_str()).collect();
- for item in contains {
- assert!(labels.contains(item), "{item:?} was not contained in {labels:?}");
- }
- for item in excludes {
- assert!(!labels.contains(item), "{item:?} was not excluded in {labels:?}");
- }
+ #[track_caller]
+ fn test_with_world_and_doc(
+ world: &TestWorld,
+ doc: Option<&Document>,
+ cursor: isize,
+ ) -> Response {
+ let cursor = if cursor < 0 {
+ world.main.len_bytes().checked_add_signed(cursor).unwrap()
+ } else {
+ cursor as usize
+ };
+ autocomplete(&world, doc, &world.main, cursor, true)
}
#[test]
- fn test_autocomplete() {
- test("#i", 2, &["int", "if conditional"], &["foo"]);
- test("#().", 4, &["insert", "remove", "len", "all"], &["foo"]);
+ fn test_autocomplete_hash_expr() {
+ test("#i", 2).must_include(["int", "if conditional"]);
}
#[test]
- fn test_autocomplete_whitespace() {
- //Check that extra space before '.' is handled correctly.
- test("#() .", 5, &[], &["insert", "remove", "len", "all"]);
- test("#{() .}", 6, &["insert", "remove", "len", "all"], &["foo"]);
-
- test("#() .a", 6, &[], &["insert", "remove", "len", "all"]);
- test("#{() .a}", 7, &["at", "any", "all"], &["foo"]);
+ fn test_autocomplete_array_method() {
+ test("#().", 4).must_include(["insert", "remove", "len", "all"]);
+ test("#{ let x = (1, 2, 3); x. }", -2).must_include(["at", "push", "pop"]);
}
+ /// Test that extra space before '.' is handled correctly.
#[test]
- fn test_autocomplete_before_window_char_boundary() {
- // Check that the `before_window` doesn't slice into invalid byte
- // boundaries.
- let s = "😀😀 #text(font: \"\")";
- test(s, s.len() - 2, &[], &[]);
+ fn test_autocomplete_whitespace() {
+ test("#() .", 5).must_exclude(["insert", "remove", "len", "all"]);
+ test("#{() .}", 6).must_include(["insert", "remove", "len", "all"]);
+ test("#() .a", 6).must_exclude(["insert", "remove", "len", "all"]);
+ test("#{() .a}", 7).must_include(["at", "any", "all"]);
}
+ /// Test that the `before_window` doesn't slice into invalid byte
+ /// boundaries.
#[test]
- fn test_autocomplete_mutable_method() {
- let s = "#{ let x = (1, 2, 3); x. }";
- test(s, s.len() - 2, &["at", "push", "pop"], &[]);
+ fn test_autocomplete_before_window_char_boundary() {
+ test("😀😀 #text(font: \"\")", -2);
}
}
diff --git a/crates/typst-ide/src/lib.rs b/crates/typst-ide/src/lib.rs
index f09e6ac5..3a192b56 100644
--- a/crates/typst-ide/src/lib.rs
+++ b/crates/typst-ide/src/lib.rs
@@ -100,7 +100,7 @@ mod tests {
use typst::diag::{FileError, FileResult};
use typst::foundations::{Bytes, Datetime, Smart};
use typst::layout::{Abs, Margin, PageElem};
- use typst::syntax::{FileId, Source};
+ use typst::syntax::{FileId, Source, VirtualPath};
use typst::text::{Font, FontBook, TextElem, TextSize};
use typst::utils::{singleton, LazyHash};
use typst::{Library, World};
@@ -117,7 +117,7 @@ mod tests {
/// This is cheap because the shared base for all test runs is lazily
/// initialized just once.
pub fn new(text: &str) -> Self {
- let main = Source::detached(text);
+ let main = Source::new(Self::main_id(), text.into());
Self {
main,
base: singleton!(TestBase, TestBase::default()),
@@ -126,7 +126,7 @@ mod tests {
/// The ID of the main file in a `TestWorld`.
pub fn main_id() -> FileId {
- *singleton!(FileId, Source::detached("").id())
+ *singleton!(FileId, FileId::new(None, VirtualPath::new("main.typ")))
}
}