use serde::{Deserialize, Serialize};
use super::{JsonStore, StoreLocation};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SearchHistory {
queries: Vec<String>,
}
impl JsonStore for SearchHistory {
const FILE: &'static str = "search_history.json";
const LOCATION: StoreLocation = StoreLocation::Data;
}
impl SearchHistory {
pub fn push(&mut self, query: String, max_stored: usize) {
if let Some(index) = self.queries.iter().position(|q| q == &query) {
if index != 0 {
self.queries.remove(index);
self.queries.insert(0, query);
self.save();
}
} else {
self.queries.insert(0, query);
if self.queries.len() > max_stored {
self.queries.truncate(max_stored);
}
self.save();
}
}
pub fn remove(&mut self, query: &str) {
self.queries.retain(|q| q != query);
self.save();
}
pub fn filtered(&self, query_lower: &str) -> Vec<String> {
if query_lower.is_empty() {
return self.queries.clone();
}
self.queries
.iter()
.filter(|q| crate::util::fuzzy_match(query_lower, q))
.cloned()
.collect()
}
#[cfg(test)]
pub fn get(&self) -> &[String] {
&self.queries
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_and_dedup() {
let mut h = SearchHistory::default();
h.push("abc".into(), 100);
h.push("def".into(), 100);
h.push("abc".into(), 100); assert_eq!(h.get(), &["abc", "def"]);
}
#[test]
fn push_duplicate_at_front_is_noop() {
let mut h = SearchHistory::default();
h.push("abc".into(), 100);
h.push("abc".into(), 100);
assert_eq!(h.get(), &["abc"]);
}
#[test]
fn push_truncates_to_max() {
let mut h = SearchHistory::default();
for i in 0..10 {
h.push(i.to_string(), 5);
}
assert_eq!(h.get().len(), 5);
assert_eq!(h.get(), &["9", "8", "7", "6", "5"]);
}
#[test]
fn remove_existing() {
let mut h = SearchHistory::default();
h.push("abc".into(), 100);
h.push("def".into(), 100);
h.remove("abc");
assert_eq!(h.get(), &["def"]);
}
#[test]
fn filtered_empty_query_returns_all() {
let mut h = SearchHistory::default();
h.push("abc".into(), 100);
h.push("xyz".into(), 100);
assert_eq!(h.filtered("").len(), 2);
}
#[test]
fn filtered_subsequence() {
let mut h = SearchHistory::default();
h.push("hello world".into(), 100);
h.push("hi there".into(), 100);
let filtered = h.filtered("hlw");
assert_eq!(filtered, vec!["hello world".to_string()]);
}
}