@applicaster/quick-brick-core 13.0.0-alpha.9946926334 → 13.0.0-rc.100
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/DeepLinking/URLSchemeListener/URLSchemeContextProvider.tsx +14 -7
- package/App/DeepLinking/URLSchemeListener/__tests__/URLSchemeListener.test.js +40 -0
- package/App/DeepLinking/URLSchemeListener/index.tsx +1 -1
- package/App/ErrorBoundary/__tests__/errorBoundary.test.jsx +232 -0
- package/App/ErrorBoundary/__tests__/store.test.js +143 -0
- package/App/ErrorBoundary/index.tsx +45 -21
- package/App/ErrorBoundary/store.ts +26 -0
- package/App/LoadingOverlay/LoadingOverlayManager.tsx +27 -0
- package/App/LoadingOverlay/index.tsx +49 -0
- package/App/LoadingOverlay/useLoadingOverlayVisibility.tsx +18 -0
- package/App/ModalProvider/ModalBottomSheet/Overlay.tsx +5 -7
- package/App/ModalProvider/ModalBottomSheet/index.tsx +6 -10
- package/App/ModalProvider/index.tsx +5 -6
- package/App/NavigationProvider/NavigationProvider.tsx +56 -43
- package/App/NavigationProvider/__tests__/navigationProvider.test.tsx +0 -44
- package/App/NetworkStatusProvider/utils.ts +0 -1
- package/App/StoreConnector/__tests__/storeConnector.test.js +17 -11
- package/App/appLifeCycleManager/__tests__/appLifeCycleManager.test.tsx +146 -0
- package/App/appLifeCycleManager/index.tsx +6 -7
- package/App/index.tsx +6 -3
- package/App/remoteContextReloader/__tests__/remoteContextReloader.test.js +11 -21
- package/App/remoteContextReloader/getRemoteContextData/getNativeRemoteContextData.ts +14 -0
- package/createZappApp/__tests__/__snapshots__/createZappApp.test.js.snap +5 -128
- package/createZappApp/__tests__/createZappApp.test.js +7 -20
- package/defaults/__tests__/defaults.test.js +3 -4
- package/index.d.ts +1 -1
- package/package.json +10 -14
- package/App/DeepLinking/URLSchemeListener/__tests__/URLSchemeListenerTest.js +0 -177
- package/App/ErrorBoundary/__tests__/__snapshots__/errorBoundary.test.js.snap +0 -9
- package/App/ErrorBoundary/__tests__/errorBoundary.test.js +0 -72
- package/App/ModalProvider/ModalChildrenWrapper/index.web.ts +0 -1
- package/App/NavigationProvider/__tests__/__snapshots__/navigationProvider.test.tsx.snap +0 -68
- package/App/StoreConnector/__tests__/__snapshots__/storeConnector.test.js.snap +0 -35
- package/App/appLifeCycleManager/__tests__/__snapshots__/appLifeCycleManager.test.js.snap +0 -30
- package/App/appLifeCycleManager/__tests__/appLifeCycleManager.test.js +0 -134
- package/App/remoteContextReloader/__tests__/__snapshots__/remoteContextReloader.test.js.snap +0 -43
- /package/App/ModalProvider/ModalChildrenWrapper/{index.ios.ts → index.ts} +0 -0
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { StyleSheet, TouchableOpacity, Easing, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { AnimatedInOut } from "@applicaster/zapp-react-native-ui-components/Components/AnimatedInOut";
|
|
5
|
-
import {
|
|
5
|
+
import { dismissModal } from "@applicaster/zapp-react-native-utils/modalState";
|
|
6
6
|
|
|
7
7
|
type Props = {
|
|
8
8
|
overlayColor: string;
|
|
@@ -35,21 +35,19 @@ const fadeInAnimation = {
|
|
|
35
35
|
delay,
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
const animationConfig =
|
|
38
|
+
const animationConfig = {
|
|
39
39
|
in: fadeInAnimation,
|
|
40
40
|
out: {
|
|
41
41
|
...fadeInAnimation,
|
|
42
|
-
onAnimationEnd,
|
|
42
|
+
onAnimationEnd: dismissModal,
|
|
43
43
|
},
|
|
44
|
-
}
|
|
44
|
+
};
|
|
45
45
|
|
|
46
46
|
export function Overlay(props: Props) {
|
|
47
|
-
const navigator = useNavigation();
|
|
48
|
-
|
|
49
47
|
return (
|
|
50
48
|
<AnimatedInOut
|
|
51
49
|
visible={props.visible}
|
|
52
|
-
animationConfig={animationConfig
|
|
50
|
+
animationConfig={animationConfig}
|
|
53
51
|
staticStyles={styles.container}
|
|
54
52
|
>
|
|
55
53
|
<TouchableOpacity
|
|
@@ -3,8 +3,8 @@ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
|
3
3
|
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
4
4
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
5
5
|
|
|
6
|
-
import React, { useCallback, useEffect,
|
|
7
|
-
import {
|
|
6
|
+
import React, { useCallback, useEffect, useState } from "react";
|
|
7
|
+
import { Keyboard, StyleSheet, View } from "react-native";
|
|
8
8
|
|
|
9
9
|
import { Overlay } from "./Overlay";
|
|
10
10
|
import { ModalBottomSheetFrame } from "./ModalBottomSheetFrame";
|
|
@@ -75,13 +75,9 @@ export function ModalBottomSheet(props: Props) {
|
|
|
75
75
|
|
|
76
76
|
const isTablet = useIsTablet();
|
|
77
77
|
|
|
78
|
-
const maxWidth =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
? Math.max(sheetTabletMaxWidth, dimensions.width)
|
|
82
|
-
: dimensions.width,
|
|
83
|
-
[isTablet]
|
|
84
|
-
);
|
|
78
|
+
const maxWidth = isTablet ? sheetTabletMaxWidth : dimensions.width;
|
|
79
|
+
|
|
80
|
+
const modalVisible = visible && sheetHeight > 0;
|
|
85
81
|
|
|
86
82
|
return (
|
|
87
83
|
<View style={styles.container}>
|
|
@@ -98,7 +94,7 @@ export function ModalBottomSheet(props: Props) {
|
|
|
98
94
|
paddingBottom={bottomOffset}
|
|
99
95
|
maxWidth={maxWidth}
|
|
100
96
|
dimensions={dimensions}
|
|
101
|
-
visible={
|
|
97
|
+
visible={modalVisible}
|
|
102
98
|
closeToast={dismiss}
|
|
103
99
|
handleProps={{
|
|
104
100
|
enabled: sheetHandleEnabled,
|
|
@@ -6,10 +6,10 @@ import { ModalChildrenWrapper } from "./ModalChildrenWrapper";
|
|
|
6
6
|
import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
|
|
7
7
|
import { ScreenContextProvider } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenContext";
|
|
8
8
|
import { ROUTE_TYPES } from "@applicaster/zapp-react-native-utils/navigationUtils/routeTypes";
|
|
9
|
-
import {
|
|
9
|
+
import { useModalStoreState } from "@applicaster/zapp-react-native-utils/modalState";
|
|
10
10
|
|
|
11
11
|
export function ModalProvider() {
|
|
12
|
-
const
|
|
12
|
+
const modalState = useModalStoreState();
|
|
13
13
|
|
|
14
14
|
const [modalVisible, setModalVisible] = React.useState(false);
|
|
15
15
|
const [modalShown, setModalShown] = React.useState(false);
|
|
@@ -35,13 +35,12 @@ export function ModalProvider() {
|
|
|
35
35
|
|
|
36
36
|
const pathname = `${ROUTE_TYPES.MODAL}/${modalScreenProps?.screenId}`;
|
|
37
37
|
|
|
38
|
+
const shouldShowModal = modalVisible && modalState?.visible;
|
|
39
|
+
|
|
38
40
|
return (
|
|
39
41
|
<PathnameContext.Provider value={pathname}>
|
|
40
42
|
<ScreenContextProvider pathname={pathname}>
|
|
41
|
-
<ModalPresenter
|
|
42
|
-
visible={modalVisible && modalState?.visible}
|
|
43
|
-
{...modalState?.options}
|
|
44
|
-
>
|
|
43
|
+
<ModalPresenter visible={shouldShowModal} {...modalState?.options}>
|
|
45
44
|
<ModalChildrenWrapper>
|
|
46
45
|
<ModalContent modalScreenProps={modalScreenProps} />
|
|
47
46
|
</ModalChildrenWrapper>
|
|
@@ -49,6 +49,7 @@ import { coreAppLogger } from "../logger";
|
|
|
49
49
|
import {
|
|
50
50
|
getPlatform,
|
|
51
51
|
isTV,
|
|
52
|
+
isVizioPlatform,
|
|
52
53
|
} from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
53
54
|
import { useConnectionInfo } from "@applicaster/zapp-react-native-utils/reactHooks/connection";
|
|
54
55
|
import {
|
|
@@ -57,16 +58,26 @@ import {
|
|
|
57
58
|
legacyScreenData,
|
|
58
59
|
openExternalUrl,
|
|
59
60
|
} from "./utils";
|
|
60
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
debounce,
|
|
63
|
+
getJsonReplacer,
|
|
64
|
+
} from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
61
65
|
|
|
62
66
|
import { findParent } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappPipesContext/ZappPipesEntryContext";
|
|
63
67
|
import { ZappPipesEntryContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
|
|
64
68
|
import { PlayNextState } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/OverlayObserver/OverlaysObserver";
|
|
65
69
|
import { inflateUrl } from "@applicaster/zapp-react-native-ui-components/Components/LinkHandler/LinkHandler";
|
|
66
|
-
import {
|
|
70
|
+
import {
|
|
71
|
+
dismissModal,
|
|
72
|
+
modalStore,
|
|
73
|
+
openModal,
|
|
74
|
+
} from "@applicaster/zapp-react-native-utils/modalState";
|
|
67
75
|
import { useSubscriberFor } from "@applicaster/zapp-react-native-utils/reactHooks/useSubscriberFor";
|
|
68
76
|
import { QBUIComponentEvents } from "@applicaster/zapp-react-native-ui-components/events";
|
|
69
77
|
import { Hook } from "@applicaster/zapp-react-native-utils/appUtils/HooksManager/Hook";
|
|
78
|
+
import { URLSchemeContext } from "../DeepLinking/URLSchemeListener/URLSchemeContextProvider";
|
|
79
|
+
|
|
80
|
+
import { loadingOverlayManager } from "../LoadingOverlay";
|
|
70
81
|
|
|
71
82
|
const logger = coreAppLogger.addSubsystem("Navigator");
|
|
72
83
|
|
|
@@ -86,6 +97,8 @@ const navigationActions = {
|
|
|
86
97
|
const platform = getPlatform();
|
|
87
98
|
|
|
88
99
|
export function NavigationProvider({ children }: Props) {
|
|
100
|
+
const { url: deepLinkURL } = React.useContext(URLSchemeContext);
|
|
101
|
+
|
|
89
102
|
const {
|
|
90
103
|
appData: { layoutVersion },
|
|
91
104
|
rivers,
|
|
@@ -114,8 +127,6 @@ export function NavigationProvider({ children }: Props) {
|
|
|
114
127
|
|
|
115
128
|
const { context } = React.useContext(ZappPipesEntryContext.Context);
|
|
116
129
|
|
|
117
|
-
const { openModal, dismissModal } = useModalStore();
|
|
118
|
-
|
|
119
130
|
const playNextOverlayState = React.useRef();
|
|
120
131
|
|
|
121
132
|
const online = useConnectionInfo(true);
|
|
@@ -141,7 +152,7 @@ export function NavigationProvider({ children }: Props) {
|
|
|
141
152
|
);
|
|
142
153
|
|
|
143
154
|
const getModalState: () => ModalState = () => {
|
|
144
|
-
const { modalState } =
|
|
155
|
+
const { modalState } = modalStore.getState();
|
|
145
156
|
|
|
146
157
|
return modalState;
|
|
147
158
|
};
|
|
@@ -385,7 +396,7 @@ export function NavigationProvider({ children }: Props) {
|
|
|
385
396
|
}
|
|
386
397
|
|
|
387
398
|
if (hookPlugin.isModalHook()) {
|
|
388
|
-
|
|
399
|
+
openModal({
|
|
389
400
|
item: payload,
|
|
390
401
|
props: { screenType: "hooks" },
|
|
391
402
|
options: { ModalContainer: hookPlugin.getModalContainer() },
|
|
@@ -393,6 +404,8 @@ export function NavigationProvider({ children }: Props) {
|
|
|
393
404
|
} else {
|
|
394
405
|
dispatch(push(route, { entry: payload, screen: hookPlugin }));
|
|
395
406
|
}
|
|
407
|
+
|
|
408
|
+
closeLoadingOverlay();
|
|
396
409
|
};
|
|
397
410
|
|
|
398
411
|
const handleHookError = ({ error, hookPlugin, callback }, dispose) => {
|
|
@@ -403,7 +416,7 @@ export function NavigationProvider({ children }: Props) {
|
|
|
403
416
|
}
|
|
404
417
|
|
|
405
418
|
if (hookPlugin.isModalHook()) {
|
|
406
|
-
|
|
419
|
+
dismissModal();
|
|
407
420
|
} else {
|
|
408
421
|
goBack(true, true);
|
|
409
422
|
}
|
|
@@ -411,9 +424,11 @@ export function NavigationProvider({ children }: Props) {
|
|
|
411
424
|
if (callback && typeof callback === "function") {
|
|
412
425
|
callback();
|
|
413
426
|
}
|
|
427
|
+
|
|
428
|
+
closeLoadingOverlay();
|
|
414
429
|
};
|
|
415
430
|
|
|
416
|
-
const handleHookCancel = ({ hookPlugin, callback }, dispose) => {
|
|
431
|
+
const handleHookCancel = ({ hookPlugin, callback, abort }, dispose) => {
|
|
417
432
|
dispose();
|
|
418
433
|
setStartUpHooks(false);
|
|
419
434
|
|
|
@@ -421,21 +436,29 @@ export function NavigationProvider({ children }: Props) {
|
|
|
421
436
|
callback();
|
|
422
437
|
}
|
|
423
438
|
|
|
424
|
-
if (
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
439
|
+
if (!abort) {
|
|
440
|
+
if (hookPlugin.isModalHook()) {
|
|
441
|
+
dismissModal();
|
|
442
|
+
} else {
|
|
443
|
+
goBack(true, true);
|
|
444
|
+
}
|
|
428
445
|
}
|
|
446
|
+
|
|
447
|
+
closeLoadingOverlay();
|
|
429
448
|
};
|
|
430
449
|
|
|
431
450
|
const handleHookSuccess = ({ hookPlugin, callback }) => {
|
|
432
451
|
callback?.();
|
|
433
452
|
|
|
434
453
|
if (hookPlugin.isModalHook()) {
|
|
435
|
-
|
|
454
|
+
dismissModal();
|
|
436
455
|
}
|
|
437
456
|
};
|
|
438
457
|
|
|
458
|
+
const handleBackgroundHook = () => {
|
|
459
|
+
openLoadingOverlay();
|
|
460
|
+
};
|
|
461
|
+
|
|
439
462
|
const checkStartUpHooks = React.useMemo(() => {
|
|
440
463
|
if (!pluginConfigurations) return false;
|
|
441
464
|
|
|
@@ -505,6 +528,8 @@ export function NavigationProvider({ children }: Props) {
|
|
|
505
528
|
);
|
|
506
529
|
|
|
507
530
|
callback?.();
|
|
531
|
+
|
|
532
|
+
closeLoadingOverlay();
|
|
508
533
|
};
|
|
509
534
|
|
|
510
535
|
const offlinePlugin = React.useMemo(() => {
|
|
@@ -619,6 +644,7 @@ export function NavigationProvider({ children }: Props) {
|
|
|
619
644
|
.on(HOOKS_EVENTS.ERROR, handleHookError)
|
|
620
645
|
.on(HOOKS_EVENTS.CANCEL, handleHookCancel)
|
|
621
646
|
.on(HOOKS_EVENTS.SUCCESS, handleHookSuccess)
|
|
647
|
+
.on(HOOKS_EVENTS.START_BACKGROUND_HOOK, handleBackgroundHook)
|
|
622
648
|
.on(
|
|
623
649
|
HOOKS_EVENTS.COMPLETE,
|
|
624
650
|
handleHookComplete(navigationAction, targetRoute, {
|
|
@@ -634,6 +660,14 @@ export function NavigationProvider({ children }: Props) {
|
|
|
634
660
|
},
|
|
635
661
|
});
|
|
636
662
|
|
|
663
|
+
const openLoadingOverlay = React.useCallback(() => {
|
|
664
|
+
deepLinkURL && isVizioPlatform() && loadingOverlayManager.show();
|
|
665
|
+
}, [deepLinkURL]);
|
|
666
|
+
|
|
667
|
+
const closeLoadingOverlay = React.useCallback(() => {
|
|
668
|
+
loadingOverlayManager.hide();
|
|
669
|
+
}, []);
|
|
670
|
+
|
|
637
671
|
const openPiP = React.useCallback(() => {
|
|
638
672
|
dispatch(setVideoPiP());
|
|
639
673
|
}, []);
|
|
@@ -654,33 +688,6 @@ export function NavigationProvider({ children }: Props) {
|
|
|
654
688
|
}
|
|
655
689
|
}, [state?.options?.videoModal?.previousMode]);
|
|
656
690
|
|
|
657
|
-
const openModalBottomSheet = React.useCallback(
|
|
658
|
-
({
|
|
659
|
-
ModalBottomSheetContent,
|
|
660
|
-
modalBottomSheetContentProps,
|
|
661
|
-
props = {},
|
|
662
|
-
options = {},
|
|
663
|
-
}) => {
|
|
664
|
-
openModal({
|
|
665
|
-
item: {
|
|
666
|
-
ModalBottomSheetContent,
|
|
667
|
-
modalBottomSheetContentProps,
|
|
668
|
-
},
|
|
669
|
-
props,
|
|
670
|
-
options: {
|
|
671
|
-
animationType: "none",
|
|
672
|
-
animated: false,
|
|
673
|
-
...options,
|
|
674
|
-
},
|
|
675
|
-
});
|
|
676
|
-
},
|
|
677
|
-
[openModal]
|
|
678
|
-
);
|
|
679
|
-
|
|
680
|
-
const closeModalBottomSheet = React.useCallback(() => {
|
|
681
|
-
dismissModal();
|
|
682
|
-
}, [dismissModal]);
|
|
683
|
-
|
|
684
691
|
const getNestedEntry = () => state?.location?.state?.nested?.entry ?? null;
|
|
685
692
|
|
|
686
693
|
// It will only work for the regular stack and pathname, It won't work for modals
|
|
@@ -721,8 +728,6 @@ export function NavigationProvider({ children }: Props) {
|
|
|
721
728
|
openModal,
|
|
722
729
|
dismissModal,
|
|
723
730
|
getModalState,
|
|
724
|
-
openModalBottomSheet,
|
|
725
|
-
closeModalBottomSheet,
|
|
726
731
|
videoModalState: state.options.videoModal,
|
|
727
732
|
openVideoModal,
|
|
728
733
|
closeVideoModal,
|
|
@@ -739,7 +744,15 @@ export function NavigationProvider({ children }: Props) {
|
|
|
739
744
|
getStackForPathname,
|
|
740
745
|
mainStack: state.stack.mainStack,
|
|
741
746
|
}),
|
|
742
|
-
[
|
|
747
|
+
[
|
|
748
|
+
rivers,
|
|
749
|
+
plugins,
|
|
750
|
+
// TODO: Compare entire screen data from state must be removed
|
|
751
|
+
// State size could be large ~150kb and take 80ms on android tv on 8 rails
|
|
752
|
+
JSON.stringify(state, getJsonReplacer()),
|
|
753
|
+
appLaunched,
|
|
754
|
+
startUpHooks,
|
|
755
|
+
]
|
|
743
756
|
);
|
|
744
757
|
|
|
745
758
|
// when running the tests, we pass the dispatch function
|
|
@@ -461,48 +461,4 @@ describe("<NavigationProvider />", () => {
|
|
|
461
461
|
});
|
|
462
462
|
});
|
|
463
463
|
});
|
|
464
|
-
|
|
465
|
-
describe("modal feature", () => {
|
|
466
|
-
it("sets the modalState when opening a modal", () => {
|
|
467
|
-
act(() => {
|
|
468
|
-
wrapper = renderContextConsumer();
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
const view = getByTestId(wrapper, "WrapperView");
|
|
472
|
-
act(() => view.props.navigator.openModal({ item: rivers.A1234 }));
|
|
473
|
-
expect(view.props.navigator.get).toMatchSnapshot();
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
it("removes the visible flag when dismissing a modal", () => {
|
|
477
|
-
act(() => {
|
|
478
|
-
wrapper = renderContextConsumer();
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
const view = getByTestId(wrapper, "WrapperView");
|
|
482
|
-
act(() => view.props.navigator.openModal({ item: rivers.A1234 }));
|
|
483
|
-
expect(view.props.navigator.getModalState()).toMatchSnapshot();
|
|
484
|
-
act(() => view.props.navigator.dismissModal());
|
|
485
|
-
expect(view.props.navigator.getModalState().visible).toBeFalsy();
|
|
486
|
-
expect(view.props.navigator.getModalState()).toMatchSnapshot();
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
it("should reset state to initial when dismissing a modal ( closeModalBottomSheet ) opened by openModalBottomSheet", () => {
|
|
490
|
-
act(() => {
|
|
491
|
-
wrapper = renderContextConsumer();
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
const view = getByTestId(wrapper, "WrapperView");
|
|
495
|
-
|
|
496
|
-
act(() =>
|
|
497
|
-
view.props.navigator.openModalBottomSheet({
|
|
498
|
-
ModalBottomSheetContent: rivers.A1234,
|
|
499
|
-
modalBottomSheetContentProps: { some: "props" },
|
|
500
|
-
})
|
|
501
|
-
);
|
|
502
|
-
|
|
503
|
-
expect(view.props.navigator.getModalState()).toMatchSnapshot();
|
|
504
|
-
act(() => view.props.navigator.closeModalBottomSheet());
|
|
505
|
-
expect(view.props.navigator.getModalState()).toMatchSnapshot();
|
|
506
|
-
});
|
|
507
|
-
});
|
|
508
464
|
});
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as ReactIs from "react-is";
|
|
3
3
|
import { View, Text } from "react-native";
|
|
4
|
-
import {
|
|
5
|
-
import { shallowToJson } from "enzyme-to-json";
|
|
4
|
+
import { render } from "@testing-library/react-native";
|
|
6
5
|
import configureStore from "redux-mock-store";
|
|
7
6
|
|
|
8
7
|
import { storeConnector } from "../index";
|
|
8
|
+
import { Provider } from "react-redux";
|
|
9
9
|
|
|
10
10
|
const styles = { foo: "bar" };
|
|
11
11
|
const components = { grid: jest.fn() };
|
|
12
12
|
const rivers = { A123: { name: "home", id: "A123" } };
|
|
13
13
|
const appSettings = { bundleIdentifier: "com.app.test" };
|
|
14
14
|
|
|
15
|
-
const Component = () => (
|
|
16
|
-
<View>
|
|
15
|
+
const Component = (props) => (
|
|
16
|
+
<View testID="test-comp" {...props}>
|
|
17
17
|
<Text>I'm a component</Text>
|
|
18
18
|
</View>
|
|
19
19
|
);
|
|
@@ -28,7 +28,6 @@ const store = configureStore()({
|
|
|
28
28
|
describe("storeConnector", () => {
|
|
29
29
|
it("returns a Component", () => {
|
|
30
30
|
const WrappedComponent = storeConnector(Component);
|
|
31
|
-
|
|
32
31
|
expect(ReactIs.isValidElementType(WrappedComponent)).toBe(true);
|
|
33
32
|
|
|
34
33
|
expect(WrappedComponent).toEqual(
|
|
@@ -41,12 +40,19 @@ describe("storeConnector", () => {
|
|
|
41
40
|
|
|
42
41
|
it("passes the props to the component", () => {
|
|
43
42
|
const WrappedComponent = storeConnector(Component);
|
|
44
|
-
const wrapper = shallow(<WrappedComponent store={store} />).dive();
|
|
45
|
-
const wrapperProps = wrapper.first().props();
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
const { getByText, getByTestId } = render(
|
|
45
|
+
<Provider store={store}>
|
|
46
|
+
<WrappedComponent />
|
|
47
|
+
</Provider>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(getByText("I'm a component")).toBeTruthy();
|
|
51
|
+
|
|
52
|
+
expect(getByTestId("test-comp").props).toMatchObject({
|
|
53
|
+
appSettings,
|
|
54
|
+
components,
|
|
55
|
+
rivers,
|
|
56
|
+
});
|
|
51
57
|
});
|
|
52
58
|
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { View, Text } from "react-native";
|
|
3
|
+
import { render, waitFor } from "@testing-library/react-native";
|
|
4
|
+
import thunk from "redux-thunk";
|
|
5
|
+
import configureStore from "redux-mock-store";
|
|
6
|
+
|
|
7
|
+
let mocked_items: Record<string, any> = {};
|
|
8
|
+
|
|
9
|
+
import { appLifeCycleManager } from "../";
|
|
10
|
+
|
|
11
|
+
const mock_actions = {};
|
|
12
|
+
|
|
13
|
+
jest.mock("@applicaster/zapp-react-native-redux/AppData", () => ({
|
|
14
|
+
loadAppData: jest.fn(() => () => Promise.resolve()),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/actions", () => ({
|
|
18
|
+
useActions: jest.fn(() => ({ actions: mock_actions })),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
jest.mock(
|
|
22
|
+
"@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage",
|
|
23
|
+
() => ({
|
|
24
|
+
localStorage: {
|
|
25
|
+
getAllItems: jest.fn(() => Promise.resolve(mocked_items)),
|
|
26
|
+
setItem: jest.fn((key, value) => {
|
|
27
|
+
mocked_items[key] = JSON.stringify(value);
|
|
28
|
+
|
|
29
|
+
return Promise.resolve(true);
|
|
30
|
+
}),
|
|
31
|
+
getItem: jest.fn((key) => {
|
|
32
|
+
if (mocked_items[key]) {
|
|
33
|
+
if (typeof mocked_items[key] === "string") {
|
|
34
|
+
return Promise.resolve(JSON.parse(mocked_items[key]));
|
|
35
|
+
} else {
|
|
36
|
+
return Promise.resolve(mocked_items[key]);
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
return Promise.resolve([]);
|
|
40
|
+
}
|
|
41
|
+
}),
|
|
42
|
+
__reset() {
|
|
43
|
+
mocked_items = {};
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const Component = () => (
|
|
50
|
+
<View>
|
|
51
|
+
<Text>I'm a component</Text>
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const QuickBrickManager = {
|
|
56
|
+
sendQuickBrickEvent: jest.fn(),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const LOADING_ERROR = new Error("loading failed");
|
|
60
|
+
|
|
61
|
+
const appLoader = jest.fn((_dispatch, _getState) => {
|
|
62
|
+
if (_getState().shouldFail) return Promise.reject(LOADING_ERROR);
|
|
63
|
+
|
|
64
|
+
return Promise.resolve();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const store = configureStore([thunk])({});
|
|
68
|
+
const dispatch = store.dispatch;
|
|
69
|
+
|
|
70
|
+
const state = {
|
|
71
|
+
rivers: {},
|
|
72
|
+
plugins: {},
|
|
73
|
+
appSettings: { runtimeConfigurationUrls: { rivers: "http://rivers.url" } },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const QUICK_BRICK_READY_EVENT = "quickBrickReady";
|
|
77
|
+
|
|
78
|
+
describe("appLifeCycleManager", () => {
|
|
79
|
+
const decorator = appLifeCycleManager(appLoader, QuickBrickManager);
|
|
80
|
+
const DecoratedComponent = decorator(Component);
|
|
81
|
+
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
appLoader.mockClear();
|
|
84
|
+
QuickBrickManager.sendQuickBrickEvent.mockClear();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("returns a function to decorate a component", () => {
|
|
88
|
+
expect(decorator).toBeDefined();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("runs the loaders, then sends quickBrickReady event", async () => {
|
|
92
|
+
const testCallback = jest.fn(() => {});
|
|
93
|
+
|
|
94
|
+
render(
|
|
95
|
+
<DecoratedComponent
|
|
96
|
+
dispatch={dispatch}
|
|
97
|
+
{...state}
|
|
98
|
+
testCallback={testCallback}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
await waitFor(() => {
|
|
103
|
+
return expect(testCallback).toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const actions = store.getActions();
|
|
107
|
+
|
|
108
|
+
expect(actions[0]).toMatchObject({ type: "SET_APP_READY" });
|
|
109
|
+
|
|
110
|
+
expect(QuickBrickManager.sendQuickBrickEvent).toHaveBeenCalledWith(
|
|
111
|
+
QUICK_BRICK_READY_EVENT
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
expect(appLoader).toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("sends the quickBrickReady event when a loader doesn't resolves", async () => {
|
|
118
|
+
const testCallback = jest.fn(() => {});
|
|
119
|
+
|
|
120
|
+
render(
|
|
121
|
+
<DecoratedComponent
|
|
122
|
+
dispatch={dispatch}
|
|
123
|
+
{...state}
|
|
124
|
+
shouldFail
|
|
125
|
+
testCallback={testCallback}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const actions = store.getActions();
|
|
130
|
+
|
|
131
|
+
await waitFor(() => {
|
|
132
|
+
return expect(testCallback).toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(actions[0]).toMatchObject({ type: "SET_APP_READY" });
|
|
136
|
+
expect(actions[1]).toMatchObject({ type: "SET_APP_LAUNCHED" });
|
|
137
|
+
|
|
138
|
+
expect(QuickBrickManager.sendQuickBrickEvent).toHaveBeenCalledWith(
|
|
139
|
+
QUICK_BRICK_READY_EVENT
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(testCallback).toHaveBeenCalled();
|
|
143
|
+
|
|
144
|
+
expect(appLoader).toHaveBeenCalled();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -131,9 +131,8 @@ export function appLifeCycleManager(appLoader, QuickBrickManager) {
|
|
|
131
131
|
)
|
|
132
132
|
.map((plugin: any) => plugin.module?.getLoginProtocol?.());
|
|
133
133
|
|
|
134
|
-
const loginProtocols: LoginProtocol[] =
|
|
135
|
-
promiseProtocols
|
|
136
|
-
);
|
|
134
|
+
const loginProtocols: LoginProtocol[] =
|
|
135
|
+
await Promise.all(promiseProtocols);
|
|
137
136
|
|
|
138
137
|
// Await all does not work here
|
|
139
138
|
for (const protocol of loginProtocols) {
|
|
@@ -183,13 +182,13 @@ export function appLifeCycleManager(appLoader, QuickBrickManager) {
|
|
|
183
182
|
data: { error },
|
|
184
183
|
});
|
|
185
184
|
}
|
|
186
|
-
|
|
187
|
-
makeAppReady();
|
|
188
|
-
checkStoredContextKeys();
|
|
189
185
|
};
|
|
190
186
|
|
|
191
187
|
React.useLayoutEffect(() => {
|
|
192
|
-
prepareApp()
|
|
188
|
+
prepareApp().then(() => {
|
|
189
|
+
makeAppReady();
|
|
190
|
+
checkStoredContextKeys();
|
|
191
|
+
});
|
|
193
192
|
}, []);
|
|
194
193
|
|
|
195
194
|
return <Component {...props} />;
|
package/App/index.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as R from "ramda";
|
|
3
|
+
|
|
3
4
|
import {
|
|
4
5
|
initialWindowMetrics,
|
|
5
6
|
SafeAreaProvider,
|
|
@@ -29,6 +30,7 @@ import { appRemoteDataLoader } from "./appRemoteDataLoader";
|
|
|
29
30
|
import { withActionExecutor } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutorContext";
|
|
30
31
|
import { withScreenDataContextProvider } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenDataContext";
|
|
31
32
|
import { LogicAggregatorContainer } from "./LogicAggregatorContainer";
|
|
33
|
+
import { LoadingOverlay } from "./LoadingOverlay";
|
|
32
34
|
|
|
33
35
|
interface AppOptions {
|
|
34
36
|
Layout: React.ComponentType;
|
|
@@ -71,10 +73,11 @@ const App = ({
|
|
|
71
73
|
</SplashLoader>
|
|
72
74
|
<ModalProvider />
|
|
73
75
|
</URLSchemeListener>
|
|
74
|
-
<
|
|
75
|
-
components={[InteractionManager, ApplyScreenHooks]}
|
|
76
|
-
/>
|
|
76
|
+
<LoadingOverlay />
|
|
77
77
|
</ErrorBoundary>
|
|
78
|
+
<LogicAggregatorContainer
|
|
79
|
+
components={[InteractionManager, ApplyScreenHooks]}
|
|
80
|
+
/>
|
|
78
81
|
</AppContainer>
|
|
79
82
|
</SafeAreaProvider>
|
|
80
83
|
</ThemeManager>
|