@applicaster/zapp-react-native-utils 12.0.0-alpha.5135101838 → 12.0.0-alpha.5208030571

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.
@@ -1,9 +1,20 @@
1
1
  import * as R from "ramda";
2
+ import {
3
+ isNilOrEmpty,
4
+ isNotNil,
5
+ } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
6
+
2
7
  import { Tree } from "./treeDataStructure/Tree";
3
8
  import { findFocusableNode, findPriorityItem } from "./treeDataStructure/Utils";
4
9
  import { subscriber } from "../../functionUtils";
5
10
  import * as FOCUS_EVENTS from "./events";
6
11
  import { coreLogger } from "../../logger";
12
+ import {
13
+ waitForActiveScreen,
14
+ waitForContent,
15
+ getContentNode,
16
+ getActiveScreen,
17
+ } from "./utils";
7
18
 
8
19
  const logger = coreLogger.addSubsystem("focusManager");
9
20
 
@@ -46,7 +57,7 @@ export const focusManager = (function () {
46
57
  let lastFocusedId = null;
47
58
  let initialFocusOnContent = null;
48
59
  let focusDisabled = false;
49
- let focusAutoEnabled = true;
60
+ let focusAutoEnabled = false;
50
61
  const eventHandler = subscriber();
51
62
 
52
63
  function disableAutomaticFocus() {
@@ -93,7 +104,7 @@ export const focusManager = (function () {
93
104
  !currentFocusable.isGroup &&
94
105
  currentFocusable.isMounted()
95
106
  ) {
96
- currentFocusable.blur(currentFocusable, direction);
107
+ currentFocusable.blur(direction);
97
108
  currentFocusable.hasLostFocus(getCurrentFocus(), direction);
98
109
  }
99
110
  }
@@ -219,38 +230,55 @@ export const focusManager = (function () {
219
230
  /**
220
231
  * sets the initial focus when the screen loads, or when focus is lost
221
232
  */
222
- function setInitialFocus(lastAddedParentNode?: any) {
233
+ function setInitialFocus(lastAddedParentNode?: any, fallback?: () => void) {
223
234
  const preferredFocus = findPriorityItem(
224
235
  lastAddedParentNode?.children || focusableTree.root.children
225
236
  );
226
237
 
227
238
  logger.log({ message: "setInitialFocus", data: { preferredFocus } });
228
239
 
229
- if (preferredFocus && preferredFocus.length > 0) {
230
- const focusCandidate = preferredFocus[0];
231
- const focusableItem = focusCandidate.component;
232
-
233
- if (
234
- focusableItem &&
235
- !focusableItem.isGroup &&
236
- isFocusEnabled(focusableItem)
237
- ) {
238
- if (initialFocusOnContent) {
239
- blur();
240
- initialFocusOnContent = false;
241
- }
240
+ if (isNilOrEmpty(preferredFocus)) {
241
+ fallback?.();
242
+ return;
243
+ }
242
244
 
243
- logger.log({
244
- message: "setInitialFocus – currentFocusNode",
245
- data: {
246
- focusableItem,
247
- },
248
- });
245
+ const focusCandidate = preferredFocus[0];
246
+ const focusableItem = focusCandidate.component;
249
247
 
250
- currentFocusNode = focusCandidate;
251
- focusableItem.willReceiveFocus(focusableItem, null);
252
- focusableItem && setFocus(focusCandidate.id, null);
248
+ const currentFocusedId = R.path(["props", "id"], getCurrentFocus());
249
+ const nextFocusedId = R.path(["props", "id"], focusableItem);
250
+
251
+ if (currentFocusedId === nextFocusedId) {
252
+ // we are going to move focus to the same node(already focused) -> nothing to do
253
+ return;
254
+ }
255
+
256
+ if (preferredFocus.length === 1 && focusableItem && focusableItem.isGroup) {
257
+ // we've found only one FocusableGroup as a candidate to focus, try to use fallback
258
+ fallback?.();
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,58 @@ export const focusManager = (function () {
410
448
  }
411
449
  }
412
450
 
451
+ async function setFocusOnMenu(currentRoute: string) {
452
+ try {
453
+ await waitForActiveScreen(currentRoute, focusableTree);
454
+
455
+ const navbar = R.find(
456
+ (child) => R.startsWith("navbar", child.id),
457
+ getActiveScreen(focusableTree).children
458
+ );
459
+
460
+ const selectedMenuItemId = R.find(
461
+ (child) => child.component.props.selected,
462
+ navbar?.children || []
463
+ )?.id;
464
+
465
+ if (selectedMenuItemId) {
466
+ // set focus on selected menu item
467
+ setFocus(selectedMenuItemId);
468
+ return;
469
+ }
470
+
471
+ const firstMenuItemId = R.path(["children", 0, "id"], navbar);
472
+
473
+ if (firstMenuItemId) {
474
+ // set focus on first menu item
475
+ setFocus(firstMenuItemId);
476
+ }
477
+ } catch (error) {
478
+ // failed to put focus on top-menu => log it
479
+ }
480
+ }
481
+
482
+ async function setFocusOnContent(currentRoute: string) {
483
+ // we need to put focus on content here
484
+ // For doing it we have to wait registration of first focusable node into focusableTree
485
+ // this is the temporary hack with waiting until content node starts having children
486
+ try {
487
+ await waitForActiveScreen(currentRoute, focusableTree);
488
+ await waitForContent(focusableTree);
489
+
490
+ const content = getContentNode(focusableTree);
491
+
492
+ const fallback = () => {
493
+ throw Error("Failed to land focus on content");
494
+ };
495
+
496
+ setInitialFocus(content, fallback);
497
+ } catch (e) {
498
+ // we've failed to put initial focus on content, put focus on menu as a last chance
499
+ setFocusOnMenu(currentRoute);
500
+ }
501
+ }
502
+
413
503
  /**
414
504
  * moves the focus in a provided direction
415
505
  * @public
@@ -422,6 +512,13 @@ export const focusManager = (function () {
422
512
  currentFocusNode.component.willLoseFocus(currentFocusNode, direction);
423
513
  const nextFocusable = findFocusableNode(direction, currentFocusNode);
424
514
 
515
+ // console.log("debug_2", "FM moveFocus", {
516
+ // direction: direction?.value,
517
+ // currentFocusNode,
518
+ // focusableTree,
519
+ // nextFocusable,
520
+ // });
521
+
425
522
  if (nextFocusable?.component?.isMounted?.()) {
426
523
  const nextRect = nextFocusable.component.getRect();
427
524
 
@@ -518,5 +615,7 @@ export const focusManager = (function () {
518
615
  enableAutomaticFocus,
519
616
  getGroupRootById,
520
617
  isGroupItemFocused,
618
+ setFocusOnMenu,
619
+ setFocusOnContent,
521
620
  };
522
621
  })();
@@ -1,4 +1,4 @@
1
- import * as R from "ramda";
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 = R.allPass([hasComponent, hasChildren]);
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
- .map((target) => {
177
- const componentRect = target.component?.getRect();
178
-
179
- const distance = measureDistance(
180
- direction,
181
- comparisonRect,
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
- const orientationDistance = measureDistance(
191
- direction,
192
- comparisonRect,
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
- return distances && distances.length > 0 ? match.target : null;
214
- }
215
-
216
- function ignoreMenuIfMultipleTargets(nodes) {
217
- if (nodes.length > 1 && nodes.map((n) => n?.target?.id).includes("navbar")) {
218
- return nodes.filter((n) => n?.target?.id !== "navbar");
219
- }
220
-
221
- return nodes;
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
+ };
@@ -0,0 +1,65 @@
1
+ import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
2
+ import { find, last, startsWith } from "ramda";
3
+
4
+ // run check each 300 ms
5
+ // run this check too often could lead to performance penalty on low-end devices
6
+ const HOW_OFTEN_TO_CHECK_CONDITION = 300; //ms
7
+
8
+ type Props = {
9
+ maxTimeout: number;
10
+ conditionFn: () => boolean;
11
+ };
12
+
13
+ export const waitUntil = ({ maxTimeout, conditionFn }: Props) => {
14
+ let interval;
15
+ return new Promise((resolve, reject) => {
16
+ interval = setInterval(() => {
17
+ if (conditionFn()) {
18
+ clearInterval(interval);
19
+ resolve(undefined);
20
+ }
21
+ }, HOW_OFTEN_TO_CHECK_CONDITION);
22
+
23
+ setTimeout(() => {
24
+ reject();
25
+ }, maxTimeout);
26
+ });
27
+ };
28
+
29
+ export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
30
+ const isActiveScreenAvailableToFocus = (): boolean => {
31
+ const routes = focusableTree.root.children;
32
+
33
+ const route = find((route) => route.id === currentRoute, routes);
34
+ return isNotNil(route);
35
+ };
36
+
37
+ return waitUntil({
38
+ maxTimeout: 2500, //ms
39
+ conditionFn: isActiveScreenAvailableToFocus,
40
+ });
41
+ };
42
+
43
+ export const getActiveScreen = (focusableTree) => {
44
+ return last(focusableTree.root.children);
45
+ };
46
+
47
+ export const getContentNode = (focusableTree) => {
48
+ const content = find(
49
+ (child) => startsWith("content", child.id),
50
+ getActiveScreen(focusableTree)?.children
51
+ );
52
+
53
+ return content;
54
+ };
55
+
56
+ export const waitForContent = (focusableTree) => {
57
+ const contentHasAnyChildren = (): boolean => {
58
+ return getContentNode(focusableTree)?.children.length > 0;
59
+ };
60
+
61
+ return waitUntil({
62
+ maxTimeout: 1000, //ms
63
+ conditionFn: contentHasAnyChildren,
64
+ });
65
+ };
@@ -296,5 +296,8 @@ export class PlayerNative extends Player {
296
296
  supportsNativeControls = (): boolean =>
297
297
  toBooleanWithDefaultFalse(this.getConfig()?.["supports_native_controls"]);
298
298
 
299
- supportNativeCast = (): boolean => toBooleanWithDefaultFalse(this.playerPluginId === "quick-brick-player-brightcove");
299
+ supportNativeCast = (): boolean =>
300
+ toBooleanWithDefaultFalse(
301
+ this.playerPluginId === "quick-brick-player-brightcove"
302
+ );
300
303
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "12.0.0-alpha.5135101838",
3
+ "version": "12.0.0-alpha.5208030571",
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.5135101838",
31
+ "@applicaster/applicaster-types": "12.0.0-alpha.5208030571",
32
32
  "buffer": "^5.2.1",
33
33
  "camelize": "^1.0.0",
34
34
  "create-react-context": "^0.2.3",
@@ -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
 
@@ -1,4 +1,7 @@
1
1
  export const useNavigation = jest.fn(() => ({
2
2
  data: {},
3
+ screenData: {
4
+ name: "someName",
5
+ },
3
6
  currentRoute: "someRoute",
4
7
  }));
@@ -496,3 +496,18 @@ export const useIsScreenActive = () => {
496
496
 
497
497
  return pathname === currentRoute;
498
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 screenKey = useMemo(() => navigator?.key, [navigator]);
49
- const screenData = useMemo(() => navigator?.data, [screenKey]);
49
+ const screenData = useContext(ScreenDataContext);
50
+
50
51
  const modalScreenData = navigator?.modalData;
51
52
 
52
53
  const isModal = useModalNavigationContext();
@@ -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
 
@@ -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
- }));
@@ -152,3 +152,8 @@ export function objectToCamelCase(obj) {
152
152
  export function isString(value: unknown): value is string {
153
153
  return typeof value === "string" || value instanceof String;
154
154
  }
155
+
156
+ /** Checks if a value is a non-empty string */
157
+ export function isNotEmptyString(value: unknown): value is string {
158
+ return isString(value) && value.length > 0;
159
+ }
@@ -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
+ };
@@ -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 comp={mockName} testID={mockName}>
41
+ <View testID={mockName}>
51
42
  {typeof children === "function" ? children() : children}
52
43
  </View>
53
44
  ))
54
- : jest.fn(() => <View comp={mockName} testID={mockName} />);
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: [{ id: "A1234", home: true }],
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 = ({ children, store = {} }: any) => {
74
- const _store = configureStore([thunk])({ ...initialState, ...store });
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, store?: unknown) => {
97
- return render(
98
- <WrappedWithProviders store={store}>{component}</WrappedWithProviders>
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
  };