@applicaster/quick-brick-core 16.0.0-rc.126 → 16.0.0-rc.128
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 +1 -1
- package/App/AssetCache/logger.ts +1 -1
- 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 +2 -2
- package/App/NavigationProvider/utils.ts +2 -2
- package/App/NetworkStatusProvider/NetworkStatusProvider.tsx +14 -6
- package/App/NetworkStatusProvider/ResumeVerificationController.ts +2 -2
- package/App/NetworkStatusProvider/__tests__/NetworkStatusProvider.test.tsx +27 -12
- package/App/NetworkStatusProvider/__tests__/ResumeVerificationController.test.ts +27 -12
- package/App/NetworkStatusProvider/__tests__/useResumeVerifiedNetInfo.test.ts +27 -12
- 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/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 +49 -16
- package/App/remoteContextReloader/helpers.ts +2 -1
- 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
|
|
package/App/AssetCache/logger.ts
CHANGED
|
@@ -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;
|
|
@@ -45,7 +45,6 @@ import {
|
|
|
45
45
|
startUpHookPluginIdentifiersSelector,
|
|
46
46
|
} from "./navigator/selectors";
|
|
47
47
|
|
|
48
|
-
import { coreAppLogger } from "../logger";
|
|
49
48
|
import {
|
|
50
49
|
getPlatform,
|
|
51
50
|
isTV,
|
|
@@ -89,8 +88,9 @@ import {
|
|
|
89
88
|
useLayoutVersion,
|
|
90
89
|
useRivers,
|
|
91
90
|
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
91
|
+
import { subsystemLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
92
92
|
|
|
93
|
-
const logger =
|
|
93
|
+
const logger = subsystemLogger("ui/navigation", "Navigator");
|
|
94
94
|
|
|
95
95
|
type Props = {
|
|
96
96
|
children?: React.ReactChild;
|
|
@@ -7,16 +7,16 @@ import {
|
|
|
7
7
|
} from "@applicaster/zapp-react-native-utils/navigationUtils";
|
|
8
8
|
import { findPluginByType } from "@applicaster/zapp-react-native-utils/pluginUtils";
|
|
9
9
|
|
|
10
|
-
import { coreAppLogger } from "../logger";
|
|
11
10
|
import { isString } from "@applicaster/zapp-react-native-utils/stringUtils";
|
|
12
11
|
import { SCREEN_TYPES } from "@applicaster/zapp-react-native-utils/navigationUtils/itemTypes";
|
|
13
12
|
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
13
|
+
import { subsystemLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
14
14
|
|
|
15
15
|
const EXTERNAL_URL_TYPE = "external_url";
|
|
16
16
|
const EXTERNAL_URL_FLAG = "open_external_url";
|
|
17
17
|
const WEBVIEW_SCREEN_IDENTIFIER = "webview_screen_qb";
|
|
18
18
|
|
|
19
|
-
const logger =
|
|
19
|
+
const logger = subsystemLogger("ui/navigation", "Navigator");
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Drops a leading `/hooks/<screen_id>` segment from a path.
|
|
@@ -12,11 +12,12 @@ import {
|
|
|
12
12
|
unsubscribeToDeviceNetworkChanges,
|
|
13
13
|
} from "./utils";
|
|
14
14
|
import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
import { useResumeVerifiedNetInfo } from "./useResumeVerifiedNetInfo";
|
|
17
17
|
import { describeNetInfo } from "./resumeVerification";
|
|
18
|
+
import { subsystemLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
18
19
|
|
|
19
|
-
const logger =
|
|
20
|
+
const logger = subsystemLogger("network/reachability", "NetworkStatusProvider");
|
|
20
21
|
|
|
21
22
|
// Use default netInfo config for all platforms (important for web platform which otherwise
|
|
22
23
|
// will use backend root which is not responding for HEAD request)
|
|
@@ -54,10 +55,17 @@ export const NetworkStatusProvider = ({
|
|
|
54
55
|
const { netInfo: verifiedNetInfo, stillOfflineAfterResumeCount } =
|
|
55
56
|
useResumeVerifiedNetInfo(liveNetInfo);
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
// Memoized so consumers only re-render when the published status changes,
|
|
59
|
+
// not on every render of this provider
|
|
60
|
+
const combinedNetInfo = React.useMemo<
|
|
61
|
+
NetInfoState & {
|
|
62
|
+
deviceStatus?: NetStatus;
|
|
63
|
+
stillOfflineAfterResumeCount: number;
|
|
64
|
+
}
|
|
65
|
+
>(
|
|
66
|
+
() => ({ ...verifiedNetInfo, stillOfflineAfterResumeCount }),
|
|
67
|
+
[verifiedNetInfo, stillOfflineAfterResumeCount]
|
|
68
|
+
);
|
|
61
69
|
|
|
62
70
|
const { onConnectionTypeChanged, onConnectionLost, onConnectionRestored } =
|
|
63
71
|
React.useMemo<NetworkHooks>(() => getNetworkHooks(plugins), []);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AppState, AppStateStatus } from "react-native";
|
|
2
2
|
import { NetInfoState } from "@react-native-community/netinfo";
|
|
3
3
|
|
|
4
|
-
import { coreAppLogger } from "../logger";
|
|
5
4
|
import { isOnline } from "./utils";
|
|
6
5
|
import {
|
|
7
6
|
RESUME_VERIFICATION_DELAY_MS,
|
|
@@ -10,8 +9,9 @@ import {
|
|
|
10
9
|
isBackgroundAppState,
|
|
11
10
|
isResumeVerificationEnabled,
|
|
12
11
|
} from "./resumeVerification";
|
|
12
|
+
import { subsystemLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
13
13
|
|
|
14
|
-
const logger =
|
|
14
|
+
const logger = subsystemLogger("network/reachability", "NetworkStatusProvider");
|
|
15
15
|
|
|
16
16
|
export type ConnectionInfo = NetInfoState & { deviceStatus?: NetStatus };
|
|
17
17
|
|
|
@@ -16,18 +16,33 @@ const mockLogger = {
|
|
|
16
16
|
error: jest.fn(),
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
//
|
|
20
|
-
jest.mock("
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
// subsystemLogger runs at module load, before mockLogger is initialised: delegate lazily
|
|
20
|
+
jest.mock("@applicaster/zapp-react-native-utils/logger", () => {
|
|
21
|
+
const actual = jest.requireActual(
|
|
22
|
+
"@applicaster/zapp-react-native-utils/logger"
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
...actual,
|
|
27
|
+
// Only the logger of this subsystem is replaced; every other module keeps its real one.
|
|
28
|
+
subsystemLogger: (subsystem: string) => {
|
|
29
|
+
if (subsystem !== "network/reachability") {
|
|
30
|
+
return actual.subsystemLogger(subsystem);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const logger = {
|
|
34
|
+
warning: (event) => mockLogger.warning(event),
|
|
35
|
+
debug: (event) => mockLogger.debug(event),
|
|
36
|
+
info: (event) => mockLogger.info(event),
|
|
37
|
+
log: (event) => mockLogger.log(event),
|
|
38
|
+
error: (event) => mockLogger.error(event),
|
|
39
|
+
addSubsystem: () => logger,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return logger;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
});
|
|
31
46
|
|
|
32
47
|
jest.mock("react-native/Libraries/AppState/AppState", () => {
|
|
33
48
|
const { EventEmitter } = require("events");
|
|
@@ -12,18 +12,33 @@ const mockLogger = {
|
|
|
12
12
|
error: jest.fn(),
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
jest.mock("
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
// subsystemLogger runs at module load, before mockLogger is initialised: delegate lazily
|
|
16
|
+
jest.mock("@applicaster/zapp-react-native-utils/logger", () => {
|
|
17
|
+
const actual = jest.requireActual(
|
|
18
|
+
"@applicaster/zapp-react-native-utils/logger"
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
...actual,
|
|
23
|
+
// Only the logger of this subsystem is replaced; every other module keeps its real one.
|
|
24
|
+
subsystemLogger: (subsystem: string) => {
|
|
25
|
+
if (subsystem !== "network/reachability") {
|
|
26
|
+
return actual.subsystemLogger(subsystem);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const logger = {
|
|
30
|
+
warning: (event) => mockLogger.warning(event),
|
|
31
|
+
debug: (event) => mockLogger.debug(event),
|
|
32
|
+
info: (event) => mockLogger.info(event),
|
|
33
|
+
log: (event) => mockLogger.log(event),
|
|
34
|
+
error: (event) => mockLogger.error(event),
|
|
35
|
+
addSubsystem: () => logger,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return logger;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
});
|
|
27
42
|
|
|
28
43
|
jest.mock("react-native/Libraries/AppState/AppState", () => {
|
|
29
44
|
const { EventEmitter } = require("events");
|
|
@@ -14,18 +14,33 @@ const mockLogger = {
|
|
|
14
14
|
error: jest.fn(),
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
//
|
|
18
|
-
jest.mock("
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
// subsystemLogger runs at module load, before mockLogger is initialised: delegate lazily
|
|
18
|
+
jest.mock("@applicaster/zapp-react-native-utils/logger", () => {
|
|
19
|
+
const actual = jest.requireActual(
|
|
20
|
+
"@applicaster/zapp-react-native-utils/logger"
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
...actual,
|
|
25
|
+
// Only the logger of this subsystem is replaced; every other module keeps its real one.
|
|
26
|
+
subsystemLogger: (subsystem: string) => {
|
|
27
|
+
if (subsystem !== "network/reachability") {
|
|
28
|
+
return actual.subsystemLogger(subsystem);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const logger = {
|
|
32
|
+
warning: (event) => mockLogger.warning(event),
|
|
33
|
+
debug: (event) => mockLogger.debug(event),
|
|
34
|
+
info: (event) => mockLogger.info(event),
|
|
35
|
+
log: (event) => mockLogger.log(event),
|
|
36
|
+
error: (event) => mockLogger.error(event),
|
|
37
|
+
addSubsystem: () => logger,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return logger;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
});
|
|
29
44
|
|
|
30
45
|
jest.mock("react-native/Libraries/AppState/AppState", () => {
|
|
31
46
|
const { EventEmitter } = require("events");
|
|
@@ -100,7 +100,12 @@ const MemoizedThemeContextProvider = React.memo(
|
|
|
100
100
|
function ThemeManagerComponent({ children, plugins }: Props) {
|
|
101
101
|
const isTablet = useIsTablet();
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
// Stable reference keeps MemoizedThemeContextProvider's byReference check
|
|
104
|
+
// effective, so theme consumers don't re-render on every parent render
|
|
105
|
+
const themes = React.useMemo(
|
|
106
|
+
() => selectThemes(isTablet, plugins),
|
|
107
|
+
[isTablet, plugins]
|
|
108
|
+
);
|
|
104
109
|
|
|
105
110
|
const [selectedThemeId, setSelectedThemeId] =
|
|
106
111
|
React.useState(DEFAULT_THEME_ID);
|
|
@@ -12,13 +12,13 @@ import { isAndroidPlatform } from "@applicaster/zapp-react-native-utils/reactUti
|
|
|
12
12
|
import { networkStatusReady } from "../NetworkStatusProvider/networkStatusReady";
|
|
13
13
|
import { updateSessionNumber } from "../SessionCounter/sessionCounter";
|
|
14
14
|
|
|
15
|
-
import { coreAppLogger } from "../logger";
|
|
16
15
|
import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
|
|
17
16
|
import { isEmptyOrNil } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
18
17
|
import { requiredSessionKeys } from "../../const";
|
|
19
18
|
import { initializeActionSetters, useActionSetters } from "../ActionSetters";
|
|
19
|
+
import { subsystemLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
20
20
|
|
|
21
|
-
const logger =
|
|
21
|
+
const logger = subsystemLogger("app/lifecycle", "AppLifeCycleManager");
|
|
22
22
|
|
|
23
23
|
const isAndroid = isAndroidPlatform();
|
|
24
24
|
|
|
@@ -2,11 +2,11 @@ import * as React from "react";
|
|
|
2
2
|
|
|
3
3
|
import { getRemoteContextData } from "../remoteContextReloader/getRemoteContextData";
|
|
4
4
|
|
|
5
|
-
import { coreAppLogger } from "../logger";
|
|
6
5
|
import { UnknownAction, Dispatch } from "@reduxjs/toolkit";
|
|
7
6
|
import { isAndroidPlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
7
|
+
import { subsystemLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
8
8
|
|
|
9
|
-
const logger =
|
|
9
|
+
const logger = subsystemLogger("app/loading", "AppRemoteDataLoader");
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* adds a call to the QuickBrickManager to tell the app is ready, reloads remote
|
|
@@ -5,8 +5,8 @@ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
|
5
5
|
import { coreAppLogger } from "../logger";
|
|
6
6
|
|
|
7
7
|
const logger = createLogger({
|
|
8
|
-
category: "
|
|
9
|
-
subsystem: "
|
|
8
|
+
category: "RemoteDataLoader",
|
|
9
|
+
subsystem: "app/loading",
|
|
10
10
|
parent: coreAppLogger,
|
|
11
11
|
});
|
|
12
12
|
|
package/App/logger.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { subsystemLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
2
2
|
|
|
3
|
-
export const coreAppLogger =
|
|
3
|
+
export const coreAppLogger = subsystemLogger("app");
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { loadAppContextData } from "@applicaster/zapp-react-native-redux";
|
|
2
|
+
import { getPromiseForType } from "../helpers";
|
|
3
|
+
import {
|
|
4
|
+
applyPersistedLayoutPreview,
|
|
5
|
+
restoreInvalidPreview,
|
|
6
|
+
} from "../../layoutPreview/persistence";
|
|
7
|
+
import { getNativeRemoteContextData } from "../getRemoteContextData/getNativeRemoteContextData";
|
|
8
|
+
|
|
9
|
+
jest.mock("@applicaster/zapp-react-native-redux", () => ({
|
|
10
|
+
...jest.requireActual("@applicaster/zapp-react-native-redux"),
|
|
11
|
+
loadAppContextData: jest.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock("../helpers", () => ({
|
|
15
|
+
getPromiseForType: jest.fn(),
|
|
16
|
+
fetchPluginConfiguration: () => (plugin) => plugin,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
jest.mock("../../AssetCache", () => ({
|
|
20
|
+
cacheAssets: jest.fn(async (data) => data),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
jest.mock("../../layoutPreview/persistence", () => ({
|
|
24
|
+
applyPersistedLayoutPreview: jest.fn(),
|
|
25
|
+
restoreInvalidPreview: jest.fn(),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
const mockedGetPromiseForType = getPromiseForType as jest.Mock;
|
|
29
|
+
|
|
30
|
+
const FILES = {
|
|
31
|
+
remoteConfigurations: { remoteConfigurations: {} },
|
|
32
|
+
pluginConfigurations: { pluginConfigurations: [] },
|
|
33
|
+
pipesEndpoints: { pipesEndpoints: {} },
|
|
34
|
+
layout: { layout: { api_version: "v2", content_types: {} } },
|
|
35
|
+
cellStyles: { cellStyles: { style: {} } },
|
|
36
|
+
presetsMapping: { presetsMapping: { preset: {} } },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const LAYOUT_FILES = ["layout", "cellStyles", "presetsMapping"];
|
|
40
|
+
|
|
41
|
+
/** Holds every request open until the test releases it. */
|
|
42
|
+
function deferredFiles(files = FILES) {
|
|
43
|
+
const pending: Record<string, () => void> = {};
|
|
44
|
+
|
|
45
|
+
mockedGetPromiseForType.mockImplementation(
|
|
46
|
+
(key: string) =>
|
|
47
|
+
new Promise((resolve, reject) => {
|
|
48
|
+
pending[key] = () =>
|
|
49
|
+
files[key] ? resolve(files[key]) : reject(new Error(key));
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return pending;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Keys of the app context handed to the store on the final load. */
|
|
57
|
+
const loadedKeys = () =>
|
|
58
|
+
Object.keys((loadAppContextData as jest.Mock).mock.calls.at(-1)[1]);
|
|
59
|
+
|
|
60
|
+
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
61
|
+
|
|
62
|
+
describe("getNativeRemoteContextData", () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
jest.clearAllMocks();
|
|
65
|
+
(applyPersistedLayoutPreview as jest.Mock).mockResolvedValue("absent");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("requests the layout files together, not one after another", async () => {
|
|
69
|
+
const pending = deferredFiles();
|
|
70
|
+
const dispatch = jest.fn();
|
|
71
|
+
|
|
72
|
+
const done = getNativeRemoteContextData(dispatch, {}, []);
|
|
73
|
+
|
|
74
|
+
await flush();
|
|
75
|
+
|
|
76
|
+
["remoteConfigurations", "pluginConfigurations", "pipesEndpoints"].forEach(
|
|
77
|
+
(key) => pending[key]()
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
await flush();
|
|
81
|
+
|
|
82
|
+
expect(Object.keys(pending)).toEqual(expect.arrayContaining(LAYOUT_FILES));
|
|
83
|
+
|
|
84
|
+
LAYOUT_FILES.forEach((key) => pending[key]());
|
|
85
|
+
await done;
|
|
86
|
+
|
|
87
|
+
expect(loadedKeys()).toEqual(
|
|
88
|
+
expect.arrayContaining(["rivers", "cellStyles", "presetsMapping"])
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("tolerates a missing presetsMapping", async () => {
|
|
93
|
+
const { presetsMapping: _presetsMapping, ...withoutPresets } = FILES;
|
|
94
|
+
|
|
95
|
+
mockedGetPromiseForType.mockImplementation(async (key: string) => {
|
|
96
|
+
if (!withoutPresets[key]) throw new Error(key);
|
|
97
|
+
|
|
98
|
+
return withoutPresets[key];
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const dispatch = jest.fn();
|
|
102
|
+
|
|
103
|
+
await expect(
|
|
104
|
+
getNativeRemoteContextData(dispatch, {}, [])
|
|
105
|
+
).resolves.toBeUndefined();
|
|
106
|
+
|
|
107
|
+
expect(loadedKeys()).toContain("rivers");
|
|
108
|
+
expect(loadedKeys()).not.toContain("presetsMapping");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("rejects when cellStyles cannot be loaded", async () => {
|
|
112
|
+
const { cellStyles: _cellStyles, ...withoutCellStyles } = FILES;
|
|
113
|
+
|
|
114
|
+
mockedGetPromiseForType.mockImplementation(async (key: string) => {
|
|
115
|
+
if (!withoutCellStyles[key]) throw new Error(key);
|
|
116
|
+
|
|
117
|
+
return withoutCellStyles[key];
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await expect(getNativeRemoteContextData(jest.fn(), {}, [])).rejects.toThrow(
|
|
121
|
+
"cellStyles"
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("refetches every layout file after restoring a broken preview", async () => {
|
|
126
|
+
(applyPersistedLayoutPreview as jest.Mock).mockResolvedValue("applied");
|
|
127
|
+
|
|
128
|
+
let restored = false;
|
|
129
|
+
|
|
130
|
+
(restoreInvalidPreview as jest.Mock).mockImplementation(async () => {
|
|
131
|
+
restored = true;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
mockedGetPromiseForType.mockImplementation(async (key: string) => {
|
|
135
|
+
if (key === "layout" && !restored) throw new Error("preview layout");
|
|
136
|
+
|
|
137
|
+
return FILES[key];
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await getNativeRemoteContextData(jest.fn(), {}, []);
|
|
141
|
+
|
|
142
|
+
const requests = mockedGetPromiseForType.mock.calls.map(([key]) => key);
|
|
143
|
+
|
|
144
|
+
LAYOUT_FILES.forEach((key) => {
|
|
145
|
+
expect(requests.filter((request) => request === key)).toHaveLength(2);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { NativeModules } = require("react-native");
|
|
2
|
+
|
|
3
|
+
NativeModules.AppLoaderBridge = { getFile: jest.fn() };
|
|
4
|
+
|
|
5
|
+
const { getPromiseForType } = require("../helpers");
|
|
6
|
+
|
|
7
|
+
const layout = { id: "layout", content_types: {} };
|
|
8
|
+
|
|
9
|
+
describe("getPromiseForType", () => {
|
|
10
|
+
it("parses the JSON string native bridges resolve", async () => {
|
|
11
|
+
NativeModules.AppLoaderBridge.getFile.mockResolvedValue(
|
|
12
|
+
JSON.stringify(layout)
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
await expect(getPromiseForType("layout")).resolves.toEqual({ layout });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("passes through the parsed file the web bridge resolves", async () => {
|
|
19
|
+
NativeModules.AppLoaderBridge.getFile.mockResolvedValue(layout);
|
|
20
|
+
|
|
21
|
+
await expect(getPromiseForType("layout")).resolves.toEqual({ layout });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("rejects when the bridge fails", async () => {
|
|
25
|
+
NativeModules.AppLoaderBridge.getFile.mockRejectedValue(new Error("404"));
|
|
26
|
+
|
|
27
|
+
await expect(getPromiseForType("layout")).rejects.toThrow(
|
|
28
|
+
"cannot retrieve configuration data for layout"
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
-
import isEmpty from "lodash/isEmpty";
|
|
3
2
|
import { Dimensions, NativeModules } from "react-native";
|
|
4
3
|
import {
|
|
5
4
|
loadAppContextData,
|
|
@@ -7,6 +6,7 @@ import {
|
|
|
7
6
|
} from "@applicaster/zapp-react-native-redux";
|
|
8
7
|
import { isTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
9
8
|
import { mapPromises } from "@applicaster/zapp-react-native-utils/arrayUtils";
|
|
9
|
+
import { isEmpty } from "@applicaster/zapp-react-native-utils/utils";
|
|
10
10
|
import { fetchPluginConfiguration, getPromiseForType } from "../helpers";
|
|
11
11
|
import { cacheAssets } from "../../AssetCache";
|
|
12
12
|
import { logger } from "@applicaster/zapp-react-native-utils/logger";
|
|
@@ -65,6 +65,33 @@ async function selectActiveLayout(
|
|
|
65
65
|
return preview;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Fetches the files that belong to the selected layout. They all derive from
|
|
70
|
+
* the same layout id, so they are requested together rather than one round
|
|
71
|
+
* trip after another. A missing presetsMapping is tolerated; a failing layout
|
|
72
|
+
* or cellStyles request rejects, as it did when they were fetched in sequence.
|
|
73
|
+
*/
|
|
74
|
+
type Settled<T> =
|
|
75
|
+
| { status: "fulfilled"; value: T }
|
|
76
|
+
| { status: "rejected"; reason: unknown };
|
|
77
|
+
|
|
78
|
+
// Promise.allSettled is not guaranteed on older TV engines, so settle by hand
|
|
79
|
+
const settle = <T>(promise: Promise<T>): Promise<Settled<T>> =>
|
|
80
|
+
promise.then(
|
|
81
|
+
(value) => ({ status: "fulfilled", value }),
|
|
82
|
+
(reason) => ({ status: "rejected", reason })
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
async function fetchLayoutFiles() {
|
|
86
|
+
const [layout, cellStyles, presetsMapping] = await Promise.all([
|
|
87
|
+
settle(getPromiseForType<any>("layout")),
|
|
88
|
+
settle(getPromiseForType<any>("cellStyles")),
|
|
89
|
+
settle(getPromiseForType<any>("presetsMapping")),
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
return { layout, cellStyles, presetsMapping };
|
|
93
|
+
}
|
|
94
|
+
|
|
68
95
|
export async function getNativeRemoteContextData(
|
|
69
96
|
dispatch,
|
|
70
97
|
runtimeConfigurationUrls,
|
|
@@ -91,22 +118,27 @@ export async function getNativeRemoteContextData(
|
|
|
91
118
|
isTabletDevice
|
|
92
119
|
);
|
|
93
120
|
|
|
94
|
-
let
|
|
121
|
+
let layoutFiles = await fetchLayoutFiles();
|
|
95
122
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
} catch (error) {
|
|
99
|
-
if (preview !== "applied") throw error;
|
|
123
|
+
if (layoutFiles.layout.status === "rejected") {
|
|
124
|
+
if (preview !== "applied") throw layoutFiles.layout.reason;
|
|
100
125
|
|
|
101
126
|
logger.error({
|
|
102
127
|
message: "Persisted preview layout failed to load",
|
|
103
|
-
data:
|
|
128
|
+
data: layoutFiles.layout.reason,
|
|
104
129
|
});
|
|
105
130
|
|
|
131
|
+
// The files fetched so far belong to the broken preview layout
|
|
106
132
|
await restoreInvalidPreview();
|
|
107
|
-
|
|
133
|
+
layoutFiles = await fetchLayoutFiles();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (layoutFiles.layout.status === "rejected") {
|
|
137
|
+
throw layoutFiles.layout.reason;
|
|
108
138
|
}
|
|
109
139
|
|
|
140
|
+
const layout = layoutFiles.layout.value;
|
|
141
|
+
|
|
110
142
|
const layoutLoadingErrorMessage = "Failed to load layout data";
|
|
111
143
|
|
|
112
144
|
if (isEmpty(layout)) {
|
|
@@ -123,16 +155,17 @@ export async function getNativeRemoteContextData(
|
|
|
123
155
|
|
|
124
156
|
dispatch(AppData.actions.merge({ layoutVersion: apiVersion || "v1" }));
|
|
125
157
|
|
|
126
|
-
|
|
158
|
+
if (layoutFiles.cellStyles.status === "rejected") {
|
|
159
|
+
throw layoutFiles.cellStyles.reason;
|
|
160
|
+
}
|
|
127
161
|
|
|
128
|
-
|
|
162
|
+
const resolvedPromises = [layoutFiles.cellStyles.value];
|
|
129
163
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
164
|
+
// don't break loading sequence if presetsMapping does not exist
|
|
165
|
+
const optionalResolvedPromise =
|
|
166
|
+
layoutFiles.presetsMapping.status === "fulfilled"
|
|
167
|
+
? layoutFiles.presetsMapping.value
|
|
168
|
+
: undefined;
|
|
136
169
|
|
|
137
170
|
const remoteData = R.mergeAll([
|
|
138
171
|
...resolvedPromises,
|
|
@@ -119,8 +119,9 @@ export async function getPromiseForType<T = FileResponse>(
|
|
|
119
119
|
try {
|
|
120
120
|
const response = await AppLoaderBridge?.getFile(key, null);
|
|
121
121
|
|
|
122
|
+
// Native bridges resolve a JSON string; the web bridge resolves the parsed file.
|
|
122
123
|
return {
|
|
123
|
-
[key]: JSON.parse(response),
|
|
124
|
+
[key]: typeof response === "string" ? JSON.parse(response) : response,
|
|
124
125
|
} as T;
|
|
125
126
|
} catch (e) {
|
|
126
127
|
throw new Error("cannot retrieve configuration data for " + key);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/quick-brick-core",
|
|
3
|
-
"version": "16.0.0-rc.
|
|
3
|
+
"version": "16.0.0-rc.128",
|
|
4
4
|
"description": "Core package for Applicaster's Quick Brick App",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@applicaster/applicaster-types": "16.0.0-rc.
|
|
32
|
-
"@applicaster/quick-brick-core-plugins": "16.0.0-rc.
|
|
33
|
-
"@applicaster/zapp-pipes-v2-client": "16.0.0-rc.
|
|
34
|
-
"@applicaster/zapp-react-native-bridge": "16.0.0-rc.
|
|
35
|
-
"@applicaster/zapp-react-native-redux": "16.0.0-rc.
|
|
36
|
-
"@applicaster/zapp-react-native-ui-components": "16.0.0-rc.
|
|
37
|
-
"@applicaster/zapp-react-native-utils": "16.0.0-rc.
|
|
31
|
+
"@applicaster/applicaster-types": "16.0.0-rc.128",
|
|
32
|
+
"@applicaster/quick-brick-core-plugins": "16.0.0-rc.128",
|
|
33
|
+
"@applicaster/zapp-pipes-v2-client": "16.0.0-rc.128",
|
|
34
|
+
"@applicaster/zapp-react-native-bridge": "16.0.0-rc.128",
|
|
35
|
+
"@applicaster/zapp-react-native-redux": "16.0.0-rc.128",
|
|
36
|
+
"@applicaster/zapp-react-native-ui-components": "16.0.0-rc.128",
|
|
37
|
+
"@applicaster/zapp-react-native-utils": "16.0.0-rc.128",
|
|
38
38
|
"atob": "^2.1.2",
|
|
39
39
|
"axios": "^0.28.0",
|
|
40
40
|
"btoa": "^1.2.1",
|