@applicaster/zapp-react-native-utils 16.0.0-rc.97 → 16.0.0-rc.98
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/actions/__tests__/openBottomSheet.inlineItems.test.ts +88 -0
- package/actionsExecutor/actions/openBottomSheet.ts +14 -5
- package/appUtils/playerManager/OverlayObserver/__tests__/getUpcomingQueue.test.ts +67 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ts +15 -2
- package/modalState/ModalOrchestrator.ts +1 -0
- package/modalState/__tests__/ModalOrchestrator.phase3.test.ts +16 -0
- package/modalState/types.ts +2 -0
- package/package.json +2 -2
|
@@ -77,6 +77,94 @@ describe("openBottomSheetAction inline items", () => {
|
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
it("opens an empty inline collection when dynamic collection options are present", async () => {
|
|
81
|
+
const result = await openBottomSheetAction({
|
|
82
|
+
type: "openBottomSheet",
|
|
83
|
+
options: {
|
|
84
|
+
header: { title: "Queue" },
|
|
85
|
+
content: {
|
|
86
|
+
items: [],
|
|
87
|
+
emptyMessage: "Queued items will be added here",
|
|
88
|
+
dynamicCollection: {
|
|
89
|
+
operations: ["remove"],
|
|
90
|
+
provider: "@{provider/queue}",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
} as any);
|
|
95
|
+
|
|
96
|
+
expect(result).toBe(ActionResult.Success);
|
|
97
|
+
|
|
98
|
+
const [menu] = (modalOrchestrator.open as jest.Mock).mock.calls[0];
|
|
99
|
+
|
|
100
|
+
expect(menu.content.items).toEqual([]);
|
|
101
|
+
expect(menu.content.emptyMessage).toBe("Queued items will be added here");
|
|
102
|
+
|
|
103
|
+
expect(menu.content.dynamicCollection).toMatchObject({
|
|
104
|
+
operations: ["remove"],
|
|
105
|
+
provider: "@{provider/queue}",
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("accepts empty inline items when dynamic_collection_options live on extensions", async () => {
|
|
110
|
+
const result = await openBottomSheetAction({
|
|
111
|
+
type: "openBottomSheet",
|
|
112
|
+
options: {
|
|
113
|
+
content: {
|
|
114
|
+
items: [],
|
|
115
|
+
extensions: {
|
|
116
|
+
dynamic_collection_options: {
|
|
117
|
+
operations: "remove,reorder",
|
|
118
|
+
provider: "@{provider/offline}",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
} as any);
|
|
124
|
+
|
|
125
|
+
expect(result).toBe(ActionResult.Success);
|
|
126
|
+
|
|
127
|
+
const [menu] = (modalOrchestrator.open as jest.Mock).mock.calls[0];
|
|
128
|
+
|
|
129
|
+
expect(menu.content.items).toEqual([]);
|
|
130
|
+
|
|
131
|
+
expect(menu.content.dynamicCollection).toMatchObject({
|
|
132
|
+
operations: ["remove", "reorder"],
|
|
133
|
+
provider: "@{provider/offline}",
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("returns an error for an empty items array with no source", async () => {
|
|
138
|
+
const result = await openBottomSheetAction({
|
|
139
|
+
type: "openBottomSheet",
|
|
140
|
+
options: {
|
|
141
|
+
content: { items: [] },
|
|
142
|
+
},
|
|
143
|
+
} as any);
|
|
144
|
+
|
|
145
|
+
expect(result).toBe(ActionResult.Error);
|
|
146
|
+
expect(modalOrchestrator.open).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("forwards content.emptyMessage onto the menu", async () => {
|
|
150
|
+
const result = await openBottomSheetAction({
|
|
151
|
+
type: "openBottomSheet",
|
|
152
|
+
options: {
|
|
153
|
+
header: { title: "Queue" },
|
|
154
|
+
content: {
|
|
155
|
+
items: [{ id: "t1", title: "Track 1" }],
|
|
156
|
+
emptyMessage: "Queued items will be added here",
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
} as any);
|
|
160
|
+
|
|
161
|
+
expect(result).toBe(ActionResult.Success);
|
|
162
|
+
|
|
163
|
+
const [menu] = (modalOrchestrator.open as jest.Mock).mock.calls[0];
|
|
164
|
+
|
|
165
|
+
expect(menu.content.emptyMessage).toBe("Queued items will be added here");
|
|
166
|
+
});
|
|
167
|
+
|
|
80
168
|
it("pushes a new sheet by default even when another sheet is already open", async () => {
|
|
81
169
|
(modalOrchestrator as any).stackSize = 1;
|
|
82
170
|
|
|
@@ -227,7 +227,18 @@ export async function openBottomSheetAction(
|
|
|
227
227
|
|
|
228
228
|
let dynamicCollection: DynamicCollectionOptions | undefined;
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
const rawDynamicCollection =
|
|
231
|
+
content?.dynamicCollection ??
|
|
232
|
+
content?.extensions?.dynamic_collection_options;
|
|
233
|
+
|
|
234
|
+
// An empty items array is a valid starting state for a provider-backed or
|
|
235
|
+
// offline collection. A sheet with neither items nor a source still errors.
|
|
236
|
+
const hasDynamicCollection = Boolean(rawDynamicCollection);
|
|
237
|
+
|
|
238
|
+
if (
|
|
239
|
+
Array.isArray(inlineItems) &&
|
|
240
|
+
(inlineItems.length > 0 || hasDynamicCollection)
|
|
241
|
+
) {
|
|
231
242
|
log_info("openBottomSheet: using inline items from action options", {
|
|
232
243
|
count: inlineItems.length,
|
|
233
244
|
});
|
|
@@ -239,10 +250,7 @@ export async function openBottomSheetAction(
|
|
|
239
250
|
entryToMenuItem(entry, behavior?.currentSelection ?? [])
|
|
240
251
|
);
|
|
241
252
|
|
|
242
|
-
dynamicCollection = parseDynamicCollectionOptions(
|
|
243
|
-
content?.dynamicCollection ??
|
|
244
|
-
content?.extensions?.dynamic_collection_options
|
|
245
|
-
);
|
|
253
|
+
dynamicCollection = parseDynamicCollectionOptions(rawDynamicCollection);
|
|
246
254
|
} else if (itemsUrl) {
|
|
247
255
|
log_info(`openBottomSheet: loading items from source: ${itemsUrl}`);
|
|
248
256
|
|
|
@@ -280,6 +288,7 @@ export async function openBottomSheetAction(
|
|
|
280
288
|
dynamicCollection,
|
|
281
289
|
themePluginId,
|
|
282
290
|
styleOverrides: styleOverrides ?? content?.styleOverrides,
|
|
291
|
+
emptyMessage: content?.emptyMessage,
|
|
283
292
|
context,
|
|
284
293
|
},
|
|
285
294
|
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getUpcomingQueue } from "../getUpcomingQueue";
|
|
2
|
+
|
|
3
|
+
describe("getUpcomingQueue", () => {
|
|
4
|
+
it("returns null when plugin is not provided or has no module", async () => {
|
|
5
|
+
expect(await getUpcomingQueue(undefined)).toBeNull();
|
|
6
|
+
expect(await getUpcomingQueue({} as any)).toBeNull();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("returns null when QueueManager is not present on module", async () => {
|
|
10
|
+
const plugin = {
|
|
11
|
+
identifier: "queue-action",
|
|
12
|
+
module: {},
|
|
13
|
+
} as any;
|
|
14
|
+
|
|
15
|
+
expect(await getUpcomingQueue(plugin)).toBeNull();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("calls setupQuickBrickQueue when QueueManager is not initialized", async () => {
|
|
19
|
+
const setupQuickBrickQueue = jest.fn();
|
|
20
|
+
const mockUpcoming = [{ id: "item_1" }];
|
|
21
|
+
|
|
22
|
+
const QueueManager = {
|
|
23
|
+
isInitialized: jest
|
|
24
|
+
.fn()
|
|
25
|
+
.mockReturnValueOnce(false)
|
|
26
|
+
.mockReturnValueOnce(true),
|
|
27
|
+
instance: {
|
|
28
|
+
getUpcomingQueue: jest.fn().mockResolvedValue(mockUpcoming),
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const plugin = {
|
|
33
|
+
identifier: "queue-action",
|
|
34
|
+
module: {
|
|
35
|
+
QueueManager,
|
|
36
|
+
setupQuickBrickQueue,
|
|
37
|
+
},
|
|
38
|
+
} as any;
|
|
39
|
+
|
|
40
|
+
const result = await getUpcomingQueue(plugin);
|
|
41
|
+
|
|
42
|
+
expect(setupQuickBrickQueue).toHaveBeenCalled();
|
|
43
|
+
expect(result).toEqual(mockUpcoming);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns upcoming items when QueueManager is already initialized", async () => {
|
|
47
|
+
const mockUpcoming = [{ id: "item_1" }, { id: "item_2" }];
|
|
48
|
+
|
|
49
|
+
const QueueManager = {
|
|
50
|
+
isInitialized: jest.fn().mockReturnValue(true),
|
|
51
|
+
instance: {
|
|
52
|
+
getUpcomingQueue: jest.fn().mockResolvedValue(mockUpcoming),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const plugin = {
|
|
57
|
+
identifier: "queue-action",
|
|
58
|
+
module: {
|
|
59
|
+
QueueManager,
|
|
60
|
+
},
|
|
61
|
+
} as any;
|
|
62
|
+
|
|
63
|
+
const result = await getUpcomingQueue(plugin);
|
|
64
|
+
|
|
65
|
+
expect(result).toEqual(mockUpcoming);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
export const getUpcomingQueue = async (
|
|
2
2
|
plugin?: QuickBrickPlugin | ZappPlugin
|
|
3
3
|
): Promise<unknown[] | null> => {
|
|
4
|
-
const
|
|
4
|
+
const module = (plugin as any)?.module;
|
|
5
|
+
const QueueManager = module?.QueueManager;
|
|
6
|
+
const setupQuickBrickQueue = module?.setupQuickBrickQueue;
|
|
5
7
|
|
|
6
|
-
if (!QueueManager
|
|
8
|
+
if (!QueueManager) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (
|
|
13
|
+
!QueueManager.isInitialized?.() &&
|
|
14
|
+
typeof setupQuickBrickQueue === "function"
|
|
15
|
+
) {
|
|
16
|
+
setupQuickBrickQueue();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!QueueManager.isInitialized?.()) {
|
|
7
20
|
return null;
|
|
8
21
|
}
|
|
9
22
|
|
|
@@ -44,4 +44,20 @@ describe("ModalOrchestrator phase 3", () => {
|
|
|
44
44
|
expect(props.title).toBe("Sleep Timer");
|
|
45
45
|
expect(props.summary).toBe("remaining");
|
|
46
46
|
});
|
|
47
|
+
|
|
48
|
+
it("forwards the queue empty message onto the sheet props", () => {
|
|
49
|
+
modalOrchestrator.open({
|
|
50
|
+
header: { title: "Queue" },
|
|
51
|
+
content: {
|
|
52
|
+
title: "Queue",
|
|
53
|
+
items: [],
|
|
54
|
+
emptyMessage: "Queued items will be added here",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const props = (modalStore.getState().modalState.screen as any)
|
|
59
|
+
?.modalBottomSheetContentProps;
|
|
60
|
+
|
|
61
|
+
expect(props.emptyMessage).toBe("Queued items will be added here");
|
|
62
|
+
});
|
|
47
63
|
});
|
package/modalState/types.ts
CHANGED
|
@@ -118,6 +118,8 @@ export interface Content {
|
|
|
118
118
|
dynamicCollection?: DynamicCollectionOptions;
|
|
119
119
|
themePluginId?: string;
|
|
120
120
|
styleOverrides?: Record<string, any>;
|
|
121
|
+
/** Shown under Next Up when the queue sheet has no upcoming items. */
|
|
122
|
+
emptyMessage?: string;
|
|
121
123
|
context?: Record<string, any>;
|
|
122
124
|
}
|
|
123
125
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "16.0.0-rc.
|
|
3
|
+
"version": "16.0.0-rc.98",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@applicaster/applicaster-types": "16.0.0-rc.
|
|
30
|
+
"@applicaster/applicaster-types": "16.0.0-rc.98",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|