@applicaster/zapp-react-native-ui-components 15.1.22-rc.0 → 15.1.22
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.
|
@@ -17,6 +17,9 @@ import {
|
|
|
17
17
|
Text,
|
|
18
18
|
View,
|
|
19
19
|
} from "react-native";
|
|
20
|
+
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
21
|
+
|
|
22
|
+
const logger = createLogger({ category: "ModalComponent:AudioPlayerButton" });
|
|
20
23
|
|
|
21
24
|
const styles = StyleSheet.create({
|
|
22
25
|
container: {
|
|
@@ -130,6 +133,28 @@ export const DEFAULT_ICONS_CONFIGURATION: IconsConfiguration = {
|
|
|
130
133
|
},
|
|
131
134
|
};
|
|
132
135
|
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Variant configurations
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
export const VARIANT_TEXT_ONLY: ButtonConfiguration = {
|
|
141
|
+
background: DEFAULT_BACKGROUND_CONFIGURATION,
|
|
142
|
+
title: DEFAULT_TITLE_CONFIGURATION,
|
|
143
|
+
icons: DEFAULT_ICONS_CONFIGURATION,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const VARIANT_WITH_ICON: ButtonConfiguration = {
|
|
147
|
+
background: DEFAULT_BACKGROUND_CONFIGURATION,
|
|
148
|
+
title: DEFAULT_TITLE_CONFIGURATION,
|
|
149
|
+
icons: {
|
|
150
|
+
...DEFAULT_ICONS_CONFIGURATION,
|
|
151
|
+
leadingIcon: {
|
|
152
|
+
enabled: true,
|
|
153
|
+
actionType: "addToPlaylist",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
133
158
|
// ---------------------------------------------------------------------------
|
|
134
159
|
// Data (runtime / localization — not styling config)
|
|
135
160
|
// ---------------------------------------------------------------------------
|
|
@@ -140,30 +165,15 @@ export type ButtonData = {
|
|
|
140
165
|
};
|
|
141
166
|
|
|
142
167
|
export type AudioPlayerButtonProps = {
|
|
143
|
-
configuration
|
|
144
|
-
data
|
|
168
|
+
configuration?: ButtonConfiguration;
|
|
169
|
+
data?: ButtonData;
|
|
145
170
|
disabled?: boolean;
|
|
146
171
|
focused?: boolean;
|
|
147
172
|
onPress?: () => void;
|
|
148
173
|
};
|
|
149
174
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
|
|
154
|
-
// Hardcoded: action type → icon asset mapping (base64 PNG)
|
|
155
|
-
const ACTION_ICON_ASSETS: Record<ButtonActionType, string> = {
|
|
156
|
-
addToQueue:
|
|
157
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAKUlEQVR42mNgGAVDCfxHw6MWjFpAvEGk4lELKLdgNJmOWkA7C0YBbQAAwiZvkR9eFgQAAAAASUVORK5CYII=",
|
|
158
|
-
addToPlaylist:
|
|
159
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAJklEQVR42mNgGAUjFvzHgmluATF4cFswGsmjkTwayaORPAqGLwAA6Jpxj9Ms9QMAAAAASUVORK5CYII=",
|
|
160
|
-
goToPlaylist:
|
|
161
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAALklEQVR42mNgGAWjYNiD/7Q2/D8tLflPiSX/ycQ0t+D/yLGAEkeMWjAKRgG9AQB4zlSshwSzWwAAAABJRU5ErkJggg==",
|
|
162
|
-
confirm:
|
|
163
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAJElEQVR42mNgGAWjYBSQCv4TwKMWDHELho/hNE/3o2AUDDcAAPv0QMAyy1CtAAAAAElFTkSuQmCC",
|
|
164
|
-
cancel:
|
|
165
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAASElEQVR42mNgGAWjGvynkVoUTf+pqA6nxv8UqiHbEooNx2cQ1QzHZiDVDcdlCcOQsoCmQUTTSKZpMqV5RqNLUUHTwm4UDBMAALBLQ73LTvoyAAAAAElFTkSuQmCC",
|
|
166
|
-
};
|
|
175
|
+
const PLACEHOLDER_RED_BOX =
|
|
176
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAJ0lEQVR4nO3NMQEAAAjDsIF/z2ACvlRAU5NMHuvPOQAAAAAAAMBdC/07Ai5sNw+CAAAAAElFTkSuQmCC";
|
|
167
177
|
|
|
168
178
|
function getTextTransformStyle(
|
|
169
179
|
textTransform: TextTransform
|
|
@@ -276,13 +286,19 @@ type ButtonLeadingIconProps = {
|
|
|
276
286
|
configuration: IconsConfiguration;
|
|
277
287
|
};
|
|
278
288
|
|
|
279
|
-
function ButtonLeadingIcon({
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
289
|
+
function ButtonLeadingIcon({ configuration }: ButtonLeadingIconProps) {
|
|
290
|
+
const asset = configuration.leadingIcon.asset || PLACEHOLDER_RED_BOX;
|
|
291
|
+
|
|
292
|
+
React.useEffect(() => {
|
|
293
|
+
if (!configuration.leadingIcon.asset) {
|
|
294
|
+
logger.log_warning(
|
|
295
|
+
"AudioPlayerButton has leadingIcon enabled but no asset was provided. Falling back to placeholder red box. Ensure the relevant plugin (e.g. playlist-experience) provides leadingIcon.asset in styleOverrides.",
|
|
296
|
+
{
|
|
297
|
+
leadingIcon: configuration.leadingIcon,
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}, [configuration.leadingIcon.asset]);
|
|
286
302
|
|
|
287
303
|
if (!asset) {
|
|
288
304
|
return null;
|
|
@@ -308,13 +324,14 @@ import { useModalBlocksStyle } from "../../ModalBlocksStyleContext";
|
|
|
308
324
|
import { mergeButtonConfiguration } from "../utils/mergeStyles";
|
|
309
325
|
|
|
310
326
|
export function AudioPlayerButton({
|
|
311
|
-
configuration,
|
|
312
|
-
data,
|
|
327
|
+
configuration = VARIANT_WITH_ICON,
|
|
328
|
+
data = { title: undefined },
|
|
313
329
|
disabled,
|
|
314
330
|
focused = false,
|
|
315
331
|
onPress,
|
|
316
332
|
}: AudioPlayerButtonProps) {
|
|
317
333
|
const modalBlocksStyle = useModalBlocksStyle();
|
|
334
|
+
|
|
318
335
|
const buttonBlock = modalBlocksStyle?.button;
|
|
319
336
|
const actionBlock = modalBlocksStyle?.action;
|
|
320
337
|
|
|
@@ -328,9 +345,22 @@ export function AudioPlayerButton({
|
|
|
328
345
|
icons: iconsConfiguration,
|
|
329
346
|
} = effectiveConfig;
|
|
330
347
|
|
|
331
|
-
const
|
|
348
|
+
const title = data.title;
|
|
349
|
+
|
|
332
350
|
const isDisabled = disabled ?? data.disabled ?? false;
|
|
333
351
|
|
|
352
|
+
React.useEffect(() => {
|
|
353
|
+
if (!title) {
|
|
354
|
+
logger.log_warning(
|
|
355
|
+
"AudioPlayerButton rendering with empty title. Ensure data.title is set.",
|
|
356
|
+
{
|
|
357
|
+
leadingIcon: iconsConfiguration.leadingIcon,
|
|
358
|
+
modalBlocksStyle: modalBlocksStyle,
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}, [title]);
|
|
363
|
+
|
|
334
364
|
const { leadingIcon } = iconsConfiguration;
|
|
335
365
|
const showIcon = shouldShowLeadingIcon(leadingIcon);
|
|
336
366
|
|
|
@@ -361,25 +391,3 @@ export function AudioPlayerButton({
|
|
|
361
391
|
</ButtonContainer>
|
|
362
392
|
);
|
|
363
393
|
}
|
|
364
|
-
|
|
365
|
-
// ---------------------------------------------------------------------------
|
|
366
|
-
// Variant configurations
|
|
367
|
-
// ---------------------------------------------------------------------------
|
|
368
|
-
|
|
369
|
-
export const VARIANT_TEXT_ONLY: ButtonConfiguration = {
|
|
370
|
-
background: DEFAULT_BACKGROUND_CONFIGURATION,
|
|
371
|
-
title: DEFAULT_TITLE_CONFIGURATION,
|
|
372
|
-
icons: DEFAULT_ICONS_CONFIGURATION,
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
export const VARIANT_WITH_ICON: ButtonConfiguration = {
|
|
376
|
-
background: DEFAULT_BACKGROUND_CONFIGURATION,
|
|
377
|
-
title: DEFAULT_TITLE_CONFIGURATION,
|
|
378
|
-
icons: {
|
|
379
|
-
...DEFAULT_ICONS_CONFIGURATION,
|
|
380
|
-
leadingIcon: {
|
|
381
|
-
enabled: true,
|
|
382
|
-
actionType: "addToPlaylist",
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
};
|
|
@@ -22,6 +22,7 @@ import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
|
22
22
|
import {
|
|
23
23
|
ContentViewModel,
|
|
24
24
|
OPERATIONS,
|
|
25
|
+
type OperationButtons,
|
|
25
26
|
hasOperation,
|
|
26
27
|
useBottomSheetContent,
|
|
27
28
|
} from "@applicaster/zapp-react-native-utils/modalState";
|
|
@@ -33,7 +34,6 @@ import { SortableList } from "./SortableList";
|
|
|
33
34
|
import {
|
|
34
35
|
AudioPlayerButton,
|
|
35
36
|
NowPlayingHeaderSection,
|
|
36
|
-
VARIANT_WITH_ICON,
|
|
37
37
|
} from "./AudioPlayer/Components";
|
|
38
38
|
import { BottomSheetDragArea } from "./BottomSheetDragArea";
|
|
39
39
|
import { ModalBlocksStyleContext } from "./ModalBlocksStyleContext";
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
parseModalBlocksConfiguration,
|
|
44
44
|
} from "@applicaster/zapp-react-native-utils/configurationUtils/modalBlocksParser";
|
|
45
45
|
import { useLocalizedStrings } from "@applicaster/zapp-react-native-utils/localizationUtils";
|
|
46
|
+
import { VARIANT_TEXT_ONLY } from "./AudioPlayer/Components/Button";
|
|
46
47
|
|
|
47
48
|
const styles = StyleSheet.create({
|
|
48
49
|
centered: {
|
|
@@ -96,6 +97,8 @@ type ModalComponentProps = {
|
|
|
96
97
|
measurementPhase?: boolean;
|
|
97
98
|
/** Copy under Next Up when the queue has no upcoming items. */
|
|
98
99
|
emptyMessage?: string;
|
|
100
|
+
/** Producer-owned title/iconURL for sheet-synthesized operation controls. */
|
|
101
|
+
operationButtons?: OperationButtons;
|
|
99
102
|
};
|
|
100
103
|
|
|
101
104
|
function _BottomSheetModalContent(props: ModalComponentProps) {
|
|
@@ -120,6 +123,7 @@ function _BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
120
123
|
themePluginId: themePluginIdProp,
|
|
121
124
|
measurementPhase = false,
|
|
122
125
|
emptyMessage,
|
|
126
|
+
operationButtons,
|
|
123
127
|
} = props;
|
|
124
128
|
|
|
125
129
|
const route = useRef(currentRoute);
|
|
@@ -174,13 +178,32 @@ function _BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
174
178
|
|
|
175
179
|
const normalizedPluginId = themePluginId?.replace(/-/g, "_");
|
|
176
180
|
|
|
181
|
+
const addLabel = operationButtons?.add?.title || "Create New";
|
|
182
|
+
const addIcon = operationButtons?.add?.iconURL;
|
|
183
|
+
|
|
184
|
+
const addButtonConfiguration = useMemo(() => {
|
|
185
|
+
const leadingIcon =
|
|
186
|
+
styleOverrides?.button?.leadingIcon ||
|
|
187
|
+
styleOverrides?.action?.leadingIcon;
|
|
188
|
+
|
|
189
|
+
const configuredIcon = leadingIcon?.default?.asset;
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
...VARIANT_TEXT_ONLY,
|
|
193
|
+
icons: {
|
|
194
|
+
...VARIANT_TEXT_ONLY.icons,
|
|
195
|
+
leadingIcon: {
|
|
196
|
+
enabled: !!(addIcon || configuredIcon),
|
|
197
|
+
asset: addIcon,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}, [addIcon, styleOverrides]);
|
|
202
|
+
|
|
177
203
|
const localizedHeaderTitle = normalizedPluginId
|
|
178
204
|
? pluginLocalizations?.[`${normalizedPluginId}_header_title`]
|
|
179
205
|
: undefined;
|
|
180
206
|
|
|
181
|
-
const createPlaylistTitle =
|
|
182
|
-
pluginLocalizations?.create_new_playlist_title || "Create New Playlist";
|
|
183
|
-
|
|
184
207
|
const nextUpTitle =
|
|
185
208
|
pluginLocalizations?.next_up_title ||
|
|
186
209
|
(normalizedPluginId
|
|
@@ -333,12 +356,9 @@ function _BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
333
356
|
editableCollection?.deleteCollection?.();
|
|
334
357
|
}, [editableCollection]);
|
|
335
358
|
|
|
336
|
-
const
|
|
337
|
-
editableCollection?.add?.(
|
|
338
|
-
|
|
339
|
-
title: createPlaylistTitle,
|
|
340
|
-
});
|
|
341
|
-
}, [editableCollection, createPlaylistTitle]);
|
|
359
|
+
const handleAdd = useCallback(() => {
|
|
360
|
+
editableCollection?.add?.();
|
|
361
|
+
}, [editableCollection]);
|
|
342
362
|
|
|
343
363
|
const headers = useMemo(() => {
|
|
344
364
|
const list: Array<{ index: number; component: () => React.ReactElement }> =
|
|
@@ -368,9 +388,9 @@ function _BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
368
388
|
style={{ paddingHorizontal: 20, paddingTop: 8, paddingBottom: 12 }}
|
|
369
389
|
>
|
|
370
390
|
<AudioPlayerButton
|
|
371
|
-
configuration={
|
|
372
|
-
data={{ title:
|
|
373
|
-
onPress={
|
|
391
|
+
configuration={addButtonConfiguration}
|
|
392
|
+
data={{ title: addLabel }}
|
|
393
|
+
onPress={handleAdd}
|
|
374
394
|
/>
|
|
375
395
|
</View>
|
|
376
396
|
),
|
|
@@ -388,8 +408,9 @@ function _BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
388
408
|
emptyMessage,
|
|
389
409
|
clearLabel,
|
|
390
410
|
hasAdd,
|
|
391
|
-
|
|
392
|
-
|
|
411
|
+
addLabel,
|
|
412
|
+
addButtonConfiguration,
|
|
413
|
+
handleAdd,
|
|
393
414
|
]);
|
|
394
415
|
|
|
395
416
|
const _items = useMemo(() => {
|
|
@@ -505,9 +526,9 @@ function _BottomSheetModalContent(props: ModalComponentProps) {
|
|
|
505
526
|
{hasAdd ? (
|
|
506
527
|
<View style={styles.someStyles}>
|
|
507
528
|
<AudioPlayerButton
|
|
508
|
-
configuration={
|
|
509
|
-
data={{ title:
|
|
510
|
-
onPress={
|
|
529
|
+
configuration={addButtonConfiguration}
|
|
530
|
+
data={{ title: addLabel }}
|
|
531
|
+
onPress={handleAdd}
|
|
511
532
|
/>
|
|
512
533
|
</View>
|
|
513
534
|
) : null}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { render, screen } from "@testing-library/react-native";
|
|
3
3
|
import { BottomSheetModalContent } from "../BottomSheetModalContent";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
ContentViewModel,
|
|
6
|
+
OPERATIONS,
|
|
7
|
+
} from "@applicaster/zapp-react-native-utils/modalState";
|
|
8
|
+
import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
|
|
9
|
+
import { Image, StyleSheet } from "react-native";
|
|
10
|
+
import {
|
|
11
|
+
AudioPlayerButton,
|
|
12
|
+
VARIANT_TEXT_ONLY,
|
|
13
|
+
} from "../AudioPlayer/Components/Button";
|
|
14
|
+
import { ModalBlocksStyleContext } from "../ModalBlocksStyleContext";
|
|
5
15
|
|
|
6
16
|
jest.mock("react-native-sortables", () => {
|
|
7
17
|
const { View } = require("react-native");
|
|
@@ -45,11 +55,11 @@ jest.mock("@applicaster/zapp-react-native-utils/theme", () => ({
|
|
|
45
55
|
}));
|
|
46
56
|
|
|
47
57
|
jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
|
|
48
|
-
usePlugins: () => [],
|
|
58
|
+
usePlugins: jest.fn(() => []),
|
|
49
59
|
}));
|
|
50
60
|
|
|
51
61
|
jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
|
|
52
|
-
useLocalizedStrings: () =>
|
|
62
|
+
useLocalizedStrings: ({ localizations }: any) => localizations || {},
|
|
53
63
|
}));
|
|
54
64
|
|
|
55
65
|
jest.mock("react-native-safe-area-context", () => ({
|
|
@@ -92,17 +102,14 @@ jest.mock("../AudioPlayer/Components", () => ({
|
|
|
92
102
|
return null;
|
|
93
103
|
},
|
|
94
104
|
AudioPlayerButton: (props: any) => {
|
|
95
|
-
const {
|
|
105
|
+
const { AudioPlayerButton } = jest.requireActual(
|
|
106
|
+
"../AudioPlayer/Components/Button"
|
|
107
|
+
);
|
|
96
108
|
|
|
97
109
|
mockAudioPlayerButtonProps.push(props);
|
|
98
110
|
|
|
99
|
-
return
|
|
100
|
-
<Text testID="create-playlist-button" onPress={props.onPress}>
|
|
101
|
-
{props.data?.title}
|
|
102
|
-
</Text>
|
|
103
|
-
);
|
|
111
|
+
return <AudioPlayerButton {...props} />;
|
|
104
112
|
},
|
|
105
|
-
VARIANT_WITH_ICON: {},
|
|
106
113
|
}));
|
|
107
114
|
|
|
108
115
|
const baseProps = {
|
|
@@ -116,6 +123,10 @@ const baseProps = {
|
|
|
116
123
|
};
|
|
117
124
|
|
|
118
125
|
describe("BottomSheetModalContent role/behavior cells", () => {
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
jest.mocked(usePlugins).mockReturnValue([]);
|
|
128
|
+
});
|
|
129
|
+
|
|
119
130
|
it("renders standard Button rows and default header when no viewModel/role", () => {
|
|
120
131
|
render(<BottomSheetModalContent {...baseProps} />);
|
|
121
132
|
|
|
@@ -180,6 +191,8 @@ describe("BottomSheetModalContent role/behavior cells", () => {
|
|
|
180
191
|
});
|
|
181
192
|
|
|
182
193
|
it("renders create playlist button when operations include add and calls editableCollection.add on press", () => {
|
|
194
|
+
mockAudioPlayerButtonProps.length = 0;
|
|
195
|
+
|
|
183
196
|
const addMock = jest.fn();
|
|
184
197
|
const { BehaviorSubject } = require("rxjs");
|
|
185
198
|
|
|
@@ -198,16 +211,234 @@ describe("BottomSheetModalContent role/behavior cells", () => {
|
|
|
198
211
|
|
|
199
212
|
render(<BottomSheetModalContent {...baseProps} viewModel={viewModel} />);
|
|
200
213
|
|
|
201
|
-
const createBtn = screen.
|
|
214
|
+
const createBtn = screen.getByText("Create New");
|
|
202
215
|
expect(createBtn).toBeTruthy();
|
|
203
216
|
|
|
217
|
+
expect(mockAudioPlayerButtonProps.length).toBeGreaterThan(0);
|
|
218
|
+
|
|
219
|
+
for (const props of mockAudioPlayerButtonProps) {
|
|
220
|
+
expect(props.configuration.icons.leadingIcon.enabled).toBe(false);
|
|
221
|
+
expect(props.data).toEqual({ title: "Create New" });
|
|
222
|
+
expect(typeof props.onPress).toBe("function");
|
|
223
|
+
}
|
|
224
|
+
|
|
204
225
|
const { fireEvent } = require("@testing-library/react-native");
|
|
205
226
|
fireEvent.press(createBtn);
|
|
206
227
|
|
|
207
|
-
expect(addMock).toHaveBeenCalledWith(
|
|
208
|
-
|
|
209
|
-
|
|
228
|
+
expect(addMock).toHaveBeenCalledWith();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it.each([["add"], ["add", "reorder"]])(
|
|
232
|
+
"renders operationButtons.add.title with plugin typography and icon for %j",
|
|
233
|
+
(...operations: string[]) => {
|
|
234
|
+
const { BehaviorSubject } = require("rxjs");
|
|
235
|
+
|
|
236
|
+
const configuration = {
|
|
237
|
+
"button.title.font_color": "#123456",
|
|
238
|
+
"button.title.font_size": 19,
|
|
239
|
+
"button.title.line_height": 27,
|
|
240
|
+
"button.background.background_color": "#ff9900",
|
|
241
|
+
"button.leading_icon.asset": "https://example.com/create.png",
|
|
242
|
+
localizations: {
|
|
243
|
+
playlist_experience_button_create_new: "Plugin key label",
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
jest
|
|
248
|
+
.mocked(usePlugins)
|
|
249
|
+
.mockReturnValue([
|
|
250
|
+
{ identifier: "playlist-experience", configuration },
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
const viewModel = new ContentViewModel({
|
|
254
|
+
title: "Playlists",
|
|
255
|
+
items: [],
|
|
256
|
+
role: "collection_selector",
|
|
257
|
+
themePluginId: "playlist-experience",
|
|
258
|
+
dynamicCollection: { operations },
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
viewModel.editableCollection = {
|
|
262
|
+
items$: new BehaviorSubject([]),
|
|
263
|
+
operations,
|
|
264
|
+
add: jest.fn(),
|
|
265
|
+
move: jest.fn(),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const styleOverrides = {
|
|
269
|
+
button: {
|
|
270
|
+
title: { default: "Wrong decorator label" },
|
|
271
|
+
leadingIcon: { default: { asset: "https://example.com/wrong.png" } },
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
render(
|
|
276
|
+
<BottomSheetModalContent
|
|
277
|
+
{...baseProps}
|
|
278
|
+
viewModel={viewModel}
|
|
279
|
+
styleOverrides={styleOverrides}
|
|
280
|
+
operationButtons={{ add: { title: "Neue Playlist" } }}
|
|
281
|
+
/>
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
expect(
|
|
285
|
+
StyleSheet.flatten(screen.getByText("Neue Playlist").props.style)
|
|
286
|
+
).toMatchObject({ color: "#123456", fontSize: 19, lineHeight: 27 });
|
|
287
|
+
|
|
288
|
+
expect(screen.UNSAFE_getByType(Image).props.source).toEqual({
|
|
289
|
+
uri: "https://example.com/create.png",
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
expect(screen.queryByText("Wrong decorator label")).toBeNull();
|
|
293
|
+
expect(screen.queryByText("Plugin key label")).toBeNull();
|
|
294
|
+
expect(styleOverrides.button.title.default).toBe("Wrong decorator label");
|
|
295
|
+
expect(configuration["button.title.font_size"]).toBe(19);
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
it("does not read the playlist-specific Add icon key", () => {
|
|
300
|
+
const { BehaviorSubject } = require("rxjs");
|
|
301
|
+
|
|
302
|
+
jest.mocked(usePlugins).mockReturnValue([
|
|
303
|
+
{
|
|
304
|
+
identifier: "playlist-experience",
|
|
305
|
+
configuration: {
|
|
306
|
+
"button.create_new_playlist_asset": "https://example.com/legacy.png",
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
]);
|
|
310
|
+
|
|
311
|
+
const viewModel = new ContentViewModel({
|
|
312
|
+
title: "Playlists",
|
|
313
|
+
items: [],
|
|
314
|
+
role: "collection_selector",
|
|
315
|
+
themePluginId: "playlist-experience",
|
|
316
|
+
dynamicCollection: { operations: ["add"] },
|
|
210
317
|
});
|
|
318
|
+
|
|
319
|
+
viewModel.editableCollection = {
|
|
320
|
+
items$: new BehaviorSubject([]),
|
|
321
|
+
operations: ["add"],
|
|
322
|
+
add: jest.fn(),
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
render(<BottomSheetModalContent {...baseProps} viewModel={viewModel} />);
|
|
326
|
+
|
|
327
|
+
expect(screen.UNSAFE_queryAllByType(Image)).toHaveLength(0);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it.each(
|
|
331
|
+
[false, true].flatMap((sortable) =>
|
|
332
|
+
[
|
|
333
|
+
{ iconURL: undefined, themeIcon: undefined, expected: undefined },
|
|
334
|
+
{
|
|
335
|
+
iconURL: "file:///bundled-add.png",
|
|
336
|
+
themeIcon: undefined,
|
|
337
|
+
expected: "file:///bundled-add.png",
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
iconURL: "https://example.com/upload.png",
|
|
341
|
+
themeIcon: undefined,
|
|
342
|
+
expected: "https://example.com/upload.png",
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
iconURL: undefined,
|
|
346
|
+
themeIcon: "https://example.com/theme.png",
|
|
347
|
+
expected: "https://example.com/theme.png",
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
iconURL: "file:///bundled-add.png",
|
|
351
|
+
themeIcon: "https://example.com/theme.png",
|
|
352
|
+
expected: "https://example.com/theme.png",
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
iconURL: "file:///bundled-add.png",
|
|
356
|
+
themeIcon: "https://example.com/theme.png",
|
|
357
|
+
themeEnabled: false,
|
|
358
|
+
expected: undefined,
|
|
359
|
+
},
|
|
360
|
+
].map((scenario) => ({ ...scenario, sortable }))
|
|
361
|
+
)
|
|
362
|
+
)(
|
|
363
|
+
"resolves Add icon presentation for %j",
|
|
364
|
+
({ sortable, iconURL, themeIcon, themeEnabled, expected }) => {
|
|
365
|
+
const { BehaviorSubject } = require("rxjs");
|
|
366
|
+
|
|
367
|
+
const operations = sortable
|
|
368
|
+
? [OPERATIONS.ADD, OPERATIONS.REORDER]
|
|
369
|
+
: [OPERATIONS.ADD];
|
|
370
|
+
|
|
371
|
+
jest.mocked(usePlugins).mockReturnValue([
|
|
372
|
+
{
|
|
373
|
+
identifier: "generic-theme",
|
|
374
|
+
configuration: themeIcon
|
|
375
|
+
? {
|
|
376
|
+
"button.leading_icon.asset": themeIcon,
|
|
377
|
+
"button.leading_icon.enabled": themeEnabled,
|
|
378
|
+
}
|
|
379
|
+
: {},
|
|
380
|
+
},
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
const viewModel = new ContentViewModel({
|
|
384
|
+
title: "Collections",
|
|
385
|
+
items: [],
|
|
386
|
+
themePluginId: "generic-theme",
|
|
387
|
+
dynamicCollection: { operations },
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
viewModel.editableCollection = {
|
|
391
|
+
items$: new BehaviorSubject([]),
|
|
392
|
+
operations,
|
|
393
|
+
add: jest.fn(),
|
|
394
|
+
move: jest.fn(),
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
render(
|
|
398
|
+
<BottomSheetModalContent
|
|
399
|
+
{...baseProps}
|
|
400
|
+
viewModel={viewModel}
|
|
401
|
+
operationButtons={{ add: { iconURL } }}
|
|
402
|
+
/>
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
if (expected) {
|
|
406
|
+
expect(screen.UNSAFE_getByType(Image).props.source).toEqual({
|
|
407
|
+
uri: expected,
|
|
408
|
+
});
|
|
409
|
+
} else {
|
|
410
|
+
expect(screen.UNSAFE_queryAllByType(Image)).toHaveLength(0);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
it("does not render a typography object as a button label", () => {
|
|
416
|
+
render(
|
|
417
|
+
<ModalBlocksStyleContext.Provider
|
|
418
|
+
value={{ button: { title: { default: { fontSize: 19 } } } }}
|
|
419
|
+
>
|
|
420
|
+
<AudioPlayerButton configuration={VARIANT_TEXT_ONLY} />
|
|
421
|
+
</ModalBlocksStyleContext.Provider>
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
expect(screen.toJSON()).toBeTruthy();
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("renders explicit button data separately from typography", () => {
|
|
428
|
+
render(
|
|
429
|
+
<ModalBlocksStyleContext.Provider
|
|
430
|
+
value={{ button: { title: { default: { fontSize: 19 } } } }}
|
|
431
|
+
>
|
|
432
|
+
<AudioPlayerButton
|
|
433
|
+
configuration={VARIANT_TEXT_ONLY}
|
|
434
|
+
data={{ title: "Create Playlist" }}
|
|
435
|
+
/>
|
|
436
|
+
</ModalBlocksStyleContext.Provider>
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
expect(
|
|
440
|
+
StyleSheet.flatten(screen.getByText("Create Playlist").props.style)
|
|
441
|
+
).toMatchObject({ fontSize: 19 });
|
|
211
442
|
});
|
|
212
443
|
|
|
213
444
|
it("shows the empty Next Up message only when now playing is on and the list is empty", () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-ui-components",
|
|
3
|
-
"version": "15.1.22
|
|
3
|
+
"version": "15.1.22",
|
|
4
4
|
"description": "Applicaster Zapp React Native ui components for the Quick Brick App",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@applicaster/applicaster-types": "15.1.22
|
|
32
|
-
"@applicaster/zapp-react-native-bridge": "15.1.22
|
|
33
|
-
"@applicaster/zapp-react-native-redux": "15.1.22
|
|
34
|
-
"@applicaster/zapp-react-native-utils": "15.1.22
|
|
31
|
+
"@applicaster/applicaster-types": "15.1.22",
|
|
32
|
+
"@applicaster/zapp-react-native-bridge": "15.1.22",
|
|
33
|
+
"@applicaster/zapp-react-native-redux": "15.1.22",
|
|
34
|
+
"@applicaster/zapp-react-native-utils": "15.1.22",
|
|
35
35
|
"fast-json-stable-stringify": "^2.1.0",
|
|
36
36
|
"promise": "^8.3.0",
|
|
37
37
|
"react-native-sortables": "1.7.1",
|