@applicaster/zapp-react-native-ui-components 14.0.0-alpha.8419134002 → 14.0.0-alpha.8557119261

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 (105) hide show
  1. package/Components/AnimatedInOut/index.tsx +5 -3
  2. package/Components/AudioPlayer/mobile/Layout.tsx +1 -1
  3. package/Components/AudioPlayer/tv/helpers.tsx +10 -3
  4. package/Components/BaseFocusable/index.tsx +23 -12
  5. package/Components/Cell/__tests__/CellWIthFocusable.test.js +3 -2
  6. package/Components/Cell/index.js +1 -1
  7. package/Components/ComponentResolver/index.ts +1 -1
  8. package/Components/FeedLoader/FeedLoader.tsx +6 -15
  9. package/Components/FeedLoader/FeedLoaderHOC.tsx +21 -0
  10. package/Components/FeedLoader/index.js +2 -8
  11. package/Components/Focusable/Focusable.tsx +5 -3
  12. package/Components/Focusable/FocusableTvOS.tsx +3 -3
  13. package/Components/Focusable/FocusableiOS.tsx +2 -2
  14. package/Components/Focusable/__tests__/index.android.test.tsx +3 -0
  15. package/Components/Focusable/index.android.tsx +12 -8
  16. package/Components/Focusable/index.tsx +1 -1
  17. package/Components/FocusableList/index.tsx +4 -0
  18. package/Components/FreezeWithCallback/__tests__/index.test.tsx +67 -43
  19. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +42 -59
  20. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +13 -10
  21. package/Components/HandlePlayable/HandlePlayable.tsx +25 -9
  22. package/Components/Layout/TV/LayoutBackground.tsx +1 -1
  23. package/Components/Layout/TV/__tests__/index.test.tsx +0 -1
  24. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -0
  25. package/Components/MasterCell/DefaultComponents/Button.tsx +1 -1
  26. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -27
  27. package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +1 -1
  28. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +1 -1
  29. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +10 -10
  30. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +18 -18
  31. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/index.test.tsx +10 -10
  32. package/Components/MasterCell/elementMapper.tsx +1 -2
  33. package/Components/MasterCell/utils/behaviorProvider.ts +82 -14
  34. package/Components/MasterCell/utils/index.ts +11 -5
  35. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +13 -18
  36. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +9 -0
  37. package/Components/OfflineHandler/__tests__/index.test.tsx +26 -35
  38. package/Components/PlayerContainer/ErrorDisplay/index.ts +1 -1
  39. package/Components/PlayerContainer/PlayerContainer.tsx +41 -28
  40. package/Components/PlayerContainer/ProgramInfo/index.tsx +1 -1
  41. package/Components/PlayerContainer/index.ts +1 -1
  42. package/Components/River/ComponentsMap/ComponentsMap.tsx +0 -1
  43. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +378 -0
  44. package/Components/River/ComponentsMap/hooks/useLoadingState.ts +2 -2
  45. package/Components/River/RefreshControl.tsx +11 -17
  46. package/Components/River/TV/River.tsx +2 -17
  47. package/Components/River/TV/index.tsx +3 -1
  48. package/Components/River/TV/withPipesV1DataLoader.tsx +43 -0
  49. package/Components/River/TV/withRiverDataLoader.tsx +17 -0
  50. package/Components/River/TV/withTVEventHandler.tsx +1 -1
  51. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  52. package/Components/River/__tests__/river.test.js +12 -26
  53. package/Components/River/index.tsx +1 -1
  54. package/Components/Screen/__tests__/Screen.test.tsx +28 -29
  55. package/Components/ScreenRevealManager/ScreenRevealManager.ts +76 -0
  56. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +107 -0
  57. package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +96 -0
  58. package/Components/ScreenRevealManager/index.ts +1 -0
  59. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +79 -0
  60. package/Components/Tabs/TV/Tabs.android.tsx +0 -2
  61. package/Components/Tabs/Tabs.tsx +2 -3
  62. package/Components/Touchable/__tests__/__snapshots__/touchable.test.tsx.snap +34 -0
  63. package/Components/Touchable/__tests__/touchable.test.tsx +12 -17
  64. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +15 -9
  65. package/Components/VideoLive/animationUtils.ts +3 -3
  66. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +3 -9
  67. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +32 -8
  68. package/Components/VideoModal/PlayerDetails.tsx +24 -2
  69. package/Components/VideoModal/PlayerWrapper.tsx +26 -142
  70. package/Components/VideoModal/VideoModal.tsx +3 -17
  71. package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +5 -5
  72. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -7
  73. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +44 -240
  74. package/Components/VideoModal/hooks/index.ts +0 -2
  75. package/Components/VideoModal/hooks/useModalSize.ts +18 -2
  76. package/Components/VideoModal/utils.ts +6 -0
  77. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +12 -16
  78. package/Components/Viewport/ViewportTracker/__tests__/viewportTracker.test.js +84 -24
  79. package/Contexts/ConfigutaionContext/__tests__/ConfigurationProvider.test.tsx +3 -3
  80. package/Contexts/ScreenContext/index.tsx +46 -6
  81. package/Decorators/ConfigurationWrapper/__tests__/withConfigurationProvider.test.tsx +3 -3
  82. package/Decorators/ConfigurationWrapper/withConfigurationProvider.tsx +2 -2
  83. package/Decorators/RiverFeedLoader/__tests__/__snapshots__/riverFeedLoader.test.tsx.snap +221 -209
  84. package/Decorators/RiverFeedLoader/__tests__/riverFeedLoader.test.tsx +14 -16
  85. package/Decorators/RiverFeedLoader/__tests__/utils.test.ts +0 -20
  86. package/Decorators/RiverFeedLoader/index.tsx +22 -4
  87. package/Decorators/RiverFeedLoader/utils/index.ts +0 -18
  88. package/Decorators/RiverResolver/__tests__/riverResolver.test.tsx +3 -6
  89. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -0
  90. package/Decorators/ZappPipesDataConnector/__tests__/NullFeedResolver.test.tsx +78 -0
  91. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +205 -0
  92. package/Decorators/ZappPipesDataConnector/__tests__/StaticFeedResolver.test.tsx +251 -0
  93. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +368 -0
  94. package/Decorators/ZappPipesDataConnector/__tests__/utils.test.ts +39 -0
  95. package/Decorators/ZappPipesDataConnector/index.tsx +26 -293
  96. package/Decorators/ZappPipesDataConnector/resolvers/NullFeedResolver.tsx +25 -0
  97. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +87 -0
  98. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +266 -0
  99. package/Decorators/ZappPipesDataConnector/types.ts +29 -0
  100. package/Decorators/ZappPipesDataConnector/utils/mongoFilter.ts +738 -0
  101. package/Decorators/ZappPipesDataConnector/utils/useFilter.tsx +157 -0
  102. package/events/index.ts +1 -0
  103. package/package.json +5 -10
  104. package/Components/River/__tests__/__snapshots__/river.test.js.snap +0 -27
  105. package/Components/VideoModal/hooks/useBackgroundColor.ts +0 -10
