1namespace ts { 2 describe("unittests:: createMapShim", () => { 3 4 const stringKeys = [ 5 "1", 6 "3", 7 "2", 8 "4", 9 "0", 10 "999", 11 "A", 12 "B", 13 "C", 14 "Z", 15 "X", 16 "X1", 17 "X2", 18 "Y" 19 ]; 20 21 const mixedKeys = [ 22 true, 23 3, 24 { toString() { return "2"; } }, 25 "4", 26 false, 27 null, // eslint-disable-line no-null/no-null 28 undefined, 29 "B", 30 { toString() { return "C"; } }, 31 "Z", 32 "X", 33 { toString() { return "X1"; } }, 34 "X2", 35 "Y" 36 ]; 37 38 function testMapIterationAddedValues<K>(keys: K[], map: ESMap<K, string>, useForEach: boolean): string { 39 let resultString = ""; 40 41 map.set(keys[0], "1"); 42 map.set(keys[1], "3"); 43 map.set(keys[2], "2"); 44 map.set(keys[3], "4"); 45 46 let addedThree = false; 47 const doForEach = (value: string, key: K) => { 48 resultString += `${key}:${value};`; 49 50 // Add a new key ("0") - the map should provide this 51 // one in the next iteration. 52 if (key === keys[0]) { 53 map.set(keys[0], "X1"); 54 map.set(keys[4], "X0"); 55 map.set(keys[3], "X4"); 56 } 57 else if (key === keys[1]) { 58 if (!addedThree) { 59 addedThree = true; 60 61 // Remove and re-add key "3"; the map should 62 // visit it after "0". 63 map.delete(keys[1]); 64 map.set(keys[1], "Y3"); 65 66 // Change the value of "2"; the map should provide 67 // it when visiting the key. 68 map.set(keys[2], "Y2"); 69 } 70 else { 71 // Check that an entry added when we visit the 72 // currently last entry will still be visited. 73 map.set(keys[5], "999"); 74 } 75 } 76 else if (key === keys[5]) { 77 // Ensure that clear() behaves correctly same as removing all keys. 78 map.set(keys[6], "A"); 79 map.set(keys[7], "B"); 80 map.set(keys[8], "C"); 81 } 82 else if (key === keys[6]) { 83 map.clear(); 84 map.set(keys[9], "Z"); 85 } 86 else if (key === keys[9]) { 87 // Check that the map behaves correctly when two items are 88 // added and removed immediately. 89 map.set(keys[10], "X"); 90 map.set(keys[11], "X1"); 91 map.set(keys[12], "X2"); 92 map.delete(keys[11]); 93 map.delete(keys[12]); 94 map.set(keys[13], "Y"); 95 } 96 }; 97 98 if (useForEach) { 99 map.forEach(doForEach); 100 } 101 else { 102 // Use an iterator. 103 const iterator = map.entries(); 104 while (true) { 105 const iterResult = iterator.next(); 106 if (iterResult.done) { 107 break; 108 } 109 110 const [key, value] = iterResult.value; 111 doForEach(value, key); 112 } 113 } 114 115 return resultString; 116 } 117 118 let MapShim!: MapConstructor; 119 beforeEach(() => { 120 function getIterator<I extends readonly any[] | ReadonlySet<any> | ReadonlyESMap<any, any> | undefined>(iterable: I): Iterator< 121 I extends ReadonlyESMap<infer K, infer V> ? [K, V] : 122 I extends ReadonlySet<infer T> ? T : 123 I extends readonly (infer T)[] ? T : 124 I extends undefined ? undefined : 125 never>; 126 function getIterator(iterable: readonly any[] | ReadonlySet<any> | ReadonlyESMap<any, any> | undefined): Iterator<any> | undefined { 127 // override `ts.getIterator` with a version that allows us to iterate over a `MapShim` in an environment with a native `Map`. 128 if (iterable instanceof MapShim) return iterable.entries(); 129 return ts.getIterator(iterable); 130 } 131 132 MapShim = ShimCollections.createMapShim(getIterator); 133 afterEach(() => { 134 MapShim = undefined!; 135 }); 136 }); 137 138 it("iterates values in insertion order and handles changes with string keys", () => { 139 const expectedResult = "1:1;3:3;2:Y2;4:X4;0:X0;3:Y3;999:999;A:A;Z:Z;X:X;Y:Y;"; 140 141 // First, ensure the test actually has the same behavior as a native Map. 142 let nativeMap = new Map<string, string>(); 143 const nativeMapForEachResult = testMapIterationAddedValues(stringKeys, nativeMap, /* useForEach */ true); 144 assert.equal(nativeMapForEachResult, expectedResult, "nativeMap-forEach"); 145 146 nativeMap = new Map<string, string>(); 147 const nativeMapIteratorResult = testMapIterationAddedValues(stringKeys, nativeMap, /* useForEach */ false); 148 assert.equal(nativeMapIteratorResult, expectedResult, "nativeMap-iterator"); 149 150 // Then, test the map shim. 151 let localShimMap = new MapShim<string, string>(); 152 const shimMapForEachResult = testMapIterationAddedValues(stringKeys, localShimMap, /* useForEach */ true); 153 assert.equal(shimMapForEachResult, expectedResult, "shimMap-forEach"); 154 155 localShimMap = new MapShim<string, string>(); 156 const shimMapIteratorResult = testMapIterationAddedValues(stringKeys, localShimMap, /* useForEach */ false); 157 assert.equal(shimMapIteratorResult, expectedResult, "shimMap-iterator"); 158 }); 159 160 it("iterates values in insertion order and handles changes with mixed-type keys", () => { 161 const expectedResult = "true:1;3:3;2:Y2;4:X4;false:X0;3:Y3;null:999;undefined:A;Z:Z;X:X;Y:Y;"; 162 163 // First, ensure the test actually has the same behavior as a native Map. 164 let nativeMap = new Map<any, string>(); 165 const nativeMapForEachResult = testMapIterationAddedValues(mixedKeys, nativeMap, /* useForEach */ true); 166 assert.equal(nativeMapForEachResult, expectedResult, "nativeMap-forEach"); 167 168 nativeMap = new Map<any, string>(); 169 const nativeMapIteratorResult = testMapIterationAddedValues(mixedKeys, nativeMap, /* useForEach */ false); 170 assert.equal(nativeMapIteratorResult, expectedResult, "nativeMap-iterator"); 171 172 // Then, test the map shim. 173 let localShimMap = new MapShim<any, string>(); 174 const shimMapForEachResult = testMapIterationAddedValues(mixedKeys, localShimMap, /* useForEach */ true); 175 assert.equal(shimMapForEachResult, expectedResult, "shimMap-forEach"); 176 177 localShimMap = new MapShim<any, string>(); 178 const shimMapIteratorResult = testMapIterationAddedValues(mixedKeys, localShimMap, /* useForEach */ false); 179 assert.equal(shimMapIteratorResult, expectedResult, "shimMap-iterator"); 180 }); 181 182 it("create from Array", () => { 183 const map = new MapShim([["a", "b"]]); 184 assert.equal(map.size, 1); 185 assert.isTrue(map.has("a")); 186 assert.equal(map.get("a"), "b"); 187 }); 188 189 it("create from Map", () => { 190 const map1 = new MapShim([["a", "b"]]); 191 const map2 = new MapShim(map1); 192 assert.equal(map1.size, 1); 193 assert.equal(map2.size, 1); 194 assert.isTrue(map2.has("a")); 195 assert.equal(map2.get("a"), "b"); 196 }); 197 198 it("set when not present", () => { 199 const map = new MapShim<string, string>(); 200 const result = map.set("a", "b"); 201 assert.equal(map.size, 1); 202 assert.strictEqual(result, map); 203 assert.isTrue(map.has("a")); 204 assert.equal(map.get("a"), "b"); 205 }); 206 207 it("set when present", () => { 208 const map = new MapShim<string, string>(); 209 map.set("a", "z"); 210 const result = map.set("a", "b"); 211 assert.equal(map.size, 1); 212 assert.strictEqual(result, map); 213 assert.isTrue(map.has("a")); 214 assert.equal(map.get("a"), "b"); 215 }); 216 217 it("has when not present", () => { 218 const map = new MapShim<string, string>(); 219 assert.isFalse(map.has("a")); 220 }); 221 222 it("has when present", () => { 223 const map = new MapShim<string, string>(); 224 map.set("a", "b"); 225 assert.isTrue(map.has("a")); 226 }); 227 228 it("get when not present", () => { 229 const map = new MapShim<string, string>(); 230 assert.isUndefined(map.get("a")); 231 }); 232 233 it("get when present", () => { 234 const map = new MapShim<string, string>(); 235 map.set("a", "b"); 236 assert.equal(map.get("a"), "b"); 237 }); 238 239 it("delete when not present", () => { 240 const map = new MapShim<string, string>(); 241 assert.isFalse(map.delete("a")); 242 }); 243 244 it("delete when present", () => { 245 const map = new MapShim<string, string>(); 246 map.set("a", "b"); 247 assert.isTrue(map.delete("a")); 248 }); 249 250 it("delete twice when present", () => { 251 const map = new MapShim<string, string>(); 252 map.set("a", "b"); 253 assert.isTrue(map.delete("a")); 254 assert.isFalse(map.delete("a")); 255 }); 256 257 it("remove only item and iterate", () => { 258 const map = new MapShim<string, string>(); 259 map.set("a", "b"); 260 map.delete("a"); 261 const actual = arrayFrom(map.keys()); 262 assert.deepEqual(actual, []); 263 }); 264 265 it("remove first item and iterate", () => { 266 const map = new MapShim<string, string>(); 267 map.set("a", "b"); 268 map.set("c", "d"); 269 map.delete("a"); 270 assert.deepEqual(arrayFrom(map.keys()), ["c"]); 271 assert.deepEqual(arrayFrom(map.values()), ["d"]); 272 assert.deepEqual(arrayFrom(map.entries()), [["c", "d"]]); 273 }); 274 275 it("remove last item and iterate", () => { 276 const map = new MapShim<string, string>(); 277 map.set("a", "b"); 278 map.set("c", "d"); 279 map.delete("c"); 280 assert.deepEqual(arrayFrom(map.keys()), ["a"]); 281 assert.deepEqual(arrayFrom(map.values()), ["b"]); 282 assert.deepEqual(arrayFrom(map.entries()), [["a", "b"]]); 283 }); 284 285 it("remove middle item and iterate", () => { 286 const map = new MapShim<string, string>(); 287 map.set("a", "b"); 288 map.set("c", "d"); 289 map.set("e", "f"); 290 map.delete("c"); 291 assert.deepEqual(arrayFrom(map.keys()), ["a", "e"]); 292 assert.deepEqual(arrayFrom(map.values()), ["b", "f"]); 293 assert.deepEqual(arrayFrom(map.entries()), [["a", "b"], ["e", "f"]]); 294 }); 295 296 it("keys", () => { 297 const map = new MapShim<string, string>(); 298 map.set("c", "d"); 299 map.set("a", "b"); 300 assert.deepEqual(arrayFrom(map.keys()), ["c", "a"]); 301 }); 302 303 it("values", () => { 304 const map = new MapShim<string, string>(); 305 map.set("c", "d"); 306 map.set("a", "b"); 307 assert.deepEqual(arrayFrom(map.values()), ["d", "b"]); 308 }); 309 310 it("entries", () => { 311 const map = new MapShim<string, string>(); 312 map.set("c", "d"); 313 map.set("a", "b"); 314 assert.deepEqual(arrayFrom(map.entries()), [["c", "d"], ["a", "b"]]); 315 }); 316 317 it("forEach", () => { 318 const map = new MapShim<string, string>(); 319 map.set("c", "d"); 320 map.set("a", "b"); 321 const actual: [string, string][] = []; 322 map.forEach((value, key) => { actual.push([key, value]); }); 323 assert.deepEqual(actual, [["c", "d"], ["a", "b"]]); 324 }); 325 }); 326} 327