@applicaster/zapp-react-native-utils 16.0.0-rc.57 → 16.0.0-rc.58
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 +40 -2
- package/actionsExecutor/ActionExecutorContext.tsx +70 -16
- package/actionsExecutor/actions/__tests__/dismissBottomSheet.test.ts +22 -0
- package/actionsExecutor/actions/__tests__/loadItemsFromSource.test.ts +88 -0
- package/actionsExecutor/actions/__tests__/openBottomSheet.customContent.test.ts +76 -0
- package/actionsExecutor/actions/__tests__/openBottomSheet.inlineItems.test.ts +130 -0
- package/actionsExecutor/actions/dismissBottomSheet.ts +22 -0
- package/actionsExecutor/actions/index.ts +4 -0
- package/actionsExecutor/actions/openBottomSheet.ts +322 -0
- package/actionsExecutor/actions/refreshComponent.ts +6 -0
- package/actionsExecutor/actions/sendCloudEvent.ts +6 -1
- package/actionsExecutor/consts.ts +23 -0
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +5 -2
- package/appUtils/playerManager/OverlayObserver/utils.ts +46 -20
- package/arrayUtils/__tests__/reorderByIds.test.ts +50 -0
- package/arrayUtils/index.ts +79 -0
- package/configurationUtils/__tests__/modalBlocksParser.test.ts +107 -0
- package/configurationUtils/manifestKeyParser.ts +38 -9
- package/configurationUtils/modalBlocksParser.ts +178 -0
- 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 +201 -0
- package/modalState/RemoteEditableCollection.ts +227 -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 +55 -0
- package/modalState/__tests__/RemoteEditableCollection.test.ts +326 -0
- package/modalState/__tests__/useBottomSheetContent.test.ts +349 -0
- package/modalState/index.ts +30 -83
- package/modalState/store.ts +102 -0
- package/modalState/types.ts +137 -0
- package/modalState/useBottomSheetContent.ts +102 -0
- package/package.json +2 -2
- package/reactHooks/index.ts +2 -0
- package/reactHooks/usePluginConfiguration.ts +4 -1
- package/reactHooks/utils/__tests__/index.test.js +22 -2
- package/reactHooks/utils/index.ts +11 -1
- /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
|
|
@@ -101,7 +101,8 @@ class ActionExecutor {
|
|
|
101
101
|
|
|
102
102
|
registerAction = <TOptions = Record<string, any>>(
|
|
103
103
|
type: string,
|
|
104
|
-
handler: ActionHandler<TOptions
|
|
104
|
+
handler: ActionHandler<TOptions>,
|
|
105
|
+
stateResolver?: (entry?: ZappEntry) => any
|
|
105
106
|
) => {
|
|
106
107
|
if (this.registeredActions[type]) {
|
|
107
108
|
log_error(`registerAction: action type: ${type} already registered`);
|
|
@@ -112,11 +113,48 @@ class ActionExecutor {
|
|
|
112
113
|
this.registeredActions[type] = handler;
|
|
113
114
|
log_debug(`registerAction: action type: ${type} registered`);
|
|
114
115
|
|
|
116
|
+
let unregisterState = () => {};
|
|
117
|
+
|
|
118
|
+
if (stateResolver) {
|
|
119
|
+
unregisterState = this.registerActionState(type, stateResolver);
|
|
120
|
+
}
|
|
121
|
+
|
|
115
122
|
return () => {
|
|
116
123
|
this.unregisterAction(type);
|
|
124
|
+
unregisterState();
|
|
117
125
|
log_debug(`registerAction: action type: ${type} un-registered`);
|
|
118
126
|
};
|
|
119
127
|
};
|
|
128
|
+
|
|
129
|
+
private actionStates: Record<string, (entry?: ZappEntry) => any> = {};
|
|
130
|
+
|
|
131
|
+
registerActionState = (
|
|
132
|
+
type: string,
|
|
133
|
+
stateResolver: (entry?: ZappEntry) => any
|
|
134
|
+
) => {
|
|
135
|
+
if (this.actionStates[type]) {
|
|
136
|
+
log_error(
|
|
137
|
+
`registerActionState: action type state: ${type} already registered`
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return () => {};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.actionStates[type] = stateResolver;
|
|
144
|
+
log_debug(`registerActionState: action type state: ${type} registered`);
|
|
145
|
+
|
|
146
|
+
return () => {
|
|
147
|
+
delete this.actionStates[type];
|
|
148
|
+
|
|
149
|
+
log_debug(
|
|
150
|
+
`registerActionState: action type state: ${type} un-registered`
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
getActionState = (type: string, entry?: ZappEntry) => {
|
|
156
|
+
return this.actionStates[type]?.(entry);
|
|
157
|
+
};
|
|
120
158
|
}
|
|
121
159
|
|
|
122
160
|
export const actionExecutor = new ActionExecutor();
|
|
@@ -19,9 +19,11 @@ import {
|
|
|
19
19
|
appRestartAction,
|
|
20
20
|
confirmDialogAction,
|
|
21
21
|
createNavigateToScreenAction,
|
|
22
|
+
dismissBottomSheetAction,
|
|
22
23
|
localStorageRemoveAction,
|
|
23
24
|
localStorageSetAction,
|
|
24
25
|
localStorageToggleFlagAction,
|
|
26
|
+
openBottomSheetAction,
|
|
25
27
|
refreshComponentAction,
|
|
26
28
|
screenSetVariableAction,
|
|
27
29
|
screenToggleFlagAction,
|
|
@@ -33,6 +35,8 @@ import {
|
|
|
33
35
|
showToastAction,
|
|
34
36
|
} from "./actions";
|
|
35
37
|
|
|
38
|
+
import { ACTION_TYPES } from "./consts";
|
|
39
|
+
|
|
36
40
|
export const { log_error, log_info, log_debug } = createLogger({
|
|
37
41
|
subsystem: "ActionExecutorContext",
|
|
38
42
|
category: "General",
|
|
@@ -41,9 +45,15 @@ export const { log_error, log_info, log_debug } = createLogger({
|
|
|
41
45
|
export type ActionExecutorContextType = {
|
|
42
46
|
registerAction: <TOptions = Record<string, any>>(
|
|
43
47
|
type: string,
|
|
44
|
-
handler: ActionHandler<TOptions
|
|
48
|
+
handler: ActionHandler<TOptions>,
|
|
49
|
+
stateResolver?: (entry?: ZappEntry) => any
|
|
45
50
|
) => void;
|
|
46
51
|
unregisterAction: (type: string) => void;
|
|
52
|
+
registerActionState: (
|
|
53
|
+
type: string,
|
|
54
|
+
stateResolver: (entry?: ZappEntry) => any
|
|
55
|
+
) => () => void;
|
|
56
|
+
getActionState: (type: string, entry?: ZappEntry) => any;
|
|
47
57
|
handleAction: (
|
|
48
58
|
action: ActionType,
|
|
49
59
|
context?: ActionExecutionContext
|
|
@@ -63,34 +73,76 @@ type Props = {
|
|
|
63
73
|
};
|
|
64
74
|
|
|
65
75
|
const prepareDefaultActions = (actionExecutor) => {
|
|
66
|
-
actionExecutor.registerAction(
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
actionExecutor.registerAction(
|
|
77
|
+
ACTION_TYPES.LOCAL_STORAGE_SET,
|
|
78
|
+
localStorageSetAction
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
actionExecutor.registerAction(
|
|
82
|
+
ACTION_TYPES.SESSION_STORAGE_SET,
|
|
83
|
+
sessionStorageSetAction
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
actionExecutor.registerAction(
|
|
87
|
+
ACTION_TYPES.LOCAL_STORAGE_REMOVE,
|
|
88
|
+
localStorageRemoveAction
|
|
89
|
+
);
|
|
69
90
|
|
|
70
91
|
actionExecutor.registerAction(
|
|
71
|
-
|
|
92
|
+
ACTION_TYPES.SESSION_STORAGE_REMOVE,
|
|
72
93
|
sessionStorageRemoveAction
|
|
73
94
|
);
|
|
74
95
|
|
|
75
|
-
actionExecutor.registerAction(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
actionExecutor.registerAction(
|
|
97
|
+
ACTION_TYPES.REFRESH_COMPONENT,
|
|
98
|
+
refreshComponentAction
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
actionExecutor.registerAction(ACTION_TYPES.SWITCH_LAYOUT, switchLayoutAction);
|
|
102
|
+
|
|
103
|
+
actionExecutor.registerAction(ACTION_TYPES.APP_RESTART, appRestartAction);
|
|
104
|
+
|
|
105
|
+
actionExecutor.registerAction(
|
|
106
|
+
ACTION_TYPES.CONFIRM_DIALOG,
|
|
107
|
+
confirmDialogAction
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
actionExecutor.registerAction(
|
|
111
|
+
ACTION_TYPES.SEND_CLOUD_EVENT,
|
|
112
|
+
sendCloudEventAction
|
|
113
|
+
);
|
|
80
114
|
|
|
81
115
|
actionExecutor.registerAction(
|
|
82
|
-
|
|
116
|
+
ACTION_TYPES.SESSION_STORAGE_TOGGLE_FLAG,
|
|
83
117
|
sessionStorageToggleFlagAction
|
|
84
118
|
);
|
|
85
119
|
|
|
86
120
|
actionExecutor.registerAction(
|
|
87
|
-
|
|
121
|
+
ACTION_TYPES.LOCAL_STORAGE_TOGGLE_FLAG,
|
|
88
122
|
localStorageToggleFlagAction
|
|
89
123
|
);
|
|
90
124
|
|
|
91
|
-
actionExecutor.registerAction(
|
|
92
|
-
|
|
93
|
-
|
|
125
|
+
actionExecutor.registerAction(
|
|
126
|
+
ACTION_TYPES.SCREEN_SET_VARIABLE,
|
|
127
|
+
screenSetVariableAction
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
actionExecutor.registerAction(
|
|
131
|
+
ACTION_TYPES.SCREEN_TOGGLE_FLAG,
|
|
132
|
+
screenToggleFlagAction
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
actionExecutor.registerAction(
|
|
136
|
+
ACTION_TYPES.OPEN_BOTTOM_SHEET,
|
|
137
|
+
openBottomSheetAction
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
actionExecutor.registerAction(
|
|
141
|
+
ACTION_TYPES.DISMISS_BOTTOM_SHEET,
|
|
142
|
+
dismissBottomSheetAction
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
actionExecutor.registerAction(ACTION_TYPES.SHOW_TOAST, showToastAction);
|
|
94
146
|
};
|
|
95
147
|
|
|
96
148
|
export const ActionExecutorContext =
|
|
@@ -108,6 +160,8 @@ export function withActionExecutor(Component) {
|
|
|
108
160
|
return {
|
|
109
161
|
unregisterAction: _actionExecutor.unregisterAction,
|
|
110
162
|
registerAction: _actionExecutor.registerAction,
|
|
163
|
+
registerActionState: _actionExecutor.registerActionState,
|
|
164
|
+
getActionState: _actionExecutor.getActionState,
|
|
111
165
|
handleAction: _actionExecutor.handleAction,
|
|
112
166
|
handleActions: _actionExecutor.handleActions,
|
|
113
167
|
handleEntryActions: _actionExecutor.handleEntryActions,
|
|
@@ -130,7 +184,7 @@ export function withActionExecutor(Component) {
|
|
|
130
184
|
|
|
131
185
|
useEffect(() => {
|
|
132
186
|
return _actionExecutor.registerAction(
|
|
133
|
-
|
|
187
|
+
ACTION_TYPES.NAVIGATE_TO_SCREEN,
|
|
134
188
|
createNavigateToScreenAction(navigator, rivers, contentTypes)
|
|
135
189
|
);
|
|
136
190
|
}, [navigator, rivers, contentTypes]);
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe("dismissBottomSheetAction", () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("calls dismissModal and returns ActionResult.Success", async () => {
|
|
15
|
+
const result = await dismissBottomSheetAction({
|
|
16
|
+
type: "dismissBottomSheet",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(dismissModal).toHaveBeenCalledTimes(1);
|
|
20
|
+
expect(result).toBe(ActionResult.Success);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { openBottomSheetAction } from "../openBottomSheet";
|
|
2
|
+
import { ActionResult } from "../../ActionExecutor";
|
|
3
|
+
import { openBottomSheetModal } from "../../../modalState/store";
|
|
4
|
+
import { modalOrchestrator } from "../../../modalState/ModalOrchestrator";
|
|
5
|
+
|
|
6
|
+
jest.mock("../../../modalState/store", () => ({
|
|
7
|
+
openBottomSheetModal: jest.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
jest.mock("../../../modalState/ModalOrchestrator", () => ({
|
|
11
|
+
modalOrchestrator: {
|
|
12
|
+
open: jest.fn(),
|
|
13
|
+
},
|
|
14
|
+
isPresentSubMenuAction: jest.requireActual("../../../modalState/types")
|
|
15
|
+
.isPresentSubMenuAction,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
describe("openBottomSheetAction custom content", () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("opens default sheet with contentComponent body props", async () => {
|
|
24
|
+
const CustomContent = function CustomContent() {
|
|
25
|
+
return null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const result = await openBottomSheetAction({
|
|
29
|
+
type: "openBottomSheet",
|
|
30
|
+
options: {
|
|
31
|
+
header: { title: "Create Playlist" },
|
|
32
|
+
content: {
|
|
33
|
+
component: CustomContent,
|
|
34
|
+
props: {
|
|
35
|
+
inputLabel: "Name your playlist",
|
|
36
|
+
buttonLabel: "Create",
|
|
37
|
+
defaultValue: "",
|
|
38
|
+
submitAction: { type: "sendCloudEvent", options: {} },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
} as any);
|
|
43
|
+
|
|
44
|
+
expect(result).toBe(ActionResult.Success);
|
|
45
|
+
expect(openBottomSheetModal).toHaveBeenCalledTimes(1);
|
|
46
|
+
expect(modalOrchestrator.open).not.toHaveBeenCalled();
|
|
47
|
+
|
|
48
|
+
const args = (openBottomSheetModal as jest.Mock).mock.calls[0][0];
|
|
49
|
+
// Default ModalComponent provides the standard header
|
|
50
|
+
expect(args.ModalBottomSheetContent).toBeUndefined();
|
|
51
|
+
|
|
52
|
+
expect(args.modalBottomSheetContentProps).toMatchObject({
|
|
53
|
+
title: "Create Playlist",
|
|
54
|
+
contentComponent: CustomContent,
|
|
55
|
+
contentComponentProps: {
|
|
56
|
+
inputLabel: "Name your playlist",
|
|
57
|
+
buttonLabel: "Create",
|
|
58
|
+
defaultValue: "",
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("returns Error when neither list data nor content.component is provided", async () => {
|
|
64
|
+
const result = await openBottomSheetAction({
|
|
65
|
+
type: "openBottomSheet",
|
|
66
|
+
options: {
|
|
67
|
+
header: { title: "Empty" },
|
|
68
|
+
content: {},
|
|
69
|
+
},
|
|
70
|
+
} as any);
|
|
71
|
+
|
|
72
|
+
expect(result).toBe(ActionResult.Error);
|
|
73
|
+
expect(openBottomSheetModal).not.toHaveBeenCalled();
|
|
74
|
+
expect(modalOrchestrator.open).not.toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { openBottomSheetAction } from "../openBottomSheet";
|
|
2
|
+
import { ActionResult } from "../../ActionExecutor";
|
|
3
|
+
import { modalOrchestrator } from "../../../modalState/ModalOrchestrator";
|
|
4
|
+
|
|
5
|
+
jest.mock("../../../modalState/store", () => ({
|
|
6
|
+
openBottomSheetModal: jest.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
jest.mock("../../../modalState/ModalOrchestrator", () => ({
|
|
10
|
+
modalOrchestrator: {
|
|
11
|
+
open: jest.fn(),
|
|
12
|
+
replace: jest.fn(),
|
|
13
|
+
stackSize: 0,
|
|
14
|
+
},
|
|
15
|
+
isPresentSubMenuAction: jest.requireActual("../../../modalState/types")
|
|
16
|
+
.isPresentSubMenuAction,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
describe("openBottomSheetAction inline items", () => {
|
|
20
|
+
const HeaderComponent = function HeaderComponent() {
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
(modalOrchestrator as any).stackSize = 0;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const speedSheetAction = {
|
|
30
|
+
type: "openBottomSheet",
|
|
31
|
+
options: {
|
|
32
|
+
header: { title: "Playback Speed" },
|
|
33
|
+
content: {
|
|
34
|
+
role: "collection_selector",
|
|
35
|
+
behavior: {
|
|
36
|
+
selectMode: "single",
|
|
37
|
+
currentSelection: ["1"],
|
|
38
|
+
},
|
|
39
|
+
items: [
|
|
40
|
+
{
|
|
41
|
+
id: "0.8",
|
|
42
|
+
title: "0.8x",
|
|
43
|
+
extensions: {
|
|
44
|
+
tap_actions: {
|
|
45
|
+
actions: [
|
|
46
|
+
{ type: "setPlaybackSpeed", options: { value: 0.8 } },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "1",
|
|
53
|
+
title: "1x",
|
|
54
|
+
extensions: {
|
|
55
|
+
tap_actions: {
|
|
56
|
+
actions: [{ type: "setPlaybackSpeed", options: { value: 1 } }],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
} as any;
|
|
64
|
+
|
|
65
|
+
it("opens orchestrator with inline items and marks current selection", async () => {
|
|
66
|
+
const result = await openBottomSheetAction(speedSheetAction);
|
|
67
|
+
|
|
68
|
+
expect(result).toBe(ActionResult.Success);
|
|
69
|
+
expect(modalOrchestrator.open).toHaveBeenCalledTimes(1);
|
|
70
|
+
expect(modalOrchestrator.replace).not.toHaveBeenCalled();
|
|
71
|
+
|
|
72
|
+
const [menu] = (modalOrchestrator.open as jest.Mock).mock.calls[0];
|
|
73
|
+
|
|
74
|
+
expect(menu.content.items[0].isSelected).toBe(false);
|
|
75
|
+
|
|
76
|
+
expect(menu.content.items[1]).toMatchObject({
|
|
77
|
+
id: "1",
|
|
78
|
+
title: "1x",
|
|
79
|
+
isSelected: true,
|
|
80
|
+
action: { type: "setPlaybackSpeed", options: { value: 1 } },
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("pushes a new sheet by default even when another sheet is already open", async () => {
|
|
85
|
+
(modalOrchestrator as any).stackSize = 1;
|
|
86
|
+
|
|
87
|
+
await openBottomSheetAction(speedSheetAction);
|
|
88
|
+
|
|
89
|
+
expect(modalOrchestrator.open).toHaveBeenCalledTimes(1);
|
|
90
|
+
expect(modalOrchestrator.replace).not.toHaveBeenCalled();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("replaces the current sheet when options.replace is true", async () => {
|
|
94
|
+
(modalOrchestrator as any).stackSize = 1;
|
|
95
|
+
|
|
96
|
+
await openBottomSheetAction({
|
|
97
|
+
...speedSheetAction,
|
|
98
|
+
options: { ...speedSheetAction.options, replace: true },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(modalOrchestrator.replace).toHaveBeenCalledTimes(1);
|
|
102
|
+
expect(modalOrchestrator.open).not.toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("forwards a custom header component and its props onto the menu", async () => {
|
|
106
|
+
await openBottomSheetAction({
|
|
107
|
+
type: "openBottomSheet",
|
|
108
|
+
options: {
|
|
109
|
+
header: {
|
|
110
|
+
title: "Sleep Timer",
|
|
111
|
+
component: HeaderComponent,
|
|
112
|
+
componentProps: { timerDate: 123 },
|
|
113
|
+
},
|
|
114
|
+
content: {
|
|
115
|
+
items: [{ id: "5", title: "5 minutes" }],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
} as any);
|
|
119
|
+
|
|
120
|
+
const [menu] = (modalOrchestrator.open as jest.Mock).mock.calls[0];
|
|
121
|
+
|
|
122
|
+
expect(menu.header).toEqual({
|
|
123
|
+
title: "Sleep Timer",
|
|
124
|
+
subtitle: undefined,
|
|
125
|
+
icon: undefined,
|
|
126
|
+
component: HeaderComponent,
|
|
127
|
+
componentProps: { timerDate: 123 },
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/// <reference types="@applicaster/applicaster-types" />
|
|
2
|
+
import { dismissModal } from "../../modalState";
|
|
3
|
+
import { ActionResult } from "../ActionExecutor";
|
|
4
|
+
import { ActionHandler } from "../types";
|
|
5
|
+
import { createLogger } from "../../logger";
|
|
6
|
+
|
|
7
|
+
const { log_info } = createLogger({
|
|
8
|
+
subsystem: "ActionExecutorContext",
|
|
9
|
+
category: "General",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Dismisses the active bottom sheet / modal.
|
|
14
|
+
*/
|
|
15
|
+
export const dismissBottomSheetAction: ActionHandler<
|
|
16
|
+
any
|
|
17
|
+
> = async (): Promise<ActionResult> => {
|
|
18
|
+
log_info("handleAction: dismissBottomSheet");
|
|
19
|
+
dismissModal();
|
|
20
|
+
|
|
21
|
+
return ActionResult.Success;
|
|
22
|
+
};
|
|
@@ -28,3 +28,7 @@ export { screenToggleFlagAction } from "./screenToggleFlag";
|
|
|
28
28
|
export { createNavigateToScreenAction } from "./navigateToScreen";
|
|
29
29
|
|
|
30
30
|
export { showToastAction } from "./showToast";
|
|
31
|
+
|
|
32
|
+
export { openBottomSheetAction } from "./openBottomSheet";
|
|
33
|
+
|
|
34
|
+
export { dismissBottomSheetAction } from "./dismissBottomSheet";
|