@applicaster/zapp-react-native-utils 15.0.0-rc.99 → 15.1.0-alpha.2915689988

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 (160) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +51 -5
  2. package/actionsExecutor/feedDecorator.ts +6 -6
  3. package/adsUtils/index.ts +2 -2
  4. package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -2
  5. package/analyticsUtils/PlayerAnalyticsManager.ts +11 -1
  6. package/analyticsUtils/README.md +1 -1
  7. package/analyticsUtils/__tests__/analyticsMapper.test.ts +35 -0
  8. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testACP_events.json +1 -1
  9. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testBlockUnlistedParams_rules.json +1 -1
  10. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEmptyRenameKeepsName_events.json +4 -0
  11. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEmptyRenameKeepsName_rules.json +6 -0
  12. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEventRegex_events.json +3 -9
  13. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testIgnoreOrdering_events.json +18 -0
  14. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testIgnoreOrdering_rules.json +16 -0
  15. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexEventNoFallback_events.json +4 -0
  16. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexEventNoFallback_rules.json +6 -0
  17. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexMultiGroupRename_events.json +4 -0
  18. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexMultiGroupRename_rules.json +6 -0
  19. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexNonNamedFullMatch_events.json +4 -0
  20. package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexNonNamedFullMatch_rules.json +6 -0
  21. package/analyticsUtils/__tests__/fixtures/index.js +20 -0
  22. package/analyticsUtils/analyticsMapper.ts +4 -1
  23. package/analyticsUtils/playerAnalyticsTracker.ts +26 -3
  24. package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +188 -0
  25. package/appUtils/HooksManager/__tests__/hooksManager.test.js +2 -16
  26. package/appUtils/HooksManager/index.ts +47 -17
  27. package/appUtils/RiverFocusManager/{index.ts → index.js} +18 -25
  28. package/appUtils/accessibilityManager/const.ts +0 -4
  29. package/appUtils/accessibilityManager/utils.ts +1 -0
  30. package/appUtils/contextKeysManager/__tests__/getKeys/failure.test.ts +2 -7
  31. package/appUtils/contextKeysManager/__tests__/getKeys/success.test.ts +0 -48
  32. package/appUtils/contextKeysManager/contextResolver.ts +22 -51
  33. package/appUtils/contextKeysManager/index.ts +10 -65
  34. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +0 -3
  35. package/appUtils/focusManager/index.ios.ts +4 -43
  36. package/appUtils/focusManagerAux/utils/index.ts +3 -5
  37. package/appUtils/focusManagerAux/utils/utils.ios.ts +3 -199
  38. package/appUtils/keyCodes/keys/keys.web.ts +4 -1
  39. package/appUtils/localizationsHelper.ts +4 -0
  40. package/appUtils/orientationHelper.ts +4 -2
  41. package/appUtils/platform/platformUtils.ts +1 -1
  42. package/appUtils/playerManager/__tests__/playerFactory.test.ts +150 -0
  43. package/appUtils/playerManager/index.ts +9 -0
  44. package/appUtils/playerManager/player.ts +1 -5
  45. package/appUtils/playerManager/playerFactory.ts +17 -34
  46. package/appUtils/playerManager/playerNative.ts +22 -30
  47. package/appUtils/playerManager/usePlayer.tsx +5 -3
  48. package/appUtils/playerManager/usePlayerState.tsx +2 -14
  49. package/cellUtils/__tests__/cellUtils.test.ts +39 -0
  50. package/cellUtils/index.ts +11 -1
  51. package/cloudEventsUtils/index.ts +1 -65
  52. package/configurationUtils/__tests__/manifestKeyParser.test.ts +26 -26
  53. package/dateUtils/__tests__/dayjs.test.ts +0 -3
  54. package/dateUtils/index.ts +2 -0
  55. package/focusManager/aux/index.ts +1 -1
  56. package/headersUtils/__tests__/headersUtils.test.js +1 -11
  57. package/headersUtils/index.ts +1 -2
  58. package/manifestUtils/defaultManifestConfigurations/player.js +7 -75
  59. package/manifestUtils/tvAction/container/index.js +1 -1
  60. package/numberUtils/__tests__/toNumber.test.ts +0 -12
  61. package/numberUtils/index.ts +1 -19
  62. package/package.json +4 -4
  63. package/playerUtils/index.ts +24 -2
  64. package/pluginUtils/index.ts +4 -9
  65. package/reactHooks/advertising/index.ts +2 -2
  66. package/reactHooks/app/__tests__/useAppState.test.ts +1 -1
  67. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  68. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +2 -1
  69. package/reactHooks/casting/index.ts +1 -0
  70. package/reactHooks/casting/useIsCasting.tsx +57 -0
  71. package/reactHooks/cell-click/__tests__/index.test.js +3 -1
  72. package/reactHooks/componentsMap/index.ts +55 -0
  73. package/reactHooks/configuration/__tests__/index.test.tsx +1 -1
  74. package/reactHooks/connection/__tests__/index.test.js +1 -1
  75. package/reactHooks/debugging/__tests__/index.test.js +4 -4
  76. package/reactHooks/device/useIsTablet.tsx +19 -14
  77. package/reactHooks/device/useMemoizedIsTablet.ts +3 -3
  78. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +23 -32
  79. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +19 -19
  80. package/reactHooks/feed/__tests__/useEntryScreenId.test.tsx +1 -4
  81. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +30 -42
  82. package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +1 -1
  83. package/reactHooks/feed/__tests__/useInflatedUrl.test.tsx +1 -1
  84. package/reactHooks/feed/useBatchLoading.ts +1 -7
  85. package/reactHooks/feed/useEntryScreenId.ts +2 -2
  86. package/reactHooks/feed/useInflatedUrl.ts +1 -1
  87. package/reactHooks/feed/usePipesCacheReset.ts +1 -3
  88. package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +16 -13
  89. package/reactHooks/layout/__tests__/index.test.tsx +1 -1
  90. package/reactHooks/layout/__tests__/useLayoutVersion.test.tsx +1 -1
  91. package/reactHooks/layout/useDimensions/__tests__/{useDimensions.test.tsx → useDimensions.test.ts} +25 -105
  92. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -2
  93. package/reactHooks/navigation/__tests__/index.test.tsx +4 -2
  94. package/reactHooks/navigation/index.ts +6 -7
  95. package/reactHooks/navigation/useRoute.ts +6 -8
  96. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +10 -27
  97. package/reactHooks/player/__tests__/useAutoSeek._test.tsx +1 -1
  98. package/reactHooks/player/__tests__/useTapSeek._test.ts +1 -1
  99. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +1 -1
  100. package/reactHooks/resolvers/__tests__/useComponentResolver.test.tsx +1 -1
  101. package/reactHooks/resolvers/useCellResolver.ts +2 -6
  102. package/reactHooks/resolvers/useComponentResolver.ts +13 -9
  103. package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +2 -2
  104. package/reactHooks/screen/__tests__/useCurrentScreenIsHook.test.ts +103 -0
  105. package/reactHooks/screen/__tests__/useCurrentScreenIsStartupHook.test.ts +94 -0
  106. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +1 -1
  107. package/reactHooks/screen/__tests__/useScreenData.test.tsx +1 -1
  108. package/reactHooks/screen/__tests__/useTargetScreenData.test.tsx +4 -12
  109. package/reactHooks/screen/index.ts +6 -0
  110. package/reactHooks/screen/useCurrentScreenIsHook.ts +9 -0
  111. package/reactHooks/screen/useCurrentScreenIsStartupHook.ts +8 -0
  112. package/reactHooks/screen/useIsStandaloneFullscreen.ts +12 -0
  113. package/reactHooks/screen/useTargetScreenData.ts +2 -4
  114. package/reactHooks/state/__tests__/useComponentScreenState.test.ts +246 -0
  115. package/reactHooks/state/index.ts +2 -0
  116. package/reactHooks/state/useComponentScreenState.ts +45 -0
  117. package/reactHooks/state/useRivers.ts +1 -1
  118. package/reactHooks/usePluginConfiguration.ts +2 -2
  119. package/reactHooks/utils/__tests__/index.test.js +1 -1
  120. package/reactHooks/videoModal/hooks/useVideoModalScreenData.tsx +22 -4
  121. package/rectUtils/index.ts +2 -2
  122. package/screenPickerUtils/index.ts +5 -0
  123. package/screenState/__tests__/index.test.ts +1 -1
  124. package/stringUtils/index.ts +1 -1
  125. package/testUtils/index.tsx +21 -30
  126. package/typeGuards/index.ts +0 -4
  127. package/utils/__tests__/clone.test.ts +158 -0
  128. package/utils/clone.ts +7 -0
  129. package/utils/index.ts +1 -13
  130. package/utils/path.ts +3 -6
  131. package/utils/pathOr.ts +1 -5
  132. package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +2 -2
  133. package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +1 -1
  134. package/zappFrameworkUtils/loginPluginUtils.ts +1 -1
  135. package/adsUtils/__tests__/createVMAP.test.ts +0 -419
  136. package/appDataUtils/__tests__/urlScheme.test.ts +0 -678
  137. package/appUtils/focusManagerAux/utils/index.ios.ts +0 -122
  138. package/cloudEventsUtils/__tests__/index.test.ts +0 -529
  139. package/enumUtils/__tests__/getEnumKeyByEnumValue.test.ts +0 -207
  140. package/errorUtils/__tests__/GeneralError.test.ts +0 -97
  141. package/errorUtils/__tests__/HttpStatusCode.test.ts +0 -344
  142. package/errorUtils/__tests__/MissingPluginError.test.ts +0 -113
  143. package/errorUtils/__tests__/NetworkError.test.ts +0 -202
  144. package/errorUtils/__tests__/getParsedResponse.test.ts +0 -188
  145. package/errorUtils/__tests__/invariant.test.ts +0 -112
  146. package/numberUtils/__tests__/toPositiveNumber.test.ts +0 -165
  147. package/reactHooks/analytics/__tests__/useSendAnalyticsOnPress.test.ts +0 -537
  148. package/reactHooks/dev/__tests__/useReRenderLog.test.ts +0 -188
  149. package/reactHooks/ui/__tests__/useFadeOutWhenBlurred.test.ts +0 -580
  150. package/rectUtils/__tests__/index.test.ts +0 -549
  151. package/screenPickerUtils/__tests__/index.test.ts +0 -333
  152. package/searchUtils/const.ts +0 -7
  153. package/searchUtils/index.ts +0 -3
  154. package/time/__tests__/BackgroundTimer.test.ts +0 -156
  155. package/time/__tests__/Timer.test.ts +0 -236
  156. package/typeGuards/__tests__/isString.test.ts +0 -21
  157. package/utils/__tests__/mergeRight.test.ts +0 -48
  158. package/utils/__tests__/selectors.test.ts +0 -124
  159. package/utils/mergeRight.ts +0 -5
  160. package/utils/selectors.ts +0 -46
