diffing.rs 17 KB

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