diffing.rs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719
  1. //! Diffing Tests
  2. //! -------------
  3. //!
  4. //! These should always compile and run, but the result is not validat root: (), m: () ed 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. mod test_logging;
  15. use DomEdit::*;
  16. struct TestDom {
  17. bump: Bump,
  18. resources: SharedResources,
  19. }
  20. impl TestDom {
  21. fn new() -> TestDom {
  22. test_logging::set_up_logging();
  23. let bump = Bump::new();
  24. let resources = SharedResources::new();
  25. TestDom { bump, resources }
  26. }
  27. fn new_factory<'a>(&'a self) -> NodeFactory<'a> {
  28. NodeFactory::new(&self.bump)
  29. }
  30. fn render<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> VNode<'a>
  31. where
  32. F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  33. {
  34. use dioxus_core::nodes::{IntoVNode, IntoVNodeList};
  35. lazy_nodes.into_vnode(NodeFactory::new(&self.bump))
  36. }
  37. fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
  38. // let mut edits = Vec::new();
  39. let mut machine = DiffMachine::new_headless(&self.resources);
  40. machine.stack.push(DiffInstruction::DiffNode { new, old });
  41. machine.mutations
  42. }
  43. fn create<'a, F1>(&'a self, left: LazyNodes<'a, F1>) -> Mutations<'a>
  44. where
  45. F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  46. {
  47. let old = self.bump.alloc(self.render(left));
  48. let mut machine = DiffMachine::new_headless(&self.resources);
  49. machine.stack.create_node(old, MountType::Append);
  50. work_sync(&mut machine);
  51. machine.mutations
  52. }
  53. fn lazy_diff<'a, F1, F2>(
  54. &'a self,
  55. left: LazyNodes<'a, F1>,
  56. right: LazyNodes<'a, F2>,
  57. ) -> (Mutations<'a>, Mutations<'a>)
  58. where
  59. F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  60. F2: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  61. {
  62. let old = self.bump.alloc(self.render(left));
  63. let new = self.bump.alloc(self.render(right));
  64. // let mut create_edits = Vec::new();
  65. let mut machine = DiffMachine::new_headless(&self.resources);
  66. machine.stack.create_node(old, MountType::Append);
  67. work_sync(&mut machine);
  68. let create_edits = machine.mutations;
  69. let mut machine = DiffMachine::new_headless(&self.resources);
  70. machine.stack.push(DiffInstruction::DiffNode { old, new });
  71. work_sync(&mut machine);
  72. let edits = machine.mutations;
  73. (create_edits, edits)
  74. }
  75. }
  76. fn work_sync(machine: &mut DiffMachine) {
  77. let mut fut = machine.work().boxed_local();
  78. while let None = (&mut fut).now_or_never() {
  79. //
  80. }
  81. }
  82. #[test]
  83. fn diffing_works() {}
  84. /// Should push the text node onto the stack and modify it
  85. #[test]
  86. fn html_and_rsx_generate_the_same_output() {
  87. let dom = TestDom::new();
  88. let (create, change) = dom.lazy_diff(
  89. rsx! ( div { "Hello world" } ),
  90. rsx! ( div { "Goodbye world" } ),
  91. );
  92. assert_eq!(
  93. create.edits,
  94. [
  95. CreateElement { id: 0, tag: "div" },
  96. CreateTextNode {
  97. id: 1,
  98. text: "Hello world"
  99. },
  100. AppendChildren { many: 1 },
  101. AppendChildren { many: 1 },
  102. ]
  103. );
  104. assert_eq!(
  105. change.edits,
  106. [
  107. PushRoot { id: 1 },
  108. SetText {
  109. text: "Goodbye world"
  110. },
  111. PopRoot
  112. ]
  113. );
  114. }
  115. /// Should result in 3 elements on the stack
  116. #[test]
  117. fn fragments_create_properly() {
  118. let dom = TestDom::new();
  119. let create = dom.create(rsx! {
  120. div { "Hello a" }
  121. div { "Hello b" }
  122. div { "Hello c" }
  123. });
  124. assert_eq!(
  125. create.edits,
  126. [
  127. CreateElement { id: 0, tag: "div" },
  128. CreateTextNode {
  129. id: 1,
  130. text: "Hello a"
  131. },
  132. AppendChildren { many: 1 },
  133. CreateElement { id: 2, tag: "div" },
  134. CreateTextNode {
  135. id: 3,
  136. text: "Hello b"
  137. },
  138. AppendChildren { many: 1 },
  139. CreateElement { id: 4, tag: "div" },
  140. CreateTextNode {
  141. id: 5,
  142. text: "Hello c"
  143. },
  144. AppendChildren { many: 1 },
  145. AppendChildren { many: 3 },
  146. ]
  147. );
  148. }
  149. /// Should result in the creation of an anchor (placeholder) and then a replacewith
  150. #[test]
  151. fn empty_fragments_create_anchors() {
  152. let dom = TestDom::new();
  153. let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
  154. let right = rsx!({ (0..1).map(|f| rsx! { div {}}) });
  155. let (create, change) = dom.lazy_diff(left, right);
  156. assert_eq!(
  157. create.edits,
  158. [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }]
  159. );
  160. assert_eq!(
  161. change.edits,
  162. [
  163. CreateElement { id: 1, tag: "div" },
  164. ReplaceWith { m: 1, root: 0 }
  165. ]
  166. );
  167. }
  168. /// Should result in the creation of an anchor (placeholder) and then a replacewith m=5
  169. #[test]
  170. fn empty_fragments_create_many_anchors() {
  171. let dom = TestDom::new();
  172. let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
  173. let right = rsx!({ (0..5).map(|f| rsx! { div {}}) });
  174. let (create, change) = dom.lazy_diff(left, right);
  175. assert_eq!(
  176. create.edits,
  177. [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }]
  178. );
  179. assert_eq!(
  180. change.edits,
  181. [
  182. CreateElement { id: 1, tag: "div" },
  183. CreateElement { id: 2, tag: "div" },
  184. CreateElement { id: 3, tag: "div" },
  185. CreateElement { id: 4, tag: "div" },
  186. CreateElement { id: 5, tag: "div" },
  187. ReplaceWith { m: 5, root: 0 }
  188. ]
  189. );
  190. }
  191. /// Should result in the creation of an anchor (placeholder) and then a replacewith
  192. /// Includes child nodes inside the fragment
  193. #[test]
  194. fn empty_fragments_create_anchors_with_many_children() {
  195. let dom = TestDom::new();
  196. let left = rsx!({ (0..0).map(|f| rsx! { div {} }) });
  197. let right = rsx!({
  198. (0..3).map(|f| {
  199. rsx! { div { "hello: {f}" }}
  200. })
  201. });
  202. let (create, change) = dom.lazy_diff(left, right);
  203. assert_eq!(
  204. create.edits,
  205. [CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }]
  206. );
  207. assert_eq!(
  208. change.edits,
  209. [
  210. CreateElement { id: 1, tag: "div" },
  211. CreateTextNode {
  212. text: "hello: 0",
  213. id: 2
  214. },
  215. AppendChildren { many: 1 },
  216. CreateElement { id: 3, tag: "div" },
  217. CreateTextNode {
  218. text: "hello: 1",
  219. id: 4
  220. },
  221. AppendChildren { many: 1 },
  222. CreateElement { id: 5, tag: "div" },
  223. CreateTextNode {
  224. text: "hello: 2",
  225. id: 6
  226. },
  227. AppendChildren { many: 1 },
  228. ReplaceWith { m: 3, root: 0 }
  229. ]
  230. );
  231. }
  232. /// Should result in every node being pushed and then replaced with an anchor
  233. #[test]
  234. fn many_items_become_fragment() {
  235. let dom = TestDom::new();
  236. let left = rsx!({
  237. (0..2).map(|f| {
  238. rsx! { div { "hello" }}
  239. })
  240. });
  241. let right = rsx!({ (0..0).map(|f| rsx! { div {} }) });
  242. let (create, change) = dom.lazy_diff(left, right);
  243. assert_eq!(
  244. create.edits,
  245. [
  246. CreateElement { id: 0, tag: "div" },
  247. CreateTextNode {
  248. text: "hello",
  249. id: 1
  250. },
  251. AppendChildren { many: 1 },
  252. CreateElement { id: 2, tag: "div" },
  253. CreateTextNode {
  254. text: "hello",
  255. id: 3
  256. },
  257. AppendChildren { many: 1 },
  258. AppendChildren { many: 2 },
  259. ]
  260. );
  261. // hmmmmmmmmm worried about reusing IDs that we shouldnt be
  262. assert_eq!(
  263. change.edits,
  264. [
  265. Remove { root: 2 },
  266. CreatePlaceholder { id: 4 },
  267. ReplaceWith { root: 0, m: 1 },
  268. ]
  269. );
  270. }
  271. /// Should result in no edits
  272. #[test]
  273. fn two_equal_fragments_are_equal() {
  274. let dom = TestDom::new();
  275. let left = rsx!({
  276. (0..2).map(|f| {
  277. rsx! { div { "hello" }}
  278. })
  279. });
  280. let right = rsx!({
  281. (0..2).map(|f| {
  282. rsx! { div { "hello" }}
  283. })
  284. });
  285. let (create, change) = dom.lazy_diff(left, right);
  286. assert!(change.edits.is_empty());
  287. }
  288. /// Should result the creation of more nodes appended after the old last node
  289. #[test]
  290. fn two_fragments_with_differrent_elements_are_differet() {
  291. let dom = TestDom::new();
  292. let left = rsx!(
  293. { (0..2).map(|f| rsx! { div { }} ) }
  294. p {}
  295. );
  296. let right = rsx!(
  297. { (0..5).map(|f| rsx! (h1 { }) ) }
  298. p {}
  299. );
  300. let edits = dom.lazy_diff(left, right);
  301. dbg!(&edits);
  302. }
  303. /// Should result in multiple nodes destroyed - with changes to the first nodes
  304. #[test]
  305. fn two_fragments_with_differrent_elements_are_differet_shorter() {
  306. let dom = TestDom::new();
  307. let left = rsx!(
  308. {(0..5).map(|f| {rsx! { div { }}})}
  309. p {}
  310. );
  311. let right = rsx!(
  312. {(0..2).map(|f| {rsx! { h1 { }}})}
  313. p {}
  314. );
  315. let (create, change) = dom.lazy_diff(left, right);
  316. assert_eq!(
  317. create.edits,
  318. [
  319. CreateElement { id: 0, tag: "div" },
  320. CreateElement { id: 1, tag: "div" },
  321. CreateElement { id: 2, tag: "div" },
  322. CreateElement { id: 3, tag: "div" },
  323. CreateElement { id: 4, tag: "div" },
  324. CreateElement { id: 5, tag: "p" },
  325. AppendChildren { many: 6 },
  326. ]
  327. );
  328. assert_eq!(
  329. change.edits,
  330. [
  331. Remove { root: 2 },
  332. Remove { root: 3 },
  333. Remove { root: 4 },
  334. CreateElement { id: 6, tag: "h1" },
  335. ReplaceWith { root: 0, m: 1 },
  336. CreateElement { id: 7, tag: "h1" },
  337. ReplaceWith { root: 1, m: 1 },
  338. ]
  339. );
  340. }
  341. /// Should result in multiple nodes destroyed - with no changes
  342. #[test]
  343. fn two_fragments_with_same_elements_are_differet() {
  344. let dom = TestDom::new();
  345. let left = rsx!(
  346. {(0..2).map(|f| {rsx! { div { }}})}
  347. p {}
  348. );
  349. let right = rsx!(
  350. {(0..5).map(|f| {rsx! { div { }}})}
  351. p {}
  352. );
  353. let (create, change) = dom.lazy_diff(left, right);
  354. assert_eq!(
  355. create.edits,
  356. [
  357. CreateElement { id: 0, tag: "div" },
  358. CreateElement { id: 1, tag: "div" },
  359. CreateElement { id: 2, tag: "p" },
  360. AppendChildren { many: 3 },
  361. ]
  362. );
  363. assert_eq!(
  364. change.edits,
  365. [
  366. CreateElement { id: 3, tag: "div" },
  367. CreateElement { id: 4, tag: "div" },
  368. CreateElement { id: 5, tag: "div" },
  369. InsertAfter { root: 1, n: 3 },
  370. ]
  371. );
  372. }
  373. /// should result in the removal of elements
  374. #[test]
  375. fn keyed_diffing_order() {
  376. let dom = TestDom::new();
  377. let left = rsx!(
  378. {(0..5).map(|f| {rsx! { div { key: "{f}" }}})}
  379. p {"e"}
  380. );
  381. let right = rsx!(
  382. {(0..2).map(|f| {rsx! { div { key: "{f}" }}})}
  383. p {"e"}
  384. );
  385. let (create, change) = dom.lazy_diff(left, right);
  386. assert_eq!(
  387. change.edits,
  388. [Remove { root: 2 }, Remove { root: 3 }, Remove { root: 4 },]
  389. );
  390. }
  391. #[test]
  392. fn fragment_keys() {
  393. let r = 1;
  394. let p = rsx! {
  395. Fragment { key: "asd {r}" }
  396. };
  397. }
  398. /// Should result in moves, but not removals or additions
  399. #[test]
  400. fn keyed_diffing_out_of_order() {
  401. let dom = TestDom::new();
  402. let left = rsx!({
  403. [0, 1, 2, 3, /**/ 4, 5, 6, /**/ 7, 8, 9].iter().map(|f| {
  404. rsx! { div { key: "{f}" }}
  405. })
  406. });
  407. let right = rsx!({
  408. [0, 1, 2, 3, /**/ 6, 4, 5, /**/ 7, 8, 9].iter().map(|f| {
  409. rsx! { div { key: "{f}" }}
  410. })
  411. });
  412. let edits = dom.lazy_diff(left, right);
  413. dbg!(&edits.1);
  414. }
  415. /// Should result in moves only
  416. #[test]
  417. fn keyed_diffing_out_of_order_adds() {
  418. let dom = TestDom::new();
  419. let left = rsx!({
  420. [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
  421. rsx! { div { key: "{f}" }}
  422. })
  423. });
  424. let right = rsx!({
  425. [/**/ 8, 7, 4, 5, 6 /**/].iter().map(|f| {
  426. rsx! { div { key: "{f}" }}
  427. })
  428. });
  429. let (_, change) = dom.lazy_diff(left, right);
  430. assert_eq!(
  431. change.edits,
  432. [
  433. PushRoot { id: 4 },
  434. PushRoot { id: 3 },
  435. InsertBefore { n: 2, root: 0 }
  436. ]
  437. );
  438. }
  439. /// Should result in moves onl
  440. #[test]
  441. fn keyed_diffing_out_of_order_adds_2() {
  442. let dom = TestDom::new();
  443. let left = rsx!({
  444. [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
  445. rsx! { div { key: "{f}" }}
  446. })
  447. });
  448. let right = rsx!({
  449. [/**/ 7, 8, 4, 5, 6 /**/].iter().map(|f| {
  450. rsx! { div { key: "{f}" }}
  451. })
  452. });
  453. let (_, change) = dom.lazy_diff(left, right);
  454. assert_eq!(
  455. change.edits,
  456. [
  457. PushRoot { id: 3 },
  458. PushRoot { id: 4 },
  459. InsertBefore { n: 2, root: 0 }
  460. ]
  461. );
  462. }
  463. /// Should result in moves onl
  464. #[test]
  465. fn keyed_diffing_out_of_order_adds_3() {
  466. let dom = TestDom::new();
  467. let left = rsx!({
  468. [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
  469. rsx! { div { key: "{f}" }}
  470. })
  471. });
  472. let right = rsx!({
  473. [/**/ 4, 8, 7, 5, 6 /**/].iter().map(|f| {
  474. rsx! { div { key: "{f}" }}
  475. })
  476. });
  477. let (_, change) = dom.lazy_diff(left, right);
  478. assert_eq!(
  479. change.edits,
  480. [
  481. PushRoot { id: 4 },
  482. PushRoot { id: 3 },
  483. InsertBefore { n: 2, root: 1 }
  484. ]
  485. );
  486. }
  487. /// Should result in moves onl
  488. #[test]
  489. fn keyed_diffing_out_of_order_adds_4() {
  490. let dom = TestDom::new();
  491. let left = rsx!({
  492. [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
  493. rsx! { div { key: "{f}" }}
  494. })
  495. });
  496. let right = rsx!({
  497. [/**/ 4, 5, 8, 7, 6 /**/].iter().map(|f| {
  498. rsx! { div { key: "{f}" }}
  499. })
  500. });
  501. let (_, change) = dom.lazy_diff(left, right);
  502. assert_eq!(
  503. change.edits,
  504. [
  505. PushRoot { id: 4 },
  506. PushRoot { id: 3 },
  507. InsertBefore { n: 2, root: 2 }
  508. ]
  509. );
  510. }
  511. /// Should result in moves onl
  512. #[test]
  513. fn keyed_diffing_out_of_order_adds_5() {
  514. let dom = TestDom::new();
  515. let left = rsx!({
  516. [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
  517. rsx! { div { key: "{f}" }}
  518. })
  519. });
  520. let right = rsx!({
  521. [/**/ 4, 5, 6, 8, 7 /**/].iter().map(|f| {
  522. rsx! { div { key: "{f}" }}
  523. })
  524. });
  525. let (_, change) = dom.lazy_diff(left, right);
  526. assert_eq!(
  527. change.edits,
  528. [PushRoot { id: 4 }, InsertBefore { n: 1, root: 3 }]
  529. );
  530. }
  531. #[test]
  532. fn keyed_diffing_additions() {
  533. let dom = TestDom::new();
  534. let left = rsx!({
  535. [/**/ 4, 5, 6, 7, 8 /**/].iter().map(|f| {
  536. rsx! { div { key: "{f}" }}
  537. })
  538. });
  539. let right = rsx!({
  540. [/**/ 4, 5, 6, 7, 8, 9, 10 /**/].iter().map(|f| {
  541. rsx! { div { key: "{f}" }}
  542. })
  543. });
  544. let (_, change) = dom.lazy_diff(left, right);
  545. assert_eq!(
  546. change.edits,
  547. [
  548. CreateElement { id: 5, tag: "div" },
  549. CreateElement { id: 6, tag: "div" },
  550. InsertAfter { n: 2, root: 4 }
  551. ]
  552. );
  553. }
  554. #[test]
  555. fn keyed_diffing_additions_and_moves_on_ends() {
  556. let dom = TestDom::new();
  557. let left = rsx!({
  558. [/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
  559. rsx! { div { key: "{f}" }}
  560. })
  561. });
  562. let right = rsx!({
  563. [/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| {
  564. // [/**/ 8, 7, 4, 5, 6, 9, 10 /**/].iter().map(|f| {
  565. rsx! { div { key: "{f}" }}
  566. })
  567. });
  568. let (_, change) = dom.lazy_diff(left, right);
  569. dbg!(change);
  570. // assert_eq!(
  571. // change.edits,
  572. // [
  573. // CreateElement { id: 5, tag: "div" },
  574. // CreateElement { id: 6, tag: "div" },
  575. // InsertAfter { n: 2, root: 4 }
  576. // ]
  577. // );
  578. }
  579. #[test]
  580. fn keyed_diffing_additions_and_moves_in_middle() {
  581. let dom = TestDom::new();
  582. let left = rsx!({
  583. [/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
  584. rsx! { div { key: "{f}" }}
  585. })
  586. });
  587. let right = rsx!({
  588. [/**/ 7, 4, 13, 17, 5, 11, 12, 6 /**/].iter().map(|f| {
  589. rsx! { div { key: "{f}" }}
  590. })
  591. });
  592. let (_, change) = dom.lazy_diff(left, right);
  593. dbg!(change);
  594. // assert_eq!(
  595. // change.edits,
  596. // [
  597. // CreateElement { id: 5, tag: "div" },
  598. // CreateElement { id: 6, tag: "div" },
  599. // InsertAfter { n: 2, root: 4 }
  600. // ]
  601. // );
  602. }
  603. #[test]
  604. fn controlled_keyed_diffing_out_of_order() {
  605. let dom = TestDom::new();
  606. let left = [4, 5, 6, 7];
  607. let left = rsx!({
  608. left.iter().map(|f| {
  609. rsx! { div { key: "{f}" "{f}" }}
  610. })
  611. });
  612. // 0, 1, 2, 6, 5, 4, 3, 7, 8, 9
  613. let right = [0, 5, 9, 6, 4];
  614. let right = rsx!({
  615. right.iter().map(|f| {
  616. rsx! { div { key: "{f}" "{f}" }}
  617. })
  618. });
  619. // LIS: 3, 7, 8,
  620. let edits = dom.lazy_diff(left, right);
  621. dbg!(&edits);
  622. }