@applicaster/zapp-react-native-utils 16.0.0-alpha.2434153375 → 16.0.0-alpha.2500443242
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/actionUtils/index.ts +3 -1
- package/actionsExecutor/ActionExecutor.ts +10 -1
- package/actionsExecutor/ActionExecutorContext.tsx +29 -1
- package/actionsExecutor/__tests__/ActionExecutor.test.ts +71 -0
- package/actionsExecutor/actions/__tests__/dismissBottomSheet.test.ts +3 -0
- package/actionsExecutor/actions/__tests__/goBack.test.ts +62 -0
- package/actionsExecutor/actions/__tests__/goHome.test.ts +19 -0
- package/actionsExecutor/actions/__tests__/navigateToScreen.test.ts +139 -5
- package/actionsExecutor/actions/__tests__/openBottomSheet.customContent.test.ts +24 -0
- package/actionsExecutor/actions/__tests__/openBottomSheet.inlineItems.test.ts +201 -1
- package/actionsExecutor/actions/__tests__/presentScreen.test.ts +127 -0
- package/actionsExecutor/actions/__tests__/showAlert.test.ts +49 -0
- package/actionsExecutor/actions/__tests__/showToast.test.ts +2 -2
- package/actionsExecutor/actions/dismissBottomSheet.ts +2 -1
- package/actionsExecutor/actions/goBack.ts +60 -0
- package/actionsExecutor/actions/goHome.ts +31 -0
- package/actionsExecutor/actions/index.ts +8 -0
- package/actionsExecutor/actions/navigateToScreen.ts +29 -45
- package/actionsExecutor/actions/openBottomSheet.ts +35 -16
- package/actionsExecutor/actions/presentScreen.ts +78 -0
- package/actionsExecutor/actions/showAlert.ts +51 -0
- package/actionsExecutor/actions/showToast.ts +2 -2
- package/actionsExecutor/consts.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +4 -11
- package/appUtils/playerManager/OverlayObserver/__tests__/getUpcomingQueue.test.ts +67 -0
- package/appUtils/playerManager/OverlayObserver/__tests__/utils.test.ts +9 -2
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ts +15 -2
- package/appUtils/playerManager/OverlayObserver/utils.ts +2 -5
- package/appUtils/playerManager/__tests__/playerNative.test.ts +76 -0
- package/appUtils/playerManager/__tests__/playerNativeCommand.test.ts +62 -0
- package/appUtils/playerManager/__tests__/playerToolbox.test.ts +97 -0
- package/appUtils/playerManager/__tests__/usePlayerControllerSetup.test.tsx +62 -0
- package/appUtils/playerManager/const.ts +13 -0
- package/appUtils/playerManager/index.ts +7 -0
- package/appUtils/playerManager/player.ts +10 -0
- package/appUtils/playerManager/playerNative.ts +22 -0
- package/appUtils/playerManager/playerNativeCommand.ts +3 -6
- package/appUtils/playerManager/playerToolbox.ts +157 -0
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +6 -0
- package/arrayUtils/__tests__/reorderByIds.test.ts +13 -0
- package/arrayUtils/index.ts +26 -43
- package/cellUtils/__tests__/getButtonsEnabled.test.ts +28 -0
- package/cellUtils/__tests__/getCellNavigationEnabled.test.ts +36 -0
- package/cellUtils/__tests__/getIsCellNavigationEnabled.test.ts +35 -0
- package/cellUtils/__tests__/isCellNavigationEnabled.test.ts +21 -0
- package/cellUtils/index.ts +74 -19
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +21 -0
- package/configurationUtils/__tests__/modalBlocksParser.test.ts +286 -1
- package/configurationUtils/index.ts +77 -73
- package/configurationUtils/manifestKeyParser.ts +12 -1
- package/configurationUtils/modalBlocksParser.ts +78 -4
- package/entryActions/__tests__/buildEntryActions.test.ts +97 -18
- package/entryActions/aliasPresentation.ts +26 -42
- package/entryActions/buildEntryActions.ts +1 -0
- package/entryActions/index.ts +0 -2
- package/focusManager/FocusManager.ts +26 -0
- package/focusManager/__tests__/FocusManager.test.ts +9 -0
- package/localizationUtils/index.ts +12 -6
- package/logger/__tests__/xrayToolbox.test.ts +69 -0
- package/logger/xrayToolbox.ts +67 -0
- package/manifestUtils/_internals/index.js +8 -3
- package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +8 -0
- package/manifestUtils/keys.js +6 -1
- package/manifestUtils/mobileAction/container/__tests__/mobileActionButtonsContainer.test.js +21 -0
- package/manifestUtils/mobileAction/groups/__tests__/buildMobileActionButtonGroups.test.js +1 -1
- package/manifestUtils/mobileAction/groups/defaults.js +2 -1
- package/modalState/ModalOrchestrator.ts +22 -9
- package/modalState/RemoteEditableCollection.ts +9 -1
- package/modalState/__tests__/ModalOrchestrator.phase3.test.ts +16 -0
- package/modalState/__tests__/RemoteEditableCollection.test.ts +90 -1
- package/modalState/__tests__/index.test.ts +30 -1
- package/modalState/__tests__/useModalStoreState.test.ts +142 -0
- package/modalState/index.ts +6 -1
- package/modalState/store.ts +9 -2
- package/modalState/types.ts +31 -2
- package/modalState/useBottomSheetContent.ts +24 -17
- package/navigationUtils/xrayNavigator.ts +12 -0
- package/package.json +2 -2
- package/perfTracker.ts +206 -0
- package/pluginUtils/__tests__/pluginUtils.test.js +32 -0
- package/pluginUtils/index.ts +29 -0
- package/reactHooks/__tests__/getPluginConfiguration.test.ts +82 -0
- package/reactHooks/cell-click/__tests__/index.test.js +64 -0
- package/reactHooks/cell-click/index.ts +21 -14
- package/reactHooks/connection/index.tsx +8 -5
- package/reactHooks/feed/__tests__/useMarkPipesDataStale.test.tsx +243 -0
- package/reactHooks/feed/useMarkPipesDataStale.ts +68 -23
- package/reactHooks/navigation/__tests__/index.test.tsx +176 -1
- package/reactHooks/navigation/__tests__/usePresentScreen.test.ts +154 -0
- package/reactHooks/navigation/index.ts +2 -0
- package/reactHooks/navigation/usePresentScreen.ts +157 -0
- package/reactHooks/navigation/useRoute.ts +22 -5
- package/reactHooks/usePluginConfiguration.ts +84 -46
- package/riverComponetsMeasurementProvider/index.tsx +8 -1
- package/uiActionsRegistrator/__tests__/subscribe.test.ts +26 -0
- package/uiActionsRegistrator/registry.ts +16 -0
package/perfTracker.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createLogger } from "./logger";
|
|
2
|
+
import { NativeModules } from "react-native";
|
|
3
|
+
|
|
4
|
+
const { ProfilerBridge } = NativeModules;
|
|
5
|
+
|
|
6
|
+
const loggerObj = createLogger({
|
|
7
|
+
category: "PerfTracker",
|
|
8
|
+
subsystem: "PerfTelemetry",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const renderCounts: Record<string, number> = {};
|
|
12
|
+
const cellRenderCounts: Record<string, number> = {};
|
|
13
|
+
|
|
14
|
+
const timingStats: Record<
|
|
15
|
+
string,
|
|
16
|
+
{ totalMs: number; count: number; maxMs: number; minMs: number }
|
|
17
|
+
> = {};
|
|
18
|
+
|
|
19
|
+
const t0 = Date.now();
|
|
20
|
+
|
|
21
|
+
/** True only where the native profiler is registered; lets always-on logs skip the perf lines without it. */
|
|
22
|
+
export const isProfilerAvailable = (): boolean => Boolean(ProfilerBridge);
|
|
23
|
+
|
|
24
|
+
export function getDelta(): number {
|
|
25
|
+
return Date.now() - t0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function recordTiming(
|
|
29
|
+
name: string,
|
|
30
|
+
durationMs: number,
|
|
31
|
+
details?: Record<string, any>
|
|
32
|
+
): void {
|
|
33
|
+
const rounded = Math.round(durationMs * 100) / 100;
|
|
34
|
+
|
|
35
|
+
if (!timingStats[name]) {
|
|
36
|
+
timingStats[name] = {
|
|
37
|
+
totalMs: rounded,
|
|
38
|
+
count: 1,
|
|
39
|
+
maxMs: rounded,
|
|
40
|
+
minMs: rounded,
|
|
41
|
+
};
|
|
42
|
+
} else {
|
|
43
|
+
const s = timingStats[name];
|
|
44
|
+
s.totalMs = Math.round((s.totalMs + rounded) * 100) / 100;
|
|
45
|
+
s.count += 1;
|
|
46
|
+
if (rounded > s.maxMs) s.maxMs = rounded;
|
|
47
|
+
if (rounded < s.minMs) s.minMs = rounded;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const stat = timingStats[name];
|
|
51
|
+
const avg = Math.round((stat.totalMs / stat.count) * 100) / 100;
|
|
52
|
+
const delta = getDelta();
|
|
53
|
+
|
|
54
|
+
if (ProfilerBridge?.updateTimer) {
|
|
55
|
+
try {
|
|
56
|
+
ProfilerBridge.updateTimer("timings", name, rounded);
|
|
57
|
+
} catch (e) {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (stat.count <= 3 || rounded >= 3 || stat.count % 10 === 0) {
|
|
61
|
+
const msg = `[PERF TIMING #${stat.count}] ${name}: ${rounded}ms (avg: ${avg}ms, max: ${stat.maxMs}ms, total: ${stat.totalMs}ms) (+${delta}ms)`;
|
|
62
|
+
|
|
63
|
+
loggerObj.log_info(msg, {
|
|
64
|
+
timingName: name,
|
|
65
|
+
durationMs: rounded,
|
|
66
|
+
avgMs: avg,
|
|
67
|
+
maxMs: stat.maxMs,
|
|
68
|
+
minMs: stat.minMs,
|
|
69
|
+
totalMs: stat.totalMs,
|
|
70
|
+
count: stat.count,
|
|
71
|
+
deltaMs: delta,
|
|
72
|
+
...(details || {}),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function measureSync<T>(
|
|
78
|
+
name: string,
|
|
79
|
+
fn: () => T,
|
|
80
|
+
details?: Record<string, any>
|
|
81
|
+
): T {
|
|
82
|
+
const start =
|
|
83
|
+
typeof performance !== "undefined" && performance.now
|
|
84
|
+
? performance.now()
|
|
85
|
+
: Date.now();
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
return fn();
|
|
89
|
+
} finally {
|
|
90
|
+
const end =
|
|
91
|
+
typeof performance !== "undefined" && performance.now
|
|
92
|
+
? performance.now()
|
|
93
|
+
: Date.now();
|
|
94
|
+
|
|
95
|
+
recordTiming(name, end - start, details);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function startTimer(
|
|
100
|
+
name: string,
|
|
101
|
+
details?: Record<string, any>
|
|
102
|
+
): (additionalDetails?: Record<string, any>) => number {
|
|
103
|
+
const start =
|
|
104
|
+
typeof performance !== "undefined" && performance.now
|
|
105
|
+
? performance.now()
|
|
106
|
+
: Date.now();
|
|
107
|
+
|
|
108
|
+
return (additionalDetails?: Record<string, any>) => {
|
|
109
|
+
const end =
|
|
110
|
+
typeof performance !== "undefined" && performance.now
|
|
111
|
+
? performance.now()
|
|
112
|
+
: Date.now();
|
|
113
|
+
|
|
114
|
+
const duration = end - start;
|
|
115
|
+
|
|
116
|
+
recordTiming(name, duration, {
|
|
117
|
+
...(details || {}),
|
|
118
|
+
...(additionalDetails || {}),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return duration;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function countRender(
|
|
126
|
+
componentName: string,
|
|
127
|
+
details?: Record<string, any>
|
|
128
|
+
): number {
|
|
129
|
+
renderCounts[componentName] = (renderCounts[componentName] || 0) + 1;
|
|
130
|
+
const count = renderCounts[componentName];
|
|
131
|
+
const delta = getDelta();
|
|
132
|
+
|
|
133
|
+
if (ProfilerBridge?.updateCounter) {
|
|
134
|
+
try {
|
|
135
|
+
ProfilerBridge.updateCounter("render_count", componentName, count);
|
|
136
|
+
} catch (e) {}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const msg = `[PERF RENDER #${count}] ${componentName} (+${delta}ms)`;
|
|
140
|
+
|
|
141
|
+
loggerObj.log_info(msg, {
|
|
142
|
+
component: componentName,
|
|
143
|
+
count,
|
|
144
|
+
deltaMs: delta,
|
|
145
|
+
...(details || {}),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return count;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function countCellRender(
|
|
152
|
+
cellType: string,
|
|
153
|
+
itemId: string | number,
|
|
154
|
+
details?: Record<string, any>
|
|
155
|
+
): number {
|
|
156
|
+
const key = `${cellType}:${itemId || "unknown"}`;
|
|
157
|
+
cellRenderCounts[key] = (cellRenderCounts[key] || 0) + 1;
|
|
158
|
+
const count = cellRenderCounts[key];
|
|
159
|
+
|
|
160
|
+
renderCounts[cellType] = (renderCounts[cellType] || 0) + 1;
|
|
161
|
+
const totalForType = renderCounts[cellType];
|
|
162
|
+
const delta = getDelta();
|
|
163
|
+
|
|
164
|
+
if (ProfilerBridge?.updateCounter) {
|
|
165
|
+
try {
|
|
166
|
+
ProfilerBridge.updateCounter("cell_render_count", cellType, totalForType);
|
|
167
|
+
} catch (e) {}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (count > 1 || totalForType <= 12 || totalForType % 10 === 0) {
|
|
171
|
+
const msg = `[PERF CELL ${
|
|
172
|
+
count > 1 ? "RE-RENDER" : "RENDER"
|
|
173
|
+
} #${count}] ${cellType} item:${itemId} (total ${cellType}: #${totalForType}) (+${delta}ms)`;
|
|
174
|
+
|
|
175
|
+
loggerObj.log_info(msg, {
|
|
176
|
+
cellType,
|
|
177
|
+
itemId,
|
|
178
|
+
itemRenderCount: count,
|
|
179
|
+
totalForCellType: totalForType,
|
|
180
|
+
deltaMs: delta,
|
|
181
|
+
...(details || {}),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return count;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function dumpSummary(checkpoint: string): void {
|
|
189
|
+
const delta = getDelta();
|
|
190
|
+
let totalRenders = 0;
|
|
191
|
+
|
|
192
|
+
for (const k in renderCounts) {
|
|
193
|
+
totalRenders += renderCounts[k];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const summaryMsg = `=== [PERF SUMMARY at ${checkpoint}] total renders: ${totalRenders} (+${delta}ms) ===`;
|
|
197
|
+
|
|
198
|
+
loggerObj.log_info(summaryMsg, {
|
|
199
|
+
checkpoint,
|
|
200
|
+
elapsedMs: delta,
|
|
201
|
+
totalRenders,
|
|
202
|
+
renderCounts,
|
|
203
|
+
cellRenderCounts,
|
|
204
|
+
timingStats,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
const mockAppStoreGet = jest.fn();
|
|
2
|
+
|
|
3
|
+
jest.mock("@applicaster/zapp-react-native-redux/AppStore", () => ({
|
|
4
|
+
appStore: { get: (key) => mockAppStoreGet(key) },
|
|
5
|
+
}));
|
|
6
|
+
|
|
1
7
|
import * as R from "ramda";
|
|
2
8
|
import * as pluginUtils from "../index";
|
|
3
9
|
|
|
@@ -148,6 +154,32 @@ describe("findPluginByIdentifier", () => {
|
|
|
148
154
|
});
|
|
149
155
|
});
|
|
150
156
|
|
|
157
|
+
describe("getPluginByIdentifier", () => {
|
|
158
|
+
const { getPluginByIdentifier } = pluginUtils;
|
|
159
|
+
|
|
160
|
+
const plugins = {
|
|
161
|
+
foo: pluginMock("foo", "foo", "some_plugin_type"),
|
|
162
|
+
bar: pluginMock("foo", "bar", "some_plugin_type"),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
beforeEach(() => {
|
|
166
|
+
mockAppStoreGet.mockReset();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("reads plugins from appStore and returns the matching plugin", () => {
|
|
170
|
+
mockAppStoreGet.mockReturnValue(plugins);
|
|
171
|
+
|
|
172
|
+
expect(getPluginByIdentifier("foo")).toEqual(plugins.foo);
|
|
173
|
+
expect(mockAppStoreGet).toHaveBeenCalledWith("plugins");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("returns undefined when the plugin is not found", () => {
|
|
177
|
+
mockAppStoreGet.mockReturnValue(plugins);
|
|
178
|
+
|
|
179
|
+
expect(getPluginByIdentifier("do_not_exist")).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
151
183
|
describe("findPluginByType", () => {
|
|
152
184
|
const { findPluginByType } = pluginUtils;
|
|
153
185
|
|
package/pluginUtils/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
+
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
2
3
|
import { toCamelCase, toPascalCase } from "../stringUtils";
|
|
3
4
|
|
|
4
5
|
import { getNavigationType } from "../navigationUtils";
|
|
@@ -97,6 +98,34 @@ export function findPluginByIdentifier(
|
|
|
97
98
|
return plugin;
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Like findPluginByIdentifier, but reads the current plugins list from appStore.
|
|
103
|
+
* Convenience for non-render / hook code that doesn't already have `plugins` in hand.
|
|
104
|
+
* @param identifier of the plugin to look for
|
|
105
|
+
* @param doNotWarn if set to true, will not log a warning if plugin is not found
|
|
106
|
+
* @return whole plugin if found, undefined otherwise
|
|
107
|
+
*/
|
|
108
|
+
export function getPluginByIdentifier(
|
|
109
|
+
identifier: string,
|
|
110
|
+
doNotWarn: boolean = false
|
|
111
|
+
): (QuickBrickPlugin | ZappPlugin | Plugin) | undefined {
|
|
112
|
+
try {
|
|
113
|
+
const plugins = appStore.get("plugins") ?? {};
|
|
114
|
+
|
|
115
|
+
return findPluginByIdentifier(identifier, plugins, doNotWarn);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (!doNotWarn) {
|
|
118
|
+
pluginUtilsLogger.warning({
|
|
119
|
+
message:
|
|
120
|
+
"Could not read plugins from appStore while resolving plugin by identifier",
|
|
121
|
+
data: { identifier, error },
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
100
129
|
const pluginTypeForItemTypeMap = {
|
|
101
130
|
playable: "player",
|
|
102
131
|
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { renderHook } from "@testing-library/react-native";
|
|
2
|
+
|
|
3
|
+
const plugin = {
|
|
4
|
+
identifier: "cast",
|
|
5
|
+
configuration: {
|
|
6
|
+
title: "Configured",
|
|
7
|
+
localizations: { fr: { close: "Fermer" } },
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
|
|
12
|
+
usePlugins: () => [plugin],
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
jest.mock(
|
|
16
|
+
"@applicaster/zapp-react-native-utils/appUtils/localizationsHelper",
|
|
17
|
+
() => ({ getUILanguage: () => "fr" })
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
getPluginConfiguration,
|
|
22
|
+
mergeLocalizations,
|
|
23
|
+
usePluginConfiguration,
|
|
24
|
+
} from "../usePluginConfiguration";
|
|
25
|
+
|
|
26
|
+
const manifest = {
|
|
27
|
+
custom_configuration_fields: [
|
|
28
|
+
{ key: "title", initial_value: "Default" },
|
|
29
|
+
{ key: "color", initial_value: "red" },
|
|
30
|
+
],
|
|
31
|
+
localizations: {
|
|
32
|
+
fields: [
|
|
33
|
+
{ key: "close", initial_value: "Close" },
|
|
34
|
+
{ key: "cancel", initial_value: "Cancel" },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
it("matches usePluginConfiguration outside React", () => {
|
|
40
|
+
const { result } = renderHook(() => usePluginConfiguration("cast", manifest));
|
|
41
|
+
|
|
42
|
+
expect(getPluginConfiguration(plugin, manifest)).toEqual(result.current);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Expected values are what R.mergeDeepWith((l, r) => (isNil(l) ? r : l)) returned before ramda was removed
|
|
46
|
+
describe("mergeLocalizations", () => {
|
|
47
|
+
const defaults = { en: { close: "Close", cancel: "Cancel" } };
|
|
48
|
+
|
|
49
|
+
it("returns the defaults when nothing is configured", () => {
|
|
50
|
+
expect(mergeLocalizations(undefined, defaults)).toEqual(defaults);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("keeps configured values, fills nil ones and missing keys from defaults", () => {
|
|
54
|
+
expect(
|
|
55
|
+
mergeLocalizations(
|
|
56
|
+
{ en: { close: null, extra: "X" }, fr: { close: "Fermer" } },
|
|
57
|
+
defaults
|
|
58
|
+
)
|
|
59
|
+
).toEqual({
|
|
60
|
+
en: { close: "Close", extra: "X", cancel: "Cancel" },
|
|
61
|
+
fr: { close: "Fermer" },
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("does not merge arrays", () => {
|
|
66
|
+
expect(
|
|
67
|
+
mergeLocalizations(
|
|
68
|
+
{ en: { close: ["a"] } },
|
|
69
|
+
{ en: { close: ["b"], cancel: "C" } }
|
|
70
|
+
)
|
|
71
|
+
).toEqual({ en: { close: ["a"], cancel: "C" } });
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("keeps configured languages first, so the fallback language stays the configured one", () => {
|
|
76
|
+
const merged = mergeLocalizations(
|
|
77
|
+
{ he: { close: "סגור" }, en: { close: "Close it" } },
|
|
78
|
+
{ en: { close: "Close" } }
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(Object.keys(merged)).toEqual(["he", "en"]);
|
|
82
|
+
});
|
|
@@ -5,6 +5,7 @@ import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/acti
|
|
|
5
5
|
|
|
6
6
|
const mockNavigator = {
|
|
7
7
|
getPathname: () => "pathnameMock",
|
|
8
|
+
currentRoute: "pathnameMock",
|
|
8
9
|
push: jest.fn(),
|
|
9
10
|
data: {},
|
|
10
11
|
};
|
|
@@ -29,6 +30,7 @@ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
|
|
|
29
30
|
),
|
|
30
31
|
useTargetScreenData: jest.fn(() => ({})),
|
|
31
32
|
useCurrentScreenData: jest.fn(() => ({})),
|
|
33
|
+
useScreenContext: jest.fn(() => ({})),
|
|
32
34
|
}));
|
|
33
35
|
|
|
34
36
|
jest.mock("@applicaster/zapp-react-native-ui-components/Contexts", () => ({
|
|
@@ -66,6 +68,12 @@ const wrapper = ({ children }) => (
|
|
|
66
68
|
</CellTapContext.Provider>
|
|
67
69
|
);
|
|
68
70
|
|
|
71
|
+
const actionExecutorWrapper = ({ children }) => (
|
|
72
|
+
<ActionExecutorContext.Provider value={actionExecutor}>
|
|
73
|
+
{children}
|
|
74
|
+
</ActionExecutorContext.Provider>
|
|
75
|
+
);
|
|
76
|
+
|
|
69
77
|
describe("useCellClick", () => {
|
|
70
78
|
it("returns a function", () => {
|
|
71
79
|
const { result } = renderHook(() => useCellClick(mockProps));
|
|
@@ -95,4 +103,60 @@ describe("useCellClick", () => {
|
|
|
95
103
|
|
|
96
104
|
cleanup();
|
|
97
105
|
});
|
|
106
|
+
|
|
107
|
+
it("does not push when the cell finishes a hook", async () => {
|
|
108
|
+
mockNavigator.push.mockClear();
|
|
109
|
+
|
|
110
|
+
const item = {
|
|
111
|
+
id: "profile-1",
|
|
112
|
+
type: { value: "feed" },
|
|
113
|
+
extensions: {
|
|
114
|
+
tap_actions: {
|
|
115
|
+
actions: [
|
|
116
|
+
{ type: "sessionStorageSet" },
|
|
117
|
+
{ type: "finishHook", options: { success: true } },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const { result } = renderHook(() => useCellClick({ item }), {
|
|
124
|
+
wrapper: actionExecutorWrapper,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await result.current();
|
|
128
|
+
|
|
129
|
+
await waitFor(() => {
|
|
130
|
+
expect(actionExecutor.handleEntryActions).toHaveBeenCalled();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(mockNavigator.push).not.toHaveBeenCalled();
|
|
134
|
+
cleanup();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("pushes after tap actions that do not finish a hook", async () => {
|
|
138
|
+
mockNavigator.push.mockClear();
|
|
139
|
+
|
|
140
|
+
const item = {
|
|
141
|
+
id: "episode-1",
|
|
142
|
+
type: { value: "feed" },
|
|
143
|
+
extensions: {
|
|
144
|
+
tap_actions: {
|
|
145
|
+
actions: [{ type: "sessionStorageSet" }],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const { result } = renderHook(() => useCellClick({ item }), {
|
|
151
|
+
wrapper: actionExecutorWrapper,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await result.current();
|
|
155
|
+
|
|
156
|
+
await waitFor(() => {
|
|
157
|
+
expect(mockNavigator.push).toHaveBeenCalled();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
cleanup();
|
|
161
|
+
});
|
|
98
162
|
});
|
|
@@ -83,6 +83,12 @@ export const useCellClick = ({
|
|
|
83
83
|
|
|
84
84
|
logOnPress(selectedItem, pathname, component, onCellTap);
|
|
85
85
|
|
|
86
|
+
const tapActions = selectedItem?.extensions?.tap_actions?.actions;
|
|
87
|
+
|
|
88
|
+
const finishesHook =
|
|
89
|
+
Array.isArray(tapActions) &&
|
|
90
|
+
tapActions.some((action) => action?.type === "finishHook");
|
|
91
|
+
|
|
86
92
|
if (selectedItem) {
|
|
87
93
|
await actionExecutor?.handleEntryActions(selectedItem, {
|
|
88
94
|
component,
|
|
@@ -101,20 +107,21 @@ export const useCellClick = ({
|
|
|
101
107
|
url: selectedItem.link.href,
|
|
102
108
|
});
|
|
103
109
|
}
|
|
104
|
-
} else {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
} else if (isFunction(onCellTap)) {
|
|
111
|
+
onCellTap?.(selectedItem, _index);
|
|
112
|
+
} else if (!finishesHook) {
|
|
113
|
+
// finishHook already completes the hook and navigates to the original
|
|
114
|
+
// target. Pushing the cell as well stacks a second copy of that screen
|
|
115
|
+
// (e.g. home/home after a profile selector hook).
|
|
116
|
+
if (currentRoute === pathname || pathname.includes("video-modal")) {
|
|
117
|
+
const targetScreen = component?.data?.target;
|
|
118
|
+
|
|
119
|
+
push(
|
|
120
|
+
R.when(
|
|
121
|
+
() => targetScreen,
|
|
122
|
+
R.mergeLeft({ screen_type: targetScreen })
|
|
123
|
+
)(selectedItem)
|
|
124
|
+
);
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
127
|
},
|
|
@@ -4,6 +4,11 @@ import { NetInfoState } from "@react-native-community/netinfo";
|
|
|
4
4
|
import { NetworkStatusContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NetworkStatusContext";
|
|
5
5
|
import { isOnline } from "@applicaster/quick-brick-core/App/NetworkStatusProvider/utils";
|
|
6
6
|
|
|
7
|
+
type ConnectionInfo = NetInfoState & {
|
|
8
|
+
deviceStatus: NetStatus;
|
|
9
|
+
stillOfflineAfterResumeCount?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
7
12
|
/**
|
|
8
13
|
* This hook returns connection type from RN NetInfo component or boolean for iConnected if justOnlineStatus flag is set
|
|
9
14
|
* @param {boolean} justOnlineStatus - flag if to return boolean for isConnected
|
|
@@ -12,16 +17,14 @@ import { isOnline } from "@applicaster/quick-brick-core/App/NetworkStatusProvide
|
|
|
12
17
|
|
|
13
18
|
export function useConnectionInfo(justOnlineStatus: true): boolean;
|
|
14
19
|
|
|
15
|
-
export function useConnectionInfo(
|
|
16
|
-
justOnlineStatus: false
|
|
17
|
-
): NetInfoState & { deviceStatus: NetStatus };
|
|
20
|
+
export function useConnectionInfo(justOnlineStatus: false): ConnectionInfo;
|
|
18
21
|
|
|
19
22
|
export function useConnectionInfo(
|
|
20
23
|
justOnlineStatus: boolean
|
|
21
|
-
): boolean |
|
|
24
|
+
): boolean | ConnectionInfo {
|
|
22
25
|
const connectionInfo = React.useContext(
|
|
23
26
|
NetworkStatusContext
|
|
24
|
-
) as
|
|
27
|
+
) as ConnectionInfo;
|
|
25
28
|
|
|
26
29
|
return justOnlineStatus ? isOnline(connectionInfo) === true : connectionInfo;
|
|
27
30
|
}
|