@applicaster/quick-brick-core 16.0.0-rc.99 → 17.0.0-alpha.5804334713
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/App/ActionSetters/index.ts +2 -2
- package/App/ActionsProvider/ActionsProvider.tsx +1 -1
- package/App/ActionsProvider/LegacyActionsRegistryAdapter.tsx +17 -1
- package/App/ActionsProvider/__tests__/LegacyActionsRegistryAdapter.test.tsx +64 -0
- package/App/AssetCache/logger.ts +1 -1
- package/App/DeepLinking/URLSchemeHandler/SchemeHandlerHooks/usePresentSchemeHandler.ts +3 -0
- package/App/DeepLinking/URLSchemeListener/URLSchemeContextProvider.tsx +4 -1
- package/App/DeepLinking/logger.ts +1 -1
- package/App/ErrorBoundary/index.tsx +5 -2
- package/App/NavigationProvider/NavigationProvider.tsx +17 -11
- package/App/NavigationProvider/__tests__/navToolbox.test.ts +227 -0
- package/App/NavigationProvider/__tests__/navigationProvider.test.tsx +56 -0
- package/App/NavigationProvider/navToolbox.ts +224 -0
- package/App/NavigationProvider/utils.ts +23 -2
- package/App/NetworkStatusProvider/NetworkStatusProvider.tsx +40 -4
- package/App/NetworkStatusProvider/ResumeVerificationController.ts +282 -0
- package/App/NetworkStatusProvider/__tests__/NetworkStatusProvider.test.tsx +167 -1
- package/App/NetworkStatusProvider/__tests__/ResumeVerificationController.test.ts +486 -0
- package/App/NetworkStatusProvider/__tests__/resumeVerification.test.ts +126 -0
- package/App/NetworkStatusProvider/__tests__/useResumeVerifiedNetInfo.test.ts +204 -0
- package/App/NetworkStatusProvider/resumeVerification.ts +54 -0
- package/App/NetworkStatusProvider/useResumeVerifiedNetInfo.ts +33 -0
- package/App/ThemeManager/index.tsx +6 -1
- package/App/appLifeCycleManager/index.tsx +2 -2
- package/App/appRemoteDataLoader/index.tsx +2 -2
- package/App/appRemoteDataLoader/remoteDataLoader.ts +2 -2
- package/App/index.tsx +8 -2
- package/App/layoutPreview/LayoutPreviewReminder.tsx +136 -0
- package/App/layoutPreview/__tests__/persistence.test.ts +63 -0
- package/App/layoutPreview/persistence.ts +228 -0
- package/App/logger.js +2 -2
- package/App/remoteContextReloader/__tests__/getNativeRemoteContextData.test.ts +148 -0
- package/App/remoteContextReloader/__tests__/getPromiseForType.test.js +31 -0
- package/App/remoteContextReloader/getRemoteContextData/getNativeRemoteContextData.ts +107 -29
- package/App/remoteContextReloader/helpers.ts +2 -1
- package/App/uiActions/index.ts +13 -0
- package/App/uiActions/muteUnmute/__tests__/assetResolver.test.ts +226 -0
- package/App/uiActions/muteUnmute/__tests__/binding.test.ts +51 -0
- package/App/uiActions/muteUnmute/__tests__/defaultAssets.test.ts +47 -0
- package/App/uiActions/muteUnmute/__tests__/entryState.test.ts +57 -0
- package/App/uiActions/muteUnmute/__tests__/labelResolver.test.ts +35 -0
- package/App/uiActions/muteUnmute/assetResolver.ts +87 -0
- package/App/uiActions/muteUnmute/defaultAssets.ts +44 -0
- package/App/uiActions/muteUnmute/entryState.ts +28 -0
- package/App/uiActions/muteUnmute/index.ts +79 -0
- package/App/uiActions/muteUnmute/labelResolver.ts +15 -0
- package/App/uiActions/muteUnmute/types.ts +18 -0
- package/package.json +8 -8
|
@@ -4,8 +4,8 @@ import { allSettled } from "promise";
|
|
|
4
4
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
5
5
|
|
|
6
6
|
import * as React from "react";
|
|
7
|
-
import {
|
|
8
|
-
const logger =
|
|
7
|
+
import { subsystemLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
8
|
+
const logger = subsystemLogger("app/lifecycle", "AppLifeCycleManager");
|
|
9
9
|
|
|
10
10
|
const debugActions = (actions) =>
|
|
11
11
|
logger.debug({
|
|
@@ -8,7 +8,7 @@ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
|
8
8
|
import { LegacyActionsRegistryAdapter } from "./LegacyActionsRegistryAdapter";
|
|
9
9
|
|
|
10
10
|
const { log_debug } = createLogger({
|
|
11
|
-
subsystem: "
|
|
11
|
+
subsystem: "core/actions",
|
|
12
12
|
category: "ActionRegistration",
|
|
13
13
|
});
|
|
14
14
|
|
|
@@ -5,7 +5,7 @@ import { uiActionsRegistry } from "@applicaster/zapp-react-native-utils/uiAction
|
|
|
5
5
|
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
6
6
|
|
|
7
7
|
const { log_debug, log_warning } = createLogger({
|
|
8
|
-
subsystem: "
|
|
8
|
+
subsystem: "core/actions/legacy_registry",
|
|
9
9
|
category: "ActionRegistration",
|
|
10
10
|
});
|
|
11
11
|
|
|
@@ -66,6 +66,22 @@ function LegacyActionBridge({ plugin }: { plugin: LegacyActionPlugin }) {
|
|
|
66
66
|
};
|
|
67
67
|
}, [plugin.identifier]);
|
|
68
68
|
|
|
69
|
+
// Consumers resolve a snapshot of `value`; when the plugin publishes a new
|
|
70
|
+
// one (e.g. cast devices found), make them re-resolve so availability
|
|
71
|
+
// predicates are not read from a stale value. Registration already
|
|
72
|
+
// notifies, so the first render is skipped.
|
|
73
|
+
const isFirstValueRef = React.useRef(true);
|
|
74
|
+
|
|
75
|
+
React.useEffect(() => {
|
|
76
|
+
if (isFirstValueRef.current) {
|
|
77
|
+
isFirstValueRef.current = false;
|
|
78
|
+
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
uiActionsRegistry.notifyProviderChanged(plugin.identifier);
|
|
83
|
+
}, [value, plugin.identifier]);
|
|
84
|
+
|
|
69
85
|
return null;
|
|
70
86
|
}
|
|
71
87
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@testing-library/react-native";
|
|
3
|
+
import { uiActionsRegistry } from "@applicaster/zapp-react-native-utils/uiActionsRegistrator";
|
|
4
|
+
import { LegacyActionsRegistryAdapter } from "../LegacyActionsRegistryAdapter";
|
|
5
|
+
|
|
6
|
+
const CastContext = React.createContext<any>(null);
|
|
7
|
+
|
|
8
|
+
const plugin = {
|
|
9
|
+
identifier: "cast-action-test",
|
|
10
|
+
module: { context: CastContext },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const renderWithValue = (value) => (
|
|
14
|
+
<CastContext.Provider value={value}>
|
|
15
|
+
<LegacyActionsRegistryAdapter actionPlugins={{ cast: plugin } as any} />
|
|
16
|
+
</CastContext.Provider>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
describe("LegacyActionsRegistryAdapter", () => {
|
|
20
|
+
it("makes consumers re-resolve when the plugin publishes a new value", () => {
|
|
21
|
+
const noDevices = { isActionAvailable: () => false };
|
|
22
|
+
const devicesFound = { isActionAvailable: () => true };
|
|
23
|
+
|
|
24
|
+
const { rerender, unmount } = render(renderWithValue(noDevices));
|
|
25
|
+
|
|
26
|
+
// what a consumer resolved at first paint
|
|
27
|
+
expect(
|
|
28
|
+
uiActionsRegistry
|
|
29
|
+
.getAction("cast-action-test")
|
|
30
|
+
?.isActionAvailable?.({} as any)
|
|
31
|
+
).toBe(false);
|
|
32
|
+
|
|
33
|
+
const listener = jest.fn();
|
|
34
|
+
const unsubscribe = uiActionsRegistry.subscribe(listener);
|
|
35
|
+
|
|
36
|
+
rerender(renderWithValue(devicesFound));
|
|
37
|
+
|
|
38
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
39
|
+
|
|
40
|
+
expect(
|
|
41
|
+
uiActionsRegistry
|
|
42
|
+
.getAction("cast-action-test")
|
|
43
|
+
?.isActionAvailable?.({} as any)
|
|
44
|
+
).toBe(true);
|
|
45
|
+
|
|
46
|
+
unsubscribe();
|
|
47
|
+
unmount();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("does not notify again when the value is unchanged", () => {
|
|
51
|
+
const stable = { isActionAvailable: () => true };
|
|
52
|
+
const { rerender, unmount } = render(renderWithValue(stable));
|
|
53
|
+
|
|
54
|
+
const listener = jest.fn();
|
|
55
|
+
const unsubscribe = uiActionsRegistry.subscribe(listener);
|
|
56
|
+
|
|
57
|
+
rerender(renderWithValue(stable));
|
|
58
|
+
|
|
59
|
+
expect(listener).not.toHaveBeenCalled();
|
|
60
|
+
|
|
61
|
+
unsubscribe();
|
|
62
|
+
unmount();
|
|
63
|
+
});
|
|
64
|
+
});
|
package/App/AssetCache/logger.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
|
|
29
29
|
import { log_error, log_info } from "../../logger";
|
|
30
30
|
import { useRivers } from "@applicaster/zapp-react-native-utils/reactHooks/state";
|
|
31
|
+
import { rememberLayoutPreview } from "../../../layoutPreview/persistence";
|
|
31
32
|
|
|
32
33
|
const getFeed = (query) => ({
|
|
33
34
|
content: { src: query.data_source },
|
|
@@ -155,6 +156,8 @@ export function usePresentSchemeHandler({
|
|
|
155
156
|
rivers_configuration_id
|
|
156
157
|
);
|
|
157
158
|
|
|
159
|
+
await rememberLayoutPreview(rivers_configuration_id);
|
|
160
|
+
|
|
158
161
|
onFinish((done) => {
|
|
159
162
|
actions?.setAppReady();
|
|
160
163
|
navigator.replace(homeRiver);
|
|
@@ -3,6 +3,7 @@ import React, {
|
|
|
3
3
|
createContext,
|
|
4
4
|
useEffect,
|
|
5
5
|
useCallback,
|
|
6
|
+
useMemo,
|
|
6
7
|
ComponentType,
|
|
7
8
|
ReactChild,
|
|
8
9
|
} from "react";
|
|
@@ -196,9 +197,11 @@ function URLSchemeContextProvider(props: Props) {
|
|
|
196
197
|
};
|
|
197
198
|
}, []);
|
|
198
199
|
|
|
200
|
+
const contextValue = useMemo(() => ({ url: state, done }), [state, done]);
|
|
201
|
+
|
|
199
202
|
return (
|
|
200
203
|
<View style={fillStyles.container}>
|
|
201
|
-
<URLSchemeContext.Provider value={
|
|
204
|
+
<URLSchemeContext.Provider value={contextValue}>
|
|
202
205
|
{props.children}
|
|
203
206
|
</URLSchemeContext.Provider>
|
|
204
207
|
</View>
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import pick from "lodash/pick";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { subsystemLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
5
5
|
import { ErrorScreen } from "@applicaster/zapp-react-native-ui-components/Components/ErrorScreen";
|
|
6
6
|
|
|
7
7
|
import { useErrorStore } from "./store";
|
|
8
8
|
import * as QuickBrickManager from "@applicaster/zapp-react-native-bridge/QuickBrick";
|
|
9
9
|
|
|
10
|
-
const errorBoundaryLogger =
|
|
10
|
+
const errorBoundaryLogger = subsystemLogger(
|
|
11
|
+
"crash/error_boundary",
|
|
12
|
+
"ErrorBoundary"
|
|
13
|
+
);
|
|
11
14
|
|
|
12
15
|
type Props = {
|
|
13
16
|
children: React.ReactNode;
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
useGetScreenOrientation,
|
|
14
14
|
} from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
|
|
15
15
|
import { focusManager } from "@applicaster/zapp-react-native-utils/focusManager/FocusManager";
|
|
16
|
+
import { setXrayNavigator } from "@applicaster/zapp-react-native-utils/navigationUtils/xrayNavigator";
|
|
17
|
+
import { registerNavToolbox } from "./navToolbox";
|
|
16
18
|
|
|
17
19
|
import reducer, {
|
|
18
20
|
ACTIONS,
|
|
@@ -43,7 +45,6 @@ import {
|
|
|
43
45
|
startUpHookPluginIdentifiersSelector,
|
|
44
46
|
} from "./navigator/selectors";
|
|
45
47
|
|
|
46
|
-
import { coreAppLogger } from "../logger";
|
|
47
48
|
import {
|
|
48
49
|
getPlatform,
|
|
49
50
|
isTV,
|
|
@@ -87,8 +88,9 @@ import {
|
|
|
87
88
|
useLayoutVersion,
|
|
88
89
|
useRivers,
|
|
89
90
|
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
91
|
+
import { subsystemLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
90
92
|
|
|
91
|
-
const logger =
|
|
93
|
+
const logger = subsystemLogger("ui/navigation", "Navigator");
|
|
92
94
|
|
|
93
95
|
type Props = {
|
|
94
96
|
children?: React.ReactChild;
|
|
@@ -353,10 +355,7 @@ export function NavigationProvider({ children }: Props) {
|
|
|
353
355
|
state: firstRoute.state,
|
|
354
356
|
};
|
|
355
357
|
|
|
356
|
-
if (
|
|
357
|
-
stateForBack?.state?.entry &&
|
|
358
|
-
"home" in stateForBack?.state?.entry
|
|
359
|
-
) {
|
|
358
|
+
if (stateForBack?.state?.entry?.home) {
|
|
360
359
|
goHome();
|
|
361
360
|
} else {
|
|
362
361
|
dispatch(back(stateForBack));
|
|
@@ -763,11 +762,11 @@ export function NavigationProvider({ children }: Props) {
|
|
|
763
762
|
const getStackForPathname = (
|
|
764
763
|
pathname: string
|
|
765
764
|
): NavigationScreenState | undefined => {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
765
|
+
// Topmost match: the active screen if a route is on the stack twice.
|
|
766
|
+
return R.findLast(
|
|
767
|
+
({ route }) => route === pathname,
|
|
768
|
+
state?.stack?.mainStack ?? []
|
|
769
|
+
);
|
|
771
770
|
};
|
|
772
771
|
|
|
773
772
|
const navigator: QuickBrickAppNavigator = React.useMemo(
|
|
@@ -832,6 +831,13 @@ export function NavigationProvider({ children }: Props) {
|
|
|
832
831
|
navigator.state = state;
|
|
833
832
|
}
|
|
834
833
|
|
|
834
|
+
React.useLayoutEffect(() => {
|
|
835
|
+
setXrayNavigator(navigator);
|
|
836
|
+
registerNavToolbox();
|
|
837
|
+
|
|
838
|
+
return () => setXrayNavigator(undefined);
|
|
839
|
+
}, [navigator]);
|
|
840
|
+
|
|
835
841
|
React.useLayoutEffect(() => {
|
|
836
842
|
if (pathname === "/" && appLaunched) {
|
|
837
843
|
goHome(true);
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
const mockRegister = jest.fn().mockResolvedValue(undefined);
|
|
2
|
+
|
|
3
|
+
// The X-Ray mock (__mocks__/@applicaster/quick-brick-xray) has no toolbox API: give it one.
|
|
4
|
+
require("@applicaster/quick-brick-xray").registerReactToolbox = mockRegister;
|
|
5
|
+
|
|
6
|
+
// Required after the toolbox API is in place: loading them can register the player toolbox.
|
|
7
|
+
const {
|
|
8
|
+
modalStore,
|
|
9
|
+
} = require("@applicaster/zapp-react-native-utils/modalState");
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
setXrayNavigator,
|
|
13
|
+
} = require("@applicaster/zapp-react-native-utils/navigationUtils/xrayNavigator");
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
playerManager,
|
|
17
|
+
} = require("@applicaster/zapp-react-native-utils/appUtils/playerManager");
|
|
18
|
+
|
|
19
|
+
const { registerNavToolbox } = require("../navToolbox");
|
|
20
|
+
|
|
21
|
+
const navigator = (overrides = {}) => {
|
|
22
|
+
const nav = {
|
|
23
|
+
getPathname: () => "/river/a/river/b",
|
|
24
|
+
getStack: () => [{ route: "/river/a" }, { route: "/river/a/river/b" }],
|
|
25
|
+
canGoBack: () => true,
|
|
26
|
+
goBack: jest.fn(),
|
|
27
|
+
goHome: jest.fn(),
|
|
28
|
+
getPlayNextOverlay: () => null,
|
|
29
|
+
getModalState: () => modalStore.getState().modalState,
|
|
30
|
+
dismissModal: jest.fn(),
|
|
31
|
+
videoModalState: { visible: false },
|
|
32
|
+
minimiseVideoModal: jest.fn(),
|
|
33
|
+
maximiseVideoModal: jest.fn(),
|
|
34
|
+
closeVideoModal: jest.fn(),
|
|
35
|
+
...overrides,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
setXrayNavigator(nav as unknown as QuickBrickAppNavigator);
|
|
39
|
+
|
|
40
|
+
return nav;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const videoModal = (mode: string) => ({
|
|
44
|
+
videoModalState: { visible: true, mode, item: { id: "v1", title: "V" } },
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const call = (name: string) => {
|
|
48
|
+
// Other toolboxes (player) may register first.
|
|
49
|
+
const { tools } = mockRegister.mock.calls.find(
|
|
50
|
+
([toolbox]) => toolbox.name === "nav"
|
|
51
|
+
)[0];
|
|
52
|
+
|
|
53
|
+
return tools.find((tool) => tool.name === name).handler({});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
describe("navToolbox", () => {
|
|
57
|
+
beforeAll(() => registerNavToolbox());
|
|
58
|
+
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
modalStore.getState().setModalState({ visible: false, screen: null });
|
|
61
|
+
jest.restoreAllMocks();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("throws until the NavigationProvider sets the navigator", () => {
|
|
65
|
+
setXrayNavigator(undefined);
|
|
66
|
+
|
|
67
|
+
expect(() => call("stack")).toThrow("Navigation is not available yet");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("goes back one screen", () => {
|
|
71
|
+
const nav = navigator();
|
|
72
|
+
|
|
73
|
+
expect(call("back")).toEqual({ from: "/river/a/river/b", wentBack: true });
|
|
74
|
+
expect(nav.goBack).toHaveBeenCalledWith(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("does not go back from the root", () => {
|
|
78
|
+
const nav = navigator({ canGoBack: () => false });
|
|
79
|
+
|
|
80
|
+
expect(call("back")).toMatchObject({ wentBack: false });
|
|
81
|
+
expect(nav.goBack).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("does not pop the screen under a modal or the video modal", () => {
|
|
85
|
+
const nav = navigator(videoModal("MAXIMIZED"));
|
|
86
|
+
|
|
87
|
+
expect(call("back")).toEqual({
|
|
88
|
+
from: "video-modal/v1",
|
|
89
|
+
wentBack: false,
|
|
90
|
+
blockedBy: "video-modal",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
modalStore.getState().openModal({ item: { id: "m" } });
|
|
94
|
+
|
|
95
|
+
expect(call("back")).toMatchObject({ blockedBy: "modal" });
|
|
96
|
+
expect(nav.goBack).not.toHaveBeenCalled();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("only dismisses Play Next when it shows", () => {
|
|
100
|
+
const nav = navigator({ getPlayNextOverlay: () => ({}) });
|
|
101
|
+
|
|
102
|
+
expect(call("back")).toMatchObject({
|
|
103
|
+
wentBack: false,
|
|
104
|
+
dismissed: "playNext",
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(nav.goBack).toHaveBeenCalledWith(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("leaves a hook screen to cancel itself", () => {
|
|
111
|
+
const nav = navigator({ getPathname: () => "/river/a/hooks/login" });
|
|
112
|
+
|
|
113
|
+
expect(call("back")).toMatchObject({ wentBack: false, blockedBy: "hook" });
|
|
114
|
+
expect(nav.goBack).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("goes home", () => {
|
|
118
|
+
const nav = navigator();
|
|
119
|
+
|
|
120
|
+
expect(call("home")).toEqual({ from: "/river/a/river/b", wentHome: true });
|
|
121
|
+
expect(nav.goHome).toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("does not go home under a modal or the video modal", () => {
|
|
125
|
+
const nav = navigator(videoModal("MAXIMIZED"));
|
|
126
|
+
|
|
127
|
+
expect(call("home")).toEqual({
|
|
128
|
+
from: "video-modal/v1",
|
|
129
|
+
wentHome: false,
|
|
130
|
+
blockedBy: "video-modal",
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
modalStore.getState().openModal({ item: { id: "m" } });
|
|
134
|
+
|
|
135
|
+
expect(call("home")).toMatchObject({ blockedBy: "modal" });
|
|
136
|
+
expect(nav.goHome).not.toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("goes home with the mini player docked", () => {
|
|
140
|
+
const nav = navigator(videoModal("MINIMIZED"));
|
|
141
|
+
|
|
142
|
+
expect(call("home")).toMatchObject({ wentHome: true });
|
|
143
|
+
expect(nav.goHome).toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("dismisses the modal store's modal, not the video modal", () => {
|
|
147
|
+
const nav = navigator(videoModal("MAXIMIZED"));
|
|
148
|
+
|
|
149
|
+
expect(() => call("dismissModal")).toThrow("No modal is open");
|
|
150
|
+
|
|
151
|
+
modalStore.getState().openModal({ item: { id: "m" } });
|
|
152
|
+
|
|
153
|
+
expect(call("dismissModal")).toBe("OK");
|
|
154
|
+
expect(nav.dismissModal).toHaveBeenCalled();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("minimises only a maximised video modal", () => {
|
|
158
|
+
const fullscreen = navigator(videoModal("FULLSCREEN"));
|
|
159
|
+
|
|
160
|
+
expect(() => call("minimizeVideoModal")).toThrow("mode is FULLSCREEN");
|
|
161
|
+
expect(fullscreen.minimiseVideoModal).not.toHaveBeenCalled();
|
|
162
|
+
|
|
163
|
+
const maximized = navigator(videoModal("MAXIMIZED"));
|
|
164
|
+
|
|
165
|
+
call("minimizeVideoModal");
|
|
166
|
+
|
|
167
|
+
expect(maximized.minimiseVideoModal).toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("enters and leaves fullscreen through the player, which also rotates the phone", () => {
|
|
171
|
+
const invoke = jest.spyOn(playerManager, "invokeHandler");
|
|
172
|
+
const playerContainer = jest.fn();
|
|
173
|
+
|
|
174
|
+
playerManager.on("toggleFullscreen", playerContainer);
|
|
175
|
+
|
|
176
|
+
navigator(videoModal("MAXIMIZED"));
|
|
177
|
+
call("fullscreenVideoModal");
|
|
178
|
+
|
|
179
|
+
expect(invoke).toHaveBeenCalledWith("toggleFullscreen", {
|
|
180
|
+
fullscreen: true,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const fullscreen = navigator(videoModal("FULLSCREEN"));
|
|
184
|
+
|
|
185
|
+
call("maximizeVideoModal");
|
|
186
|
+
|
|
187
|
+
expect(invoke).toHaveBeenCalledWith("toggleFullscreen", {
|
|
188
|
+
fullscreen: false,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(fullscreen.maximiseVideoModal).not.toHaveBeenCalled();
|
|
192
|
+
|
|
193
|
+
playerManager.removeHandler("toggleFullscreen", playerContainer);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("does not claim fullscreen without a player, or from the mini player", () => {
|
|
197
|
+
navigator(videoModal("MAXIMIZED"));
|
|
198
|
+
|
|
199
|
+
expect(() => call("fullscreenVideoModal")).toThrow("shows no player");
|
|
200
|
+
|
|
201
|
+
navigator(videoModal("MINIMIZED"));
|
|
202
|
+
|
|
203
|
+
expect(() => call("fullscreenVideoModal")).toThrow("mode is MINIMIZED");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("expands the mini player", () => {
|
|
207
|
+
const nav = navigator(videoModal("MINIMIZED"));
|
|
208
|
+
|
|
209
|
+
call("maximizeVideoModal");
|
|
210
|
+
|
|
211
|
+
expect(nav.maximiseVideoModal).toHaveBeenCalled();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("reports the video modal", () => {
|
|
215
|
+
navigator(videoModal("MINIMIZED"));
|
|
216
|
+
|
|
217
|
+
expect(call("videoModal")).toMatchObject({
|
|
218
|
+
visible: true,
|
|
219
|
+
mode: "MINIMIZED",
|
|
220
|
+
entry: { id: "v1", title: "V" },
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
navigator();
|
|
224
|
+
|
|
225
|
+
expect(call("videoModal")).toMatchObject({ visible: false });
|
|
226
|
+
});
|
|
227
|
+
});
|
|
@@ -410,6 +410,62 @@ describe("<NavigationProvider />", () => {
|
|
|
410
410
|
});
|
|
411
411
|
});
|
|
412
412
|
|
|
413
|
+
describe("when backToTop is requested from a non-home tab", () => {
|
|
414
|
+
const listenRiver = {
|
|
415
|
+
id: "LISTEN",
|
|
416
|
+
home: false,
|
|
417
|
+
navigations: [],
|
|
418
|
+
ui_components: [],
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
beforeEach(() => {
|
|
422
|
+
wrapper = renderContextConsumer({
|
|
423
|
+
...store,
|
|
424
|
+
rivers: { ...rivers, LISTEN: listenRiver },
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
act(() => {
|
|
428
|
+
getNavigatorValues(wrapper).navigator.replace(listenRiver);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("returns to the tab root from the second level", () => {
|
|
433
|
+
act(() => {
|
|
434
|
+
getNavigatorValues(wrapper).navigator.push(rivers.A1234);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
act(() => {
|
|
438
|
+
getNavigatorValues(wrapper).navigator.goBack(false, false, true);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const { navigator, state } = getNavigatorValues(wrapper);
|
|
442
|
+
|
|
443
|
+
expect(navigator.currentRoute).toEqual("/river/LISTEN");
|
|
444
|
+
expect(navigator.activeRiver).toEqual(listenRiver);
|
|
445
|
+
expect(state.stack.mainStack).toHaveLength(1);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("returns to the tab root from the third level", () => {
|
|
449
|
+
act(() => {
|
|
450
|
+
getNavigatorValues(wrapper).navigator.push(rivers.A1234);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
act(() => {
|
|
454
|
+
getNavigatorValues(wrapper).navigator.push(rivers.C0987);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
act(() => {
|
|
458
|
+
getNavigatorValues(wrapper).navigator.goBack(false, false, true);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const { navigator, state } = getNavigatorValues(wrapper);
|
|
462
|
+
|
|
463
|
+
expect(navigator.currentRoute).toEqual("/river/LISTEN");
|
|
464
|
+
expect(navigator.activeRiver).toEqual(listenRiver);
|
|
465
|
+
expect(state.stack.mainStack).toHaveLength(1);
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
413
469
|
describe("when there is a previous route and there are hooks", () => {
|
|
414
470
|
beforeEach(() => {
|
|
415
471
|
wrapper = renderContextConsumer();
|