@applicaster/zapp-react-native-utils 16.0.0-alpha.1263110541 → 16.0.0-alpha.1314750780

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 (120) hide show
  1. package/actionUtils/index.ts +3 -1
  2. package/actionsExecutor/ActionExecutor.ts +10 -1
  3. package/actionsExecutor/ActionExecutorContext.tsx +13 -1
  4. package/actionsExecutor/__tests__/ActionExecutor.test.ts +71 -0
  5. package/actionsExecutor/__tests__/StorageActions.test.ts +98 -0
  6. package/actionsExecutor/__tests__/feedDecorator.test.ts +30 -0
  7. package/actionsExecutor/__tests__/screenToggleFlag.test.ts +90 -0
  8. package/actionsExecutor/actions/__tests__/dismissBottomSheet.test.ts +3 -0
  9. package/actionsExecutor/actions/__tests__/navigateToScreen.test.ts +186 -5
  10. package/actionsExecutor/actions/__tests__/openBottomSheet.customContent.test.ts +24 -0
  11. package/actionsExecutor/actions/__tests__/openBottomSheet.inlineItems.test.ts +273 -1
  12. package/actionsExecutor/actions/__tests__/presentScreen.test.ts +127 -0
  13. package/actionsExecutor/actions/__tests__/showAlert.test.ts +49 -0
  14. package/actionsExecutor/actions/__tests__/showToast.test.ts +2 -2
  15. package/actionsExecutor/actions/dismissBottomSheet.ts +2 -1
  16. package/actionsExecutor/actions/index.ts +4 -0
  17. package/actionsExecutor/actions/navigateToScreen.ts +33 -43
  18. package/actionsExecutor/actions/openBottomSheet.ts +36 -16
  19. package/actionsExecutor/actions/presentScreen.ts +78 -0
  20. package/actionsExecutor/actions/screenToggleFlag.ts +1 -3
  21. package/actionsExecutor/actions/showAlert.ts +51 -0
  22. package/actionsExecutor/actions/showToast.ts +2 -2
  23. package/actionsExecutor/consts.ts +2 -0
  24. package/actionsExecutor/feedDecorator.ts +6 -0
  25. package/actionsExecutor/types.ts +7 -0
  26. package/appUtils/contextKeysManager/__tests__/getKey/failure.test.ts +1 -22
  27. package/appUtils/contextKeysManager/__tests__/getKey/success/fullSearch.test.ts +1 -25
  28. package/appUtils/contextKeysManager/__tests__/getKey/success/searchInSecure.test.ts +1 -7
  29. package/appUtils/contextKeysManager/__tests__/removeKey/failure.test.ts +0 -25
  30. package/appUtils/contextKeysManager/__tests__/removeKey/success.test.ts +0 -6
  31. package/appUtils/contextKeysManager/__tests__/setKey/failure/failedToSaveInStorage.test.ts +0 -40
  32. package/appUtils/contextKeysManager/__tests__/setKey/{success/saveItemAndReference.test.ts → success.test.ts} +2 -42
  33. package/appUtils/contextKeysManager/index.ts +28 -129
  34. package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +4 -11
  35. package/appUtils/playerManager/OverlayObserver/__tests__/getUpcomingQueue.test.ts +67 -0
  36. package/appUtils/playerManager/OverlayObserver/__tests__/utils.test.ts +9 -2
  37. package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ts +15 -2
  38. package/appUtils/playerManager/OverlayObserver/utils.ts +2 -5
  39. package/appUtils/playerManager/__tests__/playerDiagnostics.test.ts +62 -0
  40. package/appUtils/playerManager/__tests__/playerNative.test.ts +76 -0
  41. package/appUtils/playerManager/__tests__/playerNativeCommand.test.ts +62 -0
  42. package/appUtils/playerManager/__tests__/playerToolbox.test.ts +281 -0
  43. package/appUtils/playerManager/__tests__/usePlayerControllerSetup.test.tsx +62 -0
  44. package/appUtils/playerManager/const.ts +13 -0
  45. package/appUtils/playerManager/index.ts +7 -0
  46. package/appUtils/playerManager/player.ts +25 -0
  47. package/appUtils/playerManager/playerNative.ts +22 -0
  48. package/appUtils/playerManager/playerNativeCommand.ts +3 -6
  49. package/appUtils/playerManager/playerToolbox.ts +196 -0
  50. package/appUtils/playerManager/usePlayerControllerSetup.tsx +6 -0
  51. package/arrayUtils/__tests__/reorderByIds.test.ts +13 -0
  52. package/arrayUtils/index.ts +26 -43
  53. package/cellUtils/__tests__/getButtonsEnabled.test.ts +28 -0
  54. package/cellUtils/__tests__/getCellNavigationEnabled.test.ts +36 -0
  55. package/cellUtils/__tests__/getIsCellNavigationEnabled.test.ts +35 -0
  56. package/cellUtils/__tests__/isCellNavigationEnabled.test.ts +21 -0
  57. package/cellUtils/index.ts +40 -0
  58. package/configurationUtils/__tests__/manifestKeyParser.test.ts +21 -0
  59. package/configurationUtils/__tests__/modalBlocksParser.test.ts +297 -1
  60. package/configurationUtils/manifestKeyParser.ts +12 -1
  61. package/configurationUtils/modalBlocksParser.ts +78 -4
  62. package/entryActions/__tests__/buildEntryActions.test.ts +97 -18
  63. package/entryActions/aliasPresentation.ts +26 -42
  64. package/entryActions/buildEntryActions.ts +1 -0
  65. package/entryActions/index.ts +0 -2
  66. package/focusManager/FocusManager.ts +26 -0
  67. package/focusManager/__tests__/FocusManager.test.ts +9 -0
  68. package/localizationUtils/index.ts +12 -6
  69. package/logger/__tests__/xrayToolbox.test.ts +69 -0
  70. package/logger/xrayToolbox.ts +67 -0
  71. package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
  72. package/manifestUtils/defaultManifestConfigurations/player.js +8 -0
  73. package/manifestUtils/keys.js +7 -2
  74. package/manifestUtils/mobileAction/container/__tests__/mobileActionButtonsContainer.test.js +21 -0
  75. package/manifestUtils/mobileAction/groups/__tests__/buildMobileActionButtonGroups.test.js +1 -1
  76. package/manifestUtils/mobileAction/groups/defaults.js +2 -1
  77. package/modalState/ModalOrchestrator.ts +23 -9
  78. package/modalState/RemoteEditableCollection.ts +9 -1
  79. package/modalState/__tests__/ModalOrchestrator.phase3.test.ts +39 -0
  80. package/modalState/__tests__/RemoteEditableCollection.test.ts +90 -1
  81. package/modalState/__tests__/index.test.ts +30 -1
  82. package/modalState/__tests__/useModalStoreState.test.ts +142 -0
  83. package/modalState/index.ts +8 -1
  84. package/modalState/store.ts +9 -2
  85. package/modalState/types.ts +42 -2
  86. package/modalState/useBottomSheetContent.ts +24 -17
  87. package/navigationUtils/xrayNavigator.ts +12 -0
  88. package/package.json +3 -2
  89. package/pluginUtils/__tests__/pluginUtils.test.js +32 -0
  90. package/pluginUtils/index.ts +29 -0
  91. package/quickBrickAssets/__tests__/index.test.ts +18 -0
  92. package/quickBrickAssets/index.ts +35 -0
  93. package/reactHooks/__tests__/getPluginConfiguration.test.ts +82 -0
  94. package/reactHooks/actions/index.ts +29 -23
  95. package/reactHooks/cell-click/__tests__/index.test.js +131 -1
  96. package/reactHooks/cell-click/index.ts +30 -15
  97. package/reactHooks/connection/index.tsx +8 -5
  98. package/reactHooks/feed/__tests__/feedInvalidation.test.tsx +338 -0
  99. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +8 -2
  100. package/reactHooks/feed/__tests__/useMarkPipesDataStale.test.tsx +243 -0
  101. package/reactHooks/feed/useFeedLoader.tsx +28 -1
  102. package/reactHooks/feed/useLoadPipesDataDispatch.ts +12 -11
  103. package/reactHooks/feed/useMarkPipesDataStale.ts +68 -23
  104. package/reactHooks/navigation/__tests__/index.test.tsx +176 -1
  105. package/reactHooks/navigation/__tests__/usePresentScreen.test.ts +154 -0
  106. package/reactHooks/navigation/index.ts +2 -0
  107. package/reactHooks/navigation/usePresentScreen.ts +157 -0
  108. package/reactHooks/navigation/useRoute.ts +22 -5
  109. package/reactHooks/usePluginConfiguration.ts +84 -46
  110. package/riverComponetsMeasurementProvider/index.tsx +3 -1
  111. package/uiActionsRegistrator/__tests__/subscribe.test.ts +26 -0
  112. package/uiActionsRegistrator/registry.ts +16 -0
  113. package/appUtils/contextKeysManager/__tests__/getKey/success/byReference.test.ts +0 -56
  114. package/appUtils/contextKeysManager/__tests__/getKeyByReference/failure.test.ts +0 -265
  115. package/appUtils/contextKeysManager/__tests__/getKeyByReference/success.test.ts +0 -303
  116. package/appUtils/contextKeysManager/__tests__/removeReferenceForKey/failure.test.ts +0 -82
  117. package/appUtils/contextKeysManager/__tests__/removeReferenceForKey/success.test.ts +0 -41
  118. package/appUtils/contextKeysManager/__tests__/setKey/success/saveItemButNotReference.test.ts +0 -208
  119. package/appUtils/contextKeysManager/__tests__/setReferenceForKey/failure.test.ts +0 -93
  120. package/appUtils/contextKeysManager/__tests__/setReferenceForKey/success.test.ts +0 -45
