diffing.rs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. //! Diffing Tests
  2. //! -------------
  3. //!
  4. //! These should always compile and run, but the result is not validated for each test.
  5. //! TODO: Validate the results beyond visual inspection.
  6. use bumpalo::Bump;
  7. use anyhow::{Context, Result};
  8. use dioxus::{
  9. arena::SharedResources, diff::DiffMachine, prelude::*, DiffInstruction, DomEdit, MountType,
  10. };
  11. use dioxus_core as dioxus;
  12. use dioxus_html as dioxus_elements;
  13. use futures_util::FutureExt;
  14. struct TestDom {
  15. bump: Bump,
  16. resources: SharedResources,
  17. }
  18. impl TestDom {
  19. fn new() -> TestDom {
  20. let bump = Bump::new();
  21. let resources = SharedResources::new();
  22. TestDom { bump, resources }
  23. }
  24. fn new_factory<'a>(&'a self) -> NodeFactory<'a> {
  25. NodeFactory::new(&self.bump)
  26. }
  27. fn render<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> VNode<'a>
  28. where
  29. F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  30. {
  31. use dioxus_core::nodes::{IntoVNode, IntoVNodeList};
  32. lazy_nodes.into_vnode(NodeFactory::new(&self.bump))
  33. }
  34. fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
  35. // let mut edits = Vec::new();
  36. let mut machine = DiffMachine::new_headless(&self.resources);
  37. machine.stack.push(DiffInstruction::DiffNode { new, old });
  38. machine.mutations
  39. }
  40. fn create<'a, F1>(&'a self, left: LazyNodes<'a, F1>) -> Mutations<'a>
  41. where
  42. F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  43. {
  44. let old = self.bump.alloc(self.render(left));
  45. let mut machine = DiffMachine::new_headless(&self.resources);
  46. machine.stack.create_node(old, MountType::Append);
  47. work_sync(&mut machine);
  48. machine.mutations
  49. }
  50. fn lazy_diff<'a, F1, F2>(
  51. &'a self,
  52. left: LazyNodes<'a, F1>,
  53. right: LazyNodes<'a, F2>,
  54. ) -> (Mutations<'a>, Mutations<'a>)
  55. where
  56. F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  57. F2: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  58. {
  59. let old = self.bump.alloc(self.render(left));
  60. let new = self.bump.alloc(self.render(right));
  61. // let mut create_edits = Vec::new();
  62. let mut machine = DiffMachine::new_headless(&self.resources);
  63. machine.stack.create_node(old, MountType::Append);
  64. work_sync(&mut machine);
  65. let create_edits = machine.mutations;
  66. let mut machine = DiffMachine::new_headless(&self.resources);
  67. machine.stack.push(DiffInstruction::DiffNode { old, new });
  68. work_sync(&mut machine);
  69. let edits = machine.mutations;
  70. (create_edits, edits)
  71. }
  72. }
  73. fn work_sync(machine: &mut DiffMachine) {
  74. let mut fut = machine.work().boxed_local();
  75. while let None = (&mut fut).now_or_never() {
  76. //
  77. }
  78. }
  79. #[test]
  80. fn diffing_works() {}
  81. /// Should push the text node onto the stack and modify it
  82. #[test]
  83. fn html_and_rsx_generate_the_same_output() {
  84. let dom = TestDom::new();
  85. let edits = dom.lazy_diff(
  86. rsx! ( div { "Hello world" } ),
  87. rsx! ( div { "Goodbye world" } ),
  88. );
  89. dbg!(edits);
  90. }
  91. /// Should result in 3 elements on the stack
  92. #[test]
  93. fn fragments_create_properly() {
  94. let dom = TestDom::new();
  95. let Mutations { edits, noderefs } = dom.create(rsx! {
  96. div { "Hello a" }
  97. div { "Hello b" }
  98. div { "Hello c" }
  99. });
  100. assert!(&edits[0].is("CreateElement"));
  101. assert!(&edits[3].is("CreateElement"));
  102. assert!(&edits[6].is("CreateElement"));
  103. assert_eq!(*edits.last().unwrap(), DomEdit::AppendChildren { many: 3 });
  104. dbg!(edits);
  105. }
  106. /// Should result in the creation of an anchor (placeholder) and then a replacewith
  107. #[test]
  108. fn empty_fragments_create_anchors() {
  109. let dom = TestDom::new();
  110. let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
  111. let right = rsx!({ (0..1).map(|f| rsx! { div {}}) });
  112. let edits = dom.lazy_diff(left, right);
  113. dbg!(edits);
  114. }
  115. /// Should result in the creation of an anchor (placeholder) and then a replacewith m=5
  116. #[test]
  117. fn empty_fragments_create_many_anchors() {
  118. let dom = TestDom::new();
  119. let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
  120. let right = rsx!({ (0..5).map(|f| rsx! { div {}}) });
  121. let edits = dom.lazy_diff(left, right);
  122. dbg!(edits);
  123. }
  124. /// Should result in the creation of an anchor (placeholder) and then a replacewith
  125. /// Includes child nodes inside the fragment
  126. #[test]
  127. fn empty_fragments_create_anchors_with_many_children() {
  128. let dom = TestDom::new();
  129. let left = rsx!({ (0..0).map(|f| rsx! { div {} }) });
  130. let right = rsx!({
  131. (0..5).map(|f| {
  132. rsx! { div { "hello" }}
  133. })
  134. });
  135. let edits = dom.lazy_diff(left, right);
  136. dbg!(&edits);
  137. let last_edit = edits.1.edits.last().unwrap();
  138. assert!(last_edit.is("ReplaceWith"));
  139. }
  140. /// Should result in every node being pushed and then replaced with an anchor
  141. #[test]
  142. fn many_items_become_fragment() {
  143. let dom = TestDom::new();
  144. let left = rsx!({
  145. (0..2).map(|f| {
  146. rsx! { div { "hello" }}
  147. })
  148. });
  149. let right = rsx!({ (0..0).map(|f| rsx! { div {} }) });
  150. let edits = dom.lazy_diff(left, right);
  151. dbg!(&edits);
  152. }
  153. /// Should result in no edits
  154. #[test]
  155. fn two_equal_fragments_are_equal() {
  156. let dom = TestDom::new();
  157. let left = rsx!({
  158. (0..2).map(|f| {
  159. rsx! { div { "hello" }}
  160. })
  161. });
  162. let right = rsx!({
  163. (0..2).map(|f| {
  164. rsx! { div { "hello" }}
  165. })
  166. });
  167. let edits = dom.lazy_diff(left, right);
  168. dbg!(&edits);
  169. assert!(edits.1.edits.is_empty());
  170. }
  171. /// Should result the creation of more nodes appended after the old last node
  172. #[test]
  173. fn two_fragments_with_differrent_elements_are_differet() {
  174. let dom = TestDom::new();
  175. let left = rsx!(
  176. {(0..2).map(|f| {rsx! { div { }}})}
  177. p {}
  178. );
  179. let right = rsx!(
  180. {(0..5).map(|f| {rsx! { h1 { }}})}
  181. p {}
  182. );
  183. let edits = dom.lazy_diff(left, right);
  184. dbg!(&edits);
  185. }
  186. /// Should result in multiple nodes destroyed - with changes to the first nodes
  187. #[test]
  188. fn two_fragments_with_differrent_elements_are_differet_shorter() {
  189. let dom = TestDom::new();
  190. let left = rsx!(
  191. {(0..5).map(|f| {rsx! { div { }}})}
  192. p {}
  193. );
  194. let right = rsx!(
  195. {(0..2).map(|f| {rsx! { h1 { }}})}
  196. p {}
  197. );
  198. let edits = dom.lazy_diff(left, right);
  199. dbg!(&edits);
  200. }
  201. /// Should result in multiple nodes destroyed - with no changes
  202. #[test]
  203. fn two_fragments_with_same_elements_are_differet() {
  204. let dom = TestDom::new();
  205. let left = rsx!(
  206. {(0..2).map(|f| {rsx! { div { }}})}
  207. p {}
  208. );
  209. let right = rsx!(
  210. {(0..5).map(|f| {rsx! { div { }}})}
  211. p {}
  212. );
  213. let edits = dom.lazy_diff(left, right);
  214. dbg!(&edits);
  215. }
  216. // Similar test from above, but with extra child nodes
  217. #[test]
  218. fn two_fragments_with_same_elements_are_differet_shorter() {
  219. let dom = TestDom::new();
  220. let left = rsx!(
  221. {(0..5).map(|f| {rsx! { div { }}})}
  222. p {"e"}
  223. );
  224. let right = rsx!(
  225. {(0..2).map(|f| {rsx! { div { }}})}
  226. p {"e"}
  227. );
  228. let edits = dom.lazy_diff(left, right);
  229. dbg!(&edits);
  230. }
  231. /// should result in the removal of elements
  232. #[test]
  233. fn keyed_diffing_order() {
  234. let dom = TestDom::new();
  235. let left = rsx!(
  236. {(0..5).map(|f| {rsx! { div { key: "{f}" }}})}
  237. p {"e"}
  238. );
  239. let right = rsx!(
  240. {(0..2).map(|f| {rsx! { div { key: "{f}" }}})}
  241. p {"e"}
  242. );
  243. let edits = dom.lazy_diff(left, right);
  244. dbg!(&edits);
  245. }
  246. #[test]
  247. fn fragment_keys() {
  248. let r = 1;
  249. let p = rsx! {
  250. Fragment { key: "asd {r}" }
  251. };
  252. }
  253. /// Should result in moves, but not removals or additions
  254. #[test]
  255. fn keyed_diffing_out_of_order() {
  256. let dom = TestDom::new();
  257. // 0, 1, 2, 3, 4, 5, 6, 7, 8,
  258. let left = rsx!({
  259. (0..3).chain(3..6).chain(6..9).map(|f| {
  260. rsx! { div { key: "{f}" }}
  261. })
  262. });
  263. // 0, 1, 2, 6, 5, 4, 3, 7, 8, 9
  264. let right = rsx!({
  265. (0..3).chain((3..7).rev()).chain(7..10).map(|f| {
  266. rsx! { div { key: "{f}" }}
  267. })
  268. });
  269. // LIS: 3, 7, 8,
  270. let edits = dom.lazy_diff(left, right);
  271. dbg!(&edits);
  272. }
  273. #[test]
  274. fn controlled_keyed_diffing_out_of_order() {
  275. let dom = TestDom::new();
  276. let left = [4, 5, 6, 7];
  277. let left = rsx!({
  278. left.iter().map(|f| {
  279. rsx! { div { key: "{f}" "{f}" }}
  280. })
  281. });
  282. // 0, 1, 2, 6, 5, 4, 3, 7, 8, 9
  283. let right = [0, 5, 9, 6, 4];
  284. let right = rsx!({
  285. right.iter().map(|f| {
  286. rsx! { div { key: "{f}" "{f}" }}
  287. })
  288. });
  289. // LIS: 3, 7, 8,
  290. let edits = dom.lazy_diff(left, right);
  291. dbg!(&edits);
  292. }