@applicaster/zapp-react-native-ui-components 12.0.0-rc.99 → 12.0.2-alpha.1558929238

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 (65) hide show
  1. package/Components/Cell/Cell.tsx +29 -10
  2. package/Components/Focusable/__tests__/index.android.test.tsx +72 -0
  3. package/Components/Focusable/index.android.tsx +15 -9
  4. package/Components/FocusableList/FocusableChild.ts +2 -2
  5. package/Components/FocusableList/index.tsx +11 -12
  6. package/Components/FocusableScrollView/index.tsx +1 -1
  7. package/Components/FreezeWithCallback/Freeze.tsx +31 -0
  8. package/Components/FreezeWithCallback/LICENSE +21 -0
  9. package/Components/FreezeWithCallback/Suspender.tsx +17 -0
  10. package/Components/FreezeWithCallback/__tests__/index.test.tsx +170 -0
  11. package/Components/FreezeWithCallback/index.tsx +3 -0
  12. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +67 -7
  13. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +53 -9
  14. package/Components/Image/Image.android.tsx +1 -1
  15. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +11 -7
  16. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +1 -7
  17. package/Components/MasterCell/DefaultComponents/CellBadge/index.tsx +6 -1
  18. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +12 -0
  19. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +6 -5
  20. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +2 -2
  21. package/Components/MasterCell/DefaultComponents/ImageBorderContainer/index.ts +71 -0
  22. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  23. package/Components/MasterCell/DefaultComponents/index.ts +2 -0
  24. package/Components/MasterCell/SharedUI/ProgressBar/hooks/useFillInPercent.ts +7 -1
  25. package/Components/MasterCell/dataAdapter.ts +21 -7
  26. package/Components/MasterCell/index.tsx +5 -3
  27. package/Components/MasterCell/utils/index.ts +3 -4
  28. package/Components/PlayerContainer/PlayerContainer.tsx +14 -8
  29. package/Components/Screen/TV/hooks/index.ts +0 -2
  30. package/Components/Screen/TV/index.web.tsx +32 -19
  31. package/Components/Screen/__tests__/Screen.test.tsx +0 -49
  32. package/Components/Screen/index.tsx +2 -20
  33. package/Components/Screen/navigationHandler.ts +1 -3
  34. package/Components/Screen/orientationHandler.ts +18 -7
  35. package/Components/Screen/utils.ts +16 -0
  36. package/Components/ScreenResolver/index.tsx +6 -11
  37. package/Components/Transitioner/Scene.tsx +9 -2
  38. package/Components/Transitioner/Transitioner.tsx +38 -23
  39. package/Components/Transitioner/__tests__/Scene.test.js +7 -0
  40. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +1 -0
  41. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +5 -0
  42. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +3 -9
  43. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +87 -41
  44. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +20 -9
  45. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +41 -22
  46. package/Components/VideoModal/PlayerDetails.tsx +16 -2
  47. package/Components/VideoModal/PlayerWrapper.tsx +84 -66
  48. package/Components/VideoModal/VideoModal.tsx +33 -27
  49. package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +7 -0
  50. package/Components/VideoModal/__tests__/__snapshots__/PlayerDetails.test.tsx.snap +2 -0
  51. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +72 -10
  52. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +8 -3
  53. package/Components/ZappFrameworkComponents/BarView/Buttons/BackButton.tsx +7 -2
  54. package/Components/ZappFrameworkComponents/BarView/Buttons/CloseButton.tsx +7 -2
  55. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +87 -0
  56. package/Components/ZappFrameworkComponents/Buttons/ActionButton.tsx +9 -4
  57. package/Components/default-cell-renderer/__tests__/__snapshots__/defaultCellRenderer.test.tsx.snap +4 -4
  58. package/Contexts/CachedDimensionsContext/index.ts +13 -9
  59. package/Contexts/ConfirmDialogState/index.ts +1 -1
  60. package/Contexts/FocusableGroupContext/withFocusableContext.tsx +18 -11
  61. package/Contexts/ScreenContext/index.tsx +6 -1
  62. package/const.ts +6 -0
  63. package/index.js +3 -0
  64. package/package.json +5 -5
  65. package/Components/Screen/TV/hooks/useRestoreFocus.ts +0 -22
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { join, path, merge } from "ramda";
2
+ import { join, merge, path } from "ramda";
3
3
  import { View } from "react-native";
