@applicaster/zapp-react-native-utils 14.0.0-rc.50 → 14.0.0-rc.52

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 (37) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +60 -84
  2. package/actionsExecutor/ScreenActions.ts +164 -0
  3. package/actionsExecutor/StorageActions.ts +110 -0
  4. package/actionsExecutor/feedDecorator.ts +171 -0
  5. package/actionsExecutor/screenResolver.ts +11 -0
  6. package/analyticsUtils/AnalyticsEvents/helper.ts +1 -1
  7. package/analyticsUtils/__tests__/analyticsUtils.test.js +0 -11
  8. package/appUtils/contextKeysManager/contextResolver.ts +42 -1
  9. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +3 -0
  10. package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
  11. package/appUtils/focusManager/index.ts +72 -12
  12. package/appUtils/focusManagerAux/utils/index.ts +88 -3
  13. package/package.json +2 -2
  14. package/reactHooks/cell-click/__tests__/index.test.js +3 -0
  15. package/reactHooks/cell-click/index.ts +8 -1
  16. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +8 -2
  17. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +71 -31
  18. package/reactHooks/feed/index.ts +2 -0
  19. package/reactHooks/feed/useBatchLoading.ts +14 -9
  20. package/reactHooks/feed/useFeedLoader.tsx +36 -38
  21. package/reactHooks/feed/useLoadPipesDataDispatch.ts +57 -0
  22. package/reactHooks/navigation/useRoute.ts +7 -2
  23. package/reactHooks/navigation/useScreenStateStore.ts +8 -0
  24. package/reactHooks/state/index.ts +1 -1
  25. package/reactHooks/state/useHomeRiver.ts +4 -2
  26. package/screenPickerUtils/index.ts +7 -0
  27. package/storage/ScreenSingleValueProvider.ts +204 -0
  28. package/storage/ScreenStateMultiSelectProvider.ts +293 -0
  29. package/storage/StorageMultiSelectProvider.ts +192 -0
  30. package/storage/StorageSingleSelectProvider.ts +108 -0
  31. package/utils/__tests__/find.test.ts +36 -0
  32. package/utils/__tests__/pathOr.test.ts +37 -0
  33. package/utils/__tests__/startsWith.test.ts +30 -0
  34. package/utils/find.ts +3 -0
  35. package/utils/index.ts +6 -0
  36. package/utils/pathOr.ts +5 -0
  37. package/utils/startsWith.ts +9 -0
@@ -1,10 +1,13 @@
1
1
  import { ContextKeysManager } from "./index";
2
2
  import * as R from "ramda";
3
+ import * as _ from "lodash";
4
+ import { useScreenStateStore } from "../../reactHooks/navigation/useScreenStateStore";
3
5
 
