@fluidframework/map 2.0.0-rc.1.0.6 → 2.0.0-rc.2.0.1

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 (132) hide show
  1. package/{.eslintrc.js → .eslintrc.cjs} +10 -1
  2. package/{.mocharc.js → .mocharc.cjs} +1 -1
  3. package/CHANGELOG.md +11 -0
  4. package/{api-extractor-esm.json → api-extractor-cjs.json} +5 -1
  5. package/api-extractor-lint.json +1 -1
  6. package/api-extractor.json +1 -1
  7. package/api-report/map.api.md +14 -57
  8. package/dist/directory.d.ts +10 -50
  9. package/dist/directory.d.ts.map +1 -1
  10. package/dist/directory.js +76 -164
  11. package/dist/directory.js.map +1 -1
  12. package/dist/index.d.ts +45 -4
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +43 -8
  15. package/dist/index.js.map +1 -1
  16. package/dist/interfaces.d.ts.map +1 -1
  17. package/dist/interfaces.js.map +1 -1
  18. package/dist/internalInterfaces.d.ts +2 -2
  19. package/dist/internalInterfaces.d.ts.map +1 -1
  20. package/dist/internalInterfaces.js.map +1 -1
  21. package/dist/localValues.d.ts +3 -5
  22. package/dist/localValues.d.ts.map +1 -1
  23. package/dist/localValues.js +9 -8
  24. package/dist/localValues.js.map +1 -1
  25. package/dist/map-alpha.d.ts +31 -116
  26. package/dist/map-beta.d.ts +24 -105
  27. package/dist/map-public.d.ts +24 -105
  28. package/dist/map-untrimmed.d.ts +31 -116
  29. package/dist/map.d.ts +4 -23
  30. package/dist/map.d.ts.map +1 -1
  31. package/dist/map.js +6 -29
  32. package/dist/map.js.map +1 -1
  33. package/dist/mapKernel.d.ts +3 -4
  34. package/dist/mapKernel.d.ts.map +1 -1
  35. package/dist/mapKernel.js +30 -35
  36. package/dist/mapKernel.js.map +1 -1
  37. package/dist/package.json +3 -0
  38. package/dist/packageVersion.d.ts +1 -1
  39. package/dist/packageVersion.js +1 -1
  40. package/dist/packageVersion.js.map +1 -1
  41. package/dist/tsdoc-metadata.json +1 -1
  42. package/lib/{directory.d.mts → directory.d.ts} +11 -51
  43. package/lib/directory.d.ts.map +1 -0
  44. package/lib/{directory.mjs → directory.js} +77 -165
  45. package/lib/directory.js.map +1 -0
  46. package/lib/index.d.ts +61 -0
  47. package/lib/index.d.ts.map +1 -0
  48. package/lib/index.js +55 -0
  49. package/lib/index.js.map +1 -0
  50. package/lib/{interfaces.d.mts → interfaces.d.ts} +1 -1
  51. package/lib/interfaces.d.ts.map +1 -0
  52. package/lib/{interfaces.mjs → interfaces.js} +1 -1
  53. package/lib/interfaces.js.map +1 -0
  54. package/lib/{internalInterfaces.d.mts → internalInterfaces.d.ts} +3 -3
  55. package/lib/internalInterfaces.d.ts.map +1 -0
  56. package/lib/{internalInterfaces.mjs → internalInterfaces.js} +1 -1
  57. package/lib/internalInterfaces.js.map +1 -0
  58. package/lib/{localValues.d.mts → localValues.d.ts} +4 -6
  59. package/lib/localValues.d.ts.map +1 -0
  60. package/lib/{localValues.mjs → localValues.js} +10 -9
  61. package/lib/localValues.js.map +1 -0
  62. package/lib/{map-alpha.d.mts → map-alpha.d.ts} +43 -116
  63. package/lib/{map-beta.d.mts → map-beta.d.ts} +36 -105
  64. package/lib/{map-public.d.mts → map-public.d.ts} +36 -105
  65. package/lib/{map-untrimmed.d.mts → map-untrimmed.d.ts} +43 -116
  66. package/lib/{map.d.mts → map.d.ts} +5 -24
  67. package/lib/map.d.ts.map +1 -0
  68. package/lib/{map.mjs → map.js} +5 -28
  69. package/lib/map.js.map +1 -0
  70. package/lib/{mapKernel.d.mts → mapKernel.d.ts} +4 -5
  71. package/lib/mapKernel.d.ts.map +1 -0
  72. package/lib/{mapKernel.mjs → mapKernel.js} +32 -37
  73. package/lib/mapKernel.js.map +1 -0
  74. package/lib/{packageVersion.d.mts → packageVersion.d.ts} +2 -2
  75. package/lib/packageVersion.d.ts.map +1 -0
  76. package/lib/{packageVersion.mjs → packageVersion.js} +2 -2
  77. package/lib/packageVersion.js.map +1 -0
  78. package/lib/test/memory/directory.spec.js +71 -0
  79. package/lib/test/memory/directory.spec.js.map +1 -0
  80. package/lib/test/memory/map.spec.js +71 -0
  81. package/lib/test/memory/map.spec.js.map +1 -0
  82. package/lib/test/mocha/directory.order.spec.js +422 -0
  83. package/lib/test/mocha/directory.order.spec.js.map +1 -0
  84. package/lib/test/mocha/directory.snapshot.spec.js +111 -0
  85. package/lib/test/mocha/directory.snapshot.spec.js.map +1 -0
  86. package/lib/test/mocha/directory.spec.js +1406 -0
  87. package/lib/test/mocha/directory.spec.js.map +1 -0
  88. package/lib/test/mocha/directoryEquivalenceUtils.js +36 -0
  89. package/lib/test/mocha/directoryEquivalenceUtils.js.map +1 -0
  90. package/lib/test/mocha/directoryFuzzTests.spec.js +337 -0
  91. package/lib/test/mocha/directoryFuzzTests.spec.js.map +1 -0
  92. package/lib/test/mocha/dirname.cjs +16 -0
  93. package/lib/test/mocha/dirname.cjs.map +1 -0
  94. package/lib/test/mocha/map.fuzz.spec.js +114 -0
  95. package/lib/test/mocha/map.fuzz.spec.js.map +1 -0
  96. package/lib/test/mocha/map.spec.js +685 -0
  97. package/lib/test/mocha/map.spec.js.map +1 -0
  98. package/lib/test/mocha/rebasing.spec.js +158 -0
  99. package/lib/test/mocha/rebasing.spec.js.map +1 -0
  100. package/lib/test/mocha/reconnection.spec.js +327 -0
  101. package/lib/test/mocha/reconnection.spec.js.map +1 -0
  102. package/lib/test/types/validateMapPrevious.generated.js +66 -0
  103. package/lib/test/types/validateMapPrevious.generated.js.map +1 -0
  104. package/package.json +55 -52
  105. package/src/directory.ts +122 -217
  106. package/src/index.ts +57 -4
  107. package/src/interfaces.ts +2 -2
  108. package/src/internalInterfaces.ts +2 -2
  109. package/src/localValues.ts +14 -9
  110. package/src/map.ts +7 -32
  111. package/src/mapKernel.ts +40 -42
  112. package/src/packageVersion.ts +1 -1
  113. package/tsconfig.cjs.json +7 -0
  114. package/tsconfig.json +2 -5
  115. package/lib/directory.d.mts.map +0 -1
  116. package/lib/directory.mjs.map +0 -1
  117. package/lib/index.d.mts +0 -9
  118. package/lib/index.d.mts.map +0 -1
  119. package/lib/index.mjs +0 -8
  120. package/lib/index.mjs.map +0 -1
  121. package/lib/interfaces.d.mts.map +0 -1
  122. package/lib/interfaces.mjs.map +0 -1
  123. package/lib/internalInterfaces.d.mts.map +0 -1
  124. package/lib/internalInterfaces.mjs.map +0 -1
  125. package/lib/localValues.d.mts.map +0 -1
  126. package/lib/localValues.mjs.map +0 -1
  127. package/lib/map.d.mts.map +0 -1
  128. package/lib/map.mjs.map +0 -1
  129. package/lib/mapKernel.d.mts.map +0 -1
  130. package/lib/mapKernel.mjs.map +0 -1
  131. package/lib/packageVersion.d.mts.map +0 -1
  132. package/lib/packageVersion.mjs.map +0 -1
