@@ -4,8 +4,8 @@ import serializeJavascript from 'serialize-javascript';
4
4
import mem , { memDecorator , memClear } from './index.js' ;
5
5
6
6
test ( 'memoize' , t => {
7
- let i = 0 ;
8
- const fixture = ( a ?: unknown , b ?: unknown ) => i ++ ;
7
+ let index = 0 ;
8
+ const fixture = ( a ?: unknown , b ?: unknown ) => index ++ ;
9
9
const memoized = mem ( fixture ) ;
10
10
t . is ( memoized ( ) , 0 ) ;
11
11
t . is ( memoized ( ) , 0 ) ;
@@ -28,13 +28,13 @@ test('memoize', t => {
28
28
t . is ( memoized ( true ) , 5 ) ;
29
29
30
30
// Ensure that functions are stored by reference and not by "value" (e.g. their `.toString()` representation)
31
- t . is ( memoized ( ( ) => i ++ ) , 6 ) ;
32
- t . is ( memoized ( ( ) => i ++ ) , 7 ) ;
31
+ t . is ( memoized ( ( ) => index ++ ) , 6 ) ;
32
+ t . is ( memoized ( ( ) => index ++ ) , 7 ) ;
33
33
} ) ;
34
34
35
35
test ( 'cacheKey option' , t => {
36
- let i = 0 ;
37
- const fixture = ( ..._arguments : any ) => i ++ ;
36
+ let index = 0 ;
37
+ const fixture = ( ..._arguments : any ) => index ++ ;
38
38
const memoized = mem ( fixture , { cacheKey : ( [ firstArgument ] ) => String ( firstArgument ) } ) ;
39
39
t . is ( memoized ( 1 ) , 0 ) ;
40
40
t . is ( memoized ( 1 ) , 0 ) ;
@@ -44,8 +44,8 @@ test('cacheKey option', t => {
44
44
} ) ;
45
45
46
46
test ( 'memoize with multiple non-primitive arguments' , t => {
47
- let i = 0 ;
48
- const memoized = mem ( ( a ?: unknown , b ?: unknown , c ?: unknown ) => i ++ , { cacheKey : JSON . stringify } ) ;
47
+ let index = 0 ;
48
+ const memoized = mem ( ( a ?: unknown , b ?: unknown , c ?: unknown ) => index ++ , { cacheKey : JSON . stringify } ) ;
49
49
t . is ( memoized ( ) , 0 ) ;
50
50
t . is ( memoized ( ) , 0 ) ;
51
51
t . is ( memoized ( { foo : true } , { bar : false } ) , 1 ) ;
@@ -55,8 +55,8 @@ test('memoize with multiple non-primitive arguments', t => {
55
55
} ) ;
56
56
57
57
test ( 'memoize with regexp arguments' , t => {
58
- let i = 0 ;
59
- const memoized = mem ( ( a ?: unknown ) => i ++ , { cacheKey : serializeJavascript } ) ;
58
+ let index = 0 ;
59
+ const memoized = mem ( ( a ?: unknown ) => index ++ , { cacheKey : serializeJavascript } ) ;
60
60
t . is ( memoized ( ) , 0 ) ;
61
61
t . is ( memoized ( ) , 0 ) ;
62
62
t . is ( memoized ( / S i n d r e S o r h u s / ) , 1 ) ;
@@ -66,10 +66,10 @@ test('memoize with regexp arguments', t => {
66
66
} ) ;
67
67
68
68
test ( 'memoize with Symbol arguments' , t => {
69
- let i = 0 ;
69
+ let index = 0 ;
70
70
const argument1 = Symbol ( 'fixture1' ) ;
71
71
const argument2 = Symbol ( 'fixture2' ) ;
72
- const memoized = mem ( ( a ?: unknown ) => i ++ ) ;
72
+ const memoized = mem ( ( a ?: unknown ) => index ++ ) ;
73
73
t . is ( memoized ( ) , 0 ) ;
74
74
t . is ( memoized ( ) , 0 ) ;
75
75
t . is ( memoized ( argument1 ) , 1 ) ;
@@ -79,8 +79,8 @@ test('memoize with Symbol arguments', t => {
79
79
} ) ;
80
80
81
81
test ( 'maxAge option' , async t => {
82
- let i = 0 ;
83
- const fixture = ( a ?: unknown ) => i ++ ;
82
+ let index = 0 ;
83
+ const fixture = ( a ?: unknown ) => index ++ ;
84
84
const memoized = mem ( fixture , { maxAge : 100 } ) ;
85
85
t . is ( memoized ( 1 ) , 0 ) ;
86
86
t . is ( memoized ( 1 ) , 0 ) ;
@@ -91,8 +91,8 @@ test('maxAge option', async t => {
91
91
} ) ;
92
92
93
93
test ( 'maxAge option deletes old items' , async t => {
94
- let i = 0 ;
95
- const fixture = ( a ?: unknown ) => i ++ ;
94
+ let index = 0 ;
95
+ const fixture = ( a ?: unknown ) => index ++ ;
96
96
const cache = new Map < number , number > ( ) ;
97
97
const deleted : number [ ] = [ ] ;
98
98
const _delete = cache . delete . bind ( cache ) ;
@@ -115,13 +115,13 @@ test('maxAge option deletes old items', async t => {
115
115
} ) ;
116
116
117
117
test ( 'maxAge items are deleted even if function throws' , async t => {
118
- let i = 0 ;
118
+ let index = 0 ;
119
119
const fixture = ( a ?: unknown ) => {
120
- if ( i === 1 ) {
120
+ if ( index === 1 ) {
121
121
throw new Error ( 'failure' ) ;
122
122
}
123
123
124
- return i ++ ;
124
+ return index ++ ;
125
125
} ;
126
126
127
127
const cache = new Map ( ) ;
@@ -139,8 +139,8 @@ test('maxAge items are deleted even if function throws', async t => {
139
139
} ) ;
140
140
141
141
test ( 'cache option' , t => {
142
- let i = 0 ;
143
- const fixture = ( ..._arguments : any ) => i ++ ;
142
+ let index = 0 ;
143
+ const fixture = ( ..._arguments : any ) => index ++ ;
144
144
const memoized = mem ( fixture , {
145
145
cache : new WeakMap ( ) ,
146
146
cacheKey : < ReturnValue > ( [ firstArgument ] : [ ReturnValue ] ) : ReturnValue => firstArgument ,
@@ -154,8 +154,8 @@ test('cache option', t => {
154
154
} ) ;
155
155
156
156
test ( 'promise support' , async t => {
157
- let i = 0 ;
158
- const memoized = mem ( async ( a ?: unknown ) => i ++ ) ;
157
+ let index = 0 ;
158
+ const memoized = mem ( async ( a ?: unknown ) => index ++ ) ;
159
159
t . is ( await memoized ( ) , 0 ) ;
160
160
t . is ( await memoized ( ) , 0 ) ;
161
161
t . is ( await memoized ( 10 ) , 1 ) ;
@@ -166,8 +166,8 @@ test('preserves the original function name', t => {
166
166
} ) ;
167
167
168
168
test ( '.clear()' , t => {
169
- let i = 0 ;
170
- const fixture = ( ) => i ++ ;
169
+ let index = 0 ;
170
+ const fixture = ( ) => index ++ ;
171
171
const memoized = mem ( fixture ) ;
172
172
t . is ( memoized ( ) , 0 ) ;
173
173
t . is ( memoized ( ) , 0 ) ;
@@ -240,3 +240,102 @@ test('memClear() throws when called on an unclearable cache', t => {
240
240
instanceOf : TypeError ,
241
241
} ) ;
242
242
} ) ;
243
+
244
+ test ( 'maxAge - cache item expires after specified duration' , async t => {
245
+ let index = 0 ;
246
+ const fixture = ( ) => index ++ ;
247
+ const memoized = mem ( fixture , { maxAge : 100 } ) ;
248
+
249
+ t . is ( memoized ( ) , 0 ) ; // Initial call, cached
250
+ t . is ( memoized ( ) , 0 ) ; // Subsequent call, still cached
251
+ await delay ( 150 ) ; // Wait for longer than maxAge
252
+ t . is ( memoized ( ) , 1 ) ; // Cache expired, should compute again
253
+ } ) ;
254
+
255
+ test ( 'maxAge - cache expiration timing is accurate' , async t => {
256
+ let index = 0 ;
257
+ const fixture = ( ) => index ++ ;
258
+ const memoized = mem ( fixture , { maxAge : 100 } ) ;
259
+
260
+ t . is ( memoized ( ) , 0 ) ;
261
+ await delay ( 90 ) ; // Wait for slightly less than maxAge
262
+ t . is ( memoized ( ) , 0 ) ; // Should still be cached
263
+ await delay ( 20 ) ; // Total delay now exceeds maxAge
264
+ t . is ( memoized ( ) , 1 ) ; // Should recompute as cache has expired
265
+ } ) ;
266
+
267
+ test ( 'maxAge - expired items are not present in cache' , async t => {
268
+ let index = 0 ;
269
+ const fixture = ( ) => index ++ ;
270
+ const cache = new Map ( ) ;
271
+ const memoized = mem ( fixture , { maxAge : 100 , cache} ) ;
272
+
273
+ memoized ( ) ; // Call to cache the result
274
+ await delay ( 150 ) ; // Wait for cache to expire
275
+ memoized ( ) ; // Recompute and recache
276
+ t . is ( cache . size , 1 ) ; // Only one item should be in the cache
277
+ } ) ;
278
+
279
+ test ( 'maxAge - complex arguments and cache expiration' , async t => {
280
+ let index = 0 ;
281
+ const fixture = object => index ++ ;
282
+ const memoized = mem ( fixture , { maxAge : 100 , cacheKey : JSON . stringify } ) ;
283
+
284
+ const arg = { key : 'value' } ;
285
+ t . is ( memoized ( arg ) , 0 ) ;
286
+ await delay ( 150 ) ;
287
+ t . is ( memoized ( arg ) , 1 ) ; // Argument is the same, but should recompute due to expiration
288
+ } ) ;
289
+
290
+ test ( 'maxAge - concurrent calls return cached value' , async t => {
291
+ let index = 0 ;
292
+ const fixture = ( ) => index ++ ;
293
+ const memoized = mem ( fixture , { maxAge : 100 } ) ;
294
+
295
+ t . is ( memoized ( ) , 0 ) ;
296
+ await delay ( 50 ) ; // Delay less than maxAge
297
+ t . is ( memoized ( ) , 0 ) ; // Should return cached value
298
+ } ) ;
299
+
300
+ test ( 'maxAge - different arguments have separate expirations' , async t => {
301
+ let index = 0 ;
302
+ const fixture = x => index ++ ;
303
+ const memoized = mem ( fixture , { maxAge : 100 } ) ;
304
+
305
+ t . is ( memoized ( 'a' ) , 0 ) ;
306
+ await delay ( 150 ) ; // Expire the cache for 'a'
307
+ t . is ( memoized ( 'b' ) , 1 ) ; // 'b' should be a separate cache entry
308
+ t . is ( memoized ( 'a' ) , 2 ) ; // 'a' should be recomputed
309
+ } ) ;
310
+
311
+ test ( 'maxAge - zero maxAge means no caching' , t => {
312
+ let index = 0 ;
313
+ const fixture = ( ) => index ++ ;
314
+ const memoized = mem ( fixture , { maxAge : 0 } ) ;
315
+
316
+ t . is ( memoized ( ) , 0 ) ;
317
+ t . is ( memoized ( ) , 1 ) ; // No caching, should increment
318
+ } ) ;
319
+
320
+ test ( 'maxAge - immediate expiration' , async t => {
321
+ let index = 0 ;
322
+ const fixture = ( ) => index ++ ;
323
+ const memoized = mem ( fixture , { maxAge : 1 } ) ;
324
+ t . is ( memoized ( ) , 0 ) ;
325
+ await delay ( 10 ) ;
326
+ t . is ( memoized ( ) , 1 ) ; // Cache should expire immediately
327
+ } ) ;
328
+
329
+ test ( 'maxAge - high concurrency' , async t => {
330
+ let index = 0 ;
331
+ const fixture = ( ) => index ++ ;
332
+ const memoized = mem ( fixture , { maxAge : 50 } ) ;
333
+
334
+ // Simulate concurrent calls
335
+ for ( let job = 0 ; job < 10_000 ; job ++ ) {
336
+ memoized ( ) ;
337
+ }
338
+
339
+ await delay ( 100 ) ;
340
+ t . is ( memoized ( ) , 1 ) ;
341
+ } ) ;
0 commit comments