@applicaster/zapp-react-native-utils 16.0.0-alpha.8716141508 → 16.0.0-alpha.9024462102
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/actionsExecutor/ActionExecutor.ts +50 -3
- package/actionsExecutor/ActionExecutorContext.tsx +99 -17
- package/actionsExecutor/__tests__/ActionExecutor.test.ts +71 -0
- package/actionsExecutor/__tests__/feedDecorator.test.ts +61 -0
- package/actionsExecutor/actions/__tests__/dismissBottomSheet.test.ts +25 -0
- package/actionsExecutor/actions/__tests__/goBack.test.ts +62 -0
- package/actionsExecutor/actions/__tests__/goHome.test.ts +19 -0
- package/actionsExecutor/actions/__tests__/loadItemsFromSource.test.ts +88 -0
- package/actionsExecutor/actions/__tests__/navigateToScreen.test.ts +133 -0
- package/actionsExecutor/actions/__tests__/openBottomSheet.customContent.test.ts +76 -0
- package/actionsExecutor/actions/__tests__/openBottomSheet.inlineItems.test.ts +300 -0
- 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 +88 -0
- package/actionsExecutor/actions/dismissBottomSheet.ts +23 -0
- package/actionsExecutor/actions/goBack.ts +60 -0
- package/actionsExecutor/actions/goHome.ts +31 -0
- package/actionsExecutor/actions/index.ts +12 -0
- package/actionsExecutor/actions/navigateToScreen.ts +18 -4
- package/actionsExecutor/actions/openBottomSheet.ts +338 -0
- package/actionsExecutor/actions/presentScreen.ts +78 -0
- package/actionsExecutor/actions/refreshComponent.ts +6 -0
- package/actionsExecutor/actions/sendCloudEvent.ts +6 -1
- package/actionsExecutor/actions/showAlert.ts +51 -0
- package/actionsExecutor/actions/showToast.ts +64 -8
- package/actionsExecutor/consts.ts +27 -0
- package/actionsExecutor/feedDecorator.ts +6 -6
- package/analyticsUtils/PlayerAnalyticsManager.ts +1 -1
- package/appUtils/HooksManager/index.ts +9 -0
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +131 -54
- package/appUtils/playerManager/OverlayObserver/__tests__/getUpcomingQueue.test.ts +67 -0
- package/appUtils/playerManager/OverlayObserver/__tests__/liveContent.test.ts +205 -0
- package/appUtils/playerManager/OverlayObserver/__tests__/utils.test.ts +56 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.android.tv.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ios.tv.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ts +24 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.web.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/utils.ts +51 -20
- package/appUtils/playerManager/__tests__/playerContent.test.ts +403 -0
- package/appUtils/playerManager/__tests__/playerFactory.test.ts +1 -1
- package/appUtils/playerManager/__tests__/playerNative.test.ts +38 -0
- package/appUtils/playerManager/__tests__/usePlayerContent.test.tsx +64 -0
- package/appUtils/playerManager/{conts.ts → const.ts} +14 -0
- package/appUtils/playerManager/index.ts +1 -1
- package/appUtils/playerManager/player.ts +115 -3
- package/appUtils/playerManager/playerFactory.ts +1 -1
- package/appUtils/playerManager/playerNative.ts +6 -4
- package/appUtils/playerManager/usePlayerContent.tsx +43 -0
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +1 -1
- package/appUtils/playerManager/userLanguagePreference.ts +1 -1
- package/arrayUtils/__tests__/reorderByIds.test.ts +63 -0
- package/arrayUtils/index.ts +62 -0
- package/cellUtils/index.ts +30 -3
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +21 -0
- package/configurationUtils/__tests__/modalBlocksParser.test.ts +269 -0
- package/configurationUtils/manifestKeyParser.ts +50 -10
- package/configurationUtils/modalBlocksParser.ts +214 -0
- 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/manifestUtils/__tests__/player.audioControls.test.js +158 -0
- package/manifestUtils/_internals/index.js +8 -3
- package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +330 -1
- package/manifestUtils/index.js +3 -0
- package/manifestUtils/modalBlocks.js +304 -0
- package/modalState/ContentViewModel.ts +113 -0
- package/modalState/EditableCollection.ts +17 -0
- package/modalState/EditableCollectionRegistry.ts +51 -0
- package/modalState/ModalOrchestrator.ts +212 -0
- package/modalState/RemoteEditableCollection.ts +235 -0
- package/modalState/__tests__/ContentViewModel.editable.test.ts +69 -0
- package/modalState/__tests__/ContentViewModel.test.ts +165 -0
- package/modalState/__tests__/EditableCollectionRegistry.test.ts +112 -0
- package/modalState/__tests__/ModalOrchestrator.phase3.test.ts +63 -0
- package/modalState/__tests__/RemoteEditableCollection.test.ts +415 -0
- package/modalState/__tests__/index.test.ts +30 -1
- package/modalState/__tests__/useBottomSheetContent.test.ts +349 -0
- package/modalState/__tests__/useModalStoreState.test.ts +142 -0
- package/modalState/index.ts +35 -83
- package/modalState/store.ts +109 -0
- package/modalState/types.ts +162 -0
- package/modalState/useBottomSheetContent.ts +109 -0
- package/package.json +2 -2
- package/playerUtils/__tests__/contentDataKeys.test.ts +297 -0
- package/playerUtils/__tests__/resolvePlayerActions.test.ts +138 -0
- package/playerUtils/__tests__/resolvePlayerTitleSubtitle.test.ts +180 -0
- package/playerUtils/contentDataKeys.ts +134 -0
- package/playerUtils/index.ts +37 -16
- package/playerUtils/isAudioItem.ts +26 -0
- package/playerUtils/resolvePlayerActions.ts +71 -0
- package/playerUtils/resolvePlayerTitleSubtitle.ts +76 -0
- package/pluginUtils/__tests__/pluginUtils.test.js +32 -0
- package/pluginUtils/index.ts +29 -0
- package/reactHooks/actions/index.ts +67 -2
- package/reactHooks/cell-click/__tests__/index.test.js +64 -0
- package/reactHooks/cell-click/index.ts +21 -14
- package/reactHooks/connection/__tests__/index.test.js +154 -2
- package/reactHooks/connection/index.tsx +71 -7
- package/reactHooks/index.ts +2 -0
- 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 +4 -1
- package/reactHooks/utils/__tests__/index.test.js +22 -2
- package/reactHooks/utils/index.ts +11 -1
- package/uiActionsRegistrator/__tests__/resolveActionIdentifiers.test.ts +93 -0
- package/uiActionsRegistrator/__tests__/subscribe.test.ts +107 -0
- package/uiActionsRegistrator/__tests__/usableActionItems.test.ts +52 -0
- package/uiActionsRegistrator/index.ts +25 -203
- package/uiActionsRegistrator/registry.ts +335 -0
- package/uiActionsRegistrator/resolveActionIdentifiers.ts +100 -0
- package/uiActionsRegistrator/usableActionItems.ts +66 -0
- package/zappFrameworkUtils/localStorageHelper.ts +10 -2
- package/appUtils/playerManager/usePlayerTitleSummaryOverlay.tsx +0 -56
- package/playerUtils/__tests__/getPlayerActionButtons.test.ts +0 -54
- package/playerUtils/getPlayerActionButtons.ts +0 -17
- /package/reactHooks/actions/__tests__/{index.test.js → index.test.tsx} +0 -0
|
@@ -12,7 +12,7 @@ const { log_error, log_debug, log_info } = createLogger({
|
|
|
12
12
|
category: "General",
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
class ActionExecutor {
|
|
15
|
+
export class ActionExecutor {
|
|
16
16
|
private registeredActions: Record<string, ActionHandler> = {};
|
|
17
17
|
|
|
18
18
|
// TODO: Remove context
|
|
@@ -56,7 +56,6 @@ 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 @@ 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;
|
|
@@ -101,7 +110,8 @@ class ActionExecutor {
|
|
|
101
110
|
|
|
102
111
|
registerAction = <TOptions = Record<string, any>>(
|
|
103
112
|
type: string,
|
|
104
|
-
handler: ActionHandler<TOptions
|
|
113
|
+
handler: ActionHandler<TOptions>,
|
|
114
|
+
stateResolver?: (entry?: ZappEntry) => any
|
|
105
115
|
) => {
|
|
106
116
|
if (this.registeredActions[type]) {
|
|
107
117
|
log_error(`registerAction: action type: ${type} already registered`);
|
|
@@ -112,11 +122,48 @@ class ActionExecutor {
|
|
|
112
122
|
this.registeredActions[type] = handler;
|
|
113
123
|
log_debug(`registerAction: action type: ${type} registered`);
|
|
114
124
|
|
|
125
|
+
let unregisterState = () => {};
|
|
126
|
+
|
|
127
|
+
if (stateResolver) {
|
|
128
|
+
unregisterState = this.registerActionState(type, stateResolver);
|
|
129
|
+
}
|
|
130
|
+
|
|
115
131
|
return () => {
|
|
116
132
|
this.unregisterAction(type);
|
|
133
|
+
unregisterState();
|
|
117
134
|
log_debug(`registerAction: action type: ${type} un-registered`);
|
|
118
135
|
};
|
|
119
136
|
};
|
|
137
|
+
|
|
138
|
+
private actionStates: Record<string, (entry?: ZappEntry) => any> = {};
|
|
139
|
+
|
|
140
|
+
registerActionState = (
|
|
141
|
+
type: string,
|
|
142
|
+
stateResolver: (entry?: ZappEntry) => any
|
|
143
|
+
) => {
|
|
144
|
+
if (this.actionStates[type]) {
|
|
145
|
+
log_error(
|
|
146
|
+
`registerActionState: action type state: ${type} already registered`
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return () => {};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.actionStates[type] = stateResolver;
|
|
153
|
+
log_debug(`registerActionState: action type state: ${type} registered`);
|
|
154
|
+
|
|
155
|
+
return () => {
|
|
156
|
+
delete this.actionStates[type];
|
|
157
|
+
|
|
158
|
+
log_debug(
|
|
159
|
+
`registerActionState: action type state: ${type} un-registered`
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
getActionState = (type: string, entry?: ZappEntry) => {
|
|
165
|
+
return this.actionStates[type]?.(entry);
|
|
166
|
+
};
|
|
120
167
|
}
|
|
121
168
|
|
|
122
169
|
export const actionExecutor = new ActionExecutor();
|
|
@@ -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,10 +18,16 @@ import { createLogger } from "../logger";
|
|
|
18
18
|
import {
|
|
19
19
|
appRestartAction,
|
|
20
20
|
confirmDialogAction,
|
|
21
|
+
showAlertAction,
|
|
22
|
+
createGoBackAction,
|
|
23
|
+
createGoHomeAction,
|
|
21
24
|
createNavigateToScreenAction,
|
|
25
|
+
dismissBottomSheetAction,
|
|
22
26
|
localStorageRemoveAction,
|
|
23
27
|
localStorageSetAction,
|
|
24
28
|
localStorageToggleFlagAction,
|
|
29
|
+
createPresentScreenAction,
|
|
30
|
+
openBottomSheetAction,
|
|
25
31
|
refreshComponentAction,
|
|
26
32
|
screenSetVariableAction,
|
|
27
33
|
screenToggleFlagAction,
|
|
@@ -33,6 +39,8 @@ import {
|
|
|
33
39
|
showToastAction,
|
|
34
40
|
} from "./actions";
|
|
35
41
|
|
|
42
|
+
import { ACTION_TYPES } from "./consts";
|
|
43
|
+
|
|
36
44
|
export const { log_error, log_info, log_debug } = createLogger({
|
|
37
45
|
subsystem: "ActionExecutorContext",
|
|
38
46
|
category: "General",
|
|
@@ -41,9 +49,15 @@ export const { log_error, log_info, log_debug } = createLogger({
|
|
|
41
49
|
export type ActionExecutorContextType = {
|
|
42
50
|
registerAction: <TOptions = Record<string, any>>(
|
|
43
51
|
type: string,
|
|
44
|
-
handler: ActionHandler<TOptions
|
|
52
|
+
handler: ActionHandler<TOptions>,
|
|
53
|
+
stateResolver?: (entry?: ZappEntry) => any
|
|
45
54
|
) => void;
|
|
46
55
|
unregisterAction: (type: string) => void;
|
|
56
|
+
registerActionState: (
|
|
57
|
+
type: string,
|
|
58
|
+
stateResolver: (entry?: ZappEntry) => any
|
|
59
|
+
) => () => void;
|
|
60
|
+
getActionState: (type: string, entry?: ZappEntry) => any;
|
|
47
61
|
handleAction: (
|
|
48
62
|
action: ActionType,
|
|
49
63
|
context?: ActionExecutionContext
|
|
@@ -63,34 +77,78 @@ type Props = {
|
|
|
63
77
|
};
|
|
64
78
|
|
|
65
79
|
const prepareDefaultActions = (actionExecutor) => {
|
|
66
|
-
actionExecutor.registerAction(
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
actionExecutor.registerAction(
|
|
81
|
+
ACTION_TYPES.LOCAL_STORAGE_SET,
|
|
82
|
+
localStorageSetAction
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
actionExecutor.registerAction(
|
|
86
|
+
ACTION_TYPES.SESSION_STORAGE_SET,
|
|
87
|
+
sessionStorageSetAction
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
actionExecutor.registerAction(
|
|
91
|
+
ACTION_TYPES.LOCAL_STORAGE_REMOVE,
|
|
92
|
+
localStorageRemoveAction
|
|
93
|
+
);
|
|
69
94
|
|
|
70
95
|
actionExecutor.registerAction(
|
|
71
|
-
|
|
96
|
+
ACTION_TYPES.SESSION_STORAGE_REMOVE,
|
|
72
97
|
sessionStorageRemoveAction
|
|
73
98
|
);
|
|
74
99
|
|
|
75
|
-
actionExecutor.registerAction(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
100
|
+
actionExecutor.registerAction(
|
|
101
|
+
ACTION_TYPES.REFRESH_COMPONENT,
|
|
102
|
+
refreshComponentAction
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
actionExecutor.registerAction(ACTION_TYPES.SWITCH_LAYOUT, switchLayoutAction);
|
|
106
|
+
|
|
107
|
+
actionExecutor.registerAction(ACTION_TYPES.APP_RESTART, appRestartAction);
|
|
108
|
+
|
|
109
|
+
actionExecutor.registerAction(
|
|
110
|
+
ACTION_TYPES.CONFIRM_DIALOG,
|
|
111
|
+
confirmDialogAction
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
actionExecutor.registerAction(ACTION_TYPES.SHOW_ALERT, showAlertAction);
|
|
80
115
|
|
|
81
116
|
actionExecutor.registerAction(
|
|
82
|
-
|
|
117
|
+
ACTION_TYPES.SEND_CLOUD_EVENT,
|
|
118
|
+
sendCloudEventAction
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
actionExecutor.registerAction(
|
|
122
|
+
ACTION_TYPES.SESSION_STORAGE_TOGGLE_FLAG,
|
|
83
123
|
sessionStorageToggleFlagAction
|
|
84
124
|
);
|
|
85
125
|
|
|
86
126
|
actionExecutor.registerAction(
|
|
87
|
-
|
|
127
|
+
ACTION_TYPES.LOCAL_STORAGE_TOGGLE_FLAG,
|
|
88
128
|
localStorageToggleFlagAction
|
|
89
129
|
);
|
|
90
130
|
|
|
91
|
-
actionExecutor.registerAction(
|
|
92
|
-
|
|
93
|
-
|
|
131
|
+
actionExecutor.registerAction(
|
|
132
|
+
ACTION_TYPES.SCREEN_SET_VARIABLE,
|
|
133
|
+
screenSetVariableAction
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
actionExecutor.registerAction(
|
|
137
|
+
ACTION_TYPES.SCREEN_TOGGLE_FLAG,
|
|
138
|
+
screenToggleFlagAction
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
actionExecutor.registerAction(
|
|
142
|
+
ACTION_TYPES.OPEN_BOTTOM_SHEET,
|
|
143
|
+
openBottomSheetAction
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
actionExecutor.registerAction(
|
|
147
|
+
ACTION_TYPES.DISMISS_BOTTOM_SHEET,
|
|
148
|
+
dismissBottomSheetAction
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
actionExecutor.registerAction(ACTION_TYPES.SHOW_TOAST, showToastAction);
|
|
94
152
|
};
|
|
95
153
|
|
|
96
154
|
export const ActionExecutorContext =
|
|
@@ -103,11 +161,14 @@ export function withActionExecutor(Component) {
|
|
|
103
161
|
const navigator = useNavigation();
|
|
104
162
|
const rivers = useRivers();
|
|
105
163
|
const contentTypes = useContentTypes();
|
|
164
|
+
const presentScreen = usePresentScreen();
|
|
106
165
|
|
|
107
166
|
const handlers = useMemo(() => {
|
|
108
167
|
return {
|
|
109
168
|
unregisterAction: _actionExecutor.unregisterAction,
|
|
110
169
|
registerAction: _actionExecutor.registerAction,
|
|
170
|
+
registerActionState: _actionExecutor.registerActionState,
|
|
171
|
+
getActionState: _actionExecutor.getActionState,
|
|
111
172
|
handleAction: _actionExecutor.handleAction,
|
|
112
173
|
handleActions: _actionExecutor.handleActions,
|
|
113
174
|
handleEntryActions: _actionExecutor.handleEntryActions,
|
|
@@ -130,11 +191,32 @@ export function withActionExecutor(Component) {
|
|
|
130
191
|
|
|
131
192
|
useEffect(() => {
|
|
132
193
|
return _actionExecutor.registerAction(
|
|
133
|
-
|
|
194
|
+
ACTION_TYPES.NAVIGATE_TO_SCREEN,
|
|
134
195
|
createNavigateToScreenAction(navigator, rivers, contentTypes)
|
|
135
196
|
);
|
|
136
197
|
}, [navigator, rivers, contentTypes]);
|
|
137
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
|
+
|
|
138
220
|
return (
|
|
139
221
|
<ActionExecutorContext.Provider value={handlers}>
|
|
140
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,61 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { dismissBottomSheetAction } from "../dismissBottomSheet";
|
|
2
|
+
import { dismissModal } from "../../../modalState";
|
|
3
|
+
import { ActionResult } from "../../ActionExecutor";
|
|
4
|
+
|
|
5
|
+
jest.mock("../../../modalState", () => ({
|
|
6
|
+
dismissModal: jest.fn(),
|
|
7
|
+
modalOrchestrator: {
|
|
8
|
+
close: jest.fn(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe("dismissBottomSheetAction", () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("calls dismissModal and returns ActionResult.Success", async () => {
|
|
18
|
+
const result = await dismissBottomSheetAction({
|
|
19
|
+
type: "dismissBottomSheet",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(dismissModal).toHaveBeenCalledTimes(1);
|
|
23
|
+
expect(result).toBe(ActionResult.Success);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { loadItemsFromSource } from "../openBottomSheet";
|
|
2
|
+
|
|
3
|
+
const mockCall = jest.fn();
|
|
4
|
+
|
|
5
|
+
jest.mock("@applicaster/zapp-pipes-v2-client", () => ({
|
|
6
|
+
RequestBuilder: jest.fn().mockImplementation(() => ({
|
|
7
|
+
setEntryContext: jest.fn().mockReturnThis(),
|
|
8
|
+
setScreenContext: jest.fn().mockReturnThis(),
|
|
9
|
+
setUrl: jest.fn().mockReturnThis(),
|
|
10
|
+
buildAxiosRequest: jest.fn().mockResolvedValue(undefined),
|
|
11
|
+
call: (...args: any[]) => mockCall(...args),
|
|
12
|
+
})),
|
|
13
|
+
PipesClientResponseHelper: jest.fn().mockImplementation(() => ({
|
|
14
|
+
error: null,
|
|
15
|
+
})),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
describe("loadItemsFromSource", () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("returns items plus role and behavior from feed.extensions", async () => {
|
|
24
|
+
mockCall.mockResolvedValueOnce({
|
|
25
|
+
response: {
|
|
26
|
+
entry: [{ id: "a", title: "Playlist A", media_group: [] }],
|
|
27
|
+
extensions: {
|
|
28
|
+
role: "collection_selector",
|
|
29
|
+
behavior: {
|
|
30
|
+
select_mode: "multi",
|
|
31
|
+
current_selection: ["a"],
|
|
32
|
+
selector: "extensions.tag",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const result = await loadItemsFromSource("https://example.com/feed");
|
|
39
|
+
|
|
40
|
+
expect(result.role).toBe("collection_selector");
|
|
41
|
+
|
|
42
|
+
expect(result.behavior).toEqual({
|
|
43
|
+
selectMode: "multi",
|
|
44
|
+
currentSelection: ["a"],
|
|
45
|
+
selector: "extensions.tag",
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(result.items).toHaveLength(1);
|
|
49
|
+
expect(result.items[0].isSelected).toBe(true);
|
|
50
|
+
expect(result.items[0].title).toBe("Playlist A");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("returns undefined role/behavior when feed has no extensions", async () => {
|
|
54
|
+
mockCall.mockResolvedValueOnce({
|
|
55
|
+
response: {
|
|
56
|
+
entry: [{ id: "1", title: "Plain", media_group: [] }],
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const result = await loadItemsFromSource("https://example.com/plain");
|
|
61
|
+
|
|
62
|
+
expect(result.role).toBeUndefined();
|
|
63
|
+
expect(result.behavior).toBeUndefined();
|
|
64
|
+
expect(result.items).toHaveLength(1);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("preserves events in dynamic_collection_options", async () => {
|
|
68
|
+
mockCall.mockResolvedValueOnce({
|
|
69
|
+
response: {
|
|
70
|
+
entry: [],
|
|
71
|
+
extensions: {
|
|
72
|
+
dynamic_collection_options: {
|
|
73
|
+
operations: "add,remove",
|
|
74
|
+
events: {
|
|
75
|
+
add: [{ type: "showTextInput", options: {} }],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const result = await loadItemsFromSource("https://example.com/feed");
|
|
83
|
+
|
|
84
|
+
expect(result.dynamicCollection?.events).toEqual({
|
|
85
|
+
add: [{ type: "showTextInput", options: {} }],
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|