@@ -0,0 +1,685 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { strict as assert } from "node:assert";
6
+ import { runGCTests } from "@fluid-private/test-dds-utils";
7
+ import { MockFluidDataStoreRuntime, MockContainerRuntimeFactory, MockSharedObjectServices, MockStorage, } from "@fluidframework/test-runtime-utils";
8
+ import { AttachState } from "@fluidframework/container-definitions";
9
+ import { MapFactory, SharedMap } from "../../map.js";
10
+ function createConnectedMap(id, runtimeFactory) {
11
+ const dataStoreRuntime = new MockFluidDataStoreRuntime();
12
+ const containerRuntime = runtimeFactory.createContainerRuntime(dataStoreRuntime);
13
+ const services = {
14
+ deltaConnection: dataStoreRuntime.createDeltaConnection(),
15
+ objectStorage: new MockStorage(),
16
+ };
17
+ const map = new SharedMap(id, dataStoreRuntime, MapFactory.Attributes);
18
+ map.connect(services);
19
+ return map;
20
+ }
21
+ function createLocalMap(id) {
22
+ const map = new SharedMap(id, new MockFluidDataStoreRuntime(), MapFactory.Attributes);
23
+ return map;
24
+ }
25
+ class TestSharedMap extends SharedMap {
26
+ testApplyStashedOp(content) {
27
+ this.lastMetadata = undefined;
28
+ this.applyStashedOp(content);
29
+ return this.lastMetadata;
30
+ }
31
+ submitLocalMessage(op, localOpMetadata) {
32
+ this.lastMetadata = localOpMetadata;
33
+ super.submitLocalMessage(op, localOpMetadata);
34
+ }
35
+ }
36
+ describe("Map", () => {
37
+ describe("Local state", () => {
38
+ let map;
39
+ beforeEach("createLocalMap", async () => {
40
+ map = createLocalMap("testMap");
41
+ });
42
+ describe("API", () => {
43
+ it("Can create a new map", () => {
44
+ assert.ok(map, "could not create a new map");
45
+ });
46
+ it("Can set and get map data", async () => {
47
+ map.set("testKey", "testValue");
48
+ map.set("testKey2", "testValue2");
49
+ assert.equal(map.get("testKey"), "testValue", "could not retrieve set key 1");
50
+ assert.equal(map.get("testKey2"), "testValue2", "could not retrieve set key 2");
51
+ });
52
+ it("should fire correct map events", async () => {
53
+ const dummyMap = map;
54
+ let valueChangedExpected = true;
55
+ let clearExpected = false;
56
+ let previousValue;
57
+ dummyMap.on("op", (arg1, arg2, arg3) => {
58
+ assert.fail("shouldn't receive an op event");
59
+ });
60
+ dummyMap.on("valueChanged", (changed, local, target) => {
61
+ assert.equal(valueChangedExpected, true, "valueChange event not expected");
62
+ valueChangedExpected = false;
63
+ assert.equal(changed.key, "marco");
64
+ assert.equal(changed.previousValue, previousValue);
65
+ assert.equal(local, true, "local should be true for local action for valueChanged event");
66
+ assert.equal(target, dummyMap, "target should be the map for valueChanged event");
67
+ });
68
+ dummyMap.on("clear", (local, target) => {
69
+ assert.equal(clearExpected, true, "clear event not expected");
70
+ clearExpected = false;
71
+ assert.equal(local, true, "local should be true for local action for clear event");
72
+ assert.equal(target, dummyMap, "target should be the map for clear event");
73
+ });
74
+ dummyMap.on("error", (error) => {
75
+ // propagate error in the event handlers
76
+ throw error;
77
+ });
78
+ // Test set
79
+ previousValue = undefined;
80
+ dummyMap.set("marco", "polo");
81
+ assert.equal(valueChangedExpected, false, "missing valueChanged event");
82
+ // Test delete
83
+ previousValue = "polo";
84
+ valueChangedExpected = true;
85
+ dummyMap.delete("marco");
86
+ assert.equal(valueChangedExpected, false, "missing valueChanged event");
87
+ // Test clear
88
+ clearExpected = true;
89
+ dummyMap.clear();
90
+ assert.equal(clearExpected, false, "missing clear event");
91
+ });
92
+ it("Should return undefined when a key does not exist in the map", () => {
93
+ assert.equal(map.get("missing"), undefined, "get() did not return undefined for missing key");
94
+ });
95
+ it("Should reject undefined and null key sets", () => {
96
+ assert.throws(() => {
97
+ map.set(undefined, "one");
98
+ }, "Should throw for key of undefined");
99
+ assert.throws(() => {
100
+ // eslint-disable-next-line unicorn/no-null
101
+ map.set(null, "two");
102
+ }, "Should throw for key of null");
103
+ });
104
+ });
105
+ describe("Serialize", () => {
106
+ it("Should serialize the map as a JSON object", () => {
107
+ map.set("first", "second");
108
+ map.set("third", "fourth");
109
+ map.set("fifth", "sixth");
110
+ const subMap = createLocalMap("subMap");
111
+ map.set("object", subMap.handle);
112
+ const summaryContent = map.getAttachSummary().summary.tree.header
113
+ .content;
114
+ const subMapHandleUrl = subMap.handle.absolutePath;
115
+ assert.equal(summaryContent, `{"blobs":[],"content":{"first":{"type":"Plain","value":"second"},"third":{"type":"Plain","value":"fourth"},"fifth":{"type":"Plain","value":"sixth"},"object":{"type":"Plain","value":{"type":"__fluid_handle__","url":"${subMapHandleUrl}"}}}}`);
116
+ });
117
+ it("Should serialize an undefined value", () => {
118
+ map.set("first", "second");
119
+ map.set("third", "fourth");
120
+ map.set("fifth", undefined);
121
+ assert.ok(map.has("fifth"));
122
+ const subMap = createLocalMap("subMap");
123
+ map.set("object", subMap.handle);
124
+ const summaryContent = map.getAttachSummary().summary.tree.header
125
+ .content;
126
+ const subMapHandleUrl = subMap.handle.absolutePath;
127
+ assert.equal(summaryContent, `{"blobs":[],"content":{"first":{"type":"Plain","value":"second"},"third":{"type":"Plain","value":"fourth"},"fifth":{"type":"Plain"},"object":{"type":"Plain","value":{"type":"__fluid_handle__","url":"${subMapHandleUrl}"}}}}`);
128
+ });
129
+ it("Should serialize an object with nested handles", async () => {
130
+ const subMap = createLocalMap("subMap");
131
+ const subMap2 = createLocalMap("subMap2");
132
+ const containingObject = {
133
+ subMapHandle: subMap.handle,
134
+ nestedObj: {
135
+ subMap2Handle: subMap2.handle,
136
+ },
137
+ };
138
+ map.set("object", containingObject);
139
+ const subMapHandleUrl = subMap.handle.absolutePath;
140
+ const subMap2HandleUrl = subMap2.handle.absolutePath;
141
+ const summaryContent = map.getAttachSummary().summary.tree.header
142
+ .content;
143
+ assert.equal(summaryContent, `{"blobs":[],"content":{"object":{"type":"Plain","value":{"subMapHandle":{"type":"__fluid_handle__","url":"${subMapHandleUrl}"},"nestedObj":{"subMap2Handle":{"type":"__fluid_handle__","url":"${subMap2HandleUrl}"}}}}}}`);
144
+ });
145
+ it("can load old serialization format", async () => {
146
+ map.set("key", "value");
147
+ const content = JSON.stringify({
148
+ key: {
149
+ type: "Plain",
150
+ value: "value",
151
+ },
152
+ });
153
+ const services = new MockSharedObjectServices({ header: content });
154
+ const factory = new MapFactory();
155
+ const loadedMap = await factory.load(new MockFluidDataStoreRuntime(), "mapId", services, factory.attributes);
156
+ assert(loadedMap.get("key") === "value");
157
+ });
158
+ it("new serialization format for small maps", async () => {
159
+ map.set("key", "value");
160
+ const summaryTree = map.getAttachSummary().summary;
161
+ assert.strictEqual(Object.keys(summaryTree.tree).length, 1, "summary tree should only have one blob");
162
+ const summaryContent = summaryTree.tree.header?.content;
163
+ const expectedContent = JSON.stringify({
164
+ blobs: [],
165
+ content: {
166
+ key: {
167
+ type: "Plain",
168
+ value: "value",
169
+ },
170
+ },
171
+ });
172
+ assert.strictEqual(summaryContent, expectedContent, "The summary content is not as expected");
173
+ const services = new MockSharedObjectServices({ header: summaryContent });
174
+ const factory = new MapFactory();
175
+ const loadedMap = await factory.load(new MockFluidDataStoreRuntime(), "mapId", services, factory.attributes);
176
+ assert(loadedMap.get("key") === "value");
177
+ });
178
+ it("new serialization format for big maps", async () => {
179
+ map.set("key", "value");
180
+ // 40K char string
181
+ let longString = "01234567890";
182
+ for (let i = 0; i < 12; i++) {
183
+ longString = longString + longString;
184
+ }
185
+ map.set("longValue", longString);
186
+ map.set("zzz", "the end");
187
+ const summaryTree = map.getAttachSummary().summary;
188
+ assert.strictEqual(Object.keys(summaryTree.tree).length, 2, "There should be 2 entries in the summary tree");
189
+ const expectedContent1 = JSON.stringify({
190
+ blobs: ["blob0"],
191
+ content: {
192
+ key: {
193
+ type: "Plain",
194
+ value: "value",
195
+ },
196
+ zzz: {
197
+ type: "Plain",
198
+ value: "the end",
199
+ },
200
+ },
201
+ });
202
+ const expectedContent2 = JSON.stringify({
203
+ longValue: {
204
+ type: "Plain",
205
+ value: longString,
206
+ },
207
+ });
208
+ const header = summaryTree.tree.header;
209
+ const blob0 = summaryTree.tree.blob0;
210
+ assert.strictEqual(header?.content, expectedContent1, "header content is not as expected");
211
+ assert.strictEqual(blob0?.content, expectedContent2, "blob0 content is not as expected");
212
+ const services = new MockSharedObjectServices({
213
+ header: header.content,
214
+ blob0: blob0.content,
215
+ });
216
+ const factory = new MapFactory();
217
+ const loadedMap = await factory.load(new MockFluidDataStoreRuntime(), "mapId", services, factory.attributes);
218
+ assert(loadedMap.get("key") === "value");
219
+ assert(loadedMap.get("longValue") === longString);
220
+ assert(loadedMap.get("zzz") === "the end");
221
+ });
222
+ });
223
+ describe("Op processing", () => {
224
+ /**
225
+ * These tests test the scenario found in the following bug:
226
+ * {@link https://github.com/microsoft/FluidFramework/issues/2400}
227
+ *
228
+ * - A SharedMap in local state set a key.
229
+ *
230
+ * - A second SharedMap is then created from the snapshot of the first one.
231
+ *
232
+ * - The second SharedMap sets a new value to the same key.
233
+ *
234
+ * - The expected behavior is that the first SharedMap updates the key with the new value. But in the bug
235
+ * the first SharedMap stores the key in its pending state even though it does not send out an op. So,
236
+ * when it gets a remote op with the same key, it ignores it as it has a pending set with the same key.
237
+ */
238
+ it("should correctly process a set operation sent in local state", async () => {
239
+ const dataStoreRuntime1 = new MockFluidDataStoreRuntime();
240
+ const map1 = new SharedMap("testMap1", dataStoreRuntime1, MapFactory.Attributes);
241
+ // Set a key in local state.
242
+ const key = "testKey";
243
+ const value = "testValue";
244
+ map1.set(key, value);
245
+ // Load a new SharedMap in connected state from the snapshot of the first one.
246
+ const containerRuntimeFactory = new MockContainerRuntimeFactory();
247
+ const dataStoreRuntime2 = new MockFluidDataStoreRuntime();
248
+ const containerRuntime2 = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime2);
249
+ const services2 = MockSharedObjectServices.createFromSummary(map1.getAttachSummary().summary);
250
+ services2.deltaConnection = dataStoreRuntime2.createDeltaConnection();
251
+ const map2 = new SharedMap("testMap2", dataStoreRuntime2, MapFactory.Attributes);
252
+ await map2.load(services2);
253
+ // Now connect the first SharedMap
254
+ dataStoreRuntime1.setAttachState(AttachState.Attached);
255
+ const containerRuntime1 = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime1);
256
+ const services1 = {
257
+ deltaConnection: dataStoreRuntime1.createDeltaConnection(),
258
+ objectStorage: new MockStorage(undefined),
259
+ };
260
+ map1.connect(services1);
261
+ // Verify that both the maps have the key.
262
+ assert.equal(map1.get(key), value, "The first map does not have the key");
263
+ assert.equal(map2.get(key), value, "The second map does not have the key");
264
+ // Set a new value for the same key in the second SharedMap.
265
+ const newValue = "newValue";
266
+ map2.set(key, newValue);
267
+ // Process the message.
268
+ containerRuntimeFactory.processAllMessages();
269
+ // Verify that both the maps have the new value.
270
+ assert.equal(map1.get(key), newValue, "The first map did not get the new value");
271
+ assert.equal(map2.get(key), newValue, "The second map did not get the new value");
272
+ });
273
+ it("metadata op", async () => {
274
+ const serializable = { type: "Plain", value: "value" };
275
+ const dataStoreRuntime1 = new MockFluidDataStoreRuntime();
276
+ const op = { type: "set", key: "key", value: serializable };
277
+ const map1 = new TestSharedMap("testMap1", dataStoreRuntime1, MapFactory.Attributes);
278
+ const containerRuntimeFactory = new MockContainerRuntimeFactory();
279
+ containerRuntimeFactory.createContainerRuntime(dataStoreRuntime1);
280
+ map1.connect({
281
+ deltaConnection: dataStoreRuntime1.createDeltaConnection(),
282
+ objectStorage: new MockStorage(undefined),
283
+ });
284
+ let metadata = map1.testApplyStashedOp(op);
285
+ assert.equal(metadata?.type, "add");
286
+ assert.equal(metadata.pendingMessageId, 0);
287
+ const editMetadata = map1.testApplyStashedOp(op);
288
+ assert.equal(editMetadata.type, "edit");
289
+ assert.equal(editMetadata.pendingMessageId, 1);
290
+ assert.equal(editMetadata.previousValue.value, "value");
291
+ const serializable2 = { type: "Plain", value: "value2" };
292
+ const op2 = { type: "set", key: "key2", value: serializable2 };
293
+ metadata = map1.testApplyStashedOp(op2);
294
+ assert.equal(metadata?.type, "add");
295
+ assert.equal(metadata.pendingMessageId, 2);
296
+ const op3 = { type: "delete", key: "key2" };
297
+ metadata = map1.testApplyStashedOp(op3);
298
+ assert.equal(metadata.type, "edit");
299
+ assert.equal(metadata.pendingMessageId, 3);
300
+ assert.equal(metadata.previousValue.value, "value2");
301
+ const op4 = { type: "clear" };
302
+ metadata = map1.testApplyStashedOp(op4);
303
+ assert.equal(metadata.pendingMessageId, 4);
304
+ assert.equal(metadata.type, "clear");
305
+ assert.equal(metadata.previousMap?.get("key")?.value, "value");
306
+ assert.equal(metadata.previousMap?.has("key2"), false);
307
+ });
308
+ });
309
+ });
310
+ describe("Connected state", () => {
311
+ let containerRuntimeFactory;
312
+ let map1;
313
+ let map2;
314
+ beforeEach("createConnectedMaps", async () => {
315
+ containerRuntimeFactory = new MockContainerRuntimeFactory();
316
+ // Create the first map
317
+ map1 = createConnectedMap("map1", containerRuntimeFactory);
318
+ // Create and connect a second map
319
+ map2 = createConnectedMap("map2", containerRuntimeFactory);
320
+ });
321
+ describe("API", () => {
322
+ describe(".get()", () => {
323
+ it("Should be able to retrieve a key", () => {
324
+ const value = "value";
325
+ map1.set("test", value);
326
+ containerRuntimeFactory.processAllMessages();
327
+ // Verify the local SharedMap
328
+ assert.equal(map1.get("test"), value, "could not retrieve key");
329
+ // Verify the remote SharedMap
330
+ assert.equal(map2.get("test"), value, "could not retrieve key from the remote map");
331
+ });
332
+ });
333
+ describe(".has()", () => {
334
+ it("Should return false when a key is not in the map", () => {
335
+ assert.equal(map1.has("notInSet"), false, "has() did not return false for missing key");
336
+ });
337
+ it("Should return true when a key is in the map", () => {
338
+ map1.set("inSet", "value");
339
+ containerRuntimeFactory.processAllMessages();
340
+ // Verify the local SharedMap
341
+ assert.equal(map1.has("inSet"), true, "could not find the key");
342
+ // Verify the remote SharedMap
343
+ assert.equal(map2.has("inSet"), true, "could not find the key in the remote map");
344
+ });
345
+ });
346
+ describe(".set()", () => {
347
+ it("Should set a key to a value", () => {
348
+ const value = "value";
349
+ map1.set("test", value);
350
+ containerRuntimeFactory.processAllMessages();
351
+ // Verify the local SharedMap
352
+ assert.equal(map1.has("test"), true, "could not find the set key");
353
+ assert.equal(map1.get("test"), value, "could not get the set key");
354
+ // Verify the remote SharedMap
355
+ assert.equal(map2.has("test"), true, "could not find the set key in remote map");
356
+ assert.equal(map2.get("test"), value, "could not get the set key from remote map");
357
+ });
358
+ it("Should be able to set a shared object handle as a key", () => {
359
+ const subMap = createLocalMap("subMap");
360
+ map1.set("test", subMap.handle);
361
+ containerRuntimeFactory.processAllMessages();
362
+ // Verify the local SharedMap
363
+ const localSubMap = map1.get("test");
364
+ assert(localSubMap);
365
+ assert.equal(localSubMap.absolutePath, subMap.handle.absolutePath, "could not get the handle's path");
366
+ // Verify the remote SharedMap
367
+ const remoteSubMap = map2.get("test");
368
+ assert(remoteSubMap);
369
+ assert.equal(remoteSubMap.absolutePath, subMap.handle.absolutePath, "could not get the handle's path in remote map");
370
+ });
371
+ it("Should be able to set and retrieve a plain object with nested handles", async () => {
372
+ const subMap = createLocalMap("subMap");
373
+ const subMap2 = createLocalMap("subMap2");
374
+ const containingObject = {
375
+ subMapHandle: subMap.handle,
376
+ nestedObj: {
377
+ subMap2Handle: subMap2.handle,
378
+ },
379
+ };
380
+ map1.set("object", containingObject);
381
+ containerRuntimeFactory.processAllMessages();
382
+ const retrieved = map1.get("object");
383
+ const retrievedSubMap = await retrieved.subMapHandle.get();
384
+ assert.equal(retrievedSubMap, subMap, "could not get nested map 1");
385
+ const retrievedSubMap2 = await retrieved.nestedObj.subMap2Handle.get();
386
+ assert.equal(retrievedSubMap2, subMap2, "could not get nested map 2");
387
+ });
388
+ it("Shouldn't clear value remotely if there is pending set", () => {
389
+ const valuesChanged = [];
390
+ let clearCount = 0;
391
+ map1.on("valueChanged", (changed, local, target) => {
392
+ valuesChanged.push(changed);
393
+ });
394
+ map1.on("clear", (local, target) => {
395
+ clearCount++;
396
+ });
397
+ map2.set("map2key", "value2");
398
+ map2.clear();
399
+ map1.set("map1Key", "value1");
400
+ map2.clear();
401
+ containerRuntimeFactory.processSomeMessages(2);
402
+ assert.equal(valuesChanged.length, 3);
403
+ assert.equal(valuesChanged[0].key, "map1Key");
404
+ assert.equal(valuesChanged[0].previousValue, undefined);
405
+ assert.equal(valuesChanged[1].key, "map2key");
406
+ assert.equal(valuesChanged[1].previousValue, undefined);
407
+ assert.equal(valuesChanged[2].key, "map1Key");
408
+ assert.equal(valuesChanged[2].previousValue, undefined);
409
+ assert.equal(clearCount, 1);
410
+ assert.equal(map1.size, 1);
411
+ assert.equal(map1.get("map1Key"), "value1");
412
+ containerRuntimeFactory.processSomeMessages(2);
413
+ assert.equal(valuesChanged.length, 3);
414
+ assert.equal(clearCount, 2);
415
+ assert.equal(map1.size, 0);
416
+ });
417
+ it("Shouldn't keep the old pending set after a local clear", () => {
418
+ map1.set("1", 1);
419
+ map1.set("2", 2);
420
+ map1.set("3", 3);
421
+ map1.clear();
422
+ map1.set("1", 2);
423
+ containerRuntimeFactory.processAllMessages();
424
+ assert.equal(map1.get("1"), 2);
425
+ assert.equal(map1.get("2"), undefined);
426
+ assert.equal(map1.get("3"), undefined);
427
+ assert.equal(map2.get("1"), 2);
428
+ assert.equal(map2.get("2"), undefined);
429
+ assert.equal(map2.get("3"), undefined);
430
+ });
431
+ it("Shouldn't overwrite value if there is pending set", () => {
432
+ const value1 = "value1";
433
+ const pending1 = "pending1";
434
+ const pending2 = "pending2";
435
+ map1.set("test", value1);
436
+ map2.set("test", pending1);
437
+ map2.set("test", pending2);
438
+ containerRuntimeFactory.processSomeMessages(1);
439
+ // Verify the SharedMap with processed message
440
+ assert.equal(map1.has("test"), true, "could not find the set key");
441
+ assert.equal(map1.get("test"), value1, "could not get the set key");
442
+ // Verify the SharedMap with 2 pending messages
443
+ assert.equal(map2.has("test"), true, "could not find the set key in pending map");
444
+ assert.equal(map2.get("test"), pending2, "could not get the set key from pending map");
445
+ containerRuntimeFactory.processSomeMessages(1);
446
+ // Verify the SharedMap gets updated from remote
447
+ assert.equal(map1.has("test"), true, "could not find the set key");
448
+ assert.equal(map1.get("test"), pending1, "could not get the set key");
449
+ // Verify the SharedMap with 1 pending message
450
+ assert.equal(map2.has("test"), true, "could not find the set key in pending map");
451
+ assert.equal(map2.get("test"), pending2, "could not get the set key from pending map");
452
+ });
453
+ it("Shouldn't set values when pending clear", () => {
454
+ const key = "test";
455
+ map1.set(key, "map1value1");
456
+ map2.set(key, "map2value2");
457
+ map2.clear();
458
+ map2.set(key, "map2value3");
459
+ map2.clear();
460
+ // map1.set(key, "map1value1");
461
+ containerRuntimeFactory.processSomeMessages(1);
462
+ // Verify the SharedMap with processed message
463
+ assert.equal(map1.has("test"), true, "could not find the set key");
464
+ assert.equal(map1.get("test"), "map1value1", "could not get the set key");
465
+ // Verify the SharedMap with 2 pending clears
466
+ assert.equal(map2.has("test"), false, "found the set key in pending map");
467
+ // map2.set(key, "map2value2");
468
+ containerRuntimeFactory.processSomeMessages(1);
469
+ // Verify the SharedMap gets updated from remote
470
+ assert.equal(map1.has("test"), true, "could not find the set key");
471
+ assert.equal(map1.get("test"), "map2value2", "could not get the set key");
472
+ // Verify the SharedMap with 2 pending clears
473
+ assert.equal(map2.has("test"), false, "found the set key in pending map");
474
+ // map2.clear();
475
+ containerRuntimeFactory.processSomeMessages(1);
476
+ // Verify the SharedMap gets updated from remote clear
477
+ assert.equal(map1.has("test"), false, "found the set key");
478
+ // Verify the SharedMap with 1 pending clear
479
+ assert.equal(map2.has("test"), false, "found the set key in pending map");
480
+ // map2.set(key, "map2value3");
481
+ containerRuntimeFactory.processSomeMessages(1);
482
+ // Verify the SharedMap gets updated from remote
483
+ assert.equal(map1.has("test"), true, "could not find the set key");
484
+ assert.equal(map1.get("test"), "map2value3", "could not get the set key");
485
+ // Verify the SharedMap with 1 pending clear
486
+ assert.equal(map2.has("test"), false, "found the set key in pending map");
487
+ // map2.clear();
488
+ containerRuntimeFactory.processSomeMessages(1);
489
+ // Verify the SharedMap gets updated from remote clear
490
+ assert.equal(map1.has("test"), false, "found the set key");
491
+ // Verify the SharedMap with no more pending clear
492
+ assert.equal(map2.has("test"), false, "found the set key in pending map");
493
+ map1.set(key, "map1value4");
494
+ containerRuntimeFactory.processSomeMessages(1);
495
+ // Verify the SharedMap gets updated from local
496
+ assert.equal(map1.has("test"), true, "could not find the set key");
497
+ assert.equal(map1.get("test"), "map1value4", "could not get the set key");
498
+ // Verify the SharedMap gets updated from remote
499
+ assert.equal(map1.has("test"), true, "could not find the set key");
500
+ assert.equal(map1.get("test"), "map1value4", "could not get the set key");
501
+ });
502
+ });
503
+ describe(".delete()", () => {
504
+ it("Can set and delete map key", async () => {
505
+ map1.set("testKey", "testValue");
506
+ map1.set("testKey2", "testValue2");
507
+ map1.delete("testKey");
508
+ map1.delete("testKey2");
509
+ assert.equal(map1.has("testKey"), false, "could not delete key 1");
510
+ assert.equal(map1.has("testKey2"), false, "could not delete key 2");
511
+ map1.set("testKey", "testValue");
512
+ map1.set("testKey2", "testValue2");
513
+ assert.equal(map1.get("testKey"), "testValue", "could not retrieve set key 1 after delete");
514
+ assert.equal(map1.get("testKey2"), "testValue2", "could not retrieve set key 2 after delete");
515
+ });
516
+ /**
517
+ * It is an unusual scenario, the client of map1 executes an invalid delete (since "foo" does not exist in its keys),
518
+ * but it can remotely delete the "foo" which is locally inserted in map2 but not ack'd yet.
519
+ *
520
+ * This merge outcome might be undesirable: this test case is mostly here to document Map's behavior.
521
+ * Please communicate any concerns about the merge outcome to the DDS team.
522
+ */
523
+ it("Can remotely delete a key which should be unknown to the local client", () => {
524
+ map1.set("foo", 1);
525
+ containerRuntimeFactory.processAllMessages();
526
+ map1.delete("foo");
527
+ map2.set("foo", 2);
528
+ map1.delete("foo");
529
+ containerRuntimeFactory.processAllMessages();
530
+ assert.equal(map1.get("foo"), undefined);
531
+ assert.equal(map2.get("foo"), undefined);
532
+ });
533
+ });
534
+ describe(".forEach()", () => {
535
+ it("Should iterate over all keys in the map", () => {
536
+ // We use a set to mark the values we want to insert. When we iterate we will remove from the set
537
+ // and then check it's empty at the end
538
+ const set = new Set();
539
+ set.add("first");
540
+ set.add("second");
541
+ set.add("third");
542
+ for (const value of set) {
543
+ map1.set(value, value);
544
+ }
545
+ containerRuntimeFactory.processAllMessages();
546
+ // Verify the local SharedMap
547
+ for (const [key, value] of map1.entries()) {
548
+ assert.ok(set.has(key), "the key should be present in the set");
549
+ assert.equal(key, value, "the value should match the set value");
550
+ assert.equal(map1.get(key), value, "could not get key");
551
+ }
552
+ // Verify the remote SharedMap
553
+ for (const [key, value] of map2.entries()) {
554
+ assert.ok(set.has(key), "the key in remote map should be present in the set");
555
+ assert.equal(key, value, "the value should match the set value in the remote map");
556
+ assert.equal(map2.get(key), value, "could not get key in the remote map");
557
+ set.delete(key);
558
+ }
559
+ assert.equal(set.size, 0);
560
+ });
561
+ });
562
+ describe(".size", () => {
563
+ it("shouldn't count keys deleted concurrent to a clear op", () => {
564
+ map1.clear();
565
+ map2.delete("dummy");
566
+ containerRuntimeFactory.processAllMessages();
567
+ assert.equal(map1.size, 0);
568
+ assert.equal(map2.size, 0);
569
+ });
570
+ it("should count the key with undefined value concurrent to a clear op", () => {
571
+ map1.clear();
572
+ map2.set("1", undefined);
573
+ containerRuntimeFactory.processSomeMessages(1);
574
+ assert.equal(map1.size, 0);
575
+ assert.equal(map2.size, 1);
576
+ containerRuntimeFactory.processSomeMessages(1);
577
+ assert.equal(map1.size, 1);
578
+ assert.equal(map2.size, 1);
579
+ });
580
+ it("should count keys correctly after local operations", () => {
581
+ map1.set("1", 1);
582
+ map1.set("2", 1);
583
+ map2.set("3", 1);
584
+ assert.equal(map1.size, 2);
585
+ assert.equal(map2.size, 1);
586
+ map1.set("2", 2);
587
+ map1.delete("1");
588
+ map2.set("2", 1);
589
+ assert.equal(map1.size, 1);
590
+ assert.equal(map2.size, 2);
591
+ map1.delete("1");
592
+ map2.clear();
593
+ assert.equal(map1.size, 1);
594
+ assert.equal(map2.size, 0);
595
+ });
596
+ it("should count keys correctly after remote operations", () => {
597
+ map1.set("1", 1);
598
+ map1.set("2", 1);
599
+ map2.set("3", 1);
600
+ containerRuntimeFactory.processSomeMessages(2);
601
+ assert.equal(map1.size, 2);
602
+ assert.equal(map2.size, 3);
603
+ containerRuntimeFactory.processSomeMessages(1);
604
+ assert.equal(map1.size, 3);
605
+ assert.equal(map2.size, 3);
606
+ map1.delete("3");
607
+ map2.clear();
608
+ containerRuntimeFactory.processSomeMessages(1);
609
+ assert.equal(map1.size, 2);
610
+ assert.equal(map2.size, 0);
611
+ containerRuntimeFactory.processSomeMessages(1);
612
+ assert.equal(map1.size, 0);
613
+ assert.equal(map2.size, 0);
614
+ });
615
+ });
616
+ });
617
+ });
618
+ describe("Garbage Collection", () => {
619
+ class GCSharedMapProvider {
620
+ constructor() {
621
+ this.subMapCount = 0;
622
+ this._expectedRoutes = [];
623
+ this.containerRuntimeFactory = new MockContainerRuntimeFactory();
624
+ this.map1 = createConnectedMap("map1", this.containerRuntimeFactory);
625
+ this.map2 = createConnectedMap("map2", this.containerRuntimeFactory);
626
+ }
627
+ /**
628
+ * {@inheritDoc @fluid-private/test-dds-utils#IGCTestProvider.sharedObject}
629
+ */
630
+ get sharedObject() {
631
+ // Return the remote SharedMap because we want to verify its summary data.
632
+ return this.map2;
633
+ }
634
+ /**
635
+ * {@inheritDoc @fluid-private/test-dds-utils#IGCTestProvider.expectedOutboundRoutes}
636
+ */
637
+ get expectedOutboundRoutes() {
638
+ return this._expectedRoutes;
639
+ }
640
+ /**
641
+ * {@inheritDoc @fluid-private/test-dds-utils#IGCTestProvider.addOutboundRoutes}
642
+ */
643
+ async addOutboundRoutes() {
644
+ const newSubMapId = `subMap-${++this.subMapCount}`;
645
+ const subMap = createLocalMap(newSubMapId);
646
+ this.map1.set(newSubMapId, subMap.handle);
647
+ this._expectedRoutes.push(subMap.handle.absolutePath);
648
+ this.containerRuntimeFactory.processAllMessages();
649
+ }
650
+ /**
651
+ * {@inheritDoc @fluid-private/test-dds-utils#IGCTestProvider.deleteOutboundRoutes}
652
+ */
653
+ async deleteOutboundRoutes() {
654
+ // Delete the last handle that was added.
655
+ const subMapId = `subMap-${this.subMapCount}`;
656
+ const deletedHandle = this.map1.get(subMapId);
657
+ assert(deletedHandle, "Route must be added before deleting");
658
+ this.map1.delete(subMapId);
659
+ // Remove deleted handle's route from expected routes.
660
+ this._expectedRoutes = this._expectedRoutes.filter((route) => route !== deletedHandle.absolutePath);
661
+ this.containerRuntimeFactory.processAllMessages();
662
+ }
663
+ /**
664
+ * {@inheritDoc @fluid-private/test-dds-utils#IGCTestProvider.addNestedHandles}
665
+ */
666
+ async addNestedHandles() {
667
+ const subMapId1 = `subMap-${++this.subMapCount}`;
668
+ const subMapId2 = `subMap-${++this.subMapCount}`;
669
+ const subMap = createLocalMap(subMapId1);
670
+ const subMap2 = createLocalMap(subMapId2);
671
+ const containingObject = {
672
+ subMapHandle: subMap.handle,
673
+ nestedObj: {
674
+ subMap2Handle: subMap2.handle,
675
+ },
676
+ };
677
+ this.map1.set(subMapId2, containingObject);
678
+ this._expectedRoutes.push(subMap.handle.absolutePath, subMap2.handle.absolutePath);
679
+ this.containerRuntimeFactory.processAllMessages();
680
+ }
681
+ }
682
+ runGCTests(GCSharedMapProvider);
683
+ });
684
+ });
685
+ //# sourceMappingURL=map.spec.js.map