use crate::fuzzy_matcher::util::{assert_order, wrap_matches};
use super::*;
fn wrap_fuzzy_match(matcher: &dyn FuzzyMatcher, line: &str, pattern: &str) -> Option<String> {
let (score, indices) = matcher.fuzzy_indices(line, pattern)?;
println!("score: {score:?}, indices: {indices:?}");
Some(wrap_matches(line, &indices))
}
#[test]
fn test_match_or_not() {
let matcher = SkimMatcherV2::default();
assert_eq!(Some(0), matcher.fuzzy_match("", ""));
assert_eq!(Some(0), matcher.fuzzy_match("abcdefaghi", ""));
assert_eq!(None, matcher.fuzzy_match("", "a"));
assert_eq!(None, matcher.fuzzy_match("abcdefaghi", "中"));
assert_eq!(None, matcher.fuzzy_match("abc", "abx"));
assert!(matcher.fuzzy_match("axbycz", "abc").is_some());
assert!(matcher.fuzzy_match("axbycz", "xyz").is_some());
assert_eq!("[a]x[b]y[c]z", &wrap_fuzzy_match(&matcher, "axbycz", "abc").unwrap());
assert_eq!("a[x]b[y]c[z]", &wrap_fuzzy_match(&matcher, "axbycz", "xyz").unwrap());
assert_eq!(
"[H]ello, [世]界",
&wrap_fuzzy_match(&matcher, "Hello, 世界", "H世").unwrap()
);
}
#[test]
fn test_match_quality() {
let matcher = SkimMatcherV2::default().ignore_case();
assert_order(&matcher, "ab", &["ab", "aoo_boo", "acb"]);
assert_order(&matcher, "CC", &["CamelCase", "camelCase", "camelcase"]);
assert_order(&matcher, "cC", &["camelCase", "CamelCase", "camelcase"]);
assert_order(
&matcher,
"cc",
&["camel case", "camelCase", "CamelCase", "camelcase", "camel ace"],
);
assert_order(
&matcher,
"Da.Te",
&["Data.Text", "Data.Text.Lazy", "Data.Aeson.Encoding.text"],
);
assert_order(&matcher, "is", &["isIEEE", "inSuf"]);
assert_order(&matcher, "ma", &["map", "many", "maximum"]);
assert_order(&matcher, "print", &["printf", "sprintf"]);
assert_order(&matcher, "ast", &["ast", "AST", "INT_FAST16_MAX"]);
assert_order(&matcher, "Int", &["int", "INT", "PRINT"]);
}
fn simple_match(
matcher: &SkimMatcherV2,
choice: &str,
pattern: &str,
case_sensitive: bool,
with_pos: bool,
) -> Option<(ScoreType, Vec<IndexType>)> {
let choice: Vec<char> = choice.chars().collect();
let pattern: Vec<char> = pattern.chars().collect();
let first_match_indices = cheap_matches(&choice, &pattern, case_sensitive)?;
matcher.simple_match(&choice, &pattern, &first_match_indices, case_sensitive, with_pos)
}
#[test]
fn test_match_or_not_simple() {
let matcher = SkimMatcherV2::default();
assert_eq!(
simple_match(&matcher, "axbycz", "xyz", false, true).unwrap().1,
vec![1, 3, 5]
);
assert_eq!(simple_match(&matcher, "", "", false, false), Some((0, vec![])));
assert_eq!(
simple_match(&matcher, "abcdefaghi", "", false, false),
Some((0, vec![]))
);
assert_eq!(simple_match(&matcher, "", "a", false, false), None);
assert_eq!(simple_match(&matcher, "abcdefaghi", "中", false, false), None);
assert_eq!(simple_match(&matcher, "abc", "abx", false, false), None);
assert_eq!(
simple_match(&matcher, "axbycz", "abc", false, true).unwrap().1,
vec![0, 2, 4]
);
assert_eq!(
simple_match(&matcher, "axbycz", "xyz", false, true).unwrap().1,
vec![1, 3, 5]
);
assert_eq!(
simple_match(&matcher, "Hello, 世界", "H世", false, true).unwrap().1,
vec![0, 7]
);
}
#[test]
fn test_match_or_not_v2() {
let matcher = SkimMatcherV2::default().debug(true);
assert_eq!(matcher.fuzzy_match("", ""), Some(0));
assert_eq!(matcher.fuzzy_match("abcdefaghi", ""), Some(0));
assert_eq!(matcher.fuzzy_match("", "a"), None);
assert_eq!(matcher.fuzzy_match("abcdefaghi", "中"), None);
assert_eq!(matcher.fuzzy_match("abc", "abx"), None);
assert!(matcher.fuzzy_match("axbycz", "abc").is_some());
assert!(matcher.fuzzy_match("axbycz", "xyz").is_some());
assert_eq!(&wrap_fuzzy_match(&matcher, "axbycz", "abc").unwrap(), "[a]x[b]y[c]z");
assert_eq!(&wrap_fuzzy_match(&matcher, "axbycz", "xyz").unwrap(), "a[x]b[y]c[z]");
assert_eq!(
&wrap_fuzzy_match(&matcher, "Hello, 世界", "H世").unwrap(),
"[H]ello, [世]界"
);
}
#[test]
fn test_case_option_v2() {
let matcher = SkimMatcherV2::default().ignore_case();
assert!(matcher.fuzzy_match("aBc", "abc").is_some());
assert!(matcher.fuzzy_match("aBc", "aBc").is_some());
assert!(matcher.fuzzy_match("aBc", "aBC").is_some());
let matcher = SkimMatcherV2::default().respect_case();
assert!(matcher.fuzzy_match("aBc", "abc").is_none());
assert!(matcher.fuzzy_match("aBc", "aBc").is_some());
assert!(matcher.fuzzy_match("aBc", "aBC").is_none());
let matcher = SkimMatcherV2::default().smart_case();
assert!(matcher.fuzzy_match("aBc", "abc").is_some());
assert!(matcher.fuzzy_match("aBc", "aBc").is_some());
assert!(matcher.fuzzy_match("aBc", "aBC").is_none());
}
#[test]
fn test_matcher_quality_v2() {
let matcher = SkimMatcherV2::default();
assert_order(&matcher, "ab", &["ab", "aoo_boo", "acb"]);
assert_order(
&matcher,
"cc",
&["camel case", "camelCase", "CamelCase", "camelcase", "camel ace"],
);
assert_order(
&matcher,
"Da.Te",
&["Data.Text", "Data.Text.Lazy", "Data.Aeson.Encoding.Text"],
);
assert_order(&matcher, "is", &["isIEEE", "inSuf"]);
assert_order(&matcher, "ma", &["map", "many", "maximum"]);
assert_order(&matcher, "print", &["printf", "sprintf"]);
assert_order(&matcher, "ast", &["ast", "AST", "INT_FAST16_MAX"]);
assert_order(&matcher, "int", &["int", "INT", "PRINT"]);
}
#[test]
fn test_reuse_should_not_affect_indices() {
let matcher = SkimMatcherV2::default();
let pattern = "139";
for num in 0..10000 {
let choice = num.to_string();
if let Some((_score, indices)) = matcher.fuzzy_indices(&choice, pattern) {
assert_eq!(indices.len(), 3);
}
}
}
#[test]
fn builder_setters_are_chainable() {
let matcher = SkimMatcherV2::default()
.score_config(SkimScoreConfig::default())
.use_cache(true)
.debug(false);
assert!(matcher.fuzzy_match("foobar", "fb").is_some());
}
#[test]
fn element_limit_falls_back_to_simple_match() {
let matcher = SkimMatcherV2::default().ignore_case().element_limit(1);
let (_score, indices) = matcher.fuzzy_indices("hello", "l").unwrap();
assert_eq!(indices.len(), 1);
let (_score, indices) = matcher.fuzzy_indices("axbycz", "abc").unwrap();
assert_eq!(indices.len(), 3);
assert!(matcher.fuzzy_match("abc", "xyz").is_none());
}
#[test]
fn simple_match_empty_pattern_scores_zero() {
let matcher = SkimMatcherV2::default().element_limit(1);
assert_eq!(matcher.fuzzy_match("hello", ""), Some(0));
}
#[test]
fn use_cache_disabled_drops_buffers_after_match() {
let matcher = SkimMatcherV2::default().ignore_case().use_cache(false);
let (_score, indices) = matcher.fuzzy_indices("foobar", "fb").unwrap();
assert_eq!(indices, vec![0, 3]);
assert!(matcher.fuzzy_match("foobar", "fb").is_some());
assert!(matcher.fuzzy_indices("foobar", "bar").is_some());
}
#[test]
fn simple_match_single_char_at_start_has_no_prev_char() {
let matcher = SkimMatcherV2::default();
let (score, indices) = simple_match(&matcher, "hello", "h", false, true).unwrap();
assert_eq!(indices, vec![0]);
let (mid_score, _) = simple_match(&matcher, "ahello", "h", false, true).unwrap();
assert_ne!(score, mid_score, "start-of-string bonus should differ from mid-word");
}
#[test]
fn simple_match_score_only_skips_position_tracking() {
let matcher = SkimMatcherV2::default();
let (score, positions) = simple_match(&matcher, "axbycz", "abc", false, false).unwrap();
assert!(score > 0);
assert!(positions.is_empty(), "score-only path must not collect positions");
let (pos_score, positions) = simple_match(&matcher, "axbycz", "abc", false, true).unwrap();
assert_eq!(score, pos_score);
assert_eq!(positions, vec![0, 2, 4]);
}
#[test]
fn gappy_match_traceback_skips_to_first_column() {
let matcher = SkimMatcherV2::default();
for (choice, pattern, expected) in [
("a___________b", "ab", vec![0usize, 12]),
("x_a_____b__c", "abc", vec![2, 8, 11]),
("a_b_______c", "abc", vec![0, 2, 10]),
] {
let (_score, indices) = matcher.fuzzy_indices(choice, pattern).expect("should match");
assert_eq!(indices, expected, "choice={choice:?} pattern={pattern:?}");
}
}
#[test]
fn build_in_place_bonus_short_buffer_skips_first_char_multiplier() {
let matcher = SkimMatcherV2::default();
let mut b = [0i32];
matcher.build_in_place_bonus(&[], &mut b);
assert_eq!(b, [0], "empty choice leaves the buffer untouched (no b[1] access)");
}
#[test]
fn calculate_score_with_pos_stops_when_pattern_exhausted_early() {
let matcher = SkimMatcherV2::default();
let choice: Vec<char> = "abXc".chars().collect();
let pattern: Vec<char> = "ab".chars().collect();
let (_score, pos) = matcher.calculate_score_with_pos(&choice, &pattern, 0, 3, false, true);
assert_eq!(pos, vec![0, 1], "only 'a','b' match; the trailing range is ignored");
}