@applicaster/zapp-react-native-ui-components 14.0.0-alpha.2175196485 → 14.0.0-alpha.2292190333

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 (74) hide show
  1. package/Components/AudioPlayer/mobile/Layout.tsx +1 -1
  2. package/Components/AudioPlayer/tv/helpers.tsx +10 -3
  3. package/Components/BaseFocusable/index.tsx +23 -12
  4. package/Components/Cell/__tests__/CellWIthFocusable.test.js +3 -2
  5. package/Components/Cell/index.js +1 -1
  6. package/Components/ComponentResolver/index.ts +1 -1
  7. package/Components/FeedLoader/FeedLoader.tsx +6 -15
  8. package/Components/FeedLoader/FeedLoaderHOC.tsx +21 -0
  9. package/Components/FeedLoader/index.js +2 -8
  10. package/Components/Focusable/FocusableiOS.tsx +1 -1
  11. package/Components/Focusable/Touchable.tsx +6 -3
  12. package/Components/Focusable/index.android.tsx +11 -4
  13. package/Components/Focusable/index.tsx +1 -1
  14. package/Components/FocusableList/FocusableItem.tsx +22 -3
  15. package/Components/FocusableList/FocusableListItemWrapper.tsx +2 -1
  16. package/Components/FocusableList/index.tsx +14 -7
  17. package/Components/FreezeWithCallback/__tests__/index.test.tsx +67 -43
  18. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +42 -59
  19. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +13 -10
  20. package/Components/Layout/TV/LayoutBackground.tsx +1 -1
  21. package/Components/Layout/TV/__tests__/index.test.tsx +0 -1
  22. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -0
  23. package/Components/MasterCell/DefaultComponents/Button.tsx +1 -1
  24. package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +1 -1
  25. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +1 -1
  26. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +10 -10
  27. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +18 -18
  28. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/index.test.tsx +10 -10
  29. package/Components/MasterCell/utils/behaviorProvider.ts +82 -14
  30. package/Components/MasterCell/utils/index.ts +11 -5
  31. package/Components/OfflineHandler/__tests__/index.test.tsx +26 -35
  32. package/Components/PlayerContainer/ErrorDisplay/index.ts +1 -1
  33. package/Components/PlayerContainer/ProgramInfo/index.tsx +1 -1
  34. package/Components/PlayerContainer/index.ts +1 -1
  35. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +378 -0
  36. package/Components/River/ComponentsMap/hooks/useLoadingState.ts +2 -2
  37. package/Components/River/RefreshControl.tsx +11 -17
  38. package/Components/River/TV/River.tsx +2 -17
  39. package/Components/River/TV/index.tsx +3 -1
  40. package/Components/River/TV/withPipesV1DataLoader.tsx +43 -0
  41. package/Components/River/TV/withRiverDataLoader.tsx +17 -0
  42. package/Components/River/__tests__/river.test.js +12 -26
  43. package/Components/River/index.tsx +1 -1
  44. package/Components/Screen/__tests__/Screen.test.tsx +28 -29
  45. package/Components/Tabs/Tabs.tsx +2 -3
  46. package/Components/Touchable/__tests__/touchable.test.tsx +12 -17
  47. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +3 -9
  48. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +24 -8
  49. package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +5 -5
  50. package/Contexts/ConfigutaionContext/__tests__/ConfigurationProvider.test.tsx +3 -3
  51. package/Contexts/ScreenContext/index.tsx +46 -6
  52. package/Decorators/ConfigurationWrapper/__tests__/withConfigurationProvider.test.tsx +3 -3
  53. package/Decorators/RiverFeedLoader/__tests__/__snapshots__/riverFeedLoader.test.tsx.snap +221 -209
  54. package/Decorators/RiverFeedLoader/__tests__/riverFeedLoader.test.tsx +14 -16
  55. package/Decorators/RiverFeedLoader/__tests__/utils.test.ts +0 -20
  56. package/Decorators/RiverFeedLoader/index.tsx +22 -4
  57. package/Decorators/RiverFeedLoader/utils/index.ts +0 -18
  58. package/Decorators/RiverResolver/__tests__/riverResolver.test.tsx +3 -6
  59. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -0
  60. package/Decorators/ZappPipesDataConnector/__tests__/NullFeedResolver.test.tsx +78 -0
  61. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +205 -0
  62. package/Decorators/ZappPipesDataConnector/__tests__/StaticFeedResolver.test.tsx +251 -0
  63. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +368 -0
  64. package/Decorators/ZappPipesDataConnector/__tests__/utils.test.ts +39 -0
  65. package/Decorators/ZappPipesDataConnector/index.tsx +26 -293
  66. package/Decorators/ZappPipesDataConnector/resolvers/NullFeedResolver.tsx +25 -0
  67. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +87 -0
  68. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +266 -0
  69. package/Decorators/ZappPipesDataConnector/types.ts +29 -0
  70. package/Decorators/ZappPipesDataConnector/utils/mongoFilter.ts +738 -0
  71. package/Decorators/ZappPipesDataConnector/utils/useFilter.tsx +136 -0
  72. package/events/index.ts +1 -0
  73. package/package.json +5 -6
  74. package/Components/River/__tests__/__snapshots__/river.test.js.snap +0 -27
