@applicaster/zapp-react-native-utils 15.0.0-rc.95 → 15.0.0-rc.97

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "15.0.0-rc.95",
3
+ "version": "15.0.0-rc.97",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "homepage": "https://github.com/applicaster/quickbrick#readme",
29
29
  "dependencies": {
30
- "@applicaster/applicaster-types": "15.0.0-rc.95",
30
+ "@applicaster/applicaster-types": "15.0.0-rc.97",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -0,0 +1,549 @@
1
+ import {
2
+ measureDimensionalIntersection,
3
+ measureVerticalIntersection,
4
+ measureHorizontalIntersection,
5
+ measureDistance,
6
+ measureEuclidianDistanceToCenter,
7
+ isOnDirection,
8
+ } from "../index";
9
+
10
+ describe("rectUtils", () => {
11
+ describe("measureVerticalIntersection", () => {
12
+ it("should return 0 when rectangles don't overlap vertically", () => {
13
+ const current = { top: 0, bottom: 100, left: 0, right: 100 };
14
+ const target = { top: 150, bottom: 250, left: 0, right: 100 };
15
+
16
+ expect(measureVerticalIntersection(current, target)).toBe(0);
17
+ });
18
+
19
+ it("should return 0 when target is above current", () => {
20
+ const current = { top: 100, bottom: 200, left: 0, right: 100 };
21
+ const target = { top: 0, bottom: 50, left: 0, right: 100 };
22
+
23
+ expect(measureVerticalIntersection(current, target)).toBe(0);
24
+ });
25
+
26
+ it("should measure partial vertical overlap", () => {
27
+ const current = { top: 0, bottom: 100, left: 0, right: 100 };
28
+ const target = { top: 50, bottom: 150, left: 0, right: 100 };
29
+
30
+ expect(measureVerticalIntersection(current, target)).toBe(50);
31
+ });
32
+
33
+ it("should measure full vertical overlap when target inside current", () => {
34
+ const current = { top: 0, bottom: 200, left: 0, right: 100 };
35
+ const target = { top: 50, bottom: 150, left: 0, right: 100 };
36
+
37
+ expect(measureVerticalIntersection(current, target)).toBe(100);
38
+ });
39
+
40
+ it("should measure full vertical overlap when current inside target", () => {
41
+ const current = { top: 50, bottom: 150, left: 0, right: 100 };
42
+ const target = { top: 0, bottom: 200, left: 0, right: 100 };
43
+
44
+ expect(measureVerticalIntersection(current, target)).toBe(100);
45
+ });
46
+
47
+ it("should handle exact vertical alignment", () => {
48
+ const current = { top: 0, bottom: 100, left: 0, right: 100 };
49
+ const target = { top: 0, bottom: 100, left: 100, right: 200 };
50
+
51
+ expect(measureVerticalIntersection(current, target)).toBe(100);
52
+ });
53
+
54
+ it("should handle touching edges", () => {
55
+ const current = { top: 0, bottom: 100, left: 0, right: 100 };
56
+ const target = { top: 100, bottom: 200, left: 0, right: 100 };
57
+
58
+ expect(measureVerticalIntersection(current, target)).toBe(0);
59
+ });
60
+ });
61
+
62
+ describe("measureHorizontalIntersection", () => {
63
+ it("should return 0 when rectangles don't overlap horizontally", () => {
64
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
65
+ const target = { left: 150, right: 250, top: 0, bottom: 100 };
66
+
67
+ expect(measureHorizontalIntersection(current, target)).toBe(0);
68
+ });
69
+
70
+ it("should return 0 when target is to the left of current", () => {
71
+ const current = { left: 100, right: 200, top: 0, bottom: 100 };
72
+ const target = { left: 0, right: 50, top: 0, bottom: 100 };
73
+
74
+ expect(measureHorizontalIntersection(current, target)).toBe(0);
75
+ });
76
+
77
+ it("should measure partial horizontal overlap", () => {
78
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
79
+ const target = { left: 50, right: 150, top: 0, bottom: 100 };
80
+
81
+ expect(measureHorizontalIntersection(current, target)).toBe(50);
82
+ });
83
+
84
+ it("should measure full horizontal overlap when target inside current", () => {
85
+ const current = { left: 0, right: 200, top: 0, bottom: 100 };
86
+ const target = { left: 50, right: 150, top: 0, bottom: 100 };
87
+
88
+ expect(measureHorizontalIntersection(current, target)).toBe(100);
89
+ });
90
+
91
+ it("should measure full horizontal overlap when current inside target", () => {
92
+ const current = { left: 50, right: 150, top: 0, bottom: 100 };
93
+ const target = { left: 0, right: 200, top: 0, bottom: 100 };
94
+
95
+ expect(measureHorizontalIntersection(current, target)).toBe(100);
96
+ });
97
+
98
+ it("should handle exact horizontal alignment", () => {
99
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
100
+ const target = { left: 0, right: 100, top: 100, bottom: 200 };
101
+
102
+ expect(measureHorizontalIntersection(current, target)).toBe(100);
103
+ });
104
+
105
+ it("should handle touching edges", () => {
106
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
107
+ const target = { left: 100, right: 200, top: 0, bottom: 100 };
108
+
109
+ expect(measureHorizontalIntersection(current, target)).toBe(0);
110
+ });
111
+ });
112
+
113
+ describe("measureDimensionalIntersection", () => {
114
+ it("should call measureVerticalIntersection for horizontal direction", () => {
115
+ const current = { top: 0, bottom: 100, left: 0, right: 100 };
116
+ const target = { top: 50, bottom: 150, left: 0, right: 100 };
117
+ const direction = { isHorizontal: true };
118
+
119
+ const result = measureDimensionalIntersection(direction, current, target);
120
+
121
+ expect(result).toBe(50);
122
+ });
123
+
124
+ it("should call measureHorizontalIntersection for vertical direction", () => {
125
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
126
+ const target = { left: 50, right: 150, top: 0, bottom: 100 };
127
+ const direction = { isHorizontal: false };
128
+
129
+ const result = measureDimensionalIntersection(direction, current, target);
130
+
131
+ expect(result).toBe(50);
132
+ });
133
+
134
+ it("should handle no intersection for horizontal movement", () => {
135
+ const current = { top: 0, bottom: 100, left: 0, right: 100 };
136
+ const target = { top: 200, bottom: 300, left: 0, right: 100 };
137
+ const direction = { isHorizontal: true };
138
+
139
+ const result = measureDimensionalIntersection(direction, current, target);
140
+
141
+ expect(result).toBe(0);
142
+ });
143
+
144
+ it("should handle no intersection for vertical movement", () => {
145
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
146
+ const target = { left: 200, right: 300, top: 0, bottom: 100 };
147
+ const direction = { isHorizontal: false };
148
+
149
+ const result = measureDimensionalIntersection(direction, current, target);
150
+
151
+ expect(result).toBe(0);
152
+ });
153
+ });
154
+
155
+ describe("measureDistance", () => {
156
+ describe("right direction", () => {
157
+ it("should return positive distance when target is to the right", () => {
158
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
159
+ const target = { left: 150, right: 250, top: 0, bottom: 100 };
160
+ const direction = { value: "right" };
161
+
162
+ const result = measureDistance(direction, current, target);
163
+
164
+ expect(result).toBe(50);
165
+ });
166
+
167
+ it("should return negative distance when target is to the left", () => {
168
+ const current = { left: 100, right: 200, top: 0, bottom: 100 };
169
+ const target = { left: 0, right: 50, top: 0, bottom: 100 };
170
+ const direction = { value: "right" };
171
+
172
+ const result = measureDistance(direction, current, target);
173
+
174
+ // Formula: -1 * (current.right - target.left) = -1 * (200 - 0) = -200
175
+ expect(result).toBe(-200);
176
+ });
177
+
178
+ it("should return 0 when target touches current", () => {
179
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
180
+ const target = { left: 100, right: 200, top: 0, bottom: 100 };
181
+ const direction = { value: "right" };
182
+
183
+ const result = Number(measureDistance(direction, current, target));
184
+
185
+ // Formula: -1 * (100 - 100) = -0 (signed zero)
186
+ expect(Math.abs(result)).toBe(0);
187
+ });
188
+ });
189
+
190
+ describe("left direction", () => {
191
+ it("should return positive distance when target is to the left", () => {
192
+ const current = { left: 100, right: 200, top: 0, bottom: 100 };
193
+ const target = { left: 0, right: 50, top: 0, bottom: 100 };
194
+ const direction = { value: "left" };
195
+
196
+ const result = measureDistance(direction, current, target);
197
+
198
+ expect(result).toBe(50);
199
+ });
200
+
201
+ it("should return negative distance when target is to the right", () => {
202
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
203
+ const target = { left: 150, right: 250, top: 0, bottom: 100 };
204
+ const direction = { value: "left" };
205
+
206
+ const result = measureDistance(direction, current, target);
207
+
208
+ // Formula: current.left - target.right = 0 - 250 = -250
209
+ expect(result).toBe(-250);
210
+ });
211
+
212
+ it("should return 0 when target touches current", () => {
213
+ const current = { left: 100, right: 200, top: 0, bottom: 100 };
214
+ const target = { left: 0, right: 100, top: 0, bottom: 100 };
215
+ const direction = { value: "left" };
216
+
217
+ const result = measureDistance(direction, current, target);
218
+
219
+ expect(result).toBe(0);
220
+ });
221
+ });
222
+
223
+ describe("up direction", () => {
224
+ it("should return positive distance when target is above", () => {
225
+ const current = { left: 0, right: 100, top: 100, bottom: 200 };
226
+
227
+ const target = {
228
+ left: 0,
229
+ right: 100,
230
+ top: 0,
231
+ bottom: 50,
232
+ x: 0,
233
+ width: 100,
234
+ };
235
+
236
+ const direction = { value: "up" };
237
+
238
+ const result = measureDistance(direction, current, target);
239
+
240
+ expect(result).toBe(50);
241
+ });
242
+
243
+ it("should return negative distance when target is below", () => {
244
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
245
+
246
+ const target = {
247
+ left: 0,
248
+ right: 100,
249
+ top: 150,
250
+ bottom: 250,
251
+ x: 0,
252
+ width: 100,
253
+ };
254
+
255
+ const direction = { value: "up" };
256
+
257
+ const result = measureDistance(direction, current, target);
258
+
259
+ // Formula: current.top - target.bottom = 0 - 250 = -250
260
+ expect(result).toBe(-250);
261
+ });
262
+
263
+ it("should return -10000 when target is offscreen to the left", () => {
264
+ const current = { left: 0, right: 100, top: 100, bottom: 200 };
265
+
266
+ const target = {
267
+ left: -200,
268
+ right: -100,
269
+ top: 0,
270
+ bottom: 50,
271
+ x: -200,
272
+ width: 100,
273
+ };
274
+
275
+ const direction = { value: "up" };
276
+
277
+ const result = measureDistance(direction, current, target);
278
+
279
+ expect(result).toBe(-10000);
280
+ });
281
+
282
+ it("should handle target at x = 0", () => {
283
+ const current = { left: 0, right: 100, top: 100, bottom: 200 };
284
+
285
+ const target = {
286
+ left: 0,
287
+ right: 100,
288
+ top: 0,
289
+ bottom: 50,
290
+ x: 0,
291
+ width: 100,
292
+ };
293
+
294
+ const direction = { value: "up" };
295
+
296
+ const result = measureDistance(direction, current, target);
297
+
298
+ expect(result).toBe(50);
299
+ });
300
+ });
301
+
302
+ describe("down direction", () => {
303
+ it("should return positive distance when target is below", () => {
304
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
305
+ const target = { left: 0, right: 100, top: 150, bottom: 250, x: 0 };
306
+ const direction = { value: "down" };
307
+
308
+ const result = measureDistance(direction, current, target);
309
+
310
+ expect(result).toBe(50);
311
+ });
312
+
313
+ it("should return negative distance when target is above", () => {
314
+ const current = { left: 0, right: 100, top: 100, bottom: 200 };
315
+ const target = { left: 0, right: 100, top: 0, bottom: 50, x: 0 };
316
+ const direction = { value: "down" };
317
+
318
+ const result = measureDistance(direction, current, target);
319
+
320
+ // Formula: -1 * (current.bottom - target.top) = -1 * (200 - 0) = -200
321
+ expect(result).toBe(-200);
322
+ });
323
+
324
+ it("should return -10000 when target is offscreen to the right", () => {
325
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
326
+
327
+ const target = {
328
+ left: 2000,
329
+ right: 2100,
330
+ top: 150,
331
+ bottom: 250,
332
+ x: 2000,
333
+ };
334
+
335
+ const direction = { value: "down" };
336
+
337
+ const result = measureDistance(direction, current, target);
338
+
339
+ expect(result).toBe(-10000);
340
+ });
341
+
342
+ it("should handle target at x = 1920", () => {
343
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
344
+
345
+ const target = {
346
+ left: 1920,
347
+ right: 2020,
348
+ top: 150,
349
+ bottom: 250,
350
+ x: 1920,
351
+ };
352
+
353
+ const direction = { value: "down" };
354
+
355
+ const result = measureDistance(direction, current, target);
356
+
357
+ expect(result).toBe(50);
358
+ });
359
+ });
360
+ });
361
+
362
+ describe("measureEuclidianDistanceToCenter", () => {
363
+ it("should calculate distance between centers", () => {
364
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
365
+ const target = { left: 300, right: 400, top: 400, bottom: 500 };
366
+
367
+ const result = measureEuclidianDistanceToCenter(current, target);
368
+
369
+ // Current center: (50, 50)
370
+ // Target center: (350, 450)
371
+ // Distance: sqrt((350-50)^2 + (450-50)^2) = sqrt(90000 + 160000) = sqrt(250000) = 500
372
+ expect(result).toBe(500);
373
+ });
374
+
375
+ it("should return 0 for same rectangle", () => {
376
+ const rect = { left: 0, right: 100, top: 0, bottom: 100 };
377
+
378
+ const result = measureEuclidianDistanceToCenter(rect, rect);
379
+
380
+ expect(result).toBe(0);
381
+ });
382
+
383
+ it("should calculate distance for adjacent rectangles", () => {
384
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
385
+ const target = { left: 100, right: 200, top: 0, bottom: 100 };
386
+
387
+ const result = measureEuclidianDistanceToCenter(current, target);
388
+
389
+ // Current center: (50, 50)
390
+ // Target center: (150, 50)
391
+ // Distance: sqrt((150-50)^2 + (50-50)^2) = sqrt(10000) = 100
392
+ expect(result).toBe(100);
393
+ });
394
+
395
+ it("should calculate distance for vertical separation", () => {
396
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
397
+ const target = { left: 0, right: 100, top: 100, bottom: 200 };
398
+
399
+ const result = measureEuclidianDistanceToCenter(current, target);
400
+
401
+ // Current center: (50, 50)
402
+ // Target center: (50, 150)
403
+ // Distance: sqrt((50-50)^2 + (150-50)^2) = sqrt(10000) = 100
404
+ expect(result).toBe(100);
405
+ });
406
+
407
+ it("should calculate distance for diagonal separation", () => {
408
+ const current = { left: 0, right: 60, top: 0, bottom: 80 };
409
+ const target = { left: 60, right: 120, top: 80, bottom: 160 };
410
+
411
+ const result = measureEuclidianDistanceToCenter(current, target);
412
+
413
+ // Current center: (30, 40)
414
+ // Target center: (90, 120)
415
+ // Distance: sqrt((90-30)^2 + (120-40)^2) = sqrt(3600 + 6400) = sqrt(10000) = 100
416
+ expect(result).toBe(100);
417
+ });
418
+
419
+ it("should handle rectangles with different sizes", () => {
420
+ const current = { left: 0, right: 50, top: 0, bottom: 50 };
421
+ const target = { left: 100, right: 300, top: 100, bottom: 300 };
422
+
423
+ const result = measureEuclidianDistanceToCenter(current, target);
424
+
425
+ // Current center: (25, 25)
426
+ // Target center: (200, 200)
427
+ // Distance: sqrt((200-25)^2 + (200-25)^2) = sqrt(175^2 + 175^2) = sqrt(61250) ≈ 247.49
428
+ expect(result).toBeCloseTo(247.49, 1);
429
+ });
430
+ });
431
+
432
+ describe("isOnDirection", () => {
433
+ describe("horizontal directions", () => {
434
+ it("should return true when target is to the right", () => {
435
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
436
+ const target = { left: 150, right: 250, top: 0, bottom: 100 };
437
+ const direction = { value: "right", isHorizontal: true };
438
+
439
+ const result = isOnDirection(direction, current, target);
440
+
441
+ expect(result).toBe(true);
442
+ });
443
+
444
+ it("should return false when target is to the left but direction is right", () => {
445
+ const current = { left: 100, right: 200, top: 0, bottom: 100 };
446
+ const target = { left: 0, right: 50, top: 0, bottom: 100 };
447
+ const direction = { value: "right", isHorizontal: true };
448
+
449
+ const result = isOnDirection(direction, current, target);
450
+
451
+ expect(result).toBe(false);
452
+ });
453
+
454
+ it("should return true when target is to the left", () => {
455
+ const current = { left: 100, right: 200, top: 0, bottom: 100 };
456
+ const target = { left: 0, right: 50, top: 0, bottom: 100 };
457
+ const direction = { value: "left", isHorizontal: true };
458
+
459
+ const result = isOnDirection(direction, current, target);
460
+
461
+ expect(result).toBe(true);
462
+ });
463
+
464
+ it("should return false when target is to the right but direction is left", () => {
465
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
466
+ const target = { left: 150, right: 250, top: 0, bottom: 100 };
467
+ const direction = { value: "left", isHorizontal: true };
468
+
469
+ const result = isOnDirection(direction, current, target);
470
+
471
+ expect(result).toBe(false);
472
+ });
473
+ });
474
+
475
+ describe("vertical directions", () => {
476
+ it("should return true when target is above", () => {
477
+ const current = { left: 0, right: 100, top: 100, bottom: 200 };
478
+ const target = { left: 0, right: 100, top: 0, bottom: 50 };
479
+ const direction = { value: "up", isVertical: true };
480
+
481
+ const result = isOnDirection(direction, current, target);
482
+
483
+ expect(result).toBe(true);
484
+ });
485
+
486
+ it("should return false when target is below but direction is up", () => {
487
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
488
+ const target = { left: 0, right: 100, top: 150, bottom: 250 };
489
+ const direction = { value: "up", isVertical: true };
490
+
491
+ const result = isOnDirection(direction, current, target);
492
+
493
+ expect(result).toBe(false);
494
+ });
495
+
496
+ it("should return true when target is below", () => {
497
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
498
+ const target = { left: 0, right: 100, top: 150, bottom: 250 };
499
+ const direction = { value: "down", isVertical: true };
500
+
501
+ const result = isOnDirection(direction, current, target);
502
+
503
+ expect(result).toBe(true);
504
+ });
505
+
506
+ it("should return false when target is above but direction is down", () => {
507
+ const current = { left: 0, right: 100, top: 100, bottom: 200 };
508
+ const target = { left: 0, right: 100, top: 0, bottom: 50 };
509
+ const direction = { value: "down", isVertical: true };
510
+
511
+ const result = isOnDirection(direction, current, target);
512
+
513
+ expect(result).toBe(false);
514
+ });
515
+ });
516
+
517
+ describe("edge cases", () => {
518
+ it("should handle aligned centers", () => {
519
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
520
+ const target = { left: 100, right: 200, top: 0, bottom: 100 };
521
+ const direction = { value: "right", isHorizontal: true };
522
+
523
+ const result = isOnDirection(direction, current, target);
524
+
525
+ expect(result).toBe(true);
526
+ });
527
+
528
+ it("should handle overlapping rectangles", () => {
529
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
530
+ const target = { left: 50, right: 150, top: 50, bottom: 150 };
531
+ const direction = { value: "right", isHorizontal: true };
532
+
533
+ const result = isOnDirection(direction, current, target);
534
+
535
+ expect(result).toBe(true);
536
+ });
537
+
538
+ it("should return undefined for invalid direction", () => {
539
+ const current = { left: 0, right: 100, top: 0, bottom: 100 };
540
+ const target = { left: 150, right: 250, top: 0, bottom: 100 };
541
+ const direction = { value: "right" }; // No isHorizontal or isVertical
542
+
543
+ const result = isOnDirection(direction, current, target);
544
+
545
+ expect(result).toBeUndefined();
546
+ });
547
+ });
548
+ });
549
+ });
@@ -9,7 +9,7 @@
9
9
  */
10
10
  export function measureDimensionalIntersection(direction, current, target) {
11
11
  if (direction.isHorizontal) {
12
- return measureVerticalIntersectiont(current, target);
12
+ return measureVerticalIntersection(current, target);
13
13
  } else {
14
14
  return measureHorizontalIntersection(current, target);
15
15
  }
@@ -25,7 +25,7 @@ export function measureDimensionalIntersection(direction, current, target) {
25
25
  * @param {Object} target rect object {right, left, top, bottom, x, y, width. height}
26
26
  * @returns {Int} Returns vertical intersection of current and target object
27
27
  */
28
- export function measureVerticalIntersectiont(current, target) {
28
+ export function measureVerticalIntersection(current, target) {
29
29
  if (target.top >= current.bottom || target.bottom <= current.top) {
30
30
  return 0;
31
31
  }
@@ -0,0 +1,333 @@
1
+ import {
2
+ getFocusableId,
3
+ getPickerSelectorId,
4
+ SCREEN_PICKER_CONTAINER,
5
+ getScreenPickerId,
6
+ getScreenPickerSelectorContainerId,
7
+ getScreenPickerContentContainerId,
8
+ } from "../index";
9
+
10
+ describe("screenPickerUtils", () => {
11
+ describe("getFocusableId", () => {
12
+ it("should generate focusable ID with PickerItem prefix", () => {
13
+ expect(getFocusableId("item1")).toBe("PickerItem.item1");
14
+ });
15
+
16
+ it("should handle numeric IDs", () => {
17
+ expect(getFocusableId(123)).toBe("PickerItem.123");
18
+ });
19
+
20
+ it("should handle string IDs with special characters", () => {
21
+ expect(getFocusableId("item-123_test")).toBe("PickerItem.item-123_test");
22
+ });
23
+
24
+ it("should handle empty string ID", () => {
25
+ expect(getFocusableId("")).toBe("PickerItem.");
26
+ });
27
+
28
+ it("should handle IDs with spaces", () => {
29
+ expect(getFocusableId("my item")).toBe("PickerItem.my item");
30
+ });
31
+
32
+ it("should handle IDs with dots", () => {
33
+ expect(getFocusableId("parent.child")).toBe("PickerItem.parent.child");
34
+ });
35
+
36
+ it("should handle null ID", () => {
37
+ expect(getFocusableId(null)).toBe("PickerItem.null");
38
+ });
39
+
40
+ it("should handle undefined ID", () => {
41
+ expect(getFocusableId(undefined)).toBe("PickerItem.undefined");
42
+ });
43
+ });
44
+
45
+ describe("getPickerSelectorId", () => {
46
+ it("should generate picker selector ID with PickerSelector prefix", () => {
47
+ expect(getPickerSelectorId("selector1")).toBe("PickerSelector.selector1");
48
+ });
49
+
50
+ it("should handle numeric IDs", () => {
51
+ expect(getPickerSelectorId(456)).toBe("PickerSelector.456");
52
+ });
53
+
54
+ it("should handle string IDs with special characters", () => {
55
+ expect(getPickerSelectorId("selector-abc_xyz")).toBe(
56
+ "PickerSelector.selector-abc_xyz"
57
+ );
58
+ });
59
+
60
+ it("should handle empty string ID", () => {
61
+ expect(getPickerSelectorId("")).toBe("PickerSelector.");
62
+ });
63
+
64
+ it("should handle IDs with spaces", () => {
65
+ expect(getPickerSelectorId("my selector")).toBe(
66
+ "PickerSelector.my selector"
67
+ );
68
+ });
69
+
70
+ it("should handle null ID", () => {
71
+ expect(getPickerSelectorId(null)).toBe("PickerSelector.null");
72
+ });
73
+
74
+ it("should handle undefined ID", () => {
75
+ expect(getPickerSelectorId(undefined)).toBe("PickerSelector.undefined");
76
+ });
77
+ });
78
+
79
+ describe("SCREEN_PICKER_CONTAINER", () => {
80
+ it("should have correct constant value", () => {
81
+ expect(SCREEN_PICKER_CONTAINER).toBe("ScreenPickerContainer");
82
+ });
83
+
84
+ it("should be a string", () => {
85
+ expect(typeof SCREEN_PICKER_CONTAINER).toBe("string");
86
+ });
87
+ });
88
+
89
+ describe("getScreenPickerId", () => {
90
+ it("should generate screen picker ID with container prefix", () => {
91
+ expect(getScreenPickerId("screen1")).toBe(
92
+ "ScreenPickerContainer.screen1"
93
+ );
94
+ });
95
+
96
+ it("should use SCREEN_PICKER_CONTAINER constant", () => {
97
+ const id = "test";
98
+ expect(getScreenPickerId(id)).toBe(`${SCREEN_PICKER_CONTAINER}.${id}`);
99
+ });
100
+
101
+ it("should handle numeric IDs", () => {
102
+ expect(getScreenPickerId(789)).toBe("ScreenPickerContainer.789");
103
+ });
104
+
105
+ it("should handle string IDs with special characters", () => {
106
+ expect(getScreenPickerId("screen-123_test")).toBe(
107
+ "ScreenPickerContainer.screen-123_test"
108
+ );
109
+ });
110
+
111
+ it("should handle empty string ID", () => {
112
+ expect(getScreenPickerId("")).toBe("ScreenPickerContainer.");
113
+ });
114
+
115
+ it("should handle IDs with spaces", () => {
116
+ expect(getScreenPickerId("my screen")).toBe(
117
+ "ScreenPickerContainer.my screen"
118
+ );
119
+ });
120
+
121
+ it("should handle null ID", () => {
122
+ expect(getScreenPickerId(null)).toBe("ScreenPickerContainer.null");
123
+ });
124
+
125
+ it("should handle undefined ID", () => {
126
+ expect(getScreenPickerId(undefined)).toBe(
127
+ "ScreenPickerContainer.undefined"
128
+ );
129
+ });
130
+ });
131
+
132
+ describe("getScreenPickerSelectorContainerId", () => {
133
+ it("should generate selector container ID with suffix", () => {
134
+ expect(getScreenPickerSelectorContainerId("screen1")).toBe(
135
+ "ScreenPickerContainer.screen1-screen-selector"
136
+ );
137
+ });
138
+
139
+ it("should build on top of getScreenPickerId", () => {
140
+ const id = "test";
141
+ const baseId = getScreenPickerId(id);
142
+
143
+ expect(getScreenPickerSelectorContainerId(id)).toBe(
144
+ `${baseId}-screen-selector`
145
+ );
146
+ });
147
+
148
+ it("should handle numeric IDs", () => {
149
+ expect(getScreenPickerSelectorContainerId(123)).toBe(
150
+ "ScreenPickerContainer.123-screen-selector"
151
+ );
152
+ });
153
+
154
+ it("should handle string IDs with special characters", () => {
155
+ expect(getScreenPickerSelectorContainerId("screen-abc_xyz")).toBe(
156
+ "ScreenPickerContainer.screen-abc_xyz-screen-selector"
157
+ );
158
+ });
159
+
160
+ it("should handle empty string ID", () => {
161
+ expect(getScreenPickerSelectorContainerId("")).toBe(
162
+ "ScreenPickerContainer.-screen-selector"
163
+ );
164
+ });
165
+
166
+ it("should handle IDs with spaces", () => {
167
+ expect(getScreenPickerSelectorContainerId("my screen")).toBe(
168
+ "ScreenPickerContainer.my screen-screen-selector"
169
+ );
170
+ });
171
+
172
+ it("should handle null ID", () => {
173
+ expect(getScreenPickerSelectorContainerId(null)).toBe(
174
+ "ScreenPickerContainer.null-screen-selector"
175
+ );
176
+ });
177
+
178
+ it("should handle undefined ID", () => {
179
+ expect(getScreenPickerSelectorContainerId(undefined)).toBe(
180
+ "ScreenPickerContainer.undefined-screen-selector"
181
+ );
182
+ });
183
+ });
184
+
185
+ describe("getScreenPickerContentContainerId", () => {
186
+ it("should generate content container ID with suffix", () => {
187
+ expect(getScreenPickerContentContainerId("screen1")).toBe(
188
+ "ScreenPickerContainer.screen1-screen-container"
189
+ );
190
+ });
191
+
192
+ it("should build on top of getScreenPickerId", () => {
193
+ const id = "test";
194
+ const baseId = getScreenPickerId(id);
195
+
196
+ expect(getScreenPickerContentContainerId(id)).toBe(
197
+ `${baseId}-screen-container`
198
+ );
199
+ });
200
+
201
+ it("should handle numeric IDs", () => {
202
+ expect(getScreenPickerContentContainerId(456)).toBe(
203
+ "ScreenPickerContainer.456-screen-container"
204
+ );
205
+ });
206
+
207
+ it("should handle string IDs with special characters", () => {
208
+ expect(getScreenPickerContentContainerId("content-abc_xyz")).toBe(
209
+ "ScreenPickerContainer.content-abc_xyz-screen-container"
210
+ );
211
+ });
212
+
213
+ it("should handle empty string ID", () => {
214
+ expect(getScreenPickerContentContainerId("")).toBe(
215
+ "ScreenPickerContainer.-screen-container"
216
+ );
217
+ });
218
+
219
+ it("should handle IDs with spaces", () => {
220
+ expect(getScreenPickerContentContainerId("my content")).toBe(
221
+ "ScreenPickerContainer.my content-screen-container"
222
+ );
223
+ });
224
+
225
+ it("should handle null ID", () => {
226
+ expect(getScreenPickerContentContainerId(null)).toBe(
227
+ "ScreenPickerContainer.null-screen-container"
228
+ );
229
+ });
230
+
231
+ it("should handle undefined ID", () => {
232
+ expect(getScreenPickerContentContainerId(undefined)).toBe(
233
+ "ScreenPickerContainer.undefined-screen-container"
234
+ );
235
+ });
236
+ });
237
+
238
+ describe("ID relationships", () => {
239
+ it("should generate different IDs for different functions", () => {
240
+ const id = "test";
241
+
242
+ const focusableId = getFocusableId(id);
243
+ const selectorId = getPickerSelectorId(id);
244
+ const screenPickerId = getScreenPickerId(id);
245
+
246
+ expect(focusableId).not.toBe(selectorId);
247
+ expect(focusableId).not.toBe(screenPickerId);
248
+ expect(selectorId).not.toBe(screenPickerId);
249
+ });
250
+
251
+ it("should have consistent prefixes across calls", () => {
252
+ expect(getFocusableId("a")).toContain("PickerItem.");
253
+ expect(getFocusableId("b")).toContain("PickerItem.");
254
+ expect(getPickerSelectorId("a")).toContain("PickerSelector.");
255
+ expect(getPickerSelectorId("b")).toContain("PickerSelector.");
256
+ expect(getScreenPickerId("a")).toContain("ScreenPickerContainer.");
257
+ expect(getScreenPickerId("b")).toContain("ScreenPickerContainer.");
258
+ });
259
+
260
+ it("should generate selector and content IDs from same base", () => {
261
+ const id = "common";
262
+ const baseId = getScreenPickerId(id);
263
+ const selectorId = getScreenPickerSelectorContainerId(id);
264
+ const contentId = getScreenPickerContentContainerId(id);
265
+
266
+ expect(selectorId).toContain(baseId);
267
+ expect(contentId).toContain(baseId);
268
+ expect(selectorId).not.toBe(contentId);
269
+ });
270
+
271
+ it("should distinguish between selector and content containers", () => {
272
+ const id = "test";
273
+
274
+ const selectorId = getScreenPickerSelectorContainerId(id);
275
+ const contentId = getScreenPickerContentContainerId(id);
276
+
277
+ expect(selectorId).toContain("-screen-selector");
278
+ expect(contentId).toContain("-screen-container");
279
+ expect(selectorId).not.toBe(contentId);
280
+ });
281
+ });
282
+
283
+ describe("special characters and edge cases", () => {
284
+ it("should handle IDs with slashes", () => {
285
+ const id = "path/to/screen";
286
+
287
+ expect(getFocusableId(id)).toBe("PickerItem.path/to/screen");
288
+ expect(getPickerSelectorId(id)).toBe("PickerSelector.path/to/screen");
289
+
290
+ expect(getScreenPickerId(id)).toBe(
291
+ "ScreenPickerContainer.path/to/screen"
292
+ );
293
+ });
294
+
295
+ it("should handle IDs with Unicode characters", () => {
296
+ const id = "écran-日本語";
297
+
298
+ expect(getFocusableId(id)).toBe("PickerItem.écran-日本語");
299
+ expect(getPickerSelectorId(id)).toBe("PickerSelector.écran-日本語");
300
+ expect(getScreenPickerId(id)).toBe("ScreenPickerContainer.écran-日本語");
301
+ });
302
+
303
+ it("should handle very long IDs", () => {
304
+ const id = "a".repeat(1000);
305
+
306
+ expect(getFocusableId(id)).toContain("PickerItem.");
307
+ expect(getPickerSelectorId(id)).toContain("PickerSelector.");
308
+ expect(getScreenPickerId(id)).toContain("ScreenPickerContainer.");
309
+ });
310
+
311
+ it("should handle IDs with equals signs", () => {
312
+ const id = "key=value";
313
+
314
+ expect(getFocusableId(id)).toBe("PickerItem.key=value");
315
+ expect(getPickerSelectorId(id)).toBe("PickerSelector.key=value");
316
+ expect(getScreenPickerId(id)).toBe("ScreenPickerContainer.key=value");
317
+ });
318
+
319
+ it("should handle boolean values", () => {
320
+ expect(getFocusableId(true)).toBe("PickerItem.true");
321
+ expect(getPickerSelectorId(false)).toBe("PickerSelector.false");
322
+ expect(getScreenPickerId(true)).toBe("ScreenPickerContainer.true");
323
+ });
324
+
325
+ it("should handle object IDs", () => {
326
+ const obj = { toString: () => "customId" };
327
+
328
+ expect(getFocusableId(obj)).toContain("PickerItem.");
329
+ expect(getPickerSelectorId(obj)).toContain("PickerSelector.");
330
+ expect(getScreenPickerId(obj)).toContain("ScreenPickerContainer.");
331
+ });
332
+ });
333
+ });
@@ -0,0 +1,156 @@
1
+ const mockBackgroundTimerModule = {
2
+ setTimeout: jest.fn(),
3
+ clearTimeout: jest.fn(),
4
+ };
5
+
6
+ jest.mock("../../reactUtils", () => ({
7
+ platformSelect: jest.fn((options) => options.android || options.default),
8
+ }));
9
+
10
+ describe("BackgroundTimer", () => {
11
+ let BackgroundTimer: any;
12
+
13
+ const setupTimer = (withNativeModule = true) => {
14
+ jest.resetModules();
15
+
16
+ jest.doMock("react-native", () => ({
17
+ NativeModules: {
18
+ BackgroundTimer: withNativeModule
19
+ ? mockBackgroundTimerModule
20
+ : undefined,
21
+ },
22
+ DeviceEventEmitter: {
23
+ addListener: jest.fn(),
24
+ removeListener: jest.fn(),
25
+ },
26
+ }));
27
+
28
+ BackgroundTimer = require("../BackgroundTimer").default;
29
+ };
30
+
31
+ beforeEach(() => {
32
+ jest.clearAllMocks();
33
+ });
34
+
35
+ describe("Native Module Environment", () => {
36
+ beforeEach(() => {
37
+ setupTimer(true);
38
+ });
39
+
40
+ it("should initialize device event listener on startup", () => {
41
+ const { DeviceEventEmitter } = require("react-native");
42
+
43
+ expect(DeviceEventEmitter.addListener).toHaveBeenCalledWith(
44
+ "BackgroundTimer.timer.fired",
45
+ expect.any(Function)
46
+ );
47
+ });
48
+
49
+ it("should delegate setTimeout to native module and return a unique ID", () => {
50
+ const callback1 = jest.fn();
51
+ const callback2 = jest.fn();
52
+ const timeout = 1000;
53
+
54
+ const id1 = BackgroundTimer.setTimeout(callback1, timeout);
55
+ const id2 = BackgroundTimer.setTimeout(callback2, timeout);
56
+
57
+ expect(mockBackgroundTimerModule.setTimeout).toHaveBeenCalledWith(
58
+ id1,
59
+ timeout
60
+ );
61
+
62
+ expect(mockBackgroundTimerModule.setTimeout).toHaveBeenCalledWith(
63
+ id2,
64
+ timeout
65
+ );
66
+
67
+ expect(id1).not.toBe(id2);
68
+ expect(typeof id1).toBe("number");
69
+ });
70
+
71
+ it("should delegate clearTimeout to native module", () => {
72
+ const id = BackgroundTimer.setTimeout(jest.fn(), 1000);
73
+ BackgroundTimer.clearTimeout(id);
74
+
75
+ expect(mockBackgroundTimerModule.clearTimeout).toHaveBeenCalledWith(id);
76
+ });
77
+
78
+ it("should execute callback when native timer fires", () => {
79
+ const { DeviceEventEmitter } = require("react-native");
80
+ const callback = jest.fn();
81
+
82
+ const id = BackgroundTimer.setTimeout(callback, 1000);
83
+
84
+ const listener = DeviceEventEmitter.addListener.mock.calls[0][1];
85
+
86
+ listener(id);
87
+
88
+ expect(callback).toHaveBeenCalledTimes(1);
89
+ });
90
+
91
+ it("should cleanup callback after execution (prevent double firing)", () => {
92
+ const { DeviceEventEmitter } = require("react-native");
93
+ const callback = jest.fn();
94
+
95
+ const id = BackgroundTimer.setTimeout(callback, 1000);
96
+ const listener = DeviceEventEmitter.addListener.mock.calls[0][1];
97
+
98
+ listener(id);
99
+ listener(id);
100
+
101
+ expect(callback).toHaveBeenCalledTimes(1);
102
+ });
103
+
104
+ it("should not execute callback if cleared before firing", () => {
105
+ const { DeviceEventEmitter } = require("react-native");
106
+ const callback = jest.fn();
107
+
108
+ const id = BackgroundTimer.setTimeout(callback, 1000);
109
+ BackgroundTimer.clearTimeout(id);
110
+
111
+ const listener = DeviceEventEmitter.addListener.mock.calls[0][1];
112
+ listener(id);
113
+
114
+ expect(callback).not.toHaveBeenCalled();
115
+ });
116
+
117
+ it("should ignore events for unknown/invalid IDs", () => {
118
+ const { DeviceEventEmitter } = require("react-native");
119
+ const callback = jest.fn();
120
+
121
+ BackgroundTimer.setTimeout(callback, 1000);
122
+
123
+ const listener = DeviceEventEmitter.addListener.mock.calls[0][1];
124
+ listener(99999);
125
+
126
+ expect(callback).not.toHaveBeenCalled();
127
+ });
128
+ });
129
+
130
+ describe("Fallback Environment (No Native Module)", () => {
131
+ beforeEach(() => {
132
+ setupTimer(false);
133
+ });
134
+
135
+ it("should use global setTimeout when native module is missing", () => {
136
+ const callback = jest.fn();
137
+ const timeout = 500;
138
+
139
+ const globalSpy = jest.spyOn(global, "setTimeout");
140
+
141
+ BackgroundTimer.setTimeout(callback, timeout);
142
+
143
+ expect(globalSpy).toHaveBeenCalledWith(callback, timeout);
144
+ });
145
+
146
+ it("should use global clearTimeout when native module is missing", () => {
147
+ const callback = jest.fn();
148
+ const globalSpy = jest.spyOn(global, "clearTimeout");
149
+
150
+ const id = BackgroundTimer.setTimeout(callback, 500);
151
+ BackgroundTimer.clearTimeout(id);
152
+
153
+ expect(globalSpy).toHaveBeenCalledWith(id);
154
+ });
155
+ });
156
+ });
@@ -0,0 +1,236 @@
1
+ import { Timer } from "../Timer";
2
+ import BackgroundTimer from "../BackgroundTimer";
3
+
4
+ // Mock BackgroundTimer
5
+ jest.mock("../BackgroundTimer", () => ({
6
+ __esModule: true,
7
+ default: {
8
+ setTimeout: jest.fn((callback, delay) => {
9
+ return setTimeout(callback, delay);
10
+ }),
11
+ clearTimeout: jest.fn((id) => {
12
+ clearTimeout(id as NodeJS.Timeout);
13
+ }),
14
+ },
15
+ }));
16
+
17
+ describe("Timer", () => {
18
+ let mockCallback: jest.Mock;
19
+
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+ jest.useFakeTimers();
23
+ mockCallback = jest.fn();
24
+ });
25
+
26
+ afterEach(() => {
27
+ jest.useRealTimers();
28
+ });
29
+
30
+ describe("constructor", () => {
31
+ it("should create timer with callback and remaining time", () => {
32
+ const timer = new Timer(mockCallback, 1000);
33
+
34
+ expect(timer).toBeInstanceOf(Timer);
35
+ });
36
+
37
+ it("should create timer with callback only (optional remaining param)", () => {
38
+ const timer = new Timer(mockCallback);
39
+
40
+ expect(timer).toBeInstanceOf(Timer);
41
+ });
42
+
43
+ it("should not start automatically", () => {
44
+ // eslint-disable-next-line no-new
45
+ new Timer(mockCallback, 1000);
46
+
47
+ expect(BackgroundTimer.setTimeout).not.toHaveBeenCalled();
48
+ });
49
+ });
50
+
51
+ describe("start", () => {
52
+ it("should call setTimeout with callback and remaining time", () => {
53
+ const timer = new Timer(mockCallback, 1000);
54
+
55
+ timer.start();
56
+
57
+ expect(BackgroundTimer.setTimeout).toHaveBeenCalledWith(
58
+ mockCallback,
59
+ 1000
60
+ );
61
+ });
62
+
63
+ it("should reset remaining time to 0 after starting", () => {
64
+ const timer = new Timer(mockCallback, 5000);
65
+
66
+ timer.start();
67
+ timer.clear();
68
+ timer.start();
69
+
70
+ expect(BackgroundTimer.setTimeout).toHaveBeenCalledTimes(2);
71
+ });
72
+ });
73
+
74
+ describe("pause", () => {
75
+ it("should clear timeout and calculate remaining time correctly", () => {
76
+ const startTime = 1000;
77
+ const pauseTime = 1500;
78
+
79
+ jest
80
+ .spyOn(Date, "now")
81
+ .mockReturnValueOnce(startTime) // start
82
+ .mockReturnValueOnce(pauseTime); // pause
83
+
84
+ const timer = new Timer(mockCallback, 2000);
85
+
86
+ timer.start();
87
+ timer.pause();
88
+
89
+ expect(BackgroundTimer.clearTimeout).toHaveBeenCalled();
90
+ });
91
+
92
+ it("should be safe to call pause multiple times", () => {
93
+ const timer = new Timer(mockCallback, 1000);
94
+
95
+ timer.start();
96
+ timer.pause();
97
+ timer.pause();
98
+
99
+ expect(BackgroundTimer.clearTimeout).toHaveBeenCalledTimes(2);
100
+ });
101
+ });
102
+
103
+ describe("resume", () => {
104
+ it("should restart timer with remaining time after pause", () => {
105
+ const startTime = 1000;
106
+ const pauseTime = 1500;
107
+
108
+ jest
109
+ .spyOn(Date, "now")
110
+ .mockReturnValueOnce(startTime)
111
+ .mockReturnValueOnce(pauseTime);
112
+
113
+ const timer = new Timer(mockCallback, 2000);
114
+
115
+ timer.start();
116
+ timer.pause();
117
+ timer.resume();
118
+
119
+ expect(BackgroundTimer.setTimeout).toHaveBeenCalledTimes(2);
120
+ });
121
+
122
+ it("should be callable without prior pause", () => {
123
+ const timer = new Timer(mockCallback, 1000);
124
+
125
+ timer.resume();
126
+
127
+ expect(BackgroundTimer.setTimeout).toHaveBeenCalled();
128
+ });
129
+ });
130
+
131
+ describe("clear", () => {
132
+ it("should clear timeout and be safe to call without starting timer", () => {
133
+ const timer = new Timer(mockCallback, 1000);
134
+
135
+ expect(() => {
136
+ timer.clear();
137
+ }).not.toThrow();
138
+
139
+ expect(BackgroundTimer.clearTimeout).toHaveBeenCalled();
140
+ });
141
+
142
+ it("should be callable multiple times", () => {
143
+ const timer = new Timer(mockCallback, 1000);
144
+
145
+ timer.start();
146
+ timer.clear();
147
+ timer.clear();
148
+
149
+ expect(BackgroundTimer.clearTimeout).toHaveBeenCalledTimes(2);
150
+ });
151
+ });
152
+
153
+ describe("timer lifecycle", () => {
154
+ it("should support complete lifecycle: start -> pause -> resume -> clear", () => {
155
+ const timer = new Timer(mockCallback, 1000);
156
+
157
+ timer.start();
158
+ expect(BackgroundTimer.setTimeout).toHaveBeenCalledTimes(1);
159
+
160
+ timer.pause();
161
+ expect(BackgroundTimer.clearTimeout).toHaveBeenCalledTimes(1);
162
+
163
+ timer.resume();
164
+ expect(BackgroundTimer.setTimeout).toHaveBeenCalledTimes(2);
165
+
166
+ timer.clear();
167
+ expect(BackgroundTimer.clearTimeout).toHaveBeenCalledTimes(2);
168
+ });
169
+ });
170
+
171
+ describe("edge cases", () => {
172
+ it("should handle zero delay", () => {
173
+ const timer = new Timer(mockCallback, 0);
174
+
175
+ timer.start();
176
+
177
+ expect(BackgroundTimer.setTimeout).toHaveBeenCalledWith(mockCallback, 0);
178
+ });
179
+
180
+ it("should handle very long delays", () => {
181
+ const longDelay = Number.MAX_SAFE_INTEGER;
182
+ const timer = new Timer(mockCallback, longDelay);
183
+
184
+ timer.start();
185
+
186
+ expect(BackgroundTimer.setTimeout).toHaveBeenCalledWith(
187
+ mockCallback,
188
+ longDelay
189
+ );
190
+ });
191
+
192
+ it("should handle negative delays gracefully", () => {
193
+ const timer = new Timer(mockCallback, -1000);
194
+
195
+ expect(() => {
196
+ timer.start();
197
+ }).not.toThrow();
198
+ });
199
+
200
+ it("should maintain callback reference through lifecycle", () => {
201
+ const specificCallback = jest.fn();
202
+ const timer = new Timer(specificCallback, 1000);
203
+
204
+ timer.start();
205
+
206
+ expect(BackgroundTimer.setTimeout).toHaveBeenCalledWith(
207
+ specificCallback,
208
+ 1000
209
+ );
210
+ });
211
+ });
212
+
213
+ describe("callback execution safety", () => {
214
+ it("should not execute callback immediately on start", () => {
215
+ const timer = new Timer(mockCallback, 1000);
216
+
217
+ timer.start();
218
+
219
+ expect(mockCallback).not.toHaveBeenCalled();
220
+ });
221
+
222
+ it("should not execute callback when paused or cleared", () => {
223
+ const timer1 = new Timer(mockCallback, 100);
224
+ const timer2 = new Timer(mockCallback, 100);
225
+
226
+ timer1.start();
227
+ timer1.pause();
228
+ timer2.start();
229
+ timer2.clear();
230
+
231
+ jest.advanceTimersByTime(200);
232
+
233
+ expect(mockCallback).not.toHaveBeenCalled();
234
+ });
235
+ });
236
+ });
@@ -0,0 +1,21 @@
1
+ import { isString } from "../index";
2
+
3
+ describe("isString", () => {
4
+ it("should return true for string primitives", () => {
5
+ expect(isString("hello")).toBe(true);
6
+ expect(isString("")).toBe(true);
7
+ });
8
+
9
+ it("should return true for String object instances", () => {
10
+ // eslint-disable-next-line no-new-wrappers
11
+ expect(isString(new String("test"))).toBe(true);
12
+ });
13
+
14
+ it("should return false for non-string values", () => {
15
+ expect(isString(123)).toBe(false);
16
+ expect(isString(null)).toBe(false);
17
+ expect(isString(undefined)).toBe(false);
18
+ expect(isString({})).toBe(false);
19
+ expect(isString([])).toBe(false);
20
+ });
21
+ });
@@ -1,3 +1,7 @@
1
1
  export const isString = (value: unknown) => {
2
+ if (typeof value === "object" && value instanceof String) {
3
+ return true;
4
+ }
5
+
2
6
  return typeof value === "string";
3
7
  };