@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 +2 -2
- package/rectUtils/__tests__/index.test.ts +549 -0
- package/rectUtils/index.ts +2 -2
- package/screenPickerUtils/__tests__/index.test.ts +333 -0
- package/time/__tests__/BackgroundTimer.test.ts +156 -0
- package/time/__tests__/Timer.test.ts +236 -0
- package/typeGuards/__tests__/isString.test.ts +21 -0
- package/typeGuards/index.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "15.0.0-rc.
|
|
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.
|
|
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
|
+
});
|
package/rectUtils/index.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export function measureDimensionalIntersection(direction, current, target) {
|
|
11
11
|
if (direction.isHorizontal) {
|
|
12
|
-
return
|
|
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
|
|
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
|
+
});
|