@@ -0,0 +1,368 @@
1
+ import React from "react";
2
+ import * as useFeedLoaderModule from "@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedLoader";
3
+ import * as useFeedRefreshModule from "@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedRefresh";
4
+ import { favoritesListener } from "@applicaster/zapp-react-native-bridge/Favorites";
5
+ import { UrlFeedResolver } from "../resolvers/UrlFeedResolver";
6
+ import { renderWithProviders } from "@applicaster/zapp-react-native-utils/testUtils";
7
+ import * as ZappPipesModule from "@applicaster/zapp-pipes-v2-client";
8
+
9
+ const mockSubscribeForKeyChanges = jest.fn();
10
+
11
+ jest
12
+ .spyOn(ZappPipesModule, "subscribeForKeyChanges")
13
+ .mockImplementation(mockSubscribeForKeyChanges);
14
+
15
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedLoader");
16
+
17
+ jest.mock(
18
+ "@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedRefresh"
19
+ );
20
+
21
+ jest.mock("@applicaster/zapp-pipes-v2-client");
22
+
23
+ jest.mock("@applicaster/zapp-react-native-bridge/Favorites", () => ({
24
+ favoritesListener: {
25
+ on: jest.fn(),
26
+ removeHandler: jest.fn(),
27
+ },
28
+ }));
29
+
30
+ jest.mock("../utils", () => ({
31
+ isVerticalListOrGrid: jest.fn(
32
+ (component) =>
33
+ component?.type === "vertical_list" || component?.type === "vertical_grid"
34
+ ),
35
+ }));
36
+
37
+ const componentRequiredKeys = { rules: { item_limit: 10 } };
38
+
39
+ describe("UrlFeedResolver", () => {
40
+ const mockChildren = jest.fn(() => <div data-testid="mock-children" />);
41
+ const mockReloadData = jest.fn();
42
+ const mockLoadNext = jest.fn();
43
+
44
+ const mockAddDataSourceListener = jest.fn().mockReturnValue(jest.fn());
45
+
46
+ beforeEach(() => {
47
+ jest.clearAllMocks();
48
+
49
+ (useFeedLoaderModule.useFeedLoader as jest.Mock).mockReturnValue({
50
+ reloadData: mockReloadData,
51
+ loadNext: mockLoadNext,
52
+ error: null,
53
+ loading: false,
54
+ url: "test-url",
55
+ data: { entry: [{ id: "1" }] },
56
+ });
57
+ });
58
+
59
+ it("should use feedUrl directly when provided", () => {
60
+ const props = {
61
+ feedUrl: "https://example.com/feed",
62
+ component: { ...componentRequiredKeys } as any,
63
+ children: mockChildren,
64
+ riverId: "test-river",
65
+ };
66
+
67
+ renderWithProviders(<UrlFeedResolver {...props} />);
68
+
69
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
70
+ feedUrl: "https://example.com/feed",
71
+ pipesOptions: expect.any(Object),
72
+ });
73
+ });
74
+
75
+ it("should use component.data.source when no feedUrl is provided", () => {
76
+ const props = {
77
+ component: {
78
+ data: {
79
+ source: "https://example.com/source",
80
+ },
81
+ ...componentRequiredKeys,
82
+ } as any,
83
+ children: mockChildren,
84
+ riverId: "test-river",
85
+ };
86
+
87
+ renderWithProviders(<UrlFeedResolver {...props} />);
88
+
89
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
90
+ feedUrl: "https://example.com/source",
91
+ pipesOptions: expect.any(Object),
92
+ });
93
+ });
94
+
95
+ it("should handle FAVOURITES type correctly", () => {
96
+ const props = {
97
+ component: {
98
+ data: {
99
+ source: "favorites-source",
100
+ type: "FAVOURITES",
101
+ },
102
+ ...componentRequiredKeys,
103
+ } as any,
104
+ children: mockChildren,
105
+ riverId: "test-river",
106
+ };
107
+
108
+ renderWithProviders(<UrlFeedResolver {...props} />);
109
+
110
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
111
+ feedUrl: "favorites-source",
112
+ pipesOptions: expect.objectContaining({
113
+ clearCache: true,
114
+ loadLocalFavorites: true,
115
+ }),
116
+ });
117
+
118
+ expect(favoritesListener.on).toHaveBeenCalledWith(
119
+ "FAVORITES_CHANGED",
120
+ mockReloadData
121
+ );
122
+ });
123
+
124
+ it("should handle APPLICASTER_COLLECTION type correctly", () => {
125
+ const props = {
126
+ component: {
127
+ data: {
128
+ source: "collection-id",
129
+ type: "APPLICASTER_COLLECTION",
130
+ },
131
+ ...componentRequiredKeys,
132
+ } as any,
133
+ children: mockChildren,
134
+ riverId: "test-river",
135
+ };
136
+
137
+ renderWithProviders(<UrlFeedResolver {...props} />);
138
+
139
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
140
+ feedUrl:
141
+ "applicaster://fetchData?type=APPLICASTER_COLLECTION&collectionId=collection-id",
142
+ pipesOptions: expect.any(Object),
143
+ });
144
+ });
145
+
146
+ it("should handle APPLICASTER_CATEGORY type correctly", () => {
147
+ const props = {
148
+ component: {
149
+ data: {
150
+ source: "category-id",
151
+ type: "APPLICASTER_CATEGORY",
152
+ },
153
+ ...componentRequiredKeys,
154
+ } as any,
155
+ children: mockChildren,
156
+ riverId: "test-river",
157
+ plugins: [],
158
+ };
159
+
160
+ renderWithProviders(<UrlFeedResolver {...props} />);
161
+
162
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
163
+ feedUrl: expect.stringContaining(
164
+ "applicaster://fetchData?type=APPLICASTER_CATEGORY&categoryId=category-id"
165
+ ),
166
+ pipesOptions: expect.any(Object),
167
+ });
168
+ });
169
+
170
+ it("should set up URL context key changes listener", () => {
171
+ const props = {
172
+ feedUrl: "https://example.com/feed",
173
+ component: { ...componentRequiredKeys } as any,
174
+ children: mockChildren,
175
+ riverId: "test-river",
176
+ };
177
+
178
+ renderWithProviders(<UrlFeedResolver {...props} />);
179
+
180
+ expect(mockSubscribeForKeyChanges.mock.calls[0][0]).toMatchObject({
181
+ url: "https://example.com/feed",
182
+ callback: 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
+ renderWithProviders(<UrlFeedResolver {...props} />);
203
+
204
+ expect(mockAddDataSourceListener).toHaveBeenCalledWith(mockReloadData);
205
+ expect(mockSubscribeForKeyChanges).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
+ renderWithProviders(<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
+ renderWithProviders(<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
+ renderWithProviders(<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
+ renderWithProviders(<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
+ renderWithProviders(<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
+ renderWithProviders(<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 } = renderWithProviders(<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
+ });