Simplify autocompletion tests

A

A
This commit is contained in:
Laurenz 2024-11-09 11:37:01 +01:00
parent 8d4f01d284
commit 6f294eac56
2 changed files with 88 additions and 35 deletions

View File

@ -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;
#[track_caller]
fn test(text: &str, cursor: usize, contains: &[&str], excludes: &[&str]) {
let world = TestWorld::new(text);
let doc = typst::compile(&world).output.ok();
let (_, completions) =
autocomplete(&world, doc.as_ref(), &world.main, cursor, true)
.unwrap_or_default();
type Response = Option<(usize, Vec<Completion>)>;
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:?}");
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 not excluded in {labels:?}");
assert!(
!labels.contains(item),
"{item:?} was wrongly contained in {labels:?}",
);
}
self
}
}
#[track_caller]
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();
test_with_world_and_doc(world, doc.as_ref(), cursor)
}
#[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_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_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"]);
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_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, &[], &[]);
}
#[test]
fn test_autocomplete_mutable_method() {
let s = "#{ let x = (1, 2, 3); x. }";
test(s, s.len() - 2, &["at", "push", "pop"], &[]);
test("😀😀 #text(font: \"\")", -2);
}
}

View File

@ -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")))
}
}