@applicaster/zapp-react-native-utils 12.0.0-alpha.4802689107 → 12.0.0-alpha.4837064762
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/appUtils/focusManager/index.ts +127 -24
- package/appUtils/focusManager/treeDataStructure/Utils/index.js +97 -42
- package/appUtils/playerManager/player.ts +1 -0
- package/appUtils/playerManager/playerNative.ts +5 -0
- package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +2 -1
- package/package.json +2 -2
- package/playerUtils/configurationGenerator.ts +3 -1
- package/reactHooks/autoscrolling/__tests__/{useTrackedView.test.ts → useTrackedView.test.tsx} +16 -1
- package/reactHooks/layout/__tests__/{index.test.js → index.test.tsx} +43 -29
- package/reactHooks/layout/getDeviceInfo/index.ts +4 -2
- package/reactHooks/layout/types/index.ts +1 -0
- package/reactHooks/layout/useDimensions/useDimensions.ts +5 -2
- package/reactHooks/navigation/__mocks__/index.ts +13 -0
- package/reactHooks/navigation/__mocks__/useNavigation.ts +3 -0
- package/reactHooks/navigation/index.ts +20 -0
- package/reactHooks/screen/useScreenContext.ts +4 -3
- package/reactUtils/helpers.tsx +1 -1
- package/screenState/index.ts +0 -8
- package/testUtils/getByTestId.ts +14 -0
- package/testUtils/index.tsx +24 -22
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
+
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
3
|
+
|
|
2
4
|
import { Tree } from "./treeDataStructure/Tree";
|
|
3
5
|
import { findFocusableNode, findPriorityItem } from "./treeDataStructure/Utils";
|
|
4
6
|
import { subscriber } from "../../functionUtils";
|
|
@@ -17,6 +19,22 @@ const isFocusEnabled = (focusableItem): boolean => {
|
|
|
17
19
|
return true;
|
|
18
20
|
};
|
|
19
21
|
|
|
22
|
+
const waitUntil = (conditionFn) => {
|
|
23
|
+
let interval;
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
interval = setInterval(() => {
|
|
26
|
+
if (conditionFn()) {
|
|
27
|
+
clearInterval(interval);
|
|
28
|
+
resolve(undefined);
|
|
29
|
+
}
|
|
30
|
+
}, 1000);
|
|
31
|
+
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
reject();
|
|
34
|
+
}, 2500);
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
20
38
|
/**
|
|
21
39
|
* The focusManager is meant to keep references of focusables elements in the UI, and provide
|
|
22
40
|
* ways to assign the focus to one of the focusables.
|
|
@@ -46,7 +64,7 @@ export const focusManager = (function () {
|
|
|
46
64
|
let lastFocusedId = null;
|
|
47
65
|
let initialFocusOnContent = null;
|
|
48
66
|
let focusDisabled = false;
|
|
49
|
-
let focusAutoEnabled =
|
|
67
|
+
let focusAutoEnabled = false;
|
|
50
68
|
const eventHandler = subscriber();
|
|
51
69
|
|
|
52
70
|
function disableAutomaticFocus() {
|
|
@@ -93,7 +111,7 @@ export const focusManager = (function () {
|
|
|
93
111
|
!currentFocusable.isGroup &&
|
|
94
112
|
currentFocusable.isMounted()
|
|
95
113
|
) {
|
|
96
|
-
currentFocusable.blur(
|
|
114
|
+
currentFocusable.blur(direction);
|
|
97
115
|
currentFocusable.hasLostFocus(getCurrentFocus(), direction);
|
|
98
116
|
}
|
|
99
117
|
}
|
|
@@ -226,31 +244,41 @@ export const focusManager = (function () {
|
|
|
226
244
|
|
|
227
245
|
logger.log({ message: "setInitialFocus", data: { preferredFocus } });
|
|
228
246
|
|
|
229
|
-
if (preferredFocus
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
!focusableItem.isGroup &&
|
|
236
|
-
isFocusEnabled(focusableItem)
|
|
237
|
-
) {
|
|
238
|
-
if (initialFocusOnContent) {
|
|
239
|
-
blur();
|
|
240
|
-
initialFocusOnContent = false;
|
|
241
|
-
}
|
|
247
|
+
if (isNilOrEmpty(preferredFocus)) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const focusCandidate = preferredFocus[0];
|
|
252
|
+
const focusableItem = focusCandidate.component;
|
|
242
253
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
data: {
|
|
246
|
-
focusableItem,
|
|
247
|
-
},
|
|
248
|
-
});
|
|
254
|
+
const currentFocusedId = R.path(["props", "id"], getCurrentFocus());
|
|
255
|
+
const nextFocusedId = R.path(["props", "id"], focusableItem);
|
|
249
256
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
257
|
+
if (currentFocusedId === nextFocusedId) {
|
|
258
|
+
// we are going to move focus to the same node(already focused) -> nothing to do
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (
|
|
263
|
+
focusableItem &&
|
|
264
|
+
!focusableItem.isGroup &&
|
|
265
|
+
isFocusEnabled(focusableItem)
|
|
266
|
+
) {
|
|
267
|
+
if (initialFocusOnContent) {
|
|
268
|
+
blur();
|
|
269
|
+
initialFocusOnContent = false;
|
|
253
270
|
}
|
|
271
|
+
|
|
272
|
+
logger.log({
|
|
273
|
+
message: "setInitialFocus – currentFocusNode",
|
|
274
|
+
data: {
|
|
275
|
+
focusableItem,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
currentFocusNode = focusCandidate;
|
|
280
|
+
focusableItem.willReceiveFocus(focusableItem, null);
|
|
281
|
+
focusableItem && setFocus(focusCandidate.id, null);
|
|
254
282
|
}
|
|
255
283
|
}
|
|
256
284
|
|
|
@@ -323,6 +351,8 @@ export const focusManager = (function () {
|
|
|
323
351
|
* @returns {React.Component} the registered component
|
|
324
352
|
*/
|
|
325
353
|
function registerGroup(id, component) {
|
|
354
|
+
// console.log("debug_2", "FM registerGroup", { id, component });
|
|
355
|
+
|
|
326
356
|
focusableTree.addNode({ id, component });
|
|
327
357
|
|
|
328
358
|
return component;
|
|
@@ -338,6 +368,8 @@ export const focusManager = (function () {
|
|
|
338
368
|
* @returns {React.Component} the registered component
|
|
339
369
|
*/
|
|
340
370
|
function registerItem(id, component) {
|
|
371
|
+
// console.log("debug_2", "FM registerItem", { id, component });
|
|
372
|
+
|
|
341
373
|
focusableTree.addNode({ id, component });
|
|
342
374
|
|
|
343
375
|
if (!currentFocusNode && id === lastFocusedId && !id.includes("menu")) {
|
|
@@ -354,6 +386,8 @@ export const focusManager = (function () {
|
|
|
354
386
|
* @param {String} id of the group to unregister
|
|
355
387
|
*/
|
|
356
388
|
function unregisterGroup(id) {
|
|
389
|
+
// console.log("debug_2", "FM unregisterGroup", { id });
|
|
390
|
+
|
|
357
391
|
focusableTree.findAndRemoveNode(id);
|
|
358
392
|
}
|
|
359
393
|
|
|
@@ -365,6 +399,8 @@ export const focusManager = (function () {
|
|
|
365
399
|
* @param {String} id of the Focusable to unregister
|
|
366
400
|
*/
|
|
367
401
|
function unregisterItem(id) {
|
|
402
|
+
// console.log("debug_2", "FM unregisterItem", { id });
|
|
403
|
+
|
|
368
404
|
focusableTree.findAndRemoveNode(id);
|
|
369
405
|
|
|
370
406
|
if (currentFocusNode && currentFocusNode.id === id) {
|
|
@@ -403,6 +439,8 @@ export const focusManager = (function () {
|
|
|
403
439
|
}
|
|
404
440
|
|
|
405
441
|
function setupInitialFocus(id: string, focusOnContent?: boolean) {
|
|
442
|
+
console.log("debug_2", "FM - setupInitialFocus", { id, focusOnContent });
|
|
443
|
+
|
|
406
444
|
if (focusOnContent) {
|
|
407
445
|
initialFocusOnContent = focusOnContent;
|
|
408
446
|
} else {
|
|
@@ -410,6 +448,62 @@ export const focusManager = (function () {
|
|
|
410
448
|
}
|
|
411
449
|
}
|
|
412
450
|
|
|
451
|
+
function setFocusOnMenu() {
|
|
452
|
+
const activeScreen = R.last(focusableTree.root.children);
|
|
453
|
+
const navbar = R.find(
|
|
454
|
+
(child) => R.startsWith("navbar", child.id),
|
|
455
|
+
activeScreen.children
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
const selectedMenuItemId = R.find(
|
|
459
|
+
(child) => child.component.props.selected,
|
|
460
|
+
navbar?.children || []
|
|
461
|
+
)?.id;
|
|
462
|
+
|
|
463
|
+
if (selectedMenuItemId) {
|
|
464
|
+
// set focus on selected menu item
|
|
465
|
+
setFocus(selectedMenuItemId);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const firstMenuItemId = R.path(["children", 0, "id"], navbar);
|
|
470
|
+
|
|
471
|
+
if (firstMenuItemId) {
|
|
472
|
+
// set focus on first menu item
|
|
473
|
+
setFocus(firstMenuItemId);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function setFocusOnContent() {
|
|
478
|
+
// we need to put focus on content here
|
|
479
|
+
// For doing it we have to wait registration of first focusable node into focusableTree
|
|
480
|
+
// this is the temporary hack with waiting until content node starts having children
|
|
481
|
+
const getContentNode = () => {
|
|
482
|
+
const activeScreen = R.last(focusableTree.root.children);
|
|
483
|
+
const content = R.find(
|
|
484
|
+
(child) => R.startsWith("content", child.id),
|
|
485
|
+
activeScreen.children
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
return content;
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
const contentHasAnyChildren = () => {
|
|
492
|
+
return getContentNode()?.children.length > 0;
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
waitUntil(contentHasAnyChildren)
|
|
496
|
+
.then(() => {
|
|
497
|
+
const content = getContentNode();
|
|
498
|
+
|
|
499
|
+
setInitialFocus(content);
|
|
500
|
+
})
|
|
501
|
+
.catch(() => {
|
|
502
|
+
// we failed to wait any content, put focus on menu as a last chance
|
|
503
|
+
setFocusOnMenu();
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
413
507
|
/**
|
|
414
508
|
* moves the focus in a provided direction
|
|
415
509
|
* @public
|
|
@@ -422,6 +516,13 @@ export const focusManager = (function () {
|
|
|
422
516
|
currentFocusNode.component.willLoseFocus(currentFocusNode, direction);
|
|
423
517
|
const nextFocusable = findFocusableNode(direction, currentFocusNode);
|
|
424
518
|
|
|
519
|
+
console.log("debug_2", "FM moveFocus", {
|
|
520
|
+
direction: direction?.value,
|
|
521
|
+
currentFocusNode,
|
|
522
|
+
focusableTree,
|
|
523
|
+
nextFocusable,
|
|
524
|
+
});
|
|
525
|
+
|
|
425
526
|
if (nextFocusable?.component?.isMounted?.()) {
|
|
426
527
|
const nextRect = nextFocusable.component.getRect();
|
|
427
528
|
|
|
@@ -518,5 +619,7 @@ export const focusManager = (function () {
|
|
|
518
619
|
enableAutomaticFocus,
|
|
519
620
|
getGroupRootById,
|
|
520
621
|
isGroupItemFocused,
|
|
622
|
+
setFocusOnMenu,
|
|
623
|
+
setFocusOnContent,
|
|
521
624
|
};
|
|
522
625
|
})();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { allPass, pathOr, startsWith, isNil } from "ramda";
|
|
2
2
|
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
3
3
|
|
|
4
4
|
import { rejectItemByPath } from "../../../../objectUtils";
|
|
@@ -143,7 +143,49 @@ const hasChildren = (target) =>
|
|
|
143
143
|
|
|
144
144
|
const hasComponent = (target) => !!target?.component;
|
|
145
145
|
|
|
146
|
-
const hasComponentAndChildren =
|
|
146
|
+
const hasComponentAndChildren = allPass([hasComponent, hasChildren]);
|
|
147
|
+
|
|
148
|
+
const positiveOrZero = (value) => value >= 0;
|
|
149
|
+
|
|
150
|
+
const excludeFromFocusSearching = pathOr(false, [
|
|
151
|
+
"component",
|
|
152
|
+
"props",
|
|
153
|
+
"excludeFromFocusSearching",
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
// const isAllScreens = all(excludeFromFocusSearching);
|
|
157
|
+
|
|
158
|
+
const isNavBar = (target) => startsWith("navbar", target?.id);
|
|
159
|
+
|
|
160
|
+
const ignoreMenuIfMultipleTargets = (nodes) => {
|
|
161
|
+
if (nodes.length > 1 && nodes.map((n) => n?.target?.id).includes("navbar")) {
|
|
162
|
+
return nodes.filter((n) => !isNavBar(n?.target));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return nodes;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const toDistance = ({ target, direction, comparisonRect }) => {
|
|
169
|
+
const componentRect = target.component?.getRect();
|
|
170
|
+
|
|
171
|
+
const orientationDistance = measureDistance(
|
|
172
|
+
direction,
|
|
173
|
+
comparisonRect,
|
|
174
|
+
componentRect
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (orientationDistance === -1) {
|
|
178
|
+
// ignores offscreen targets when moving up/down
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const euclidDistance = measureEuclidianDistanceToCenter(
|
|
183
|
+
comparisonRect,
|
|
184
|
+
componentRect
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
return orientationDistance > 0 ? euclidDistance : orientationDistance;
|
|
188
|
+
};
|
|
147
189
|
|
|
148
190
|
/**
|
|
149
191
|
* Get target in shortest distance in specified direction
|
|
@@ -173,52 +215,32 @@ export function getTargetInDirection(
|
|
|
173
215
|
|
|
174
216
|
const distances = targets
|
|
175
217
|
.filter(hasComponentAndChildren)
|
|
176
|
-
.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
componentRect
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
if (distance === -1) {
|
|
186
|
-
// ignores offscreen targets when moving up/down
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
218
|
+
.filter((target) => !excludeFromFocusSearching(target))
|
|
219
|
+
.map((target) => ({
|
|
220
|
+
distance: toDistance({ target, direction, comparisonRect }),
|
|
221
|
+
target,
|
|
222
|
+
}))
|
|
223
|
+
.filter(({ distance }) => positiveOrZero(distance));
|
|
189
224
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
componentRect
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
const euclidDistance = measureEuclidianDistanceToCenter(
|
|
197
|
-
comparisonRect,
|
|
198
|
-
componentRect
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
return {
|
|
202
|
-
distance:
|
|
203
|
-
orientationDistance > 0 ? euclidDistance : orientationDistance,
|
|
204
|
-
target,
|
|
205
|
-
};
|
|
206
|
-
})
|
|
207
|
-
.filter((obj) => obj && obj.distance >= 0);
|
|
225
|
+
// if (isAllScreens(targets)) {
|
|
226
|
+
// return last(targets);
|
|
227
|
+
// }
|
|
208
228
|
|
|
209
229
|
const match = distances
|
|
210
230
|
.sort((a, b) => a.distance - b.distance) // sorts the distances
|
|
211
231
|
.filter(ignoreMenuIfMultipleTargets)[0]; // filters out multiple targets if necessary // gets the first element
|
|
212
232
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
233
|
+
// console.log("debug_2", "FM getTargetInDirection", {
|
|
234
|
+
// direction: direction?.value,
|
|
235
|
+
// currentNode,
|
|
236
|
+
// targetNodes,
|
|
237
|
+
// targets,
|
|
238
|
+
// comparisonRect,
|
|
239
|
+
// distances,
|
|
240
|
+
// match,
|
|
241
|
+
// });
|
|
242
|
+
|
|
243
|
+
return match?.target;
|
|
222
244
|
}
|
|
223
245
|
|
|
224
246
|
/**
|
|
@@ -309,3 +331,36 @@ export function hasPriorityInDirection(direction, component) {
|
|
|
309
331
|
|
|
310
332
|
return (prioritiseFocusOn & mappedDirection) > 0;
|
|
311
333
|
}
|
|
334
|
+
|
|
335
|
+
export const getNodeBeforeRoot = (node) => {
|
|
336
|
+
if (isNil(node)) {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (node.parent.id === "root") {
|
|
341
|
+
return node;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return getNodeBeforeRoot(node.parent);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export const isAllowedToMoveTo = (currentNode, nextNode) => {
|
|
348
|
+
const screenForCurrentNode = getNodeBeforeRoot(currentNode);
|
|
349
|
+
const screenForNextNode = getNodeBeforeRoot(nextNode);
|
|
350
|
+
|
|
351
|
+
return screenForCurrentNode.id === screenForNextNode.id;
|
|
352
|
+
|
|
353
|
+
// console.log("debug_2", "isAllowedToMoveTo", {
|
|
354
|
+
// nodeBeforeRoot,
|
|
355
|
+
// });
|
|
356
|
+
|
|
357
|
+
// if (nodeBeforeRoot?.id === "navbar") {
|
|
358
|
+
// return true;
|
|
359
|
+
// }
|
|
360
|
+
|
|
361
|
+
// if (nodeBeforeRoot?.component?.props?.isActive) {
|
|
362
|
+
// return true;
|
|
363
|
+
// }
|
|
364
|
+
|
|
365
|
+
// return false;
|
|
366
|
+
};
|
|
@@ -295,4 +295,9 @@ export class PlayerNative extends Player {
|
|
|
295
295
|
|
|
296
296
|
supportsNativeControls = (): boolean =>
|
|
297
297
|
toBooleanWithDefaultFalse(this.getConfig()?.["supports_native_controls"]);
|
|
298
|
+
|
|
299
|
+
supportNativeCast = (): boolean =>
|
|
300
|
+
toBooleanWithDefaultFalse(
|
|
301
|
+
this.playerPluginId === "quick-brick-player-brightcove"
|
|
302
|
+
);
|
|
298
303
|
}
|
|
@@ -1663,7 +1663,8 @@ function getPlayerConfiguration({ platform }) {
|
|
|
1663
1663
|
platform.includes("lg") ||
|
|
1664
1664
|
platform.includes("samsung") ||
|
|
1665
1665
|
platform.includes("android_tv") ||
|
|
1666
|
-
platform.includes("amazon")
|
|
1666
|
+
platform.includes("amazon") ||
|
|
1667
|
+
platform.includes("vizio")
|
|
1667
1668
|
) {
|
|
1668
1669
|
styles.fields.push(
|
|
1669
1670
|
// Captions Menu
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "12.0.0-alpha.
|
|
3
|
+
"version": "12.0.0-alpha.4837064762",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@applicaster/applicaster-types": "12.0.0-alpha.
|
|
31
|
+
"@applicaster/applicaster-types": "12.0.0-alpha.4837064762",
|
|
32
32
|
"buffer": "^5.2.1",
|
|
33
33
|
"camelize": "^1.0.0",
|
|
34
34
|
"create-react-context": "^0.2.3",
|
|
@@ -22,6 +22,7 @@ function getDevice(platform) {
|
|
|
22
22
|
case "android_tv_for_quickbrick":
|
|
23
23
|
case "lg_tv":
|
|
24
24
|
case "samsung_tv":
|
|
25
|
+
case "vizio":
|
|
25
26
|
case "web":
|
|
26
27
|
return "tv";
|
|
27
28
|
}
|
|
@@ -1659,7 +1660,8 @@ function getPlayerConfiguration({ platform }) {
|
|
|
1659
1660
|
platform.includes("lg") ||
|
|
1660
1661
|
platform.includes("samsung") ||
|
|
1661
1662
|
platform.includes("android_tv") ||
|
|
1662
|
-
platform.includes("amazon")
|
|
1663
|
+
platform.includes("amazon") ||
|
|
1664
|
+
platform.includes("vizio")
|
|
1663
1665
|
) {
|
|
1664
1666
|
styles.fields.push(
|
|
1665
1667
|
// Captions Menu for TV
|
package/reactHooks/autoscrolling/__tests__/{useTrackedView.test.ts → useTrackedView.test.tsx}
RENAMED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
1
3
|
import { act, renderHook } from "@testing-library/react-hooks";
|
|
4
|
+
import { Provider } from "react-redux";
|
|
5
|
+
import configureStore from "redux-mock-store";
|
|
2
6
|
|
|
3
7
|
const mockUpdateComponentsPositions = jest.fn();
|
|
4
8
|
|
|
@@ -19,11 +23,22 @@ jest.useFakeTimers();
|
|
|
19
23
|
|
|
20
24
|
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation");
|
|
21
25
|
|
|
26
|
+
const mockStore = configureStore();
|
|
27
|
+
|
|
28
|
+
const store = mockStore({ appData: {} });
|
|
29
|
+
|
|
30
|
+
const Wrapper = ({ children }: { children: React.ReactChild }) => (
|
|
31
|
+
/* @ts-ignore */
|
|
32
|
+
<Provider store={store}>{children}</Provider>
|
|
33
|
+
);
|
|
34
|
+
|
|
22
35
|
const { useTrackedView } = require("../useTrackedView");
|
|
23
36
|
|
|
24
37
|
describe("useTrackCurrentAutoScrollingElement", () => {
|
|
25
38
|
it("should update position for selected component", async () => {
|
|
26
|
-
const { result } = renderHook(() => useTrackedView("123")
|
|
39
|
+
const { result } = renderHook(() => useTrackedView("123"), {
|
|
40
|
+
wrapper: Wrapper,
|
|
41
|
+
});
|
|
27
42
|
|
|
28
43
|
// Fast-forward until all timers have been executed
|
|
29
44
|
jest.advanceTimersByTime(3000);
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
1
3
|
import { cleanup, renderHook } from "@testing-library/react-hooks";
|
|
4
|
+
import { Provider } from "react-redux";
|
|
5
|
+
import configureStore from "redux-mock-store";
|
|
2
6
|
|
|
3
7
|
import * as ReactNative from "react-native";
|
|
4
8
|
|
|
@@ -48,19 +52,43 @@ jest.mock("../../navigation", () => ({
|
|
|
48
52
|
}));
|
|
49
53
|
|
|
50
54
|
const { Dimensions } = require("react-native");
|
|
51
|
-
const { useDimensions } = require("
|
|
55
|
+
const { useDimensions } = require("..");
|
|
56
|
+
|
|
57
|
+
const mockStore = configureStore();
|
|
58
|
+
|
|
59
|
+
const getWrapper = () => {
|
|
60
|
+
const store = mockStore({ appData: {} });
|
|
61
|
+
|
|
62
|
+
const Wrapper = ({ children }: { children: React.ReactChild }) => (
|
|
63
|
+
/* @ts-ignore */
|
|
64
|
+
<Provider store={store}>{children}</Provider>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return Wrapper;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const testHook = (context, params?) => {
|
|
71
|
+
const wrapper = getWrapper();
|
|
72
|
+
|
|
73
|
+
const {
|
|
74
|
+
result,
|
|
75
|
+
result: { current: dimensions },
|
|
76
|
+
} = renderHook(() => useDimensions(context, params), {
|
|
77
|
+
wrapper,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return { result, dimensions };
|
|
81
|
+
};
|
|
52
82
|
|
|
53
83
|
describe("useDimensions", () => {
|
|
54
84
|
it("renders", () => {
|
|
55
|
-
const { result } =
|
|
56
|
-
useDimensions("window", { fullDimensions: true })
|
|
57
|
-
);
|
|
85
|
+
const { result } = testHook("window", { fullDimensions: true });
|
|
58
86
|
|
|
59
87
|
expect(result).toBeDefined();
|
|
60
88
|
});
|
|
61
89
|
|
|
62
90
|
it("sets event listener", () => {
|
|
63
|
-
|
|
91
|
+
testHook("window", { fullDimensions: true });
|
|
64
92
|
|
|
65
93
|
expect(Dimensions.addEventListener).toBeCalledWith(
|
|
66
94
|
"change",
|
|
@@ -69,16 +97,14 @@ describe("useDimensions", () => {
|
|
|
69
97
|
});
|
|
70
98
|
|
|
71
99
|
it("removes event listener", () => {
|
|
72
|
-
|
|
100
|
+
testHook("window", { fullDimensions: true });
|
|
73
101
|
cleanup();
|
|
74
102
|
|
|
75
103
|
expect(mockRemoveFn).toBeCalled();
|
|
76
104
|
});
|
|
77
105
|
|
|
78
106
|
it("Returns full dimensions object", () => {
|
|
79
|
-
const {
|
|
80
|
-
result: { current: dimensions },
|
|
81
|
-
} = renderHook(() => useDimensions("window", { fullDimensions: true }));
|
|
107
|
+
const { dimensions } = testHook("window", { fullDimensions: true });
|
|
82
108
|
|
|
83
109
|
expect(dimensions).toHaveProperty("height");
|
|
84
110
|
expect(dimensions).toHaveProperty("width");
|
|
@@ -87,20 +113,17 @@ describe("useDimensions", () => {
|
|
|
87
113
|
});
|
|
88
114
|
|
|
89
115
|
it("Returns shorthand dimensions object", () => {
|
|
90
|
-
const {
|
|
91
|
-
result: { current: dimensions },
|
|
92
|
-
} = renderHook(() => useDimensions("window"));
|
|
116
|
+
const { dimensions } = testHook("window");
|
|
93
117
|
|
|
94
118
|
expect(dimensions).toHaveProperty("height");
|
|
95
119
|
expect(dimensions).toHaveProperty("width");
|
|
96
120
|
});
|
|
97
121
|
|
|
98
122
|
it("Returns the device info object", () => {
|
|
99
|
-
const {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
);
|
|
123
|
+
const { dimensions } = testHook("window", {
|
|
124
|
+
fullDimensions: true,
|
|
125
|
+
deviceInfo: true,
|
|
126
|
+
});
|
|
104
127
|
|
|
105
128
|
expect(dimensions).toHaveProperty("height");
|
|
106
129
|
expect(dimensions).toHaveProperty("width");
|
|
@@ -113,10 +136,7 @@ describe("useDimensions", () => {
|
|
|
113
136
|
it("applies the scale on TV, and screen context", () => {
|
|
114
137
|
tv_flag = true;
|
|
115
138
|
const { screen } = mocked_return_value(tv_flag);
|
|
116
|
-
|
|
117
|
-
const {
|
|
118
|
-
result: { current: dimensions },
|
|
119
|
-
} = renderHook(() => useDimensions("screen"));
|
|
139
|
+
const { dimensions } = testHook("screen");
|
|
120
140
|
|
|
121
141
|
expect(dimensions).toHaveProperty("height", screen.height * screen.scale);
|
|
122
142
|
expect(dimensions).toHaveProperty("width", screen.width * screen.scale);
|
|
@@ -128,10 +148,7 @@ describe("useDimensions", () => {
|
|
|
128
148
|
it("doesn't apply the scale on mobile with screen context", () => {
|
|
129
149
|
tv_flag = false;
|
|
130
150
|
const { screen } = mocked_return_value(tv_flag);
|
|
131
|
-
|
|
132
|
-
const {
|
|
133
|
-
result: { current: dimensions },
|
|
134
|
-
} = renderHook(() => useDimensions("screen"));
|
|
151
|
+
const { dimensions } = testHook("screen");
|
|
135
152
|
|
|
136
153
|
expect(dimensions).toHaveProperty("height", screen.height);
|
|
137
154
|
expect(dimensions).toHaveProperty("width", screen.width);
|
|
@@ -141,10 +158,7 @@ describe("useDimensions", () => {
|
|
|
141
158
|
it("doesn't apply the scale on TV with window context", () => {
|
|
142
159
|
tv_flag = true;
|
|
143
160
|
const { window } = mocked_return_value(tv_flag);
|
|
144
|
-
|
|
145
|
-
const {
|
|
146
|
-
result: { current: dimensions },
|
|
147
|
-
} = renderHook(() => useDimensions("window"));
|
|
161
|
+
const { dimensions } = testHook("window");
|
|
148
162
|
|
|
149
163
|
expect(dimensions).toHaveProperty("height", window.height);
|
|
150
164
|
expect(dimensions).toHaveProperty("width", window.width);
|
|
@@ -3,13 +3,15 @@ import { isTablet } from "../isTablet";
|
|
|
3
3
|
/**
|
|
4
4
|
* Returns an object with additional device info that can be used to determine if tablet
|
|
5
5
|
* @param {Number} dimensions - Dimensions object passed to the function
|
|
6
|
+
* @param {Object} appData - application data object passed to the function
|
|
6
7
|
* @returns {DeviceInfo} deviceInfo - returns an object with scale, isTablet, and orientation
|
|
7
8
|
*/
|
|
8
|
-
export const getDeviceInfo = (dimensions) => {
|
|
9
|
+
export const getDeviceInfo = (dimensions, appData) => {
|
|
9
10
|
const scale = dimensions?.scale;
|
|
10
11
|
const isLandscape = dimensions?.width > dimensions?.height;
|
|
11
12
|
const orientation = isLandscape ? "landscape" : "portrait";
|
|
12
13
|
const tablet = isTablet(dimensions, orientation) || false;
|
|
14
|
+
const isTabletLandscape = tablet && !appData?.isTabletPortrait;
|
|
13
15
|
|
|
14
|
-
return { scale, isTablet: tablet, orientation };
|
|
16
|
+
return { scale, isTablet: tablet, orientation, isTabletLandscape };
|
|
15
17
|
};
|
|
@@ -3,6 +3,8 @@ import { useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
|
3
3
|
import * as R from "ramda";
|
|
4
4
|
|
|
5
5
|
import { Dimensions, Platform, StatusBar } from "react-native";
|
|
6
|
+
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
7
|
+
|
|
6
8
|
import { isTV } from "../../../reactUtils";
|
|
7
9
|
|
|
8
10
|
import { Options, UseDimensions } from "../types";
|
|
@@ -36,6 +38,7 @@ export const useDimensions: UseDimensions = (
|
|
|
36
38
|
fullDimensions = { fullDimensions: false, updateForInactiveScreens: true }
|
|
37
39
|
) => {
|
|
38
40
|
const isActive = useIsScreenActive();
|
|
41
|
+
const { appData } = usePickFromState(["appData"]);
|
|
39
42
|
|
|
40
43
|
if (typeof fullDimensions === "boolean") {
|
|
41
44
|
// eslint-disable-next-line no-console
|
|
@@ -50,7 +53,7 @@ export const useDimensions: UseDimensions = (
|
|
|
50
53
|
: { ...fullDimensions };
|
|
51
54
|
|
|
52
55
|
const [dimensions, setDimensions] = useState(getInitialDimensions(context));
|
|
53
|
-
const deviceInfo = useRef(getDeviceInfo(dimensions));
|
|
56
|
+
const deviceInfo = useRef(getDeviceInfo(dimensions, appData));
|
|
54
57
|
|
|
55
58
|
const applyOptions = R.compose(
|
|
56
59
|
R.when(() => options.deviceInfo, R.assoc("deviceInfo", deviceInfo.current)),
|
|
@@ -85,7 +88,7 @@ export const useDimensions: UseDimensions = (
|
|
|
85
88
|
// eslint-disable-next-line no-console
|
|
86
89
|
console.log("change: wrong values - width equal to height, ignoring");
|
|
87
90
|
} else if (isActive || options.updateForInactiveScreens) {
|
|
88
|
-
deviceInfo.current = getDeviceInfo(newDimensions);
|
|
91
|
+
deviceInfo.current = getDeviceInfo(newDimensions, appData);
|
|
89
92
|
setDimensions(newDimensions);
|
|
90
93
|
}
|
|
91
94
|
};
|
|
@@ -15,8 +15,21 @@ export const mockMenu: ZappNavigation = {
|
|
|
15
15
|
// preset_id?: null | unknown;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
export const useRoute = jest.fn(() => ({
|
|
19
|
+
screenData: {
|
|
20
|
+
name: "someName",
|
|
21
|
+
},
|
|
22
|
+
pathname: "someRoute",
|
|
23
|
+
}));
|
|
24
|
+
|
|
18
25
|
export const useNavigation = jest.fn(() => ({
|
|
19
26
|
data: {},
|
|
27
|
+
screenData: {
|
|
28
|
+
name: "someName",
|
|
29
|
+
},
|
|
30
|
+
activeRiver: {
|
|
31
|
+
id: "someId",
|
|
32
|
+
},
|
|
20
33
|
currentRoute: "someRoute",
|
|
21
34
|
}));
|
|
22
35
|
|
|
@@ -251,6 +251,11 @@ export const useRoute = (): {
|
|
|
251
251
|
return composeRouteData(screenData);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// case 1: stack not yet updated in navigator context, but navigator.screenData is updated already.
|
|
255
|
+
if (!currentStack) {
|
|
256
|
+
return composeRouteData(navigator.screenData);
|
|
257
|
+
}
|
|
258
|
+
|
|
254
259
|
return composeRouteData(currentRouteScreenData);
|
|
255
260
|
};
|
|
256
261
|
|
|
@@ -491,3 +496,18 @@ export const useIsScreenActive = () => {
|
|
|
491
496
|
|
|
492
497
|
return pathname === currentRoute;
|
|
493
498
|
};
|
|
499
|
+
|
|
500
|
+
type Return = (prefix: string) => string;
|
|
501
|
+
|
|
502
|
+
export const useUniqueRouteSuffix = (): Return => {
|
|
503
|
+
const pathname = usePathname();
|
|
504
|
+
const { currentRoute } = useNavigation();
|
|
505
|
+
|
|
506
|
+
const suffix = `currentRoute:${currentRoute}_pathname:${pathname}`;
|
|
507
|
+
|
|
508
|
+
const withUniqueRouteSuffix = React.useCallback((prefix: string) => {
|
|
509
|
+
return `${prefix}___${suffix}`;
|
|
510
|
+
}, []);
|
|
511
|
+
|
|
512
|
+
return withUniqueRouteSuffix;
|
|
513
|
+
};
|
|
@@ -5,8 +5,9 @@ import { useNestedNavigationContext } from "@applicaster/zapp-react-native-ui-co
|
|
|
5
5
|
import { useNavigation } from "../navigation/useNavigation";
|
|
6
6
|
|
|
7
7
|
import { ScreenContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenContext";
|
|
8
|
+
import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/Components/Layout/TV";
|
|
8
9
|
|
|
9
|
-
function useScreenContextV2() {
|
|
10
|
+
export function useScreenContextV2() {
|
|
10
11
|
return useContext(ScreenContext);
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -45,8 +46,8 @@ export function useSetNavbarState() {
|
|
|
45
46
|
export function useScreenContext(): NavigationScreenData {
|
|
46
47
|
const navigator = useNavigation();
|
|
47
48
|
|
|
48
|
-
const
|
|
49
|
-
|
|
49
|
+
const screenData = useContext(ScreenDataContext);
|
|
50
|
+
|
|
50
51
|
const modalScreenData = navigator?.modalData;
|
|
51
52
|
|
|
52
53
|
const isModal = useModalNavigationContext();
|
package/reactUtils/helpers.tsx
CHANGED
|
@@ -7,7 +7,7 @@ export const isAnyNil = R.any(R.isNil);
|
|
|
7
7
|
|
|
8
8
|
export const isNilOrEmpty = R.either(R.isNil, R.isEmpty);
|
|
9
9
|
|
|
10
|
-
export const isNotEmpty = R.complement(R.isEmpty);
|
|
10
|
+
export const isNotEmpty = R.both(isNotNil, R.complement(R.isEmpty));
|
|
11
11
|
|
|
12
12
|
export const withoutNil = R.reject(R.isNil);
|
|
13
13
|
|
package/screenState/index.ts
CHANGED
|
@@ -49,11 +49,3 @@ export function useScreenState(
|
|
|
49
49
|
setSelectedEntry: (entryId) => setSelectedEntry(screenId, entryId),
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
export const useScreenScrollStore = create<{
|
|
54
|
-
scrollPosition: number;
|
|
55
|
-
scrollingVelocity: number;
|
|
56
|
-
}>(() => ({
|
|
57
|
-
scrollPosition: 0,
|
|
58
|
-
scrollingVelocity: 0,
|
|
59
|
-
}));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const getByTestId = (wrapper, testId) => {
|
|
2
|
+
try {
|
|
3
|
+
if (wrapper?.getByTestId) {
|
|
4
|
+
return wrapper?.queryAllByTestId(testId)?.[0] ?? undefined;
|
|
5
|
+
} else if (wrapper?.root?.findByProps) {
|
|
6
|
+
return wrapper.root.findAllByProps({ testID: testId })?.[0] ?? undefined;
|
|
7
|
+
}
|
|
8
|
+
} catch (e) {
|
|
9
|
+
// eslint-disable-next-line no-console
|
|
10
|
+
console.log("getByTestId E:", e);
|
|
11
|
+
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
};
|
package/testUtils/index.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
2
|
|
|
3
|
-
import React from "react";
|
|
3
|
+
import React, { PropsWithChildren } from "react";
|
|
4
4
|
import { View } from "react-native";
|
|
5
5
|
|
|
6
6
|
import { Provider } from "react-redux";
|
|
@@ -11,6 +11,8 @@ import { SafeAreaProvider } from "react-native-safe-area-context";
|
|
|
11
11
|
import { render } from "@testing-library/react-native";
|
|
12
12
|
import { AnalyticsProvider } from "../analyticsUtils";
|
|
13
13
|
|
|
14
|
+
export { getByTestId } from "./getByTestId";
|
|
15
|
+
|
|
14
16
|
/**
|
|
15
17
|
* Function to be used in tandem with enzyme findWhere(node => node.prop('testID') === 'yourID')
|
|
16
18
|
* Example:
|
|
@@ -21,17 +23,6 @@ import { AnalyticsProvider } from "../analyticsUtils";
|
|
|
21
23
|
export const testIDEquals = (testId: any): boolean =>
|
|
22
24
|
R.compose(R.equals(testId), R.invoker(1, "prop")("testID"));
|
|
23
25
|
|
|
24
|
-
export const getByTestId = (wrapper, testId) => {
|
|
25
|
-
try {
|
|
26
|
-
return wrapper?.queryAllByTestId(testId)?.[0] ?? undefined;
|
|
27
|
-
} catch (e) {
|
|
28
|
-
// eslint-disable-next-line no-console
|
|
29
|
-
console.log("getByTestId E:", e);
|
|
30
|
-
|
|
31
|
-
return undefined;
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
26
|
/** Helper function for react-test-renderer, accepts type and renderer
|
|
36
27
|
* * @param {*} type React Component type instance or name of component
|
|
37
28
|
* * @param {*} renderer renderer instance
|
|
@@ -47,11 +38,11 @@ export const findByType = (type, renderer) => {
|
|
|
47
38
|
export function createComponentMock(mockName, hasChildren = false) {
|
|
48
39
|
return hasChildren
|
|
49
40
|
? jest.fn(({ children }) => (
|
|
50
|
-
<View
|
|
41
|
+
<View testID={mockName}>
|
|
51
42
|
{typeof children === "function" ? children() : children}
|
|
52
43
|
</View>
|
|
53
44
|
))
|
|
54
|
-
: jest.fn(() => <View
|
|
45
|
+
: jest.fn(() => <View testID={mockName} />);
|
|
55
46
|
}
|
|
56
47
|
|
|
57
48
|
const initialState = {
|
|
@@ -64,14 +55,21 @@ const initialState = {
|
|
|
64
55
|
AppContainer: createComponentMock("appContainer", true),
|
|
65
56
|
DisplayError: createComponentMock("displayError"),
|
|
66
57
|
},
|
|
67
|
-
rivers:
|
|
58
|
+
rivers: { id: "A1234", home: true },
|
|
68
59
|
appSettings: {},
|
|
69
60
|
plugins: [],
|
|
70
|
-
appState: { appReady: true },
|
|
61
|
+
appState: { appReady: true, appLaunched: true },
|
|
62
|
+
appData: { layoutVersion: "v1" },
|
|
63
|
+
contentTypes: null,
|
|
71
64
|
};
|
|
72
65
|
|
|
73
|
-
export const WrappedWithProviders = ({
|
|
74
|
-
|
|
66
|
+
export const WrappedWithProviders = ({
|
|
67
|
+
children,
|
|
68
|
+
store: storeObj = {},
|
|
69
|
+
}: any) => {
|
|
70
|
+
const _store = configureStore([thunk])(
|
|
71
|
+
R.mergeDeepWith(R.concat, { ...initialState }, storeObj)
|
|
72
|
+
);
|
|
75
73
|
|
|
76
74
|
return (
|
|
77
75
|
<SafeAreaProvider
|
|
@@ -93,8 +91,12 @@ export const WrappedWithProviders = ({ children, store = {} }: any) => {
|
|
|
93
91
|
* @param store optional store to pass to the provider
|
|
94
92
|
* @returns
|
|
95
93
|
*/
|
|
96
|
-
export const renderWithProviders = (component,
|
|
97
|
-
return render(
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
export const renderWithProviders = (component, storeObj?: unknown) => {
|
|
95
|
+
return render(component, {
|
|
96
|
+
wrapper: function TestWrapper({ children }: PropsWithChildren<any>) {
|
|
97
|
+
return (
|
|
98
|
+
<WrappedWithProviders store={storeObj}>{children}</WrappedWithProviders>
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
});
|
|
100
102
|
};
|