@applicaster/zapp-react-native-ui-components 15.0.0-alpha.9542542611 → 15.0.0-alpha.9617326943
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.
- package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
- package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
- package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
- package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
- package/Components/HookRenderer/HookRenderer.tsx +40 -10
- package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
- package/Components/PlayerContainer/PlayerContainer.tsx +1 -17
- package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
- package/Components/PreloaderWrapper/index.tsx +15 -0
- package/Components/River/RefreshControl.tsx +9 -3
- package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
- package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
- package/Components/ScreenFeedLoader/index.ts +1 -0
- package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
- package/Components/ScreenResolver/hooks/index.ts +3 -0
- package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
- package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
- package/Components/ScreenResolver/index.tsx +15 -117
- package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
- package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
- package/Components/ScreenResolver/utils/index.ts +1 -0
- package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
- package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
- package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
- package/Components/ScreenResolverFeedProvider/index.ts +1 -0
- package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
- package/Components/index.js +1 -1
- package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
- package/Contexts/ScreenContext/index.tsx +46 -1
- package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
- package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
- package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
- package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
- package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
- package/package.json +5 -5
- package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
- package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
- package/Components/PlayerContainer/useRestrictMobilePlayback.tsx +0 -131
- /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
|
@@ -12,12 +12,26 @@ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
|
12
12
|
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
13
13
|
import { ScreenTrackedViewPositionsContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenTrackedViewPositionsContext";
|
|
14
14
|
import { useEventAlerts } from "./utils/useEventAlerts";
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
import {
|
|
16
|
+
selectRiverById,
|
|
17
|
+
useAppSelector,
|
|
18
|
+
} from "@applicaster/zapp-react-native-redux";
|
|
19
|
+
import { getScreenDataSource } from "./utils/getScreenDataSource";
|
|
20
|
+
import { ScreenResolverFeedProvider } from "../ScreenResolverFeedProvider/ScreenResolverFeedProvider";
|
|
21
|
+
|
|
22
|
+
const { log_debug } = createLogger({
|
|
17
23
|
category: "ScreenContainer",
|
|
18
24
|
subsystem: "General",
|
|
19
25
|
});
|
|
20
26
|
|
|
27
|
+
/** Provides screen-feed from general-screen configuration (if defined) */
|
|
28
|
+
const useFeedData = (id) => {
|
|
29
|
+
const river = useAppSelector((state) => selectRiverById(state, id));
|
|
30
|
+
const feedData = getScreenDataSource(river);
|
|
31
|
+
|
|
32
|
+
return feedData;
|
|
33
|
+
};
|
|
34
|
+
|
|
21
35
|
export const GeneralContentScreen = ({
|
|
22
36
|
feed,
|
|
23
37
|
screenId,
|
|
@@ -54,20 +68,15 @@ export const GeneralContentScreen = ({
|
|
|
54
68
|
useEffect(() => {
|
|
55
69
|
if (!riverActionProvidersReady) {
|
|
56
70
|
if (actionsInitialStateSetters.length > 0) {
|
|
57
|
-
log_info(
|
|
58
|
-
"ScreenContainer: starting to check river action providers to initialize",
|
|
59
|
-
{ actionsInitialStateSetters }
|
|
60
|
-
);
|
|
61
|
-
|
|
62
71
|
allSettled(actionsInitialStateSetters).finally(() => {
|
|
63
|
-
|
|
72
|
+
log_debug(
|
|
64
73
|
"ScreenContainer: action provider ready, completed. Starting to present screen"
|
|
65
74
|
);
|
|
66
75
|
|
|
67
76
|
setRiverActionProvidersReady(true);
|
|
68
77
|
});
|
|
69
78
|
} else {
|
|
70
|
-
|
|
79
|
+
log_debug(
|
|
71
80
|
"ScreenContainer: no action provider to check, completed. Starting to present screen"
|
|
72
81
|
);
|
|
73
82
|
|
|
@@ -108,24 +117,26 @@ export const GeneralContentScreen = ({
|
|
|
108
117
|
if (!isReady || isNilOrEmpty(components || uiComponents)) return null;
|
|
109
118
|
|
|
110
119
|
return (
|
|
111
|
-
<
|
|
112
|
-
<
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
120
|
+
<ScreenResolverFeedProvider id={screenId} useFeedData={useFeedData}>
|
|
121
|
+
<ScreenTrackedViewPositionsContext.Provider>
|
|
122
|
+
<CellTapContext.Provider value={contextValue}>
|
|
123
|
+
<ComponentsMap
|
|
124
|
+
feed={feed}
|
|
125
|
+
riverId={screenId}
|
|
126
|
+
groupId={groupId || `general-content-screen-${screenId}`}
|
|
127
|
+
riverComponents={components || uiComponents}
|
|
128
|
+
scrollViewExtraProps={scrollViewExtraProps}
|
|
129
|
+
getStaticComponentFeed={getStaticComponentFeed}
|
|
130
|
+
extraAnchorPointYOffset={extraAnchorPointYOffset}
|
|
131
|
+
isScreenWrappedInContainer={isScreenWrappedInContainer}
|
|
132
|
+
parentFocus={parentFocus}
|
|
133
|
+
focused={focused}
|
|
134
|
+
containerHeight={containerHeight}
|
|
135
|
+
preferredFocus={preferredFocus}
|
|
136
|
+
{...componentsMapExtraProps}
|
|
137
|
+
/>
|
|
138
|
+
</CellTapContext.Provider>
|
|
139
|
+
</ScreenTrackedViewPositionsContext.Provider>
|
|
140
|
+
</ScreenResolverFeedProvider>
|
|
130
141
|
);
|
|
131
142
|
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@testing-library/react-native";
|
|
3
|
+
import { GeneralContentScreen } from "../GeneralContentScreen";
|
|
4
|
+
|
|
5
|
+
const mockUseAppSelector = jest.fn();
|
|
6
|
+
const mockSelectRiverById = jest.fn();
|
|
7
|
+
const mockProviderSpy = jest.fn();
|
|
8
|
+
|
|
9
|
+
jest.mock("../../River/ComponentsMap", () => ({
|
|
10
|
+
ComponentsMap: () => {
|
|
11
|
+
const React = require("react");
|
|
12
|
+
const { View } = require("react-native");
|
|
13
|
+
|
|
14
|
+
return <View testID="components-map" />;
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/actions", () => ({
|
|
19
|
+
useActions: jest.fn(() => jest.fn()),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock("../utils", () => ({
|
|
23
|
+
logger: { warn: jest.fn() },
|
|
24
|
+
whenMatchingType: jest.fn((_type, value) => value),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/layout", () => ({
|
|
28
|
+
useLayoutVersion: jest.fn(() => false),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
jest.mock(
|
|
32
|
+
"@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenData",
|
|
33
|
+
() => ({
|
|
34
|
+
useScreenData: jest.fn(() => ({
|
|
35
|
+
ui_components: [{ id: "ui-component" }],
|
|
36
|
+
general: {},
|
|
37
|
+
})),
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
jest.mock("../utils/useCurationAPI", () => ({
|
|
42
|
+
useCurationAPI: jest.fn(() => [{ id: "curation-component" }]),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
jest.mock("@applicaster/quick-brick-core/App/ActionSetters", () => ({
|
|
46
|
+
useRiverInitialState: jest.fn(() => []),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
jest.mock("../utils/useEventAlerts", () => ({
|
|
50
|
+
useEventAlerts: jest.fn(),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
jest.mock("@applicaster/zapp-react-native-redux", () => ({
|
|
54
|
+
useAppSelector: (...args) => mockUseAppSelector(...args),
|
|
55
|
+
selectRiverById: (...args) => mockSelectRiverById(...args),
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
jest.mock("../utils/getScreenDataSource", () => ({
|
|
59
|
+
getScreenDataSource: jest.fn(() => ({
|
|
60
|
+
source: "https://feed",
|
|
61
|
+
mapping: {},
|
|
62
|
+
})),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
jest.mock(
|
|
66
|
+
"../../ScreenResolverFeedProvider/ScreenResolverFeedProvider",
|
|
67
|
+
() => ({
|
|
68
|
+
ScreenResolverFeedProvider: ({ id, useFeedData, children }) => {
|
|
69
|
+
const React = require("react");
|
|
70
|
+
const { View } = require("react-native");
|
|
71
|
+
|
|
72
|
+
mockProviderSpy(id, useFeedData);
|
|
73
|
+
useFeedData(id);
|
|
74
|
+
|
|
75
|
+
return <View testID="screen-resolver-feed-provider">{children}</View>;
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
describe("GeneralContentScreen", () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
jest.clearAllMocks();
|
|
83
|
+
mockUseAppSelector.mockImplementation((selector) => selector({}));
|
|
84
|
+
mockSelectRiverById.mockReturnValue({ id: "screen-1" });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("wraps content with ScreenResolverFeedProvider and renders ComponentsMap", () => {
|
|
88
|
+
const { getByTestId } = render(
|
|
89
|
+
<GeneralContentScreen
|
|
90
|
+
screenId="screen-1"
|
|
91
|
+
feed={null}
|
|
92
|
+
components={[{ id: "component-1" }]}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(getByTestId("screen-resolver-feed-provider")).toBeDefined();
|
|
97
|
+
expect(getByTestId("components-map")).toBeDefined();
|
|
98
|
+
|
|
99
|
+
expect(mockProviderSpy).toHaveBeenCalledWith(
|
|
100
|
+
"screen-1",
|
|
101
|
+
expect.any(Function)
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getScreenDataSource } from "../getScreenDataSource";
|
|
2
|
+
|
|
3
|
+
describe("getScreenDataSource", () => {
|
|
4
|
+
it("returns screen_feed data when present", () => {
|
|
5
|
+
const result = getScreenDataSource({
|
|
6
|
+
data: {
|
|
7
|
+
screen_feed: {
|
|
8
|
+
source: "https://feed",
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
expect(result).toEqual({ source: "https://feed" });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("returns undefined when screen_feed is missing", () => {
|
|
17
|
+
expect(getScreenDataSource({ data: {} })).toBeUndefined();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { get } from "@applicaster/zapp-react-native-utils/utils";
|
|
2
|
+
|
|
3
|
+
const lookupPath = ["data", "screen_feed"];
|
|
4
|
+
|
|
5
|
+
export const getScreenDataSource = (
|
|
6
|
+
screenData: any
|
|
7
|
+
): Option<ZappDataSource> => {
|
|
8
|
+
return get(screenData, lookupPath) as ZappDataSource | undefined;
|
|
9
|
+
};
|
|
@@ -6,7 +6,12 @@ import {
|
|
|
6
6
|
} from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
7
7
|
import { useHookAnalytics } from "@applicaster/zapp-react-native-utils/analyticsUtils/helpers/hooks";
|
|
8
8
|
import { useSetNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
import { componentsLogger } from "../../Helpers/logger";
|
|
11
|
+
|
|
12
|
+
const logger = componentsLogger.addSubsystem("HookRenderer");
|
|
13
|
+
|
|
14
|
+
const HOOK_PRESENTATION_TYPE = "Hook";
|
|
10
15
|
|
|
11
16
|
type Props = {
|
|
12
17
|
focused?: boolean;
|
|
@@ -15,20 +20,17 @@ type Props = {
|
|
|
15
20
|
callback: hookCallback;
|
|
16
21
|
};
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
|
|
21
|
-
screenData: { payload, hookPlugin },
|
|
22
|
-
callback,
|
|
23
|
-
} = props;
|
|
24
|
-
|
|
25
|
-
const { setVisible: showNavBar } = useSetNavbarState();
|
|
23
|
+
const HookRenderer = (props: Props) => {
|
|
24
|
+
const { focused, screenData, callback } = props;
|
|
25
|
+
const { payload, hookPlugin } = screenData;
|
|
26
26
|
|
|
27
27
|
const {
|
|
28
28
|
module: { Component: HookComponent, presentFullScreen },
|
|
29
29
|
configuration,
|
|
30
30
|
} = hookPlugin;
|
|
31
31
|
|
|
32
|
+
const { setVisible: showNavBar } = useSetNavbarState();
|
|
33
|
+
|
|
32
34
|
useHookAnalytics(props);
|
|
33
35
|
|
|
34
36
|
const isNavBarVisible = useIsNavBarVisible();
|
|
@@ -63,8 +65,36 @@ export const HookRenderer = (props: Props) => {
|
|
|
63
65
|
hookPlugin,
|
|
64
66
|
focused,
|
|
65
67
|
parentFocus,
|
|
66
|
-
presentationType:
|
|
68
|
+
presentationType: HOOK_PRESENTATION_TYPE,
|
|
67
69
|
}}
|
|
68
70
|
/>
|
|
69
71
|
);
|
|
70
72
|
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Guard component to prevent rendering HookRenderer when screenData or hookPlugin is missing. This is to avoid potential crashes due to missing data.
|
|
76
|
+
*/
|
|
77
|
+
const HookRendererGuard = (props: Props) => {
|
|
78
|
+
React.useEffect(() => {
|
|
79
|
+
if (!props.screenData) {
|
|
80
|
+
logger.error(
|
|
81
|
+
"HookRenderer received no screenData, screen cannot be rendered"
|
|
82
|
+
);
|
|
83
|
+
} else if (!props.screenData.hookPlugin) {
|
|
84
|
+
logger.error(
|
|
85
|
+
"HookRenderer received screenData with no hookPlugin, screen cannot be rendered",
|
|
86
|
+
{
|
|
87
|
+
screenData: props.screenData,
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}, [props.screenData]);
|
|
92
|
+
|
|
93
|
+
if (!props.screenData || !props.screenData.hookPlugin) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return <HookRenderer {...props} />;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export { HookRendererGuard as HookRenderer };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { render } from "@testing-library/react-native";
|
|
4
|
+
import { HookRenderer } from "..";
|
|
5
|
+
|
|
6
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactUtils", () => ({
|
|
7
|
+
isTV: jest.fn(() => false),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/navigation", () => ({
|
|
11
|
+
useBackHandler: jest.fn(),
|
|
12
|
+
useIsNavBarVisible: jest.fn(() => true),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
jest.mock(
|
|
16
|
+
"@applicaster/zapp-react-native-utils/analyticsUtils/helpers/hooks",
|
|
17
|
+
() => ({
|
|
18
|
+
useHookAnalytics: jest.fn(),
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => ({
|
|
23
|
+
useSetNavbarState: jest.fn(() => ({
|
|
24
|
+
setVisible: jest.fn(),
|
|
25
|
+
})),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
describe("HookRenderer", () => {
|
|
29
|
+
it("returns null when hookPlugin is missing", () => {
|
|
30
|
+
const { toJSON } = render(
|
|
31
|
+
<HookRenderer callback={jest.fn()} screenData={{ payload: {} } as any} />
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(toJSON()).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("passes Hook presentationType to rendered hook component", () => {
|
|
38
|
+
const HookComponent = (props) => (
|
|
39
|
+
<View testID="hook-component" {...props} />
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const { getByTestId } = render(
|
|
43
|
+
<HookRenderer
|
|
44
|
+
callback={jest.fn()}
|
|
45
|
+
screenData={{
|
|
46
|
+
payload: { foo: "bar" },
|
|
47
|
+
hookPlugin: {
|
|
48
|
+
module: {
|
|
49
|
+
Component: HookComponent,
|
|
50
|
+
presentFullScreen: false,
|
|
51
|
+
},
|
|
52
|
+
configuration: {},
|
|
53
|
+
},
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(getByTestId("hook-component").props.presentationType).toBe("Hook");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -46,7 +46,6 @@ import {
|
|
|
46
46
|
PlayerContainerContextProvider,
|
|
47
47
|
} from "./PlayerContainerContext";
|
|
48
48
|
import { FocusableGroup } from "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup";
|
|
49
|
-
import { ErrorDisplay } from "./ErrorDisplay";
|
|
50
49
|
import { PlayerFocusableWrapperView } from "./WappersView/PlayerFocusableWrapperView";
|
|
51
50
|
import { FocusableGroupMainContainerId } from "./index";
|
|
52
51
|
import { isPlayable } from "@applicaster/zapp-react-native-utils/navigationUtils/itemTypeMatchers";
|
|
@@ -61,7 +60,6 @@ import {
|
|
|
61
60
|
PlayerNativeSendCommand,
|
|
62
61
|
} from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand";
|
|
63
62
|
import { useAppData } from "@applicaster/zapp-react-native-redux";
|
|
64
|
-
import { useRestrictMobilePlayback } from "./useRestrictMobilePlayback";
|
|
65
63
|
|
|
66
64
|
type Props = {
|
|
67
65
|
Player: React.ComponentType<any>;
|
|
@@ -274,12 +272,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
274
272
|
);
|
|
275
273
|
}, [playerManager.isRegistered()]);
|
|
276
274
|
|
|
277
|
-
const { isRestricted } = useRestrictMobilePlayback({
|
|
278
|
-
player,
|
|
279
|
-
entry: item,
|
|
280
|
-
close,
|
|
281
|
-
});
|
|
282
|
-
|
|
283
275
|
const playEntry = (entry) => navigator.replaceTop(entry, { mode });
|
|
284
276
|
|
|
285
277
|
const onPlayNextPerformNextVideoPlay = React.useCallback(() => {
|
|
@@ -346,12 +338,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
346
338
|
playerContainerLogger.error(errorObj);
|
|
347
339
|
|
|
348
340
|
setState({ error: errorObj });
|
|
349
|
-
|
|
350
|
-
if (!isTvOS) {
|
|
351
|
-
setTimeout(() => {
|
|
352
|
-
close();
|
|
353
|
-
}, 800);
|
|
354
|
-
}
|
|
355
341
|
},
|
|
356
342
|
[close]
|
|
357
343
|
);
|
|
@@ -669,7 +655,7 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
669
655
|
<PlayerFocusableWrapperView
|
|
670
656
|
nextFocusDown={context.bottomFocusableId}
|
|
671
657
|
>
|
|
672
|
-
{!Player
|
|
658
|
+
{!Player ? null : (
|
|
673
659
|
<Player
|
|
674
660
|
source={{
|
|
675
661
|
uri,
|
|
@@ -702,8 +688,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
702
688
|
</Player>
|
|
703
689
|
)}
|
|
704
690
|
</PlayerFocusableWrapperView>
|
|
705
|
-
|
|
706
|
-
{state.error ? <ErrorDisplay error={state.error} /> : null}
|
|
707
691
|
</View>
|
|
708
692
|
{/* Components container */}
|
|
709
693
|
{isInlineTV && context.showComponentsContainer ? (
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text } from "react-native";
|
|
3
|
+
import { render } from "@testing-library/react-native";
|
|
4
|
+
import { PreloaderWrapper } from "..";
|
|
5
|
+
|
|
6
|
+
describe("PreloaderWrapper", () => {
|
|
7
|
+
it("renders children when preloader is hidden", () => {
|
|
8
|
+
const { getByText } = render(
|
|
9
|
+
<PreloaderWrapper showPreloader={false}>
|
|
10
|
+
<Text>content</Text>
|
|
11
|
+
</PreloaderWrapper>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
expect(getByText("content")).toBeDefined();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("renders null when preloader is shown", () => {
|
|
18
|
+
const { queryByText } = render(
|
|
19
|
+
<PreloaderWrapper showPreloader>
|
|
20
|
+
<Text>content</Text>
|
|
21
|
+
</PreloaderWrapper>
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
expect(queryByText("content")).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
type PreloaderWrapperProps = {
|
|
4
|
+
showPreloader?: boolean;
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const PreloaderWrapper: React.FC<PreloaderWrapperProps> = ({
|
|
9
|
+
showPreloader = false,
|
|
10
|
+
children,
|
|
11
|
+
}) => {
|
|
12
|
+
return !showPreloader ? children : null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default PreloaderWrapper;
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
StyleSheet,
|
|
6
6
|
} from "react-native";
|
|
7
7
|
import * as R from "ramda";
|
|
8
|
+
import { path } from "@applicaster/zapp-react-native-utils/utils";
|
|
9
|
+
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
8
10
|
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
9
11
|
import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/localizationUtils";
|
|
10
12
|
import { useAnalytics } from "@applicaster/zapp-react-native-utils/analyticsUtils";
|
|
@@ -62,9 +64,13 @@ export const usePullToRefresh = (
|
|
|
62
64
|
|
|
63
65
|
const [refreshing, setRefreshing] = React.useState(false);
|
|
64
66
|
|
|
65
|
-
const feeds: string[] =
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
const feeds: string[] = React.useMemo(
|
|
68
|
+
() =>
|
|
69
|
+
(riverComponents || [])
|
|
70
|
+
.map((riverComponent) => path(["data", "source"], riverComponent))
|
|
71
|
+
.filter((feed) => !isNilOrEmpty(feed)),
|
|
72
|
+
[riverComponents]
|
|
73
|
+
);
|
|
68
74
|
|
|
69
75
|
const feedsLength = feeds.length;
|
|
70
76
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
import { PreloaderWrapper } from "../PreloaderWrapper";
|
|
3
|
+
import { useScreenContextV2 } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
4
|
+
import { useFeedLoader } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
5
|
+
|
|
6
|
+
import { componentsLogger } from "../../Helpers/logger";
|
|
7
|
+
|
|
8
|
+
const logger = componentsLogger.addSubsystem("ScreenFeedLoader");
|
|
9
|
+
|
|
10
|
+
/** Loads and provides `feedData` and store to */
|
|
11
|
+
export const ScreenFeedLoader: React.FC<
|
|
12
|
+
React.PropsWithChildren<{ id: string; feedData: any }>
|
|
13
|
+
> = ({ id, feedData, children }) => {
|
|
14
|
+
const { source: feedUrl, mapping } = feedData;
|
|
15
|
+
|
|
16
|
+
const { data, loading, error } = useFeedLoader({
|
|
17
|
+
feedUrl,
|
|
18
|
+
mapping,
|
|
19
|
+
pipesOptions: {},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const feedStore = useScreenContextV2()._feedStore;
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (data && !loading) {
|
|
26
|
+
feedStore.setState({ screenFeed: data, screenFeedError: null });
|
|
27
|
+
|
|
28
|
+
logger.log("screenFeed set for active screen", { data, screenId: id });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (error && !loading) {
|
|
32
|
+
feedStore.setState({ screenFeed: data, screenFeedError: error });
|
|
33
|
+
|
|
34
|
+
logger.warning("Feed data error:", {
|
|
35
|
+
data,
|
|
36
|
+
loading,
|
|
37
|
+
error,
|
|
38
|
+
screenId: id,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}, [data, loading, error, feedStore, id]);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<PreloaderWrapper showPreloader={loading}>{children}</PreloaderWrapper>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text } from "react-native";
|
|
3
|
+
import { render, waitFor } from "@testing-library/react-native";
|
|
4
|
+
import { ScreenFeedLoader } from "../ScreenFeedLoader";
|
|
5
|
+
|
|
6
|
+
const mockUseFeedLoader = jest.fn();
|
|
7
|
+
const mockUseScreenContextV2 = jest.fn();
|
|
8
|
+
const mockSetState = jest.fn();
|
|
9
|
+
|
|
10
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactHooks", () => ({
|
|
11
|
+
useFeedLoader: (...args) => mockUseFeedLoader(...args),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock(
|
|
15
|
+
"@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext",
|
|
16
|
+
() => ({
|
|
17
|
+
useScreenContextV2: (...args) => mockUseScreenContextV2(...args),
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
describe("ScreenFeedLoader", () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
|
|
25
|
+
mockUseScreenContextV2.mockReturnValue({
|
|
26
|
+
_feedStore: {
|
|
27
|
+
setState: mockSetState,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("hides children while loading", () => {
|
|
33
|
+
mockUseFeedLoader.mockReturnValue({
|
|
34
|
+
data: null,
|
|
35
|
+
loading: true,
|
|
36
|
+
error: null,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const { queryByText } = render(
|
|
40
|
+
<ScreenFeedLoader id="test" feedData={{ source: "url", mapping: {} }}>
|
|
41
|
+
<Text>child</Text>
|
|
42
|
+
</ScreenFeedLoader>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(queryByText("child")).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("writes loaded feed data to _feedStore", async () => {
|
|
49
|
+
const data = { entry: { id: "1" } };
|
|
50
|
+
|
|
51
|
+
mockUseFeedLoader.mockReturnValue({
|
|
52
|
+
data,
|
|
53
|
+
loading: false,
|
|
54
|
+
error: null,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
render(
|
|
58
|
+
<ScreenFeedLoader id="test" feedData={{ source: "url", mapping: {} }}>
|
|
59
|
+
<Text>child</Text>
|
|
60
|
+
</ScreenFeedLoader>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
await waitFor(() => {
|
|
64
|
+
expect(mockSetState).toHaveBeenCalledWith({
|
|
65
|
+
screenFeed: data,
|
|
66
|
+
screenFeedError: null,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("writes feed error to _feedStore", async () => {
|
|
72
|
+
const error = new Error("feed failed");
|
|
73
|
+
const data = { fallback: true };
|
|
74
|
+
|
|
75
|
+
mockUseFeedLoader.mockReturnValue({
|
|
76
|
+
data,
|
|
77
|
+
loading: false,
|
|
78
|
+
error,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
render(
|
|
82
|
+
<ScreenFeedLoader id="test" feedData={{ source: "url", mapping: {} }}>
|
|
83
|
+
<Text>child</Text>
|
|
84
|
+
</ScreenFeedLoader>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
await waitFor(() => {
|
|
88
|
+
expect(mockSetState).toHaveBeenCalledWith({
|
|
89
|
+
screenFeed: data,
|
|
90
|
+
screenFeedError: error,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ScreenFeedLoader } from "./ScreenFeedLoader";
|
|
@@ -53,6 +53,9 @@ const mockComponents = { ScreenType1, ScreenType2, PlayerController };
|
|
|
53
53
|
|
|
54
54
|
const mockState = {
|
|
55
55
|
components: mockComponents,
|
|
56
|
+
remoteConfigurations: {
|
|
57
|
+
assets: {},
|
|
58
|
+
},
|
|
56
59
|
plugins: [
|
|
57
60
|
mockScreenType3,
|
|
58
61
|
mockScreenType4,
|
|
@@ -127,6 +130,21 @@ const getWrapper = (screenId, screenType, screenData) => {
|
|
|
127
130
|
);
|
|
128
131
|
};
|
|
129
132
|
|
|
133
|
+
const getWrappedWrapper = (screenId, screenType, screenData) => {
|
|
134
|
+
const ScreenResolver = require("../").ScreenResolver;
|
|
135
|
+
|
|
136
|
+
return renderWithProviders(
|
|
137
|
+
<ScreenResolver
|
|
138
|
+
{...{
|
|
139
|
+
screenId,
|
|
140
|
+
screenType,
|
|
141
|
+
screenData,
|
|
142
|
+
}}
|
|
143
|
+
/>,
|
|
144
|
+
mockState
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
130
148
|
describe("<ScreenResolver />", () => {
|
|
131
149
|
it("renders correctly", () => {
|
|
132
150
|
const wrapper = getWrapper("1234", "screen_type_1", {});
|
|
@@ -134,6 +152,12 @@ describe("<ScreenResolver />", () => {
|
|
|
134
152
|
expect(wrapper.getByTestId("screen_type_1")).toBeDefined();
|
|
135
153
|
});
|
|
136
154
|
|
|
155
|
+
it("renders correctly when wrapped with default screen context", () => {
|
|
156
|
+
const wrapper = getWrappedWrapper("1234", "screen_type_1", {});
|
|
157
|
+
|
|
158
|
+
expect(wrapper.getByTestId("screen_type_1")).toBeDefined();
|
|
159
|
+
});
|
|
160
|
+
|
|
137
161
|
it("picks screen from plugins if it exists", () => {
|
|
138
162
|
const wrapper = getWrapper("A1234", "screen_type_3", {});
|
|
139
163
|
|