@@ -0,0 +1,94 @@
1
+ import { renderHook } from "@testing-library/react-native";
2
+ import { useCurrentScreenIsStartupHook } from "../useCurrentScreenIsStartupHook";
3
+
4
+ import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
5
+ import { isFilledArray } from "@applicaster/zapp-react-native-utils/arrayUtils";
6
+
7
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks");
8
+ jest.mock("@applicaster/zapp-react-native-utils/arrayUtils");
9
+
10
+ const mockUseNavigation = useNavigation as jest.Mock;
11
+ const mockIsFilledArray = isFilledArray as jest.Mock;
12
+
13
+ describe("useCurrentScreenIsStartupHook", () => {
14
+ beforeEach(() => {
15
+ jest.clearAllMocks();
16
+ });
17
+
18
+ it("returns true when startUpHooks is a filled array", () => {
19
+ mockUseNavigation.mockReturnValue({
20
+ startUpHooks: ["hook1"],
21
+ });
22
+
23
+ mockIsFilledArray.mockReturnValue(true);
24
+
25
+ const { result } = renderHook(() => useCurrentScreenIsStartupHook());
26
+
27
+ expect(result.current).toBe(true);
28
+ expect(mockIsFilledArray).toHaveBeenCalledWith(["hook1"]);
29
+ });
30
+
31
+ it('returns true when startUpHooks is "in_process"', () => {
32
+ mockUseNavigation.mockReturnValue({
33
+ startUpHooks: "in_process",
34
+ });
35
+
36
+ mockIsFilledArray.mockReturnValue(false);
37
+
38
+ const { result } = renderHook(() => useCurrentScreenIsStartupHook());
39
+
40
+ expect(result.current).toBe(true);
41
+ expect(mockIsFilledArray).toHaveBeenCalledWith("in_process");
42
+ });
43
+
44
+ it("returns false when startUpHooks is empty array", () => {
45
+ mockUseNavigation.mockReturnValue({
46
+ startUpHooks: [],
47
+ });
48
+
49
+ mockIsFilledArray.mockReturnValue(false);
50
+
51
+ const { result } = renderHook(() => useCurrentScreenIsStartupHook());
52
+
53
+ expect(result.current).toBe(false);
54
+ expect(mockIsFilledArray).toHaveBeenCalledWith([]);
55
+ });
56
+
57
+ it("returns false when startUpHooks is undefined", () => {
58
+ mockUseNavigation.mockReturnValue({
59
+ startUpHooks: undefined,
60
+ });
61
+
62
+ mockIsFilledArray.mockReturnValue(false);
63
+
64
+ const { result } = renderHook(() => useCurrentScreenIsStartupHook());
65
+
66
+ expect(result.current).toBe(false);
67
+ expect(mockIsFilledArray).toHaveBeenCalledWith(undefined);
68
+ });
69
+
70
+ it("returns false when startUpHooks is null", () => {
71
+ mockUseNavigation.mockReturnValue({
72
+ startUpHooks: null,
73
+ });
74
+
75
+ mockIsFilledArray.mockReturnValue(false);
76
+
77
+ const { result } = renderHook(() => useCurrentScreenIsStartupHook());
78
+
79
+ expect(result.current).toBe(false);
80
+ expect(mockIsFilledArray).toHaveBeenCalledWith(null);
81
+ });
82
+
83
+ it("prioritizes filled array over string comparison", () => {
84
+ mockUseNavigation.mockReturnValue({
85
+ startUpHooks: ["hook1"],
86
+ });
87
+
88
+ mockIsFilledArray.mockReturnValue(true);
89
+
90
+ const { result } = renderHook(() => useCurrentScreenIsStartupHook());
91
+
92
+ expect(result.current).toBe(true);
93
+ });
94
+ });
@@ -1,4 +1,4 @@
1
- import { renderHook } from "@testing-library/react-native";
1
+ import { renderHook } from "@testing-library/react-hooks";
2
2
 
