@applicaster/zapp-react-native-utils 16.0.0-alpha.2663208063 → 16.0.0-alpha.2710214351
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/__tests__/StorageActions.test.ts +98 -0
- package/actionsExecutor/__tests__/feedDecorator.test.ts +91 -0
- package/actionsExecutor/__tests__/screenToggleFlag.test.ts +90 -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 +267 -0
- package/actionsExecutor/actions/__tests__/openBottomSheet.customContent.test.ts +24 -0
- package/actionsExecutor/actions/__tests__/openBottomSheet.inlineItems.test.ts +273 -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 +43 -45
- package/actionsExecutor/actions/openBottomSheet.ts +36 -16
- package/actionsExecutor/actions/presentScreen.ts +78 -0
- 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 +4 -0
- package/actionsExecutor/feedDecorator.ts +12 -6
- 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.android.tv.ts +4 -1
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ios.tv.ts +4 -1
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ts +21 -2
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.web.ts +4 -1
- package/appUtils/playerManager/OverlayObserver/utils.ts +9 -7
- package/appUtils/playerManager/__tests__/playerDiagnostics.test.ts +62 -0
- package/appUtils/playerManager/__tests__/playerNative.test.ts +114 -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 +27 -3
- 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 +70 -3
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +21 -0
- package/configurationUtils/__tests__/modalBlocksParser.test.ts +297 -1
- package/configurationUtils/manifestKeyParser.ts +12 -1
- package/configurationUtils/modalBlocksParser.ts +78 -4
- package/entryActions/__tests__/buildEntryActions.test.ts +235 -0
- package/entryActions/aliasPresentation.ts +108 -0
- package/entryActions/buildEntryActions.ts +167 -0
- package/entryActions/index.ts +15 -0
- package/entryActions/types.ts +39 -0
- 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 +30 -1
- 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 +23 -9
- 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__/index.test.ts +30 -1
- package/modalState/__tests__/useModalStoreState.test.ts +142 -0
- package/modalState/index.ts +8 -1
- package/modalState/store.ts +9 -2
- package/modalState/types.ts +42 -2
- package/modalState/useBottomSheetContent.ts +24 -17
- package/navigationUtils/xrayNavigator.ts +12 -0
- package/package.json +3 -2
- package/playerUtils/__tests__/resolvePlayerActions.test.ts +138 -0
- package/playerUtils/index.ts +17 -1
- package/playerUtils/resolvePlayerActions.ts +71 -0
- 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 +74 -3
- 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 +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 +3 -1
- package/uiActionsRegistrator/__tests__/resolveActionIdentifiers.test.ts +93 -0
- package/uiActionsRegistrator/__tests__/subscribe.test.ts +133 -0
- package/uiActionsRegistrator/__tests__/usableActionItems.test.ts +52 -0
- package/uiActionsRegistrator/index.ts +25 -203
- package/uiActionsRegistrator/registry.ts +351 -0
- package/uiActionsRegistrator/resolveActionIdentifiers.ts +100 -0
- package/uiActionsRegistrator/usableActionItems.ts +66 -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/playerUtils/__tests__/getPlayerActionButtons.test.ts +0 -54
- package/playerUtils/getPlayerActionButtons.ts +0 -17
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;
|
|
@@ -9,7 +9,7 @@ import { ActionExecutionContext, ActionHandler } from "./types";
|
|
|
9
9
|
import { batchRemoveAllFromNamespaceForStorage } from "../zappFrameworkUtils/localStorageHelper";
|
|
10
10
|
import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
|
|
11
11
|
|
|
12
|
-
import { useNavigation, useRivers } from "../reactHooks";
|
|
12
|
+
import { useNavigation, usePresentScreen, useRivers } from "../reactHooks";
|
|
13
13
|
import { useContentTypes } from "@applicaster/zapp-react-native-redux/hooks";
|
|
14
14
|
import { useSubscriberFor } from "../reactHooks/useSubscriberFor";
|
|
15
15
|
import { APP_EVENTS } from "../appUtils/events";
|
|
@@ -18,11 +18,15 @@ import { createLogger } from "../logger";
|
|
|
18
18
|
import {
|
|
19
19
|
appRestartAction,
|
|
20
20
|
confirmDialogAction,
|
|
21
|
+
showAlertAction,
|
|
22
|
+
createGoBackAction,
|
|
23
|
+
createGoHomeAction,
|
|
21
24
|
createNavigateToScreenAction,
|
|
22
25
|
dismissBottomSheetAction,
|
|
23
26
|
localStorageRemoveAction,
|
|
24
27
|
localStorageSetAction,
|
|
25
28
|
localStorageToggleFlagAction,
|
|
29
|
+
createPresentScreenAction,
|
|
26
30
|
openBottomSheetAction,
|
|
27
31
|
refreshComponentAction,
|
|
28
32
|
screenSetVariableAction,
|
|
@@ -107,6 +111,8 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
107
111
|
confirmDialogAction
|
|
108
112
|
);
|
|
109
113
|
|
|
114
|
+
actionExecutor.registerAction(ACTION_TYPES.SHOW_ALERT, showAlertAction);
|
|
115
|
+
|
|
110
116
|
actionExecutor.registerAction(
|
|
111
117
|
ACTION_TYPES.SEND_CLOUD_EVENT,
|
|
112
118
|
sendCloudEventAction
|
|
@@ -155,6 +161,7 @@ export function withActionExecutor(Component) {
|
|
|
155
161
|
const navigator = useNavigation();
|
|
156
162
|
const rivers = useRivers();
|
|
157
163
|
const contentTypes = useContentTypes();
|
|
164
|
+
const presentScreen = usePresentScreen();
|
|
158
165
|
|
|
159
166
|
const handlers = useMemo(() => {
|
|
160
167
|
return {
|
|
@@ -189,6 +196,27 @@ export function withActionExecutor(Component) {
|
|
|
189
196
|
);
|
|
190
197
|
}, [navigator, rivers, contentTypes]);
|
|
191
198
|
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
return _actionExecutor.registerAction(
|
|
201
|
+
ACTION_TYPES.PRESENT_SCREEN,
|
|
202
|
+
createPresentScreenAction(presentScreen)
|
|
203
|
+
);
|
|
204
|
+
}, [presentScreen]);
|
|
205
|
+
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
return _actionExecutor.registerAction(
|
|
208
|
+
ACTION_TYPES.GO_BACK,
|
|
209
|
+
createGoBackAction(navigator)
|
|
210
|
+
);
|
|
211
|
+
}, [navigator]);
|
|
212
|
+
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
return _actionExecutor.registerAction(
|
|
215
|
+
ACTION_TYPES.GO_HOME,
|
|
216
|
+
createGoHomeAction(navigator)
|
|
217
|
+
);
|
|
218
|
+
}, [navigator]);
|
|
219
|
+
|
|
192
220
|
return (
|
|
193
221
|
<ActionExecutorContext.Provider value={handlers}>
|
|
194
222
|
<Component {...props} />
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { decorateFeed } from "../feedDecorator";
|
|
2
|
+
|
|
3
|
+
const preferenceFeed = (selectMode: string) =>
|
|
4
|
+
({
|
|
5
|
+
id: "agree-component",
|
|
6
|
+
title: "I agree to the terms",
|
|
7
|
+
type: { value: "feed" },
|
|
8
|
+
extensions: {
|
|
9
|
+
role: "preference_editor",
|
|
10
|
+
preference_editor_options: {
|
|
11
|
+
key: "agree",
|
|
12
|
+
scope: "screen",
|
|
13
|
+
select_mode: selectMode,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
entry: [
|
|
17
|
+
{
|
|
18
|
+
id: "I agree to the terms-agree",
|
|
19
|
+
title: "I agree to the terms",
|
|
20
|
+
type: { value: "action" },
|
|
21
|
+
extensions: { tag: "true" },
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
}) as unknown as ZappFeed;
|
|
25
|
+
|
|
26
|
+
describe("decorateFeed", () => {
|
|
27
|
+
it("decorates single select feeds with screenSetVariable", () => {
|
|
28
|
+
const decorated = decorateFeed(preferenceFeed("single"));
|
|
29
|
+
|
|
30
|
+
expect(decorated.extensions.behavior).toEqual(
|
|
31
|
+
expect.objectContaining({
|
|
32
|
+
select_mode: "single",
|
|
33
|
+
current_selection: "@{screen/agree}",
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
expect(decorated.entry[0].extensions.tap_actions.actions[0].type).toBe(
|
|
38
|
+
"screenSetVariable"
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("decorates multi select feeds with screenToggleFlag", () => {
|
|
43
|
+
const decorated = decorateFeed(preferenceFeed("multi"));
|
|
44
|
+
|
|
45
|
+
expect(decorated.extensions.behavior.select_mode).toBe("multi");
|
|
46
|
+
|
|
47
|
+
expect(decorated.entry[0].extensions.tap_actions.actions[0].type).toBe(
|
|
48
|
+
"screenToggleFlag"
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("leaves a feed without the preference_editor role untouched", () => {
|
|
53
|
+
const feed = preferenceFeed("single");
|
|
54
|
+
feed.extensions.role = undefined;
|
|
55
|
+
|
|
56
|
+
const decorated = decorateFeed(feed);
|
|
57
|
+
|
|
58
|
+
expect(decorated.extensions.behavior).toBeUndefined();
|
|
59
|
+
expect(decorated.entry[0].extensions.tap_actions).toBeUndefined();
|
|
60
|
+
});
|
|
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
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createGoBackAction } from "../goBack";
|
|
2
|
+
import { ActionResult } from "../../ActionExecutor";
|
|
3
|
+
|
|
4
|
+
function createNavigator(canGoBack = true) {
|
|
5
|
+
return {
|
|
6
|
+
goBack: jest.fn(),
|
|
7
|
+
canGoBack: jest.fn().mockReturnValue(canGoBack),
|
|
8
|
+
} as unknown as QuickBrickAppNavigator;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("goBack", () => {
|
|
12
|
+
it("goes back with fallbackToHome enabled when no options are provided", async () => {
|
|
13
|
+
const navigator = createNavigator();
|
|
14
|
+
|
|
15
|
+
const result = await createGoBackAction(navigator)({ type: "goBack" });
|
|
16
|
+
|
|
17
|
+
expect(navigator.goBack).toHaveBeenCalledWith(true, false, false);
|
|
18
|
+
expect(result).toBe(ActionResult.Success);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("forwards backToTop", async () => {
|
|
22
|
+
const navigator = createNavigator();
|
|
23
|
+
|
|
24
|
+
const action = {
|
|
25
|
+
type: "goBack",
|
|
26
|
+
options: { backToTop: true },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const result = await createGoBackAction(navigator)(action);
|
|
30
|
+
|
|
31
|
+
expect(navigator.goBack).toHaveBeenCalledWith(true, false, true);
|
|
32
|
+
expect(result).toBe(ActionResult.Success);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("goes back without falling back to home when the stack allows it", async () => {
|
|
36
|
+
const navigator = createNavigator(true);
|
|
37
|
+
|
|
38
|
+
const action = {
|
|
39
|
+
type: "goBack",
|
|
40
|
+
options: { fallbackToHome: false },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const result = await createGoBackAction(navigator)(action);
|
|
44
|
+
|
|
45
|
+
expect(navigator.goBack).toHaveBeenCalledWith(false, false, false);
|
|
46
|
+
expect(result).toBe(ActionResult.Success);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("does nothing when there is nothing to go back to and fallbackToHome is off", async () => {
|
|
50
|
+
const navigator = createNavigator(false);
|
|
51
|
+
|
|
52
|
+
const action = {
|
|
53
|
+
type: "goBack",
|
|
54
|
+
options: { fallbackToHome: false },
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const result = await createGoBackAction(navigator)(action);
|
|
58
|
+
|
|
59
|
+
expect(navigator.goBack).not.toHaveBeenCalled();
|
|
60
|
+
expect(result).toBe(ActionResult.Error);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createGoHomeAction } from "../goHome";
|
|
2
|
+
import { ActionResult } from "../../ActionExecutor";
|
|
3
|
+
|
|
4
|
+
function createNavigator() {
|
|
5
|
+
return {
|
|
6
|
+
goHome: jest.fn(),
|
|
7
|
+
} as unknown as QuickBrickAppNavigator;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe("goHome", () => {
|
|
11
|
+
it("navigates to the home screen", async () => {
|
|
12
|
+
const navigator = createNavigator();
|
|
13
|
+
|
|
14
|
+
const result = await createGoHomeAction(navigator)({ type: "goHome" });
|
|
15
|
+
|
|
16
|
+
expect(navigator.goHome).toHaveBeenCalledWith();
|
|
17
|
+
expect(result).toBe(ActionResult.Success);
|
|
18
|
+
});
|
|
19
|
+
});
|