4
4
 
5
5
  import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable";
@@ -67,6 +67,10 @@ type Props = {
67
67
  behavior: Behavior;
68
68
  };
69
69
 
70
+ type State = {
71
+ hasFocusableInside: boolean;
72
+ };
73
+
70
74
  const baseCellStyles = {
71
75
  height: "100%",
72
76
  display: "flex",
@@ -78,7 +82,7 @@ const touchableStyles = {
78
82
  height: "100%",
79
83
  } as const;
80
84
 
81
- export class CellComponent extends React.Component<Props> {
85
+ export class CellComponent extends React.Component<Props, State> {
82
86
  constructor(props) {
83
87
  super(props);
84
88
  this.onPress = this.onPress.bind(this);
@@ -88,6 +92,10 @@ export class CellComponent extends React.Component<Props> {
88
92
  this.hasReceivedFocus = this.hasReceivedFocus.bind(this);
89
93
  this.scrollVertically = this.scrollVertically.bind(this);
90
94
  this.scrollToIndex = this.scrollToIndex.bind(this);
95
+
96
+ this.state = {
97
+ hasFocusableInside: props.CellRenderer.hasFocusableInside?.(props.item),
98
+ };
91
99
  }
92
100
 
93
101
  setScreenLayout(componentAnchorPointY, screenLayout) {
@@ -140,10 +148,6 @@ export class CellComponent extends React.Component<Props> {
140
148
  shouldScrollVertically?.(mouse, focusable, id, title)
141
149
  ) {
142
150
  this.scrollVertically(focusable);
143
-
144
- const accessibilityManager = AccessibilityManager.getInstance();
145
- // Text from DSP should be localized already
146
- accessibilityManager.readText({ text: title });
147
151
  }
148
152
  }
149
153
  }
@@ -192,6 +196,16 @@ export class CellComponent extends React.Component<Props> {
192
196
  return !isFocusable ? false : focused || focusableFocused;
193
197
  }
194
198
 
199
+ componentDidUpdate(prevProps: Readonly<Props>) {
200
+ if (prevProps.item !== this.props.item) {
201
+ this.setState({
202
+ hasFocusableInside: this.props.CellRenderer.hasFocusableInside?.(
203
+ this.props.item
204
+ ),
205
+ });
206
+ }
207
+ }
208
+
195
209
  render() {
196
210
  const {
197
211
  item,
@@ -220,9 +234,7 @@ export class CellComponent extends React.Component<Props> {
220
234
  this.onFocus(focusable, mouse);
221
235
  };
222
236
 
223
- const hasFocusableInside = CellRenderer.hasFocusableInside?.(item);
224
-
225
- if (hasFocusableInside) {
237
+ if (this.state.hasFocusableInside) {
226
238
  return (
227
239
  <CellWithFocusable
228
240
  CellRenderer={CellRenderer}
@@ -260,13 +272,20 @@ export class CellComponent extends React.Component<Props> {
260
272
  skipFocusManagerRegistration={skipFocusManagerRegistration}
261
273
  >
262
274
  {(focused, event) => {
275
+ const isFocused = this.isCellFocused(focused);
276
+
277
+ if (isFocused) {
278
+ const accessibilityManager = AccessibilityManager.getInstance();
279
+ accessibilityManager.readText({ text: item.title });
280
+ }
281
+
263
282
  return (
264
283
  <FocusableCell
265
284
  {...{
266
285
  index,
267
286
  CellRenderer,
268
287
  item,
269
- focused: this.isCellFocused(focused),
288
+ focused: isFocused,
270
289
  scrollTo: this.scrollToIndex(event),
271
290
  behavior,
272
291
  }}
@@ -0,0 +1,72 @@
1
+ import React from "react";
2
+ import { render, act } from "@testing-library/react-native";
3
+ import { Touchable } from "../../Touchable";
4
+
5
+ const mockUseIsRTL = jest.fn();
6
+
7
+ jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
8
+ useIsRTL: mockUseIsRTL,
9
+ }));
10
+
11
+ const mockUseFocusManager = jest.fn();
12
+
13
+ jest.mock("@applicaster/zapp-react-native-utils/focusManager", () => ({
14
+ useFocusManager: mockUseFocusManager,
15
+ }));
16
+
17
+ const mockUsePrevious = jest.fn();
18
+
19
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/utils", () => ({
20
+ usePrevious: mockUsePrevious,
21
+ }));
22
+
23
+ const { Focusable } = require("../index.android");
24
+
25
+ describe("Focusable", () => {
26
+ const mockFocusManager = {
27
+ isFocused: jest.fn(),
28
+ onDisableFocusChange: jest.fn(),
29
+ registerFocusable: jest.fn(),
30
+ focusedId: "test-id",
31
+ };
32
+
33
+ beforeEach(() => {
34
+ jest.clearAllMocks();
35
+ mockUseIsRTL.mockReturnValue(false);
36
+ mockUseFocusManager.mockReturnValue(mockFocusManager);
37
+ mockUsePrevious.mockReturnValue(false);
38
+ });
39
+
40
+ it("updates disableFocus state when disableFocus prop changes", () => {
41
+ const { rerender } = render(
42
+ <Focusable id="test-id" disableFocus={false}>
43
+ <Touchable testID="touchable" />
44
+ </Focusable>
45
+ );
46
+
47
+ rerender(
48
+ <Focusable id="test-id" disableFocus={true}>
49
+ <Touchable testID="touchable" />
50
+ </Focusable>
51
+ );
52
+
53
+ expect(mockFocusManager.onDisableFocusChange).toHaveBeenCalledWith(
54
+ "test-id"
55
+ );
56
+ });
57
+
58
+ it("calls focusManager.registerFocusable on mount and unregisters on unmount", () => {
59
+ const unregister = jest.fn();
60
+ mockFocusManager.registerFocusable.mockReturnValue(unregister);
61
+
62
+ const { unmount } = render(
63
+ <Focusable id="test-id">
64
+ <Touchable testID="touchable" />
65
+ </Focusable>
66
+ );
67
+
68
+ expect(mockFocusManager.registerFocusable).toHaveBeenCalled();
69
+ act(() => unmount());
70
+ expect(unregister).toHaveBeenCalled();
71
+ });
72
+ });
@@ -128,7 +128,10 @@ function FocusableComponent(props: Props, forwardedRef) {
128
128
 
129
129
  const _onBlur = React.useCallback(
130
130
  (ref, options = {}) => {
131
- setFocused(false);
131
+ if (ref.props.id !== focusManager.focusedId) {
132
+ setFocused(false);
133
+ }
134
+
132
135
  onBlur?.(ref, options);
133
136
  },
134
137
  [onBlur]
@@ -148,15 +151,14 @@ function FocusableComponent(props: Props, forwardedRef) {
148
151
  [isRTL, nextFocusDown, nextFocusUp, nextFocusLeft, nextFocusRight]
149
152
  );
150
153
 
151
- const contextState = React.useMemo(
152
- () => ({
154
+ const contextState = React.useMemo(() => {
155
+ return {
153
156
  focused,
154
157
  ...parentFocus,
155
158
  ref: touchableRef,
156
159
  setIsFocusable,
157
- }),
158
- [parentFocus, focused]
159
- );
160
+ };
161
+ }, [parentFocus, focused]);
160
162
 
161
163
  return (
162
164
  <Touchable
@@ -173,9 +175,13 @@ function FocusableComponent(props: Props, forwardedRef) {
173
175
  {...parentFocus}
174
176
  >
175
177
  <FocusableContext.Provider value={contextState}>
176
- {typeof children === "function"
177
- ? children(focused, parentFocus, touchableRef)
178
- : children}
178
+ {React.useMemo(
179
+ () =>
180
+ typeof children === "function"
181
+ ? children(focused, parentFocus, touchableRef)
182
+ : children,
183
+ [children, focused, parentFocus]
184
+ )}
179
185
  </FocusableContext.Provider>
180
186
  </Touchable>
181
187
  );
@@ -31,14 +31,14 @@ const useParentFocus = (
31
31
 
32
32
  function FocusableChildComponent(props) {
33
33
  const { setIsFocusable, ...focusableContext } = useFocusable();
34
+ const focused = useCellState(props.id);
35
+
34
36
  const parentFocus = useParentFocus(props, focusableContext);
35
37
 
36
38
  const onLoadFailed = React.useCallback(() => {
37
39
  setIsFocusable(false);
38
40
  }, []);
39
41
 
40
- const focused = useCellState(props.id);
41
-
42
42
  return props.renderItem({
43
43
  ...props,
44
44
  focused,
@@ -88,6 +88,7 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
88
88
  data = [],
89
89
  // eslint-disable-next-line unused-imports/no-unused-vars
90
90
  onAllComponentsLoaded, // TODO: re-implement it
91
+ omitPropsPropagation = [],
91
92
  } = props;
92
93
 
93
94
  useCheckItemIdsForUnique({ componentId: props.id, items: data });
@@ -252,9 +253,9 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
252
253
  ]
253
254
  );
254
255
 
255
- const flatListProps = React.useMemo(
256
- () =>
257
- R.omit([
256
+ const getFlatListProps = React.useCallback(
257
+ R.omit(
258
+ [
258
259
  "onListElementFocus",
259
260
  "onListElementPress",
260
261
  "onListElementPressOut",
@@ -267,17 +268,17 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
267
268
  "focusableItemProps",
268
269
  "renderItem",
269
270
  "extraChildrenData",
270
- "extraData",
271
- "keyExtractor",
272
271
  "onAllComponentsLoaded",
273
272
  "focused",
274
273
  "id",
275
274
  "initialFocusDirection",
276
275
  "withStateMemory",
277
276
  "useSequentialLoading",
278
- ...(props.omitPropsPropagation || []),
279
- ])(props),
280
- [props]
277
+ ...omitPropsPropagation,
278
+ ],
279
+ R.__
280
+ ),
281
+ [omitPropsPropagation]
281
282
  );
282
283
 
283
284
  const onEndReached = React.useCallback(
@@ -289,7 +290,7 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
289
290
 
290
291
  const initialNumToRender = toFiniteNumberWithDefault(
291
292
  10,
292
- flatListProps.initialNumToRender
293
+ props.initialNumToRender
293
294
  );
294
295
 
295
296
  return (
@@ -298,12 +299,10 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
298
299
  <FlatList
299
300
  focusable={false}
300
301
  scrollEnabled={false}
301
- extraData={props.extraData}
302
302
  ref={ref}
303
- {...flatListProps}
303
+ {...getFlatListProps(props)}
304
304
  renderItem={renderItem}
305
305
  onEndReached={onEndReached}
306
- keyExtractor={props.keyExtractor}
307
306
  initialNumToRender={initialNumToRender}
308
307
  />
309
308
  </ChildrenFocusDeactivatorView>
@@ -284,7 +284,7 @@ function FocusableScrollViewComponent(props: Props, ref) {
284
284
  };
285
285
 
286
286
  return (
287
- <View onLayout={onItemLayout}>
287
+ <View key={id} onLayout={onItemLayout}>
288
288
  <Focusable
289
289
  {...nextFocus}
290
290
  onFocus={(element, direction) =>
@@ -0,0 +1,31 @@
1
+ import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
2
+ import React, { Suspense, useEffect } from "react";
3
+ import { Suspender } from "./Suspender";
4
+
5
+ interface Props {
6
+ freeze: boolean;
7
+ children: React.ReactNode;
8
+ placeholder?: React.ReactNode;
9
+ onRelease?: () => void;
10
+ }
11
+
12
+ export function Freeze({
13
+ freeze,
14
+ children,
15
+ placeholder = null,
16
+ onRelease,
17
+ }: Props) {
18
+ const prevFreeze = usePrevious(freeze);
19
+
20
+ useEffect(() => {
21
+ if (prevFreeze && !freeze && onRelease) {
22
+ onRelease();
23
+ }
24
+ }, [freeze, onRelease, prevFreeze]);
25
+
26
+ return (
27
+ <Suspense fallback={placeholder}>
28
+ <Suspender freeze={freeze}>{children}</Suspender>
29
+ </Suspense>
30
+ );
31
+ }
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Software Mansion
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ import React, { Fragment } from "react";
2
+
3
+ const infiniteThenable = { then() {} };
4
+
5
+ export const Suspender = ({
6
+ freeze,
7
+ children,
8
+ }: {
9
+ freeze: boolean;
10
+ children: React.ReactNode;
11
+ }) => {
12
+ if (freeze) {
13
+ throw infiniteThenable;
14
+ }
15
+
16
+ return <Fragment>{children}</Fragment>;
17
+ };
@@ -0,0 +1,170 @@
1
+ import React, { Dispatch, useEffect, useState } from "react";
2
+ import {
3
+ act,
4
+ create,
5
+ ReactTestRenderer,
6
+ ReactTestRendererJSON,
7
+ } from "react-test-renderer";
8
+ import { FreezeWithCallback } from "../index";
9
+
10
+ const SimpleContent = () => <div />;
11
+
12
+ interface InnerProps {
13
+ value: number;
14
+ }
15
+ // eslint-disable-next-line unused-imports/no-unused-vars
16
+ const Inner = ({ value }: InnerProps) => <div />;
17
+
18
+ interface TestSubscriber {
19
+ renderCount: number;
20
+ subscription: Dispatch<number>;
21
+ }
22
+
23
+ function createSubscriberComponent(): [React.FC, TestSubscriber] {
24
+ const testState: TestSubscriber = {
25
+ renderCount: 0,
26
+ subscription: () => {},
27
+ };
28
+
29
+ const Subscriber: React.FC = () => {
30
+ const [value, setValue] = useState(0);
31
+
32
+ useEffect(() => {
33
+ testState.subscription = setValue;
34
+ }, []);
35
+
36
+ testState.renderCount += 1;
37
+
38
+ return <Inner value={value} />;
39
+ };
40
+
41
+ return [Subscriber, testState];
42
+ }
43
+
44
+ interface ContainerProps {
45
+ freeze: boolean;
46
+ children: React.ReactNode;
47
+ }
48
+
49
+ const Container = ({ freeze, children }: ContainerProps) => (
50
+ <FreezeWithCallback freeze={freeze}>{children}</FreezeWithCallback>
51
+ );
52
+
53
+ function setupTest(initialFreeze: boolean = false) {
54
+ let testRenderer: ReactTestRenderer | undefined;
55
+ const [Subscriber, subscriberState] = createSubscriberComponent();
56
+
57
+ act(() => {
58
+ testRenderer = create(
59
+ <Container freeze={initialFreeze}>
60
+ <Subscriber />
61
+ </Container>
62
+ );
63
+ });
64
+
65
+ return {
66
+ testRenderer,
67
+ testInstance: testRenderer?.root,
68
+ subscriberState,
69
+ updateFreeze: (freeze: boolean) => {
70
+ act(() =>
71
+ testRenderer?.update(
72
+ <Container freeze={freeze}>
73
+ <Subscriber />
74
+ </Container>
75
+ )
76
+ );
77
+ },
78
+ };
79
+ }
80
+
81
+ describe("FreezeWithCallback", () => {
82
+ test("Renders stuff not frozen", () => {
83
+ const testRenderer = create(
84
+ <Container freeze={false}>
85
+ <SimpleContent />
86
+ </Container>
87
+ );
88
+
89
+ expect(testRenderer.root.findByType(SimpleContent)).toBeTruthy();
90
+ });
91
+
92
+ test("Does not render stuff when frozen", () => {
93
+ const testRenderer = create(
94
+ <Container freeze>
95
+ <SimpleContent />
96
+ </Container>
97
+ );
98
+
99
+ expect(testRenderer.root.findAllByType(SimpleContent)).toHaveLength(0);
100
+ });
101
+
102
+ test("Stuff is gone after freeze", () => {
103
+ const testRenderer = create(
104
+ <Container freeze={false}>
105
+ <SimpleContent />
106
+ </Container>
107
+ );
108
+
109
+ expect(testRenderer.root.findByType(SimpleContent)).toBeTruthy();
110
+
111
+ act(() =>
112
+ testRenderer.update(
113
+ <Container freeze>
114
+ <SimpleContent />
115
+ </Container>
116
+ )
117
+ );
118
+
119
+ expect(testRenderer.toJSON()).toBe(null);
120
+ });
121
+
122
+ test("Updates work when not frozen", () => {
123
+ const { testInstance, subscriberState } = setupTest();
124
+
125
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
126
+ act(() => subscriberState.subscription(1));
127
+ expect(testInstance?.findByType(Inner).props.value).toEqual(1);
128
+ expect(subscriberState.renderCount).toBe(2);
129
+ });
130
+
131
+ test("Updates does not propagate when frozen", () => {
132
+ const { testInstance, subscriberState, updateFreeze } = setupTest();
133
+
134
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
135
+ updateFreeze(true);
136
+ act(() => subscriberState.subscription(1));
137
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
138
+ expect(subscriberState.renderCount).toBe(1);
139
+ });
140
+
141
+ test("State persists after defrost", () => {
142
+ const { testInstance, subscriberState, updateFreeze, testRenderer } =
143
+ setupTest();
144
+
145
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
146
+ act(() => subscriberState.subscription(1));
147
+ expect(testInstance?.findByType(Inner).props.value).toEqual(1);
148
+
149
+ updateFreeze(true);
150
+ expect(testRenderer?.toJSON()).toBe(null);
151
+
152
+ updateFreeze(false);
153
+ expect((testRenderer?.toJSON() as ReactTestRendererJSON).type).toBe("div");
154
+ expect(testInstance?.findByType(Inner).props.value).toEqual(1);
155
+ });
156
+
157
+ test("Update propagate after defrost", () => {
158
+ const { testInstance, subscriberState, updateFreeze } = setupTest();
159
+
160
+ updateFreeze(true);
161
+ act(() => subscriberState.subscription(1));
162
+ act(() => subscriberState.subscription(2));
163
+ act(() => subscriberState.subscription(3));
164
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
165
+
166
+ updateFreeze(false);
167
+ expect(testInstance?.findByType(Inner).props.value).toEqual(3);
168
+ expect(subscriberState.renderCount).toBe(2);
169
+ });
170
+ });
@@ -0,0 +1,3 @@
1
+ import { Freeze } from "./Freeze";
2
+
3
+ export const FreezeWithCallback = Freeze;
@@ -1,3 +1,4 @@
1
+ import * as React from "react";
1
2
  import { renderHook } from "@testing-library/react-hooks";
2
3
 
3
4
  import {
@@ -9,6 +10,42 @@ import {
9
10
  import * as redux from "react-redux";
10
11
  import * as layoutPresets from "@applicaster/zapp-react-native-redux/hooks/useLayoutPresets";
11
12
  import * as pipesFeeds from "@applicaster/zapp-react-native-redux/hooks/useZappPipesFeeds";
13
+ import { NavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NavigationContext";
14
+ import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
15
+
16
+ import { Provider } from "react-redux";
17
+ import configureStore from "redux-mock-store";
18
+
19
+ const homeStack = {
20
+ route: "/home",
21
+ state: {
22
+ screen: { id: "123" },
23
+ entry: { id: "abc" },
24
+ },
25
+ };
26
+
27
+ const mainStackNavigator = {
28
+ getStackForPathname: jest.fn().mockReturnValue(homeStack),
29
+ modalState: {
30
+ screen: {
31
+ id: "modal-1",
32
+ },
33
+ },
34
+ };
35
+
36
+ const store = configureStore()({});
37
+
38
+ const wrapper = ({ children }) => (
39
+ <Provider store={store}>
40
+ <NavigationContext.Provider
41
+ value={{ ...mainStackNavigator, currentRoute: homeStack.route }}
42
+ >
43
+ <PathnameContext.Provider value={homeStack.route}>
44
+ {children}
45
+ </PathnameContext.Provider>
46
+ </NavigationContext.Provider>
47
+ </Provider>
48
+ );
12
49
 
13
50
  describe("getTransformedPreset should return the passed components if smartComponents is empty", () => {
14
51
  describe("getTransformedPreset function", () => {
@@ -94,9 +131,13 @@ describe("getTransformedPreset should return the passed components if smartCompo
94
131
  },
95
132
  };
96
133
 
134
+ const mockUrlsMap = {
135
+ [mockSmartComp.id]: mockSmartComp.data.source,
136
+ };
137
+
97
138
  it("should return the component itself if its component_type is not SMART_COMPONENT_TYPE", () => {
98
139
  expect(
99
- enrichComponent(mockComp, 0, mockFeeds, mockLayoutPresets)
140
+ enrichComponent(mockComp, 0, mockFeeds, mockLayoutPresets, mockUrlsMap)
100
141
  ).toEqual([mockComp]);
101
142
  });
102
143
 
@@ -106,8 +147,18 @@ describe("getTransformedPreset should return the passed components if smartCompo
106
147
  data: { source: "missing_source" },
107
148
  };
108
149
 
150
+ const missing_source_urls_map = {
151
+ [missing_source_comp.id]: missing_source_comp.data.source,
152
+ };
153
+
109
154
  expect(
110
- enrichComponent(missing_source_comp, 0, mockFeeds, mockLayoutPresets)
155
+ enrichComponent(
156
+ missing_source_comp,
157
+ 0,
158
+ mockFeeds,
159
+ mockLayoutPresets,
160
+ missing_source_urls_map
161
+ )
111
162
  ).toEqual(null);
112
163
  });
113
164
 
@@ -116,7 +167,8 @@ describe("getTransformedPreset should return the passed components if smartCompo
116
167
  mockSmartComp,
117
168
  0,
118
169
  mockFeeds,
119
- mockLayoutPresets
170
+ mockLayoutPresets,
171
+ mockUrlsMap
120
172
  );
121
173
 
122
174
  expect(Array.isArray(result)).toBe(true);
@@ -262,7 +314,9 @@ describe("getTransformedPreset should return the passed components if smartCompo
262
314
 
263
315
  mockUseLayoutPresets.mockReturnValue({});
264
316
 
265
- const { result } = renderHook(() => useCurationAPI(mockComponents));
317
+ const { result } = renderHook(() => useCurationAPI(mockComponents), {
318
+ wrapper,
319
+ });
266
320
 
267
321
  // if there are no smart components, it should return the original array
268
322
  expect(result.current).toEqual(mockComponents);
@@ -313,7 +367,9 @@ describe("getTransformedPreset should return the passed components if smartCompo
313
367
 
314
368
  mockUseLayoutPresets.mockReturnValue(mockLayoutPresets);
315
369
 
316
- const { result } = renderHook(() => useCurationAPI(mockComponents));
370
+ const { result } = renderHook(() => useCurationAPI(mockComponents), {
371
+ wrapper,
372
+ });
317
373
 
318
374
  expect(result.current).toEqual(mockTransformedComponents);
319
375
  });
@@ -383,7 +439,9 @@ describe("getTransformedPreset should return the passed components if smartCompo
383
439
 
384
440
  mockUseLayoutPresets.mockReturnValue(mockLayoutPresets);
385
441
 
386
- const { result } = renderHook(() => useCurationAPI(mockComponents));
442
+ const { result } = renderHook(() => useCurationAPI(mockComponents), {
443
+ wrapper,
444
+ });
387
445
 
388
446
  expect(result.current).toEqual(mockTransformedComponents);
389
447
  });
@@ -434,7 +492,9 @@ describe("getTransformedPreset should return the passed components if smartCompo
434
492
  const mockUseLayoutPresets = jest.spyOn(layoutPresets, "useLayoutPresets");
435
493
  mockUseLayoutPresets.mockReturnValue(mockLayoutPresets);
436
494
 
437
- const { result } = renderHook(() => useCurationAPI(mockComponents));
495
+ const { result } = renderHook(() => useCurationAPI(mockComponents), {
496
+ wrapper,
497
+ });
438
498
 
439
499
  expect(result.current).toEqual(mockTransformedComponents);
440
500
  });