use super::path::Location;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum NavChange {
#[default]
Initial,
Push,
Pop,
Replace,
Reset,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct RouteStack {
entries: Vec<Option<Location>>,
generation: u64,
change: NavChange,
}
impl RouteStack {
pub fn entries(&self) -> &[Option<Location>] {
&self.entries
}
pub fn depth(&self) -> usize {
self.entries.len()
}
pub fn current(&self) -> Option<&Location> {
self.entries.last().and_then(Option::as_ref)
}
pub fn current_route(&self) -> Option<&Location> {
self.entries.iter().rev().find_map(Option::as_ref)
}
pub fn generation(&self) -> u64 {
self.generation
}
pub fn change(&self) -> NavChange {
self.change
}
pub(super) fn set(&mut self, entries: Vec<Option<Location>>) {
self.change = derive_nav_change(&self.entries, &entries);
self.entries = entries;
self.generation += 1;
}
}
pub(super) fn derive_nav_change(prev: &[Option<Location>], next: &[Option<Location>]) -> NavChange {
if prev.is_empty() {
return NavChange::Initial;
}
if next.len() == prev.len() + 1 && next[..prev.len()] == *prev {
return NavChange::Push;
}
if prev.len() == next.len() + 1 && prev[..next.len()] == *next {
return NavChange::Pop;
}
if prev.len() == next.len() && prev[..prev.len() - 1] == next[..next.len() - 1] {
return NavChange::Replace;
}
NavChange::Reset
}
#[cfg(test)]
mod tests {
use super::*;
fn loc(path: &str) -> Option<Location> {
Some(Location::parse(path))
}
#[test]
fn empty_prev_is_always_initial() {
assert_eq!(derive_nav_change(&[], &[loc("/a")]), NavChange::Initial);
assert_eq!(derive_nav_change(&[], &[]), NavChange::Initial);
}
#[test]
fn an_exact_one_entry_extension_is_push() {
let prev = [loc("/a")];
let next = [loc("/a"), loc("/b")];
assert_eq!(derive_nav_change(&prev, &next), NavChange::Push);
}
#[test]
fn an_exact_one_entry_prefix_shrink_is_pop() {
let prev = [loc("/a"), loc("/b")];
let next = [loc("/a")];
assert_eq!(derive_nav_change(&prev, &next), NavChange::Pop);
}
#[test]
fn same_len_top_only_differs_is_replace() {
let prev = [loc("/a"), loc("/b")];
let next = [loc("/a"), loc("/c")];
assert_eq!(derive_nav_change(&prev, &next), NavChange::Replace);
}
#[test]
fn same_len_single_entry_differs_is_replace() {
let prev = [loc("/a")];
let next = [loc("/b")];
assert_eq!(derive_nav_change(&prev, &next), NavChange::Replace);
}
#[test]
fn a_whole_chain_swap_is_reset() {
let prev = [loc("/a")];
let next = [loc("/b"), loc("/c")];
assert_eq!(derive_nav_change(&prev, &next), NavChange::Reset);
}
#[test]
fn a_multi_page_pop_is_reset_not_pop() {
let prev = [loc("/a"), loc("/b"), loc("/c")];
let next = [loc("/a")];
assert_eq!(derive_nav_change(&prev, &next), NavChange::Reset);
}
#[test]
fn none_entries_participate_in_the_diff_like_any_other() {
let prev = [loc("/a"), None];
let next = [loc("/a"), None, loc("/c")];
assert_eq!(derive_nav_change(&prev, &next), NavChange::Push);
}
#[test]
fn current_reports_the_raw_top_including_none() {
let mut stack = RouteStack::default();
stack.set(vec![loc("/a"), None]);
assert_eq!(stack.current(), None);
assert_eq!(stack.current_route().map(|l| l.path.as_str()), Some("/a"));
}
#[test]
fn current_route_skips_every_routeless_page_on_top() {
let mut stack = RouteStack::default();
stack.set(vec![None, None]);
assert_eq!(
stack.current_route(),
None,
"no page in the stack carries a route"
);
}
#[test]
fn set_bumps_generation_and_replaces_entries() {
let mut stack = RouteStack::default();
assert_eq!(stack.generation(), 0);
assert_eq!(stack.change(), NavChange::Initial);
stack.set(vec![loc("/a")]);
assert_eq!(stack.generation(), 1);
assert_eq!(stack.change(), NavChange::Initial);
assert_eq!(stack.depth(), 1);
stack.set(vec![loc("/a"), loc("/b")]);
assert_eq!(stack.generation(), 2);
assert_eq!(stack.change(), NavChange::Push);
assert_eq!(stack.depth(), 2);
}
}