use crate::domain::model::issue::IssueView;
use crate::domain::model::status::Status;
pub(super) struct Overview {
pub total: u32,
pub by_status: Vec<(Status, u32)>,
}
pub(super) fn compute_overview(filtered: &IssueView<'_>) -> Overview {
let total = filtered.len() as u32;
let mut status_map: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
for issue in filtered {
*status_map
.entry(issue.status.as_str().to_string())
.or_insert(0) += 1;
}
let mut by_status: Vec<(Status, u32)> = status_map
.into_iter()
.map(|(s, c)| (Status::unresolved(s), c))
.collect();
by_status.sort_by(|a, b| a.0.cmp(&b.0));
Overview { total, by_status }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::model::issue::Issue;
use crate::domain::usecases::issue::tests::{defect, feature, ir, IssueFixture};
#[test]
fn empty_slice_returns_zero_total() {
scenario().when_overview().then_empty();
}
#[test]
fn counts_total_correctly() {
scenario()
.given(feature("A").status("open"))
.given(defect("B").status("open"))
.given(feature("C").status("closed"))
.when_overview()
.then_total(3);
}
#[test]
fn groups_by_status() {
scenario()
.given(feature("A").status("open"))
.given(feature("B").status("open"))
.given(feature("C").status("closed"))
.when_overview()
.then_status_count("open", 2)
.then_status_count("closed", 1);
}
#[test]
fn by_status_is_sorted_alphabetically() {
scenario()
.given(feature("A").status("open"))
.given(feature("B").status("closed"))
.when_overview()
.then_by_status_is_sorted();
}
struct Scenario {
issues: Vec<Issue>,
}
fn scenario() -> Scenario {
Scenario { issues: vec![] }
}
impl Scenario {
fn given(mut self, fixture: IssueFixture) -> Self {
let id = self.issues.len() as u64 + 1;
self.issues.push(fixture.build(ir(id)));
self
}
fn when_overview(self) -> OverviewOutcome {
let view = IssueView::from_slice(&self.issues);
let result = compute_overview(&view);
OverviewOutcome { result }
}
}
struct OverviewOutcome {
result: Overview,
}
impl OverviewOutcome {
fn then_total(self, expected: u32) -> Self {
assert_eq!(self.result.total, expected);
self
}
fn then_empty(self) -> Self {
assert_eq!(self.result.total, 0);
assert!(self.result.by_status.is_empty());
self
}
fn then_status_count(self, status: &str, expected: u32) -> Self {
let count = self
.result
.by_status
.iter()
.find(|(s, _)| s.as_str() == status)
.map(|(_, c)| *c);
assert_eq!(count, Some(expected));
self
}
fn then_by_status_is_sorted(self) -> Self {
let labels: Vec<&str> = self
.result
.by_status
.iter()
.map(|(s, _)| s.as_str())
.collect();
let mut sorted = labels.clone();
sorted.sort();
assert_eq!(labels, sorted);
self
}
}
}