#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Nav<R> {
stack: Vec<R>,
}
impl<R> Nav<R> {
#[must_use]
pub fn new(root: R) -> Self {
Self { stack: vec![root] }
}
#[must_use]
pub fn current(&self) -> &R {
self.stack.last().expect("Nav is never empty")
}
pub fn current_mut(&mut self) -> &mut R {
self.stack.last_mut().expect("Nav is never empty")
}
#[must_use]
pub fn root(&self) -> &R {
&self.stack[0]
}
#[must_use]
pub fn depth(&self) -> usize {
self.stack.len()
}
#[must_use]
pub fn can_pop(&self) -> bool {
self.stack.len() > 1
}
#[must_use]
pub fn stack(&self) -> &[R] {
&self.stack
}
pub fn push(&mut self, route: R) {
self.stack.push(route);
}
pub fn pop(&mut self) -> Option<R> {
if self.can_pop() {
self.stack.pop()
} else {
None
}
}
pub fn replace(&mut self, route: R) {
*self.current_mut() = route;
}
pub fn pop_to_root(&mut self) -> bool {
let popped = self.can_pop();
self.stack.truncate(1);
popped
}
pub fn reset(&mut self, root: R) {
self.stack.clear();
self.stack.push(root);
}
}
impl<R: PartialEq> Nav<R> {
pub fn pop_to(&mut self, route: &R) -> bool {
if let Some(idx) = self.stack.iter().rposition(|r| r == route) {
let moved = idx + 1 < self.stack.len();
self.stack.truncate(idx + 1);
moved
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, PartialEq, Eq, Debug)]
enum Route {
Home,
List,
Detail(u32),
}
#[test]
fn root_is_never_popped() {
let mut nav = Nav::new(Route::Home);
assert_eq!(nav.depth(), 1);
assert!(!nav.can_pop());
assert_eq!(nav.pop(), None);
assert_eq!(nav.current(), &Route::Home);
}
#[test]
fn push_pop_replace() {
let mut nav = Nav::new(Route::Home);
nav.push(Route::List);
nav.push(Route::Detail(3));
assert_eq!(nav.depth(), 3);
assert_eq!(nav.current(), &Route::Detail(3));
assert!(nav.can_pop());
assert_eq!(nav.pop(), Some(Route::Detail(3)));
assert_eq!(nav.current(), &Route::List);
nav.replace(Route::Detail(9));
assert_eq!(nav.current(), &Route::Detail(9));
assert_eq!(nav.depth(), 2, "replace doesn't grow the stack");
}
#[test]
fn pop_to_root_and_reset() {
let mut nav = Nav::new(Route::Home);
nav.push(Route::List);
nav.push(Route::Detail(1));
assert!(nav.pop_to_root());
assert_eq!(nav.current(), &Route::Home);
assert_eq!(nav.depth(), 1);
assert!(!nav.pop_to_root(), "already at root");
nav.push(Route::List);
nav.reset(Route::Detail(5));
assert_eq!(nav.current(), &Route::Detail(5));
assert_eq!(nav.depth(), 1);
}
#[test]
fn pop_to_a_route() {
let mut nav = Nav::new(Route::Home);
nav.push(Route::List);
nav.push(Route::Detail(1));
nav.push(Route::Detail(2));
assert!(nav.pop_to(&Route::List));
assert_eq!(nav.current(), &Route::List);
assert_eq!(nav.depth(), 2);
assert!(!nav.pop_to(&Route::Detail(9)));
assert!(!nav.pop_to(&Route::List));
}
#[test]
fn stack_and_root_accessors() {
let mut nav = Nav::new(Route::Home);
nav.push(Route::List);
assert_eq!(nav.root(), &Route::Home);
assert_eq!(nav.stack(), &[Route::Home, Route::List]);
}
}