Lines
95.2 %
Functions
41.67 %
Branches
100 %
use crate::types::{AccountSuggestion, CommoditySuggestion, TagSuggestion};
pub trait Suggestion: Clone + serde::de::DeserializeOwned + 'static {
fn display_text(&self) -> &str;
fn matches(&self, query: &str) -> bool;
fn get_id(&self) -> &str;
fn get_secondary_value(&self) -> Option<&str>;
}
impl Suggestion for AccountSuggestion {
fn display_text(&self) -> &str {
&self.name
fn matches(&self, query: &str) -> bool {
self.name.to_lowercase().contains(&query.to_lowercase())
fn get_id(&self) -> &str {
&self.id
fn get_secondary_value(&self) -> Option<&str> {
self.currency.as_deref()
impl Suggestion for CommoditySuggestion {
&self.symbol
let q = query.to_lowercase();
self.symbol.to_lowercase().contains(&q) || self.name.to_lowercase().contains(&q)
Some(&self.id)
impl Suggestion for TagSuggestion {
self.name.to_lowercase().contains(&q)
|| self
.value
.as_ref()
.is_some_and(|v| v.to_lowercase().contains(&q))
self.value.as_deref()
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn account_suggestion_matches_case_insensitive() {
let suggestion = AccountSuggestion {
id: "123".into(),
name: "Checking Account".into(),
currency: Some("456".into()),
};
assert!(suggestion.matches("checking"));
assert!(suggestion.matches("ACCOUNT"));
assert!(suggestion.matches("Checking"));
assert!(!suggestion.matches("savings"));
fn account_suggestion_returns_correct_id() {
id: "acc-123".into(),
name: "Test".into(),
currency: Some("cur-456".into()),
assert_eq!(suggestion.get_id(), "acc-123");
assert_eq!(suggestion.get_secondary_value(), Some("cur-456"));
fn account_suggestion_handles_none_currency() {
currency: None,
assert_eq!(suggestion.get_secondary_value(), None);
fn commodity_matches_symbol_or_name() {
let suggestion = CommoditySuggestion {
id: "com-123".into(),
symbol: "USD".into(),
name: "US Dollar".into(),
assert!(suggestion.matches("usd"));
assert!(suggestion.matches("USD"));
assert!(suggestion.matches("dollar"));
assert!(suggestion.matches("US"));
assert!(!suggestion.matches("euro"));
fn commodity_returns_symbol_as_display_text() {
symbol: "EUR".into(),
name: "Euro".into(),
assert_eq!(suggestion.display_text(), "EUR");
fn commodity_returns_id_as_secondary() {
id: "com-456".into(),
symbol: "GBP".into(),
name: "British Pound".into(),
assert_eq!(suggestion.get_id(), "com-456");
assert_eq!(suggestion.get_secondary_value(), Some("com-456"));
fn tag_matches_name_or_value() {
let suggestion = TagSuggestion {
name: "category".into(),
value: Some("groceries".into()),
assert!(suggestion.matches("cat"));
assert!(suggestion.matches("CATEGORY"));
assert!(suggestion.matches("groc"));
assert!(!suggestion.matches("transport"));
fn tag_matches_name_only_when_no_value() {
name: "status".into(),
value: None,
assert!(suggestion.matches("stat"));
assert!(!suggestion.matches("value"));
fn tag_returns_name_as_id() {
name: "priority".into(),
value: Some("high".into()),
assert_eq!(suggestion.get_id(), "priority");
assert_eq!(suggestion.get_secondary_value(), Some("high"));