@applicaster/zapp-react-native-utils 14.0.0-alpha.1118824347 → 14.0.0-alpha.1190505115

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.
Files changed (60) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +60 -84
  2. package/actionsExecutor/ScreenActions.ts +164 -0
  3. package/actionsExecutor/StorageActions.ts +110 -0
  4. package/actionsExecutor/feedDecorator.ts +171 -0
  5. package/actionsExecutor/screenResolver.ts +11 -0
  6. package/analyticsUtils/AnalyticsEvents/helper.ts +1 -1
  7. package/analyticsUtils/AnalyticsEvents/sendHeaderClickEvent.ts +1 -1
  8. package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -1
  9. package/analyticsUtils/__tests__/analyticsUtils.test.js +0 -11
  10. package/analyticsUtils/index.tsx +3 -4
  11. package/analyticsUtils/manager.ts +1 -1
  12. package/appUtils/accessibilityManager/index.ts +3 -3
  13. package/appUtils/contextKeysManager/contextResolver.ts +42 -1
  14. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +2 -1
  15. package/appUtils/focusManager/index.ios.ts +10 -0
  16. package/appUtils/focusManager/index.ts +35 -36
  17. package/appUtils/focusManager/treeDataStructure/Tree/index.js +1 -1
  18. package/appUtils/focusManagerAux/utils/index.ts +80 -23
  19. package/configurationUtils/__tests__/manifestKeyParser.test.ts +0 -1
  20. package/index.d.ts +0 -9
  21. package/navigationUtils/__tests__/mapContentTypesToRivers.test.ts +130 -0
  22. package/navigationUtils/index.ts +6 -4
  23. package/package.json +2 -3
  24. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +15 -14
  25. package/reactHooks/cell-click/__tests__/index.test.js +3 -0
  26. package/reactHooks/cell-click/index.ts +8 -1
  27. package/reactHooks/debugging/__tests__/index.test.js +0 -1
  28. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +47 -90
  29. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +71 -31
  30. package/reactHooks/feed/index.ts +2 -0
  31. package/reactHooks/feed/useBatchLoading.ts +15 -8
  32. package/reactHooks/feed/useFeedLoader.tsx +36 -34
  33. package/reactHooks/feed/useLoadPipesDataDispatch.ts +57 -0
  34. package/reactHooks/feed/usePipesCacheReset.ts +2 -2
  35. package/reactHooks/flatList/useSequentialRenderItem.tsx +3 -3
  36. package/reactHooks/layout/__tests__/index.test.tsx +3 -1
  37. package/reactHooks/layout/useDimensions/__tests__/useDimensions.test.ts +34 -36
  38. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -3
  39. package/reactHooks/layout/useLayoutVersion.ts +5 -5
  40. package/reactHooks/navigation/index.ts +5 -7
  41. package/reactHooks/navigation/useRoute.ts +7 -2
  42. package/reactHooks/navigation/useScreenStateStore.ts +8 -0
  43. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +4 -0
  44. package/reactHooks/state/index.ts +1 -1
  45. package/reactHooks/state/useHomeRiver.ts +4 -2
  46. package/reactHooks/state/useRivers.ts +7 -8
  47. package/screenPickerUtils/index.ts +7 -0
  48. package/storage/ScreenSingleValueProvider.ts +204 -0
  49. package/storage/ScreenStateMultiSelectProvider.ts +293 -0
  50. package/storage/StorageMultiSelectProvider.ts +192 -0
  51. package/storage/StorageSingleSelectProvider.ts +108 -0
  52. package/testUtils/index.tsx +7 -8
  53. package/time/BackgroundTimer.ts +1 -1
  54. package/utils/__tests__/find.test.ts +36 -0
  55. package/utils/__tests__/pathOr.test.ts +37 -0
  56. package/utils/__tests__/startsWith.test.ts +30 -0
  57. package/utils/find.ts +3 -0
  58. package/utils/index.ts +8 -0
  59. package/utils/pathOr.ts +5 -0
  60. package/utils/startsWith.ts +9 -0