@@ -1,7 +1,23 @@
1
1
  import { openBottomSheetAction } from "../openBottomSheet";
2
- import { ActionResult } from "../../ActionExecutor";
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
 
@@ -97,4 +257,116 @@ describe("openBottomSheetAction inline items", () => {
97
257
  expect(modalOrchestrator.replace).toHaveBeenCalledTimes(1);
98
258
  expect(modalOrchestrator.open).not.toHaveBeenCalled();
99
259
  });
260
+
261
+ it("executes all actions in tap_actions when onItemPress is called", async () => {
262
+ const multiActionSheet = {
263
+ type: "openBottomSheet",
264
+ options: {
265
+ header: { title: "Target Playlists" },
266
+ content: {
267
+ role: "collection_selector",
268
+ behavior: { selectMode: "none", currentSelection: [] },
269
+ items: [
270
+ {
271
+ id: "playlist-1",
272
+ title: "My Playlist",
273
+ extensions: {
274
+ tap_actions: {
275
+ actions: [
276
+ {
277
+ type: "sendCloudEvent",
278
+ options: { subject: "add_collection_to_collection" },
279
+ },
280
+ {
281
+ type: "showToast",
282
+ options: { message: "Added to playlist" },
283
+ },
284
+ { type: "dismissBottomSheet", options: {} },
285
+ ],
286
+ },
287
+ },
288
+ },
289
+ ],
290
+ },
291
+ },
292
+ } as any;
293
+
294
+ await openBottomSheetAction(multiActionSheet);
295
+
296
+ const [menu, onItemPress] = (modalOrchestrator.open as jest.Mock).mock
297
+ .calls[0];
298
+
299
+ expect(menu.content.items[0].actions).toHaveLength(3);
300
+
301
+ const handleActionsSpy = jest
302
+ .spyOn(actionExecutor, "handleActions")
303
+ .mockResolvedValue(ActionResult.Success);
304
+
305
+ await onItemPress(menu.content.items[0]);
306
+
307
+ expect(handleActionsSpy).toHaveBeenCalledWith(
308
+ [
309
+ {
310
+ type: "sendCloudEvent",
311
+ options: { subject: "add_collection_to_collection" },
312
+ },
313
+ {
314
+ type: "showToast",
315
+ options: { message: "Added to playlist" },
316
+ },
317
+ { type: "dismissBottomSheet", options: {} },
318
+ ],
319
+ expect.objectContaining({
320
+ entry: menu.content.items[0],
321
+ })
322
+ );
323
+
324
+ handleActionsSpy.mockRestore();
325
+ });
326
+
327
+ it("executes preceding actions and excludes presentSubMenu from actionExecutor", async () => {
328
+ const subMenuSheet = {
329
+ type: "openBottomSheet",
330
+ options: {
331
+ header: { title: "Menu" },
332
+ content: {
333
+ items: [
334
+ {
335
+ id: "item-with-submenu",
336
+ title: "More Options",
337
+ extensions: {
338
+ tap_actions: {
339
+ actions: [
340
+ { type: "sendAnalytics", options: { event: "click_more" } },
341
+ {
342
+ type: "presentSubMenu",
343
+ menu: { content: { title: "Sub Options", items: [] } },
344
+ },
345
+ ],
346
+ },
347
+ },
348
+ },
349
+ ],
350
+ },
351
+ },
352
+ } as any;
353
+
354
+ await openBottomSheetAction(subMenuSheet);
355
+
356
+ const [menu, onItemPress] = (modalOrchestrator.open as jest.Mock).mock
357
+ .calls[0];
358
+
359
+ const handleActionsSpy = jest
360
+ .spyOn(actionExecutor, "handleActions")
361
+ .mockResolvedValue(ActionResult.Success);
362
+
363
+ await onItemPress(menu.content.items[0]);
364
+
365
+ expect(handleActionsSpy).toHaveBeenCalledWith(
366
+ [{ type: "sendAnalytics", options: { event: "click_more" } }],
367
+ expect.objectContaining({ entry: menu.content.items[0] })
368
+ );
369
+
370
+ handleActionsSpy.mockRestore();
371
+ });
100
372
  });
