use rdom_core::NodeId;
use crate::style::CounterOp;
#[derive(Debug, Clone)]
struct Instance {
name: String,
value: i32,
scope_parent: Option<NodeId>,
}
#[derive(Debug, Default, Clone)]
pub(super) struct CounterState {
instances: Vec<Instance>,
}
impl CounterState {
pub(super) fn enter(
&mut self,
parent: Option<NodeId>,
reset: &[CounterOp],
increment: &[CounterOp],
) {
for op in reset {
self.instances.push(Instance {
name: op.name.clone(),
value: op.value,
scope_parent: parent,
});
}
for op in increment {
match self.instances.iter_mut().rev().find(|i| i.name == op.name) {
Some(inst) => inst.value = inst.value.saturating_add(op.value),
None => self.instances.push(Instance {
name: op.name.clone(),
value: op.value,
scope_parent: parent,
}),
}
}
}
pub(super) fn exit(&mut self, element: NodeId) {
self.instances.retain(|i| i.scope_parent != Some(element));
}
pub(super) fn value(&self, name: &str) -> i32 {
self.instances
.iter()
.rev()
.find(|i| i.name == name)
.map_or(0, |i| i.value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rdom_core::Dom;
fn op(name: &str, value: i32) -> CounterOp {
CounterOp {
name: name.into(),
value,
}
}
#[test]
fn nested_lists_scope_and_resume() {
let mut dom: Dom = Dom::new();
let root = dom.root();
let ol = dom.create_element("ol");
let li1 = dom.create_element("li");
let inner = dom.create_element("ol");
let inner_li = dom.create_element("li");
let li2 = dom.create_element("li");
let ol2 = dom.create_element("ol");
dom.append_child(root, ol).unwrap();
dom.append_child(ol, li1).unwrap();
dom.append_child(li1, inner).unwrap();
dom.append_child(inner, inner_li).unwrap();
dom.append_child(ol, li2).unwrap();
dom.append_child(root, ol2).unwrap();
let mut st = CounterState::default();
st.enter(Some(root), &[op("list-item", 0)], &[]); st.enter(Some(ol), &[], &[op("list-item", 1)]); assert_eq!(st.value("list-item"), 1);
st.enter(Some(li1), &[op("list-item", 0)], &[]); st.enter(Some(inner), &[], &[op("list-item", 1)]); assert_eq!(st.value("list-item"), 1);
st.exit(inner_li);
st.exit(inner); assert_eq!(
st.value("list-item"),
1,
"still in the inner <ol>'s scope (a following sibling would see it)"
);
st.exit(li1); assert_eq!(st.value("list-item"), 1, "outer count resumes");
st.enter(Some(ol), &[], &[op("list-item", 1)]); assert_eq!(st.value("list-item"), 2);
st.exit(li2);
st.exit(ol);
st.enter(Some(root), &[op("list-item", 0)], &[]);
st.enter(Some(ol2), &[], &[op("list-item", 1)]);
assert_eq!(st.value("list-item"), 1);
}
#[test]
fn increment_without_reset_creates_the_counter_and_missing_reads_zero() {
let mut st = CounterState::default();
assert_eq!(st.value("x"), 0);
st.enter(None, &[], &[op("x", 5)]);
assert_eq!(st.value("x"), 5);
st.enter(None, &[], &[op("x", -2)]);
assert_eq!(st.value("x"), 3);
}
}