use crate::{Card, Column};
use std::collections::HashSet;
use uuid::Uuid;
pub trait CardFilter {
fn matches(&self, card: &Card) -> bool;
}
pub struct BoardFilter<'a> {
board_id: Uuid,
columns: &'a [Column],
}
impl<'a> BoardFilter<'a> {
pub fn new(board_id: Uuid, columns: &'a [Column]) -> Self {
Self { board_id, columns }
}
}
impl CardFilter for BoardFilter<'_> {
fn matches(&self, card: &Card) -> bool {
self.columns
.iter()
.any(|col| col.id == card.column_id && col.board_id == self.board_id)
}
}
pub struct ColumnFilter {
column_id: Uuid,
}
impl ColumnFilter {
pub fn new(column_id: Uuid) -> Self {
Self { column_id }
}
}
impl CardFilter for ColumnFilter {
fn matches(&self, card: &Card) -> bool {
card.column_id == self.column_id
}
}
pub struct SprintFilter {
sprint_ids: HashSet<Uuid>,
}
impl SprintFilter {
pub fn in_sprints(ids: impl IntoIterator<Item = Uuid>) -> Self {
Self {
sprint_ids: ids.into_iter().collect(),
}
}
pub fn in_sprint(id: Uuid) -> Self {
Self::in_sprints(std::iter::once(id))
}
}
impl CardFilter for SprintFilter {
fn matches(&self, card: &Card) -> bool {
card.sprint_id
.is_some_and(|id| self.sprint_ids.contains(&id))
}
}
pub struct UnassignedOnlyFilter;
impl CardFilter for UnassignedOnlyFilter {
fn matches(&self, card: &Card) -> bool {
card.sprint_id.is_none()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Board;
fn create_test_card(board: &mut Board, column_id: Uuid) -> Card {
Card::new(board, column_id, "Test Card", 0)
}
#[test]
fn test_board_filter() {
let board = Board::new("Test Board", None::<String>);
let column = Column::new(board.id, "Todo", 0);
let other_column = Column::new(Uuid::new_v4(), "Other", 0);
let mut board_mut = board.clone();
let card = create_test_card(&mut board_mut, column.id);
let columns = vec![column.clone(), other_column];
let filter = BoardFilter::new(board.id, &columns);
assert!(filter.matches(&card));
let mut board_mut2 = board.clone();
let other_card = create_test_card(&mut board_mut2, Uuid::new_v4());
assert!(!filter.matches(&other_card));
}
#[test]
fn test_column_filter() {
let board = Board::new("Test Board", None::<String>);
let column1 = Column::new(board.id, "Todo", 0);
let column2 = Column::new(board.id, "Done", 1);
let mut board_mut = board.clone();
let card1 = create_test_card(&mut board_mut, column1.id);
let card2 = create_test_card(&mut board_mut, column2.id);
let filter = ColumnFilter::new(column1.id);
assert!(filter.matches(&card1));
assert!(!filter.matches(&card2));
}
#[test]
fn test_sprint_filter() {
let board = Board::new("Test Board", None::<String>);
let column = Column::new(board.id, "Todo", 0);
let mut board_mut = board.clone();
let mut card = create_test_card(&mut board_mut, column.id);
let sprint_id = Uuid::new_v4();
card.sprint_id = Some(sprint_id);
let filter = SprintFilter::in_sprint(sprint_id);
assert!(filter.matches(&card));
let other_sprint = Uuid::new_v4();
let filter2 = SprintFilter::in_sprint(other_sprint);
assert!(!filter2.matches(&card));
let filter3 = SprintFilter::in_sprints(vec![sprint_id, other_sprint]);
assert!(filter3.matches(&card));
}
#[test]
fn test_unassigned_only_filter() {
let board = Board::new("Test Board", None::<String>);
let column = Column::new(board.id, "Todo", 0);
let mut board_mut = board.clone();
let mut assigned_card = create_test_card(&mut board_mut, column.id);
assigned_card.sprint_id = Some(Uuid::new_v4());
let unassigned_card = create_test_card(&mut board_mut, column.id);
let filter = UnassignedOnlyFilter;
assert!(!filter.matches(&assigned_card));
assert!(filter.matches(&unassigned_card));
}
}