use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Phase {
Working,
Stopped,
Other,
}
impl Phase {
pub fn of(state: crate::session::ActivityState) -> Phase {
use crate::session::ActivityState;
match state {
ActivityState::Working => Phase::Working,
ActivityState::WaitingForInput => Phase::Stopped,
ActivityState::Asking | ActivityState::ApiError => Phase::Other,
}
}
}
#[derive(Debug, Default)]
pub struct Seen {
working: HashSet<String>,
done: HashSet<String>,
}
impl Seen {
pub fn observe<'a>(
&mut self,
live: impl IntoIterator<Item = (&'a str, Phase)>,
viewed: Option<&str>,
) -> bool {
let before = self.done.len();
let mut changed = false;
let mut present = HashSet::new();
for (key, phase) in live {
present.insert(key);
let looking = viewed == Some(key);
match phase {
Phase::Working => {
self.working.insert(key.to_string());
changed |= self.done.remove(key);
}
Phase::Stopped => {
if self.working.remove(key) && !looking {
changed |= self.done.insert(key.to_string());
}
}
Phase::Other => {}
}
if looking {
changed |= self.done.remove(key);
}
}
self.working.retain(|key| present.contains(key.as_str()));
self.done.retain(|key| present.contains(key.as_str()));
changed || self.done.len() != before
}
pub fn is_done(&self, key: &str) -> bool {
self.done.contains(key)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_turn_that_ends_unwatched_is_marked_until_looked_at() {
let mut seen = Seen::default();
assert!(!seen.observe([("a", Phase::Working)], None));
assert!(!seen.is_done("a"), "working is not done");
assert!(seen.observe([("a", Phase::Stopped)], None), "no frame owed");
assert!(seen.is_done("a"), "an unwatched stop was not marked");
assert!(!seen.observe([("a", Phase::Stopped)], None));
assert!(seen.is_done("a"));
assert!(seen.observe([("a", Phase::Stopped)], Some("a")));
assert!(!seen.is_done("a"), "looking did not clear it");
seen.observe([("a", Phase::Stopped)], None);
assert!(!seen.is_done("a"), "the same stop was marked twice");
}
#[test]
fn a_stop_nobody_saw_begin_is_not_marked() {
let mut seen = Seen::default();
seen.observe([("a", Phase::Stopped)], None);
assert!(!seen.is_done("a"));
}
#[test]
fn a_turn_that_ends_while_watched_is_not_marked() {
let mut seen = Seen::default();
seen.observe([("a", Phase::Working)], Some("a"));
seen.observe([("a", Phase::Stopped)], Some("a"));
seen.observe([("a", Phase::Stopped)], None);
assert!(!seen.is_done("a"));
}
#[test]
fn a_new_turn_clears_the_mark_until_it_ends() {
let mut seen = Seen::default();
seen.observe([("a", Phase::Working)], None);
seen.observe([("a", Phase::Stopped)], None);
assert!(seen.observe([("a", Phase::Working)], None));
assert!(!seen.is_done("a"));
seen.observe([("a", Phase::Stopped)], None);
assert!(seen.is_done("a"));
}
#[test]
fn a_question_mid_turn_leaves_the_turn_armed() {
let mut seen = Seen::default();
seen.observe([("a", Phase::Working)], None);
seen.observe([("a", Phase::Other)], None);
assert!(!seen.is_done("a"), "a question is not a finished turn");
seen.observe([("a", Phase::Stopped)], None);
assert!(seen.is_done("a"));
}
#[test]
fn an_ended_session_is_forgotten() {
let mut seen = Seen::default();
seen.observe([("a", Phase::Working), ("b", Phase::Working)], None);
seen.observe([("a", Phase::Stopped), ("b", Phase::Working)], None);
assert!(seen.is_done("a"));
assert!(seen.observe([("b", Phase::Working)], None), "no frame owed");
assert!(!seen.is_done("a"));
seen.observe([("a", Phase::Stopped)], None);
assert!(!seen.is_done("a"));
}
#[test]
fn a_row_reads_its_phase_off_its_dot() {
use crate::session::ActivityState;
assert_eq!(Phase::of(ActivityState::Working), Phase::Working);
assert_eq!(Phase::of(ActivityState::WaitingForInput), Phase::Stopped);
assert_eq!(Phase::of(ActivityState::Asking), Phase::Other);
assert_eq!(Phase::of(ActivityState::ApiError), Phase::Other);
}
}