@fluidframework/sequence 2.0.0-rc.2.0.2 → 2.0.0-rc.3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +1 -1
  3. package/api-report/sequence.api.md +32 -32
  4. package/beta.d.ts +11 -0
  5. package/dist/{localValues.d.ts → IntervalCollectionValues.d.ts} +13 -12
  6. package/dist/IntervalCollectionValues.d.ts.map +1 -0
  7. package/dist/{localValues.js → IntervalCollectionValues.js} +6 -6
  8. package/dist/IntervalCollectionValues.js.map +1 -0
  9. package/dist/beta.d.ts +12 -0
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +10 -10
  13. package/dist/index.js.map +1 -1
  14. package/dist/intervalCollection.d.ts +10 -10
  15. package/dist/intervalCollection.d.ts.map +1 -1
  16. package/dist/intervalCollection.js +103 -99
  17. package/dist/intervalCollection.js.map +1 -1
  18. package/dist/{defaultMap.d.ts → intervalCollectionMap.d.ts} +11 -44
  19. package/dist/intervalCollectionMap.d.ts.map +1 -0
  20. package/dist/{defaultMap.js → intervalCollectionMap.js} +15 -70
  21. package/dist/intervalCollectionMap.js.map +1 -0
  22. package/dist/{defaultMapInterfaces.d.ts → intervalCollectionMapInterfaces.d.ts} +17 -16
  23. package/dist/intervalCollectionMapInterfaces.d.ts.map +1 -0
  24. package/dist/{defaultMapInterfaces.js → intervalCollectionMapInterfaces.js} +1 -1
  25. package/dist/intervalCollectionMapInterfaces.js.map +1 -0
  26. package/dist/intervalIndex/endpointInRangeIndex.d.ts +1 -1
  27. package/dist/intervalIndex/endpointInRangeIndex.d.ts.map +1 -1
  28. package/dist/intervalIndex/endpointInRangeIndex.js +3 -3
  29. package/dist/intervalIndex/endpointInRangeIndex.js.map +1 -1
  30. package/dist/intervalIndex/endpointIndex.d.ts +1 -1
  31. package/dist/intervalIndex/endpointIndex.d.ts.map +1 -1
  32. package/dist/intervalIndex/endpointIndex.js +3 -3
  33. package/dist/intervalIndex/endpointIndex.js.map +1 -1
  34. package/dist/intervalIndex/idIntervalIndex.d.ts.map +1 -1
  35. package/dist/intervalIndex/idIntervalIndex.js +3 -3
  36. package/dist/intervalIndex/idIntervalIndex.js.map +1 -1
  37. package/dist/intervalIndex/overlappingIntervalsIndex.d.ts +3 -3
  38. package/dist/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -1
  39. package/dist/intervalIndex/overlappingIntervalsIndex.js +2 -3
  40. package/dist/intervalIndex/overlappingIntervalsIndex.js.map +1 -1
  41. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -1
  42. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js +6 -6
  43. package/dist/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -1
  44. package/dist/intervalIndex/sequenceIntervalIndexes.d.ts +1 -1
  45. package/dist/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -1
  46. package/dist/intervalIndex/sequenceIntervalIndexes.js.map +1 -1
  47. package/dist/intervalIndex/startpointInRangeIndex.d.ts +1 -1
  48. package/dist/intervalIndex/startpointInRangeIndex.d.ts.map +1 -1
  49. package/dist/intervalIndex/startpointInRangeIndex.js +3 -3
  50. package/dist/intervalIndex/startpointInRangeIndex.js.map +1 -1
  51. package/dist/intervalTree.d.ts +1 -1
  52. package/dist/intervalTree.d.ts.map +1 -1
  53. package/dist/intervalTree.js +2 -2
  54. package/dist/intervalTree.js.map +1 -1
  55. package/dist/intervals/interval.d.ts +1 -1
  56. package/dist/intervals/interval.d.ts.map +1 -1
  57. package/dist/intervals/interval.js +10 -10
  58. package/dist/intervals/interval.js.map +1 -1
  59. package/dist/intervals/intervalUtils.d.ts +1 -1
  60. package/dist/intervals/intervalUtils.d.ts.map +1 -1
  61. package/dist/intervals/intervalUtils.js +5 -5
  62. package/dist/intervals/intervalUtils.js.map +1 -1
  63. package/dist/intervals/sequenceInterval.d.ts +1 -1
  64. package/dist/intervals/sequenceInterval.d.ts.map +1 -1
  65. package/dist/intervals/sequenceInterval.js +34 -34
  66. package/dist/intervals/sequenceInterval.js.map +1 -1
  67. package/dist/legacy.d.ts +61 -0
  68. package/dist/packageVersion.d.ts +1 -1
  69. package/dist/packageVersion.js +1 -1
  70. package/dist/packageVersion.js.map +1 -1
  71. package/dist/public.d.ts +12 -0
  72. package/dist/revertibles.d.ts +2 -2
  73. package/dist/revertibles.d.ts.map +1 -1
  74. package/dist/revertibles.js +34 -34
  75. package/dist/revertibles.js.map +1 -1
  76. package/dist/sequence.d.ts +7 -6
  77. package/dist/sequence.d.ts.map +1 -1
  78. package/dist/sequence.js +34 -34
  79. package/dist/sequence.js.map +1 -1
  80. package/dist/sequenceDeltaEvent.d.ts +1 -1
  81. package/dist/sequenceDeltaEvent.d.ts.map +1 -1
  82. package/dist/sequenceDeltaEvent.js +4 -4
  83. package/dist/sequenceDeltaEvent.js.map +1 -1
  84. package/dist/sequenceFactory.d.ts +1 -1
  85. package/dist/sequenceFactory.d.ts.map +1 -1
  86. package/dist/sequenceFactory.js +3 -3
  87. package/dist/sequenceFactory.js.map +1 -1
  88. package/dist/sharedIntervalCollection.d.ts +4 -3
  89. package/dist/sharedIntervalCollection.d.ts.map +1 -1
  90. package/dist/sharedIntervalCollection.js +5 -5
  91. package/dist/sharedIntervalCollection.js.map +1 -1
  92. package/dist/sharedSequence.d.ts +3 -2
  93. package/dist/sharedSequence.d.ts.map +1 -1
  94. package/dist/sharedSequence.js +4 -4
  95. package/dist/sharedSequence.js.map +1 -1
  96. package/dist/sharedString.d.ts +2 -2
  97. package/dist/sharedString.d.ts.map +1 -1
  98. package/dist/sharedString.js +9 -9
  99. package/dist/sharedString.js.map +1 -1
  100. package/internal.d.ts +11 -0
  101. package/legacy.d.ts +11 -0
  102. package/lib/{localValues.d.ts → IntervalCollectionValues.d.ts} +13 -12
  103. package/lib/IntervalCollectionValues.d.ts.map +1 -0
  104. package/lib/{localValues.js → IntervalCollectionValues.js} +3 -3
  105. package/lib/IntervalCollectionValues.js.map +1 -0
  106. package/lib/beta.d.ts +12 -0
  107. package/lib/index.d.ts +2 -2
  108. package/lib/index.d.ts.map +1 -1
  109. package/lib/index.js +1 -1
  110. package/lib/index.js.map +1 -1
  111. package/lib/intervalCollection.d.ts +10 -10
  112. package/lib/intervalCollection.d.ts.map +1 -1
  113. package/lib/intervalCollection.js +8 -4
  114. package/lib/intervalCollection.js.map +1 -1
  115. package/lib/{defaultMap.d.ts → intervalCollectionMap.d.ts} +11 -44
  116. package/lib/intervalCollectionMap.d.ts.map +1 -0
  117. package/lib/{defaultMap.js → intervalCollectionMap.js} +8 -63
  118. package/lib/intervalCollectionMap.js.map +1 -0
  119. package/lib/{defaultMapInterfaces.d.ts → intervalCollectionMapInterfaces.d.ts} +17 -16
  120. package/lib/intervalCollectionMapInterfaces.d.ts.map +1 -0
  121. package/lib/{defaultMapInterfaces.js → intervalCollectionMapInterfaces.js} +1 -1
  122. package/lib/intervalCollectionMapInterfaces.js.map +1 -0
  123. package/lib/intervalIndex/endpointInRangeIndex.d.ts +1 -1
  124. package/lib/intervalIndex/endpointInRangeIndex.d.ts.map +1 -1
  125. package/lib/intervalIndex/endpointInRangeIndex.js +1 -1
  126. package/lib/intervalIndex/endpointInRangeIndex.js.map +1 -1
  127. package/lib/intervalIndex/endpointIndex.d.ts +1 -1
  128. package/lib/intervalIndex/endpointIndex.d.ts.map +1 -1
  129. package/lib/intervalIndex/endpointIndex.js +1 -1
  130. package/lib/intervalIndex/endpointIndex.js.map +1 -1
  131. package/lib/intervalIndex/idIntervalIndex.d.ts.map +1 -1
  132. package/lib/intervalIndex/idIntervalIndex.js +1 -1
  133. package/lib/intervalIndex/idIntervalIndex.js.map +1 -1
  134. package/lib/intervalIndex/overlappingIntervalsIndex.d.ts +3 -3
  135. package/lib/intervalIndex/overlappingIntervalsIndex.d.ts.map +1 -1
  136. package/lib/intervalIndex/overlappingIntervalsIndex.js +2 -3
  137. package/lib/intervalIndex/overlappingIntervalsIndex.js.map +1 -1
  138. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.d.ts.map +1 -1
  139. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js +2 -2
  140. package/lib/intervalIndex/overlappingSequenceIntervalsIndex.js.map +1 -1
  141. package/lib/intervalIndex/sequenceIntervalIndexes.d.ts +1 -1
  142. package/lib/intervalIndex/sequenceIntervalIndexes.d.ts.map +1 -1
  143. package/lib/intervalIndex/sequenceIntervalIndexes.js.map +1 -1
  144. package/lib/intervalIndex/startpointInRangeIndex.d.ts +1 -1
  145. package/lib/intervalIndex/startpointInRangeIndex.d.ts.map +1 -1
  146. package/lib/intervalIndex/startpointInRangeIndex.js +1 -1
  147. package/lib/intervalIndex/startpointInRangeIndex.js.map +1 -1
  148. package/lib/intervalTree.d.ts +1 -1
  149. package/lib/intervalTree.d.ts.map +1 -1
  150. package/lib/intervalTree.js +1 -1
  151. package/lib/intervalTree.js.map +1 -1
  152. package/lib/intervals/interval.d.ts +1 -1
  153. package/lib/intervals/interval.d.ts.map +1 -1
  154. package/lib/intervals/interval.js +3 -3
  155. package/lib/intervals/interval.js.map +1 -1
  156. package/lib/intervals/intervalUtils.d.ts +1 -1
  157. package/lib/intervals/intervalUtils.d.ts.map +1 -1
  158. package/lib/intervals/intervalUtils.js +1 -1
  159. package/lib/intervals/intervalUtils.js.map +1 -1
  160. package/lib/intervals/sequenceInterval.d.ts +1 -1
  161. package/lib/intervals/sequenceInterval.d.ts.map +1 -1
  162. package/lib/intervals/sequenceInterval.js +3 -3
  163. package/lib/intervals/sequenceInterval.js.map +1 -1
  164. package/lib/legacy.d.ts +61 -0
  165. package/lib/packageVersion.d.ts +1 -1
  166. package/lib/packageVersion.js +1 -1
  167. package/lib/packageVersion.js.map +1 -1
  168. package/lib/public.d.ts +12 -0
  169. package/lib/revertibles.d.ts +2 -2
  170. package/lib/revertibles.d.ts.map +1 -1
  171. package/lib/revertibles.js +3 -4
  172. package/lib/revertibles.js.map +1 -1
  173. package/lib/sequence.d.ts +7 -6
  174. package/lib/sequence.d.ts.map +1 -1
  175. package/lib/sequence.js +11 -12
  176. package/lib/sequence.js.map +1 -1
  177. package/lib/sequenceDeltaEvent.d.ts +1 -1
  178. package/lib/sequenceDeltaEvent.d.ts.map +1 -1
  179. package/lib/sequenceDeltaEvent.js +3 -4
  180. package/lib/sequenceDeltaEvent.js.map +1 -1
  181. package/lib/sequenceFactory.d.ts +1 -1
  182. package/lib/sequenceFactory.d.ts.map +1 -1
  183. package/lib/sequenceFactory.js +1 -1
  184. package/lib/sequenceFactory.js.map +1 -1
  185. package/lib/sharedIntervalCollection.d.ts +4 -3
  186. package/lib/sharedIntervalCollection.d.ts.map +1 -1
  187. package/lib/sharedIntervalCollection.js +4 -4
  188. package/lib/sharedIntervalCollection.js.map +1 -1
  189. package/lib/sharedSequence.d.ts +3 -2
  190. package/lib/sharedSequence.d.ts.map +1 -1
  191. package/lib/sharedSequence.js +2 -2
  192. package/lib/sharedSequence.js.map +1 -1
  193. package/lib/sharedString.d.ts +2 -2
  194. package/lib/sharedString.d.ts.map +1 -1
  195. package/lib/sharedString.js +1 -1
  196. package/lib/sharedString.js.map +1 -1
  197. package/package.json +41 -58
  198. package/src/{localValues.ts → IntervalCollectionValues.ts} +26 -18
  199. package/src/index.ts +2 -2
  200. package/src/intervalCollection.ts +46 -47
  201. package/src/{defaultMap.ts → intervalCollectionMap.ts} +42 -105
  202. package/src/{defaultMapInterfaces.ts → intervalCollectionMapInterfaces.ts} +26 -16
  203. package/src/intervalIndex/endpointInRangeIndex.ts +4 -1
  204. package/src/intervalIndex/endpointIndex.ts +4 -1
  205. package/src/intervalIndex/idIntervalIndex.ts +4 -2
  206. package/src/intervalIndex/overlappingIntervalsIndex.ts +8 -5
  207. package/src/intervalIndex/overlappingSequenceIntervalsIndex.ts +6 -3
  208. package/src/intervalIndex/sequenceIntervalIndexes.ts +3 -1
  209. package/src/intervalIndex/startpointInRangeIndex.ts +4 -1
  210. package/src/intervalTree.ts +4 -3
  211. package/src/intervals/interval.ts +6 -3
  212. package/src/intervals/intervalUtils.ts +4 -2
  213. package/src/intervals/sequenceInterval.ts +5 -3
  214. package/src/packageVersion.ts +1 -1
  215. package/src/revertibles.ts +10 -10
  216. package/src/sequence.ts +24 -31
  217. package/src/sequenceDeltaEvent.ts +3 -4
  218. package/src/sequenceFactory.ts +4 -3
  219. package/src/sharedIntervalCollection.ts +12 -18
  220. package/src/sharedSequence.ts +9 -6
  221. package/src/sharedString.ts +4 -3
  222. package/api-extractor-cjs.json +0 -8
  223. package/dist/defaultMap.d.ts.map +0 -1
  224. package/dist/defaultMap.js.map +0 -1
  225. package/dist/defaultMapInterfaces.d.ts.map +0 -1
  226. package/dist/defaultMapInterfaces.js.map +0 -1
  227. package/dist/localValues.d.ts.map +0 -1
  228. package/dist/localValues.js.map +0 -1
  229. package/dist/sequence-alpha.d.ts +0 -1432
  230. package/dist/sequence-beta.d.ts +0 -246
  231. package/dist/sequence-public.d.ts +0 -246
  232. package/dist/sequence-untrimmed.d.ts +0 -1820
  233. package/lib/defaultMap.d.ts.map +0 -1
  234. package/lib/defaultMap.js.map +0 -1
  235. package/lib/defaultMapInterfaces.d.ts.map +0 -1
  236. package/lib/defaultMapInterfaces.js.map +0 -1
  237. package/lib/localValues.d.ts.map +0 -1
  238. package/lib/localValues.js.map +0 -1
  239. package/lib/sequence-alpha.d.ts +0 -1432
  240. package/lib/sequence-beta.d.ts +0 -246
  241. package/lib/sequence-public.d.ts +0 -246
  242. package/lib/sequence-untrimmed.d.ts +0 -1820
  243. package/lib/test/collections.intervalTree.js +0 -73
  244. package/lib/test/collections.intervalTree.js.map +0 -1
  245. package/lib/test/createSnapshotFiles.js +0 -15
  246. package/lib/test/createSnapshotFiles.js.map +0 -1
  247. package/lib/test/dirname.cjs +0 -16
  248. package/lib/test/dirname.cjs.map +0 -1
  249. package/lib/test/endpointInRangeIndex.spec.js +0 -182
  250. package/lib/test/endpointInRangeIndex.spec.js.map +0 -1
  251. package/lib/test/fuzz/fuzzUtils.js +0 -250
  252. package/lib/test/fuzz/fuzzUtils.js.map +0 -1
  253. package/lib/test/fuzz/intervalCollection.fuzz.spec.js +0 -200
  254. package/lib/test/fuzz/intervalCollection.fuzz.spec.js.map +0 -1
  255. package/lib/test/fuzz/intervalRevertibles.fuzz.spec.js +0 -129
  256. package/lib/test/fuzz/intervalRevertibles.fuzz.spec.js.map +0 -1
  257. package/lib/test/fuzz/sharedString.fuzz.spec.js +0 -91
  258. package/lib/test/fuzz/sharedString.fuzz.spec.js.map +0 -1
  259. package/lib/test/generateSharedStrings.js +0 -138
  260. package/lib/test/generateSharedStrings.js.map +0 -1
  261. package/lib/test/intervalCollection.detached.spec.js +0 -126
  262. package/lib/test/intervalCollection.detached.spec.js.map +0 -1
  263. package/lib/test/intervalCollection.events.spec.js +0 -491
  264. package/lib/test/intervalCollection.events.spec.js.map +0 -1
  265. package/lib/test/intervalCollection.perf.spec.js +0 -88
  266. package/lib/test/intervalCollection.perf.spec.js.map +0 -1
  267. package/lib/test/intervalCollection.snapshot.spec.js +0 -171
  268. package/lib/test/intervalCollection.snapshot.spec.js.map +0 -1
  269. package/lib/test/intervalCollection.spec.js +0 -1660
  270. package/lib/test/intervalCollection.spec.js.map +0 -1
  271. package/lib/test/intervalIndexTestUtils.js +0 -49
  272. package/lib/test/intervalIndexTestUtils.js.map +0 -1
  273. package/lib/test/intervalRebasing.spec.js +0 -589
  274. package/lib/test/intervalRebasing.spec.js.map +0 -1
  275. package/lib/test/intervalStashedOps.spec.js +0 -142
  276. package/lib/test/intervalStashedOps.spec.js.map +0 -1
  277. package/lib/test/intervalTestUtils.js +0 -81
  278. package/lib/test/intervalTestUtils.js.map +0 -1
  279. package/lib/test/marshalling.spec.js +0 -55
  280. package/lib/test/marshalling.spec.js.map +0 -1
  281. package/lib/test/memory/sharedSequence.spec.js +0 -82
  282. package/lib/test/memory/sharedSequence.spec.js.map +0 -1
  283. package/lib/test/memory/sharedString.spec.js +0 -134
  284. package/lib/test/memory/sharedString.spec.js.map +0 -1
  285. package/lib/test/overlappingSequenceIntervalsIndex.spec.js +0 -348
  286. package/lib/test/overlappingSequenceIntervalsIndex.spec.js.map +0 -1
  287. package/lib/test/partialLoad.spec.js +0 -211
  288. package/lib/test/partialLoad.spec.js.map +0 -1
  289. package/lib/test/rebasing.spec.js +0 -81
  290. package/lib/test/rebasing.spec.js.map +0 -1
  291. package/lib/test/reentrancy.spec.js +0 -174
  292. package/lib/test/reentrancy.spec.js.map +0 -1
  293. package/lib/test/revertibles.spec.js +0 -971
  294. package/lib/test/revertibles.spec.js.map +0 -1
  295. package/lib/test/sequenceDeltaEvent.spec.js +0 -2144
  296. package/lib/test/sequenceDeltaEvent.spec.js.map +0 -1
  297. package/lib/test/sharedIntervalCollection.spec.js +0 -159
  298. package/lib/test/sharedIntervalCollection.spec.js.map +0 -1
  299. package/lib/test/sharedString.spec.js +0 -532
  300. package/lib/test/sharedString.spec.js.map +0 -1
  301. package/lib/test/snapshotEmptyProps.spec.js +0 -45
  302. package/lib/test/snapshotEmptyProps.spec.js.map +0 -1
  303. package/lib/test/snapshotVersion.spec.js +0 -149
  304. package/lib/test/snapshotVersion.spec.js.map +0 -1
  305. package/lib/test/startpointInRangeIndex.spec.js +0 -182
  306. package/lib/test/startpointInRangeIndex.spec.js.map +0 -1
  307. package/lib/test/subSequence.spec.js +0 -92
  308. package/lib/test/subSequence.spec.js.map +0 -1
  309. package/lib/test/types/validateSequencePrevious.generated.js +0 -162
  310. package/lib/test/types/validateSequencePrevious.generated.js.map +0 -1
  311. package/lib/test/v1IntervalCollectionHelpers.js +0 -93
  312. package/lib/test/v1IntervalCollectionHelpers.js.map +0 -1
  313. /package/{dist → lib}/tsdoc-metadata.json +0 -0
