diffing.rs 18 KB

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