use crate::indexing::push_identifier;
use crate::skill::Skill;
pub(crate) fn searchable_text(skill: &Skill) -> String {
let mut tokens: Vec<String> = Vec::new();
if !skill.name.is_empty() {
push_identifier(&skill.name, &mut tokens);
}
if !skill.description.is_empty() {
tokens.push(skill.description.clone());
}
for tag in &skill.tags {
if !tag.is_empty() {
push_identifier(tag, &mut tokens);
}
}
tokens.join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn slides_skill() -> Skill {
Skill {
id: "frontend-slides".into(),
name: "frontend-slides".into(),
description: "Build animation-rich HTML presentations".into(),
tags: vec![
"frontend".into(),
"presentations".into(),
"slide deck".into(),
],
tools: vec!["fs__write_file".into()],
metadata: HashMap::from([("stacks".into(), vec!["react".into()])]),
body: "# Frontend Slides\n\nLong body that must not affect ranking…".into(),
}
}
#[test]
fn searchable_text_is_deterministic() {
let skill = slides_skill();
assert_eq!(searchable_text(&skill), searchable_text(&skill));
}
#[test]
fn searchable_text_splits_hyphenated_name_for_word_matching() {
let skill = slides_skill();
let text = searchable_text(&skill);
assert!(
text.contains("frontend-slides"),
"whole name missing: {text}"
);
assert!(text.contains("presentations"), "tag missing: {text}");
}
#[test]
fn searchable_text_excludes_body() {
let skill = slides_skill();
let text = searchable_text(&skill);
assert!(
!text.contains("must not affect ranking"),
"body leaked into index: {text}"
);
}
#[test]
fn searchable_text_splits_snake_case_identifiers() {
let skill = Skill {
id: "code_review".into(),
name: "code_review".into(),
description: String::new(),
tags: vec![],
tools: vec![],
metadata: HashMap::new(),
body: String::new(),
};
let text = searchable_text(&skill);
assert!(text.contains("code review"), "snake_case not split: {text}");
}
#[test]
fn searchable_text_includes_tag_phrases() {
let skill = slides_skill();
let text = searchable_text(&skill);
assert!(text.contains("slide deck"), "tag phrase missing: {text}");
}
#[test]
fn searchable_text_excludes_metadata() {
let skill = slides_skill();
let text = searchable_text(&skill);
assert!(
!text.contains("react"),
"metadata leaked into index: {text}"
);
}
#[test]
fn searchable_text_excludes_tools() {
let skill = slides_skill();
let text = searchable_text(&skill);
assert!(
!text.contains("write_file"),
"tool dep leaked into index: {text}"
);
}
}