@applicaster/zapp-react-native-ui-components 14.0.0-alpha.2332850672 → 14.0.0-alpha.2385258459

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 (108) hide show
  1. package/Components/AnimatedInOut/index.tsx +5 -3
  2. package/Components/AudioPlayer/index.tsx +15 -0
  3. package/Components/AudioPlayer/{AudioPlayerMobileLayout.tsx → mobile/Layout.tsx} +10 -5
  4. package/Components/AudioPlayer/{__tests__ → mobile/__tests__}/__snapshots__/audioPlayerMobileLayout.test.js.snap +1 -1
  5. package/Components/AudioPlayer/{__tests__ → mobile/__tests__}/audioPlayerMobileLayout.test.js +2 -2
  6. package/Components/AudioPlayer/mobile/index.tsx +18 -0
  7. package/Components/AudioPlayer/{Artwork.tsx → tv/Artwork.tsx} +3 -2
  8. package/Components/AudioPlayer/{Channel.tsx → tv/Channel.tsx} +7 -7
  9. package/Components/AudioPlayer/tv/Layout.tsx +168 -0
  10. package/Components/AudioPlayer/{Runtime.tsx → tv/Runtime.tsx} +7 -1
  11. package/Components/AudioPlayer/{Summary.tsx → tv/Summary.tsx} +6 -2
  12. package/Components/AudioPlayer/{Title.tsx → tv/Title.tsx} +6 -2
  13. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/Runtime.test.js.snap +2 -2
  14. package/Components/AudioPlayer/tv/__tests__/__snapshots__/audioPlayer.test.js.snap +164 -0
  15. package/Components/AudioPlayer/tv/__tests__/__snapshots__/channel.test.js.snap +19 -0
  16. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/summary.test.js.snap +1 -2
  17. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/title.test.js.snap +1 -2
  18. package/Components/AudioPlayer/{__tests__ → tv/__tests__}/audioPlayer.test.js +7 -3
  19. package/Components/AudioPlayer/{helpers.tsx → tv/helpers.tsx} +11 -5
  20. package/Components/AudioPlayer/{AudioPlayer.tsx → tv/index.tsx} +21 -97
  21. package/Components/AudioPlayer/types.ts +40 -0
  22. package/Components/Cell/index.js +7 -3
  23. package/Components/ComponentResolver/index.ts +1 -1
  24. package/Components/FeedLoader/index.js +1 -1
  25. package/Components/Focusable/Focusable.tsx +5 -3
  26. package/Components/Focusable/FocusableTvOS.tsx +3 -3
  27. package/Components/Focusable/FocusableiOS.tsx +2 -2
  28. package/Components/Focusable/__tests__/index.android.test.tsx +3 -0
  29. package/Components/Focusable/index.android.tsx +12 -8
  30. package/Components/Focusable/index.tsx +1 -1
  31. package/Components/FocusableList/index.tsx +4 -0
  32. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +42 -59
  33. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +12 -8
  34. package/Components/HandlePlayable/HandlePlayable.tsx +25 -9
  35. package/Components/Layout/TV/LayoutBackground.tsx +1 -1
  36. package/Components/MasterCell/DefaultComponents/Button.tsx +1 -1
  37. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -27
  38. package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +1 -1
  39. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +1 -1
  40. package/Components/MasterCell/elementMapper.tsx +1 -2
  41. package/Components/MasterCell/index.tsx +1 -1
  42. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +13 -18
  43. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +9 -0
  44. package/Components/OfflineHandler/__tests__/index.test.tsx +20 -22
  45. package/Components/PlayerContainer/ErrorDisplay/index.ts +1 -1
  46. package/Components/PlayerContainer/PlayerContainer.tsx +42 -32
  47. package/Components/PlayerContainer/ProgramInfo/index.tsx +1 -1
  48. package/Components/PlayerContainer/index.ts +1 -1
  49. package/Components/PlayerImageBackground/index.tsx +1 -1
  50. package/Components/River/ComponentsMap/ComponentsMap.tsx +0 -1
  51. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +379 -0
  52. package/Components/River/ComponentsMap/hooks/useLoadingState.ts +2 -2
  53. package/Components/River/RefreshControl.tsx +6 -4
  54. package/Components/River/TV/River.tsx +2 -17
  55. package/Components/River/TV/index.tsx +3 -1
  56. package/Components/River/TV/withPipesV1DataLoader.tsx +43 -0
  57. package/Components/River/TV/withRiverDataLoader.tsx +17 -0
  58. package/Components/River/TV/withTVEventHandler.tsx +1 -1
  59. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  60. package/Components/River/index.tsx +1 -1
  61. package/Components/Screen/__tests__/Screen.test.tsx +23 -12
  62. package/Components/ScreenRevealManager/ScreenRevealManager.ts +76 -0
  63. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +107 -0
  64. package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +96 -0
  65. package/Components/ScreenRevealManager/index.ts +1 -0
  66. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +79 -0
  67. package/Components/Tabs/TV/Tabs.android.tsx +0 -2
  68. package/Components/Touchable/__tests__/__snapshots__/touchable.test.tsx.snap +34 -0
  69. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +15 -9
  70. package/Components/VideoLive/animationUtils.ts +3 -3
  71. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +32 -8
  72. package/Components/VideoModal/PlayerDetails.tsx +24 -2
  73. package/Components/VideoModal/PlayerWrapper.tsx +26 -142
  74. package/Components/VideoModal/VideoModal.tsx +3 -17
  75. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -7
  76. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +44 -240
  77. package/Components/VideoModal/hooks/index.ts +0 -2
  78. package/Components/VideoModal/hooks/useModalSize.ts +18 -2
  79. package/Components/VideoModal/utils.ts +6 -0
  80. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +12 -16
  81. package/Components/Viewport/ViewportTracker/__tests__/viewportTracker.test.js +84 -24
  82. package/Components/Viewport/VisibilitySensor/VisibilitySensor.tsx +3 -3
  83. package/Components/default-cell-renderer/viewTrees/tv/DefaultCell/index.ts +3 -3
  84. package/Decorators/ConfigurationWrapper/withConfigurationProvider.tsx +2 -2
  85. package/Decorators/RiverResolver/__tests__/riverResolver.test.tsx +3 -6
  86. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -0
  87. package/Decorators/ZappPipesDataConnector/__tests__/NullFeedResolver.test.tsx +78 -0
  88. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +205 -0
  89. package/Decorators/ZappPipesDataConnector/__tests__/StaticFeedResolver.test.tsx +251 -0
  90. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +368 -0
  91. package/Decorators/ZappPipesDataConnector/__tests__/utils.test.ts +39 -0
  92. package/Decorators/ZappPipesDataConnector/index.tsx +26 -293
  93. package/Decorators/ZappPipesDataConnector/resolvers/NullFeedResolver.tsx +25 -0
  94. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +87 -0
  95. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +241 -0
  96. package/Decorators/ZappPipesDataConnector/types.ts +29 -0
  97. package/package.json +5 -10
  98. package/Components/AudioPlayer/AudioPlayerTVLayout.tsx +0 -161
  99. package/Components/AudioPlayer/__tests__/__snapshots__/audioPlayer.test.js.snap +0 -66
  100. package/Components/AudioPlayer/__tests__/__snapshots__/channel.test.js.snap +0 -28
  101. package/Components/AudioPlayer/index.ts +0 -1
  102. package/Components/VideoModal/hooks/useBackgroundColor.ts +0 -10
  103. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/Runtime.test.js +0 -0
  104. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/__snapshots__/artWork.test.js.snap +0 -0
  105. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/artWork.test.js +0 -0
  106. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/channel.test.js +0 -0
  107. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/summary.test.js +0 -0
  108. /package/Components/AudioPlayer/{__tests__ → tv/__tests__}/title.test.js +0 -0