@@ -4,15 +4,19 @@ import { DispatchProp } from "react-redux";
4
4
 
5
5
  import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useRoute";
6
6
 
7
- import { mapAndSplit } from "@applicaster/zapp-react-native-utils/arrayUtils";
7
+ import {
8
+ mapAndSplit,
9
+ mapPromises,
10
+ reducePromises,
11
+ } from "@applicaster/zapp-react-native-utils/arrayUtils";
8
12
 
9
13
  import {
10
14
  DATASOURCE_CHUNKS,
11
15
  getDatasourceUrl,
12
16
  ignoreComponentsWithClearCacheFlag,
13
- loadDatasources,
14
17
  usePipesContexts,
15
18
  } from "./utils";
19
+ import { useLoadPipesDataDispatch } from "@applicaster/zapp-react-native-utils/reactHooks";
16
20
 
17
21
  type RiverProps = {
18
22
  dispatch: DispatchProp;
@@ -25,9 +29,10 @@ export function WithRiverFeedLoader(Component: ZappComponent) {
25
29
  return function WrappedWithRiverFeedLoader(props: RiverProps) {
26
30
  const { river } = props;
27
31
  const { screenData, pathname } = useRoute();
28
-
29
32
  const pipesContexts = usePipesContexts(river.id, pathname);
30
33
 
34
+ const loadPipesDataDispatcher = useLoadPipesDataDispatch();
35
+
31
36
  const componentsToLoad = ignoreComponentsWithClearCacheFlag(
32
37
  river?.ui_components || []
33
38
  );
@@ -49,7 +54,20 @@ export function WithRiverFeedLoader(Component: ZappComponent) {
49
54
  item?.filter((item2) => item2 !== undefined)
50
55
  );
51
56
 
52
- loadDatasources(nonEmptyDataSources, river?.id, props.dispatch);
57
+ reducePromises<string, void>(
58
+ mapPromises<string, void>((url) =>
59
+ loadPipesDataDispatcher(
60
+ url,
61
+ {},
62
+ {
63
+ withResolvers: true,
64
+ withScreenRouteMapping: true,
65
+ }
66
+ )
67
+ ),
68
+ undefined,
69
+ nonEmptyDataSources
70
+ );
53
71
  }, []);
54
72
 
55
73
  return <Component {...props} />;
@@ -1,10 +1,4 @@
1
1
  import * as R from "ramda";
2
- import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
3
-
4
- import {
5
- mapPromises,
6
- reducePromises,
7
- } from "@applicaster/zapp-react-native-utils/arrayUtils";
8
2
 
9
3
  export { riverIsCurrentRoute, usePipesContexts } from "./usePipesContexts";
10
4
 
@@ -12,18 +6,6 @@ export { getDatasourceUrl } from "./getDatasourceUrl";
12
6
 
13
7
  export const DATASOURCE_CHUNKS = 10;
14
8
 
15
- export async function loadDatasources(urls: string[][], riverId, dispatch) {
16
- return reducePromises<string, void>(
17
- mapPromises<string, void>((url) => {
18
- if (url) {
19
- return dispatch(loadPipesData(url, { riverId }));
20
- }
21
- }),
22
- undefined,
23
- urls
24
- );
25
- }
26
-
27
9
  export const ignoreComponentsWithClearCacheFlag = R.reject(
28
10
  R.pathEq(["rules", "clear_cache_on_reload"], true)
29
11
  );
@@ -13,12 +13,9 @@ const rivers = {
13
13
  },
14
14
  };