@@ -1,1660 +0,0 @@
1
- /*!
2
- * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
- * Licensed under the MIT License.
4
- */
5
- import { strict as assert } from "assert";
6
- import { ReferenceType, SlidingPreference, reservedRangeLabelsKey, } from "@fluidframework/merge-tree";
7
- import { MockFluidDataStoreRuntime, MockContainerRuntimeFactory, MockContainerRuntimeFactoryForReconnection, MockStorage, MockEmptyDeltaConnection, } from "@fluidframework/test-runtime-utils";
8
- import { LoggingError } from "@fluidframework/telemetry-utils";
9
- import { AttachState } from "@fluidframework/container-definitions";
10
- import { SharedString } from "../sharedString.js";
11
- import { SharedStringFactory } from "../sequenceFactory.js";
12
- import { Side } from "../intervalCollection.js";
13
- import { IntervalStickiness } from "../intervals/index.js";
14
- import { assertSequenceIntervals } from "./intervalTestUtils.js";
15
- class MockIntervalIndex {
16
- constructor() {
17
- this.intervals = new Array();
18
- }
19
- add(interval) {
20
- this.intervals.push(interval);
21
- }
22
- remove(interval) {
23
- const idx = this.intervals.indexOf(interval);
24
- if (idx !== -1) {
25
- this.intervals.splice(idx, 1);
26
- return true;
27
- }
28
- return false;
29
- }
30
- get(idx) {
31
- return this.intervals[idx];
32
- }
33
- size() {
34
- return this.intervals.length;
35
- }
36
- }
37
- function assertIntervalEquals(string, interval, endpoints) {
38
- assert(interval);
39
- assert.equal(string.localReferencePositionToPosition(interval.start), endpoints.start, "mismatched start");
40
- assert.equal(string.localReferencePositionToPosition(interval.end), endpoints.end, "mismatched end");
41
- }
42
- describe("SharedString interval collections", () => {
43
- let sharedString;
44
- let dataStoreRuntime1;
45
- beforeEach(() => {
46
- dataStoreRuntime1 = new MockFluidDataStoreRuntime({ clientId: "1" });
47
- sharedString = new SharedString(dataStoreRuntime1, "shared-string-1", SharedStringFactory.Attributes);
48
- });
49
- describe("in a connected state with a remote SharedString", () => {
50
- let sharedString2;
51
- let containerRuntimeFactory;
52
- beforeEach(() => {
53
- containerRuntimeFactory = new MockContainerRuntimeFactory();
54
- // Connect the first SharedString.
55
- dataStoreRuntime1.setAttachState(AttachState.Attached);
56
- dataStoreRuntime1.options = {
57
- intervalStickinessEnabled: true,
58
- };
59
- const containerRuntime1 = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime1);
60
- const services1 = {
61
- deltaConnection: dataStoreRuntime1.createDeltaConnection(),
62
- objectStorage: new MockStorage(),
63
- };
64
- sharedString.initializeLocal();
65
- sharedString.connect(services1);
66
- // Create and connect a second SharedString.
67
- const dataStoreRuntime2 = new MockFluidDataStoreRuntime({ clientId: "2" });
68
- const containerRuntime2 = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime2);
69
- dataStoreRuntime2.options = {
70
- intervalStickinessEnabled: true,
71
- };
72
- const services2 = {
73
- deltaConnection: dataStoreRuntime2.createDeltaConnection(),
74
- objectStorage: new MockStorage(),
75
- };
76
- sharedString2 = new SharedString(dataStoreRuntime2, "shared-string-2", SharedStringFactory.Attributes);
77
- sharedString2.initializeLocal();
78
- sharedString2.connect(services2);
79
- });
80
- it("can maintain interval consistency", () => {
81
- const collection1 = sharedString.getIntervalCollection("test");
82
- sharedString.insertText(0, "xyz");
83
- containerRuntimeFactory.processAllMessages();
84
- const collection2 = sharedString2.getIntervalCollection("test");
85
- assert.notStrictEqual(collection2, undefined, "undefined");
86
- assert.strictEqual(sharedString.getText(), sharedString2.getText(), "not equal text");
87
- sharedString.insertText(0, "abc");
88
- const interval = collection1.add({ start: 1, end: 1 });
89
- const intervalId = interval.getIntervalId();
90
- assert(intervalId);
91
- sharedString2.insertText(0, "wha");
92
- containerRuntimeFactory.processAllMessages();
93
- assert.strictEqual(sharedString.getText(), "whaabcxyz", "different text 1");
94
- assert.strictEqual(sharedString.getText(), "whaabcxyz", "different text 2");
95
- assertSequenceIntervals(sharedString, collection1, [{ start: 4, end: 4 }]);
96
- assertSequenceIntervals(sharedString2, collection2, [{ start: 4, end: 4 }]);
97
- collection2.change(intervalId, { start: 1, end: 6 });
98
- sharedString.removeText(0, 2);
99
- collection1.change(intervalId, { start: 0, end: 5 });
100
- containerRuntimeFactory.processAllMessages();
101
- assertSequenceIntervals(sharedString, collection1, [{ start: 0, end: 5 }]);
102
- assertSequenceIntervals(sharedString2, collection2, [{ start: 0, end: 5 }]);
103
- collection1.change(intervalId, {
104
- start: sharedString.getLength() - 1,
105
- end: sharedString.getLength() - 1,
106
- });
107
- containerRuntimeFactory.processAllMessages();
108
- assertSequenceIntervals(sharedString, collection1, [
109
- { start: sharedString.getLength() - 1, end: sharedString.getLength() - 1 },
110
- ]);
111
- assertSequenceIntervals(sharedString2, collection2, [
112
- { start: sharedString2.getLength() - 1, end: sharedString2.getLength() - 1 },
113
- ]);
114
- });
115
- describe("changing endpoints and/or properties", () => {
116
- it("changes only endpoints with new signature", () => {
117
- const collection = sharedString.getIntervalCollection("test");
118
- sharedString.insertText(0, "hello world");
119
- const id = collection.add({ start: 0, end: 3, props: { a: 1 } }).getIntervalId();
120
- collection.change(id, { start: 1, end: 4 });
121
- assertIntervalEquals(sharedString, collection.getIntervalById(id), {
122
- start: 1,
123
- end: 4,
124
- });
125
- assert.equal(collection.getIntervalById(id)?.properties.a, 1);
126
- });
127
- it("changes only properties with new signature", () => {
128
- const collection = sharedString.getIntervalCollection("test");
129
- sharedString.insertText(0, "hello world");
130
- const id = collection.add({ start: 0, end: 3, props: { a: 1 } }).getIntervalId();
131
- collection.change(id, { props: { a: 2 } });
132
- assertIntervalEquals(sharedString, collection.getIntervalById(id), {
133
- start: 0,
134
- end: 3,
135
- });
136
- assert.equal(collection.getIntervalById(id)?.properties.a, 2);
137
- });
138
- it("changes endpoints and properties with new signature", () => {
139
- const collection = sharedString.getIntervalCollection("test");
140
- sharedString.insertText(0, "hello world");
141
- const id = collection.add({ start: 0, end: 3, props: { a: 1 } }).getIntervalId();
142
- collection.change(id, { start: 1, end: 4, props: { a: 2 } });
143
- assertIntervalEquals(sharedString, collection.getIntervalById(id), {
144
- start: 1,
145
- end: 4,
146
- });
147
- assert.equal(collection.getIntervalById(id)?.properties.a, 2);
148
- });
149
- it("changes endpoints and properties with new signature on a remote sharedString", () => {
150
- const collection = sharedString.getIntervalCollection("test");
151
- sharedString.insertText(0, "hello world");
152
- const id = collection.add({ start: 0, end: 3, props: { a: 1 } }).getIntervalId();
153
- containerRuntimeFactory.processAllMessages();
154
- const collection2 = sharedString2.getIntervalCollection("test");
155
- collection.change(id, { start: 1, end: 4, props: { a: 2 } });
156
- containerRuntimeFactory.processAllMessages();
157
- assertIntervalEquals(sharedString, collection.getIntervalById(id), {
158
- start: 1,
159
- end: 4,
160
- });
161
- assertIntervalEquals(sharedString2, collection2.getIntervalById(id), {
162
- start: 1,
163
- end: 4,
164
- });
165
- assert.equal(collection.getIntervalById(id)?.properties.a, 2);
166
- assert.equal(collection2.getIntervalById(id)?.properties.a, 2);
167
- });
168
- it("passes empty property set to change", () => {
169
- const collection = sharedString.getIntervalCollection("test");
170
- sharedString.insertText(0, "hello world");
171
- const id = collection.add({ start: 0, end: 3, props: { a: 1 } }).getIntervalId();
172
- collection.change(id, { props: {} });
173
- assertIntervalEquals(sharedString, collection.getIntervalById(id), {
174
- start: 0,
175
- end: 3,
176
- });
177
- assert.equal(collection.getIntervalById(id)?.properties.a, 1);
178
- });
179
- it("passes undefined endpoints and properties to change", () => {
180
- const collection = sharedString.getIntervalCollection("test");
181
- sharedString.insertText(0, "hello world");
182
- const id = collection.add({ start: 0, end: 3, props: { a: 1 } }).getIntervalId();
183
- collection.change(id, { start: undefined, end: undefined, props: undefined });
184
- assertIntervalEquals(sharedString, collection.getIntervalById(id), {
185
- start: 0,
186
- end: 3,
187
- });
188
- assert.equal(collection.getIntervalById(id)?.properties.a, 1);
189
- });
190
- });
191
- // Regression test for bug described in <https://dev.azure.com/fluidframework/internal/_workitems/edit/4477>
192
- //
193
- // This test involves a crash inside RBTree when multiple intervals slide
194
- // off the string
195
- //
196
- // More specifically, previously we didn't properly clear the segment
197
- // on local references which became detached, which caused crashes on
198
- // some IntervalCollection workflows
199
- it("passes regression test for #4477", () => {
200
- sharedString.insertText(0, "ABC");
201
- sharedString.insertText(0, "D");
202
- // DABC
203
- sharedString.removeRange(0, 1);
204
- // [D]ABC
205
- const collection = sharedString.getIntervalCollection("test");
206
- collection.add({ start: 0, end: 0, props: { intervalId: "x" } });
207
- // x
208
- // [D]ABC
209
- sharedString.removeRange(0, 1);
210
- // x
211
- // [D][A]BC
212
- collection.add({ start: 0, end: 0, props: { intervalId: "y" } });
213
- // x y
214
- // [D][A]BC
215
- sharedString.removeRange(0, 1);
216
- sharedString.removeRange(0, 1);
217
- sharedString.insertText(0, "EFGHIJK");
218
- sharedString.insertText(0, "LMNO");
219
- containerRuntimeFactory.processAllMessages();
220
- sharedString.insertText(0, "P");
221
- // x, y are detached
222
- // [ ]
223
- // string is PLMNOEFGHIJK
224
- collection.add({ start: 7, end: 11, props: { intervalId: "z" } });
225
- sharedString.removeRange(11, 12);
226
- containerRuntimeFactory.processAllMessages();
227
- });
228
- describe("remain consistent on double-delete", () => {
229
- let collection;
230
- let collection2;
231
- beforeEach(() => {
232
- sharedString.insertText(0, "01234");
233
- collection = sharedString.getIntervalCollection("test");
234
- collection2 = sharedString2.getIntervalCollection("test");
235
- containerRuntimeFactory.processAllMessages();
236
- });
237
- it("causing references to slide forward", () => {
238
- sharedString2.removeRange(2, 3);
239
- collection.add({ start: 2, end: 2 });
240
- sharedString.removeRange(2, 4);
241
- containerRuntimeFactory.processAllMessages();
242
- assertSequenceIntervals(sharedString, collection, [{ start: 2, end: 2 }]);
243
- assertSequenceIntervals(sharedString2, collection2, [{ start: 2, end: 2 }]);
244
- });
245
- it("causing references to slide backward", () => {
246
- sharedString2.removeRange(2, 3);
247
- collection.add({ start: 2, end: 2 });
248
- sharedString.removeRange(2, 5);
249
- containerRuntimeFactory.processAllMessages();
250
- assertSequenceIntervals(sharedString, collection, [{ start: 1, end: 1 }]);
251
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 1 }]);
252
- });
253
- });
254
- it("errors creating invalid intervals", () => {
255
- const collection1 = sharedString.getIntervalCollection("test");
256
- containerRuntimeFactory.processAllMessages();
257
- assert.throws(() => collection1.add({ start: 0, end: 0 }), "Should throw creating interval on empty string");
258
- assert.throws(() => collection1.add({ start: 1, end: 3 }), "Should throw creating interval on empty string");
259
- sharedString.insertText(0, "ABCD");
260
- containerRuntimeFactory.processAllMessages();
261
- assert.throws(() => collection1.add({ start: 2, end: 5 }), "Should throw creating interval past end of string");
262
- // There is no check for creating an interval at a negative offset
263
- // assert.throws(() => collection1.add(-1, 2, IntervalType.SlideOnRemove),
264
- // "Should throw creating interval at negative position");
265
- });
266
- it("can create and slide interval to a marker", () => {
267
- sharedString.insertText(0, "ABCD");
268
- sharedString.insertMarker(4, ReferenceType.Tile, { nodeType: "Paragraph" });
269
- const collection1 = sharedString.getIntervalCollection("test");
270
- containerRuntimeFactory.processAllMessages();
271
- const collection2 = sharedString2.getIntervalCollection("test");
272
- collection1.add({ start: 3, end: 4 });
273
- containerRuntimeFactory.processAllMessages();
274
- assertSequenceIntervals(sharedString, collection1, [{ start: 3, end: 4 }]);
275
- assertSequenceIntervals(sharedString2, collection2, [{ start: 3, end: 4 }]);
276
- sharedString.removeRange(3, 4);
277
- containerRuntimeFactory.processAllMessages();
278
- assertSequenceIntervals(sharedString, collection1, [{ start: 3, end: 3 }]);
279
- assertSequenceIntervals(sharedString2, collection2, [{ start: 3, end: 3 }]);
280
- });
281
- it("can slide intervals nearer", () => {
282
- const collection1 = sharedString.getIntervalCollection("test");
283
- sharedString.insertText(0, "ABCD");
284
- containerRuntimeFactory.processAllMessages();
285
- const collection2 = sharedString2.getIntervalCollection("test");
286
- // Conflicting remove/add interval at end of string
287
- collection1.add({ start: 1, end: 3 });
288
- sharedString2.removeRange(3, 4);
289
- containerRuntimeFactory.processAllMessages();
290
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 2 }]);
291
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 2 }]);
292
- // Remove location of end of interval
293
- sharedString.removeRange(2, 3);
294
- assert.equal(sharedString.getText(), "AB");
295
- assertSequenceIntervals(sharedString, collection1, [
296
- // odd behavior - end of interval doesn't slide
297
- // until ack, so position beyond end of string
298
- { start: 1, end: 2 },
299
- ]);
300
- containerRuntimeFactory.processAllMessages();
301
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 1 }]);
302
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 1 }]);
303
- // Remove location of start and end of interval
304
- sharedString.removeRange(1, 2);
305
- assertSequenceIntervals(sharedString, collection1, [
306
- // odd behavior - start of interval doesn't slide
307
- // until ack, so not found by overlapping search
308
- { start: 1, end: 1 },
309
- ], false);
310
- containerRuntimeFactory.processAllMessages();
311
- assertSequenceIntervals(sharedString, collection1, [{ start: 0, end: 0 }]);
312
- assertSequenceIntervals(sharedString2, collection2, [{ start: 0, end: 0 }]);
313
- // Interval on empty string
314
- sharedString.removeRange(0, 1);
315
- assertSequenceIntervals(sharedString, collection1, [
316
- // Search finds interval at end of string
317
- { start: 0, end: 0 },
318
- ]);
319
- containerRuntimeFactory.processAllMessages();
320
- assertSequenceIntervals(sharedString, collection1, [
321
- // Interval becomes detached when string is acked empty
322
- { start: -1, end: -1 },
323
- ], false);
324
- assertSequenceIntervals(sharedString2, collection2, [{ start: -1, end: -1 }], false);
325
- });
326
- it("remains consistent when a change to the same position but different segment is issued", () => {
327
- // This is a regression test for an issue in LocalIntervalCollection, which avoided actually modifying
328
- // intervals on change operations if it perceived them to already have the same position. That logic was
329
- // invalid in 2 ways:
330
- // 1. for remote ops, the position requested for change potentially refers to a different revision from
331
- // the local position.
332
- // 2. for local ops, even if an interval appears to be at the position it's being changed to, it might
333
- // actually be associated with a removed segment and pending slide. In this case, failing to update
334
- // the interval locally but still emitting a change op causes inconsistent behavior, since subsequent
335
- // slides may be to different segments (in this test, the danger is that the client issuing the change
336
- // op may end up with their interval pointing to the "Y" if they fail to change it locally)
337
- sharedString.insertText(0, "ABCDE");
338
- const collection1 = sharedString.getIntervalCollection("test");
339
- containerRuntimeFactory.processAllMessages();
340
- const interval = collection1.add({ start: 1, end: 3 });
341
- sharedString2.insertText(2, "XY");
342
- sharedString2.removeRange(1, 3);
343
- sharedString.removeRange(1, 4);
344
- const intervalId = interval.getIntervalId();
345
- assert(intervalId);
346
- collection1.change(intervalId, { start: 1, end: 1 });
347
- containerRuntimeFactory.processAllMessages();
348
- assert.equal(sharedString.getText(), "AYE");
349
- assertSequenceIntervals(sharedString, collection1, [{ start: 2, end: 2 }]);
350
- assertSequenceIntervals(sharedString2, sharedString2.getIntervalCollection("test"), [
351
- { start: 2, end: 2 },
352
- ]);
353
- });
354
- it("can slide intervals nearer to locally removed segment", () => {
355
- const collection1 = sharedString.getIntervalCollection("test");
356
- sharedString.insertText(0, "ABCD");
357
- containerRuntimeFactory.processAllMessages();
358
- const collection2 = sharedString2.getIntervalCollection("test");
359
- sharedString2.removeRange(3, 4);
360
- collection1.add({ start: 1, end: 3 });
361
- sharedString.removeRange(1, 3);
362
- containerRuntimeFactory.processAllMessages();
363
- assertSequenceIntervals(sharedString, collection1, [{ start: 0, end: 0 }]);
364
- assertSequenceIntervals(sharedString2, collection2, [{ start: 0, end: 0 }]);
365
- });
366
- it("consistent after remove all/insert text conflict", () => {
367
- const collection1 = sharedString.getIntervalCollection("test");
368
- sharedString.insertText(0, "ABCD");
369
- collection1.add({ start: 1, end: 3 });
370
- containerRuntimeFactory.processAllMessages();
371
- const collection2 = sharedString2.getIntervalCollection("test");
372
- sharedString.insertText(0, "XYZ");
373
- sharedString2.removeRange(0, 4);
374
- containerRuntimeFactory.processAllMessages();
375
- assertSequenceIntervals(sharedString, collection1, [{ start: 2, end: 2 }]);
376
- assertSequenceIntervals(sharedString2, collection2, [{ start: 2, end: 2 }]);
377
- sharedString2.removeRange(0, 3);
378
- sharedString.insertText(0, "PQ");
379
- containerRuntimeFactory.processAllMessages();
380
- assertSequenceIntervals(sharedString, collection1, [{ start: -1, end: -1 }], false);
381
- assertSequenceIntervals(sharedString2, collection2, [{ start: -1, end: -1 }], false);
382
- sharedString2.removeRange(0, 2);
383
- containerRuntimeFactory.processAllMessages();
384
- assertSequenceIntervals(sharedString, collection1, [{ start: -1, end: -1 }], false);
385
- assertSequenceIntervals(sharedString2, collection2, [{ start: -1, end: -1 }], false);
386
- });
387
- it("can slide intervals on remove ack", () => {
388
- const collection1 = sharedString.getIntervalCollection("test");
389
- sharedString.insertText(0, "ABCD");
390
- containerRuntimeFactory.processAllMessages();
391
- const collection2 = sharedString2.getIntervalCollection("test");
392
- collection1.add({ start: 1, end: 3 });
393
- containerRuntimeFactory.processAllMessages();
394
- sharedString.insertText(2, "X");
395
- assert.strictEqual(sharedString.getText(), "ABXCD");
396
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 4 }]);
397
- sharedString2.removeRange(1, 2);
398
- assert.strictEqual(sharedString2.getText(), "ACD");
399
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 2 }]);
400
- containerRuntimeFactory.processAllMessages();
401
- assert.strictEqual(sharedString.getText(), "AXCD");
402
- assert.strictEqual(sharedString2.getText(), "AXCD");
403
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 3 }]);
404
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 3 }]);
405
- });
406
- it("can slide intervals to segment not referenced by remove", () => {
407
- const collection1 = sharedString.getIntervalCollection("test");
408
- sharedString.insertText(0, "ABCD");
409
- containerRuntimeFactory.processAllMessages();
410
- const collection2 = sharedString2.getIntervalCollection("test");
411
- sharedString.insertText(2, "X");
412
- assert.strictEqual(sharedString.getText(), "ABXCD");
413
- collection1.add({ start: 1, end: 3 });
414
- sharedString2.removeRange(1, 2);
415
- assert.strictEqual(sharedString2.getText(), "ACD");
416
- containerRuntimeFactory.processAllMessages();
417
- assert.strictEqual(sharedString.getText(), "AXCD");
418
- assert.strictEqual(sharedString2.getText(), "AXCD");
419
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 2 }]);
420
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 2 }]);
421
- });
422
- it("can slide intervals on create ack", () => {
423
- // Create and connect a third SharedString.
424
- const dataStoreRuntime3 = new MockFluidDataStoreRuntime({ clientId: "3" });
425
- const containerRuntime3 = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime3);
426
- const services3 = {
427
- deltaConnection: containerRuntime3.createDeltaConnection(),
428
- objectStorage: new MockStorage(),
429
- };
430
- const sharedString3 = new SharedString(dataStoreRuntime3, "shared-string-3", SharedStringFactory.Attributes);
431
- sharedString3.initializeLocal();
432
- sharedString3.connect(services3);
433
- const collection1 = sharedString.getIntervalCollection("test");
434
- sharedString.insertText(0, "ABCD");
435
- containerRuntimeFactory.processAllMessages();
436
- const collection2 = sharedString2.getIntervalCollection("test");
437
- const collection3 = sharedString3.getIntervalCollection("test");
438
- sharedString.removeRange(1, 2);
439
- assert.strictEqual(sharedString.getText(), "ACD");
440
- sharedString2.insertText(2, "X");
441
- assert.strictEqual(sharedString2.getText(), "ABXCD");
442
- collection3.add({ start: 1, end: 3 });
443
- containerRuntimeFactory.processAllMessages();
444
- assert.strictEqual(sharedString.getText(), "AXCD");
445
- assert.strictEqual(sharedString2.getText(), "AXCD");
446
- assert.strictEqual(sharedString3.getText(), "AXCD");
447
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 3 }]);
448
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 3 }]);
449
- assertSequenceIntervals(sharedString3, collection3, [{ start: 1, end: 3 }]);
450
- });
451
- it("can slide intervals on change ack", () => {
452
- // Create and connect a third SharedString.
453
- const dataStoreRuntime3 = new MockFluidDataStoreRuntime({ clientId: "3" });
454
- const containerRuntime3 = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime3);
455
- const services3 = {
456
- deltaConnection: containerRuntime3.createDeltaConnection(),
457
- objectStorage: new MockStorage(),
458
- };
459
- const sharedString3 = new SharedString(dataStoreRuntime3, "shared-string-3", SharedStringFactory.Attributes);
460
- sharedString3.initializeLocal();
461
- sharedString3.connect(services3);
462
- const collection1 = sharedString.getIntervalCollection("test");
463
- sharedString.insertText(0, "ABCD");
464
- const interval = collection1.add({ start: 0, end: 0 });
465
- containerRuntimeFactory.processAllMessages();
466
- const collection2 = sharedString2.getIntervalCollection("test");
467
- const collection3 = sharedString3.getIntervalCollection("test");
468
- sharedString.removeRange(1, 2);
469
- assert.strictEqual(sharedString.getText(), "ACD");
470
- sharedString2.insertText(2, "X");
471
- assert.strictEqual(sharedString2.getText(), "ABXCD");
472
- const intervalId = interval.getIntervalId();
473
- assert(intervalId);
474
- collection3.change(intervalId, { start: 1, end: 3 });
475
- containerRuntimeFactory.processAllMessages();
476
- assert.strictEqual(sharedString.getText(), "AXCD");
477
- assert.strictEqual(sharedString2.getText(), "AXCD");
478
- assert.strictEqual(sharedString3.getText(), "AXCD");
479
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 3 }]);
480
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 3 }]);
481
- assertSequenceIntervals(sharedString3, collection3, [{ start: 1, end: 3 }]);
482
- sharedString.removeRange(3, 4);
483
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 3 }]);
484
- containerRuntimeFactory.processAllMessages();
485
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 2 }]);
486
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 2 }]);
487
- assertSequenceIntervals(sharedString3, collection3, [{ start: 1, end: 2 }]);
488
- });
489
- it("can slide intervals on create before remove", () => {
490
- const collection1 = sharedString.getIntervalCollection("test");
491
- sharedString.insertText(0, "ABCD");
492
- containerRuntimeFactory.processAllMessages();
493
- const collection2 = sharedString2.getIntervalCollection("test");
494
- collection2.add({ start: 2, end: 3 });
495
- sharedString.removeRange(1, 3);
496
- containerRuntimeFactory.processAllMessages();
497
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 1 }]);
498
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 1 }]);
499
- });
500
- it("can slide intervals on remove before create", () => {
501
- const collection1 = sharedString.getIntervalCollection("test");
502
- sharedString.insertText(0, "ABCDE");
503
- containerRuntimeFactory.processAllMessages();
504
- const collection2 = sharedString2.getIntervalCollection("test");
505
- sharedString.removeRange(1, 3);
506
- assert.strictEqual(sharedString.getText(), "ADE");
507
- collection2.add({ start: 1, end: 3 });
508
- containerRuntimeFactory.processAllMessages();
509
- // before fixing this, at this point the start range on sharedString
510
- // is on the removed segment. Can't detect that from the interval API.
511
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 1 }]);
512
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 1 }]);
513
- // More operations reveal the problem
514
- sharedString.insertText(2, "X");
515
- assert.strictEqual(sharedString.getText(), "ADXE");
516
- sharedString2.removeRange(1, 2);
517
- assert.strictEqual(sharedString2.getText(), "AE");
518
- containerRuntimeFactory.processAllMessages();
519
- assert.strictEqual(sharedString.getText(), "AXE");
520
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 1 }]);
521
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 1 }]);
522
- });
523
- it("can maintain different offsets on removed segment", () => {
524
- const collection1 = sharedString.getIntervalCollection("test");
525
- sharedString.insertText(0, "ABCD");
526
- containerRuntimeFactory.processAllMessages();
527
- const collection2 = sharedString2.getIntervalCollection("test");
528
- collection1.add({ start: 1, end: 3 });
529
- sharedString.insertText(2, "XY");
530
- assert.strictEqual(sharedString.getText(), "ABXYCD");
531
- sharedString2.removeRange(0, 4);
532
- assert.strictEqual(sharedString2.getText(), "");
533
- containerRuntimeFactory.processAllMessages();
534
- assert.strictEqual(sharedString.getText(), "XY");
535
- assert.strictEqual(sharedString2.getText(), "XY");
536
- assertSequenceIntervals(sharedString, collection1, [{ start: 0, end: 1 }]);
537
- assertSequenceIntervals(sharedString2, collection2, [{ start: 0, end: 1 }]);
538
- });
539
- it("tolerates creation of an interval with no segment due to concurrent delete", () => {
540
- sharedString.insertText(0, "ABCDEF");
541
- const collection1 = sharedString.getIntervalCollection("test");
542
- const collection2 = sharedString2.getIntervalCollection("test");
543
- containerRuntimeFactory.processAllMessages();
544
- sharedString2.removeRange(0, sharedString2.getLength());
545
- collection1.add({ start: 1, end: 1 });
546
- sharedString2.insertText(0, "X");
547
- containerRuntimeFactory.processAllMessages();
548
- assertSequenceIntervals(sharedString, collection1, [{ start: -1, end: -1 }], false);
549
- assertSequenceIntervals(sharedString2, collection2, [{ start: -1, end: -1 }], false);
550
- });
551
- it("can maintain consistency of LocalReference's when segments are packed", async () => {
552
- // sharedString.insertMarker(0, ReferenceType.Tile, { nodeType: "Paragraph" });
553
- const collection1 = sharedString.getIntervalCollection("test2");
554
- containerRuntimeFactory.processAllMessages();
555
- const collection2 = sharedString2.getIntervalCollection("test2");
556
- sharedString.insertText(0, "a");
557
- sharedString.insertText(1, "b");
558
- sharedString.insertText(2, "c");
559
- sharedString.insertText(3, "d");
560
- sharedString.insertText(4, "e");
561
- sharedString.insertText(5, "f");
562
- containerRuntimeFactory.processAllMessages();
563
- assert.strictEqual(sharedString.getText(), "abcdef", "incorrect text 1");
564
- assert.strictEqual(sharedString2.getText(), "abcdef", "incorrect text 2");
565
- collection1.add({ start: 2, end: 2 });
566
- containerRuntimeFactory.processAllMessages();
567
- assertSequenceIntervals(sharedString, collection1, [{ start: 2, end: 2 }]);
568
- assertSequenceIntervals(sharedString2, collection2, [{ start: 2, end: 2 }]);
569
- sharedString.insertText(0, "a");
570
- sharedString.insertText(1, "b");
571
- sharedString.insertText(2, "c");
572
- sharedString.insertText(3, "d");
573
- sharedString.insertText(4, "e");
574
- sharedString.insertText(5, "f");
575
- containerRuntimeFactory.processAllMessages();
576
- assert.strictEqual(sharedString.getText(), "abcdefabcdef", "incorrect text 2");
577
- assert.strictEqual(sharedString2.getText(), "abcdefabcdef", "incorrect text 3");
578
- collection1.add({ start: 5, end: 5 });
579
- collection1.add({ start: 2, end: 2 });
580
- containerRuntimeFactory.processAllMessages();
581
- assertSequenceIntervals(sharedString, collection1, [
582
- { start: 2, end: 2 },
583
- { start: 5, end: 5 },
584
- { start: 8, end: 8 },
585
- ]);
586
- assertSequenceIntervals(sharedString2, collection2, [
587
- { start: 2, end: 2 },
588
- { start: 5, end: 5 },
589
- { start: 8, end: 8 },
590
- ]);
591
- // Summarize to cause Zamboni to pack segments. Confirm consistency after packing.
592
- await sharedString2.summarize();
593
- assertSequenceIntervals(sharedString, collection1, [
594
- { start: 2, end: 2 },
595
- { start: 5, end: 5 },
596
- { start: 8, end: 8 },
597
- ]);
598
- assertSequenceIntervals(sharedString2, collection2, [
599
- { start: 2, end: 2 },
600
- { start: 5, end: 5 },
601
- { start: 8, end: 8 },
602
- ]);
603
- });
604
- it("ignores remote changes that would be overridden by multiple local ones", () => {
605
- // The idea of this test is to verify multiple pending local changes are tracked accurately.
606
- // No tracking at all of pending changes would cause collection 1 to see all 5 values: 0, 1, 2, 3, 4.
607
- // Tracking that there is only a local change, but not which one it was might cause collection 1 to
608
- // see 4 values: 0, 2, 3, 4.
609
- // Correct tracking should cause collection1 to only see 3 values: 0, 2, 4
610
- sharedString.insertText(0, "ABCDEF");
611
- const collection1 = sharedString.getIntervalCollection("test");
612
- const endpointsForCollection1 = [];
613
- const sequenceIntervalToEndpoints = (interval) => ({
614
- start: sharedString.localReferencePositionToPosition(interval.start),
615
- end: sharedString.localReferencePositionToPosition(interval.end),
616
- });
617
- collection1.on("addInterval", (interval) => {
618
- endpointsForCollection1.push(sequenceIntervalToEndpoints(interval));
619
- });
620
- collection1.on("changeInterval", (interval) => {
621
- const { start, end } = sequenceIntervalToEndpoints(interval);
622
- // IntervalCollection is a bit noisy when it comes to change events; this logic makes sure
623
- // to only append for actually changed values.
624
- const prevValue = endpointsForCollection1[endpointsForCollection1.length - 1];
625
- if (prevValue.start !== start || prevValue.end !== end) {
626
- endpointsForCollection1.push({ start, end });
627
- }
628
- });
629
- const id = collection1.add({ start: 0, end: 0 }).getIntervalId();
630
- assert(id);
631
- containerRuntimeFactory.processAllMessages();
632
- const collection2 = sharedString2.getIntervalCollection("test");
633
- collection2.change(id, { start: 1, end: 1 });
634
- collection1.change(id, { start: 2, end: 2 });
635
- assertIntervalEquals(sharedString2, collection2.getIntervalById(id), {
636
- start: 1,
637
- end: 1,
638
- });
639
- assertIntervalEquals(sharedString, collection1.getIntervalById(id), {
640
- start: 2,
641
- end: 2,
642
- });
643
- collection2.change(id, { start: 3, end: 3 });
644
- collection1.change(id, { start: 4, end: 4 });
645
- containerRuntimeFactory.processAllMessages();
646
- assert.deepEqual(endpointsForCollection1, [
647
- { start: 0, end: 0 },
648
- { start: 2, end: 2 },
649
- { start: 4, end: 4 },
650
- ]);
651
- });
652
- it("propagates delete op to second runtime", async () => {
653
- // Create and connect a second SharedString.
654
- const runtime2 = new MockFluidDataStoreRuntime();
655
- containerRuntimeFactory.createContainerRuntime(runtime2);
656
- sharedString2 = new SharedString(runtime2, "shared-string-2", SharedStringFactory.Attributes);
657
- const services2 = {
658
- deltaConnection: runtime2.createDeltaConnection(),
659
- objectStorage: new MockStorage(),
660
- };
661
- sharedString2.initializeLocal();
662
- sharedString2.connect(services2);
663
- sharedString.insertText(0, "hello friend");
664
- const collection1 = sharedString.getIntervalCollection("test");
665
- const collection2 = sharedString2.getIntervalCollection("test");
666
- containerRuntimeFactory.processAllMessages();
667
- const interval = collection1.add({ start: 6, end: 8 }); // the "fr" in "friend"
668
- containerRuntimeFactory.processAllMessages();
669
- const intervalId = interval.getIntervalId();
670
- assert(intervalId);
671
- collection1.removeIntervalById(intervalId);
672
- containerRuntimeFactory.processAllMessages();
673
- assertSequenceIntervals(sharedString2, collection2, []);
674
- });
675
- it("can round trip intervals", async () => {
676
- sharedString.insertText(0, "ABCDEF");
677
- const collection1 = sharedString.getIntervalCollection("test");
678
- const id = collection1.add({ start: 2, end: 2 }).getIntervalId();
679
- assert(id);
680
- containerRuntimeFactory.processAllMessages();
681
- const summaryTree = await sharedString.summarize();
682
- const services = {
683
- deltaConnection: new MockEmptyDeltaConnection(),
684
- objectStorage: MockStorage.createFromSummary(summaryTree.summary),
685
- };
686
- const dataStoreRuntime2 = new MockFluidDataStoreRuntime();
687
- const sharedString3 = new SharedString(dataStoreRuntime2, "shared-string-3", SharedStringFactory.Attributes);
688
- await sharedString3.load(services);
689
- await sharedString3.loaded;
690
- const collection2 = sharedString3.getIntervalCollection("test");
691
- assertIntervalEquals(sharedString, collection1.getIntervalById(id), {
692
- start: 2,
693
- end: 2,
694
- });
695
- assertIntervalEquals(sharedString3, collection2.getIntervalById(id), {
696
- start: 2,
697
- end: 2,
698
- });
699
- });
700
- describe("intervalCollection comparator consistency", () => {
701
- // This is a regression suite for an issue caught by fuzz testing:
702
- // if intervals A, B, C are created which initially compare A < B < C,
703
- // it's possible that string operations can change this order. Specifically,
704
- // removing substrings of text can make LocalReferences which previously compared
705
- // unequal now compare equal. Since the interval comparator is lexicographical on
706
- // the array [start reference, end reference, id], collapsing previously-unequal
707
- // references to now equal ones can cause issues.
708
- // The immediate way this manifests is that attempting to remove the interval fails
709
- // in red-black tree code, since the key isn't at the expected location.
710
- let collection;
711
- beforeEach(() => {
712
- sharedString.insertText(0, "ABCDEFG");
713
- collection = sharedString.getIntervalCollection("test");
714
- });
715
- it("retains intervalTree coherency when falling back to end comparison", () => {
716
- collection.add({ start: 1, end: 6 });
717
- collection.add({ start: 2, end: 5 });
718
- const initiallyLargest = collection.add({ start: 3, end: 4 });
719
- sharedString.removeRange(1, 4);
720
- // Interval slide doesn't happen until creation is acked, so interval sort order
721
- // is still by start position, which do not compare equal despite all appearing to be 1
722
- assertSequenceIntervals(sharedString, collection, [
723
- { start: 1, end: 3 },
724
- { start: 1, end: 2 },
725
- { start: 1, end: 1 },
726
- ]);
727
- const initiallyLargestId = initiallyLargest.getIntervalId();
728
- assert(initiallyLargestId);
729
- collection.removeIntervalById(initiallyLargestId);
730
- assertSequenceIntervals(sharedString, collection, [
731
- { start: 1, end: 3 },
732
- { start: 1, end: 2 },
733
- ]);
734
- containerRuntimeFactory.processAllMessages();
735
- // After processing messages, intervals slide and order is as expected.
736
- assertSequenceIntervals(sharedString, collection, [
737
- { start: 1, end: 2 },
738
- { start: 1, end: 3 },
739
- ]);
740
- });
741
- it("retains intervalTree coherency after slide when falling back to end comparison", () => {
742
- collection.add({ start: 1, end: 6 });
743
- collection.add({ start: 2, end: 5 });
744
- const initiallyLargest = collection.add({ start: 3, end: 4 });
745
- sharedString.removeRange(1, 4);
746
- assertSequenceIntervals(sharedString, collection, [
747
- { start: 1, end: 3 },
748
- { start: 1, end: 2 },
749
- { start: 1, end: 1 },
750
- ]);
751
- containerRuntimeFactory.processAllMessages();
752
- assertSequenceIntervals(sharedString, collection, [
753
- { start: 1, end: 1 },
754
- { start: 1, end: 2 },
755
- { start: 1, end: 3 },
756
- ]);
757
- const initiallyLargestId = initiallyLargest.getIntervalId();
758
- assert(initiallyLargestId);
759
- collection.removeIntervalById(initiallyLargestId);
760
- assertSequenceIntervals(sharedString, collection, [
761
- { start: 1, end: 2 },
762
- { start: 1, end: 3 },
763
- ]);
764
- containerRuntimeFactory.processAllMessages();
765
- assertSequenceIntervals(sharedString, collection, [
766
- { start: 1, end: 2 },
767
- { start: 1, end: 3 },
768
- ]);
769
- });
770
- it("retains intervalTree coherency when falling back to id comparison", () => {
771
- const [idLowest, idMiddle, idLargest] = ["a", "b", "c"];
772
- collection.add({ start: 0, end: 1, props: { intervalId: idLargest } });
773
- collection.add({ start: 0, end: 2, props: { intervalId: idMiddle } });
774
- collection.add({ start: 0, end: 3, props: { intervalId: idLowest } });
775
- sharedString.removeRange(1, 4);
776
- assertSequenceIntervals(sharedString, collection, [
777
- { start: 0, end: 1 },
778
- { start: 0, end: 1 },
779
- { start: 0, end: 1 },
780
- ]);
781
- collection.removeIntervalById(idLowest);
782
- assertSequenceIntervals(sharedString, collection, [
783
- { start: 0, end: 1 },
784
- { start: 0, end: 1 },
785
- ]);
786
- containerRuntimeFactory.processAllMessages();
787
- assertSequenceIntervals(sharedString, collection, [
788
- { start: 0, end: 1 },
789
- { start: 0, end: 1 },
790
- ]);
791
- });
792
- it("retains intervalTree coherency after slide when falling back to id comparison", () => {
793
- const [idLowest, idMiddle, idLargest] = ["a", "b", "c"];
794
- collection.add({ start: 0, end: 1, props: { intervalId: idLargest } });
795
- collection.add({ start: 0, end: 2, props: { intervalId: idMiddle } });
796
- collection.add({ start: 0, end: 3, props: { intervalId: idLowest } });
797
- sharedString.removeRange(1, 4);
798
- assertSequenceIntervals(sharedString, collection, [
799
- { start: 0, end: 1 },
800
- { start: 0, end: 1 },
801
- { start: 0, end: 1 },
802
- ]);
803
- containerRuntimeFactory.processAllMessages();
804
- assertSequenceIntervals(sharedString, collection, [
805
- { start: 0, end: 1 },
806
- { start: 0, end: 1 },
807
- { start: 0, end: 1 },
808
- ]);
809
- collection.removeIntervalById(idLowest);
810
- assertSequenceIntervals(sharedString, collection, [
811
- { start: 0, end: 1 },
812
- { start: 0, end: 1 },
813
- ]);
814
- containerRuntimeFactory.processAllMessages();
815
- assertSequenceIntervals(sharedString, collection, [
816
- { start: 0, end: 1 },
817
- { start: 0, end: 1 },
818
- ]);
819
- });
820
- it("retains intervalTree coherency after slide on create ack", () => {
821
- // The code in createAck needs to change the reference positions for an interval.
822
- // The test verifies that is done correctly and that the listener is added
823
- // to fix the interval position on subsequent slide.
824
- containerRuntimeFactory.processAllMessages();
825
- collection.add({ start: 4, end: 4 });
826
- collection.add({ start: 4, end: 5 });
827
- sharedString2.removeRange(1, 2);
828
- const initiallySmallest = collection.add({ start: 1, end: 6 });
829
- sharedString2.removeRange(1, 3);
830
- assertSequenceIntervals(sharedString, collection, [
831
- { start: 1, end: 6 },
832
- { start: 4, end: 4 },
833
- { start: 4, end: 5 },
834
- ]);
835
- containerRuntimeFactory.processAllMessages();
836
- assertSequenceIntervals(sharedString, collection, [
837
- { start: 1, end: 1 },
838
- { start: 1, end: 2 },
839
- { start: 1, end: 3 },
840
- ]);
841
- const initiallySmallestId = initiallySmallest.getIntervalId();
842
- assert(initiallySmallestId);
843
- collection.removeIntervalById(initiallySmallestId);
844
- assertSequenceIntervals(sharedString, collection, [
845
- { start: 1, end: 1 },
846
- { start: 1, end: 2 },
847
- ]);
848
- containerRuntimeFactory.processAllMessages();
849
- assertSequenceIntervals(sharedString, collection, [
850
- { start: 1, end: 1 },
851
- { start: 1, end: 2 },
852
- ]);
853
- });
854
- });
855
- it("test IntervalCollection creation events", () => {
856
- let createCalls1 = 0;
857
- const createInfo1 = [];
858
- const createCallback1 = (label, local, target) => {
859
- assert.strictEqual(target, sharedString, "Expected event to target sharedString");
860
- createInfo1[createCalls1++] = { local, label };
861
- };
862
- sharedString.on("createIntervalCollection", createCallback1);
863
- let createCalls2 = 0;
864
- const createInfo2 = [];
865
- const createCallback2 = (label, local, target) => {
866
- assert.strictEqual(target, sharedString2, "Expected event to target sharedString2");
867
- createInfo2[createCalls2++] = { local, label };
868
- };
869
- sharedString2.on("createIntervalCollection", createCallback2);
870
- sharedString.insertText(0, "hello world");
871
- containerRuntimeFactory.processAllMessages();
872
- const collection1 = sharedString.getIntervalCollection("test1");
873
- const interval1 = collection1.add({ start: 0, end: 1 });
874
- const intervalId1 = interval1.getIntervalId();
875
- assert(intervalId1);
876
- collection1.change(intervalId1, { start: 1, end: 4 });
877
- const collection2 = sharedString2.getIntervalCollection("test2");
878
- const interval2 = collection2.add({ start: 0, end: 2 });
879
- const intervalId2 = interval2.getIntervalId();
880
- assert(intervalId2);
881
- collection2.removeIntervalById(intervalId2);
882
- const collection3 = sharedString2.getIntervalCollection("test3");
883
- collection3.add({ start: 0, end: 3 });
884
- containerRuntimeFactory.processAllMessages();
885
- const verifyCreateEvents = (s, createInfo, infoArray) => {
886
- let i = 0;
887
- const labels = s.getIntervalCollectionLabels();
888
- for (const label of labels) {
889
- assert.equal(label, infoArray[i].label, `Bad label ${i}: ${label}`);
890
- assert.equal(label, createInfo[i].label, `Bad label ${i}: ${createInfo[i].label}`);
891
- assert.equal(createInfo[i].local, infoArray[i].local, `Bad local value ${i}: ${createInfo[i].local}`);
892
- i++;
893
- }
894
- assert.equal(infoArray.length, createInfo.length, `Wrong number of create calls: ${i}`);
895
- };
896
- verifyCreateEvents(sharedString, createInfo1, [
897
- { label: "test1", local: true },
898
- { label: "test2", local: false },
899
- { label: "test3", local: false },
900
- ]);
901
- verifyCreateEvents(sharedString2, createInfo2, [
902
- { label: "test2", local: true },
903
- { label: "test3", local: true },
904
- { label: "test1", local: false },
905
- ]);
906
- });
907
- it("can be concurrently created", () => {
908
- sharedString.insertText(0, "hello world");
909
- const collection1 = sharedString.getIntervalCollection("test");
910
- const collection2 = sharedString2.getIntervalCollection("test");
911
- containerRuntimeFactory.processAllMessages();
912
- assert.equal(Array.from(collection1).length, 0);
913
- assert.equal(Array.from(collection2).length, 0);
914
- });
915
- it("doesn't slide references on ack if there are pending remote changes", () => {
916
- sharedString.insertText(0, "ABCDEF");
917
- const collection1 = sharedString.getIntervalCollection("test");
918
- const collection2 = sharedString2.getIntervalCollection("test");
919
- containerRuntimeFactory.processAllMessages();
920
- sharedString.removeRange(3, 6);
921
- const interval = collection2.add({ start: 3, end: 4 });
922
- const intervalId = interval.getIntervalId();
923
- assert(intervalId);
924
- collection2.change(intervalId, { start: 1, end: 5 });
925
- assert.equal(containerRuntimeFactory.outstandingMessageCount, 3, "Unexpected number of ops");
926
- containerRuntimeFactory.processOneMessage();
927
- assertSequenceIntervals(sharedString2, collection2, [
928
- { start: 1, end: 3 /* hasn't yet been acked */ },
929
- ]);
930
- containerRuntimeFactory.processOneMessage();
931
- assertSequenceIntervals(sharedString2, collection2, [
932
- { start: 1, end: 3 /* hasn't yet been acked */ },
933
- ]);
934
- containerRuntimeFactory.processOneMessage();
935
- assertSequenceIntervals(sharedString2, collection2, [{ start: 1, end: 2 }]);
936
- assert.equal(sharedString.getText(), "ABC");
937
- assertSequenceIntervals(sharedString, collection1, [{ start: 1, end: 2 }]);
938
- });
939
- describe("have eventually consistent property sets", () => {
940
- it("when an interval is modified with a pending change", () => {
941
- sharedString.insertText(0, "ABC");
942
- const collection1 = sharedString.getIntervalCollection("test");
943
- const collection2 = sharedString2.getIntervalCollection("test");
944
- const interval = collection1.add({ start: 0, end: 0 });
945
- containerRuntimeFactory.processAllMessages();
946
- const id = interval.getIntervalId();
947
- assert(id);
948
- collection1.change(id, { start: 1, end: 1 });
949
- collection1.change(id, { props: { propName: "losing value" } });
950
- collection2.change(id, { props: { propName: "winning value" } });
951
- containerRuntimeFactory.processAllMessages();
952
- assert.equal(collection1.getIntervalById(id)?.properties.propName, "winning value");
953
- assert.equal(collection2.getIntervalById(id)?.properties.propName, "winning value");
954
- });
955
- });
956
- });
957
- describe("reconnect", () => {
958
- let containerRuntimeFactory;
959
- let containerRuntime1;
960
- let containerRuntime2;
961
- let sharedString2;
962
- let collection1;
963
- let collection2;
964
- let interval;
965
- beforeEach(async () => {
966
- containerRuntimeFactory = new MockContainerRuntimeFactoryForReconnection();
967
- // Connect the first SharedString.
968
- containerRuntime1 = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime1);
969
- const services1 = {
970
- deltaConnection: dataStoreRuntime1.createDeltaConnection(),
971
- objectStorage: new MockStorage(),
972
- };
973
- sharedString.initializeLocal();
974
- sharedString.connect(services1);
975
- // Create and connect a second SharedString.
976
- const runtime2 = new MockFluidDataStoreRuntime({ clientId: "2" });
977
- containerRuntime2 = containerRuntimeFactory.createContainerRuntime(runtime2);
978
- sharedString2 = new SharedString(runtime2, "shared-string-2", SharedStringFactory.Attributes);
979
- const services2 = {
980
- deltaConnection: runtime2.createDeltaConnection(),
981
- objectStorage: new MockStorage(),
982
- };
983
- sharedString2.initializeLocal();
984
- sharedString2.connect(services2);
985
- sharedString.insertText(0, "hello friend");
986
- collection1 = sharedString.getIntervalCollection("test");
987
- containerRuntimeFactory.processAllMessages();
988
- collection2 = sharedString2.getIntervalCollection("test");
989
- containerRuntimeFactory.processAllMessages();
990
- // Note: at the start of each test, this interval is only visible to client 1.
991
- interval = collection1.add({ start: 6, end: 8 }); // the "fr" in "friend"
992
- });
993
- it("addInterval resubmitted with concurrent insert", async () => {
994
- containerRuntime1.connected = false;
995
- sharedString2.insertText(7, "amily its my f");
996
- containerRuntimeFactory.processAllMessages();
997
- containerRuntime1.connected = true;
998
- containerRuntimeFactory.processAllMessages();
999
- assert.equal(sharedString2.getText(), "hello family its my friend");
1000
- assertSequenceIntervals(sharedString2, collection2, [{ start: 6, end: 22 }]);
1001
- assertSequenceIntervals(sharedString, collection1, [{ start: 6, end: 22 }]);
1002
- });
1003
- // This is useful to ensure rebasing reconnection ops doesn't take into account local string state
1004
- // that has been applied since the interval addition.
1005
- it("addInterval and string operations resubmitted with concurrent insert", async () => {
1006
- containerRuntime1.connected = false;
1007
- sharedString2.insertText(7, "amily its my f");
1008
- sharedString.removeText(0, 5);
1009
- sharedString.insertText(0, "hi");
1010
- containerRuntimeFactory.processAllMessages();
1011
- containerRuntime1.connected = true;
1012
- containerRuntimeFactory.processAllMessages();
1013
- assert.equal(sharedString2.getText(), "hi family its my friend");
1014
- assertSequenceIntervals(sharedString2, collection2, [{ start: 3, end: 19 }]);
1015
- assertSequenceIntervals(sharedString, collection1, [{ start: 3, end: 19 }]);
1016
- });
1017
- describe("correctly tracks pendingChanges for", () => {
1018
- // This is a regression suite for an issue involving faulty update of the pendingChange maps
1019
- // when both an add and a change op are rebased. Pending change tracking should only apply
1020
- // to "change" ops, but was also erroneously updated for "add" ops. Change tracking should also
1021
- // properly handle rebasing ops that only affect one endpoint.
1022
- it("an add followed by a change", () => {
1023
- const intervalId = interval.getIntervalId();
1024
- assert(intervalId);
1025
- collection1.removeIntervalById(intervalId);
1026
- containerRuntimeFactory.processAllMessages();
1027
- containerRuntime1.connected = false;
1028
- const newInterval = collection1.add({ start: 0, end: 1 });
1029
- sharedString.insertText(2, "llo he");
1030
- const newIntervalId = newInterval.getIntervalId();
1031
- assert(newIntervalId);
1032
- collection1.change(newIntervalId, { start: 6, end: 7 });
1033
- // Previously would fail: rebase of the "add" op would cause "Mismatch in pending changes"
1034
- // assert to fire (since the pending change wasn't actually the addition of the interval;
1035
- // it was the change)
1036
- containerRuntime1.connected = true;
1037
- containerRuntimeFactory.processAllMessages();
1038
- const expectedIntervals = [{ start: 6, end: 7 }];
1039
- assertSequenceIntervals(sharedString, collection1, expectedIntervals);
1040
- assertSequenceIntervals(sharedString2, collection2, expectedIntervals);
1041
- });
1042
- it("a change", () => {
1043
- // Like above, but the string-modifying operation is performed remotely. This means the pendingChange
1044
- // recorded prior to rebasing will have a different index from the pendingChange that would be generated
1045
- // upon rebasing (so failing to update would cause mismatch)
1046
- const intervalId = interval.getIntervalId();
1047
- const start = 6;
1048
- const end = 7;
1049
- assert(intervalId);
1050
- collection1.removeIntervalById(intervalId);
1051
- containerRuntimeFactory.processAllMessages();
1052
- containerRuntime1.connected = false;
1053
- const newInterval = collection1.add({ start: 0, end: 1 });
1054
- sharedString2.insertText(2, "llo he");
1055
- const newIntervalId = newInterval.getIntervalId();
1056
- assert(newIntervalId);
1057
- collection1.change(newIntervalId, { start, end });
1058
- containerRuntimeFactory.processAllMessages();
1059
- containerRuntime1.connected = true;
1060
- containerRuntimeFactory.processAllMessages();
1061
- const expectedStart = start + "llo he".length;
1062
- const expectedEnd = end + "llo he".length;
1063
- const expectedIntervals = [{ start: expectedStart ?? 0, end: expectedEnd }];
1064
- assertSequenceIntervals(sharedString, collection1, expectedIntervals);
1065
- assertSequenceIntervals(sharedString2, collection2, expectedIntervals);
1066
- });
1067
- });
1068
- it("can rebase a change operation to positions that are invalid in the current view", () => {
1069
- // This is a regression test for an issue in which attempting to rebase an interval op could hit
1070
- // issues in local position validation. The root cause was that the rebase logic round-tripped its
1071
- // rebase positions through a SequenceInterval (i.e. constructed an interval with the desired rebase
1072
- // positions, then serialized it). The problem is that interval isn't always valid to construct on
1073
- // the state of the local client's merge tree.
1074
- containerRuntimeFactory.processAllMessages();
1075
- containerRuntime1.connected = false;
1076
- // Since there aren't any other ops, the idea is the rebased version of this op would be the same as
1077
- // the original version. However, at the time the client is rebasing, it only has a single character of
1078
- // text. So it's impossible to generate valid LocalReference_s with positions that evaluate to 8 and 9
1079
- // as the original problematic implementation did.
1080
- const intervalId = interval.getIntervalId();
1081
- assert(intervalId);
1082
- collection1.change(intervalId, { start: 8, end: 9 });
1083
- sharedString.removeRange(1, sharedString.getLength());
1084
- containerRuntime1.connected = true;
1085
- containerRuntimeFactory.processAllMessages();
1086
- assertSequenceIntervals(sharedString, collection1, [{ start: 0, end: 0 }]);
1087
- assertSequenceIntervals(sharedString2, collection2, [{ start: 0, end: 0 }]);
1088
- });
1089
- it("can rebase changeProperty ops", () => {
1090
- containerRuntime1.connected = false;
1091
- const intervalId = interval.getIntervalId();
1092
- assert(intervalId);
1093
- collection1.change(intervalId, { props: { foo: "prop" } });
1094
- containerRuntime1.connected = true;
1095
- containerRuntimeFactory.processAllMessages();
1096
- assertSequenceIntervals(sharedString, collection1, [{ start: 6, end: 8 }]);
1097
- assertSequenceIntervals(sharedString2, collection2, [{ start: 6, end: 8 }]);
1098
- const interval2 = collection2.getIntervalById(intervalId);
1099
- assert.equal(interval2?.properties.foo, "prop");
1100
- assert.equal(interval.properties.foo, "prop");
1101
- });
1102
- it("addInterval resubmitted with concurrent delete", async () => {
1103
- containerRuntime1.connected = false;
1104
- sharedString2.removeText(5, 9);
1105
- containerRuntimeFactory.processAllMessages();
1106
- containerRuntime1.connected = true;
1107
- containerRuntimeFactory.processAllMessages();
1108
- assert.equal(sharedString2.getText(), "helloend");
1109
- assertSequenceIntervals(sharedString2, collection2, [{ start: 5, end: 5 }]);
1110
- assertSequenceIntervals(sharedString, collection1, [{ start: 5, end: 5 }]);
1111
- });
1112
- it("delete resubmitted with concurrent insert", async () => {
1113
- containerRuntimeFactory.processAllMessages();
1114
- containerRuntime1.connected = false;
1115
- const intervalId = interval.getIntervalId();
1116
- assert(intervalId);
1117
- collection1.removeIntervalById(intervalId);
1118
- sharedString2.insertText(7, "amily its my f");
1119
- containerRuntimeFactory.processAllMessages();
1120
- containerRuntime1.connected = true;
1121
- containerRuntimeFactory.processAllMessages();
1122
- // Verify that the changes were correctly received by the second SharedString
1123
- assert.equal(sharedString2.getText(), "hello family its my friend");
1124
- assertSequenceIntervals(sharedString2, collection2, []);
1125
- assertSequenceIntervals(sharedString, collection1, []);
1126
- });
1127
- it("change resubmitted with concurrent insert", async () => {
1128
- containerRuntimeFactory.processAllMessages();
1129
- containerRuntime1.connected = false;
1130
- const intervalId = interval.getIntervalId();
1131
- assert(intervalId);
1132
- collection1.change(intervalId, { start: 5, end: 9 }); // " fri"
1133
- sharedString2.insertText(7, "amily its my f");
1134
- containerRuntimeFactory.processAllMessages();
1135
- containerRuntime1.connected = true;
1136
- containerRuntimeFactory.processAllMessages();
1137
- assert.equal(sharedString2.getText(), "hello family its my friend");
1138
- assertSequenceIntervals(sharedString2, collection2, [{ start: 5, end: 23 }]);
1139
- assertSequenceIntervals(sharedString, collection1, [{ start: 5, end: 23 }]);
1140
- });
1141
- it("change resubmitted with concurrent delete", async () => {
1142
- containerRuntimeFactory.processAllMessages();
1143
- containerRuntime1.connected = false;
1144
- const intervalId = interval.getIntervalId();
1145
- assert(intervalId);
1146
- collection1.change(intervalId, { start: 5, end: 9 }); // " fri"
1147
- sharedString2.removeText(8, 10);
1148
- containerRuntimeFactory.processAllMessages();
1149
- containerRuntime1.connected = true;
1150
- containerRuntimeFactory.processAllMessages();
1151
- assert.equal(sharedString2.getText(), "hello frnd");
1152
- assertSequenceIntervals(sharedString2, collection2, [{ start: 5, end: 8 }]);
1153
- assertSequenceIntervals(sharedString, collection1, [{ start: 5, end: 8 }]);
1154
- });
1155
- });
1156
- describe("querying intervals with index API's", () => {
1157
- describe("support attaching/detaching an index", () => {
1158
- let collection;
1159
- let mockIntervalIndex;
1160
- let id1;
1161
- let id2;
1162
- beforeEach(() => {
1163
- sharedString.initializeLocal();
1164
- collection = sharedString.getIntervalCollection("test");
1165
- sharedString.insertText(0, "xyzabc");
1166
- id1 = collection.add({ start: 1, end: 1 }).getIntervalId();
1167
- id2 = collection.add({ start: 1, end: 3 }).getIntervalId();
1168
- mockIntervalIndex = new MockIntervalIndex();
1169
- collection.attachIndex(mockIntervalIndex);
1170
- });
1171
- it("can add all intervals in collection to the attached index", () => {
1172
- assert.strictEqual(collection.getIntervalById(id1), mockIntervalIndex.get(0));
1173
- assert.strictEqual(collection.getIntervalById(id2), mockIntervalIndex.get(1));
1174
- });
1175
- it("the intervals in attached index should be synced with those in collection after updating", () => {
1176
- const id3 = collection.add({ start: 2, end: 5 }).getIntervalId();
1177
- assert.strictEqual(collection.getIntervalById(id3), mockIntervalIndex.get(2));
1178
- collection.removeIntervalById(id2);
1179
- assert.strictEqual(collection.getIntervalById(id1), mockIntervalIndex.get(0));
1180
- assert.strictEqual(collection.getIntervalById(id3), mockIntervalIndex.get(1));
1181
- });
1182
- it("detached index should not affect the intervals in collection", () => {
1183
- assert.equal(collection.detachIndex(mockIntervalIndex), true);
1184
- assert.equal(mockIntervalIndex.size(), 0);
1185
- assertIntervalEquals(sharedString, collection.getIntervalById(id1), {
1186
- start: 1,
1187
- end: 1,
1188
- });
1189
- assertIntervalEquals(sharedString, collection.getIntervalById(id2), {
1190
- start: 1,
1191
- end: 3,
1192
- });
1193
- });
1194
- it("can not detach the index does not exist", () => {
1195
- assert.equal(collection.detachIndex(mockIntervalIndex), true);
1196
- assert.equal(collection.detachIndex(mockIntervalIndex), false);
1197
- });
1198
- });
1199
- });
1200
- describe("maintain consistency between the collection label and that in interval properties", () => {
1201
- let collection;
1202
- beforeEach(() => {
1203
- sharedString.initializeLocal();
1204
- collection = sharedString.getIntervalCollection("test");
1205
- sharedString.insertText(0, "xyz");
1206
- });
1207
- it("can not insert the interval which does not belong to this collection", () => {
1208
- assert.throws(() => {
1209
- collection.add({
1210
- start: 1,
1211
- end: 1,
1212
- props: {
1213
- [reservedRangeLabelsKey]: ["test2"],
1214
- },
1215
- });
1216
- }, LoggingError, "The collection is unable to add an interval which does not belong to it");
1217
- });
1218
- it("can not modify the interval's label after it has been inserted to the collection", () => {
1219
- const id = collection.add({ start: 1, end: 1 }).getIntervalId();
1220
- assert.throws(() => {
1221
- collection.change(id, { props: { [reservedRangeLabelsKey]: ["test2"] } });
1222
- }, LoggingError, "The label property of an interval should not be modified once inserted to the collection");
1223
- });
1224
- });
1225
- describe("interval stickiness", () => {
1226
- let containerRuntimeFactory;
1227
- beforeEach(() => {
1228
- dataStoreRuntime1 = new MockFluidDataStoreRuntime({ clientId: "1" });
1229
- dataStoreRuntime1.options = {
1230
- intervalStickinessEnabled: true,
1231
- mergeTreeReferencesCanSlideToEndpoint: true,
1232
- };
1233
- sharedString = new SharedString(dataStoreRuntime1, "shared-string-1", SharedStringFactory.Attributes);
1234
- containerRuntimeFactory = new MockContainerRuntimeFactory();
1235
- dataStoreRuntime1.setAttachState(AttachState.Attached);
1236
- const containerRuntime1 = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime1);
1237
- const services1 = {
1238
- deltaConnection: containerRuntime1.createDeltaConnection(),
1239
- objectStorage: new MockStorage(),
1240
- };
1241
- sharedString.initializeLocal();
1242
- sharedString.connect(services1);
1243
- });
1244
- it("has start stickiness", () => {
1245
- // (-Xabc)-
1246
- // (-Xdefabc)-
1247
- const collection = sharedString.getIntervalCollection("test");
1248
- sharedString.insertText(0, "Xabc");
1249
- containerRuntimeFactory.processAllMessages();
1250
- const interval1 = collection.add({
1251
- start: "start",
1252
- end: { pos: 3, side: Side.After },
1253
- });
1254
- assert.equal(interval1.stickiness, IntervalStickiness.START);
1255
- assert.equal(interval1.startSide, Side.Before);
1256
- assert.equal(interval1.endSide, Side.After);
1257
- assert.equal(interval1.start.slidingPreference, SlidingPreference.BACKWARD);
1258
- assert.equal(interval1.end.slidingPreference, SlidingPreference.BACKWARD);
1259
- const intervalId = interval1.getIntervalId();
1260
- assert(intervalId);
1261
- sharedString.insertText(1, "def");
1262
- containerRuntimeFactory.processAllMessages();
1263
- assert.strictEqual(sharedString.getText(), "Xdefabc", "different text");
1264
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 6 }]);
1265
- });
1266
- it("has start stickiness during delete inside interval", () => {
1267
- // (-Xabc)-
1268
- // (-Xdefabc)-
1269
- // (-Xfabc)-
1270
- const collection = sharedString.getIntervalCollection("test");
1271
- sharedString.insertText(0, "Xabc");
1272
- containerRuntimeFactory.processAllMessages();
1273
- const interval1 = collection.add({ start: "start", end: { pos: 3, side: Side.After } });
1274
- assert.equal(interval1.stickiness, IntervalStickiness.START);
1275
- const intervalId = interval1.getIntervalId();
1276
- assert(intervalId);
1277
- sharedString.insertText(1, "def");
1278
- containerRuntimeFactory.processAllMessages();
1279
- sharedString.removeRange(1, 3);
1280
- containerRuntimeFactory.processAllMessages();
1281
- assert.strictEqual(sharedString.getText(), "Xfabc", "different text");
1282
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 4 }]);
1283
- });
1284
- it("has start stickiness during delete of start of interval", () => {
1285
- // -abc(Xdef]-
1286
- // -abc(Xghidef]-
1287
- // -(aghidef]-
1288
- const collection = sharedString.getIntervalCollection("test");
1289
- sharedString.insertText(0, "abcXdef");
1290
- containerRuntimeFactory.processAllMessages();
1291
- const interval1 = collection.add({
1292
- start: { pos: 3, side: Side.After },
1293
- end: { pos: 6, side: Side.After },
1294
- props: undefined,
1295
- });
1296
- assert.equal(interval1.stickiness, IntervalStickiness.START);
1297
- assert.equal(interval1.startSide, Side.After);
1298
- assert.equal(interval1.endSide, Side.After);
1299
- const intervalId = interval1.getIntervalId();
1300
- assert(intervalId);
1301
- sharedString.insertText(4, "ghi");
1302
- containerRuntimeFactory.processAllMessages();
1303
- assert.strictEqual(sharedString.getText(), "abcXghidef", "different text");
1304
- assertSequenceIntervals(sharedString, collection, [{ start: 3, end: 9 }]);
1305
- sharedString.removeRange(1, 4);
1306
- containerRuntimeFactory.processAllMessages();
1307
- assert.strictEqual(interval1.start.getSegment()?.constructor.name, "TextSegment");
1308
- assert.strictEqual(interval1.start.getSegment()?.isLeaf(), true);
1309
- assert.strictEqual(interval1.end.getSegment()?.constructor.name, "TextSegment");
1310
- assert.strictEqual(sharedString.getText(), "aghidef", "different text");
1311
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 6 }]);
1312
- });
1313
- it("has start stickiness when spanning whole string and insertion at index 0", () => {
1314
- // (-abc]-
1315
- // (-Xabc]-
1316
- const collection = sharedString.getIntervalCollection("test");
1317
- sharedString.insertText(0, "abc");
1318
- containerRuntimeFactory.processAllMessages();
1319
- const interval1 = collection.add({ start: "start", end: { pos: 2, side: Side.After } });
1320
- assert.equal(interval1.stickiness, IntervalStickiness.START);
1321
- const intervalId = interval1.getIntervalId();
1322
- assert(intervalId);
1323
- sharedString.insertText(0, "X");
1324
- containerRuntimeFactory.processAllMessages();
1325
- assert.strictEqual(interval1.start.getSegment()?.constructor.name, "StartOfTreeSegment");
1326
- assert.strictEqual(interval1.end.getSegment()?.constructor.name, "TextSegment");
1327
- assert.strictEqual(sharedString.getText(), "Xabc", "different text");
1328
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 3 }], false);
1329
- });
1330
- it("has full stickiness when spanning whole string and insertion at index 0", () => {
1331
- // (-abc)-
1332
- // (-Xabc)-
1333
- const collection = sharedString.getIntervalCollection("test");
1334
- sharedString.insertText(0, "abc");
1335
- containerRuntimeFactory.processAllMessages();
1336
- const interval1 = collection.add({
1337
- start: "start",
1338
- end: { pos: 2, side: Side.Before },
1339
- props: undefined,
1340
- });
1341
- assert.equal(interval1.stickiness, IntervalStickiness.FULL);
1342
- const intervalId = interval1.getIntervalId();
1343
- assert(intervalId);
1344
- sharedString.insertText(0, "X");
1345
- containerRuntimeFactory.processAllMessages();
1346
- assert.strictEqual(interval1.start.getSegment()?.constructor.name, "StartOfTreeSegment");
1347
- assert.strictEqual(interval1.end.getSegment()?.constructor.name, "TextSegment");
1348
- assert.strictEqual(sharedString.getText(), "Xabc", "different text");
1349
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 3 }], false);
1350
- });
1351
- it("has end stickiness when spanning whole string and insertion at index 0", () => {
1352
- // -[abc-)
1353
- // -X[abc-)
1354
- // -X[abcX-)
1355
- const collection = sharedString.getIntervalCollection("test");
1356
- sharedString.insertText(0, "abc");
1357
- containerRuntimeFactory.processAllMessages();
1358
- const interval1 = collection.add({ start: 0, end: "end" });
1359
- assert.equal(interval1.stickiness, IntervalStickiness.END);
1360
- const intervalId = interval1.getIntervalId();
1361
- assert(intervalId);
1362
- sharedString.insertText(0, "X");
1363
- containerRuntimeFactory.processAllMessages();
1364
- assert.strictEqual(interval1.start.getSegment()?.constructor.name, "TextSegment");
1365
- assert.strictEqual(interval1.end.getSegment()?.constructor.name, "EndOfTreeSegment");
1366
- assert.strictEqual(sharedString.getText(), "Xabc", "different text");
1367
- assertSequenceIntervals(sharedString, collection, [{ start: 1, end: 4 }], false);
1368
- sharedString.insertText(4, "X");
1369
- containerRuntimeFactory.processAllMessages();
1370
- assert.strictEqual(interval1.start.getSegment()?.constructor.name, "TextSegment");
1371
- assert.strictEqual(interval1.end.getSegment()?.constructor.name, "EndOfTreeSegment");
1372
- assert.strictEqual(sharedString.getText(), "XabcX", "different text");
1373
- assertSequenceIntervals(sharedString, collection, [{ start: 1, end: 5 }], false);
1374
- });
1375
- it("full stickiness doesn't slide off string when entire string is deleted", () => {
1376
- // -(abc)def-
1377
- const collection = sharedString.getIntervalCollection("test");
1378
- sharedString.insertText(0, "abcdef");
1379
- containerRuntimeFactory.processAllMessages();
1380
- const interval1 = collection.add({
1381
- start: { pos: 0, side: Side.After },
1382
- end: { pos: 2, side: Side.Before },
1383
- props: undefined,
1384
- });
1385
- assert.equal(interval1.stickiness, IntervalStickiness.FULL);
1386
- assert.equal(interval1.startSide, Side.After);
1387
- assert.equal(interval1.endSide, Side.Before);
1388
- const intervalId = interval1.getIntervalId();
1389
- assert(intervalId);
1390
- sharedString.removeRange(0, 6);
1391
- containerRuntimeFactory.processAllMessages();
1392
- sharedString.insertText(0, "XXX");
1393
- containerRuntimeFactory.processAllMessages();
1394
- assert.strictEqual(sharedString.getText(), "XXX", "different text");
1395
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 3 }], false);
1396
- });
1397
- it("none stickiness slides off string when entire string is deleted", () => {
1398
- const collection = sharedString.getIntervalCollection("test");
1399
- sharedString.insertText(0, "abc");
1400
- containerRuntimeFactory.processAllMessages();
1401
- const interval1 = collection.add({
1402
- start: { pos: 1, side: Side.Before },
1403
- end: { pos: 2, side: Side.After },
1404
- props: undefined,
1405
- });
1406
- assert.equal(interval1.stickiness, IntervalStickiness.NONE);
1407
- const intervalId = interval1.getIntervalId();
1408
- assert(intervalId);
1409
- sharedString.removeRange(0, 3);
1410
- containerRuntimeFactory.processAllMessages();
1411
- sharedString.insertText(0, "XXX");
1412
- containerRuntimeFactory.processAllMessages();
1413
- assert.strictEqual(sharedString.getText(), "XXX", "different text");
1414
- assertSequenceIntervals(sharedString, collection, [{ start: -1, end: -1 }], false);
1415
- });
1416
- it("none stickiness slides off string when entire string is deleted incrementally", () => {
1417
- const collection = sharedString.getIntervalCollection("test");
1418
- sharedString.insertText(0, "abc");
1419
- containerRuntimeFactory.processAllMessages();
1420
- const interval1 = collection.add({
1421
- start: { pos: 1, side: Side.Before },
1422
- end: { pos: 2, side: Side.After },
1423
- props: undefined,
1424
- });
1425
- assert.equal(interval1.stickiness, IntervalStickiness.NONE);
1426
- const intervalId = interval1.getIntervalId();
1427
- assert(intervalId);
1428
- sharedString.removeRange(0, 1);
1429
- sharedString.removeRange(0, 1);
1430
- sharedString.removeRange(0, 1);
1431
- containerRuntimeFactory.processAllMessages();
1432
- sharedString.insertText(0, "XXX");
1433
- containerRuntimeFactory.processAllMessages();
1434
- assert.strictEqual(sharedString.getText(), "XXX", "different text");
1435
- assertSequenceIntervals(sharedString, collection, [{ start: -1, end: -1 }], false);
1436
- });
1437
- it("full stickiness doesn't slide off string when entire string is deleted incrementally", () => {
1438
- // -(abc)-
1439
- // (--)
1440
- // (-XXX-)
1441
- const collection = sharedString.getIntervalCollection("test");
1442
- sharedString.insertText(0, "abc");
1443
- containerRuntimeFactory.processAllMessages();
1444
- const interval1 = collection.add({
1445
- start: { pos: 0, side: Side.After },
1446
- end: { pos: 2, side: Side.Before },
1447
- props: undefined,
1448
- });
1449
- assert.equal(interval1.stickiness, IntervalStickiness.FULL);
1450
- const intervalId = interval1.getIntervalId();
1451
- assert(intervalId);
1452
- sharedString.removeRange(0, 1);
1453
- sharedString.removeRange(0, 1);
1454
- sharedString.removeRange(0, 1);
1455
- containerRuntimeFactory.processAllMessages();
1456
- sharedString.insertText(0, "XXX");
1457
- containerRuntimeFactory.processAllMessages();
1458
- assert.strictEqual(sharedString.getText(), "XXX", "different text");
1459
- assert.strictEqual(interval1.start.slidingPreference, SlidingPreference.BACKWARD);
1460
- assert.strictEqual(interval1.end.slidingPreference, SlidingPreference.FORWARD);
1461
- assert.strictEqual(interval1.start.getSegment()?.constructor.name, "StartOfTreeSegment");
1462
- assert.strictEqual(interval1.end.getSegment()?.constructor.name, "EndOfTreeSegment");
1463
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 3 }], false);
1464
- });
1465
- it("doesn't have start stickiness when spanning whole string and insertion at index 0", () => {
1466
- // -[abc-)
1467
- // -X[abc-)
1468
- const collection = sharedString.getIntervalCollection("test");
1469
- sharedString.insertText(0, "abc");
1470
- containerRuntimeFactory.processAllMessages();
1471
- const interval1 = collection.add({
1472
- start: { pos: 0, side: Side.Before },
1473
- end: "end",
1474
- props: undefined,
1475
- });
1476
- assert.equal(interval1.stickiness, IntervalStickiness.END);
1477
- const intervalId = interval1.getIntervalId();
1478
- assert(intervalId);
1479
- sharedString.insertText(0, "X");
1480
- containerRuntimeFactory.processAllMessages();
1481
- assert.notStrictEqual(interval1.start.getSegment()?.constructor.name, "StartOfTreeSegment");
1482
- assert.strictEqual(sharedString.getText(), "Xabc", "different text");
1483
- assertSequenceIntervals(sharedString, collection, [{ start: 1, end: 4 }], false);
1484
- });
1485
- it("slides to endpoint after deleting all text to left of start-sticky+exclusive reference", () => {
1486
- // -a(bcde]f-
1487
- // (-Xde]f
1488
- const collection = sharedString.getIntervalCollection("test");
1489
- sharedString.insertText(0, "abcdef");
1490
- containerRuntimeFactory.processAllMessages();
1491
- const interval1 = collection.add({
1492
- start: { pos: 1, side: Side.After },
1493
- end: { pos: 5, side: Side.After },
1494
- props: undefined,
1495
- });
1496
- assert.equal(interval1.stickiness, IntervalStickiness.START);
1497
- const intervalId = interval1.getIntervalId();
1498
- assert(intervalId);
1499
- sharedString.removeRange(0, 3);
1500
- sharedString.insertText(0, "X");
1501
- containerRuntimeFactory.processAllMessages();
1502
- assert.strictEqual(sharedString.getText(), "Xdef", "different text");
1503
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 3 }], false);
1504
- });
1505
- it("has end stickiness", () => {
1506
- // -[abc)-
1507
- // -[abdefc)-
1508
- const collection = sharedString.getIntervalCollection("test");
1509
- sharedString.insertText(0, "abc");
1510
- containerRuntimeFactory.processAllMessages();
1511
- const interval1 = collection.add({
1512
- start: { pos: 0, side: Side.Before },
1513
- end: { pos: 2, side: Side.Before },
1514
- props: undefined,
1515
- });
1516
- assert.equal(interval1.stickiness, IntervalStickiness.END);
1517
- assert.equal(interval1.start.slidingPreference, SlidingPreference.FORWARD);
1518
- assert.equal(interval1.end.slidingPreference, SlidingPreference.FORWARD);
1519
- const intervalId = interval1.getIntervalId();
1520
- assert(intervalId);
1521
- sharedString.insertText(2, "def");
1522
- containerRuntimeFactory.processAllMessages();
1523
- assert.strictEqual(sharedString.getText(), "abdefc", "different text");
1524
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 5 }]);
1525
- });
1526
- it("has end stickiness during delete of end of interval", () => {
1527
- // -[abcX)-
1528
- // -[abcf)-
1529
- const collection = sharedString.getIntervalCollection("test");
1530
- sharedString.insertText(0, "abcXdef");
1531
- containerRuntimeFactory.processAllMessages();
1532
- const interval1 = collection.add({
1533
- start: { pos: 0, side: Side.Before },
1534
- end: { pos: 4, side: Side.Before },
1535
- props: undefined,
1536
- });
1537
- assert.equal(interval1.stickiness, IntervalStickiness.END);
1538
- const intervalId = interval1.getIntervalId();
1539
- assert(intervalId);
1540
- containerRuntimeFactory.processAllMessages();
1541
- sharedString.removeRange(3, 6);
1542
- containerRuntimeFactory.processAllMessages();
1543
- assert.strictEqual(sharedString.getText(), "abcf", "different text");
1544
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 3 }]);
1545
- });
1546
- it("has end stickiness by default", () => {
1547
- // [abcX)
1548
- // [abcf)
1549
- const collection = sharedString.getIntervalCollection("test");
1550
- sharedString.insertText(0, "abcXdef");
1551
- containerRuntimeFactory.processAllMessages();
1552
- const interval1 = collection.add({ start: 0, end: 3 });
1553
- assert.equal(interval1.stickiness, IntervalStickiness.END);
1554
- assert.equal(interval1.start.slidingPreference, SlidingPreference.FORWARD);
1555
- assert.equal(interval1.end.slidingPreference, SlidingPreference.FORWARD);
1556
- const intervalId = interval1.getIntervalId();
1557
- assert(intervalId);
1558
- containerRuntimeFactory.processAllMessages();
1559
- sharedString.removeRange(3, 6);
1560
- containerRuntimeFactory.processAllMessages();
1561
- assert.strictEqual(sharedString.getText(), "abcf", "different text");
1562
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 3 }]);
1563
- });
1564
- it("has none stickiness during insert", () => {
1565
- // -[ab]c-
1566
- const collection = sharedString.getIntervalCollection("test");
1567
- sharedString.insertText(0, "abc");
1568
- containerRuntimeFactory.processAllMessages();
1569
- const interval1 = collection.add({
1570
- start: { pos: 0, side: Side.Before },
1571
- end: { pos: 1, side: Side.After },
1572
- props: undefined,
1573
- });
1574
- assert.equal(interval1.stickiness, IntervalStickiness.NONE);
1575
- assert.equal(interval1.start.slidingPreference, SlidingPreference.FORWARD);
1576
- assert.equal(interval1.end.slidingPreference, SlidingPreference.BACKWARD);
1577
- const intervalId = interval1.getIntervalId();
1578
- assert(intervalId);
1579
- sharedString.insertText(2, "def");
1580
- containerRuntimeFactory.processAllMessages();
1581
- assert.strictEqual(sharedString.getText(), "abdefc", "different text");
1582
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 1 }]);
1583
- });
1584
- it("has correct sliding preference for full stickiness", () => {
1585
- const collection = sharedString.getIntervalCollection("test");
1586
- sharedString.insertText(0, "abc");
1587
- containerRuntimeFactory.processAllMessages();
1588
- const interval1 = collection.add({
1589
- start: "start",
1590
- end: { pos: 2, side: Side.Before },
1591
- props: undefined,
1592
- });
1593
- assert.equal(interval1.stickiness, IntervalStickiness.FULL);
1594
- assert.equal(interval1.start.slidingPreference, SlidingPreference.BACKWARD);
1595
- assert.equal(interval1.end.slidingPreference, SlidingPreference.FORWARD);
1596
- });
1597
- it("slides backward reference to correct position when remove is unacked", () => {
1598
- sharedString.insertText(0, "ABC");
1599
- // (AB]C
1600
- containerRuntimeFactory.processAllMessages();
1601
- const start = { pos: 0, side: Side.After };
1602
- const end = { pos: 1, side: Side.After };
1603
- const collection = sharedString.getIntervalCollection("test");
1604
- collection.add({ end, start });
1605
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 1 }]);
1606
- sharedString.removeText(1, 2);
1607
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 0 }]);
1608
- containerRuntimeFactory.processAllMessages();
1609
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 0 }]);
1610
- });
1611
- it("slides backward reference to correct position when remove multiple segments is unacked", () => {
1612
- sharedString.insertText(0, "ABC");
1613
- // (AB]C
1614
- // (AYYYXXXB]C
1615
- containerRuntimeFactory.processAllMessages();
1616
- const start = { pos: 0, side: Side.After };
1617
- const end = { pos: 1, side: Side.After };
1618
- const collection = sharedString.getIntervalCollection("test");
1619
- collection.add({ end, start });
1620
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 1 }]);
1621
- sharedString.insertText(1, "XXX");
1622
- sharedString.insertText(1, "YYY");
1623
- sharedString.removeText(1, 8);
1624
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 0 }]);
1625
- containerRuntimeFactory.processAllMessages();
1626
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 0 }]);
1627
- });
1628
- it("slides backward reference to correct position when start of string remove is unacked", () => {
1629
- sharedString.insertText(0, "ABC");
1630
- // (AB]C
1631
- containerRuntimeFactory.processAllMessages();
1632
- const start = { pos: 0, side: Side.After };
1633
- const end = { pos: 1, side: Side.Before };
1634
- const collection = sharedString.getIntervalCollection("test");
1635
- const interval = collection.add({ end, start });
1636
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 1 }]);
1637
- sharedString.removeText(0, 2);
1638
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 0 }]);
1639
- containerRuntimeFactory.processAllMessages();
1640
- assert.strictEqual(interval.start.getSegment()?.constructor.name, "StartOfTreeSegment");
1641
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 0 }]);
1642
- });
1643
- it.skip("slides forward reference to correct position when remove of end of string is unacked", () => {
1644
- dataStoreRuntime1.options.mergeTreeReferencesCanSlideToEndpoint = false;
1645
- sharedString.insertText(0, "ABC");
1646
- // (ABC]
1647
- containerRuntimeFactory.processAllMessages();
1648
- const start = 0;
1649
- const end = 2;
1650
- const collection = sharedString.getIntervalCollection("test");
1651
- collection.add({ end, start });
1652
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 2 }]);
1653
- sharedString.removeText(1, 3);
1654
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 0 }]);
1655
- containerRuntimeFactory.processAllMessages();
1656
- assertSequenceIntervals(sharedString, collection, [{ start: 0, end: 0 }]);
1657
- });
1658
- });
1659
- });
1660
- //# sourceMappingURL=intervalCollection.spec.js.map