aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-nucleo/src/tests.rs
diff options
context:
space:
mode:
authorMichelle Tilley <michelle@michelletilley.net>2026-02-25 19:47:41 -0800
committerEllie Huxtable <ellie@elliehuxtable.com>2026-03-16 15:36:14 -0700
commit7049c9b7878b2a3be013272469f94ee39d8a7e2c (patch)
tree3736e7e540341576d67e45c491e2f269e0acd061 /crates/atuin-nucleo/src/tests.rs
parentUpdate readme (diff)
downloadatuin-7049c9b7878b2a3be013272469f94ee39d8a7e2c.zip
feat: Add custom filtering and scoring mechanisms
Diffstat (limited to 'crates/atuin-nucleo/src/tests.rs')
-rw-r--r--crates/atuin-nucleo/src/tests.rs242
1 files changed, 241 insertions, 1 deletions
diff --git a/crates/atuin-nucleo/src/tests.rs b/crates/atuin-nucleo/src/tests.rs
index 676c50df..96c4d99c 100644
--- a/crates/atuin-nucleo/src/tests.rs
+++ b/crates/atuin-nucleo/src/tests.rs
@@ -2,7 +2,7 @@ use std::sync::Arc;
use nucleo_matcher::Config;
-use crate::Nucleo;
+use crate::{pattern, Nucleo};
#[test]
fn active_injector_count() {
@@ -25,3 +25,243 @@ fn active_injector_count() {
drop(injector3);
assert_eq!(nucleo.active_injectors(), 0);
}
+
+#[derive(Clone, Debug)]
+struct TestItem {
+ text: String,
+ category: u32,
+ priority: u32,
+}
+
+#[test]
+fn filter_excludes_items() {
+ let mut nucleo: Nucleo<TestItem> = Nucleo::new(Config::DEFAULT, Arc::new(|| ()), Some(1), 1);
+ let injector = nucleo.injector();
+
+ // Add items with different categories
+ injector.push(
+ TestItem {
+ text: "apple".into(),
+ category: 1,
+ priority: 10,
+ },
+ |item, cols| cols[0] = item.text.clone().into(),
+ );
+ injector.push(
+ TestItem {
+ text: "apricot".into(),
+ category: 2,
+ priority: 20,
+ },
+ |item, cols| cols[0] = item.text.clone().into(),
+ );
+ injector.push(
+ TestItem {
+ text: "avocado".into(),
+ category: 1,
+ priority: 30,
+ },
+ |item, cols| cols[0] = item.text.clone().into(),
+ );
+
+ // Search without filter - should get all 3
+ nucleo.pattern.reparse(
+ 0,
+ "a",
+ pattern::CaseMatching::Ignore,
+ pattern::Normalization::Smart,
+ false,
+ );
+ while nucleo.tick(10).running {}
+ assert_eq!(nucleo.snapshot().matched_item_count(), 3);
+
+ // Set filter to only include category 1
+ nucleo.set_filter(Some(Arc::new(|item: &TestItem| item.category == 1)));
+
+ // Search again - should only get 2 items (apple, avocado)
+ while nucleo.tick(10).running {}
+ assert_eq!(nucleo.snapshot().matched_item_count(), 2);
+
+ // Verify the items are correct
+ let items: Vec<_> = nucleo
+ .snapshot()
+ .matched_items(..)
+ .map(|i| i.data.text.clone())
+ .collect();
+ assert!(items.contains(&"apple".to_string()));
+ assert!(items.contains(&"avocado".to_string()));
+ assert!(!items.contains(&"apricot".to_string()));
+
+ // Remove filter - should get all 3 again
+ nucleo.set_filter(None);
+ while nucleo.tick(10).running {}
+ assert_eq!(nucleo.snapshot().matched_item_count(), 3);
+}
+
+#[test]
+fn scorer_affects_sort_order() {
+ let mut nucleo: Nucleo<TestItem> = Nucleo::new(Config::DEFAULT, Arc::new(|| ()), Some(1), 1);
+ let injector = nucleo.injector();
+
+ // Add items with different priorities
+ injector.push(
+ TestItem {
+ text: "banana".into(),
+ category: 1,
+ priority: 10,
+ },
+ |item, cols| cols[0] = item.text.clone().into(),
+ );
+ injector.push(
+ TestItem {
+ text: "blueberry".into(),
+ category: 1,
+ priority: 100,
+ },
+ |item, cols| cols[0] = item.text.clone().into(),
+ );
+ injector.push(
+ TestItem {
+ text: "blackberry".into(),
+ category: 1,
+ priority: 50,
+ },
+ |item, cols| cols[0] = item.text.clone().into(),
+ );
+
+ // Search without scorer - results sorted by fuzzy score
+ nucleo.pattern.reparse(
+ 0,
+ "b",
+ pattern::CaseMatching::Ignore,
+ pattern::Normalization::Smart,
+ false,
+ );
+ while nucleo.tick(10).running {}
+ assert_eq!(nucleo.snapshot().matched_item_count(), 3);
+
+ // Set scorer that uses priority as the score (ignoring fuzzy score)
+ nucleo.set_scorer(Some(Arc::new(|item: &TestItem, _fuzzy_score| {
+ item.priority
+ })));
+
+ // Search again - should be sorted by priority (high to low)
+ while nucleo.tick(10).running {}
+ let items: Vec<_> = nucleo
+ .snapshot()
+ .matched_items(..)
+ .map(|i| i.data.clone())
+ .collect();
+ assert_eq!(items.len(), 3);
+ assert_eq!(items[0].text, "blueberry"); // priority 100
+ assert_eq!(items[1].text, "blackberry"); // priority 50
+ assert_eq!(items[2].text, "banana"); // priority 10
+
+ // Verify external_score is set correctly
+ let matches = nucleo.snapshot().matches();
+ assert_eq!(matches[0].external_score, 100);
+ assert_eq!(matches[1].external_score, 50);
+ assert_eq!(matches[2].external_score, 10);
+}
+
+#[test]
+fn filter_and_scorer_combined() {
+ let mut nucleo: Nucleo<TestItem> = Nucleo::new(Config::DEFAULT, Arc::new(|| ()), Some(1), 1);
+ let injector = nucleo.injector();
+
+ injector.push(
+ TestItem {
+ text: "cherry".into(),
+ category: 1,
+ priority: 10,
+ },
+ |item, cols| cols[0] = item.text.clone().into(),
+ );
+ injector.push(
+ TestItem {
+ text: "cranberry".into(),
+ category: 2,
+ priority: 100,
+ },
+ |item, cols| cols[0] = item.text.clone().into(),
+ );
+ injector.push(
+ TestItem {
+ text: "coconut".into(),
+ category: 1,
+ priority: 50,
+ },
+ |item, cols| cols[0] = item.text.clone().into(),
+ );
+
+ // Set both filter (category 1) and scorer (priority)
+ nucleo.set_filter(Some(Arc::new(|item: &TestItem| item.category == 1)));
+ nucleo.set_scorer(Some(Arc::new(|item: &TestItem, _| item.priority)));
+
+ nucleo.pattern.reparse(
+ 0,
+ "c",
+ pattern::CaseMatching::Ignore,
+ pattern::Normalization::Smart,
+ false,
+ );
+ while nucleo.tick(10).running {}
+
+ // Should have 2 items (cherry, coconut) sorted by priority
+ let items: Vec<_> = nucleo
+ .snapshot()
+ .matched_items(..)
+ .map(|i| i.data.clone())
+ .collect();
+ assert_eq!(items.len(), 2);
+ assert_eq!(items[0].text, "coconut"); // priority 50
+ assert_eq!(items[1].text, "cherry"); // priority 10
+}
+
+#[test]
+fn scorer_combines_with_fuzzy_score() {
+ let mut nucleo: Nucleo<TestItem> = Nucleo::new(Config::DEFAULT, Arc::new(|| ()), Some(1), 1);
+ let injector = nucleo.injector();
+
+ injector.push(
+ TestItem {
+ text: "date".into(),
+ category: 1,
+ priority: 100,
+ },
+ |item, cols| cols[0] = item.text.clone().into(),
+ );
+ injector.push(
+ TestItem {
+ text: "dragon fruit".into(),
+ category: 1,
+ priority: 10,
+ },
+ |item, cols| cols[0] = item.text.clone().into(),
+ );
+
+ // Set scorer that combines fuzzy score with priority
+ nucleo.set_scorer(Some(Arc::new(|item: &TestItem, fuzzy_score| {
+ fuzzy_score + item.priority
+ })));
+
+ nucleo.pattern.reparse(
+ 0,
+ "d",
+ pattern::CaseMatching::Ignore,
+ pattern::Normalization::Smart,
+ false,
+ );
+ while nucleo.tick(10).running {}
+
+ // Both items match, verify that external_score includes priority boost
+ let matches = nucleo.snapshot().matches();
+ assert_eq!(matches.len(), 2);
+
+ // The raw fuzzy scores should be in Match.score
+ // The combined scores should be in Match.external_score
+ for m in matches {
+ let item = nucleo.snapshot().get_item(m.idx).unwrap();
+ assert_eq!(m.external_score, m.score + item.data.priority);
+ }
+}