15
15
 
16
- jest.doMock(
17
- "@applicaster/zapp-react-native-redux/hooks/usePickFromState",
18
- () => ({
19
- usePickFromState: jest.fn(() => ({ rivers })),
20
- })
21
- );
16
+ jest.doMock("@applicaster/zapp-react-native-redux/hooks", () => ({
17
+ usePickFromState: jest.fn(() => ({ rivers })),
18
+ }));
22
19
 
23
20
  jest.doMock(
24
21
  "@applicaster/zapp-react-native-utils/reactHooks/navigation/usePathname",
@@ -0,0 +1,25 @@
1
+ // ResolverSelector.tsx
2
+ import React from "react";
3
+ import { ComponentDataSourceContext, ZappPipesDataProps } from "./types";
4
+ import { StaticFeedResolver } from "./resolvers/StaticFeedResolver";
5
+ import { UrlFeedResolver } from "./resolvers/UrlFeedResolver";
6
+ import { NullFeedResolver } from "./resolvers/NullFeedResolver";
7
+
8
+ type ResolverSelectorProps = ComponentDataSourceContext & {
9
+ children: (dataProps: ZappPipesDataProps) => React.ReactNode;
10
+ };
11
+
12
+ export function ResolverSelector(props: ResolverSelectorProps) {
13
+ const { getStaticComponentFeed, component, feedUrl, children } = props;
14
+
15
+ // Determine which resolver to use
16
+ if (getStaticComponentFeed) {
17
+ return <StaticFeedResolver {...props}>{children}</StaticFeedResolver>;
18
+ }
19
+
20
+ if (feedUrl || component?.data?.source) {
21
+ return <UrlFeedResolver {...props}>{children}</UrlFeedResolver>;
22
+ }
23
+
24
+ return <NullFeedResolver>{children}</NullFeedResolver>;
25
+ }
@@ -0,0 +1,78 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import { render } from "@testing-library/react-native";
4
+ import { NullFeedResolver } from "../resolvers/NullFeedResolver";
5
+ import { ReloadDataFunction } from "@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedLoader";
6
+
7
+ type MockChildrenArgs = {
8
+ zappPipesData?: {
9
+ loading: boolean;
10
+ data?: any;
11
+ error?: Error | null;
12
+ };
13
+ reloadData?: ReloadDataFunction;
14
+ loadNextData?: () => void;
15
+ };
16
+
17
+ describe("NullFeedResolver", () => {
18
+ it("should render children with correct default props", () => {
19
+ const mockChildren = jest.fn((_args: MockChildrenArgs) => (
20
+ <View testID="mock-children" />
21
+ ));
22
+
23
+ render(<NullFeedResolver>{mockChildren}</NullFeedResolver>);
24
+
25
+ expect(mockChildren).toHaveBeenCalledWith(
26
+ expect.objectContaining({
27
+ zappPipesData: {
28
+ url: "",
29
+ loading: false,
30
+ data: null,
31
+ error: null,
32
+ },
33
+ reloadData: expect.any(Function),
34
+ loadNextData: undefined,
35
+ })
36
+ );
37
+ });
38
+
39
+ it("should render children component", () => {
40
+ const { getByTestId } = render(
41
+ <NullFeedResolver>{() => <View testID="test-child" />}</NullFeedResolver>
42
+ );
43
+
44
+ expect(getByTestId("test-child")).toBeTruthy();
45
+ });
46
+
47
+ it("should create a memoized data object that doesn't change on re-renders", () => {
48
+ const mockChildren = jest.fn(() => <View testID="mock-children" />);
49
+
50
+ const { rerender } = render(
51
+ <NullFeedResolver>{mockChildren}</NullFeedResolver>
52
+ );
53
+
54
+ expect(mockChildren).toHaveBeenCalled();
55
+ // @ts-ignore
56
+ const firstCallDataProps = mockChildren.mock.calls[0][0];
57
+
58
+ rerender(<NullFeedResolver>{mockChildren}</NullFeedResolver>);
59
+
60
+ // @ts-ignore
61
+ const secondCallDataProps = mockChildren.mock.calls[1][0];
62
+
63
+ expect(firstCallDataProps).toBe(secondCallDataProps);
64
+ });
65
+
66
+ it("should have reloadData function that returns null", () => {
67
+ const mockChildren = jest.fn(() => <View testID="mock-children" />);
68
+
69
+ render(<NullFeedResolver>{mockChildren}</NullFeedResolver>);
70
+
71
+ expect(mockChildren).toHaveBeenCalled();
72
+
73
+ // @ts-ignore
74
+ const { reloadData } = mockChildren.mock.calls[0][0];
75
+
76
+ expect(reloadData()).toBeNull();
77
+ });
78
+ });
@@ -0,0 +1,205 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import { render } from "@testing-library/react-native";
4
+ import { ResolverSelector } from "../ResolverSelector";
5
+ import { StaticFeedResolver } from "../resolvers/StaticFeedResolver";
6
+ import { UrlFeedResolver } from "../resolvers/UrlFeedResolver";
7
+ import { NullFeedResolver } from "../resolvers/NullFeedResolver";
8
+
9
+ jest.mock("../resolvers/StaticFeedResolver", () => ({
10
+ StaticFeedResolver: jest.fn(() => null),
11
+ }));
12
+
13
+ jest.mock("../resolvers/UrlFeedResolver", () => ({
14
+ UrlFeedResolver: jest.fn(() => null),
15
+ }));
16
+
17
+ jest.mock("../resolvers/NullFeedResolver", () => ({
18
+ NullFeedResolver: jest.fn(() => null),
19
+ }));
20
+
21
+ const testPlugin = {
22
+ identifier: "test-plugin",
23
+ name: "test",
24
+ type: "general" as PluginType,
25
+ module: jest.fn(),
26
+ };
27
+
28
+ describe("ResolverSelector", () => {
29
+ const mockChildren = jest.fn(() => <View testID="mock-children" />);
30
+
31
+ const mockComponent = {
32
+ id: "test-component",
33
+ type: "vertical_list",
34
+ styles: {},
35
+ } as any;
36
+
37
+ beforeEach(() => {
38
+ jest.clearAllMocks();
39
+ });
40
+
41
+ it("should render StaticFeedResolver when getStaticComponentFeed is provided", () => {
42
+ const props = {
43
+ getStaticComponentFeed: jest.fn(),
44
+ component: mockComponent,
45
+ children: mockChildren,
46
+ riverId: "test-river",
47
+ };
48
+
49
+ render(<ResolverSelector {...props} />);
50
+
51
+ expect(StaticFeedResolver).toHaveBeenCalledWith(
52
+ expect.objectContaining({
53
+ getStaticComponentFeed: props.getStaticComponentFeed,
54
+ component: mockComponent,
55
+ children: mockChildren,
56
+ }),
57
+ expect.anything()
58
+ );
59
+
60
+ expect(UrlFeedResolver).not.toHaveBeenCalled();
61
+ expect(NullFeedResolver).not.toHaveBeenCalled();
62
+ });
63
+
64
+ it("should render UrlFeedResolver when feedUrl is provided", () => {
65
+ const props = {
66
+ feedUrl: "https://example.com/feed",
67
+ component: mockComponent,
68
+ children: mockChildren,
69
+ riverId: "test-river",
70
+ };
71
+
72
+ render(<ResolverSelector {...props} />);
73
+
74
+ expect(UrlFeedResolver).toHaveBeenCalledWith(
75
+ expect.objectContaining({
76
+ feedUrl: props.feedUrl,
77
+ component: mockComponent,
78
+ children: mockChildren,
79
+ }),
80
+ expect.anything()
81
+ );
82
+
83
+ expect(StaticFeedResolver).not.toHaveBeenCalled();
84
+ expect(NullFeedResolver).not.toHaveBeenCalled();
85
+ });
86
+
87
+ it("should render UrlFeedResolver when component.data.source is provided", () => {
88
+ const componentWithSource = {
89
+ ...mockComponent,
90
+ data: {
91
+ source: "data-source",
92
+ },
93
+ };
94
+
95
+ const props = {
96
+ component: componentWithSource,
97
+ children: mockChildren,
98
+ riverId: "test-river",
99
+ };
100
+
101
+ render(<ResolverSelector {...props} />);
102
+
103
+ expect(UrlFeedResolver).toHaveBeenCalledWith(
104
+ expect.objectContaining({
105
+ component: componentWithSource,
106
+ children: mockChildren,
107
+ }),
108
+ expect.anything()
109
+ );
110
+
111
+ expect(StaticFeedResolver).not.toHaveBeenCalled();
112
+ expect(NullFeedResolver).not.toHaveBeenCalled();
113
+ });
114
+
115
+ it("should render NullFeedResolver when no data source is provided", () => {
116
+ const props = {
117
+ component: mockComponent,
118
+ children: mockChildren,
119
+ riverId: "test-river",
120
+ };
121
+
122
+ render(<ResolverSelector {...props} />);
123
+
124
+ expect(NullFeedResolver).toHaveBeenCalledWith(
125
+ expect.objectContaining({
126
+ children: mockChildren,
127
+ }),
128
+ expect.anything()
129
+ );
130
+
131
+ expect(StaticFeedResolver).not.toHaveBeenCalled();
132
+ expect(UrlFeedResolver).not.toHaveBeenCalled();
133
+ });
134
+
135
+ it("should pass all props to StaticFeedResolver", () => {
136
+ const props = {
137
+ getStaticComponentFeed: jest.fn(),
138
+ component: mockComponent,
139
+ children: mockChildren,
140
+ riverId: "test-river",
141
+ isLast: true,
142
+ componentIndex: 1,
143
+ isScreenWrappedInContainer: true,
144
+ entryContext: { entry: "test" },
145
+ screenContext: { screen: "test" },
146
+ searchContext: "search",
147
+ screenData: { data: "test" },
148
+ plugins: [testPlugin],
149
+ };
150
+
151
+ render(<ResolverSelector {...props} />);
152
+
153
+ expect(StaticFeedResolver).toHaveBeenCalledWith(
154
+ expect.objectContaining(props),
155
+ expect.anything()
156
+ );
157
+ });
158
+
159
+ it("should pass all props to UrlFeedResolver", () => {
160
+ const props = {
161
+ feedUrl: "https://example.com/feed",
162
+ component: mockComponent,
163
+ children: mockChildren,
164
+ riverId: "test-river",
165
+ isLast: true,
166
+ componentIndex: 1,
167
+ isScreenWrappedInContainer: true,
168
+ entryContext: { entry: "test" },
169
+ screenContext: { screen: "test" },
170
+ searchContext: "search",
171
+ screenData: { data: "test" },
172
+ plugins: [testPlugin],
173
+ };
174
+
175
+ render(<ResolverSelector {...props} />);
176
+
177
+ expect(UrlFeedResolver).toHaveBeenCalledWith(
178
+ expect.objectContaining(props),
179
+ expect.anything()
180
+ );
181
+ });
182
+
183
+ it("should prioritize StaticFeedResolver over UrlFeedResolver when both conditions are met", () => {
184
+ const componentWithSource = {
185
+ ...mockComponent,
186
+ data: {
187
+ source: "data-source",
188
+ },
189
+ };
190
+
191
+ const props = {
192
+ getStaticComponentFeed: jest.fn(),
193
+ feedUrl: "https://example.com/feed",
194
+ component: componentWithSource,
195
+ children: mockChildren,
196
+ riverId: "test-river",
197
+ };
198
+
199
+ render(<ResolverSelector {...props} />);
200
+
201
+ expect(StaticFeedResolver).toHaveBeenCalled();
202
+ expect(UrlFeedResolver).not.toHaveBeenCalled();
203
+ expect(NullFeedResolver).not.toHaveBeenCalled();
204
+ });
205
+ });
@@ -0,0 +1,251 @@
1
+ import React from "react";
2
+ import { render, act, waitFor } from "@testing-library/react-native";
3
+ import { StaticFeedResolver } from "../resolvers/StaticFeedResolver";
4
+ import { View } from "react-native";
5
+ import { ReloadDataFunction } from "@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedLoader";
6
+
7
+ type MockChildrenArgs = {
8
+ zappPipesData?: {
9
+ loading: boolean;
10
+ data?: any;
11
+ error?: Error | null;
12
+ };
13
+ reloadData?: ReloadDataFunction;
14
+ };
15
+
16
+ describe("StaticFeedResolver", () => {
17
+ const mockChildren = jest.fn((_args: MockChildrenArgs) => (
18
+ <View data-testid="mock-children" />
19
+ ));
20
+
21
+ const mockComponent = {
22
+ id: "test-component",
23
+ position: 1,
24
+ } as any;
25
+
26
+ const mockGetStaticComponentFeed = jest.fn();
27
+
28
+ beforeEach(() => {
29
+ jest.clearAllMocks();
30
+ mockGetStaticComponentFeed.mockResolvedValue({ entry: [{ id: "1" }] });
31
+ });
32
+
33
+ it("should generate correct static URL based on component", () => {
34
+ render(
35
+ <StaticFeedResolver
36
+ getStaticComponentFeed={mockGetStaticComponentFeed}
37
+ component={mockComponent}
38
+ componentIndex={0}
39
+ riverId="test-river"
40
+ >
41
+ {mockChildren}
42
+ </StaticFeedResolver>
43
+ );
44
+
45
+ expect(mockChildren).toHaveBeenCalledWith(
46
+ expect.objectContaining({
47
+ zappPipesData: expect.objectContaining({
48
+ url: "static://component?id=test-component&position=1",
49
+ }),
50
+ })
51
+ );
52
+ });
53
+
54
+ it("should start with loading state", () => {
55
+ render(
56
+ <StaticFeedResolver
57
+ getStaticComponentFeed={mockGetStaticComponentFeed}
58
+ component={mockComponent}
59
+ componentIndex={0}
60
+ riverId="test-river"
61
+ >
62
+ {mockChildren}
63
+ </StaticFeedResolver>
64
+ );
65
+
66
+ expect(mockChildren).toHaveBeenCalledWith(
67
+ expect.objectContaining({
68
+ zappPipesData: expect.objectContaining({
69
+ loading: true,
70
+ data: null,
71
+ error: null,
72
+ }),
73
+ })
74
+ );
75
+ });
76
+
77
+ it("should fetch data when mounted", async () => {
78
+ render(
79
+ <StaticFeedResolver
80
+ getStaticComponentFeed={mockGetStaticComponentFeed}
81
+ component={mockComponent}
82
+ componentIndex={0}
83
+ riverId="test-river"
84
+ >
85
+ {mockChildren}
86
+ </StaticFeedResolver>
87
+ );
88
+
89
+ expect(mockGetStaticComponentFeed).toHaveBeenCalledWith({
90
+ index: 0,
91
+ component: mockComponent,
92
+ });
93
+
94
+ await waitFor(() => {
95
+ expect(mockChildren).toHaveBeenLastCalledWith(
96
+ expect.objectContaining({
97
+ zappPipesData: expect.objectContaining({
98
+ loading: false,
99
+ data: { entry: [{ id: "1" }] },
100
+ }),
101
+ })
102
+ );
103
+ });
104
+ });
105
+
106
+ it("should handle errors from getStaticComponentFeed", async () => {
107
+ const error = new Error("Test error");
108
+ mockGetStaticComponentFeed.mockRejectedValue(error);
109
+
110
+ render(
111
+ <StaticFeedResolver
112
+ getStaticComponentFeed={mockGetStaticComponentFeed}
113
+ component={mockComponent}
114
+ componentIndex={0}
115
+ riverId="test-river"
116
+ >
117
+ {mockChildren}
118
+ </StaticFeedResolver>
119
+ );
120
+
121
+ await waitFor(() => {
122
+ expect(mockChildren).toHaveBeenLastCalledWith(
123
+ expect.objectContaining({
124
+ zappPipesData: expect.objectContaining({
125
+ loading: false,
126
+ error,
127
+ }),
128
+ })
129
+ );
130
+ });
131
+ });
132
+
133
+ it("should provide a reloadData function that refreshes the data", async () => {
134
+ render(
135
+ <StaticFeedResolver
136
+ getStaticComponentFeed={mockGetStaticComponentFeed}
137
+ component={mockComponent}
138
+ componentIndex={0}
139
+ riverId="test-river"
140
+ >
141
+ {mockChildren}
142
+ </StaticFeedResolver>
143
+ );
144
+
145
+ await waitFor(() => {
146
+ expect(mockChildren).toHaveBeenLastCalledWith(
147
+ expect.objectContaining({
148
+ zappPipesData: expect.objectContaining({
149
+ loading: false,
150
+ }),
151
+ })
152
+ );
153
+ });
154
+
155
+ const { reloadData } =
156
+ mockChildren.mock.calls[mockChildren.mock.calls.length - 1][0];
157
+
158
+ mockGetStaticComponentFeed.mockClear();
159
+ mockGetStaticComponentFeed.mockResolvedValue({ entry: [{ id: "2" }] });
160
+
161
+ await act(async () => {
162
+ await reloadData();
163
+ });
164
+
165
+ expect(mockGetStaticComponentFeed).toHaveBeenCalledWith({
166
+ index: 0,
167
+ component: mockComponent,
168
+ });
169
+
170
+ await waitFor(() => {
171
+ expect(mockChildren).toHaveBeenLastCalledWith(
172
+ expect.objectContaining({
173
+ zappPipesData: expect.objectContaining({
174
+ data: { entry: [{ id: "2" }] },
175
+ }),
176
+ })
177
+ );
178
+ });
179
+ });
180
+
181
+ it("should handle errors in reloadData", async () => {
182
+ render(
183
+ <StaticFeedResolver
184
+ getStaticComponentFeed={mockGetStaticComponentFeed}
185
+ component={mockComponent}
186
+ componentIndex={0}
187
+ riverId="test-river"
188
+ >
189
+ {mockChildren}
190
+ </StaticFeedResolver>
191
+ );
192
+
193
+ await waitFor(() => {
194
+ expect(mockChildren).toHaveBeenLastCalledWith(
195
+ expect.objectContaining({
196
+ zappPipesData: expect.objectContaining({
197
+ loading: false,
198
+ }),
199
+ })
200
+ );
201
+ });
202
+
203
+ const { reloadData } =
204
+ mockChildren.mock.calls[mockChildren.mock.calls.length - 1][0];
205
+
206
+ const error = new Error("Reload error");
207
+ mockGetStaticComponentFeed.mockRejectedValue(error);
208
+
209
+ await expect(reloadData()).rejects.toEqual(error);
210
+
211
+ await waitFor(() => {
212
+ expect(mockChildren).toHaveBeenLastCalledWith(
213
+ expect.objectContaining({
214
+ zappPipesData: expect.objectContaining({
215
+ error,
216
+ }),
217
+ })
218
+ );
219
+ });
220
+ });
221
+
222
+ it("should not fetch data if getStaticComponentFeed is not provided", async () => {
223
+ render(
224
+ <StaticFeedResolver
225
+ component={mockComponent}
226
+ componentIndex={0}
227
+ riverId="test-river"
228
+ >
229
+ {mockChildren}
230
+ </StaticFeedResolver>
231
+ );
232
+
233
+ expect(mockGetStaticComponentFeed).not.toHaveBeenCalled();
234
+ });
235
+
236
+ it("should provide reloadData function that resolves immediately if getStaticComponentFeed is not provided", async () => {
237
+ render(
238
+ <StaticFeedResolver
239
+ component={mockComponent}
240
+ componentIndex={0}
241
+ riverId="test-river"
242
+ >
243
+ {mockChildren}
244
+ </StaticFeedResolver>
245
+ );
246
+
247
+ const { reloadData } = mockChildren.mock.calls[0][0];
248
+
249
+ await expect(reloadData()).resolves.toBeUndefined();
250
+ });
251
+ });