Skip to main content

pinto/backlog/
workflow.rs

1//! Kanban workflow.
2
3use super::Status;
4
5/// Kanban workflow (ordered set of columns).
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Workflow {
8    columns: Vec<Status>,
9}
10
11impl Workflow {
12    /// Build a workflow from columns (left to right in workflow order).
13    pub fn new(columns: impl IntoIterator<Item = Status>) -> Self {
14        Self {
15            columns: columns.into_iter().collect(),
16        }
17    }
18
19    /// List of columns.
20    #[must_use]
21    pub fn columns(&self) -> &[Status] {
22        &self.columns
23    }
24
25    /// Default state (first column).
26    #[must_use]
27    pub fn default_status(&self) -> Option<&Status> {
28        self.columns.first()
29    }
30
31    /// Whether the specified state exists in the workflow.
32    #[must_use]
33    pub fn contains(&self, status: &Status) -> bool {
34        self.columns.contains(status)
35    }
36
37    /// Whether a transition from `from` to `to` is valid.
38    ///
39    /// It is valid when both columns are known; Kanban permits moves between any columns.
40    #[must_use]
41    pub fn can_transition(&self, from: &Status, to: &Status) -> bool {
42        self.contains(from) && self.contains(to)
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    fn workflow() -> Workflow {
51        Workflow::new(
52            ["todo", "in-progress", "review", "done"]
53                .into_iter()
54                .map(Status::new),
55        )
56    }
57
58    #[test]
59    fn workflow_default_status_is_first_column() {
60        assert_eq!(workflow().default_status(), Some(&Status::new("todo")));
61    }
62
63    #[test]
64    fn workflow_allows_transition_between_known_columns() {
65        let wf = workflow();
66        assert!(wf.can_transition(&Status::new("todo"), &Status::new("done")));
67        assert!(wf.can_transition(&Status::new("review"), &Status::new("in-progress")));
68    }
69
70    #[test]
71    fn workflow_rejects_transition_to_unknown_column() {
72        let wf = workflow();
73        assert!(!wf.can_transition(&Status::new("todo"), &Status::new("archived")));
74        assert!(!wf.can_transition(&Status::new("backlog"), &Status::new("done")));
75    }
76}