test-changes.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import ist from "ist"
  2. import {schema, doc, p, blockquote, h1} from "prosemirror-test-builder"
  3. import {Transform} from "prosemirror-transform"
  4. import {Node} from "prosemirror-model"
  5. import {ChangeSet} from "prosemirror-changeset"
  6. describe("ChangeSet", () => {
  7. it("finds a single insertion",
  8. find(doc(p("hello")), tr => tr.insert(3, t("XY")), [[3, 3, 3, 5]]))
  9. it("finds a single deletion",
  10. find(doc(p("hello")), tr => tr.delete(3, 5), [[3, 5, 3, 3]]))
  11. it("identifies a replacement",
  12. find(doc(p("hello")), tr => tr.replaceWith(3, 5, t("juj")),
  13. [[3, 5, 3, 6]]))
  14. it("merges adjacent canceling edits",
  15. find(doc(p("hello")),
  16. tr => tr.delete(3, 5).insert(3, t("ll")),
  17. []))
  18. it("doesn't crash when cancelling edits are followed by others",
  19. find(doc(p("hello")),
  20. tr => tr.delete(2, 3).insert(2, t("e")).delete(5, 6),
  21. [[5, 6, 5, 5]]))
  22. it("stops handling an inserted span after collapsing it",
  23. find(doc(p("abcba")), tr => tr.insert(2, t("b")).insert(6, t("b")).delete(3, 6),
  24. [[3, 4, 3, 3]]))
  25. it("partially merges insert at start",
  26. find(doc(p("helLo")), tr => tr.delete(3, 5).insert(3, t("l")),
  27. [[4, 5, 4, 4]]))
  28. it("partially merges insert at end",
  29. find(doc(p("helLo")), tr => tr.delete(3, 5).insert(3, t("L")),
  30. [[3, 4, 3, 3]]))
  31. it("partially merges delete at start",
  32. find(doc(p("abc")), tr => tr.insert(3, t("xyz")).delete(3, 4),
  33. [[3, 3, 3, 5]]))
  34. it("partially merges delete at end",
  35. find(doc(p("abc")), tr => tr.insert(3, t("xyz")).delete(5, 6),
  36. [[3, 3, 3, 5]]))
  37. it("finds multiple insertions",
  38. find(doc(p("abc")), tr => tr.insert(1, t("x")).insert(5, t("y")),
  39. [[1, 1, 1, 2], [4, 4, 5, 6]]))
  40. it("finds multiple deletions",
  41. find(doc(p("xyz")), tr => tr.delete(1, 2).delete(2, 3),
  42. [[1, 2, 1, 1], [3, 4, 2, 2]]))
  43. it("identifies a deletion between insertions",
  44. find(doc(p("zyz")), tr => tr.insert(2, t("A")).insert(4, t("B")).delete(3, 4),
  45. [[2, 3, 2, 4]]))
  46. it("can add a deletion in a new addStep call", find(doc(p("hello")), [
  47. tr => tr.delete(1, 2),
  48. tr => tr.delete(2, 3)
  49. ], [[1, 2, 1, 1], [3, 4, 2, 2]]))
  50. it("merges delete/insert from different addStep calls", find(doc(p("hello")), [
  51. tr => tr.delete(3, 5),
  52. tr => tr.insert(3, t("ll"))
  53. ], []))
  54. it("revert a deletion by inserting the character again", find(doc(p("bar")), [
  55. tr => tr.delete(2, 3), // br
  56. tr => tr.insert(2, t("x")), // bxr
  57. tr => tr.insert(2, t("a")) // baxr
  58. ], [[3, 3, 3, 4]]))
  59. it("insert character before changed character", find(doc(p("bar")), [
  60. tr => tr.delete(2, 3), // br
  61. tr => tr.insert(2, t("x")), // bxr
  62. tr => tr.insert(2, t("x")) // bxxr
  63. ], [[2, 3, 2, 4]]))
  64. it("partially merges delete/insert from different addStep calls", find(doc(p("heljo")), [
  65. tr => tr.delete(3, 5),
  66. tr => tr.insert(3, t("ll"))
  67. ], [[4, 5, 4, 5]]))
  68. it("merges insert/delete from different addStep calls", find(doc(p("ok")), [
  69. tr => tr.insert(2, t("--")),
  70. tr => tr.delete(2, 4)
  71. ], []))
  72. it("partially merges insert/delete from different addStep calls", find(doc(p("ok")), [
  73. tr => tr.insert(2, t("--")),
  74. tr => tr.delete(2, 3)
  75. ], [[2, 2, 2, 3]]))
  76. it("maps deletions forward", find(doc(p("foobar")), [
  77. tr => tr.delete(5, 6),
  78. tr => tr.insert(1, t("OKAY"))
  79. ], [[1, 1, 1, 5], [5, 6, 9, 9]]))
  80. it("can incrementally undo then redo", find(doc(p("bar")), [
  81. tr => tr.delete(2, 3),
  82. tr => tr.insert(2, t("a")),
  83. tr => tr.delete(2, 3)
  84. ], [[2, 3, 2, 2]]))
  85. it("can map through complicated changesets", find(doc(p("12345678901234")), [
  86. tr => tr.delete(9, 12).insert(6, t("xyz")).replaceWith(2, 3, t("uv")),
  87. tr => tr.delete(14, 15).insert(13, t("90")).delete(8, 9)
  88. ], [[2, 3, 2, 4], [6, 6, 7, 9], [11, 12, 14, 14], [13, 14, 15, 15]]))
  89. it("computes a proper diff of the changes",
  90. find(doc(p("abcd"), p("efgh")), tr => tr.delete(2, 10).insert(2, t("cdef")),
  91. [[2, 3, 2, 2], [5, 7, 4, 4], [9, 10, 6, 6]]))
  92. it("handles re-adding content step by step", find(doc(p("one two three")), [
  93. tr => tr.delete(1, 14),
  94. tr => tr.insert(1, t("two")),
  95. tr => tr.insert(4, t(" ")),
  96. tr => tr.insert(5, t("three"))
  97. ], [[1, 5, 1, 1]]))
  98. it("doesn't get confused by split deletions", find(doc(blockquote(h1("one"), p("two four"))), [
  99. tr => tr.delete(7, 11),
  100. tr => tr.replaceWith(0, 13, blockquote(h1("one"), p("four")))
  101. ], [[7, 11, 7, 7, [[4, 0]], []]], true))
  102. it("doesn't get confused by multiply split deletions", find(doc(blockquote(h1("one"), p("two three"))), [
  103. tr => tr.delete(14, 16),
  104. tr => tr.delete(7, 11),
  105. tr => tr.delete(3, 5),
  106. tr => tr.replaceWith(0, 10, blockquote(h1("o"), p("thr")))
  107. ], [[3, 5, 3, 3, [[2, 2]], []], [8, 12, 6, 6, [[3, 1], [1, 3]], []],
  108. [14, 16, 8, 8, [[2, 0]], []]], true))
  109. it("won't lose the order of overlapping changes", find(doc(p("12345")), [
  110. tr => tr.delete(4, 5),
  111. tr => tr.replaceWith(2, 2, t("a")),
  112. tr => tr.delete(1, 6),
  113. tr => tr.replaceWith(1, 1, t("1a235"))
  114. ], [[2, 2, 2, 3, [], [[1, 1]]], [4, 5, 5, 5, [[1, 0]], []]], [0, 0, 1, 1]))
  115. it("properly maps deleted positions", find(doc(p("jTKqvPrzApX")), [
  116. tr => tr.delete(8, 11),
  117. tr => tr.replaceWith(1, 1, t("MPu")),
  118. tr => tr.delete(2, 12),
  119. tr => tr.replaceWith(2, 2, t("PujTKqvPrX"))
  120. ], [[1, 1, 1, 4, [], [[3, 2]]], [8, 11, 11, 11, [[3, 1]], []]], [1, 2, 2, 2]))
  121. it("fuzz issue 1", find(doc(p("hzwiKqBPzn")), [
  122. tr => tr.delete(3, 7),
  123. tr => tr.replaceWith(5, 5, t("LH")),
  124. tr => tr.replaceWith(6, 6, t("uE")),
  125. tr => tr.delete(1, 6),
  126. tr => tr.delete(3, 6)
  127. ], [[1, 11, 1, 3, [[2, 1], [4, 0], [2, 1], [2, 0]], [[2, 0]]]], [0, 1, 0, 1, 0]))
  128. it("fuzz issue 2", find(doc(p("eAMISWgauf")), [
  129. tr => tr.delete(5, 10),
  130. tr => tr.replaceWith(5, 5, t("KkM")),
  131. tr => tr.replaceWith(3, 3, t("UDO")),
  132. tr => tr.delete(1, 12),
  133. tr => tr.replaceWith(1, 1, t("eAUDOMIKkMf")),
  134. tr => tr.delete(5, 8),
  135. tr => tr.replaceWith(3, 3, t("qX"))
  136. ], [[3, 10, 3, 10, [[2, 0], [5, 2]], [[7, 0]]]], [2, 0, 0, 0, 0, 0, 0]))
  137. it("fuzz issue 3", find(doc(p("hfxjahnOuH")), [
  138. tr => tr.delete(1, 5),
  139. tr => tr.replaceWith(3, 3, t("X")),
  140. tr => tr.delete(1, 8),
  141. tr => tr.replaceWith(1, 1, t("ahXnOuH")),
  142. tr => tr.delete(2, 4),
  143. tr => tr.replaceWith(2, 2, t("tn")),
  144. tr => tr.delete(5, 7),
  145. tr => tr.delete(1, 6),
  146. tr => tr.replaceWith(1, 1, t("atnnH")),
  147. tr => tr.delete(2, 6)
  148. ], [[1, 11, 1, 2, [[4, 1], [1, 0], [1, 1], [1, 0], [2, 1], [1, 0]], [[1, 0]]]], [1, 0, 1, 1, 1, 1, 1, 0, 0, 0]))
  149. it("correctly handles steps with multiple map entries", find(doc(p()), [
  150. tr => tr.replaceWith(1, 1, t("ab")),
  151. tr => tr.wrap(tr.doc.resolve(1).blockRange()!, [{type: schema.nodes.blockquote}])
  152. ], [[0, 0, 0, 1], [1, 1, 2, 4], [2, 2, 5, 6]]))
  153. })
  154. function find(doc: Node, build: ((tr: Transform) => void) | ((tr: Transform) => void)[],
  155. changes: any[], sep?: number[] | boolean) {
  156. return () => {
  157. let set = ChangeSet.create(doc), curDoc = doc
  158. if (!Array.isArray(build)) build = [build]
  159. build.forEach((build, i) => {
  160. let tr = new Transform(curDoc)
  161. build(tr)
  162. set = set.addSteps(tr.doc, tr.mapping.maps, !sep ? 0 : Array.isArray(sep) ? sep[i] : i)
  163. curDoc = tr.doc
  164. })
  165. let owner = sep && changes.length && changes[0].length > 4
  166. ist(JSON.stringify(set.changes.map(ch => {
  167. let range: any[] = [ch.fromA, ch.toA, ch.fromB, ch.toB]
  168. if (owner) range.push(ch.deleted.map(d => [d.length, d.data]),
  169. ch.inserted.map(d => [d.length, d.data]))
  170. return range
  171. })), JSON.stringify(changes))
  172. }
  173. }
  174. function t(str: string) { return schema.text(str) }