@applicaster/zapp-react-native-utils 16.0.0-alpha.1593888345 → 16.0.0-alpha.1733304298
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 +3 -0
- package/actionsExecutor/__tests__/ActionExecutor.test.ts +71 -0
- package/actionsExecutor/__tests__/StorageActions.test.ts +98 -0
- package/actionsExecutor/__tests__/feedDecorator.test.ts +30 -0
- package/actionsExecutor/__tests__/screenToggleFlag.test.ts +90 -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 +160 -0
- package/actionsExecutor/actions/__tests__/showAlert.test.ts +49 -0
- package/actionsExecutor/actions/__tests__/showToast.test.ts +2 -2
- package/actionsExecutor/actions/index.ts +2 -0
- package/actionsExecutor/actions/navigateToScreen.ts +29 -45
- package/actionsExecutor/actions/openBottomSheet.ts +21 -6
- package/actionsExecutor/actions/screenToggleFlag.ts +1 -3
- package/actionsExecutor/actions/showAlert.ts +51 -0
- package/actionsExecutor/actions/showToast.ts +2 -2
- package/actionsExecutor/consts.ts +1 -0
- package/actionsExecutor/feedDecorator.ts +6 -0
- package/analyticsUtils/__tests__/playerAnalyticsSafeguard.test.ts +62 -0
- package/analyticsUtils/playerAnalyticsTracker.ts +1 -0
- package/appUtils/contextKeysManager/__tests__/getKey/failure.test.ts +1 -22
- package/appUtils/contextKeysManager/__tests__/getKey/success/fullSearch.test.ts +1 -25
- package/appUtils/contextKeysManager/__tests__/getKey/success/searchInSecure.test.ts +1 -7
- package/appUtils/contextKeysManager/__tests__/removeKey/failure.test.ts +0 -25
- package/appUtils/contextKeysManager/__tests__/removeKey/success.test.ts +0 -6
- package/appUtils/contextKeysManager/__tests__/setKey/failure/failedToSaveInStorage.test.ts +0 -40
- package/appUtils/contextKeysManager/__tests__/setKey/{success/saveItemAndReference.test.ts → success.test.ts} +2 -42
- package/appUtils/contextKeysManager/index.ts +28 -129
- 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__/playerDiagnostics.test.ts +62 -0
- 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 +281 -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 +25 -0
- package/appUtils/playerManager/playerNative.ts +22 -0
- package/appUtils/playerManager/playerNativeCommand.ts +3 -6
- package/appUtils/playerManager/playerToolbox.ts +196 -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 +40 -0
- package/configurationUtils/__tests__/modalBlocksParser.test.ts +189 -46
- package/configurationUtils/modalBlocksParser.ts +57 -101
- 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/defaultManifestConfigurations/player.js +8 -0
- package/manifestUtils/keys.js +7 -2
- 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 +2 -0
- package/modalState/RemoteEditableCollection.ts +9 -1
- package/modalState/__tests__/ModalOrchestrator.phase3.test.ts +39 -0
- package/modalState/__tests__/RemoteEditableCollection.test.ts +90 -1
- package/modalState/__tests__/useModalStoreState.test.ts +142 -0
- package/modalState/index.ts +2 -0
- package/modalState/store.ts +9 -2
- package/modalState/types.ts +14 -0
- package/modalState/useBottomSheetContent.ts +17 -12
- package/navigationUtils/xrayNavigator.ts +12 -0
- package/package.json +3 -2
- package/pluginUtils/__tests__/pluginUtils.test.js +32 -0
- package/pluginUtils/index.ts +29 -0
- package/quickBrickAssets/__tests__/index.test.ts +18 -0
- package/quickBrickAssets/index.ts +35 -0
- package/reactHooks/__tests__/getPluginConfiguration.test.ts +82 -0
- package/reactHooks/actions/index.ts +29 -23
- 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__/feedInvalidation.test.tsx +338 -0
- package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +8 -2
- package/reactHooks/feed/__tests__/useMarkPipesDataStale.test.tsx +243 -0
- package/reactHooks/feed/useFeedLoader.tsx +28 -1
- package/reactHooks/feed/useLoadPipesDataDispatch.ts +12 -11
- package/reactHooks/feed/useMarkPipesDataStale.ts +68 -23
- package/reactHooks/navigation/__tests__/index.test.tsx +128 -1
- package/reactHooks/navigation/useRoute.ts +8 -4
- package/reactHooks/usePluginConfiguration.ts +84 -46
- package/riverComponetsMeasurementProvider/index.tsx +3 -1
- package/uiActionsRegistrator/__tests__/subscribe.test.ts +26 -0
- package/uiActionsRegistrator/registry.ts +16 -0
- package/appUtils/contextKeysManager/__tests__/getKey/success/byReference.test.ts +0 -56
- package/appUtils/contextKeysManager/__tests__/getKeyByReference/failure.test.ts +0 -265
- package/appUtils/contextKeysManager/__tests__/getKeyByReference/success.test.ts +0 -303
- package/appUtils/contextKeysManager/__tests__/removeReferenceForKey/failure.test.ts +0 -82
- package/appUtils/contextKeysManager/__tests__/removeReferenceForKey/success.test.ts +0 -41
- package/appUtils/contextKeysManager/__tests__/setKey/success/saveItemButNotReference.test.ts +0 -208
- package/appUtils/contextKeysManager/__tests__/setReferenceForKey/failure.test.ts +0 -93
- package/appUtils/contextKeysManager/__tests__/setReferenceForKey/success.test.ts +0 -45
|
@@ -2,6 +2,22 @@ import { openBottomSheetAction } from "../openBottomSheet";
|
|
|
2
2
|
import { actionExecutor, ActionResult } from "../../ActionExecutor";
|
|
3
3
|
import { modalOrchestrator } from "../../../modalState/ModalOrchestrator";
|
|
4
4
|
|
|
5
|
+
jest.mock("@applicaster/zapp-pipes-v2-client", () => ({
|
|
6
|
+
PipesClientResponseHelper: jest.fn(() => ({ error: null })),
|
|
7
|
+
RequestBuilder: jest.fn(() => ({
|
|
8
|
+
setEntryContext: jest.fn().mockReturnThis(),
|
|
9
|
+
setScreenContext: jest.fn().mockReturnThis(),
|
|
10
|
+
setUrl: jest.fn().mockReturnThis(),
|
|
11
|
+
buildAxiosRequest: jest.fn().mockResolvedValue(undefined),
|
|
12
|
+
call: jest.fn().mockResolvedValue({
|
|
13
|
+
response: {
|
|
14
|
+
entry: [],
|
|
15
|
+
extensions: { dynamic_collection_options: { operations: "add" } },
|
|
16
|
+
},
|
|
17
|
+
}),
|
|
18
|
+
})),
|
|
19
|
+
}));
|
|
20
|
+
|
|
5
21
|
jest.mock("../../../modalState/store", () => ({
|
|
6
22
|
openBottomSheetModal: jest.fn(),
|
|
7
23
|
}));
|
|
@@ -77,6 +93,150 @@ describe("openBottomSheetAction inline items", () => {
|
|
|
77
93
|
});
|
|
78
94
|
});
|
|
79
95
|
|
|
96
|
+
it("opens an empty inline collection when dynamic collection options are present", async () => {
|
|
97
|
+
const result = await openBottomSheetAction({
|
|
98
|
+
type: "openBottomSheet",
|
|
99
|
+
options: {
|
|
100
|
+
header: { title: "Queue" },
|
|
101
|
+
content: {
|
|
102
|
+
items: [],
|
|
103
|
+
emptyMessage: "Queued items will be added here",
|
|
104
|
+
dynamicCollection: {
|
|
105
|
+
operations: ["remove"],
|
|
106
|
+
provider: "@{provider/queue}",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
} as any);
|
|
111
|
+
|
|
112
|
+
expect(result).toBe(ActionResult.Success);
|
|
113
|
+
|
|
114
|
+
const [menu] = (modalOrchestrator.open as jest.Mock).mock.calls[0];
|
|
115
|
+
|
|
116
|
+
expect(menu.content.items).toEqual([]);
|
|
117
|
+
expect(menu.content.emptyMessage).toBe("Queued items will be added here");
|
|
118
|
+
|
|
119
|
+
expect(menu.content.dynamicCollection).toMatchObject({
|
|
120
|
+
operations: ["remove"],
|
|
121
|
+
provider: "@{provider/queue}",
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("accepts empty inline items when dynamic_collection_options live on extensions", async () => {
|
|
126
|
+
const result = await openBottomSheetAction({
|
|
127
|
+
type: "openBottomSheet",
|
|
128
|
+
options: {
|
|
129
|
+
content: {
|
|
130
|
+
items: [],
|
|
131
|
+
extensions: {
|
|
132
|
+
dynamic_collection_options: {
|
|
133
|
+
operations: "remove,reorder",
|
|
134
|
+
provider: "@{provider/offline}",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
} as any);
|
|
140
|
+
|
|
141
|
+
expect(result).toBe(ActionResult.Success);
|
|
142
|
+
|
|
143
|
+
const [menu] = (modalOrchestrator.open as jest.Mock).mock.calls[0];
|
|
144
|
+
|
|
145
|
+
expect(menu.content.items).toEqual([]);
|
|
146
|
+
|
|
147
|
+
expect(menu.content.dynamicCollection).toMatchObject({
|
|
148
|
+
operations: ["remove", "reorder"],
|
|
149
|
+
provider: "@{provider/offline}",
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("returns an error for an empty items array with no source", async () => {
|
|
154
|
+
const result = await openBottomSheetAction({
|
|
155
|
+
type: "openBottomSheet",
|
|
156
|
+
options: {
|
|
157
|
+
content: { items: [] },
|
|
158
|
+
},
|
|
159
|
+
} as any);
|
|
160
|
+
|
|
161
|
+
expect(result).toBe(ActionResult.Error);
|
|
162
|
+
expect(modalOrchestrator.open).not.toHaveBeenCalled();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("forwards content.emptyMessage onto the menu", async () => {
|
|
166
|
+
const result = await openBottomSheetAction({
|
|
167
|
+
type: "openBottomSheet",
|
|
168
|
+
options: {
|
|
169
|
+
header: { title: "Queue" },
|
|
170
|
+
content: {
|
|
171
|
+
items: [{ id: "t1", title: "Track 1" }],
|
|
172
|
+
emptyMessage: "Queued items will be added here",
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
} as any);
|
|
176
|
+
|
|
177
|
+
expect(result).toBe(ActionResult.Success);
|
|
178
|
+
|
|
179
|
+
const [menu] = (modalOrchestrator.open as jest.Mock).mock.calls[0];
|
|
180
|
+
|
|
181
|
+
expect(menu.content.emptyMessage).toBe("Queued items will be added here");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("forwards content.operationButtons onto the menu", async () => {
|
|
185
|
+
const result = await openBottomSheetAction({
|
|
186
|
+
type: "openBottomSheet",
|
|
187
|
+
options: {
|
|
188
|
+
header: { title: "Playlists" },
|
|
189
|
+
content: {
|
|
190
|
+
items: [{ id: "p1", title: "Playlist 1" }],
|
|
191
|
+
operationButtons: {
|
|
192
|
+
add: {
|
|
193
|
+
title: "Create New Playlist",
|
|
194
|
+
iconURL: "https://example.com/add.png",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
} as any);
|
|
200
|
+
|
|
201
|
+
expect(result).toBe(ActionResult.Success);
|
|
202
|
+
|
|
203
|
+
const [menu] = (modalOrchestrator.open as jest.Mock).mock.calls[0];
|
|
204
|
+
|
|
205
|
+
expect(menu.content.operationButtons).toEqual({
|
|
206
|
+
add: {
|
|
207
|
+
title: "Create New Playlist",
|
|
208
|
+
iconURL: "https://example.com/add.png",
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("preserves Add presentation when loading itemsUrl content", async () => {
|
|
214
|
+
const result = await openBottomSheetAction({
|
|
215
|
+
type: "openBottomSheet",
|
|
216
|
+
options: {
|
|
217
|
+
content: {
|
|
218
|
+
itemsUrl: "https://example.com/collections",
|
|
219
|
+
operationButtons: {
|
|
220
|
+
add: {
|
|
221
|
+
title: "Create Collection",
|
|
222
|
+
iconURL: "file:///bundled-add.png",
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
expect(result).toBe(ActionResult.Success);
|
|
230
|
+
const [menu] = (modalOrchestrator.open as jest.Mock).mock.calls[0];
|
|
231
|
+
|
|
232
|
+
expect(menu.content).toMatchObject({
|
|
233
|
+
operationButtons: {
|
|
234
|
+
add: { title: "Create Collection", iconURL: "file:///bundled-add.png" },
|
|
235
|
+
},
|
|
236
|
+
dynamicCollection: { operations: ["add"] },
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
80
240
|
it("pushes a new sheet by default even when another sheet is already open", async () => {
|
|
81
241
|
(modalOrchestrator as any).stackSize = 1;
|
|
82
242
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { showAlertAction } from "../showAlert";
|
|
2
|
+
import { ActionResult } from "../../ActionExecutor";
|
|
3
|
+
import { showAlertDialog } from "../../../alertUtils";
|
|
4
|
+
|
|
5
|
+
jest.mock("../../../alertUtils", () => ({
|
|
6
|
+
showAlertDialog: jest.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
const dialog = showAlertDialog as jest.Mock;
|
|
10
|
+
|
|
11
|
+
const dismiss = () => dialog.mock.calls[0][0].completion();
|
|
12
|
+
|
|
13
|
+
describe("showAlert", () => {
|
|
14
|
+
beforeEach(() => dialog.mockReset());
|
|
15
|
+
|
|
16
|
+
it("shows the notice and resolves once it is dismissed", async () => {
|
|
17
|
+
const result = showAlertAction({
|
|
18
|
+
type: "showAlert",
|
|
19
|
+
options: { title: "Locked", message: "Ask the account owner." },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(dialog).toHaveBeenCalledWith(
|
|
23
|
+
expect.objectContaining({
|
|
24
|
+
title: "Locked",
|
|
25
|
+
message: "Ask the account owner.",
|
|
26
|
+
okButtonText: "OK",
|
|
27
|
+
})
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
dismiss();
|
|
31
|
+
|
|
32
|
+
// Dismissing a notice is not a cancellation: the chain carries on.
|
|
33
|
+
await expect(result).resolves.toBe(ActionResult.Success);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("takes the dismiss label from the action", async () => {
|
|
37
|
+
const result = showAlertAction({
|
|
38
|
+
type: "showAlert",
|
|
39
|
+
options: { title: "Locked", okButtonText: "Got it" },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(dialog).toHaveBeenCalledWith(
|
|
43
|
+
expect.objectContaining({ okButtonText: "Got it" })
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
dismiss();
|
|
47
|
+
await result;
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -31,7 +31,7 @@ describe("showToastAction", () => {
|
|
|
31
31
|
message: "Added to queue",
|
|
32
32
|
extraMessage: "Extra info",
|
|
33
33
|
style: undefined,
|
|
34
|
-
timeout:
|
|
34
|
+
timeout: 5000,
|
|
35
35
|
source: "confirmation",
|
|
36
36
|
},
|
|
37
37
|
]);
|
|
@@ -78,7 +78,7 @@ describe("showToastAction", () => {
|
|
|
78
78
|
message: undefined,
|
|
79
79
|
extraMessage: undefined,
|
|
80
80
|
style: undefined,
|
|
81
|
-
timeout:
|
|
81
|
+
timeout: 5000,
|
|
82
82
|
source: "confirmation",
|
|
83
83
|
},
|
|
84
84
|
]);
|
|
@@ -15,6 +15,8 @@ export { appRestartAction } from "./appRestart";
|
|
|
15
15
|
|
|
16
16
|
export { confirmDialogAction } from "./confirmDialog";
|
|
17
17
|
|
|
18
|
+
export { showAlertAction } from "./showAlert";
|
|
19
|
+
|
|
18
20
|
export { sendCloudEventAction } from "./sendCloudEvent";
|
|
19
21
|
|
|
20
22
|
export { sessionStorageToggleFlagAction } from "./sessionStorageToggleFlag";
|
|
@@ -13,7 +13,7 @@ const { log_info, log_error } = createLogger({
|
|
|
13
13
|
*/
|
|
14
14
|
export interface NavigateToScreenOptions {
|
|
15
15
|
/** The screen type to navigate to (from content types mapping) */
|
|
16
|
-
typeMapping
|
|
16
|
+
typeMapping?: string;
|
|
17
17
|
|
|
18
18
|
/** Navigation action: push adds to history, replace replaces current screen */
|
|
19
19
|
navigationAction?: "push" | "replace";
|
|
@@ -58,14 +58,6 @@ export function createNavigateToScreenAction(
|
|
|
58
58
|
action,
|
|
59
59
|
context?: ActionExecutionContext
|
|
60
60
|
): Promise<ActionResult> {
|
|
61
|
-
const screenType = action.options?.typeMapping;
|
|
62
|
-
|
|
63
|
-
if (!screenType) {
|
|
64
|
-
log_error("navigateToScreen: typeMapping option is missing");
|
|
65
|
-
|
|
66
|
-
return ActionResult.Error;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
61
|
const navigationAction = action.options?.navigationAction;
|
|
70
62
|
const entrySource = action.options?.entry;
|
|
71
63
|
const navigationOptions = action.options?.navigationOptions;
|
|
@@ -76,61 +68,53 @@ export function createNavigateToScreenAction(
|
|
|
76
68
|
: entrySource
|
|
77
69
|
: null;
|
|
78
70
|
|
|
79
|
-
|
|
80
|
-
if (typeof entry !== "object") {
|
|
81
|
-
log_error(
|
|
82
|
-
`navigateToScreen: entry option is not an object, entry: ${entry}`
|
|
83
|
-
);
|
|
71
|
+
const screenType = action.options?.typeMapping || entry?.type?.value;
|
|
84
72
|
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
const navigate =
|
|
74
|
+
navigationAction === "push" ? navigator.push : navigator.replace;
|
|
75
|
+
|
|
76
|
+
let navigatingEntry: ZappEntry | ZappRiver = entry;
|
|
87
77
|
|
|
78
|
+
if (entry && screenType) {
|
|
88
79
|
log_info(
|
|
89
80
|
`navigateToScreen: navigating to screen type: ${screenType} with entry id: ${entry.id}`
|
|
90
81
|
);
|
|
91
82
|
|
|
92
|
-
|
|
83
|
+
navigatingEntry = {
|
|
93
84
|
...entry,
|
|
94
|
-
type: {
|
|
95
|
-
value: screenType,
|
|
96
|
-
},
|
|
85
|
+
type: { value: screenType },
|
|
97
86
|
};
|
|
87
|
+
} else {
|
|
88
|
+
if (!screenType) {
|
|
89
|
+
log_error(
|
|
90
|
+
"navigateToScreen: options.typeMapping or entry?.type?.value is not provided"
|
|
91
|
+
);
|
|
98
92
|
|
|
99
|
-
|
|
100
|
-
navigator.push(overriddenEntry, navigationOptions);
|
|
101
|
-
} else {
|
|
102
|
-
// Use replaceTop for entries since replace() only accepts ZappRiver
|
|
103
|
-
navigator.replaceTop(overriddenEntry, navigationOptions);
|
|
93
|
+
return ActionResult.Error;
|
|
104
94
|
}
|
|
105
95
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const screenId = contentTypes?.[screenType]?.screen_id || null;
|
|
96
|
+
const screenId = contentTypes?.[screenType]?.screen_id || null;
|
|
97
|
+
const river = rivers?.[screenId];
|
|
110
98
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
99
|
+
if (!screenId) {
|
|
100
|
+
log_error(
|
|
101
|
+
`navigateToScreen: can not resolve screen type mapping: ${screenType}`
|
|
102
|
+
);
|
|
115
103
|
|
|
116
|
-
|
|
117
|
-
|
|
104
|
+
return ActionResult.Error;
|
|
105
|
+
}
|
|
118
106
|
|
|
119
|
-
|
|
107
|
+
if (!river) {
|
|
108
|
+
log_error("navigateToScreen: can not resolve river");
|
|
120
109
|
|
|
121
|
-
|
|
122
|
-
|
|
110
|
+
return ActionResult.Error;
|
|
111
|
+
}
|
|
123
112
|
|
|
124
|
-
|
|
113
|
+
navigatingEntry = river;
|
|
125
114
|
}
|
|
126
115
|
|
|
127
116
|
context?.callback?.({ success: false, error: null, abort: true });
|
|
128
|
-
|
|
129
|
-
if (navigationAction === "push") {
|
|
130
|
-
navigator.push(river, navigationOptions);
|
|
131
|
-
} else {
|
|
132
|
-
navigator.replace(river, navigationOptions);
|
|
133
|
-
}
|
|
117
|
+
navigate(navigatingEntry, navigationOptions);
|
|
134
118
|
|
|
135
119
|
return ActionResult.Success;
|
|
136
120
|
};
|
|
@@ -208,7 +208,11 @@ export async function openBottomSheetAction(
|
|
|
208
208
|
items: [],
|
|
209
209
|
onPress: () => {},
|
|
210
210
|
contentComponent: content.component,
|
|
211
|
-
contentComponentProps:
|
|
211
|
+
contentComponentProps: {
|
|
212
|
+
...(content.props || {}),
|
|
213
|
+
actionContext: context,
|
|
214
|
+
},
|
|
215
|
+
themePluginId,
|
|
212
216
|
},
|
|
213
217
|
});
|
|
214
218
|
|
|
@@ -224,7 +228,18 @@ export async function openBottomSheetAction(
|
|
|
224
228
|
|
|
225
229
|
let dynamicCollection: DynamicCollectionOptions | undefined;
|
|
226
230
|
|
|
227
|
-
|
|
231
|
+
const rawDynamicCollection =
|
|
232
|
+
content?.dynamicCollection ??
|
|
233
|
+
content?.extensions?.dynamic_collection_options;
|
|
234
|
+
|
|
235
|
+
// An empty items array is a valid starting state for a provider-backed or
|
|
236
|
+
// offline collection. A sheet with neither items nor a source still errors.
|
|
237
|
+
const hasDynamicCollection = Boolean(rawDynamicCollection);
|
|
238
|
+
|
|
239
|
+
if (
|
|
240
|
+
Array.isArray(inlineItems) &&
|
|
241
|
+
(inlineItems.length > 0 || hasDynamicCollection)
|
|
242
|
+
) {
|
|
228
243
|
log_info("openBottomSheet: using inline items from action options", {
|
|
229
244
|
count: inlineItems.length,
|
|
230
245
|
});
|
|
@@ -236,10 +251,7 @@ export async function openBottomSheetAction(
|
|
|
236
251
|
entryToMenuItem(entry, behavior?.currentSelection ?? [])
|
|
237
252
|
);
|
|
238
253
|
|
|
239
|
-
dynamicCollection = parseDynamicCollectionOptions(
|
|
240
|
-
content?.dynamicCollection ??
|
|
241
|
-
content?.extensions?.dynamic_collection_options
|
|
242
|
-
);
|
|
254
|
+
dynamicCollection = parseDynamicCollectionOptions(rawDynamicCollection);
|
|
243
255
|
} else if (itemsUrl) {
|
|
244
256
|
log_info(`openBottomSheet: loading items from source: ${itemsUrl}`);
|
|
245
257
|
|
|
@@ -277,6 +289,9 @@ export async function openBottomSheetAction(
|
|
|
277
289
|
dynamicCollection,
|
|
278
290
|
themePluginId,
|
|
279
291
|
styleOverrides: styleOverrides ?? content?.styleOverrides,
|
|
292
|
+
emptyMessage: content?.emptyMessage,
|
|
293
|
+
operationButtons: content?.operationButtons,
|
|
294
|
+
context,
|
|
280
295
|
},
|
|
281
296
|
};
|
|
282
297
|
|
|
@@ -27,12 +27,10 @@ export const screenToggleFlagAction: ActionHandler<
|
|
|
27
27
|
const screenRoute = context?.screenRoute;
|
|
28
28
|
const screenStateStore = context?.screenStateStore;
|
|
29
29
|
|
|
30
|
-
await screenToggleFlag(
|
|
30
|
+
return await screenToggleFlag(
|
|
31
31
|
screenRoute,
|
|
32
32
|
screenStateStore,
|
|
33
33
|
{ entry: context?.entry, options: action.options },
|
|
34
34
|
action
|
|
35
35
|
);
|
|
36
|
-
|
|
37
|
-
return ActionResult.Success;
|
|
38
36
|
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/// <reference types="@applicaster/applicaster-types" />
|
|
2
|
+
import { showAlertDialog } from "../../alertUtils";
|
|
3
|
+
import { ActionResult } from "../ActionExecutor";
|
|
4
|
+
import { ActionHandler } from "../types";
|
|
5
|
+
import { createLogger } from "../../logger";
|
|
6
|
+
|
|
7
|
+
const { log_info } = createLogger({
|
|
8
|
+
subsystem: "ActionExecutorContext",
|
|
9
|
+
category: "General",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Options for the showAlert action.
|
|
14
|
+
*/
|
|
15
|
+
export interface ShowAlertOptions {
|
|
16
|
+
/** Dialog title (required) */
|
|
17
|
+
title: string;
|
|
18
|
+
|
|
19
|
+
/** Dialog message */
|
|
20
|
+
message?: string;
|
|
21
|
+
|
|
22
|
+
/** Dismiss button text. Defaults to "OK" */
|
|
23
|
+
okButtonText?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Tells the user something and waits for them to acknowledge it.
|
|
28
|
+
*
|
|
29
|
+
* One button, because there is nothing to decide. Use `confirmDialog` when the
|
|
30
|
+
* user is choosing between going on and backing out; a `confirmDialog` without
|
|
31
|
+
* a cancel label still renders two buttons, the second one blank.
|
|
32
|
+
*
|
|
33
|
+
* Always resolves to Success: dismissing a notice is not a cancellation, so the
|
|
34
|
+
* rest of the chain runs.
|
|
35
|
+
*/
|
|
36
|
+
export const showAlertAction: ActionHandler<ShowAlertOptions> = async (
|
|
37
|
+
action
|
|
38
|
+
): Promise<ActionResult> => {
|
|
39
|
+
log_info("handleAction: showAlert event");
|
|
40
|
+
|
|
41
|
+
const options = action.options as ShowAlertOptions;
|
|
42
|
+
|
|
43
|
+
return new Promise<ActionResult>((resolve) => {
|
|
44
|
+
showAlertDialog({
|
|
45
|
+
title: options?.title,
|
|
46
|
+
message: options?.message,
|
|
47
|
+
okButtonText: options?.okButtonText || "OK",
|
|
48
|
+
completion: () => resolve(ActionResult.Success),
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
};
|
|
@@ -5,7 +5,7 @@ import { postEvent } from "../../reactHooks/useSubscriberFor";
|
|
|
5
5
|
|
|
6
6
|
export const CONFIRMATION_TOAST_SOURCE = "confirmation";
|
|
7
7
|
|
|
8
|
-
export const CONFIRMATION_TOAST_DEFAULT_TIMEOUT =
|
|
8
|
+
export const CONFIRMATION_TOAST_DEFAULT_TIMEOUT = 5000;
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Shows a mobile confirmation toast with Theme defaults from
|
|
@@ -13,7 +13,7 @@ export const CONFIRMATION_TOAST_DEFAULT_TIMEOUT = 1000;
|
|
|
13
13
|
*
|
|
14
14
|
* This action is confirmation-only by design: it always stamps
|
|
15
15
|
* `source: "confirmation"` (so Theme confirmation styles apply) and defaults
|
|
16
|
-
* `timeout` to
|
|
16
|
+
* `timeout` to 5000 ms. Non-confirmation toasts (e.g. Offline Experience)
|
|
17
17
|
* must post on the shared `"showToast"` event bus directly and omit `source`.
|
|
18
18
|
* Callers may override individual style fields; Theme supplies the rest.
|
|
19
19
|
*
|
|
@@ -47,6 +47,11 @@ function makeMultiSelect(feed: ZappFeed, key, decoratedFeed) {
|
|
|
47
47
|
? "sessionStorageToggleFlag"
|
|
48
48
|
: "localStorageToggleFlag";
|
|
49
49
|
|
|
50
|
+
// the toggle actions read their limit from their own options
|
|
51
|
+
const maxItems = Number(
|
|
52
|
+
decoratedFeed.extensions?.preference_editor_options?.max_items
|
|
53
|
+
);
|
|
54
|
+
|
|
50
55
|
decoratedFeed.entry?.forEach((entry: ZappEntry) => {
|
|
51
56
|
entry.type.value = "action";
|
|
52
57
|
entry.id = entry.extensions?.tag || entry.id;
|
|
@@ -59,6 +64,7 @@ function makeMultiSelect(feed: ZappFeed, key, decoratedFeed) {
|
|
|
59
64
|
type: actionType,
|
|
60
65
|
options: {
|
|
61
66
|
key,
|
|
67
|
+
...(maxItems > 0 && { max_items: maxItems }),
|
|
62
68
|
},
|
|
63
69
|
},
|
|
64
70
|
],
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { PlayerAnalyticsTracker } from "../playerAnalyticsTracker";
|
|
2
|
+
import { PLAYBACK_EVENT } from "../events";
|
|
3
|
+
|
|
4
|
+
const mockPostAnalyticEvent = jest.fn();
|
|
5
|
+
|
|
6
|
+
jest.mock("../manager", () => ({
|
|
7
|
+
postAnalyticEvent: (...args) => mockPostAnalyticEvent(...args),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
describe("Player Analytics Safeguards against nativeEvent leakage", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const mockEntry = {
|
|
16
|
+
id: "vod_123",
|
|
17
|
+
title: "Test Video",
|
|
18
|
+
type: { value: "video" },
|
|
19
|
+
content: { type: "video/mp4" },
|
|
20
|
+
extensions: { duration: 120 },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const mockPlayer = {
|
|
24
|
+
playerPluginId: "quick_brick_player",
|
|
25
|
+
getEntry: () => mockEntry,
|
|
26
|
+
getState: () => ({ isPaused: false, contentPosition: 0.63 }),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
it("omits nativeEvent from analytics payload if present in eventData", () => {
|
|
30
|
+
const tracker = new PlayerAnalyticsTracker();
|
|
31
|
+
tracker.setPlayer(mockPlayer as any);
|
|
32
|
+
|
|
33
|
+
const payload = tracker.prepareAnalyticPayload(PLAYBACK_EVENT.play, {
|
|
34
|
+
nativeEvent: { currentTime: 0.63, reason: "video_ready" },
|
|
35
|
+
player_type: "quick_brick_player",
|
|
36
|
+
} as any);
|
|
37
|
+
|
|
38
|
+
expect(payload.nativeEvent).toBeUndefined();
|
|
39
|
+
expect(payload.media_id).toBe("vod_123");
|
|
40
|
+
expect(payload.name).toBe("Test Video");
|
|
41
|
+
expect(payload.player_type).toBe("quick_brick_player");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("properly prepares payload when eventData is unwrapped", () => {
|
|
45
|
+
const tracker = new PlayerAnalyticsTracker();
|
|
46
|
+
tracker.setPlayer(mockPlayer as any);
|
|
47
|
+
|
|
48
|
+
const payload = tracker.prepareAnalyticPayload(PLAYBACK_EVENT.play, {
|
|
49
|
+
currentTime: 0.63,
|
|
50
|
+
reason: "video_ready",
|
|
51
|
+
player_type: "quick_brick_player",
|
|
52
|
+
} as any);
|
|
53
|
+
|
|
54
|
+
expect(payload.nativeEvent).toBeUndefined();
|
|
55
|
+
expect(payload.currentTime).toBeUndefined();
|
|
56
|
+
expect(payload.current_position).toBe(0.63);
|
|
57
|
+
expect(payload.reason).toBe("video_ready");
|
|
58
|
+
expect(payload.media_id).toBe("vod_123");
|
|
59
|
+
expect(payload.name).toBe("Test Video");
|
|
60
|
+
expect(payload.player_type).toBe("quick_brick_player");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContextKeysManager
|
|
1
|
+
import { ContextKeysManager } from "../..";
|
|
2
2
|
|
|
3
3
|
describe("Context Keys Manager - getKey", () => {
|
|
4
4
|
it("returns null if key is invalid", async () => {
|
|
@@ -34,8 +34,6 @@ describe("Context Keys Manager - getKey", () => {
|
|
|
34
34
|
secureStorage: mockedSecureStorage,
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
const getKeyByReference = jest.spyOn(contextManager, "getKeyByReference");
|
|
38
|
-
|
|
39
37
|
// run
|
|
40
38
|
const result = await contextManager.getKey(invalidKey);
|
|
41
39
|
|
|
@@ -50,7 +48,6 @@ describe("Context Keys Manager - getKey", () => {
|
|
|
50
48
|
data: { key: invalidKey },
|
|
51
49
|
});
|
|
52
50
|
|
|
53
|
-
expect(getKeyByReference).not.toHaveBeenCalled();
|
|
54
51
|
expect(mockedSessionStorage.getItem).not.toHaveBeenCalled();
|
|
55
52
|
expect(mockedLocalStorage.getItem).not.toHaveBeenCalled();
|
|
56
53
|
expect(mockedSecureStorage.getItem).not.toHaveBeenCalled();
|
|
@@ -90,10 +87,6 @@ describe("Context Keys Manager - getKey", () => {
|
|
|
90
87
|
secureStorage: mockedSecureStorage,
|
|
91
88
|
});
|
|
92
89
|
|
|
93
|
-
const getKeyByReference = jest
|
|
94
|
-
.spyOn(contextManager, "getKeyByReference")
|
|
95
|
-
.mockResolvedValueOnce(NOTHING);
|
|
96
|
-
|
|
97
90
|
// run
|
|
98
91
|
const result = await contextManager.getKey(key);
|
|
99
92
|
|
|
@@ -107,8 +100,6 @@ describe("Context Keys Manager - getKey", () => {
|
|
|
107
100
|
data: { key },
|
|
108
101
|
});
|
|
109
102
|
|
|
110
|
-
expect(getKeyByReference).toHaveBeenCalledWith("key", "namespace");
|
|
111
|
-
|
|
112
103
|
expect(mockedSessionStorage.getItem).toHaveBeenCalledWith(
|
|
113
104
|
"key",
|
|
114
105
|
"namespace"
|
|
@@ -152,10 +143,6 @@ describe("Context Keys Manager - getKey", () => {
|
|
|
152
143
|
secureStorage: mockedSecureStorage,
|
|
153
144
|
});
|
|
154
145
|
|
|
155
|
-
const getKeyByReference = jest
|
|
156
|
-
.spyOn(contextManager, "getKeyByReference")
|
|
157
|
-
.mockResolvedValueOnce(NOTHING);
|
|
158
|
-
|
|
159
146
|
// run
|
|
160
147
|
const result = await contextManager.getKey(key);
|
|
161
148
|
|
|
@@ -169,8 +156,6 @@ describe("Context Keys Manager - getKey", () => {
|
|
|
169
156
|
data: { key },
|
|
170
157
|
});
|
|
171
158
|
|
|
172
|
-
expect(getKeyByReference).toHaveBeenCalledWith("key", "namespace");
|
|
173
|
-
|
|
174
159
|
expect(mockedSessionStorage.getItem).toHaveBeenCalledWith(
|
|
175
160
|
"key",
|
|
176
161
|
"namespace"
|
|
@@ -214,10 +199,6 @@ describe("Context Keys Manager - getKey", () => {
|
|
|
214
199
|
secureStorage: mockedSecureStorage,
|
|
215
200
|
});
|
|
216
201
|
|
|
217
|
-
const getKeyByReference = jest
|
|
218
|
-
.spyOn(contextManager, "getKeyByReference")
|
|
219
|
-
.mockResolvedValueOnce(NOTHING);
|
|
220
|
-
|
|
221
202
|
// run
|
|
222
203
|
const result = await contextManager.getKey(key);
|
|
223
204
|
|
|
@@ -231,8 +212,6 @@ describe("Context Keys Manager - getKey", () => {
|
|
|
231
212
|
data: { key },
|
|
232
213
|
});
|
|
233
214
|
|
|
234
|
-
expect(getKeyByReference).toHaveBeenCalledWith("key", "namespace");
|
|
235
|
-
|
|
236
215
|
expect(mockedSessionStorage.getItem).toHaveBeenCalledWith(
|
|
237
216
|
"key",
|
|
238
217
|
"namespace"
|