@@ -8,17 +8,6 @@ jest.mock("@applicaster/zapp-react-native-utils/reactUtils", () => ({
8
8
  ),
9
9
  }));
10
10
 
11
- jest.mock(
12
- "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider",
13
- () => ({
14
- StorageMultiSelectProvider: {
15
- getProvider: jest.fn(() => ({
16
- getSelectedItems: jest.fn(() => []),
17
- })),
18
- },
19
- })
20
- );
21
-
22
11
  const mock_postAnalyticEvent = jest.fn();
23
12
  const mock_startAnalyticsTimedEvent = jest.fn();
24
13
  const mock_endAnalyticsTimedEvent = jest.fn();
@@ -1,4 +1,3 @@
1
- /// <reference types="@applicaster/zapp-react-native-utils" />
2
1
  import * as R from "ramda";
3
2
  import * as React from "react";
4
3
  import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
@@ -31,7 +30,7 @@ import { ANALYTICS_CORE_EVENTS } from "./events";
31
30
  import { noop } from "../functionUtils";
32
31
 
33
32
  type ComponentWithChildrenProps = {
34
- children: React.ReactChildren;
33
+ children: React.ReactElement;
35
34
  };
36
35
 
37
36
  export function sendSelectCellEvent(item, component, headerTitle, itemIndex) {
@@ -120,11 +119,11 @@ export function getAnalyticsFunctions({
120
119
  export const AnalyticsContext =
121
120
  React.createContext<GetAnalyticsFunctions>(noop);
122
121
 
123
- export function AnalyticsProvider(props: ComponentWithChildrenProps) {
122
+ export function AnalyticsProvider({ children }: ComponentWithChildrenProps) {
124
123
  return (
125
124
  // @ts-ignore - this is a valid context provider
126
125
  <AnalyticsContext.Provider value={getAnalyticsFunctions}>
127
- {props?.children}
126
+ {children}
128
127
  </AnalyticsContext.Provider>
129
128
  );
130
129
  }
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-use-before-define */
2
2
  import * as R from "ramda";
3
3
  import { NativeModules } from "react-native";
4
- import { ANALYTICS_CORE_EVENTS } from "@applicaster/zapp-react-native-utils/analyticsUtils/events";
4
+ import { ANALYTICS_CORE_EVENTS } from "./events";
5
5
 
6
6
  import { analyticsUtilsLogger } from "./logger";
7
7
 
@@ -2,8 +2,8 @@ import { BehaviorSubject } from "rxjs";
2
2
  import { accessibilityManagerLogger as logger } from "./logger";
3
3
  import { TTSManager } from "../platform";
4
4
  import { BUTTON_ACCESSIBILITY_KEYS } from "./const";
5
- import { AccessibilityRole } from "react-native";
6
5
  import { toString } from "../../utils";
6
+ import { AccessibilityRole } from "react-native";
7
7
 
8
8
  export class AccessibilityManager {
9
9
  private static _instance: AccessibilityManager | null = null;
@@ -147,7 +147,7 @@ export class AccessibilityManager {
147
147
  accessibilityHint: `Press button to perform action on ${buttonName}`,
148
148
  "aria-label": buttonName,
149
149
  "aria-description": `Press button to perform action on ${buttonName}`,
150
- accessibilityRole: "button" as AccessibilityRole,
150
+ accessibilityRole: "button",
151
151
  "aria-role": "button",
152
152
  };
153
153
  }
@@ -166,7 +166,7 @@ export class AccessibilityManager {
166
166
  accessibilityHint: hint,
167
167
  "aria-label": label,
168
168
  "aria-description": hint,
169
- accessibilityRole: "button" as AccessibilityRole,
169
+ accessibilityRole: "button",
170
170
  "aria-role": "button",
171
171
  };
172
172
  }
@@ -1,10 +1,13 @@
1
1
  import { ContextKeysManager } from "./index";
2
2
  import * as R from "ramda";
3
+ import * as _ from "lodash";
4
+ import { useScreenStateStore } from "../../reactHooks/navigation/useScreenStateStore";
3
5
 
4
- interface IResolver {
6
+ export interface IResolver {
5
7
  resolve: (string) => Promise<string | number | object>;
6
8
  }
7
9
 
10
+ // TODO: Rename to ObjectKeyResolver or similar
8
11
  export class EntryResolver implements IResolver {
9
12
  entry: ZappEntry;
10
13
 
@@ -21,6 +24,28 @@ export class EntryResolver implements IResolver {
21
24
  }
22
25
  }
23
26
 
27
+ // TODO: Move to proper place
28
+
29
+ export class ScreenStateResolver implements IResolver {
30
+ constructor(
31
+ private screenStateStore: ReturnType<typeof useScreenStateStore>
32
+ ) {}
33
+
34
+ async resolve(key: string) {
35
+ const screenState = this.screenStateStore.getState().data;
36
+
37
+ if (!key || key.length === 0) {
38
+ return screenState;
39
+ }
40
+
41
+ if (key.includes(".")) {
42
+ return R.view(R.lensPath(key.split(".")), screenState);
43
+ }
44
+
45
+ return screenState?.[key];
46
+ }
47
+ }
48
+
24
49
  export class ContextResolver implements IResolver {
25
50
  resolve = async (compositeKey: string) =>
26
51
  ContextKeysManager.instance.getKey(compositeKey);
@@ -64,3 +89,19 @@ export const resolveObjectValues = async (
64
89
 
65
90
  return Object.fromEntries(resolvedEntries);
66
91
  };
92
+
93
+ export const extractAtValues = _.memoize((input: any): string[] => {
94
+ return _.flatMapDeep(input, (value: any) => {
95
+ if (_.isString(value)) {
96
+ const matches = value.match(/@\{([^}]*)\}/g);
97
+
98
+ return matches ? matches.map((match) => match.slice(2, -1)) : [];
99
+ }
100
+
101
+ if (_.isObject(value)) {
102
+ return extractAtValues(_.values(value));
103
+ }
104
+
105
+ return [];
106
+ });
107
+ });
@@ -25,10 +25,10 @@ exports[`focusManager should be defined 1`] = `
25
25
  "invokeHandler": [Function],
26
26
  "isCurrentFocusOnTheTopScreen": [Function],
27
27
  "isFocusDisabled": [Function],
28
+ "isFocusOn": [Function],
28
29
  "isFocusOnContent": [Function],
29
30
  "isFocusOnMenu": [Function],
30
31
  "isGroupItemFocused": [Function],
31
- "isOnRootScreen": [Function],
32
32
  "longPress": [Function],
33
33
  "moveFocus": [Function],
34
34
  "on": [Function],
@@ -67,6 +67,7 @@ exports[`focusManagerIOS should be defined 1`] = `
67
67
  "getGroupRootById": [Function],
68
68
  "getPreferredFocusChild": [Function],
69
69
  "invokeHandler": [Function],
70
+ "isFocusOn": [Function],
70
71
  "isGroupItemFocused": [Function],
71
72
  "moveFocus": [Function],
72
73
  "on": [Function],
@@ -1,6 +1,7 @@
1
1
  import { NativeModules } from "react-native";
2
2
  import * as R from "ramda";
3
3
 
4
+ import { isCurrentFocusOn } from "../focusManagerAux/utils";
4
5
  import { Tree } from "./treeDataStructure/Tree";
5
6
  import { findFocusableNode } from "./treeDataStructure/Utils";
6
7
  import { subscriber } from "../../functionUtils";
@@ -391,6 +392,14 @@ export const focusManager = (function () {
391
392
  return node;
392
393
  }
393
394
 
395
+ function isFocusOn(id): boolean {
396
+ const currentFocusNode = focusableTree.findInTree(
397
+ getCurrentFocus()?.props?.id
398
+ );
399
+
400
+ return id && isCurrentFocusOn(id, currentFocusNode);
401
+ }
402
+
394
403
  return {
395
404
  on,
396
405
  invokeHandler,
@@ -412,5 +421,6 @@ export const focusManager = (function () {
412
421
  getGroupRootById,
413
422
  isGroupItemFocused,
414
423
  getPreferredFocusChild,
424
+ isFocusOn,
415
425
  };
416
426
  })();
@@ -15,12 +15,12 @@ import { coreLogger } from "../../logger";
15
15
  import { ACTION } from "./utils/enums";
16
16
 
17
17
  import {
18
- isTabsScreen,
19
18
  findSelectedTabId,
20
19
  findSelectedMenuId,
21
- isTabsMenuFocused,
20
+ isTabsScreenContentFocused,
22
21
  isCurrentFocusOnContent,
23
22
  isCurrentFocusOnMenu,
23
+ isCurrentFocusOn,
24
24
  } from "../focusManagerAux/utils";
25
25
 
26
26
  const logger = coreLogger.addSubsystem("focusManager");
@@ -300,48 +300,46 @@ export const focusManager = (function () {
300
300
  return isCurrentFocusOnMenu(currentFocusNode);
301
301
  }
302
302
 
303
- function landFocusTo(id) {
304
- if (id) {
305
- // set focus on selected menu item
306
- const direction = undefined;
303
+ // Move focus to appropriate top navigation tab with context
304
+ function focusTopNavigation(isTabsScreen: boolean, item: ZappEntry) {
305
+ const landFocusTo = (id) => {
306
+ if (id) {
307
+ // set focus on selected menu item
308
+ const direction = undefined;
307
309
 
308
- const context: FocusManager.FocusContext = {
309
- source: "back",
310
- preserveScroll: true,
311
- };
310
+ const context: FocusManager.FocusContext = {
311
+ source: "back",
312
+ preserveScroll: true,
313
+ };
312
314
 
313
- blur(direction);
314
- setFocus(id, direction, context);
315
- }
316
- }
315
+ logger.log({ message: "landFocusTo", data: { id } });
317
316
 
318
- // Move focus to appropriate top navigation tab with context
319
- function focusTopNavigation() {
320
- // Store current focus for restoration
321
- // this.storeFocusState();
322
-
323
- if (isTabsScreen(focusableTree) && !isTabsMenuFocused(currentFocusNode)) {
324
- const selectedTabId = findSelectedTabId(focusableTree);
317
+ blur(direction);
318
+ setFocus(id, direction, context);
319
+ }
320
+ };
325
321
 
326
- console.log("debug_2", "FM - moveFocusToSelectedTab", { selectedTabId });
322
+ if (isTabsScreen && isTabsScreenContentFocused(currentFocusNode)) {
323
+ const selectedTabId = findSelectedTabId(item);
327
324
 
325
+ // Set focus with back button context to tabs-menu
328
326
  landFocusTo(selectedTabId);
329
327
 
330
328
  return;
331
329
  }
332
330
 
333
- // Set focus with back button context
334
331
  const selectedMenuItemId = findSelectedMenuId(focusableTree);
335
-
336
- console.log("debug_2", "IM - moveFocusToTopMenu", { selectedMenuItemId });
337
-
332
+ // Set focus with back button context to top-menu
338
333
  landFocusTo(selectedMenuItemId);
339
334
  }
340
335
 
341
336
  /**
342
337
  * sets the initial focus when the screen loads, or when focus is lost
343
338
  */
344
- function setInitialFocus(lastAddedParentNode?: any) {
339
+ function setInitialFocus(
340
+ lastAddedParentNode?: any,
341
+ context?: FocusManager.FocusContext
342
+ ) {
345
343
  const preferredFocus = findPriorityItem(
346
344
  lastAddedParentNode?.children || focusableTree.root.children
347
345
  );
@@ -387,7 +385,7 @@ export const focusManager = (function () {
387
385
  },
388
386
  });
389
387
 
390
- focusableItem && setFocus(focusCandidate.id, null);
388
+ focusableItem && setFocus(focusCandidate.id, null, context);
391
389
 
392
390
  return { success: true };
393
391
  }
@@ -544,12 +542,6 @@ export const focusManager = (function () {
544
542
  return haveSameParentBeforeRoot(currentFocusNode, R.last(routes));
545
543
  }
546
544
 
547
- function isOnRootScreen() {
548
- const routes = R.pathOr([], ["root", "children"], focusableTree);
549
-
550
- return routes.length <= 1;
551
- }
552
-
553
545
  function recoverFocus() {
554
546
  if (!isCurrentFocusOnTheTopScreen()) {
555
547
  // We've failed to set focused node on the new screen => run focus recovery
@@ -613,6 +605,14 @@ export const focusManager = (function () {
613
605
  return preferredFocus[0];
614
606
  }
615
607
 
608
+ function isFocusOn(id): boolean {
609
+ return (
610
+ id &&
611
+ isCurrentFocusOnTheTopScreen() &&
612
+ isCurrentFocusOn(id, currentFocusNode)
613
+ );
614
+ }
615
+
616
616
  /**
617
617
  * this is the list of the functions available externally
618
618
  * when importing the focus manager
@@ -643,10 +643,9 @@ export const focusManager = (function () {
643
643
  recoverFocus,
644
644
  isCurrentFocusOnTheTopScreen,
645
645
  findPreferredFocusChild,
646
-
647
646
  focusTopNavigation,
648
647
  isFocusOnContent,
649
648
  isFocusOnMenu,
650
- isOnRootScreen,
649
+ isFocusOn,
651
650
  };
652
651
  })();
@@ -142,7 +142,7 @@ export class Tree {
142
142
  this.hasGroupID(node)
143
143
  ? "Make sure that there are no id duplicates inside the " +
144
144
  existingNode.parent.id +
145
- " group."
145
+ " group. This can as well happen when the component is re-mounted"
146
146
  : ""
147
147
  }`,
148
148
  });
@@ -1,15 +1,30 @@
1
- import { isNil } from "@applicaster/zapp-react-native-utils/utils";
2
- import { find, last, pathOr, startsWith } from "ramda";
1
+ import {
2
+ isNil,
3
+ last,
4
+ startsWith,
5
+ find,
6
+ pathOr,
7
+ } from "@applicaster/zapp-react-native-utils/utils";
8
+
3
9
  import {
4
10
  QUICK_BRICK_CONTENT,
5
11
  QUICK_BRICK_NAVBAR,
6
12
  } from "@applicaster/quick-brick-core/const";
7
13
 
14
+ import {
15
+ getFocusableId,
16
+ SCREEN_PICKER_CONTAINER,
17
+ } from "@applicaster/zapp-react-native-utils/screenPickerUtils";
18
+
8
19
  // run check each 300 ms
9
20
  // run this check too often could lead to performance penalty on low-end devices
10
21
  const HOW_OFTEN_TO_CHECK_CONDITION = 300; // ms
11
22
 
12
- const TABS_GROUP_ID = "PickerSelector.sp-river";
23
+ const isTopMenu = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
24
+ const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
25
+ const isRoot = (node) => node?.id === "root";
26
+
27
+ const isScrenPicker = (node) => startsWith(SCREEN_PICKER_CONTAINER, node?.id);
13
28
 
14
29
  type Props = {
15
30
  maxTimeout: number;
@@ -102,19 +117,8 @@ export const waitForContent = (focusableTree) => {
102
117
  });
103
118
  };
104
119
 
105
- export function isTabsScreen(focusableTree) {
106
- const tabsGroup = focusableTree.findInTree(TABS_GROUP_ID);
107
-
108
- return !!tabsGroup;
109
- }
110
-
111
- export const findSelectedTabId = (focusableTree) => {
112
- // FIXME - find elegant way how to get ID of selected tab
113
- const tabsGroup = focusableTree.findInTree(TABS_GROUP_ID);
114
-
115
- const selectedTabId = tabsGroup.children.find(
116
- (child) => child.component.props.preferredFocus // ?? selected
117
- )?.id;
120
+ export const findSelectedTabId = (item: ZappEntry): string => {
121
+ const selectedTabId = getFocusableId(item.id);
118
122
 
119
123
  return selectedTabId;
120
124
  };
@@ -131,17 +135,70 @@ export const findSelectedMenuId = (focusableTree) => {
131
135
  return selectedMenuItemId;
132
136
  };
133
137
 
134
- export const isTabsMenuFocused = (node) => {
135
- // FIXME - find elegant way how to get ID of selected tab
136
- return node.parent.id === TABS_GROUP_ID;
138
+ export const isTabsScreenContentFocused = (node) => {
139
+ if (isRoot(node)) {
140
+ return false;
141
+ }
142
+
143
+ if (isTopMenu(node)) {
144
+ return false;
145
+ }
146
+
147
+ if (isContent(node)) {
148
+ return false;
149
+ }
150
+
151
+ if (isScrenPicker(node)) {
152
+ return true;
153
+ }
154
+
155
+ return isTabsScreenContentFocused(node.parent);
137
156
  };
138
157
 
139
158
  export const isCurrentFocusOnMenu = (node) => {
140
- // FIXME
141
- return node.parent.id.startsWith(QUICK_BRICK_NAVBAR);
159
+ if (isRoot(node)) {
160
+ return false;
161
+ }
162
+
163
+ if (isTopMenu(node)) {
164
+ return true;
165
+ }
166
+
167
+ if (isContent(node)) {
168
+ return false;
169
+ }
170
+
171
+ return isCurrentFocusOnMenu(node.parent);
142
172
  };
143
173
 
144
174
  export const isCurrentFocusOnContent = (node) => {
145
- // FIXME
146
- return !isCurrentFocusOnMenu(node);
175
+ if (isRoot(node)) {
176
+ return false;
177
+ }
178
+
179
+ if (isTopMenu(node)) {
180
+ return false;
181
+ }
182
+
183
+ if (isContent(node)) {
184
+ return true;
185
+ }
186
+
187
+ return isCurrentFocusOnContent(node.parent);
188
+ };
189
+
190
+ export const isCurrentFocusOn = (id, node) => {
191
+ if (!node) {
192
+ return false;
193
+ }
194
+
195
+ if (isRoot(node)) {
196
+ return false;
197
+ }
198
+
199
+ if (node?.id === id) {
200
+ return true;
201
+ }
202
+
203
+ return isCurrentFocusOn(id, node.parent);
147
204
  };
@@ -1,6 +1,5 @@
1
1
  import { getAllSpecificStyles } from "../manifestKeyParser";
2
2
 
3
- // Mock the dependencies
4
3
  jest.mock("@applicaster/zapp-react-native-utils/reactUtils", () => ({
5
4
  platformSelect: jest.fn((platforms) => platforms.samsung_tv), // Default to samsung for tests
6
5
  }));
package/index.d.ts CHANGED
@@ -137,12 +137,3 @@ declare type AccessibilityState = {
137
137
  reduceMotionEnabled: boolean;
138
138
  boldTextEnabled: boolean;
139
139
  };
140
-
141
- declare type AccessibilityProps = {
142
- accessibilityLabel?: string;
143
- accessibilityHint?: string;
144
- "aria-label"?: string;
145
- "aria-description"?: string;
146
- accessibilityRole?: string;
147
- "aria-role"?: string;
148
- };
@@ -0,0 +1,130 @@
1
+ import { mapContentTypesToRivers } from "../index";
2
+
3
+ describe("mapContentTypesToRivers", () => {
4
+ it("should return the correct content types mapped to rivers", () => {
5
+ const state = {
6
+ rivers: {
7
+ "river-1": {
8
+ plugin_type: "river",
9
+ },
10
+ },
11
+ contentTypes: {
12
+ "content-type-1": {
13
+ screen_id: "river-1",
14
+ },
15
+ },
16
+ };
17
+
18
+ const result = mapContentTypesToRivers(state);
19
+
20
+ expect(result).toEqual({
21
+ "content-type-1": {
22
+ screenType: "river",
23
+ screen_id: "river-1",
24
+ },
25
+ });
26
+ });
27
+
28
+ it("should return null if contentTypes is undefined", () => {
29
+ const state = {
30
+ rivers: {
31
+ "river-1": {
32
+ plugin_type: "river",
33
+ },
34
+ },
35
+ // contentTypes is missing
36
+ };
37
+
38
+ const result = mapContentTypesToRivers(state);
39
+
40
+ expect(result).toBeNull();
41
+ });
42
+
43
+ it("should skip content types whose screen does not exist in rivers", () => {
44
+ const state = {
45
+ rivers: {
46
+ "river-1": {
47
+ plugin_type: "river",
48
+ },
49
+ },
50
+ contentTypes: {
51
+ "content-type-1": {
52
+ screen_id: "river-1",
53
+ },
54
+ "content-type-2": {
55
+ screen_id: "river-2", // river-2 does not exist
56
+ },
57
+ },
58
+ };
59
+
60
+ const result = mapContentTypesToRivers(state);
61
+
62
+ expect(result).toEqual({
63
+ "content-type-1": {
64
+ screenType: "river",
65
+ screen_id: "river-1",
66
+ },
67
+ });
68
+
69
+ // result is not null, but may be undefined for missing keys
70
+ expect(result && result["content-type-2"]).toBeUndefined();
71
+ });
72
+
73
+ it("should use 'type' if 'plugin_type' is not present in river", () => {
74
+ const state = {
75
+ rivers: {
76
+ "river-1": {
77
+ type: "custom-type",
78
+ },
79
+ },
80
+ contentTypes: {
81
+ "content-type-1": {
82
+ screen_id: "river-1",
83
+ },
84
+ },
85
+ };
86
+
87
+ const result = mapContentTypesToRivers(state);
88
+
89
+ expect(result).toEqual({
90
+ "content-type-1": {
91
+ screenType: "custom-type",
92
+ screen_id: "river-1",
93
+ },
94
+ });
95
+ });
96
+
97
+ it("should skip content types if neither plugin_type nor type is present in river", () => {
98
+ const state = {
99
+ rivers: {
100
+ "river-1": {
101
+ // no plugin_type or type
102
+ },
103
+ },
104
+ contentTypes: {
105
+ "content-type-1": {
106
+ screen_id: "river-1",
107
+ },
108
+ },
109
+ };
110
+
111
+ const result = mapContentTypesToRivers(state);
112
+
113
+ expect(result).toEqual({});
114
+ });
115
+
116
+ it("should handle empty contentTypes object", () => {
117
+ const state = {
118
+ rivers: {
119
+ "river-1": {
120
+ plugin_type: "river",
121
+ },
122
+ },
123
+ contentTypes: {},
124
+ };
125
+
126
+ const result = mapContentTypesToRivers(state);
127
+
128
+ expect(result).toEqual({});
129
+ });
130
+ });
@@ -13,6 +13,7 @@ import {
13
13
  isPlayable,
14
14
  isV2River,
15
15
  } from "./itemTypeMatchers";
16
+ import { RootState } from "@applicaster/zapp-react-native-redux/store";
16
17
 
17
18
  type PathAttribute = {
18
19
  screenType: string;
@@ -377,10 +378,11 @@ export const usesVideoModal = (
377
378
  return targetScreenConfiguration?.styles?.use_video_modal;
378
379
  };
379
380
 
380
- export const mapContentTypesToRivers = ({
381
- rivers,
382
- contentTypes,
383
- }): ZappContentTypesMapped | null => {
381
+ export const mapContentTypesToRivers = (
382
+ state: Partial<RootState>
383
+ ): ZappContentTypesMapped | null => {
384
+ const { rivers, contentTypes } = state;
385
+
384
386
  if (!contentTypes) {
385
387
  return null;
386
388
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "14.0.0-alpha.1118824347",
3
+ "version": "14.0.0-alpha.1190505115",
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": "14.0.0-alpha.1118824347",
30
+ "@applicaster/applicaster-types": "14.0.0-alpha.1190505115",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -38,7 +38,6 @@
38
38
  "peerDependencies": {
39
39
  "@applicaster/zapp-pipes-v2-client": "*",
40
40
  "@react-native-community/netinfo": "*",
41
- "immer": "*",
42
41
  "react": "*",
43
42
  "react-native": "*",
44
43
  "uglify-js": "*",