Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/bench/other/LuauPolyfillMap.lua
2725 views
1
-- This file is part of the Roblox luau-polyfill repository and is licensed under MIT License; see LICENSE.txt for details
2
-- #region Array
3
-- Array related
4
local Array = {}
5
local Object = {}
6
local Map = {}
7
8
type Array<T> = { [number]: T }
9
type callbackFn<K, V> = (element: V, key: K, map: Map<K, V>) -> ()
10
type callbackFnWithThisArg<K, V> = (thisArg: Object, value: V, key: K, map: Map<K, V>) -> ()
11
type Map<K, V> = {
12
size: number,
13
-- method definitions
14
set: (self: Map<K, V>, K, V) -> Map<K, V>,
15
get: (self: Map<K, V>, K) -> V | nil,
16
clear: (self: Map<K, V>) -> (),
17
delete: (self: Map<K, V>, K) -> boolean,
18
forEach: (self: Map<K, V>, callback: callbackFn<K, V> | callbackFnWithThisArg<K, V>, thisArg: Object?) -> (),
19
has: (self: Map<K, V>, K) -> boolean,
20
keys: (self: Map<K, V>) -> Array<K>,
21
values: (self: Map<K, V>) -> Array<V>,
22
entries: (self: Map<K, V>) -> Array<Tuple<K, V>>,
23
ipairs: (self: Map<K, V>) -> any,
24
[K]: V,
25
_map: { [K]: V },
26
_array: { [number]: K },
27
}
28
type mapFn<T, U> = (element: T, index: number) -> U
29
type mapFnWithThisArg<T, U> = (thisArg: any, element: T, index: number) -> U
30
type Object = { [string]: any }
31
type Table<T, V> = { [T]: V }
32
type Tuple<T, V> = Array<T | V>
33
34
local Set = {}
35
36
-- #region Array
37
function Array.isArray(value: any): boolean
38
if typeof(value) ~= "table" then
39
return false
40
end
41
if next(value) == nil then
42
-- an empty table is an empty array
43
return true
44
end
45
46
local length = #value
47
48
if length == 0 then
49
return false
50
end
51
52
local count = 0
53
local sum = 0
54
for key in pairs(value) do
55
if typeof(key) ~= "number" then
56
return false
57
end
58
if key % 1 ~= 0 or key < 1 then
59
return false
60
end
61
count += 1
62
sum += key
63
end
64
65
return sum == (count * (count + 1) / 2)
66
end
67
68
function Array.from<T, U>(
69
value: string | Array<T> | Object,
70
mapFn: (mapFn<T, U> | mapFnWithThisArg<T, U>)?,
71
thisArg: Object?
72
): Array<U>
73
if value == nil then
74
error("cannot create array from a nil value")
75
end
76
local valueType = typeof(value)
77
78
local array = {}
79
80
if valueType == "table" and Array.isArray(value) then
81
if mapFn then
82
for i = 1, #(value :: Array<T>) do
83
if thisArg ~= nil then
84
array[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, (value :: Array<T>)[i], i)
85
else
86
array[i] = (mapFn :: mapFn<T, U>)((value :: Array<T>)[i], i)
87
end
88
end
89
else
90
for i = 1, #(value :: Array<T>) do
91
array[i] = (value :: Array<any>)[i]
92
end
93
end
94
elseif instanceOf(value, Set) then
95
if mapFn then
96
for i, v in (value :: any):ipairs() do
97
if thisArg ~= nil then
98
array[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, v, i)
99
else
100
array[i] = (mapFn :: mapFn<T, U>)(v, i)
101
end
102
end
103
else
104
for i, v in (value :: any):ipairs() do
105
array[i] = v
106
end
107
end
108
elseif instanceOf(value, Map) then
109
if mapFn then
110
for i, v in (value :: any):ipairs() do
111
if thisArg ~= nil then
112
array[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, v, i)
113
else
114
array[i] = (mapFn :: mapFn<T, U>)(v, i)
115
end
116
end
117
else
118
for i, v in (value :: any):ipairs() do
119
array[i] = v
120
end
121
end
122
elseif valueType == "string" then
123
if mapFn then
124
for i = 1, (value :: string):len() do
125
if thisArg ~= nil then
126
array[i] = (mapFn :: mapFnWithThisArg<T, U>)(thisArg, (value :: any):sub(i, i), i)
127
else
128
array[i] = (mapFn :: mapFn<T, U>)((value :: any):sub(i, i), i)
129
end
130
end
131
else
132
for i = 1, (value :: string):len() do
133
array[i] = (value :: any):sub(i, i)
134
end
135
end
136
end
137
138
return array
139
end
140
141
type callbackFnArrayMap<T, U> = (element: T, index: number, array: Array<T>) -> U
142
type callbackFnWithThisArgArrayMap<T, U, V> = (thisArg: V, element: T, index: number, array: Array<T>) -> U
143
144
-- Implements Javascript's `Array.prototype.map` as defined below
145
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
146
function Array.map<T, U, V>(
147
t: Array<T>,
148
callback: callbackFnArrayMap<T, U> | callbackFnWithThisArgArrayMap<T, U, V>,
149
thisArg: V?
150
): Array<U>
151
if typeof(t) ~= "table" then
152
error(string.format("Array.map called on %s", typeof(t)))
153
end
154
if typeof(callback) ~= "function" then
155
error("callback is not a function")
156
end
157
158
local len = #t
159
local A = {}
160
local k = 1
161
162
while k <= len do
163
local kValue = t[k]
164
165
if kValue ~= nil then
166
local mappedValue
167
168
if thisArg ~= nil then
169
mappedValue = (callback :: callbackFnWithThisArgArrayMap<T, U, V>)(thisArg, kValue, k, t)
170
else
171
mappedValue = (callback :: callbackFnArrayMap<T, U>)(kValue, k, t)
172
end
173
174
A[k] = mappedValue
175
end
176
k += 1
177
end
178
179
return A
180
end
181
182
type Function = (any, any, number, any) -> any
183
184
-- Implements Javascript's `Array.prototype.reduce` as defined below
185
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
186
function Array.reduce<T>(array: Array<T>, callback: Function, initialValue: any?): any
187
if typeof(array) ~= "table" then
188
error(string.format("Array.reduce called on %s", typeof(array)))
189
end
190
if typeof(callback) ~= "function" then
191
error("callback is not a function")
192
end
193
194
local length = #array
195
196
local value
197
local initial = 1
198
199
if initialValue ~= nil then
200
value = initialValue
201
else
202
initial = 2
203
if length == 0 then
204
error("reduce of empty array with no initial value")
205
end
206
value = array[1]
207
end
208
209
for i = initial, length do
210
value = callback(value, array[i], i, array)
211
end
212
213
return value
214
end
215
216
type callbackFnArrayForEach<T> = (element: T, index: number, array: Array<T>) -> ()
217
type callbackFnWithThisArgArrayForEach<T, U> = (thisArg: U, element: T, index: number, array: Array<T>) -> ()
218
219
-- Implements Javascript's `Array.prototype.forEach` as defined below
220
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
221
function Array.forEach<T, U>(
222
t: Array<T>,
223
callback: callbackFnArrayForEach<T> | callbackFnWithThisArgArrayForEach<T, U>,
224
thisArg: U?
225
): ()
226
if typeof(t) ~= "table" then
227
error(string.format("Array.forEach called on %s", typeof(t)))
228
end
229
if typeof(callback) ~= "function" then
230
error("callback is not a function")
231
end
232
233
local len = #t
234
local k = 1
235
236
while k <= len do
237
local kValue = t[k]
238
239
if thisArg ~= nil then
240
(callback :: callbackFnWithThisArgArrayForEach<T, U>)(thisArg, kValue, k, t)
241
else
242
(callback :: callbackFnArrayForEach<T>)(kValue, k, t)
243
end
244
245
if #t < len then
246
-- don't iterate on removed items, don't iterate more than original length
247
len = #t
248
end
249
k += 1
250
end
251
end
252
-- #endregion
253
254
-- #region Set
255
Set.__index = Set
256
257
type callbackFnSet<T> = (value: T, key: T, set: Set<T>) -> ()
258
type callbackFnWithThisArgSet<T> = (thisArg: Object, value: T, key: T, set: Set<T>) -> ()
259
260
export type Set<T> = {
261
size: number,
262
-- method definitions
263
add: (self: Set<T>, T) -> Set<T>,
264
clear: (self: Set<T>) -> (),
265
delete: (self: Set<T>, T) -> boolean,
266
forEach: (self: Set<T>, callback: callbackFnSet<T> | callbackFnWithThisArgSet<T>, thisArg: Object?) -> (),
267
has: (self: Set<T>, T) -> boolean,
268
ipairs: (self: Set<T>) -> any,
269
}
270
271
type Iterable = { ipairs: (any) -> any }
272
273
function Set.new<T>(iterable: Array<T> | Set<T> | Iterable | string | nil): Set<T>
274
local array = {}
275
local map = {}
276
if iterable ~= nil then
277
local arrayIterable: Array<any>
278
-- ROBLOX TODO: remove type casting from (iterable :: any).ipairs in next release
279
if typeof(iterable) == "table" then
280
if Array.isArray(iterable) then
281
arrayIterable = Array.from(iterable :: Array<any>)
282
elseif typeof((iterable :: Iterable).ipairs) == "function" then
283
-- handle in loop below
284
elseif _G.__DEV__ then
285
error("cannot create array from an object-like table")
286
end
287
elseif typeof(iterable) == "string" then
288
arrayIterable = Array.from(iterable :: string)
289
else
290
error(("cannot create array from value of type `%s`"):format(typeof(iterable)))
291
end
292
293
if arrayIterable then
294
for _, element in ipairs(arrayIterable) do
295
if not map[element] then
296
map[element] = true
297
table.insert(array, element)
298
end
299
end
300
elseif typeof(iterable) == "table" and typeof((iterable :: Iterable).ipairs) == "function" then
301
for _, element in (iterable :: Iterable):ipairs() do
302
if not map[element] then
303
map[element] = true
304
table.insert(array, element)
305
end
306
end
307
end
308
end
309
310
return (setmetatable({
311
size = #array,
312
_map = map,
313
_array = array,
314
}, Set) :: any) :: Set<T>
315
end
316
317
function Set:add(value)
318
if not self._map[value] then
319
-- Luau FIXME: analyze should know self is Set<T> which includes size as a number
320
self.size = self.size :: number + 1
321
self._map[value] = true
322
table.insert(self._array, value)
323
end
324
return self
325
end
326
327
function Set:clear()
328
self.size = 0
329
table.clear(self._map)
330
table.clear(self._array)
331
end
332
333
function Set:delete(value): boolean
334
if not self._map[value] then
335
return false
336
end
337
-- Luau FIXME: analyze should know self is Map<K, V> which includes size as a number
338
self.size = self.size :: number - 1
339
self._map[value] = nil
340
local index = table.find(self._array, value)
341
if index then
342
table.remove(self._array, index)
343
end
344
return true
345
end
346
347
-- Implements Javascript's `Map.prototype.forEach` as defined below
348
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/forEach
349
function Set:forEach<T>(callback: callbackFnSet<T> | callbackFnWithThisArgSet<T>, thisArg: Object?): ()
350
if typeof(callback) ~= "function" then
351
error("callback is not a function")
352
end
353
354
return Array.forEach(self._array, function(value: T)
355
if thisArg ~= nil then
356
(callback :: callbackFnWithThisArgSet<T>)(thisArg, value, value, self)
357
else
358
(callback :: callbackFnSet<T>)(value, value, self)
359
end
360
end)
361
end
362
363
function Set:has(value): boolean
364
return self._map[value] ~= nil
365
end
366
367
function Set:ipairs()
368
return ipairs(self._array)
369
end
370
371
-- #endregion Set
372
373
-- #region Object
374
function Object.entries(value: string | Object | Array<any>): Array<any>
375
assert(value :: any ~= nil, "cannot get entries from a nil value")
376
local valueType = typeof(value)
377
378
local entries: Array<Tuple<string, any>> = {}
379
if valueType == "table" then
380
for key, keyValue in pairs(value :: Object) do
381
-- Luau FIXME: Luau should see entries as Array<any>, given object is [string]: any, but it sees it as Array<Array<string>> despite all the manual annotation
382
table.insert(entries, { key :: string, keyValue :: any })
383
end
384
elseif valueType == "string" then
385
for i = 1, string.len(value :: string) do
386
entries[i] = { tostring(i), string.sub(value :: string, i, i) }
387
end
388
end
389
390
return entries
391
end
392
393
-- #endregion
394
395
-- #region instanceOf
396
397
-- ROBLOX note: Typed tbl as any to work with strict type analyze
398
-- polyfill for https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
399
function instanceOf(tbl: any, class)
400
assert(typeof(class) == "table", "Received a non-table as the second argument for instanceof")
401
402
if typeof(tbl) ~= "table" then
403
return false
404
end
405
406
local ok, hasNew = pcall(function()
407
return class.new ~= nil and tbl.new == class.new
408
end)
409
if ok and hasNew then
410
return true
411
end
412
413
local seen = { tbl = true }
414
415
while tbl and typeof(tbl) == "table" do
416
tbl = getmetatable(tbl)
417
if typeof(tbl) == "table" then
418
tbl = tbl.__index
419
420
if tbl == class then
421
return true
422
end
423
end
424
425
-- if we still have a valid table then check against seen
426
if typeof(tbl) == "table" then
427
if seen[tbl] then
428
return false
429
end
430
seen[tbl] = true
431
end
432
end
433
434
return false
435
end
436
-- #endregion
437
438
function Map.new<K, V>(iterable: Array<Array<any>>?): Map<K, V>
439
local array = {}
440
local map = {}
441
if iterable ~= nil then
442
local arrayFromIterable
443
local iterableType = typeof(iterable)
444
if iterableType == "table" then
445
if #iterable > 0 and typeof(iterable[1]) ~= "table" then
446
error("cannot create Map from {K, V} form, it must be { {K, V}... }")
447
end
448
449
arrayFromIterable = Array.from(iterable)
450
else
451
error(("cannot create array from value of type `%s`"):format(iterableType))
452
end
453
454
for _, entry in ipairs(arrayFromIterable) do
455
local key = entry[1]
456
if _G.__DEV__ then
457
if key == nil then
458
error("cannot create Map from a table that isn't an array.")
459
end
460
end
461
local val = entry[2]
462
-- only add to array if new
463
if map[key] == nil then
464
table.insert(array, key)
465
end
466
-- always assign
467
map[key] = val
468
end
469
end
470
471
return (setmetatable({
472
size = #array,
473
_map = map,
474
_array = array,
475
}, Map) :: any) :: Map<K, V>
476
end
477
478
function Map:set<K, V>(key: K, value: V): Map<K, V>
479
-- preserve initial insertion order
480
if self._map[key] == nil then
481
-- Luau FIXME: analyze should know self is Map<K, V> which includes size as a number
482
self.size = self.size :: number + 1
483
table.insert(self._array, key)
484
end
485
-- always update value
486
self._map[key] = value
487
return self
488
end
489
490
function Map:get(key)
491
return self._map[key]
492
end
493
494
function Map:clear()
495
local table_: any = table
496
self.size = 0
497
table_.clear(self._map)
498
table_.clear(self._array)
499
end
500
501
function Map:delete(key): boolean
502
if self._map[key] == nil then
503
return false
504
end
505
-- Luau FIXME: analyze should know self is Map<K, V> which includes size as a number
506
self.size = self.size :: number - 1
507
self._map[key] = nil
508
local index = table.find(self._array, key)
509
if index then
510
table.remove(self._array, index)
511
end
512
return true
513
end
514
515
-- Implements Javascript's `Map.prototype.forEach` as defined below
516
-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach
517
function Map:forEach<K, V>(callback: callbackFn<K, V> | callbackFnWithThisArg<K, V>, thisArg: Object?): ()
518
if typeof(callback) ~= "function" then
519
error("callback is not a function")
520
end
521
522
return Array.forEach(self._array, function(key: K)
523
local value: V = self._map[key] :: V
524
525
if thisArg ~= nil then
526
(callback :: callbackFnWithThisArg<K, V>)(thisArg, value, key, self)
527
else
528
(callback :: callbackFn<K, V>)(value, key, self)
529
end
530
end)
531
end
532
533
function Map:has(key): boolean
534
return self._map[key] ~= nil
535
end
536
537
function Map:keys()
538
return self._array
539
end
540
541
function Map:values()
542
return Array.map(self._array, function(key)
543
return self._map[key]
544
end)
545
end
546
547
function Map:entries()
548
return Array.map(self._array, function(key)
549
return { key, self._map[key] }
550
end)
551
end
552
553
function Map:ipairs()
554
return ipairs(self:entries())
555
end
556
557
function Map.__index(self, key)
558
local mapProp = rawget(Map, key)
559
if mapProp ~= nil then
560
return mapProp
561
end
562
563
return Map.get(self, key)
564
end
565
566
function Map.__newindex(table_, key, value)
567
table_:set(key, value)
568
end
569
570
local function coerceToMap(mapLike: Map<any, any> | Table<any, any>): Map<any, any>
571
return instanceOf(mapLike, Map) and mapLike :: Map<any, any> -- ROBLOX: order is preservered
572
or Map.new(Object.entries(mapLike)) -- ROBLOX: order is not preserved
573
end
574
575
-- local function coerceToTable(mapLike: Map<any, any> | Table<any, any>): Table<any, any>
576
-- if not instanceOf(mapLike, Map) then
577
-- return mapLike
578
-- end
579
580
-- -- create table from map
581
-- return Array.reduce(mapLike:entries(), function(tbl, entry)
582
-- tbl[entry[1]] = entry[2]
583
-- return tbl
584
-- end, {})
585
-- end
586
587
-- #region Tests to verify it works as expected
588
local function it(description: string, fn: () -> ())
589
local ok, result = pcall(fn)
590
591
if not ok then
592
error("Failed test: " .. description .. "\n" .. result)
593
end
594
end
595
596
local AN_ITEM = "bar"
597
local ANOTHER_ITEM = "baz"
598
599
-- #region [Describe] "Map"
600
-- #region [Child Describe] "constructors"
601
it("creates an empty array", function()
602
local foo = Map.new()
603
assert(foo.size == 0)
604
end)
605
606
it("creates a Map from an array", function()
607
local foo = Map.new({
608
{ AN_ITEM, "foo" },
609
{ ANOTHER_ITEM, "val" },
610
})
611
assert(foo.size == 2)
612
assert(foo:has(AN_ITEM) == true)
613
assert(foo:has(ANOTHER_ITEM) == true)
614
end)
615
616
it("creates a Map from an array with duplicate keys", function()
617
local foo = Map.new({
618
{ AN_ITEM, "foo1" },
619
{ AN_ITEM, "foo2" },
620
})
621
assert(foo.size == 1)
622
assert(foo:get(AN_ITEM) == "foo2")
623
624
assert(#foo:keys() == 1 and foo:keys()[1] == AN_ITEM)
625
assert(#foo:values() == 1 and foo:values()[1] == "foo2")
626
assert(#foo:entries() == 1)
627
assert(#foo:entries()[1] == 2)
628
629
assert(foo:entries()[1][1] == AN_ITEM)
630
assert(foo:entries()[1][2] == "foo2")
631
end)
632
633
it("preserves the order of keys first assignment", function()
634
local foo = Map.new({
635
{ AN_ITEM, "foo1" },
636
{ ANOTHER_ITEM, "bar" },
637
{ AN_ITEM, "foo2" },
638
})
639
assert(foo.size == 2)
640
assert(foo:get(AN_ITEM) == "foo2")
641
assert(foo:get(ANOTHER_ITEM) == "bar")
642
643
assert(foo:keys()[1] == AN_ITEM)
644
assert(foo:keys()[2] == ANOTHER_ITEM)
645
assert(foo:values()[1] == "foo2")
646
assert(foo:values()[2] == "bar")
647
assert(foo:entries()[1][1] == AN_ITEM)
648
assert(foo:entries()[1][2] == "foo2")
649
assert(foo:entries()[2][1] == ANOTHER_ITEM)
650
assert(foo:entries()[2][2] == "bar")
651
end)
652
-- #endregion
653
654
-- #region [Child Describe] "type"
655
it("instanceOf return true for an actual Map object", function()
656
local foo = Map.new()
657
assert(instanceOf(foo, Map) == true)
658
end)
659
660
it("instanceOf return false for an regular plain object", function()
661
local foo = {}
662
assert(instanceOf(foo, Map) == false)
663
end)
664
-- #endregion
665
666
-- #region [Child Describe] "set"
667
it("returns the Map object", function()
668
local foo = Map.new()
669
assert(foo:set(1, "baz") == foo)
670
end)
671
672
it("increments the size if the element is added for the first time", function()
673
local foo = Map.new()
674
foo:set(AN_ITEM, "foo")
675
assert(foo.size == 1)
676
end)
677
678
it("does not increment the size the second time an element is added", function()
679
local foo = Map.new()
680
foo:set(AN_ITEM, "foo")
681
foo:set(AN_ITEM, "val")
682
assert(foo.size == 1)
683
end)
684
685
it("sets values correctly to true/false", function()
686
-- Luau FIXME: Luau insists that arrays can't be mixed type
687
local foo = Map.new({ { AN_ITEM, false :: any } })
688
foo:set(AN_ITEM, false)
689
assert(foo.size == 1)
690
assert(foo:get(AN_ITEM) == false)
691
692
foo:set(AN_ITEM, true)
693
assert(foo.size == 1)
694
assert(foo:get(AN_ITEM) == true)
695
696
foo:set(AN_ITEM, false)
697
assert(foo.size == 1)
698
assert(foo:get(AN_ITEM) == false)
699
end)
700
701
-- #endregion
702
703
-- #region [Child Describe] "get"
704
it("returns value of item from provided key", function()
705
local foo = Map.new()
706
foo:set(AN_ITEM, "foo")
707
assert(foo:get(AN_ITEM) == "foo")
708
end)
709
710
it("returns nil if the item is not in the Map", function()
711
local foo = Map.new()
712
assert(foo:get(AN_ITEM) == nil)
713
end)
714
-- #endregion
715
716
-- #region [Child Describe] "clear"
717
it("sets the size to zero", function()
718
local foo = Map.new()
719
foo:set(AN_ITEM, "foo")
720
foo:clear()
721
assert(foo.size == 0)
722
end)
723
724
it("removes the items from the Map", function()
725
local foo = Map.new()
726
foo:set(AN_ITEM, "foo")
727
foo:clear()
728
assert(foo:has(AN_ITEM) == false)
729
end)
730
-- #endregion
731
732
-- #region [Child Describe] "delete"
733
it("removes the items from the Map", function()
734
local foo = Map.new()
735
foo:set(AN_ITEM, "foo")
736
foo:delete(AN_ITEM)
737
assert(foo:has(AN_ITEM) == false)
738
end)
739
740
it("returns true if the item was in the Map", function()
741
local foo = Map.new()
742
foo:set(AN_ITEM, "foo")
743
assert(foo:delete(AN_ITEM) == true)
744
end)
745
746
it("returns false if the item was not in the Map", function()
747
local foo = Map.new()
748
assert(foo:delete(AN_ITEM) == false)
749
end)
750
751
it("decrements the size if the item was in the Map", function()
752
local foo = Map.new()
753
foo:set(AN_ITEM, "foo")
754
foo:delete(AN_ITEM)
755
assert(foo.size == 0)
756
end)
757
758
it("does not decrement the size if the item was not in the Map", function()
759
local foo = Map.new()
760
foo:set(AN_ITEM, "foo")
761
foo:delete(ANOTHER_ITEM)
762
assert(foo.size == 1)
763
end)
764
765
it("deletes value set to false", function()
766
-- Luau FIXME: Luau insists arrays can't be mixed type
767
local foo = Map.new({ { AN_ITEM, false :: any } })
768
769
foo:delete(AN_ITEM)
770
771
assert(foo.size == 0)
772
assert(foo:get(AN_ITEM) == nil)
773
end)
774
-- #endregion
775
776
-- #region [Child Describe] "has"
777
it("returns true if the item is in the Map", function()
778
local foo = Map.new()
779
foo:set(AN_ITEM, "foo")
780
assert(foo:has(AN_ITEM) == true)
781
end)
782
783
it("returns false if the item is not in the Map", function()
784
local foo = Map.new()
785
assert(foo:has(AN_ITEM) == false)
786
end)
787
788
it("returns correctly with value set to false", function()
789
-- Luau FIXME: Luau insists arrays can't be mixed type
790
local foo = Map.new({ { AN_ITEM, false :: any } })
791
792
assert(foo:has(AN_ITEM) == true)
793
end)
794
-- #endregion
795
796
-- #region [Child Describe] "keys / values / entries"
797
it("returns array of elements", function()
798
local myMap = Map.new()
799
myMap:set(AN_ITEM, "foo")
800
myMap:set(ANOTHER_ITEM, "val")
801
802
assert(myMap:keys()[1] == AN_ITEM)
803
assert(myMap:keys()[2] == ANOTHER_ITEM)
804
805
assert(myMap:values()[1] == "foo")
806
assert(myMap:values()[2] == "val")
807
808
assert(myMap:entries()[1][1] == AN_ITEM)
809
assert(myMap:entries()[1][2] == "foo")
810
assert(myMap:entries()[2][1] == ANOTHER_ITEM)
811
assert(myMap:entries()[2][2] == "val")
812
end)
813
-- #endregion
814
815
-- #region [Child Describe] "__index"
816
it("can access fields directly without using get", function()
817
local typeName = "size"
818
819
local foo = Map.new({
820
{ AN_ITEM, "foo" },
821
{ ANOTHER_ITEM, "val" },
822
{ typeName, "buzz" },
823
})
824
825
assert(foo.size == 3)
826
assert(foo[AN_ITEM] == "foo")
827
assert(foo[ANOTHER_ITEM] == "val")
828
assert(foo:get(typeName) == "buzz")
829
end)
830
-- #endregion
831
832
-- #region [Child Describe] "__newindex"
833
it("can set fields directly without using set", function()
834
local foo = Map.new()
835
836
assert(foo.size == 0)
837
838
foo[AN_ITEM] = "foo"
839
foo[ANOTHER_ITEM] = "val"
840
foo.fizz = "buzz"
841
842
assert(foo.size == 3)
843
assert(foo:get(AN_ITEM) == "foo")
844
assert(foo:get(ANOTHER_ITEM) == "val")
845
assert(foo:get("fizz") == "buzz")
846
end)
847
-- #endregion
848
849
-- #region [Child Describe] "ipairs"
850
local function makeArray(...)
851
local array = {}
852
for _, item in ... do
853
table.insert(array, item)
854
end
855
return array
856
end
857
858
it("iterates on the elements by their insertion order", function()
859
local foo = Map.new()
860
foo:set(AN_ITEM, "foo")
861
foo:set(ANOTHER_ITEM, "val")
862
assert(makeArray(foo:ipairs())[1][1] == AN_ITEM)
863
assert(makeArray(foo:ipairs())[1][2] == "foo")
864
assert(makeArray(foo:ipairs())[2][1] == ANOTHER_ITEM)
865
assert(makeArray(foo:ipairs())[2][2] == "val")
866
end)
867
868
it("does not iterate on removed elements", function()
869
local foo = Map.new()
870
foo:set(AN_ITEM, "foo")
871
foo:set(ANOTHER_ITEM, "val")
872
foo:delete(AN_ITEM)
873
assert(makeArray(foo:ipairs())[1][1] == ANOTHER_ITEM)
874
assert(makeArray(foo:ipairs())[1][2] == "val")
875
end)
876
877
it("iterates on elements if the added back to the Map", function()
878
local foo = Map.new()
879
foo:set(AN_ITEM, "foo")
880
foo:set(ANOTHER_ITEM, "val")
881
foo:delete(AN_ITEM)
882
foo:set(AN_ITEM, "food")
883
assert(makeArray(foo:ipairs())[1][1] == ANOTHER_ITEM)
884
assert(makeArray(foo:ipairs())[1][2] == "val")
885
assert(makeArray(foo:ipairs())[2][1] == AN_ITEM)
886
assert(makeArray(foo:ipairs())[2][2] == "food")
887
end)
888
-- #endregion
889
890
-- #region [Child Describe] "Integration Tests"
891
-- it("MDN Examples", function()
892
-- local myMap = Map.new() :: Map<string | Object | Function, string>
893
894
-- local keyString = "a string"
895
-- local keyObj = {}
896
-- local keyFunc = function() end
897
898
-- -- setting the values
899
-- myMap:set(keyString, "value associated with 'a string'")
900
-- myMap:set(keyObj, "value associated with keyObj")
901
-- myMap:set(keyFunc, "value associated with keyFunc")
902
903
-- assert(myMap.size == 3)
904
905
-- -- getting the values
906
-- assert(myMap:get(keyString) == "value associated with 'a string'")
907
-- assert(myMap:get(keyObj) == "value associated with keyObj")
908
-- assert(myMap:get(keyFunc) == "value associated with keyFunc")
909
910
-- assert(myMap:get("a string") == "value associated with 'a string'")
911
912
-- assert(myMap:get({}) == nil) -- nil, because keyObj !== {}
913
-- assert(myMap:get(function() -- nil because keyFunc !== function () {}
914
-- end) == nil)
915
-- end)
916
917
it("handles non-traditional keys", function()
918
local myMap = Map.new() :: Map<boolean | number | string, string>
919
920
local falseKey = false
921
local trueKey = true
922
local negativeKey = -1
923
local emptyKey = ""
924
925
myMap:set(falseKey, "apple")
926
myMap:set(trueKey, "bear")
927
myMap:set(negativeKey, "corgi")
928
myMap:set(emptyKey, "doge")
929
930
assert(myMap.size == 4)
931
932
assert(myMap:get(falseKey) == "apple")
933
assert(myMap:get(trueKey) == "bear")
934
assert(myMap:get(negativeKey) == "corgi")
935
assert(myMap:get(emptyKey) == "doge")
936
937
myMap:delete(falseKey)
938
myMap:delete(trueKey)
939
myMap:delete(negativeKey)
940
myMap:delete(emptyKey)
941
942
assert(myMap.size == 0)
943
end)
944
-- #endregion
945
946
-- #endregion [Describe] "Map"
947
948
-- #region [Describe] "coerceToMap"
949
it("returns the same object if instance of Map", function()
950
local map = Map.new()
951
assert(coerceToMap(map) == map)
952
953
map = Map.new({})
954
assert(coerceToMap(map) == map)
955
956
map = Map.new({ { AN_ITEM, "foo" } })
957
assert(coerceToMap(map) == map)
958
end)
959
-- #endregion [Describe] "coerceToMap"
960
961
-- #endregion Tests to verify it works as expected
962
963