This commit is contained in:
Evan Almloff committed 2022-12-07 20:28:01 -06:00
1 parent cdb1e76f6f
commit 029255c7ac
19 files changed
+1562 -27

No files matched your search

+484
View File
@@ -0,0 +1,484 @@
use std::cmp::Ordering;
use dioxus_html::input_data::keyboard_types::{Code, Key, Modifiers};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Pos {
pub col: usize,
pub row: usize,
}
impl Pos {
pub fn new(col: usize, row: usize) -> Self {
Self { row, col }
}
pub fn up(&mut self, rope: &str) {
self.move_row(-1, rope);
}
pub fn down(&mut self, rope: &str) {
self.move_row(1, rope);
}
pub fn right(&mut self, rope: &str) {
self.move_col(1, rope);
}
pub fn left(&mut self, rope: &str) {
self.move_col(-1, rope);
}
pub fn move_row(&mut self, change: i32, rope: &str) {
let new = self.row as i32 + change;
if new >= 0 && new < rope.lines().count() as i32 {
self.row = new as usize;
}
}
pub fn move_col(&mut self, change: i32, rope: &str) {
self.realize_col(rope);
let idx = self.idx(rope) as i32;
if idx + change >= 0 && idx + change <= rope.len() as i32 {
let len_line = self.len_line(rope) as i32;
let new_col = self.col as i32 + change;
let diff = new_col - len_line;
if diff > 0 {
self.down(rope);
self.col = 0;
self.move_col(diff - 1, rope);
} else if new_col < 0 {
self.up(rope);
self.col = self.len_line(rope);
self.move_col(new_col + 1, rope);
} else {
self.col = new_col as usize;
}
}
}
pub fn col(&self, rope: &str) -> usize {
self.col.min(self.len_line(rope))
}
pub fn row(&self) -> usize {
self.row
}
fn len_line(&self, rope: &str) -> usize {
let line = rope.lines().nth(self.row).unwrap_or_default();
let len = line.len();
if len > 0 && line.chars().nth(len - 1) == Some('\n') {
len - 1
} else {
len
}
}
pub fn idx(&self, rope: &str) -> usize {
rope.lines().take(self.row).map(|l| l.len()).sum::<usize>() + self.col(rope)
}
// the column can be more than the line length, cap it
pub fn realize_col(&mut self, rope: &str) {
self.col = self.col(rope);
}
}
impl Ord for Pos {
fn cmp(&self, other: &Self) -> Ordering {
self.row.cmp(&other.row).then(self.col.cmp(&other.col))
}
}
impl PartialOrd for Pos {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cursor {
pub start: Pos,
pub end: Option<Pos>,
}
impl Cursor {
pub fn from_start(pos: Pos) -> Self {
Self {
start: pos,
end: None,
}
}
pub fn new(start: Pos, end: Pos) -> Self {
Self {
start,
end: Some(end),
}
}
fn move_cursor(&mut self, f: impl FnOnce(&mut Pos), shift: bool) {
if shift {
self.with_end(f);
} else {
f(&mut self.start);
self.end = None;
}
}
fn delete_selection(&mut self, text: &mut String) -> [i32; 2] {
let first = self.first();
let last = self.last();
let dr = first.row as i32 - last.row as i32;
let dc = if dr != 0 {
-(last.col as i32)
} else {
first.col as i32 - last.col as i32
};
text.replace_range(first.idx(text)..last.idx(text), "");
if let Some(end) = self.end.take() {
if self.start > end {
self.start = end;
}
}
[dc, dr]
}
pub fn handle_input(
&mut self,
data: &dioxus_html::KeyboardData,
text: &mut String,
max_width: usize,
) {
use Code::*;
match data.code() {
ArrowUp => {
self.move_cursor(|c| c.up(text), data.modifiers().contains(Modifiers::SHIFT));
}
ArrowDown => {
self.move_cursor(
|c| c.down(text),
data.modifiers().contains(Modifiers::SHIFT),
);
}
ArrowRight => {
if data.modifiers().contains(Modifiers::CONTROL) {
self.move_cursor(
|c| {
let mut change = 1;
let idx = c.idx(text);
let length = text.len();
while idx + change < length {
let chr = text.chars().nth(idx + change).unwrap();
if chr.is_whitespace() {
break;
}
change += 1;
}
c.move_col(change as i32, text);
},
data.modifiers().contains(Modifiers::SHIFT),
);
} else {
self.move_cursor(
|c| c.right(text),
data.modifiers().contains(Modifiers::SHIFT),
);
}
}
ArrowLeft => {
if data.modifiers().contains(Modifiers::CONTROL) {
self.move_cursor(
|c| {
let mut change = -1;
let idx = c.idx(text) as i32;
while idx + change > 0 {
let chr = text.chars().nth((idx + change) as usize).unwrap();
if chr == ' ' {
break;
}
change -= 1;
}
c.move_col(change as i32, text);
},
data.modifiers().contains(Modifiers::SHIFT),
);
} else {
self.move_cursor(
|c| c.left(text),
data.modifiers().contains(Modifiers::SHIFT),
);
}
}
End => {
self.move_cursor(
|c| c.col = c.len_line(text),
data.modifiers().contains(Modifiers::SHIFT),
);
}
Home => {
self.move_cursor(|c| c.col = 0, data.modifiers().contains(Modifiers::SHIFT));
}
Backspace => {
self.start.realize_col(text);
let mut start_idx = self.start.idx(text);
if self.end.is_some() {
self.delete_selection(text);
} else if start_idx > 0 {
self.start.left(text);
text.replace_range(start_idx - 1..start_idx, "");
if data.modifiers().contains(Modifiers::CONTROL) {
start_idx = self.start.idx(text);
while start_idx > 0
&& text
.chars()
.nth(start_idx - 1)
.filter(|c| *c != ' ')
.is_some()
{
self.start.left(text);
text.replace_range(start_idx - 1..start_idx, "");
start_idx = self.start.idx(text);
}
}
}
}
Enter => {
if text.len() + 1 - self.selection_len(text) <= max_width {
text.insert(self.start.idx(text), '\n');
self.start.col = 0;
self.start.down(text);
}
}
Tab => {
if text.len() + 1 - self.selection_len(text) <= max_width {
self.start.realize_col(text);
self.delete_selection(text);
text.insert(self.start.idx(text), '\t');
self.start.right(text);
}
}
_ => {
self.start.realize_col(text);
if let Key::Character(character) = data.key() {
if text.len() + 1 - self.selection_len(text) <= max_width {
self.delete_selection(text);
let character = character.chars().next().unwrap();
text.insert(self.start.idx(text), character);
self.start.right(text);
}
}
}
}
}
pub fn with_end(&mut self, f: impl FnOnce(&mut Pos)) {
let mut new = self.end.take().unwrap_or_else(|| self.start.clone());
f(&mut new);
self.end.replace(new);
}
pub fn first(&self) -> &Pos {
if let Some(e) = &self.end {
e.min(&self.start)
} else {
&self.start
}
}
pub fn last(&self) -> &Pos {
if let Some(e) = &self.end {
e.max(&self.start)
} else {
&self.start
}
}
pub fn selection_len(&self, text: &str) -> usize {
self.last().idx(text) - self.first().idx(text)
}
}
impl Default for Cursor {
fn default() -> Self {
Self {
start: Pos::new(0, 0),
end: None,
}
}
}
#[test]
fn pos_direction_movement() {
let mut pos = Pos::new(100, 0);
let text = "hello world\nhi";
assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
pos.down(text);
assert_eq!(pos.col(text), text.lines().nth(1).unwrap_or_default().len());
pos.up(text);
assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
pos.left(text);
assert_eq!(
pos.col(text),
text.lines().next().unwrap_or_default().len() - 1
);
pos.right(text);
assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
}
#[test]
fn pos_col_movement() {
let mut pos = Pos::new(100, 0);
let text = "hello world\nhi";
// move inside a row
pos.move_col(-5, text);
assert_eq!(
pos.col(text),
text.lines().next().unwrap_or_default().len() - 5
);
pos.move_col(5, text);
assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
// move between rows
pos.move_col(3, text);
assert_eq!(pos.col(text), 2);
pos.move_col(-3, text);
assert_eq!(pos.col(text), text.lines().next().unwrap_or_default().len());
// don't panic if moving out of range
pos.move_col(-100, text);
pos.move_col(1000, text);
}
#[test]
fn cursor_row_movement() {
let mut pos = Pos::new(100, 0);
let text = "hello world\nhi";
pos.move_row(1, text);
assert_eq!(pos.row(), 1);
pos.move_row(-1, text);
assert_eq!(pos.row(), 0);
// don't panic if moving out of range
pos.move_row(-100, text);
pos.move_row(1000, text);
}
#[test]
fn cursor_input() {
let mut cursor = Cursor::from_start(Pos::new(0, 0));
let mut text = "hello world\nhi".to_string();
for _ in 0..5 {
cursor.handle_input(
&dioxus_html::KeyboardData::new(
dioxus_html::input_data::keyboard_types::Key::ArrowRight,
dioxus_html::input_data::keyboard_types::Code::ArrowRight,
dioxus_html::input_data::keyboard_types::Location::Standard,
false,
Modifiers::empty(),
),
&mut text,
10,
);
}
for _ in 0..5 {
cursor.handle_input(
&dioxus_html::KeyboardData::new(
dioxus_html::input_data::keyboard_types::Key::Backspace,
dioxus_html::input_data::keyboard_types::Code::Backspace,
dioxus_html::input_data::keyboard_types::Location::Standard,
false,
Modifiers::empty(),
),
&mut text,
10,
);
}
assert_eq!(text, " world\nhi");
let goal_text = "hello world\nhi";
let max_width = goal_text.len();
cursor.handle_input(
&dioxus_html::KeyboardData::new(
dioxus_html::input_data::keyboard_types::Key::Character("h".to_string()),
dioxus_html::input_data::keyboard_types::Code::KeyH,
dioxus_html::input_data::keyboard_types::Location::Standard,
false,
Modifiers::empty(),
),
&mut text,
max_width,
);
cursor.handle_input(
&dioxus_html::KeyboardData::new(
dioxus_html::input_data::keyboard_types::Key::Character("e".to_string()),
dioxus_html::input_data::keyboard_types::Code::KeyE,
dioxus_html::input_data::keyboard_types::Location::Standard,
false,
Modifiers::empty(),
),
&mut text,
max_width,
);
cursor.handle_input(
&dioxus_html::KeyboardData::new(
dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
dioxus_html::input_data::keyboard_types::Code::KeyL,
dioxus_html::input_data::keyboard_types::Location::Standard,
false,
Modifiers::empty(),
),
&mut text,
max_width,
);
cursor.handle_input(
&dioxus_html::KeyboardData::new(
dioxus_html::input_data::keyboard_types::Key::Character("l".to_string()),
dioxus_html::input_data::keyboard_types::Code::KeyL,
dioxus_html::input_data::keyboard_types::Location::Standard,
false,
Modifiers::empty(),
),
&mut text,
max_width,
);
cursor.handle_input(
&dioxus_html::KeyboardData::new(
dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
dioxus_html::input_data::keyboard_types::Code::KeyO,
dioxus_html::input_data::keyboard_types::Location::Standard,
false,
Modifiers::empty(),
),
&mut text,
max_width,
);
// these should be ignored
for _ in 0..10 {
cursor.handle_input(
&dioxus_html::KeyboardData::new(
dioxus_html::input_data::keyboard_types::Key::Character("o".to_string()),
dioxus_html::input_data::keyboard_types::Code::KeyO,
dioxus_html::input_data::keyboard_types::Location::Standard,
false,
Modifiers::empty(),
),
&mut text,
max_width,
);
}
assert_eq!(text.to_string(), goal_text);
}
+3
View File
@@ -0,0 +1,3 @@
mod persistant_iterator;
pub use persistant_iterator::*;
pub mod cursor;
@@ -0,0 +1,528 @@
use crate::{node::NodeType, real_dom::RealDom, state::State, tree::TreeView, NodeId, RealNodeId};
use dioxus_core::{Mutation, Mutations};
use std::fmt::Debug;
#[derive(Debug)]
pub enum ElementProduced {
/// The iterator produced an element by progressing to the next node in a depth first order.
Progressed(RealNodeId),
/// The iterator reached the end of the tree and looped back to the root
Looped(RealNodeId),
}
impl ElementProduced {
pub fn id(&self) -> RealNodeId {
match self {
ElementProduced::Progressed(id) => *id,
ElementProduced::Looped(id) => *id,
}
}
}
#[derive(Debug)]
enum NodePosition {
AtNode,
InChild(usize),
}
impl NodePosition {
fn map(&self, mut f: impl FnMut(usize) -> usize) -> Self {
match self {
Self::AtNode => Self::AtNode,
Self::InChild(i) => Self::InChild(f(*i)),
}
}
fn get_or_insert(&mut self, child_idx: usize) -> usize {
match self {
Self::AtNode => {
*self = Self::InChild(child_idx);
child_idx
}
Self::InChild(i) => *i,
}
}
}
/// Focus systems need a iterator that can persist through changes in the [dioxus_core::VirtualDom].
/// This iterator traverses the tree depth first.
/// Iterate through it with [PersistantElementIter::next] [PersistantElementIter::prev], and update it with [PersistantElementIter::prune] (with data from [`dioxus_core::prelude::VirtualDom::work_with_deadline`]).
/// The iterator loops around when it reaches the end or the beginning.
pub struct PersistantElementIter {
// stack of elements and fragments
stack: smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
}
impl Default for PersistantElementIter {
fn default() -> Self {
PersistantElementIter {
stack: smallvec::smallvec![(NodeId(0), NodePosition::AtNode)],
}
}
}
impl PersistantElementIter {
pub fn new() -> Self {
Self::default()
}
/// remove stale element refreneces
/// returns true if the focused element is removed
pub fn prune<S: State>(&mut self, mutations: &Mutations, rdom: &RealDom<S>) -> bool {
let mut changed = false;
let ids_removed: Vec<_> = mutations
.edits
.iter()
.filter_map(|m| {
// nodes within templates will never be removedns
if let Mutation::Remove { id } = m {
Some(rdom.element_to_node_id(*id))
} else {
None
}
})
.collect();
// if any element is removed in the chain, remove it and its children from the stack
if let Some(r) = self
.stack
.iter()
.position(|(el_id, _)| ids_removed.iter().any(|id| el_id == id))
{
self.stack.truncate(r);
changed = true;
}
// if a child is removed or inserted before or at the current element, update the child index
for (el_id, child_idx) in self.stack.iter_mut() {
if let NodePosition::InChild(child_idx) = child_idx {
if let Some(children) = &rdom.tree.children_ids(*el_id) {
for m in &mutations.edits {
match m {
Mutation::Remove { id } => {
let id = rdom.element_to_node_id(*id);
if children.iter().take(*child_idx + 1).any(|c| *c == id) {
*child_idx -= 1;
}
}
Mutation::InsertBefore { id, m } => {
let id = rdom.element_to_node_id(*id);
if children.iter().take(*child_idx + 1).any(|c| *c == id) {
*child_idx += *m;
}
}
Mutation::InsertAfter { id, m } => {
let id = rdom.element_to_node_id(*id);
if children.iter().take(*child_idx).any(|c| *c == id) {
*child_idx += *m;
}
}
_ => (),
}
}
}
}
}
changed
}
/// get the next element
pub fn next<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
if self.stack.is_empty() {
let id = NodeId(0);
let new = (id, NodePosition::AtNode);
self.stack.push(new);
ElementProduced::Looped(id)
} else {
let (last, old_child_idx) = self.stack.last_mut().unwrap();
let node = &rdom[*last];
match &node.node_data.node_type {
NodeType::Element { .. } => {
let children = rdom.tree.children_ids(*last).unwrap();
*old_child_idx = old_child_idx.map(|i| i + 1);
// if we have children, go to the next child
let child_idx = old_child_idx.get_or_insert(0);
if child_idx >= children.len() {
self.pop();
self.next(rdom)
} else {
let id = children[child_idx];
if let NodeType::Element { .. } = &rdom[id].node_data.node_type {
self.stack.push((id, NodePosition::AtNode));
}
ElementProduced::Progressed(id)
}
}
NodeType::Text { .. } | NodeType::Placeholder { .. } => {
// we are at a leaf, so we are done
ElementProduced::Progressed(self.pop())
}
}
}
}
/// get the previous element
pub fn prev<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
// recursively add the last child element to the stack
fn push_back<S: State>(
stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
new_node: RealNodeId,
rdom: &RealDom<S>,
) -> RealNodeId {
match &rdom[new_node].node_data.node_type {
NodeType::Element { .. } => {
let children = rdom.tree.children_ids(new_node).unwrap();
if children.is_empty() {
new_node
} else {
stack.push((new_node, NodePosition::InChild(children.len() - 1)));
push_back(stack, *children.last().unwrap(), rdom)
}
}
_ => new_node,
}
}
if self.stack.is_empty() {
let new_node = NodeId(0);
ElementProduced::Looped(push_back(&mut self.stack, new_node, rdom))
} else {
let (last, old_child_idx) = self.stack.last_mut().unwrap();
let node = &rdom[*last];
match &node.node_data.node_type {
NodeType::Element { .. } => {
let children = rdom.tree.children_ids(*last).unwrap();
// if we have children, go to the next child
if let NodePosition::InChild(0) = old_child_idx {
ElementProduced::Progressed(self.pop())
} else {
*old_child_idx = old_child_idx.map(|i| i - 1);
if let NodePosition::InChild(child_idx) = old_child_idx {
if *child_idx >= children.len() || children.is_empty() {
self.pop();
self.prev(rdom)
} else {
let new_node = children[*child_idx];
ElementProduced::Progressed(push_back(
&mut self.stack,
new_node,
rdom,
))
}
} else {
self.pop();
self.prev(rdom)
}
}
}
NodeType::Text { .. } | NodeType::Placeholder { .. } => {
// we are at a leaf, so we are done
ElementProduced::Progressed(self.pop())
}
}
}
}
fn pop(&mut self) -> RealNodeId {
self.stack.pop().unwrap().0
}
}
#[derive(Default, Clone, Debug)]
struct Empty {}
impl State for Empty {
const PASSES: &'static [crate::AnyPass<crate::node::Node<Self>>] = &[];
const MASKS: &'static [crate::NodeMask] = &[];
}
#[test]
#[allow(unused_variables)]
fn traverse() {
use dioxus::prelude::*;
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
render!(
div{
div{
"hello"
p{
"world"
}
"hello world"
}
}
)
}
let mut vdom = VirtualDom::new(Base);
let mutations = vdom.rebuild();
let mut rdom: RealDom<Empty> = RealDom::new();
let _to_update = rdom.apply_mutations(mutations);
let mut iter = PersistantElementIter::new();
let div_tag = "div".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
let text1 = "hello".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Text { text: text1, .. }
));
let p_tag = "p".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
let text2 = "world".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Text { text: text2, .. }
));
let text3 = "hello world".to_string();
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Text { text: text3, .. }
));
assert!(matches!(
&rdom[iter.next(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Text { text: text3, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Text { text: text2, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Text { text: text1, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
assert!(matches!(
&rdom[iter.prev(&rdom).id()].node_data.node_type,
NodeType::Text { text: text3, .. }
));
}
#[test]
#[allow(unused_variables)]
fn persist_removes() {
use dioxus::prelude::*;
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
let children = match cx.generation() % 2 {
0 => 3,
1 => 2,
_ => unreachable!(),
};
render!(
div{
(0..children).map(|i|{
rsx!{
p{
key: "{i}",
"{i}"
}
}
})
}
)
}
let mut vdom = VirtualDom::new(Base);
let mut rdom: RealDom<Empty> = RealDom::new();
let build = vdom.rebuild();
let _to_update = rdom.apply_mutations(build);
// this will end on the node that is removed
let mut iter1 = PersistantElementIter::new();
// this will end on the after node that is removed
let mut iter2 = PersistantElementIter::new();
// div
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// p
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// "1"
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// p
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// "2"
iter1.next(&rdom).id();
iter2.next(&rdom).id();
// p
iter2.next(&rdom).id();
// "3"
iter2.next(&rdom).id();
vdom.mark_dirty(ScopeId(0));
let update = vdom.render_immediate();
iter1.prune(&update, &rdom);
iter2.prune(&update, &rdom);
let _to_update = rdom.apply_mutations(update);
let p_tag = "1".to_string();
let idx = iter1.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
let text = "2".to_string();
let idx = iter1.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Text { text, .. }
));
let div_tag = "div".to_string();
let idx = iter2.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Element { tag: div_tag, .. }
));
}
#[test]
#[allow(unused_variables)]
fn persist_instertions_before() {
use dioxus::prelude::*;
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
let children = match cx.generation() % 2 {
0 => 3,
1 => 2,
_ => unreachable!(),
};
render!(
div{
(0..children).map(|i|{
rsx!{
p{
key: "{i}",
"{i}"
}
}
})
}
)
}
let mut vdom = VirtualDom::new(Base);
let mut rdom: RealDom<Empty> = RealDom::new();
let build = vdom.rebuild();
let _to_update = rdom.apply_mutations(build);
let mut iter = PersistantElementIter::new();
// div
iter.next(&rdom).id();
// p
iter.next(&rdom).id();
// "1"
iter.next(&rdom).id();
// p
iter.next(&rdom).id();
// "2"
iter.next(&rdom).id();
vdom.mark_dirty(ScopeId(0));
let update = vdom.render_immediate();
iter.prune(&update, &rdom);
let _to_update = rdom.apply_mutations(update);
let p_tag = "div".to_string();
let idx = iter.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
}
#[test]
#[allow(unused_variables)]
fn persist_instertions_after() {
use dioxus::prelude::*;
#[allow(non_snake_case)]
fn Base(cx: Scope) -> Element {
let children = match cx.generation() % 2 {
0 => 3,
1 => 2,
_ => unreachable!(),
};
render!(
div{
(0..children).map(|i|{
rsx!{
p{
key: "{i}",
"{i}"
}
}
})
}
)
}
let mut vdom = VirtualDom::new(Base);
let mut rdom: RealDom<Empty> = RealDom::new();
let build = vdom.rebuild();
let _to_update = rdom.apply_mutations(build);
let mut iter = PersistantElementIter::new();
// div
iter.next(&rdom).id();
// p
iter.next(&rdom).id();
// "hello"
iter.next(&rdom).id();
// p
iter.next(&rdom).id();
// "world"
iter.next(&rdom).id();
let update = vdom.rebuild();
iter.prune(&update, &rdom);
let _to_update = rdom.apply_mutations(update);
let p_tag = "p".to_string();
let idx = iter.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Element { tag: p_tag, .. }
));
let text = "hello world".to_string();
let idx = iter.next(&rdom).id();
assert!(matches!(
&rdom[idx].node_data.node_type,
NodeType::Text { text, .. }
));
}