@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
package/actionUtils/index.ts
CHANGED
|
@@ -3,5 +3,7 @@ import { isTV } from "../reactUtils";
|
|
|
3
3
|
|
|
4
4
|
export const resolveDefaultAssetUri = (source: string | number): string =>
|
|
5
5
|
isTV()
|
|
6
|
-
?
|
|
6
|
+
? typeof source === "number"
|
|
7
|
+
? Image.resolveAssetSource(source)?.uri || ""
|
|
8
|
+
: (source as string)
|
|
7
9
|
: Image.resolveAssetSource(source as number)?.uri || "";
|
|
@@ -56,7 +56,6 @@ export class ActionExecutor {
|
|
|
56
56
|
context?: ActionExecutionContext
|
|
57
57
|
): Promise<ActionResult> => {
|
|
58
58
|
for (const action of actionTypes) {
|
|
59
|
-
// TODO: Handle action result error
|
|
60
59
|
const result = await this.handleAction(action, context);
|
|
61
60
|
|
|
62
61
|
if (result === ActionResult.Cancel) {
|
|
@@ -64,6 +63,16 @@ export class ActionExecutor {
|
|
|
64
63
|
|
|
65
64
|
return result;
|
|
66
65
|
}
|
|
66
|
+
|
|
67
|
+
if (result === ActionResult.Error) {
|
|
68
|
+
// Everything after a failed step was written for a state that never
|
|
69
|
+
// happened — the event was refused, the screen never opened — so
|
|
70
|
+
// running it is how a chain ends up telling the user their mail was
|
|
71
|
+
// sent when it was not.
|
|
72
|
+
log_error(`handleActions: action: ${action.type} failed`, { action });
|
|
73
|
+
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
67
76
|
}
|
|
68
77
|
|
|
69
78
|
return ActionResult.Success;
|
|
@@ -18,6 +18,7 @@ import { createLogger } from "../logger";
|
|
|
18
18
|
import {
|
|
19
19
|
appRestartAction,
|
|
20
20
|
confirmDialogAction,
|
|
21
|
+
showAlertAction,
|
|
21
22
|
createGoBackAction,
|
|
22
23
|
createGoHomeAction,
|
|
23
24
|
createNavigateToScreenAction,
|
|
@@ -110,6 +111,8 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
110
111
|
confirmDialogAction
|
|
111
112
|
);
|
|
112
113
|
|
|
114
|
+
actionExecutor.registerAction(ACTION_TYPES.SHOW_ALERT, showAlertAction);
|
|
115
|
+
|
|
113
116
|
actionExecutor.registerAction(
|
|
114
117
|
ACTION_TYPES.SEND_CLOUD_EVENT,
|
|
115
118
|
sendCloudEventAction
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ActionExecutor, ActionResult } from "../ActionExecutor";
|
|
2
|
+
|
|
3
|
+
jest.mock("../../logger", () => ({
|
|
4
|
+
createLogger: () => ({
|
|
5
|
+
log_error: jest.fn(),
|
|
6
|
+
log_debug: jest.fn(),
|
|
7
|
+
log_info: jest.fn(),
|
|
8
|
+
}),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe("handleActions", () => {
|
|
12
|
+
const executor = () => {
|
|
13
|
+
const ran: string[] = [];
|
|
14
|
+
const actionExecutor = new ActionExecutor();
|
|
15
|
+
|
|
16
|
+
const register = (type: string, result?: ActionResult) =>
|
|
17
|
+
actionExecutor.registerAction(type, async () => {
|
|
18
|
+
ran.push(type);
|
|
19
|
+
|
|
20
|
+
return result as ActionResult;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
register("ok", ActionResult.Success);
|
|
24
|
+
register("fails", ActionResult.Error);
|
|
25
|
+
register("cancels", ActionResult.Cancel);
|
|
26
|
+
register("recover", ActionResult.Success);
|
|
27
|
+
register("after", ActionResult.Success);
|
|
28
|
+
|
|
29
|
+
return { actionExecutor, ran };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Everything after a failed step was written for a state that never
|
|
33
|
+
// happened: the event was refused, the screen never opened.
|
|
34
|
+
it("stops when an action fails", async () => {
|
|
35
|
+
const { actionExecutor, ran } = executor();
|
|
36
|
+
|
|
37
|
+
const result = await actionExecutor.handleActions([
|
|
38
|
+
{ type: "fails" },
|
|
39
|
+
{ type: "after" },
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
expect(ran).toEqual(["fails"]);
|
|
43
|
+
expect(result).toBe(ActionResult.Error);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("still stops on a cancel", async () => {
|
|
47
|
+
const { actionExecutor, ran } = executor();
|
|
48
|
+
|
|
49
|
+
const result = await actionExecutor.handleActions([
|
|
50
|
+
{ type: "cancels" },
|
|
51
|
+
{ type: "after" },
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
expect(ran).toEqual(["cancels"]);
|
|
55
|
+
expect(result).toBe(ActionResult.Cancel);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// An action nobody registered never ran, which is a misconfigured feed
|
|
59
|
+
// rather than a step that failed. It is stepped over, as it always was.
|
|
60
|
+
it("steps over an action with no handler", async () => {
|
|
61
|
+
const { actionExecutor, ran } = executor();
|
|
62
|
+
|
|
63
|
+
const result = await actionExecutor.handleActions([
|
|
64
|
+
{ type: "nobody-registered-this" },
|
|
65
|
+
{ type: "after" },
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
expect(ran).toEqual(["after"]);
|
|
69
|
+
expect(result).toBe(ActionResult.Success);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { ActionResult } from "../ActionExecutor";
|
|
2
|
+
import { localStorageToggleFlag } from "../StorageActions";
|
|
3
|
+
import { TOGGLE_FLAG_MAX_ITEMS_REACHED_EVENT } from "../consts";
|
|
4
|
+
import { postEvent } from "../../reactHooks/useSubscriberFor";
|
|
5
|
+
|
|
6
|
+
jest.mock("../ActionExecutorContext", () => ({
|
|
7
|
+
log_error: jest.fn(),
|
|
8
|
+
log_info: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
jest.mock("../../reactHooks/useSubscriberFor", () => ({
|
|
12
|
+
postEvent: jest.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
// In-memory stand-in for the storage-backed provider; the limit check under
|
|
16
|
+
// test lives in storageToggleFlag, not in the provider.
|
|
17
|
+
const mockStore: Record<string, string[]> = {};
|
|
18
|
+
|
|
19
|
+
jest.mock("../../storage/StorageMultiSelectProvider", () => ({
|
|
20
|
+
StorageMultiSelectProvider: {
|
|
21
|
+
getProvider: (keyNamespace: string) => ({
|
|
22
|
+
getSelectedAsync: async () => [...(mockStore[keyNamespace] ?? [])],
|
|
23
|
+
addItem: async (item: string) => {
|
|
24
|
+
mockStore[keyNamespace] = [...(mockStore[keyNamespace] ?? []), item];
|
|
25
|
+
},
|
|
26
|
+
removeItem: async (item: string) => {
|
|
27
|
+
mockStore[keyNamespace] = (mockStore[keyNamespace] ?? []).filter(
|
|
28
|
+
(it) => it !== item
|
|
29
|
+
);
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
const KEY = "user_preferences.teams";
|
|
36
|
+
|
|
37
|
+
// Entries of a team selection feed with explicit tap actions
|
|
38
|
+
// (localStorageToggleFlag with key and selector).
|
|
39
|
+
const teamEntry = (n: number) =>
|
|
40
|
+
({
|
|
41
|
+
id: `team-${n}`,
|
|
42
|
+
title: `Team ${n}`,
|
|
43
|
+
type: { value: "action" },
|
|
44
|
+
extensions: { tag: `TEAM ${n}` },
|
|
45
|
+
}) as unknown as ZappEntry;
|
|
46
|
+
|
|
47
|
+
const tapAction = (extraOptions = {}) =>
|
|
48
|
+
({
|
|
49
|
+
type: "localStorageToggleFlag",
|
|
50
|
+
options: { key: KEY, selector: "extensions.tag", ...extraOptions },
|
|
51
|
+
}) as unknown as ActionType;
|
|
52
|
+
|
|
53
|
+
const tapTeams = async (count: number, extraOptions = {}) => {
|
|
54
|
+
const results: ActionResult[] = [];
|
|
55
|
+
|
|
56
|
+
for (let n = 1; n <= count; n++) {
|
|
57
|
+
results.push(
|
|
58
|
+
await localStorageToggleFlag(
|
|
59
|
+
{ entry: teamEntry(n) },
|
|
60
|
+
tapAction(extraOptions)
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return results;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
describe("localStorageToggleFlag max_items", () => {
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
delete mockStore[KEY];
|
|
71
|
+
jest.clearAllMocks();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("blocks the 11th team when max_items is in the action options", async () => {
|
|
75
|
+
const results = await tapTeams(11, { max_items: "10" });
|
|
76
|
+
|
|
77
|
+
expect(results.slice(0, 10)).toEqual(Array(10).fill(ActionResult.Success));
|
|
78
|
+
expect(results[10]).toBe(ActionResult.Cancel);
|
|
79
|
+
expect(mockStore[KEY]).toHaveLength(10);
|
|
80
|
+
|
|
81
|
+
expect(postEvent).toHaveBeenCalledWith(
|
|
82
|
+
TOGGLE_FLAG_MAX_ITEMS_REACHED_EVENT,
|
|
83
|
+
[expect.objectContaining({ maxItems: "10", tag: "TEAM 11" })]
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("still lets a selected team be removed at the limit", async () => {
|
|
88
|
+
await tapTeams(10, { max_items: "10" });
|
|
89
|
+
|
|
90
|
+
const result = await localStorageToggleFlag(
|
|
91
|
+
{ entry: teamEntry(1) },
|
|
92
|
+
tapAction({ max_items: "10" })
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(result).toBe(ActionResult.Success);
|
|
96
|
+
expect(mockStore[KEY]).toHaveLength(9);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -59,3 +59,33 @@ describe("decorateFeed", () => {
|
|
|
59
59
|
expect(decorated.entry[0].extensions.tap_actions).toBeUndefined();
|
|
60
60
|
});
|
|
61
61
|
});
|
|
62
|
+
|
|
63
|
+
describe("decorateFeed max_items", () => {
|
|
64
|
+
const toggleAction = (feed: ZappFeed) =>
|
|
65
|
+
decorateFeed(feed).entry[0].extensions.tap_actions.actions[0];
|
|
66
|
+
|
|
67
|
+
it.each([
|
|
68
|
+
["screen", "screenToggleFlag"],
|
|
69
|
+
["session", "sessionStorageToggleFlag"],
|
|
70
|
+
["ctx", "localStorageToggleFlag"],
|
|
71
|
+
])("passes max_items to the %s toggle action", (scope, type) => {
|
|
72
|
+
const feed = preferenceFeed("multi");
|
|
73
|
+
feed.extensions.preference_editor_options.scope = scope;
|
|
74
|
+
feed.extensions.preference_editor_options.max_items = "10";
|
|
75
|
+
|
|
76
|
+
const toggle = toggleAction(feed);
|
|
77
|
+
|
|
78
|
+
expect(toggle.type).toBe(type);
|
|
79
|
+
expect(toggle.options).toEqual({ key: "agree", max_items: 10 });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it.each([undefined, 0, "0", "ten", -1])(
|
|
83
|
+
"does not set a limit for max_items %p",
|
|
84
|
+
(maxItems) => {
|
|
85
|
+
const feed = preferenceFeed("multi");
|
|
86
|
+
feed.extensions.preference_editor_options.max_items = maxItems;
|
|
87
|
+
|
|
88
|
+
expect(toggleAction(feed).options).toEqual({ key: "agree" });
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { ActionExecutor, ActionResult } from "../ActionExecutor";
|
|
2
|
+
import { screenToggleFlagAction } from "../actions/screenToggleFlag";
|
|
3
|
+
|
|
4
|
+
jest.mock("../ActionExecutorContext", () => ({
|
|
5
|
+
log_error: jest.fn(),
|
|
6
|
+
log_info: jest.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
jest.mock("../StorageActions", () => ({
|
|
10
|
+
onMaxTagsReached: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
// not used here, but it pulls the native bridge logger in at import time
|
|
14
|
+
jest.mock("../../storage/ScreenSingleValueProvider", () => ({}));
|
|
15
|
+
|
|
16
|
+
jest.mock("../../logger", () => ({
|
|
17
|
+
createLogger: () => ({
|
|
18
|
+
log_error: jest.fn(),
|
|
19
|
+
log_debug: jest.fn(),
|
|
20
|
+
log_info: jest.fn(),
|
|
21
|
+
}),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const mockSelected: string[] = [];
|
|
25
|
+
|
|
26
|
+
jest.mock("../../storage/ScreenStateMultiSelectProvider", () => ({
|
|
27
|
+
ScreenMultiSelectProvider: {
|
|
28
|
+
getProvider: () => ({
|
|
29
|
+
getSelectedAsync: async () => [...mockSelected],
|
|
30
|
+
addItem: async (item: string) => {
|
|
31
|
+
mockSelected.push(item);
|
|
32
|
+
},
|
|
33
|
+
removeItem: async (item: string) => {
|
|
34
|
+
mockSelected.splice(mockSelected.indexOf(item), 1);
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
const toggleAction = (options = {}) =>
|
|
41
|
+
({
|
|
42
|
+
type: "screenToggleFlag",
|
|
43
|
+
options: { key: "teams", selector: "extensions.tag", ...options },
|
|
44
|
+
}) as unknown as ActionType;
|
|
45
|
+
|
|
46
|
+
const context = (tag: string) => ({
|
|
47
|
+
screenRoute: "/screen",
|
|
48
|
+
screenStateStore: {},
|
|
49
|
+
entry: { id: tag, extensions: { tag } },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("screenToggleFlagAction at the max_items limit", () => {
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
mockSelected.length = 0;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("returns the cancellation so the chained actions do not run", async () => {
|
|
58
|
+
const afterToggle = jest.fn();
|
|
59
|
+
const executor = new ActionExecutor();
|
|
60
|
+
|
|
61
|
+
executor.registerAction("screenToggleFlag", screenToggleFlagAction);
|
|
62
|
+
|
|
63
|
+
executor.registerAction("sendCloudEvent", async () => {
|
|
64
|
+
afterToggle();
|
|
65
|
+
|
|
66
|
+
return ActionResult.Success;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
mockSelected.push("A", "B");
|
|
70
|
+
|
|
71
|
+
const result = await executor.handleActions(
|
|
72
|
+
[toggleAction({ max_items: 2 }), { type: "sendCloudEvent" }],
|
|
73
|
+
context("C") as never
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
expect(result).toBe(ActionResult.Cancel);
|
|
77
|
+
expect(afterToggle).not.toHaveBeenCalled();
|
|
78
|
+
expect(mockSelected).toEqual(["A", "B"]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("lets the chain continue below the limit", async () => {
|
|
82
|
+
const result = await screenToggleFlagAction(
|
|
83
|
+
toggleAction({ max_items: 2 }),
|
|
84
|
+
context("A") as never
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(result).toBe(ActionResult.Success);
|
|
88
|
+
expect(mockSelected).toEqual(["A"]);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -83,9 +83,7 @@ describe("navigateToScreen navigationOptions", () => {
|
|
|
83
83
|
);
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
// non-push path falls back to replaceTop().
|
|
88
|
-
it("forwards navigationOptions to replaceTop when navigating with an entry", async () => {
|
|
86
|
+
it("forwards navigationOptions to replace when navigating with an entry", async () => {
|
|
89
87
|
const navigator = createNavigator();
|
|
90
88
|
const entry = { id: "entry-id" } as unknown as ZappEntry;
|
|
91
89
|
|
|
@@ -100,12 +98,12 @@ describe("navigateToScreen navigationOptions", () => {
|
|
|
100
98
|
|
|
101
99
|
await createNavigateToScreenAction(navigator, rivers, contentTypes)(action);
|
|
102
100
|
|
|
103
|
-
expect(navigator.
|
|
101
|
+
expect(navigator.replace).toHaveBeenCalledWith(
|
|
104
102
|
{ id: "entry-id", type: { value: "parentLock" } },
|
|
105
103
|
{ pinFlow: "change-pin" }
|
|
106
104
|
);
|
|
107
105
|
|
|
108
|
-
expect(navigator.
|
|
106
|
+
expect(navigator.replaceTop).not.toHaveBeenCalled();
|
|
109
107
|
});
|
|
110
108
|
|
|
111
109
|
// Regression guard: the no-options path must keep working unchanged.
|
|
@@ -131,3 +129,139 @@ describe("navigateToScreen navigationOptions", () => {
|
|
|
131
129
|
expect(result).toBe(ActionResult.Success);
|
|
132
130
|
});
|
|
133
131
|
});
|
|
132
|
+
|
|
133
|
+
describe("navigateToScreen target resolution", () => {
|
|
134
|
+
function run(options, context?) {
|
|
135
|
+
const navigator = createNavigator();
|
|
136
|
+
|
|
137
|
+
const result = createNavigateToScreenAction(
|
|
138
|
+
navigator,
|
|
139
|
+
rivers,
|
|
140
|
+
contentTypes
|
|
141
|
+
)({ type: "navigateToScreen", options }, context);
|
|
142
|
+
|
|
143
|
+
return { navigator, result };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
it("overrides entry.type with typeMapping when both are provided", async () => {
|
|
147
|
+
const entry = {
|
|
148
|
+
id: "entry-id",
|
|
149
|
+
type: { value: "video" },
|
|
150
|
+
} as unknown as ZappEntry;
|
|
151
|
+
|
|
152
|
+
const { navigator, result } = run({
|
|
153
|
+
typeMapping: "parentLock",
|
|
154
|
+
navigationAction: "push",
|
|
155
|
+
entry,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(await result).toBe(ActionResult.Success);
|
|
159
|
+
|
|
160
|
+
expect(navigator.push).toHaveBeenCalledWith(
|
|
161
|
+
{ id: "entry-id", type: { value: "parentLock" } },
|
|
162
|
+
undefined
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
expect(entry.type).toEqual({ value: "video" });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("navigates with the entry's own type when typeMapping is not provided", async () => {
|
|
169
|
+
const entry = {
|
|
170
|
+
id: "entry-id",
|
|
171
|
+
type: { value: "video" },
|
|
172
|
+
} as unknown as ZappEntry;
|
|
173
|
+
|
|
174
|
+
const { navigator, result } = run({ navigationAction: "push", entry });
|
|
175
|
+
|
|
176
|
+
expect(await result).toBe(ActionResult.Success);
|
|
177
|
+
|
|
178
|
+
expect(navigator.push).toHaveBeenCalledWith(
|
|
179
|
+
{ id: "entry-id", type: { value: "video" } },
|
|
180
|
+
undefined
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("resolves the context entry when entry is @{entry/} and typeMapping is not provided", async () => {
|
|
185
|
+
const entry = {
|
|
186
|
+
id: "context-entry-id",
|
|
187
|
+
type: { value: "video" },
|
|
188
|
+
} as unknown as ZappEntry;
|
|
189
|
+
|
|
190
|
+
const { navigator, result } = run({ entry: "@{entry/}" }, { entry });
|
|
191
|
+
|
|
192
|
+
expect(await result).toBe(ActionResult.Success);
|
|
193
|
+
|
|
194
|
+
expect(navigator.replace).toHaveBeenCalledWith(
|
|
195
|
+
{ id: "context-entry-id", type: { value: "video" } },
|
|
196
|
+
undefined
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("returns an error when neither typeMapping nor entry type is provided", async () => {
|
|
201
|
+
const entry = { id: "entry-id" } as unknown as ZappEntry;
|
|
202
|
+
|
|
203
|
+
const { navigator, result } = run({ navigationAction: "push", entry });
|
|
204
|
+
|
|
205
|
+
expect(await result).toBe(ActionResult.Error);
|
|
206
|
+
expect(navigator.push).not.toHaveBeenCalled();
|
|
207
|
+
expect(navigator.replace).not.toHaveBeenCalled();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("returns an error when neither typeMapping nor entry is provided", async () => {
|
|
211
|
+
const { navigator, result } = run({ navigationAction: "push" });
|
|
212
|
+
|
|
213
|
+
expect(await result).toBe(ActionResult.Error);
|
|
214
|
+
expect(navigator.push).not.toHaveBeenCalled();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("returns an error when typeMapping has no screen_id mapping", async () => {
|
|
218
|
+
const callback = jest.fn();
|
|
219
|
+
|
|
220
|
+
const { navigator, result } = run(
|
|
221
|
+
{ typeMapping: "unknownType", navigationAction: "push" },
|
|
222
|
+
{ callback }
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
expect(await result).toBe(ActionResult.Error);
|
|
226
|
+
expect(navigator.push).not.toHaveBeenCalled();
|
|
227
|
+
expect(callback).not.toHaveBeenCalled();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("returns an error when the mapped screen_id has no river", async () => {
|
|
231
|
+
const navigator = createNavigator();
|
|
232
|
+
const callback = jest.fn();
|
|
233
|
+
|
|
234
|
+
const result = await createNavigateToScreenAction(navigator, rivers, {
|
|
235
|
+
orphan: { screen_id: "missing-screen-id" },
|
|
236
|
+
} as unknown as ZappContentTypes)(
|
|
237
|
+
{
|
|
238
|
+
type: "navigateToScreen",
|
|
239
|
+
options: { typeMapping: "orphan", navigationAction: "push" },
|
|
240
|
+
},
|
|
241
|
+
{ callback }
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
expect(result).toBe(ActionResult.Error);
|
|
245
|
+
expect(navigator.push).not.toHaveBeenCalled();
|
|
246
|
+
expect(callback).not.toHaveBeenCalled();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("aborts the action pipeline before navigating to a resolved river", async () => {
|
|
250
|
+
const callback = jest.fn();
|
|
251
|
+
|
|
252
|
+
const { navigator, result } = run(
|
|
253
|
+
{ typeMapping: "parentLock" },
|
|
254
|
+
{ callback }
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(await result).toBe(ActionResult.Success);
|
|
258
|
+
|
|
259
|
+
expect(callback).toHaveBeenCalledWith({
|
|
260
|
+
success: false,
|
|
261
|
+
error: null,
|
|
262
|
+
abort: true,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(navigator.replace).toHaveBeenCalledWith(river, undefined);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
@@ -60,6 +60,30 @@ describe("openBottomSheetAction custom content", () => {
|
|
|
60
60
|
});
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
it("passes themePluginId through to the name sheet", async () => {
|
|
64
|
+
const CustomContent = function CustomContent() {
|
|
65
|
+
return null;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
await openBottomSheetAction({
|
|
69
|
+
type: "openBottomSheet",
|
|
70
|
+
options: {
|
|
71
|
+
header: { title: "Create New Playlist" },
|
|
72
|
+
themePluginId: "playlist-experience",
|
|
73
|
+
content: {
|
|
74
|
+
component: CustomContent,
|
|
75
|
+
props: { buttonLabel: "Create" },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
} as any);
|
|
79
|
+
|
|
80
|
+
const args = (openBottomSheetModal as jest.Mock).mock.calls[0][0];
|
|
81
|
+
|
|
82
|
+
expect(args.modalBottomSheetContentProps.themePluginId).toBe(
|
|
83
|
+
"playlist-experience"
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
63
87
|
it("returns Error when neither list data nor content.component is provided", async () => {
|
|
64
88
|
const result = await openBottomSheetAction({
|
|
65
89
|
type: "openBottomSheet",
|