Files
Marcos Paulo 5036ec2dbb feat(editor): wave 7
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 20:47:40 -03:00

187 lines
5.4 KiB
Rust

//! Completion triggers for each form, negative cases and `apply`.
use notescraft_editor::completion::{Trigger, apply, trigger_at};
fn trigger_text(text: &str, cursor: usize) -> Trigger {
trigger_at(text, cursor).expect("expected a trigger")
}
#[test]
fn wikilink_open_query() {
let text = "see [[hello";
let t = trigger_text(text, text.len());
match t {
Trigger::Wikilink { query, replace } => {
assert_eq!(query, "hello");
assert_eq!(replace, text.find('[').unwrap() + 2..text.len());
}
other => panic!("wrong trigger {other:?}"),
}
}
#[test]
fn wikilink_closed_is_negative() {
let text = "see [[hello]]";
assert!(trigger_at(text, text.len()).is_none());
}
#[test]
fn wikilink_newline_inside_is_negative() {
let text = "[[hello\nworld";
assert!(trigger_at(text, text.len()).is_none());
}
#[test]
fn heading_trigger() {
let text = "[[note#Head";
let t = trigger_text(text, text.len());
match t {
Trigger::Heading { note, query, replace } => {
assert_eq!(note, "note");
assert_eq!(query, "Head");
assert_eq!(replace, text.find('#').unwrap() + 1..text.len());
}
other => panic!("wrong trigger {other:?}"),
}
}
#[test]
fn block_trigger_query_with_caret() {
let text = "[[note#Head^b";
let t = trigger_text(text, text.len());
match t {
Trigger::Block { note, query, .. } => {
assert_eq!(note, "note");
assert_eq!(query, "Head^b");
}
other => panic!("wrong trigger {other:?}"),
}
}
#[test]
fn tag_trigger_word() {
let text = "a #work/tag";
let t = trigger_text(text, text.len());
match t {
Trigger::Tag { query, replace } => {
assert_eq!(query, "work/tag");
assert_eq!(replace, text.find('#').unwrap() + 1..text.len());
}
other => panic!("wrong trigger {other:?}"),
}
}
#[test]
fn tag_hash_midword_negative() {
let text = "x#not-a-tag";
assert!(trigger_at(text, text.len()).is_none());
let text = "_#not";
assert!(trigger_at(text, text.len()).is_none());
// `# heading` (a space between # and the word, cursor at end) is not a tag.
let text = "# heading";
let at = text.len();
assert!(trigger_at(text, at).is_none());
// `## double` likewise.
let text = "## title";
let at = text.len();
assert!(trigger_at(text, at).is_none());
}
#[test]
fn callout_trigger() {
let text = "> [!info";
let t = trigger_text(text, text.len());
match t {
Trigger::Callout { query, replace } => {
assert_eq!(query, "info");
assert_eq!(replace, text.find("[!").unwrap() + 2..text.len());
}
other => panic!("wrong trigger {other:?}"),
}
}
#[test]
fn callout_needs_quote_prefix() {
let text = "[!info";
assert!(trigger_at(text, text.len()).is_none());
}
#[test]
fn tag_inside_code_span_negative() {
let text = "`#maybe`";
let at = text.find('#').unwrap();
assert!(trigger_at(text, at).is_none());
}
#[test]
fn apply_appends_closing_brackets() {
let text = "[[hel";
let t = trigger_text(text, text.len());
let edit = apply(text, &t, text.len(), "hello.md").expect("apply works");
let mut out = String::from(text);
out.replace_range(edit.range.clone(), &edit.insert);
assert_eq!(out, "[[hello.md]]");
assert_eq!(edit.sel, edit.range.start + edit.insert.len()..edit.range.start + edit.insert.len());
}
#[test]
fn apply_does_not_double_close() {
let text = "[[hel]]";
// Trigger found at the moment before `]]` was typed: cursor between.
let cursor = text.find("]]").unwrap();
let t = trigger_text("[[hel", cursor);
let edit = apply(text, &t, cursor, "hello.md").expect("apply works");
let mut out = String::from(text);
out.replace_range(edit.range.clone(), &edit.insert);
assert_eq!(out, "[[hello.md]]");
}
#[test]
fn apply_tag_and_heading() {
let text = "see #my/tag";
let t = trigger_text(text, text.len());
let edit = apply(text, &t, text.len(), "my/full/tag").expect("apply works");
let mut out = String::from(text);
out.replace_range(edit.range.clone(), &edit.insert);
assert_eq!(out, "see #my/full/tag");
let text = "[[note#Hea";
let t = trigger_text(text, text.len());
let edit = apply(text, &t, text.len(), "Heading two").expect("apply works");
let mut out = String::from(text);
out.replace_range(edit.range.clone(), &edit.insert);
assert_eq!(out, "[[note#Heading two");
}
#[test]
fn apply_callout_inserts_bracket_and_space() {
let text = "> [!";
let t = trigger_text(text, text.len());
let edit = apply(text, &t, text.len(), "info").expect("apply works");
let mut out = String::from(text);
out.replace_range(edit.range.clone(), &edit.insert);
assert_eq!(out, "> [!info] ");
}
#[test]
fn triggers_suppressed_inside_code_fences() {
let text = "```\n#nota\n```";
let mid = text.find("#nota").unwrap();
assert!(trigger_at(text, mid).is_none());
let text = "```\n[[note";
let mid = text.find("[[").unwrap();
assert!(trigger_at(text, mid).is_none());
let text = "> no\n```\n> [!inf";
let mid = text.find("[!").unwrap();
assert!(trigger_at(text, mid).is_none());
}
#[test]
fn apply_rejects_bad_ranges() {
let t = Trigger::Tag { query: String::from("x"), replace: 99..100 };
let text = "abc";
assert!(apply(text, &t, 3, "y").is_none());
}