@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.
Files changed (37) hide show
  1. package/App/DeepLinking/URLSchemeListener/URLSchemeContextProvider.tsx +14 -7
  2. package/App/DeepLinking/URLSchemeListener/__tests__/URLSchemeListener.test.js +40 -0
  3. package/App/DeepLinking/URLSchemeListener/index.tsx +1 -1
  4. package/App/ErrorBoundary/__tests__/errorBoundary.test.jsx +232 -0
  5. package/App/ErrorBoundary/__tests__/store.test.js +143 -0
  6. package/App/ErrorBoundary/index.tsx +45 -21
  7. package/App/ErrorBoundary/store.ts +26 -0
  8. package/App/LoadingOverlay/LoadingOverlayManager.tsx +27 -0
  9. package/App/LoadingOverlay/index.tsx +49 -0
  10. package/App/LoadingOverlay/useLoadingOverlayVisibility.tsx +18 -0
  11. package/App/ModalProvider/ModalBottomSheet/Overlay.tsx +5 -7
  12. package/App/ModalProvider/ModalBottomSheet/index.tsx +6 -10
  13. package/App/ModalProvider/index.tsx +5 -6
  14. package/App/NavigationProvider/NavigationProvider.tsx +56 -43
  15. package/App/NavigationProvider/__tests__/navigationProvider.test.tsx +0 -44
  16. package/App/NetworkStatusProvider/utils.ts +0 -1
  17. package/App/StoreConnector/__tests__/storeConnector.test.js +17 -11
  18. package/App/appLifeCycleManager/__tests__/appLifeCycleManager.test.tsx +146 -0
  19. package/App/appLifeCycleManager/index.tsx +6 -7
  20. package/App/index.tsx +6 -3
  21. package/App/remoteContextReloader/__tests__/remoteContextReloader.test.js +11 -21
  22. package/App/remoteContextReloader/getRemoteContextData/getNativeRemoteContextData.ts +14 -0
  23. package/createZappApp/__tests__/__snapshots__/createZappApp.test.js.snap +5 -128
  24. package/createZappApp/__tests__/createZappApp.test.js +7 -20
  25. package/defaults/__tests__/defaults.test.js +3 -4
  26. package/index.d.ts +1 -1
  27. package/package.json +10 -14
  28. package/App/DeepLinking/URLSchemeListener/__tests__/URLSchemeListenerTest.js +0 -177
  29. package/App/ErrorBoundary/__tests__/__snapshots__/errorBoundary.test.js.snap +0 -9
  30. package/App/ErrorBoundary/__tests__/errorBoundary.test.js +0 -72
  31. package/App/ModalProvider/ModalChildrenWrapper/index.web.ts +0 -1
  32. package/App/NavigationProvider/__tests__/__snapshots__/navigationProvider.test.tsx.snap +0 -68
  33. package/App/StoreConnector/__tests__/__snapshots__/storeConnector.test.js.snap +0 -35
  34. package/App/appLifeCycleManager/__tests__/__snapshots__/appLifeCycleManager.test.js.snap +0 -30
  35. package/App/appLifeCycleManager/__tests__/appLifeCycleManager.test.js +0 -134
  36. package/App/remoteContextReloader/__tests__/__snapshots__/remoteContextReloader.test.js.snap +0 -43
  37. /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 { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
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 = (onAnimationEnd) => ({
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(navigator.closeModalBottomSheet)}
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, useMemo, useState } from "react";
7
- import { View, StyleSheet, Keyboard } from "react-native";
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 = useMemo(
79
- () =>
80
- isTablet
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={visible && sheetHeight > 0}
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 { useModalStore } from "@applicaster/zapp-react-native-utils/modalState";
9
+ import { useModalStoreState } from "@applicaster/zapp-react-native-utils/modalState";
10
10
 
11
11
  export function ModalProvider() {
12
- const { modalState } = useModalStore();
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 { debounce } from "@applicaster/zapp-react-native-utils/functionUtils";
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 { useModalStore } from "@applicaster/zapp-react-native-utils/modalState";
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 } = useModalStore.getState();
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
- navigator.openModal({
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
- navigator.dismissModal();
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 (hookPlugin.isModalHook()) {
425
- navigator.dismissModal();
426
- } else {
427
- goBack(true, true);
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
- navigator.dismissModal();
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
- [rivers, plugins, JSON.stringify(state), appLaunched, startUpHooks]
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,4 +1,3 @@
1
- /// <reference types="@types/tizen-tv-webapis" />
2
1
  import * as R from "ramda";
3
2
 
4
3
  import { NetInfoState } from "@react-native-community/netinfo";
@@ -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 { shallow } from "enzyme";
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
- expect(shallowToJson(wrapper)).toMatchSnapshot();
48
- expect(wrapperProps).toHaveProperty("components", components);
49
- expect(wrapperProps).toHaveProperty("rivers", rivers);
50
- expect(wrapperProps).toHaveProperty("appSettings", appSettings);
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[] = await Promise.all(
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
- <LogicAggregatorContainer
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>