diffing.rs 17 KB

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