4
- interface IResolver {
6
+ export interface IResolver {
5
7
  resolve: (string) => Promise<string | number | object>;
6
8
  }
7
9
 
10
+ // TODO: Rename to ObjectKeyResolver or similar
8
11
  export class EntryResolver implements IResolver {
9
12
  entry: ZappEntry;
10
13
 
@@ -21,6 +24,28 @@ export class EntryResolver implements IResolver {
21
24
  }
22
25
  }
23
26
 
27
+ // TODO: Move to proper place
28
+
29
+ export class ScreenStateResolver implements IResolver {
30
+ constructor(
31
+ private screenStateStore: ReturnType<typeof useScreenStateStore>
32
+ ) {}
33
+
34
+ async resolve(key: string) {
35
+ const screenState = this.screenStateStore.getState().data;
36
+
37
+ if (!key || key.length === 0) {
38
+ return screenState;
39
+ }
40
+
41
+ if (key.includes(".")) {
42
+ return R.view(R.lensPath(key.split(".")), screenState);
43
+ }
44
+
45
+ return screenState?.[key];
46
+ }
47
+ }
48
+
24
49
  export class ContextResolver implements IResolver {
25
50
  resolve = async (compositeKey: string) =>
26
51
  ContextKeysManager.instance.getKey(compositeKey);
@@ -64,3 +89,19 @@ export const resolveObjectValues = async (
64
89
 
65
90
  return Object.fromEntries(resolvedEntries);
66
91
  };
92
+
93
+ export const extractAtValues = _.memoize((input: any): string[] => {
94
+ return _.flatMapDeep(input, (value: any) => {
95
+ if (_.isString(value)) {
96
+ const matches = value.match(/@\{([^}]*)\}/g);
97
+
98
+ return matches ? matches.map((match) => match.slice(2, -1)) : [];
99
+ }
100
+
101
+ if (_.isObject(value)) {
102
+ return extractAtValues(_.values(value));
103
+ }
104
+
105
+ return [];
106
+ });
107
+ });
@@ -6,6 +6,7 @@ exports[`focusManager should be defined 1`] = `
6
6
  "disableFocus": [Function],
7
7
  "enableFocus": [Function],
8
8
  "findPreferredFocusChild": [Function],
9
+ "focusTopNavigation": [Function],
9
10
  "focusableTree": Tree {
10
11
  "loadingCounter": 0,
11
12
  "root": {
@@ -25,6 +26,8 @@ exports[`focusManager should be defined 1`] = `
25
26
  "isCurrentFocusOnTheTopScreen": [Function],
26
27
  "isFocusDisabled": [Function],
27
28
  "isFocusOn": [Function],
29
+ "isFocusOnContent": [Function],
30
+ "isFocusOnMenu": [Function],
28
31
  "isGroupItemFocused": [Function],
29
32
  "longPress": [Function],
30
33
  "moveFocus": [Function],
@@ -33,7 +33,7 @@ describe("focusManager", () => {
33
33
 
34
34
  expect(success).toBe(true);
35
35
  expect(mockSetFocus).toBeCalledTimes(1);
36
- expect(mockSetFocus).toBeCalledWith(null);
36
+ expect(mockSetFocus).toBeCalledWith(null, undefined);
37
37
  });
38
38
 
39
39
  describe("register", () => {});
@@ -14,7 +14,14 @@ import { subscriber } from "../../functionUtils";
14
14
  import { coreLogger } from "../../logger";
15
15
  import { ACTION } from "./utils/enums";
16
16
 
17
- import { isCurrentFocusOn } from "../focusManagerAux/utils";
17
+ import {
18
+ findSelectedTabId,
19
+ findSelectedMenuId,
20
+ isTabsScreenContentFocused,
21
+ isCurrentFocusOnContent,
22
+ isCurrentFocusOnMenu,
23
+ isCurrentFocusOn,
24
+ } from "../focusManagerAux/utils";
18
25
 
19
26
  const logger = coreLogger.addSubsystem("focusManager");
20
27
 
@@ -102,7 +109,7 @@ export const focusManager = (function () {
102
109
  * @private
103
110
  * @param {Object} direction of the navigation which led to this action
104
111
  */
105
- function focus(direction) {
112
+ function focus(direction, context?: FocusManager.FocusContext) {
106
113
  const currentFocusable = getCurrentFocus();
107
114
 
108
115
  if (
@@ -110,7 +117,7 @@ export const focusManager = (function () {
110
117
  !currentFocusable.isGroup &&
111
118
  currentFocusable.isMounted()
112
119
  ) {
113
- currentFocusable.setFocus(direction);
120
+ currentFocusable.setFocus(direction, context);
114
121
  }
115
122
  }
116
123
 
@@ -207,7 +214,7 @@ export const focusManager = (function () {
207
214
  * @param {Array<string>} ids - An array of node IDs to update.
208
215
  * @param {boolean} setFocus - A flag indicating whether to set focus (true) or blur (false) on the nodes.
209
216
  */
210
- const updateNodeFocus = (ids, action) => {
217
+ const updateNodeFocus = (ids, action, context: FocusManager.FocusContext) => {
211
218
  if (!ids || ids.length === 0) {
212
219
  return; // Nothing to do
213
220
  }
@@ -224,11 +231,13 @@ export const focusManager = (function () {
224
231
 
225
232
  // Function to apply the action (focus or blur)
226
233
  const applyAction = (node) => {
234
+ const direction = undefined;
235
+
227
236
  if (node && node.component) {
228
237
  if (action === "focus") {
229
- node.component.setFocus();
238
+ node.component.setFocus(direction, context);
230
239
  } else if (action === "blur") {
231
- node.component.setBlur();
240
+ node.component.setBlur(direction, context);
232
241
  }
233
242
  }
234
243
  };
@@ -255,7 +264,11 @@ export const focusManager = (function () {
255
264
  * @param {Object} direction of the navigation, which led to this focus change
256
265
  * to another group or not. defaults to false
257
266
  */
258
- function setFocus(id: string, direction?: FocusManager.Web.Direction) {
267
+ function setFocus(
268
+ id: string,
269
+ direction?: FocusManager.Web.Direction,
270
+ context?: FocusManager.FocusContext
271
+ ) {
259
272
  if (focusDisabled) return false;
260
273
 
261
274
  // due to optimisiation it's recommanded to set currentFocusNode before setFocus
@@ -268,21 +281,65 @@ export const focusManager = (function () {
268
281
  );
269
282
 
270
283
  // Set focus on current node parents and blur on previous node parents
271
- updateNodeFocus(currentNodeParentsIDs, ACTION.FOCUS);
272
- updateNodeFocus(previousNodeParentsIDs, ACTION.BLUR);
284
+ updateNodeFocus(currentNodeParentsIDs, ACTION.FOCUS, context);
285
+ updateNodeFocus(previousNodeParentsIDs, ACTION.BLUR, context);
273
286
 
274
287
  currentFocusNode = focusableTree.findInTree(id);
275
288
  }
276
289
 
277
290
  setLastFocusOnParentNode(currentFocusNode);
278
291
 
279
- focus(direction);
292
+ focus(direction, context);
293
+ }
294
+
295
+ function isFocusOnContent() {
296
+ return isCurrentFocusOnContent(currentFocusNode);
297
+ }
298
+
299
+ function isFocusOnMenu() {
300
+ return isCurrentFocusOnMenu(currentFocusNode);
301
+ }
302
+
303
+ // Move focus to appropriate top navigation tab with context
304
+ function focusTopNavigation(isTabsScreen: boolean, item: ZappEntry) {
305
+ const landFocusTo = (id) => {
306
+ if (id) {
307
+ // set focus on selected menu item
308
+ const direction = undefined;
309
+
310
+ const context: FocusManager.FocusContext = {
311
+ source: "back",
312
+ preserveScroll: true,
313
+ };
314
+
315
+ logger.log({ message: "landFocusTo", data: { id } });
316
+
317
+ blur(direction);
318
+ setFocus(id, direction, context);
319
+ }
320
+ };
321
+
322
+ if (isTabsScreen && isTabsScreenContentFocused(currentFocusNode)) {
323
+ const selectedTabId = findSelectedTabId(item);
324
+
325
+ // Set focus with back button context to tabs-menu
326
+ landFocusTo(selectedTabId);
327
+
328
+ return;
329
+ }
330
+
331
+ const selectedMenuItemId = findSelectedMenuId(focusableTree);
332
+ // Set focus with back button context to top-menu
333
+ landFocusTo(selectedMenuItemId);
280
334
  }
281
335
 
282
336
  /**
283
337
  * sets the initial focus when the screen loads, or when focus is lost
284
338
  */
285
- function setInitialFocus(lastAddedParentNode?: any) {
339
+ function setInitialFocus(
340
+ lastAddedParentNode?: any,
341
+ context?: FocusManager.FocusContext
342
+ ) {
286
343
  const preferredFocus = findPriorityItem(
287
344
  lastAddedParentNode?.children || focusableTree.root.children
288
345
  );
@@ -328,7 +385,7 @@ export const focusManager = (function () {
328
385
  },
329
386
  });
330
387
 
331
- focusableItem && setFocus(focusCandidate.id, null);
388
+ focusableItem && setFocus(focusCandidate.id, null, context);
332
389
 
333
390
  return { success: true };
334
391
  }
@@ -586,6 +643,9 @@ export const focusManager = (function () {
586
643
  recoverFocus,
587
644
  isCurrentFocusOnTheTopScreen,
588
645
  findPreferredFocusChild,
646
+ focusTopNavigation,
647
+ isFocusOnContent,
648
+ isFocusOnMenu,
589
649
  isFocusOn,
590
650
  };
591
651
  })();
@@ -1,16 +1,31 @@
1
- import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
2
- import { find, last, pathOr, startsWith } from "ramda";
1
+ import {
2
+ isNil,
3
+ last,
4
+ startsWith,
5
+ find,
6
+ pathOr,
7
+ } from "@applicaster/zapp-react-native-utils/utils";
8
+
3
9
  import {
4
10
  QUICK_BRICK_CONTENT,
5
11
  QUICK_BRICK_NAVBAR,
6
12
  } from "@applicaster/quick-brick-core/const";
7
13
 
14
+ import {
15
+ getFocusableId,
16
+ SCREEN_PICKER_CONTAINER,
17
+ } from "@applicaster/zapp-react-native-utils/screenPickerUtils";
18
+
8
19
  // run check each 300 ms
9
20
  // run this check too often could lead to performance penalty on low-end devices
10
21
  const HOW_OFTEN_TO_CHECK_CONDITION = 300; // ms
11
22
 
23
+ const isTopMenu = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
24
+ const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
12
25
  const isRoot = (node) => node?.id === "root";
13
26
 
27
+ const isScrenPicker = (node) => startsWith(SCREEN_PICKER_CONTAINER, node?.id);
28
+
14
29
  type Props = {
15
30
  maxTimeout: number;
16
31
  conditionFn: () => boolean;
@@ -51,7 +66,7 @@ export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
51
66
 
52
67
  const route = find((route) => route.id === currentRoute, routes);
53
68
 
54
- return isNotNil(route);
69
+ return !isNil(route);
55
70
  };
56
71
 
57
72
  return waitUntil({
@@ -102,6 +117,76 @@ export const waitForContent = (focusableTree) => {
102
117
  });
103
118
  };
104
119
 
120
+ export const findSelectedTabId = (item: ZappEntry): string => {
121
+ const selectedTabId = getFocusableId(item.id);
122
+
123
+ return selectedTabId;
124
+ };
125
+
126
+ export const findSelectedMenuId = (focusableTree) => {
127
+ // Set focus with back button context
128
+ const navbar = getNavbarNode(focusableTree);
129
+
130
+ const selectedMenuItemId = find(
131
+ (child) => child.component.props.selected,
132
+ navbar.children
133
+ )?.id;
134
+
135
+ return selectedMenuItemId;
136
+ };
137
+
138
+ export const isTabsScreenContentFocused = (node) => {
139
+ if (isRoot(node)) {
140
+ return false;
141
+ }
142
+
143
+ if (isTopMenu(node)) {
144
+ return false;
145
+ }
146
+
147
+ if (isContent(node)) {
148
+ return false;
149
+ }
150
+
151
+ if (isScrenPicker(node)) {
152
+ return true;
153
+ }
154
+
155
+ return isTabsScreenContentFocused(node.parent);
156
+ };
157
+
158
+ export const isCurrentFocusOnMenu = (node) => {
159
+ if (isRoot(node)) {
160
+ return false;
161
+ }
162
+
163
+ if (isTopMenu(node)) {
164
+ return true;
165
+ }
166
+
167
+ if (isContent(node)) {
168
+ return false;
169
+ }
170
+
171
+ return isCurrentFocusOnMenu(node.parent);
172
+ };
173
+
174
+ export const isCurrentFocusOnContent = (node) => {
175
+ if (isRoot(node)) {
176
+ return false;
177
+ }
178
+
179
+ if (isTopMenu(node)) {
180
+ return false;
181
+ }
182
+
183
+ if (isContent(node)) {
184
+ return true;
185
+ }
186
+
187
+ return isCurrentFocusOnContent(node.parent);
188
+ };
189
+
105
190
  export const isCurrentFocusOn = (id, node) => {
106
191
  if (!node) {
107
192
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "14.0.0-rc.50",
3
+ "version": "14.0.0-rc.52",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "homepage": "https://github.com/applicaster/quickbrick#readme",
29
29
  "dependencies": {
30
- "@applicaster/applicaster-types": "14.0.0-rc.50",
30
+ "@applicaster/applicaster-types": "14.0.0-rc.52",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -26,6 +26,9 @@ jest.mock("@applicaster/zapp-react-native-utils/analyticsUtils/", () => ({
26
26
  }));
27
27
 
28
28
  jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
29
+ ...jest.requireActual(
30
+ "@applicaster/zapp-react-native-utils/reactHooks/screen"
31
+ ),
29
32
  useTargetScreenData: jest.fn(() => ({})),
30
33
  useCurrentScreenData: jest.fn(() => ({})),
31
34
  }));
@@ -16,7 +16,8 @@ import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/acti
16
16
  import { isFunction, noop } from "../../functionUtils";
17
17
  import { useSendAnalyticsOnPress } from "../analytics";
18
18
  import { logOnPress, warnEmptyContentType } from "./helpers";
19
- import { useCurrentScreenData } from "../screen";
19
+ import { useCurrentScreenData, useScreenContext } from "../screen";
20
+ import { useScreenStateStore } from "../navigation/useScreenStateStore";
20
21
 
21
22
  /**
22
23
  * If onCellTap is defined execute the function and
@@ -42,10 +43,12 @@ export const useCellClick = ({
42
43
  }: Props): onPressReturnFn => {
43
44
  const { push, currentRoute } = useNavigation();
44
45
  const { pathname } = useRoute();
46
+ const screenStateStore = useScreenStateStore();
45
47
 
46
48
  const onCellTap: Option<Function> = React.useContext(CellTapContext);
47
49
  const actionExecutor = React.useContext(ActionExecutorContext);
48
50
  const screenData = useCurrentScreenData();
51
+ const screenState = useScreenContext()?.options;
49
52
 
50
53
  const cellSelectable = toBooleanWithDefaultTrue(
51
54
  component?.rules?.component_cells_selectable
@@ -83,6 +86,9 @@ export const useCellClick = ({
83
86
  await actionExecutor?.handleEntryActions(selectedItem, {
84
87
  component,
85
88
  screenData,
89
+ screenState,
90
+ screenRoute: pathname,
91
+ screenStateStore,
86
92
  });
87
93
  }
88
94
 
@@ -117,6 +123,7 @@ export const useCellClick = ({
117
123
  push,
118
124
  sendAnalyticsOnPress,
119
125
  screenData,
126
+ screenState,
120
127
  ]
121
128
  );
122
129
 
@@ -2,12 +2,16 @@ import { renderHook } from "@testing-library/react-hooks";
2
2
  import { allFeedsIsReady, useBatchLoading } from "../useBatchLoading";
3
3
  import { WrappedWithProviders } from "@applicaster/zapp-react-native-utils/testUtils";
4
4
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
5
+ import { waitFor } from "@testing-library/react-native";
5
6
 
6
7
  jest.mock("../../navigation");
7
8
 
8
9
  jest.mock(
9
10
  "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext",
10
11
  () => ({
12
+ ...jest.requireActual(
13
+ "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext"
14
+ ),
11
15
  useScreenContext: jest.fn().mockReturnValue({ screen: {}, entry: {} }),
12
16
  })
13
17
  );
@@ -33,7 +37,7 @@ describe("useBatchLoading", () => {
33
37
  jest.clearAllMocks();
34
38
  });
35
39
 
36
- it("loadPipesData start loading not started requests", () => {
40
+ it("loadPipesData start loading not started requests", async () => {
37
41
  const store = {
38
42
  zappPipes: {
39
43
  url1: {
@@ -65,7 +69,9 @@ describe("useBatchLoading", () => {
65
69
 
66
70
  const actions = (appStore.getStore() as any).getActions();
67
71
 
68
- expect(actions).toHaveLength(2);
72
+ await waitFor(() => {
73
+ expect(actions).toHaveLength(2);
74
+ });
69
75
 
70
76
  expect(actions[0]).toMatchObject({
71
77
  type: "ZAPP_PIPES_REQUEST_START",
@@ -2,15 +2,12 @@ import { renderHook } from "@testing-library/react-hooks";
2
2
  import * as R from "ramda";
3
3
  import * as zappPipesModule from "@applicaster/zapp-react-native-redux/ZappPipes";
4
4
  import * as reactReduxModules from "react-redux";
5
- import { Provider } from "react-redux";
6
5
  import * as React from "react";
7
- import configureStore from "redux-mock-store";
8
- import thunk from "redux-thunk";
9
6
  import * as useRouteHook from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useRoute";
10
7
  import * as useNavigationHooks from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useNavigation";
11
8
  import { useFeedLoader } from "../useFeedLoader";
12
-
13
- const mockStore = configureStore([thunk]);
9
+ import { WrappedWithProviders } from "../../../testUtils";
10
+ import { ScreenStateResolver } from "../../../appUtils/contextKeysManager/contextResolver";
14
11
 
15
12
  jest.useFakeTimers({ legacyFakeTimers: true });
16
13
 
@@ -55,13 +52,15 @@ const mockZappPipesData = {
55
52
 
56
53
  describe("useFeedLoader", () => {
57
54
  describe("with cached feed url", () => {
58
- const store = mockStore({
55
+ const store = {
59
56
  plugins: [],
60
57
  zappPipes: { "test://testfakeurl": mockZappPipesData },
61
- });
58
+ };
62
59
 
63
- const wrapper: React.FC<any> = ({ children }) => (
64
- <Provider store={store}>{children}</Provider>
60
+ const wrapper: React.FC<any> = ({ children, ...props }) => (
61
+ <WrappedWithProviders store={props.store || store}>
62
+ {children}
63
+ </WrappedWithProviders>
65
64
  );
66
65
 
67
66
  it("returns cached feed", () => {
@@ -110,8 +109,10 @@ describe("useFeedLoader", () => {
110
109
  describe("without cached feeds", () => {
111
110
  const feedUrl = "test://testfakeurl2";
112
111
 
113
- const wrapper: React.FC<any> = ({ children, store }) => (
114
- <Provider store={store}>{children}</Provider>
112
+ const wrapper: React.FC<any> = ({ children, ...props }) => (
113
+ <WrappedWithProviders store={props.store}>
114
+ {children}
115
+ </WrappedWithProviders>
115
116
  );
116
117
 
117
118
  it("It loads data for new url and returns it", () => {
@@ -123,10 +124,10 @@ describe("useFeedLoader", () => {
123
124
  .spyOn(zappPipesModule, "loadPipesData")
124
125
  .mockImplementation(jest.fn());
125
126
 
126
- const initialStore = mockStore({
127
+ const initialStore = {
127
128
  plugins: [],
128
129
  zappPipes: { "test://testfakeurl": "foobar" },
129
- });
130
+ };
130
131
 
131
132
  const { result, rerender } = renderHook(
132
133
  () => useFeedLoader({ feedUrl: "test://testfakeurl2" }),
@@ -135,15 +136,19 @@ describe("useFeedLoader", () => {
135
136
 
136
137
  expect(result.current.data).toBeNull();
137
138
 
138
- expect(loadPipesDataSpy).toBeCalledWith(feedUrl, {
139
+ expect(loadPipesDataSpy).toHaveBeenCalledWith(feedUrl, {
139
140
  clearCache: true,
140
141
  riverId: undefined,
142
+ callback: expect.any(Function),
143
+ resolvers: {
144
+ screen: expect.any(ScreenStateResolver),
145
+ },
141
146
  });
142
147
 
143
- const store2 = mockStore({
148
+ const store2 = {
144
149
  plugins: [],
145
150
  zappPipes: { "test://testfakeurl2": mockZappPipesData },
146
- });
151
+ };
147
152
 
148
153
  rerender({ store: store2 });
149
154
 
@@ -164,10 +169,10 @@ describe("useFeedLoader", () => {
164
169
  .spyOn(reactReduxModules, "useDispatch")
165
170
  .mockImplementation(() => jest.fn());
166
171
 
167
- const initialStore = mockStore({
172
+ const initialStore = {
168
173
  plugins: [],
169
174
  zappPipes: { "test://testfakeurl": "foobar" },
170
- });
175
+ };
171
176
 
172
177
  const { result, rerender } = renderHook(
173
178
  () => useFeedLoader({ feedUrl: "test://testfakeurl2" }),
@@ -176,15 +181,22 @@ describe("useFeedLoader", () => {
176
181
 
177
182
  expect(result.current.data).toBeNull();
178
183
 
179
- expect(loadPipesDataSpy).toBeCalledWith(feedUrl, {
184
+ expect(loadPipesDataSpy.mock.calls[0][0]).toBe(feedUrl);
185
+
186
+ expect(loadPipesDataSpy.mock.calls[0][1]).toMatchObject({
180
187
  clearCache: true,
181
188
  riverId: undefined,
189
+ resolvers: {
190
+ screen: {
191
+ screenStateStore: expect.any(Function),
192
+ },
193
+ },
182
194
  });
183
195
 
184
- const store2 = mockStore({
196
+ const store2 = {
185
197
  plugins: [],
186
198
  zappPipes: { "test://testfakeurl2": mockZappPipesData },
187
- });
199
+ };
188
200
 
189
201
  rerender({ store: store2 });
190
202
 
@@ -197,8 +209,10 @@ describe("useFeedLoader", () => {
197
209
  const feedUrl = "test://testfakeurl";
198
210
  const feedUrlWithNext = "test://withnexttestfakeurl";
199
211
 
200
- const wrapper: React.FC<any> = ({ children, store }) => (
201
- <Provider store={store}>{children}</Provider>
212
+ const wrapper: React.FC<any> = ({ children, ...props }) => (
213
+ <WrappedWithProviders store={props.store || {}}>
214
+ {children}
215
+ </WrappedWithProviders>
202
216
  );
203
217
 
204
218
  describe("reloadData", () => {
@@ -211,10 +225,10 @@ describe("useFeedLoader", () => {
211
225
  .spyOn(reactReduxModules, "useDispatch")
212
226
  .mockImplementation(() => jest.fn());
213
227
 
214
- const initialStore = mockStore({
228
+ const initialStore = {
215
229
  plugins: [],
216
230
  zappPipes: { [feedUrl]: "foobar" },
217
- });
231
+ };
218
232
 
219
233
  const { result } = renderHook(() => useFeedLoader({ feedUrl }), {
220
234
  wrapper,
@@ -223,11 +237,24 @@ describe("useFeedLoader", () => {
223
237
 
224
238
  const { reloadData } = result.current;
225
239
 
226
- reloadData();
240
+ reloadData?.();
241
+
242
+ expect(loadPipesDataSpy).toHaveBeenCalled();
243
+
244
+ expect(
245
+ loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][0]
246
+ ).toBe(feedUrl);
227
247
 
228
- expect(loadPipesDataSpy).toBeCalledWith(feedUrl, {
248
+ expect(
249
+ loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][1]
250
+ ).toMatchObject({
229
251
  clearCache: true,
230
252
  silentRefresh: true,
253
+ resolvers: {
254
+ screen: {
255
+ screenStateStore: expect.any(Function),
256
+ },
257
+ },
231
258
  });
232
259
 
233
260
  loadPipesDataSpy.mockRestore();
@@ -247,10 +274,10 @@ describe("useFeedLoader", () => {
247
274
  .spyOn(reactReduxModules, "useDispatch")
248
275
  .mockImplementation(() => jest.fn());
249
276
 
250
- const initialStore = mockStore({
277
+ const initialStore = {
251
278
  plugins: [],
252
279
  zappPipes: { [feedUrlWithNext]: { data: { next: nextUrl } } },
253
- });
280
+ };
254
281
 
255
282
  const { result } = renderHook(
256
283
  () => useFeedLoader({ feedUrl: feedUrlWithNext }),
@@ -262,11 +289,24 @@ describe("useFeedLoader", () => {
262
289
 
263
290
  const { loadNext } = result.current;
264
291
 
265
- loadNext();
292
+ loadNext?.();
293
+
294
+ expect(loadPipesDataSpy).toHaveBeenCalled();
295
+
296
+ expect(
297
+ loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][0]
298
+ ).toBe(nextUrl);
266
299
 
267
- expect(loadPipesDataSpy).toBeCalledWith(nextUrl, {
300
+ expect(
301
+ loadPipesDataSpy.mock.calls[loadPipesDataSpy.mock.calls.length - 1][1]
302
+ ).toMatchObject({
268
303
  parentFeed: feedUrlWithNext,
269
304
  silentRefresh: true,
305
+ resolvers: {
306
+ screen: {
307
+ screenStateStore: expect.any(Function),
308
+ },
309
+ },
270
310
  });
271
311
 
272
312
  loadPipesDataSpy.mockRestore();