//! 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()); }