@@ -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
+ });
@@ -0,0 +1,368 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react-native";
3
+ import * as useFeedLoaderModule from "@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedLoader";
4
+ import * as useFeedRefreshModule from "@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedRefresh";
5
+ import * as zappPipesClientModule from "@applicaster/zapp-pipes-v2-client";
6
+ import { favoritesListener } from "@applicaster/zapp-react-native-bridge/Favorites";
7
+ import { UrlFeedResolver } from "../resolvers/UrlFeedResolver";
8
+
9
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedLoader");
10
+
11
+ jest.mock(
12
+ "@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedRefresh"
13
+ );
14
+
15
+ jest.mock("@applicaster/zapp-pipes-v2-client");
16
+
17
+ jest.mock("@applicaster/zapp-react-native-bridge/Favorites", () => ({
18
+ favoritesListener: {
19
+ on: jest.fn(),
20
+ removeHandler: jest.fn(),
21
+ },
22
+ }));
23
+
24
+ jest.mock("../utils", () => ({
25
+ isVerticalListOrGrid: jest.fn(
26
+ (component) =>
27
+ component?.type === "vertical_list" || component?.type === "vertical_grid"
28
+ ),
29
+ }));
30
+
31
+ const componentRequiredKeys = { rules: { item_limit: 10 } };
32
+
33
+ describe("UrlFeedResolver", () => {
34
+ const mockChildren = jest.fn(() => <div data-testid="mock-children" />);
35
+ const mockReloadData = jest.fn();
36
+ const mockLoadNext = jest.fn();
37
+
38
+ const mockSubscribeForUrlContextKeyChanges =
39
+ zappPipesClientModule.subscribeForUrlContextKeyChanges as jest.Mock;
40
+
41
+ const mockAddDataSourceListener = jest.fn().mockReturnValue(jest.fn());
42
+
43
+ beforeEach(() => {
44
+ jest.clearAllMocks();
45
+
46
+ (useFeedLoaderModule.useFeedLoader as jest.Mock).mockReturnValue({
47
+ reloadData: mockReloadData,
48
+ loadNext: mockLoadNext,
49
+ error: null,
50
+ loading: false,
51
+ url: "test-url",
52
+ data: { entry: [{ id: "1" }] },
53
+ });
54
+
55
+ mockSubscribeForUrlContextKeyChanges.mockReturnValue(jest.fn());
56
+ });
57
+
58
+ it("should use feedUrl directly when provided", () => {
59
+ const props = {
60
+ feedUrl: "https://example.com/feed",
61
+ component: { ...componentRequiredKeys } as any,
62
+ children: mockChildren,
63
+ riverId: "test-river",
64
+ };
65
+
66
+ render(<UrlFeedResolver {...props} />);
67
+
68
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
69
+ feedUrl: "https://example.com/feed",
70
+ pipesOptions: expect.any(Object),
71
+ });
72
+ });
73
+
74
+ it("should use component.data.source when no feedUrl is provided", () => {
75
+ const props = {
76
+ component: {
77
+ data: {
78
+ source: "https://example.com/source",
79
+ },
80
+ ...componentRequiredKeys,
81
+ } as any,
82
+ children: mockChildren,
83
+ riverId: "test-river",
84
+ };
85
+
86
+ render(<UrlFeedResolver {...props} />);
87
+
88
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
89
+ feedUrl: "https://example.com/source",
90
+ pipesOptions: expect.any(Object),
91
+ });
92
+ });
93
+
94
+ it("should handle FAVOURITES type correctly", () => {
95
+ const props = {
96
+ component: {
97
+ data: {
98
+ source: "favorites-source",
99
+ type: "FAVOURITES",
100
+ },
101
+ ...componentRequiredKeys,
102
+ } as any,
103
+ children: mockChildren,
104
+ riverId: "test-river",
105
+ };
106
+
107
+ render(<UrlFeedResolver {...props} />);
108
+
109
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
110
+ feedUrl: "favorites-source",
111
+ pipesOptions: expect.objectContaining({
112
+ clearCache: true,
113
+ loadLocalFavorites: true,
114
+ }),
115
+ });
116
+
117
+ expect(favoritesListener.on).toHaveBeenCalledWith(
118
+ "FAVORITES_CHANGED",
119
+ mockReloadData
120
+ );
121
+ });
122
+
123
+ it("should handle APPLICASTER_COLLECTION type correctly", () => {
124
+ const props = {
125
+ component: {
126
+ data: {
127
+ source: "collection-id",
128
+ type: "APPLICASTER_COLLECTION",
129
+ },
130
+ ...componentRequiredKeys,
131
+ } as any,
132
+ children: mockChildren,
133
+ riverId: "test-river",
134
+ };
135
+
136
+ render(<UrlFeedResolver {...props} />);
137
+
138
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
139
+ feedUrl:
140
+ "applicaster://fetchData?type=APPLICASTER_COLLECTION&collectionId=collection-id",
141
+ pipesOptions: expect.any(Object),
142
+ });
143
+ });
144
+
145
+ it("should handle APPLICASTER_CATEGORY type correctly", () => {
146
+ const props = {
147
+ component: {
148
+ data: {
149
+ source: "category-id",
150
+ type: "APPLICASTER_CATEGORY",
151
+ },
152
+ ...componentRequiredKeys,
153
+ } as any,
154
+ children: mockChildren,
155
+ riverId: "test-river",
156
+ plugins: [],
157
+ };
158
+
159
+ render(<UrlFeedResolver {...props} />);
160
+
161
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
162
+ feedUrl: expect.stringContaining(
163
+ "applicaster://fetchData?type=APPLICASTER_CATEGORY&categoryId=category-id"
164
+ ),
165
+ pipesOptions: expect.any(Object),
166
+ });
167
+ });
168
+
169
+ it("should set up URL context key changes listener", () => {
170
+ const props = {
171
+ feedUrl: "https://example.com/feed",
172
+ component: { ...componentRequiredKeys } as any,
173
+ children: mockChildren,
174
+ riverId: "test-river",
175
+ };
176
+
177
+ render(<UrlFeedResolver {...props} />);
178
+
179
+ expect(mockSubscribeForUrlContextKeyChanges).toHaveBeenCalledWith(
180
+ "https://example.com/feed",
181
+ {},
182
+ mockReloadData
183
+ );
184
+ });
185
+
186
+ it("should set up pipesv2 URL listener from plugin if available", () => {
187
+ const props = {
188
+ feedUrl: "pipesv2://test-plugin/endpoint",
189
+ component: { ...componentRequiredKeys } as any,
190
+ children: mockChildren,
191
+ riverId: "test-river",
192
+ plugins: [
193
+ {
194
+ identifier: "test-plugin",
195
+ module: {
196
+ addDataSourceListener: mockAddDataSourceListener,
197
+ },
198
+ },
199
+ ] as any,
200
+ };
201
+
202
+ render(<UrlFeedResolver {...props} />);
203
+
204
+ expect(mockAddDataSourceListener).toHaveBeenCalledWith(mockReloadData);
205
+ expect(mockSubscribeForUrlContextKeyChanges).not.toHaveBeenCalled();
206
+ });
207
+
208
+ it("should apply item limit from component rules", () => {
209
+ const props = {
210
+ feedUrl: "https://example.com/feed",
211
+ component: {
212
+ rules: {
213
+ item_limit: "2",
214
+ },
215
+ } as any,
216
+ children: mockChildren,
217
+ riverId: "test-river",
218
+ };
219
+
220
+ (useFeedLoaderModule.useFeedLoader as jest.Mock).mockReturnValue({
221
+ reloadData: mockReloadData,
222
+ loadNext: mockLoadNext,
223
+ error: null,
224
+ loading: false,
225
+ url: "test-url",
226
+ data: { entry: [{ id: "1" }, { id: "2" }, { id: "3" }] },
227
+ });
228
+
229
+ render(<UrlFeedResolver {...props} />);
230
+
231
+ expect(mockChildren).toHaveBeenCalledWith(
232
+ expect.objectContaining({
233
+ zappPipesData: expect.objectContaining({
234
+ data: expect.objectContaining({
235
+ entry: [{ id: "1" }, { id: "2" }],
236
+ }),
237
+ }),
238
+ })
239
+ );
240
+ });
241
+
242
+ it("should conditionally include loadNextData based on isLast and component type", () => {
243
+ // Case 1: isLast=true, should not include loadNextData regardless of component type
244
+ const props1 = {
245
+ feedUrl: "https://example.com/feed",
246
+ component: {
247
+ type: "vertical_list",
248
+ ...componentRequiredKeys,
249
+ } as any,
250
+ children: mockChildren,
251
+ riverId: "test-river",
252
+ isLast: true,
253
+ };
254
+
255
+ render(<UrlFeedResolver {...props1} />);
256
+
257
+ expect(mockChildren).toHaveBeenCalledWith(
258
+ expect.objectContaining({
259
+ loadNextData: mockLoadNext,
260
+ })
261
+ );
262
+
263
+ mockChildren.mockClear();
264
+
265
+ // Case 2: isLast=false, vertical_list component, should not include loadNextData
266
+ const props2 = {
267
+ feedUrl: "https://example.com/feed",
268
+ component: {
269
+ type: "vertical_list",
270
+ ...componentRequiredKeys,
271
+ } as any,
272
+ children: mockChildren,
273
+ riverId: "test-river",
274
+ isLast: false,
275
+ };
276
+
277
+ render(<UrlFeedResolver {...props2} />);
278
+
279
+ expect(mockChildren).toHaveBeenCalledWith(
280
+ expect.objectContaining({
281
+ loadNextData: undefined,
282
+ })
283
+ );
284
+
285
+ mockChildren.mockClear();
286
+
287
+ // Case 3: isLast=false, non-vertical component, should include loadNextData
288
+ const props3 = {
289
+ feedUrl: "https://example.com/feed",
290
+ component: {
291
+ type: "horizontal_list",
292
+ ...componentRequiredKeys,
293
+ } as any,
294
+ children: mockChildren,
295
+ riverId: "test-river",
296
+ isLast: false,
297
+ };
298
+
299
+ render(<UrlFeedResolver {...props3} />);
300
+
301
+ expect(mockChildren).toHaveBeenCalledWith(
302
+ expect.objectContaining({
303
+ loadNextData: mockLoadNext,
304
+ })
305
+ );
306
+ });
307
+
308
+ it("should pass correct zappPipesDataProps to children", () => {
309
+ const props = {
310
+ feedUrl: "https://example.com/feed",
311
+ component: { ...componentRequiredKeys } as any,
312
+ children: mockChildren,
313
+ riverId: "test-river",
314
+ };
315
+
316
+ render(<UrlFeedResolver {...props} />);
317
+
318
+ expect(mockChildren).toHaveBeenCalledWith({
319
+ zappPipesData: {
320
+ url: "test-url",
321
+ loading: false,
322
+ data: { entry: [{ id: "1" }] },
323
+ error: null,
324
+ },
325
+ reloadData: mockReloadData,
326
+ loadNextData: mockLoadNext,
327
+ });
328
+ });
329
+
330
+ it("should apply feed refresh hook", () => {
331
+ const props = {
332
+ feedUrl: "https://example.com/feed",
333
+ component: { ...componentRequiredKeys } as any,
334
+ children: mockChildren,
335
+ riverId: "test-river",
336
+ };
337
+
338
+ render(<UrlFeedResolver {...props} />);
339
+
340
+ expect(useFeedRefreshModule.useFeedRefresh).toHaveBeenCalledWith({
341
+ reloadData: mockReloadData,
342
+ component: props.component,
343
+ });
344
+ });
345
+
346
+ it("should clean up listeners on unmount", () => {
347
+ const props = {
348
+ component: {
349
+ data: {
350
+ source: "favorites-source",
351
+ type: "FAVOURITES",
352
+ },
353
+ ...componentRequiredKeys,
354
+ } as any,
355
+ children: mockChildren,
356
+ riverId: "test-river",
357
+ };
358
+
359
+ const { unmount } = render(<UrlFeedResolver {...props} />);
360
+
361
+ unmount();
362
+
363
+ expect(favoritesListener.removeHandler).toHaveBeenCalledWith(
364
+ "FAVORITES_CHANGED",
365
+ mockReloadData
366
+ );
367
+ });
368
+ });
@@ -0,0 +1,39 @@
1
+ import { isVerticalListOrGrid } from "../utils";
2
+
3
+ describe("ZappPipesDataConnector utils", () => {
4
+ describe("isVerticalListOrGrid", () => {
5
+ it("should return true for list-qb components", () => {
6
+ const component = {
7
+ component_type: "list-qb",
8
+ } as any;
9
+
10
+ expect(isVerticalListOrGrid(component)).toBe(true);
11
+ });
12
+
13
+ it("should return true for grid-qb components", () => {
14
+ const component = {
15
+ component_type: "grid-qb",
16
+ } as any;
17
+
18
+ expect(isVerticalListOrGrid(component)).toBe(true);
19
+ });
20
+
21
+ it("should return false for other component types", () => {
22
+ const component = {
23
+ component_type: "horizontal-list",
24
+ } as any;
25
+
26
+ expect(isVerticalListOrGrid(component)).toBe(false);
27
+ });
28
+
29
+ it("should return false for undefined component", () => {
30
+ expect(isVerticalListOrGrid(undefined)).toBe(false);
31
+ });
32
+
33
+ it("should return false for component without type", () => {
34
+ const component = {} as any;
35
+
36
+ expect(isVerticalListOrGrid(component)).toBe(false);
37
+ });
38
+ });
39
+ });