3
3
  jest.mock(
4
4
  "@applicaster/zapp-react-native-ui-components/Components/River/useScreenConfiguration",
@@ -1,4 +1,4 @@
1
- import { renderHook } from "@testing-library/react-native";
1
+ import { renderHook } from "@testing-library/react-hooks";
2
2
  import configureStore from "redux-mock-store";
3
3
 
4
4
  const mockStore = configureStore();
@@ -1,24 +1,16 @@
1
1
  import * as React from "react";
2
2
  import { Provider } from "react-redux";
3
- import { renderHook } from "@testing-library/react-native";
3
+ import { renderHook } from "@testing-library/react-hooks";
4
4
  import configureStore from "redux-mock-store";
5
- import { thunk } from "redux-thunk";
5
+ import thunk from "redux-thunk";
6
6
 
7
7
  const mockStore = configureStore([thunk]);
8
8
 
9
9
  import { useTargetScreenData } from "../useTargetScreenData";
10
10
 
11
11
  describe("useTargetScreenData", function () {
12
- const river_id_2 = {
13
- id: "river_id_2",
14
- type: "any",
15
- };
16
-
17
- const river_id_1 = {
18
- id: "river_id_1",
19
- type: "any",
20
- };
21
-
12
+ const river_id_2 = {};
13
+ const river_id_1 = {};
22
14
  const screenId = "river_id_2";
23
15
  const entry = { id: "test", type: { value: "video" } };
24
16
 
@@ -12,3 +12,9 @@ export {
12
12
  } from "./useScreenContext";
13
13
 
14
14
  export { useScreenBackgroundColor } from "./useScreenBackgroundColor";
15
+
16
+ export { useCurrentScreenIsHook } from "./useCurrentScreenIsHook";
17
+
18
+ export { useCurrentScreenIsStartupHook } from "./useCurrentScreenIsStartupHook";
19
+
20
+ export { useIsStandaloneFullscreen } from "./useIsStandaloneFullscreen";
@@ -0,0 +1,9 @@
1
+ import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
2
+ import { last } from "@applicaster/zapp-react-native-utils/utils";
3
+ import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
4
+
5
+ export const useCurrentScreenIsHook = (): boolean => {
6
+ const { mainStack = [] } = useNavigation();
7
+
8
+ return toBooleanWithDefaultFalse(last(mainStack)?.route?.includes("hook"));
9
+ };
@@ -0,0 +1,8 @@
1
+ import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
2
+ import { isFilledArray } from "@applicaster/zapp-react-native-utils/arrayUtils";
3
+
4
+ export const useCurrentScreenIsStartupHook = (): boolean => {
5
+ const { startUpHooks } = useNavigation();
6
+
7
+ return isFilledArray(startUpHooks) || startUpHooks === "in_process";
8
+ };
@@ -0,0 +1,12 @@
1
+ import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
2
+ import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
3
+
4
+ export const useIsStandaloneFullscreen = (): boolean => {
5
+ const navigator = useNavigation();
6
+
7
+ return toBooleanWithDefaultFalse(
8
+ !navigator?.canGoBack() &&
9
+ // @ts-ignore
10
+ navigator?.screenData?.general?.allow_screen_plugin_presentation
11
+ );
12
+ };
@@ -1,8 +1,7 @@
1
1
  import React from "react";
2
- import { useContentTypes } from "@applicaster/zapp-react-native-redux/hooks";
2
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
3
3
  import * as R from "ramda";
4
4
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
5
- import { useRivers } from "../state";
6
5
 
7
6
  export function getTargetScreenData(
8
7
  entry: ZappEntry,
@@ -44,8 +43,7 @@ export function getTargetScreenDataFromEntry(entry: ZappEntry): ZappRiver {
44
43
  }
45
44
 
46
45
  export const useTargetScreenData = (entry: ZappEntry) => {
47
- const rivers = useRivers();
48
- const contentTypes = useContentTypes();
46
+ const { rivers, contentTypes } = usePickFromState(["rivers", "contentTypes"]);
49
47
 
50
48
  return React.useMemo(
51
49
  () => getTargetScreenData(entry, rivers, contentTypes),
@@ -0,0 +1,246 @@
1
+ import { renderHook, act } from "@testing-library/react-native";
2
+ import { useComponentScreenState } from "../useComponentScreenState";
3
+ import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
4
+
5
+ import { create } from "zustand";
6
+
7
+ const createMockStore = () => create(() => ({}));
8
+
9
+ // Mock the external dependencies
10
+ jest.mock(
11
+ "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext",
12
+ () => ({
13
+ useScreenContextV2: jest.fn(),
14
+ })
15
+ );
16
+
17
+ const mockUseScreenContextV2 = useScreenContextV2 as jest.MockedFunction<
18
+ typeof useScreenContextV2
19
+ >;
20
+
21
+ describe("useComponentScreenState", () => {
22
+ beforeEach(() => {
23
+ jest.clearAllMocks();
24
+ });
25
+
26
+ it("should return initial value when no state exists for componentId", () => {
27
+ const mockStore = createMockStore();
28
+
29
+ mockUseScreenContextV2.mockReturnValue({
30
+ _componentStateStore: mockStore,
31
+ } as any);
32
+
33
+ const initialValue = "default-value";
34
+ const componentId = "test-component";
35
+
36
+ const { result } = renderHook(() =>
37
+ useComponentScreenState(componentId, initialValue)
38
+ );
39
+
40
+ expect(result.current[0]).toBe(initialValue);
41
+ });
42
+
43
+ it("should return stored value when state exists for componentId", () => {
44
+ const mockStore = createMockStore();
45
+ mockStore.setState({ "existing-component": "stored-value" });
46
+
47
+ mockUseScreenContextV2.mockReturnValue({
48
+ _componentStateStore: mockStore,
49
+ } as any);
50
+
51
+ const { result } = renderHook(() =>
52
+ useComponentScreenState("existing-component", "default")
53
+ );
54
+
55
+ expect(result.current[0]).toBe("stored-value");
56
+ });
57
+
58
+ it("should update state when setter is called", () => {
59
+ const mockStore = createMockStore();
60
+
61
+ mockUseScreenContextV2.mockReturnValue({
62
+ _componentStateStore: mockStore,
63
+ } as any);
64
+
65
+ const { result } = renderHook(() =>
66
+ useComponentScreenState("update-test", "initial")
67
+ );
68
+
69
+ // Verify initial value
70
+ expect(result.current[0]).toBe("initial");
71
+
72
+ // Update the value
73
+ act(() => {
74
+ result.current[1]("updated-value");
75
+ });
76
+
77
+ // Verify updated value
78
+ expect(result.current[0]).toBe("updated-value");
79
+ });
80
+
81
+ it("should handle different data types correctly", () => {
82
+ const mockStore = createMockStore();
83
+
84
+ mockUseScreenContextV2.mockReturnValue({
85
+ _componentStateStore: mockStore,
86
+ } as any);
87
+
88
+ // Test with number
89
+ const { result: numberResult } = renderHook(() =>
90
+ useComponentScreenState<number>("number-test", 42)
91
+ );
92
+
93
+ expect(numberResult.current[0]).toBe(42);
94
+
95
+ // Test with boolean
96
+ const { result: boolResult } = renderHook(() =>
97
+ useComponentScreenState<boolean>("bool-test", true)
98
+ );
99
+
100
+ expect(boolResult.current[0]).toBe(true);
101
+
102
+ // Test with object
103
+ const testObj = { key: "value" };
104
+
105
+ const { result: objResult } = renderHook(() =>
106
+ useComponentScreenState<{ key: string }>("obj-test", testObj)
107
+ );
108
+
109
+ expect(objResult.current[0]).toEqual(testObj);
110
+ });
111
+
112
+ it("should maintain separate states for different componentIds", () => {
113
+ const mockStore = createMockStore();
114
+
115
+ mockUseScreenContextV2.mockReturnValue({
116
+ _componentStateStore: mockStore,
117
+ } as any);
118
+
119
+ const { result: firstResult } = renderHook(() =>
120
+ useComponentScreenState("first", "first-initial")
121
+ );
122
+
123
+ const { result: secondResult } = renderHook(() =>
124
+ useComponentScreenState("second", "second-initial")
125
+ );
126
+
127
+ // Verify initial values are separate
128
+ expect(firstResult.current[0]).toBe("first-initial");
129
+ expect(secondResult.current[0]).toBe("second-initial");
130
+
131
+ // Update first component's state
132
+ act(() => {
133
+ firstResult.current[1]("first-updated");
134
+ });
135
+
136
+ // Verify only first component's state changed
137
+ expect(firstResult.current[0]).toBe("first-updated");
138
+ expect(secondResult.current[0]).toBe("second-initial");
139
+ });
140
+
141
+ it("should return a memoized setter function", () => {
142
+ const mockStore = createMockStore();
143
+
144
+ mockUseScreenContextV2.mockReturnValue({
145
+ _componentStateStore: mockStore,
146
+ } as any);
147
+
148
+ const { result, rerender } = renderHook(
149
+ ({ id, initial }) => useComponentScreenState(id, initial),
150
+ {
151
+ initialProps: { id: "memo-test", initial: "initial" },
152
+ }
153
+ );
154
+
155
+ const firstSetter = result.current[1];
156
+
157
+ // Rerender with same props
158
+ rerender({ id: "memo-test", initial: "initial" });
159
+
160
+ const secondSetter = result.current[1];
161
+
162
+ // Setter should be the same instance due to useCallback
163
+ expect(firstSetter).toBe(secondSetter);
164
+ });
165
+
166
+ it("should update setter when componentId changes", () => {
167
+ const mockStore = createMockStore();
168
+
169
+ mockUseScreenContextV2.mockReturnValue({
170
+ _componentStateStore: mockStore,
171
+ } as any);
172
+
173
+ const { result, rerender } = renderHook(
174
+ ({ id, initial }) => useComponentScreenState(id, initial),
175
+ {
176
+ initialProps: { id: "old-id", initial: "initial" },
177
+ }
178
+ );
179
+
180
+ const firstSetter = result.current[1];
181
+
182
+ // Rerender with different componentId
183
+ rerender({ id: "new-id", initial: "initial" });
184
+
185
+ const secondSetter = result.current[1];
186
+
187
+ // Setter should be different because componentId changed
188
+ expect(firstSetter).not.toBe(secondSetter);
189
+ });
190
+
191
+ it("should call store.setState with correct parameters", () => {
192
+ const mockStore = createMockStore();
193
+ const setStateSpy = jest.spyOn(mockStore, "setState");
194
+
195
+ mockUseScreenContextV2.mockReturnValue({
196
+ _componentStateStore: mockStore,
197
+ } as any);
198
+
199
+ const { result } = renderHook(() =>
200
+ useComponentScreenState("state-test", "initial")
201
+ );
202
+
203
+ const newValue = "new-value";
204
+
205
+ act(() => {
206
+ result.current[1](newValue);
207
+ });
208
+
209
+ expect(setStateSpy).toHaveBeenCalledWith({
210
+ "state-test": newValue,
211
+ });
212
+ });
213
+
214
+ it("should handle falsy initial values correctly", () => {
215
+ const mockStore = createMockStore();
216
+
217
+ mockUseScreenContextV2.mockReturnValue({
218
+ _componentStateStore: mockStore,
219
+ } as any);
220
+
221
+ // Test with falsy values
222
+ const { result: nullResult } = renderHook(() =>
223
+ useComponentScreenState("null-test", null)
224
+ );
225
+
226
+ expect(nullResult.current[0]).toBeNull();
227
+
228
+ const { result: falseResult } = renderHook(() =>
229
+ useComponentScreenState("false-test", false)
230
+ );
231
+
232
+ expect(falseResult.current[0]).toBe(false);
233
+
234
+ const { result: zeroResult } = renderHook(() =>
235
+ useComponentScreenState("zero-test", 0)
236
+ );
237
+
238
+ expect(zeroResult.current[0]).toBe(0);
239
+
240
+ const { result: emptyStrResult } = renderHook(() =>
241
+ useComponentScreenState("empty-test", "")
242
+ );
243
+
244
+ expect(emptyStrResult.current[0]).toBe("");
245
+ });
246
+ });
@@ -3,3 +3,5 @@ export { useRivers } from "./useRivers";
3
3
  export { useHomeRiver, getHomeRiver } from "./useHomeRiver";
4
4
 
5
5
  export { ZStoreProvider, useZStore } from "./ZStoreProvider";
6
+
7
+ export { useComponentScreenState } from "./useComponentScreenState";
@@ -0,0 +1,45 @@
1
+ import * as React from "react";
2
+ import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
3
+ import { useStore } from "zustand";
4
+
5
+ /**
6
+ * A custom hook that provides persistent state storage across component re-mounts
7
+ * using the screen's component state store.
8
+ *
9
+ * @see {@link DOCS/adr/0010-useComponentScreenState.md} - ADR explaining why useComponentScreenState is used for List/Hero/Grid/Gallery components
10
+ *
11
+ * The state is shared across all components using the same componentId within the same screen context.
12
+ * If no value exists for the componentId, the initial value is returned as a fallback but is not persisted in the store.
13
+ * The initial value must be explicitly set using the returned setter function to persist it.
14
+ *
15
+ * @template T - The type of value being stored
16
+ * @param componentId - Unique identifier for this state in the component state store
17
+ * @param initialValue - The default value if no value exists for the componentId
18
+ * @returns A tuple containing the current value and a memoized setter function
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * const [count, setCount] = useComponentScreenState<number>('counter', 0);
23
+ * const [name, setName] = useComponentScreenState<string>('username', 'Anonymous');
24
+ * ```
25
+ */
26
+ export const useComponentScreenState = <T = unknown>(
27
+ componentId: string,
28
+ initialValue: T
29
+ ): [T, (value: T) => void] => {
30
+ const store = useScreenContextV2()._componentStateStore;
31
+
32
+ const value = useStore(
33
+ store,
34
+ (state) => (state[componentId] as T | undefined) ?? initialValue
35
+ );
36
+
37
+ const setValue = React.useCallback(
38
+ (nextValue: T) => {
39
+ store.setState({ [componentId]: nextValue });
40
+ },
41
+ [componentId, store]
42
+ );
43
+
44
+ return [value, setValue] as const;
45
+ };
@@ -3,6 +3,6 @@ import {
3
3
  selectRivers,
4
4
  } from "@applicaster/zapp-react-native-redux";
5
5
 
6
- export function useRivers() {
6
+ export function useRivers(): Record<string, ZappRiver> {
7
7
  return useAppSelector(selectRivers);
8
8
  }
@@ -1,7 +1,7 @@
1
1
  import * as R from "ramda";
2
2
  import { useMemo } from "react";
3
3
 
4
- import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
4
+ import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
5
5
 
6
6
  // importing this way prevents typescript packages importing from this
7
7
  // file to go crazy reporting errors on JS
@@ -25,7 +25,7 @@ export function parsePluginConfiguration(plugin, manifest) {
25
25
  }
26
26
 
27
27
  export function usePluginConfiguration<T, S>(identifier, manifest) {
28
- const plugins = usePlugins();
28
+ const { plugins } = usePickFromState(["plugins"]);
29
29
 
30
30
  const plugin = useMemo(
31
31
  () => R.find(R.propEq("identifier", identifier), plugins),
@@ -1,5 +1,5 @@
1
1
  import { usePrevious, useIsInitialRender } from "../";
2
- import { renderHook, cleanup } from "@testing-library/react-native";
2
+ import { renderHook, cleanup } from "@testing-library/react-hooks";
3
3
 
4
4
  describe("usePrevious", () => {
5
5
  it("renders", () => {
@@ -2,6 +2,12 @@ import React from "react";
2
2
  import { useContentTypes } from "@applicaster/zapp-react-native-redux/hooks";
3
3
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
4
4
  import { useRivers } from "../../state";
5
+ import { createLogger } from "../../../logger";
6
+
7
+ const { log_warning } = createLogger({
8
+ subsystem: "zapp-react-native-utils/reactHooks/videoModal",
9
+ category: "useVideoModalScreenData",
10
+ });
5
11
 
6
12
  export const useVideoModalScreenData = ():
7
13
  | (ZappEntry & { targetScreen: any }) // TODO: fix ZappEntry type ( was ZappRiver but conflict )
@@ -14,10 +20,22 @@ export const useVideoModalScreenData = ():
14
20
  const rivers = useRivers();
15
21
 
16
22
  return React.useMemo(() => {
17
- if (item?.type?.value) {
18
- const screenId = contentTypes[item?.type?.value].screen_id;
23
+ const itemType = item?.type?.value;
24
+
25
+ if (!itemType) {
26
+ return;
27
+ }
19
28
 
20
- return { ...item, targetScreen: rivers?.[screenId] };
29
+ const screenId = contentTypes[itemType]?.screen_id;
30
+
31
+ if (!screenId) {
32
+ log_warning(
33
+ `Type mapping is missing for item type: ${itemType}, title: ${item.title}`
34
+ );
35
+
36
+ return;
21
37
  }
22
- }, [item]);
38
+
39
+ return { ...item, targetScreen: rivers?.[screenId] };
40
+ }, [contentTypes, item, rivers]);
23
41
  };
@@ -9,7 +9,7 @@
9
9
  */
10
10
  export function measureDimensionalIntersection(direction, current, target) {
11
11
  if (direction.isHorizontal) {
12
- return measureVerticalIntersection(current, target);
12
+ return measureVerticalIntersectiont(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 measureVerticalIntersection(current, target) {
28
+ export function measureVerticalIntersectiont(current, target) {
29
29
  if (target.top >= current.bottom || target.bottom <= current.top) {
30
30
  return 0;
31
31
  }
@@ -1,3 +1,5 @@
1
+ import { endsWith } from "@applicaster/zapp-react-native-utils/utils";
2
+
1
3
  export const getFocusableId = (id) => `PickerItem.${id}`;
2
4
 
3
5
  export const getPickerSelectorId = (id) => `PickerSelector.${id}`;
@@ -11,3 +13,6 @@ export const getScreenPickerSelectorContainerId = (id) =>
11
13
 
12
14
  export const getScreenPickerContentContainerId = (id) =>
13
15
  `${getScreenPickerId(id)}-screen-container`;
16
+
17
+ export const isTabsScreenContentContainerId = (id) =>
18
+ endsWith("-screen-container", id);
@@ -1,4 +1,4 @@
1
- import { act, renderHook } from "@testing-library/react-native";
1
+ import { act, renderHook } from "@testing-library/react-hooks";
2
2
  import { useScreenState } from "../index";
3
3
 
4
4
  describe("useScreenState", () => {
@@ -4,7 +4,7 @@ declare const global;
4
4
  import * as R from "ramda";
5
5
  import camelize from "camelize";
6
6
  // @ts-ignore
7
- import Handlebars from "handlebars";
7
+ import Handlebars from "react-native-handlebars";
8
8
 
9
9
  global.Buffer = global.Buffer || require("buffer").Buffer;
10
10