@@ -0,0 +1,127 @@
1
+ import { createPresentScreenAction } from "../presentScreen";
2
+ import { ActionResult } from "../../ActionExecutor";
3
+
4
+ const contextEntry = { id: "context-entry" } as any;
5
+
6
+ const succeeded = { success: true, cancelled: false };
7
+
8
+ const cancelled = { success: false, cancelled: true };
9
+
10
+ describe("presentScreenAction", () => {
11
+ let presentScreen: jest.Mock;
12
+ let presentScreenAction: ReturnType<typeof createPresentScreenAction>;
13
+
14
+ beforeEach(() => {
15
+ presentScreen = jest.fn().mockResolvedValue(succeeded);
16
+ presentScreenAction = createPresentScreenAction(presentScreen);
17
+ });
18
+
19
+ it("presents the screen named by typeMapping", async () => {
20
+ await presentScreenAction({
21
+ type: "presentScreen",
22
+ options: { typeMapping: "parent_lock" },
23
+ });
24
+
25
+ expect(presentScreen).toHaveBeenCalledWith({
26
+ typeMapping: "parent_lock",
27
+ entry: undefined,
28
+ });
29
+ });
30
+
31
+ it("fails without presenting anything when typeMapping is missing", async () => {
32
+ const result = await presentScreenAction({
33
+ type: "presentScreen",
34
+ options: {},
35
+ });
36
+
37
+ expect(result).toBe(ActionResult.Error);
38
+ expect(presentScreen).not.toHaveBeenCalled();
39
+ });
40
+
41
+ it("resolves the '@{entry/}' token to the entry from the execution context", async () => {
42
+ await presentScreenAction(
43
+ {
44
+ type: "presentScreen",
45
+ options: { typeMapping: "parent_lock", entry: "@{entry/}" },
46
+ },
47
+ { entry: contextEntry }
48
+ );
49
+
50
+ expect(presentScreen).toHaveBeenCalledWith({
51
+ typeMapping: "parent_lock",
52
+ entry: contextEntry,
53
+ });
54
+ });
55
+
56
+ it("presents an entry supplied inline in the options", async () => {
57
+ const inlineEntry = { id: "inline-entry" } as any;
58
+
59
+ await presentScreenAction({
60
+ type: "presentScreen",
61
+ options: { typeMapping: "parent_lock", entry: inlineEntry },
62
+ });
63
+
64
+ expect(presentScreen).toHaveBeenCalledWith({
65
+ typeMapping: "parent_lock",
66
+ entry: inlineEntry,
67
+ });
68
+ });
69
+
70
+ it("fails without presenting anything when the entry option is not an object", async () => {
71
+ const result = await presentScreenAction({
72
+ type: "presentScreen",
73
+ options: { typeMapping: "parent_lock", entry: "not-an-entry" as any },
74
+ });
75
+
76
+ expect(result).toBe(ActionResult.Error);
77
+ expect(presentScreen).not.toHaveBeenCalled();
78
+ });
79
+
80
+ it("succeeds when the presented screen reports success", async () => {
81
+ presentScreen.mockResolvedValue(succeeded);
82
+
83
+ const result = await presentScreenAction({
84
+ type: "presentScreen",
85
+ options: { typeMapping: "parent_lock" },
86
+ });
87
+
88
+ expect(result).toBe(ActionResult.Success);
89
+ });
90
+
91
+ it("cancels the action chain when the presented screen is cancelled", async () => {
92
+ presentScreen.mockResolvedValue(cancelled);
93
+
94
+ const result = await presentScreenAction({
95
+ type: "presentScreen",
96
+ options: { typeMapping: "parent_lock" },
97
+ });
98
+
99
+ expect(result).toBe(ActionResult.Cancel);
100
+ });
101
+
102
+ it("cancels the action chain when the presented screen reports failure", async () => {
103
+ presentScreen.mockResolvedValue({ success: false, cancelled: false });
104
+
105
+ const result = await presentScreenAction({
106
+ type: "presentScreen",
107
+ options: { typeMapping: "parent_lock" },
108
+ });
109
+
110
+ expect(result).toBe(ActionResult.Cancel);
111
+ });
112
+
113
+ it("errors when the screen could not be presented at all", async () => {
114
+ presentScreen.mockResolvedValue({
115
+ success: false,
116
+ cancelled: false,
117
+ error: "can not resolve screen type mapping: parent_lock",
118
+ });
119
+
120
+ const result = await presentScreenAction({
121
+ type: "presentScreen",
122
+ options: { typeMapping: "parent_lock" },
123
+ });
124
+
125
+ expect(result).toBe(ActionResult.Error);
126
+ });
127
+ });
@@ -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: 1000,
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: 1000,
81
+ timeout: 5000,
82
82
  source: "confirmation",
83
83
  },
84
84
  ]);
@@ -1,5 +1,5 @@
1
1
  /// <reference types="@applicaster/applicaster-types" />
2
- import { dismissModal } from "../../modalState";
2
+ import { dismissModal, modalOrchestrator } from "../../modalState";
3
3
  import { ActionResult } from "../ActionExecutor";
4
4
  import { ActionHandler } from "../types";
5
5
  import { createLogger } from "../../logger";
@@ -16,6 +16,7 @@ export const dismissBottomSheetAction: ActionHandler<
16
16
  any
17
17
  > = async (): Promise<ActionResult> => {
18
18
  log_info("handleAction: dismissBottomSheet");
19
+ modalOrchestrator.close();
19
20
  dismissModal();
20
21
 
21
22
  return ActionResult.Success;
@@ -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";
@@ -27,6 +29,8 @@ export { screenToggleFlagAction } from "./screenToggleFlag";
27
29
 
28
30
  export { createNavigateToScreenAction } from "./navigateToScreen";
29
31
 
32
+ export { createPresentScreenAction } from "./presentScreen";
33
+
30
34
  export { createGoBackAction } from "./goBack";
31
35
 
32
36
  export { createGoHomeAction } from "./goHome";
@@ -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: string;
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,62 +68,60 @@ export function createNavigateToScreenAction(
76
68
  : entrySource
77
69
  : null;
78
70
 
79
- if (entry) {
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
- return ActionResult.Error;
86
- }
73
+ const navigate =
74
+ navigationAction === "push" ? navigator.push : navigator.replace;
87
75
 
76
+ let navigatingEntry: ZappEntry | ZappRiver = entry;
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
- const overriddenEntry: ZappEntry = {
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
- if (navigationAction === "push") {
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
- return ActionResult.Success;
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
- if (!screenId) {
112
- log_error(
113
- `navigateToScreen: can not resolve screen type mapping: ${screenType}`
114
- );
99
+ if (!screenId) {
100
+ log_error(
101
+ `navigateToScreen: can not resolve screen type mapping: ${screenType}`
102
+ );
115
103
 
116
- return ActionResult.Error;
117
- }
104
+ return ActionResult.Error;
105
+ }
118
106
 
119
- const river = rivers[screenId];
107
+ if (!river) {
108
+ log_error("navigateToScreen: can not resolve river");
120
109
 
121
- if (!river) {
122
- log_error("navigateToScreen: can not resolve river");
110
+ return ActionResult.Error;
111
+ }
123
112
 
124
- return ActionResult.Error;
113
+ navigatingEntry = river;
125
114
  }
126
115
 
127
116
  context?.callback?.({ success: false, error: null, abort: true });
128
117
 
129
- if (navigationAction === "push") {
130
- navigator.push(river, navigationOptions);
131
- } else {
132
- navigator.replace(river, navigationOptions);
118
+ // The destination screen resolves `@{entry/...}` from the entry context
119
+ if (entry) {
120
+ context?.setEntryContext?.(entry);
133
121
  }
134
122
 
123
+ navigate(navigatingEntry, navigationOptions);
124
+
135
125
  return ActionResult.Success;
136
126
  };
137
127
  }