@applicaster/zapp-react-native-utils 15.1.6-alpha.3988508055 → 15.1.7-alpha.1530511842
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 +7 -0
- package/actionsExecutor/ActionExecutorContext.tsx +0 -4
- package/actionsExecutor/actions/__tests__/showToast.test.ts +29 -4
- package/actionsExecutor/actions/index.ts +0 -4
- package/actionsExecutor/actions/refreshComponent.ts +0 -6
- package/actionsExecutor/actions/showToast.ts +62 -6
- package/actionsExecutor/feedDecorator.ts +6 -6
- package/actionsExecutor/types.ts +3 -5
- package/appUtils/playerManager/OverlayObserver/__tests__/utils.test.ts +41 -0
- package/appUtils/playerManager/OverlayObserver/utils.ts +3 -0
- package/manifestUtils/__tests__/player.audioControls.test.js +158 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +156 -0
- package/modalState/index.ts +83 -30
- package/package.json +2 -2
- package/reactHooks/index.ts +2 -0
- package/reactHooks/layout/index.ts +1 -1
- package/reactHooks/utils/__tests__/index.test.js +22 -2
- package/reactHooks/utils/index.ts +11 -1
- package/actionsExecutor/actions/__tests__/dismissBottomSheet.test.ts +0 -22
- package/actionsExecutor/actions/__tests__/loadItemsFromSource.test.ts +0 -88
- package/actionsExecutor/actions/__tests__/openBottomSheet.customContent.test.ts +0 -76
- package/actionsExecutor/actions/dismissBottomSheet.ts +0 -22
- package/actionsExecutor/actions/openBottomSheet.ts +0 -318
- package/modalState/ContentViewModel.ts +0 -111
- package/modalState/EditableCollection.ts +0 -17
- package/modalState/EditableCollectionRegistry.ts +0 -51
- package/modalState/ModalOrchestrator.ts +0 -198
- package/modalState/RemoteEditableCollection.ts +0 -189
- package/modalState/__tests__/ContentViewModel.editable.test.ts +0 -69
- package/modalState/__tests__/ContentViewModel.test.ts +0 -165
- package/modalState/__tests__/EditableCollectionRegistry.test.ts +0 -112
- package/modalState/__tests__/ModalOrchestrator.phase3.test.ts +0 -28
- package/modalState/__tests__/RemoteEditableCollection.test.ts +0 -259
- package/modalState/__tests__/useBottomSheetContent.test.ts +0 -372
- package/modalState/store.ts +0 -102
- package/modalState/types.ts +0 -110
- package/modalState/useBottomSheetContent.ts +0 -98
|
@@ -19,11 +19,9 @@ import {
|
|
|
19
19
|
appRestartAction,
|
|
20
20
|
confirmDialogAction,
|
|
21
21
|
createNavigateToScreenAction,
|
|
22
|
-
dismissBottomSheetAction,
|
|
23
22
|
localStorageRemoveAction,
|
|
24
23
|
localStorageSetAction,
|
|
25
24
|
localStorageToggleFlagAction,
|
|
26
|
-
openBottomSheetAction,
|
|
27
25
|
refreshComponentAction,
|
|
28
26
|
screenSetVariableAction,
|
|
29
27
|
screenToggleFlagAction,
|
|
@@ -92,8 +90,6 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
92
90
|
|
|
93
91
|
actionExecutor.registerAction("screenSetVariable", screenSetVariableAction);
|
|
94
92
|
actionExecutor.registerAction("screenToggleFlag", screenToggleFlagAction);
|
|
95
|
-
actionExecutor.registerAction("openBottomSheet", openBottomSheetAction);
|
|
96
|
-
actionExecutor.registerAction("dismissBottomSheet", dismissBottomSheetAction);
|
|
97
93
|
actionExecutor.registerAction("showToast", showToastAction);
|
|
98
94
|
};
|
|
99
95
|
|
|
@@ -11,14 +11,13 @@ describe("showToastAction", () => {
|
|
|
11
11
|
jest.clearAllMocks();
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
it("calls postEvent with
|
|
14
|
+
it("calls postEvent with confirmation source, default timeout, and returns Success", async () => {
|
|
15
15
|
const action = {
|
|
16
16
|
type: "showToast",
|
|
17
17
|
options: {
|
|
18
18
|
id: "toast-1",
|
|
19
19
|
message: "Added to queue",
|
|
20
20
|
extraMessage: "Extra info",
|
|
21
|
-
timeout: 3000,
|
|
22
21
|
},
|
|
23
22
|
};
|
|
24
23
|
|
|
@@ -32,13 +31,38 @@ describe("showToastAction", () => {
|
|
|
32
31
|
message: "Added to queue",
|
|
33
32
|
extraMessage: "Extra info",
|
|
34
33
|
style: undefined,
|
|
35
|
-
timeout:
|
|
34
|
+
timeout: 1000,
|
|
35
|
+
source: "confirmation",
|
|
36
36
|
},
|
|
37
37
|
]);
|
|
38
38
|
|
|
39
39
|
expect(result).toBe(ActionResult.Success);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
it("preserves an explicit timeout and style override", async () => {
|
|
43
|
+
const action = {
|
|
44
|
+
type: "showToast",
|
|
45
|
+
options: {
|
|
46
|
+
message: "Playlist created",
|
|
47
|
+
timeout: 3000,
|
|
48
|
+
style: { backgroundColor: "#00FF00" },
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
await showToastAction(action);
|
|
53
|
+
|
|
54
|
+
expect(postEvent).toHaveBeenCalledWith("showToast", [
|
|
55
|
+
{
|
|
56
|
+
id: undefined,
|
|
57
|
+
message: "Playlist created",
|
|
58
|
+
extraMessage: undefined,
|
|
59
|
+
style: { backgroundColor: "#00FF00" },
|
|
60
|
+
timeout: 3000,
|
|
61
|
+
source: "confirmation",
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
|
|
42
66
|
it("handles missing or empty options without throwing", async () => {
|
|
43
67
|
const action = {
|
|
44
68
|
type: "showToast",
|
|
@@ -54,7 +78,8 @@ describe("showToastAction", () => {
|
|
|
54
78
|
message: undefined,
|
|
55
79
|
extraMessage: undefined,
|
|
56
80
|
style: undefined,
|
|
57
|
-
timeout:
|
|
81
|
+
timeout: 1000,
|
|
82
|
+
source: "confirmation",
|
|
58
83
|
},
|
|
59
84
|
]);
|
|
60
85
|
|
|
@@ -28,7 +28,3 @@ 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";
|
|
@@ -82,12 +82,6 @@ export const refreshComponentAction: ActionHandler = async (
|
|
|
82
82
|
}) || source;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
if (!dataSource) {
|
|
86
|
-
log_info("handleAction: refreshComponent skipped: no dataSource found");
|
|
87
|
-
|
|
88
|
-
return ActionResult.Success;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
85
|
log_info(`handleAction: refreshComponent for dataSource:${dataSource}`, {
|
|
92
86
|
source,
|
|
93
87
|
inflatedUrl: dataSource,
|
|
@@ -3,8 +3,61 @@ import { ActionResult } from "../ActionExecutor";
|
|
|
3
3
|
import { ActionHandler } from "../types";
|
|
4
4
|
import { postEvent } from "../../reactHooks/useSubscriberFor";
|
|
5
5
|
|
|
6
|
+
export const CONFIRMATION_TOAST_SOURCE = "confirmation";
|
|
7
|
+
|
|
8
|
+
export const CONFIRMATION_TOAST_DEFAULT_TIMEOUT = 1000;
|
|
9
|
+
|
|
6
10
|
/**
|
|
7
|
-
*
|
|
11
|
+
* Shows a mobile confirmation toast with Theme defaults from
|
|
12
|
+
* `Confirmation Toast / Message` in the base theme plugin.
|
|
13
|
+
*
|
|
14
|
+
* This action is confirmation-only by design: it always stamps
|
|
15
|
+
* `source: "confirmation"` (so Theme confirmation styles apply) and defaults
|
|
16
|
+
* `timeout` to 1000 ms. Non-confirmation toasts (e.g. Offline Experience)
|
|
17
|
+
* must post on the shared `"showToast"` event bus directly and omit `source`.
|
|
18
|
+
* Callers may override individual style fields; Theme supplies the rest.
|
|
19
|
+
*
|
|
20
|
+
* @example From React via ActionExecutorContext
|
|
21
|
+
* ```tsx
|
|
22
|
+
* import React, { useContext } from "react";
|
|
23
|
+
* import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutorContext";
|
|
24
|
+
*
|
|
25
|
+
* function QueueButton() {
|
|
26
|
+
* const { handleAction } = useContext(ActionExecutorContext);
|
|
27
|
+
*
|
|
28
|
+
* const onPress = () => {
|
|
29
|
+
* void handleAction({
|
|
30
|
+
* type: "showToast",
|
|
31
|
+
* options: { message: "Added to queue" },
|
|
32
|
+
* });
|
|
33
|
+
* };
|
|
34
|
+
*
|
|
35
|
+
* return <Button onPress={onPress} title="Add to queue" />;
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example From code via the actionExecutor singleton
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { actionExecutor } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutor";
|
|
42
|
+
*
|
|
43
|
+
* await actionExecutor.handleAction({
|
|
44
|
+
* type: "showToast",
|
|
45
|
+
* options: {
|
|
46
|
+
* message: "Playlist created",
|
|
47
|
+
* style: { backgroundColor: "#00AA55" }, // optional partial override
|
|
48
|
+
* },
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @example From entry / pipes actions JSON
|
|
53
|
+
* ```json
|
|
54
|
+
* {
|
|
55
|
+
* "type": "showToast",
|
|
56
|
+
* "options": {
|
|
57
|
+
* "message": "Item removed from playlist"
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
8
61
|
*/
|
|
9
62
|
export interface ShowToastActionOptions {
|
|
10
63
|
id?: string;
|
|
@@ -17,13 +70,16 @@ export interface ShowToastActionOptions {
|
|
|
17
70
|
export const showToastAction: ActionHandler<ShowToastActionOptions> = async (
|
|
18
71
|
action
|
|
19
72
|
): Promise<ActionResult> => {
|
|
73
|
+
const options = action?.options;
|
|
74
|
+
|
|
20
75
|
postEvent("showToast", [
|
|
21
76
|
{
|
|
22
|
-
id:
|
|
23
|
-
message:
|
|
24
|
-
extraMessage:
|
|
25
|
-
style:
|
|
26
|
-
timeout:
|
|
77
|
+
id: options?.id,
|
|
78
|
+
message: options?.message,
|
|
79
|
+
extraMessage: options?.extraMessage,
|
|
80
|
+
style: options?.style,
|
|
81
|
+
timeout: options?.timeout ?? CONFIRMATION_TOAST_DEFAULT_TIMEOUT,
|
|
82
|
+
source: CONFIRMATION_TOAST_SOURCE,
|
|
27
83
|
},
|
|
28
84
|
]);
|
|
29
85
|
|
|
@@ -27,7 +27,7 @@ function makeMultiSelect(feed: ZappFeed, key, decoratedFeed) {
|
|
|
27
27
|
);
|
|
28
28
|
|
|
29
29
|
const behavior = {
|
|
30
|
-
...feed.extensions?.behavior,
|
|
30
|
+
...feed.extensions?.["behavior"],
|
|
31
31
|
select_mode: "multi",
|
|
32
32
|
current_selection: `@{${scope}/${key}}`,
|
|
33
33
|
};
|
|
@@ -75,7 +75,7 @@ function makeSingleSelect(feed: ZappFeed, key, decoratedFeed) {
|
|
|
75
75
|
);
|
|
76
76
|
|
|
77
77
|
const behavior = {
|
|
78
|
-
...feed.extensions?.behavior,
|
|
78
|
+
...feed.extensions?.["behavior"],
|
|
79
79
|
select_mode: "single",
|
|
80
80
|
current_selection: `@{${scope}/${key}}`,
|
|
81
81
|
};
|
|
@@ -141,11 +141,11 @@ function makeSingleSelect(feed: ZappFeed, key, decoratedFeed) {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
export const decorateFeed = (feed: ZappFeed) => {
|
|
144
|
-
if (!(feed.extensions?.role === "preference_editor")) {
|
|
144
|
+
if (!(feed.extensions?.["role"] === "preference_editor")) {
|
|
145
145
|
return feed;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
const key = feed.extensions?.preference_editor_options?.key;
|
|
148
|
+
const key = feed.extensions?.["preference_editor_options"]?.["key"];
|
|
149
149
|
|
|
150
150
|
if (!key) {
|
|
151
151
|
log_error(
|
|
@@ -160,8 +160,8 @@ export const decorateFeed = (feed: ZappFeed) => {
|
|
|
160
160
|
const decoratedFeed = R.clone(feed);
|
|
161
161
|
|
|
162
162
|
const isSingleSelect =
|
|
163
|
-
(feed.extensions?.preference_editor_options?.select_mode ||
|
|
164
|
-
feed.extensions?.behavior?.select_mode) === "single";
|
|
163
|
+
(feed.extensions?.["preference_editor_options"]?.select_mode ||
|
|
164
|
+
feed.extensions?.["behavior"]?.select_mode) === "single";
|
|
165
165
|
|
|
166
166
|
if (isSingleSelect) {
|
|
167
167
|
return makeSingleSelect(feed, key, decoratedFeed);
|
package/actionsExecutor/types.ts
CHANGED
|
@@ -16,10 +16,8 @@ export interface ActionExecutionContext {
|
|
|
16
16
|
/** The entry that defines the screen (if screen was opened from an entry) */
|
|
17
17
|
screenEntry?: ZappEntry;
|
|
18
18
|
|
|
19
|
-
/** The UI component that triggered the action
|
|
20
|
-
|
|
21
|
-
*/
|
|
22
|
-
component?: ZappUIComponent & { parent?: ZappUIComponent };
|
|
19
|
+
/** The UI component that triggered the action */
|
|
20
|
+
component?: ZappUIComponent;
|
|
23
21
|
|
|
24
22
|
/** The route identifier for the current screen */
|
|
25
23
|
screenRoute?: string;
|
|
@@ -33,7 +31,7 @@ export interface ActionExecutionContext {
|
|
|
33
31
|
/** Callback function for action completion */
|
|
34
32
|
callback?: (result: { success: boolean; error: any; abort: boolean }) => void;
|
|
35
33
|
|
|
36
|
-
screenState
|
|
34
|
+
screenState: Record<any, any>;
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
/**
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { retrieveFeedUrl, retrievePlayPreviousFeedUrl } from "../utils";
|
|
2
|
+
|
|
3
|
+
const entryWith = (extensions: Record<string, unknown>): ZappEntry =>
|
|
4
|
+
({ id: "1", type: { value: "video" }, extensions }) as ZappEntry;
|
|
5
|
+
|
|
6
|
+
describe("playlist feed url accessors", () => {
|
|
7
|
+
it("returns the next feed url when present", () => {
|
|
8
|
+
const entry = entryWith({ play_next_feed_url: "https://feeds/next.json" });
|
|
9
|
+
|
|
10
|
+
expect(retrieveFeedUrl(entry)).toBe("https://feeds/next.json");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns the previous feed url when present", () => {
|
|
14
|
+
const entry = entryWith({
|
|
15
|
+
play_previous_feed_url: "https://feeds/prev.json",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
expect(retrievePlayPreviousFeedUrl(entry)).toBe("https://feeds/prev.json");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns undefined when only the next url is present", () => {
|
|
22
|
+
const entry = entryWith({ play_next_feed_url: "https://feeds/next.json" });
|
|
23
|
+
|
|
24
|
+
expect(retrievePlayPreviousFeedUrl(entry)).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns undefined for an entry with no extensions", () => {
|
|
28
|
+
expect(
|
|
29
|
+
retrievePlayPreviousFeedUrl({
|
|
30
|
+
id: "1",
|
|
31
|
+
type: { value: "video" },
|
|
32
|
+
} as ZappEntry)
|
|
33
|
+
).toBeUndefined();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("returns undefined for a missing entry", () => {
|
|
37
|
+
expect(
|
|
38
|
+
retrievePlayPreviousFeedUrl(undefined as unknown as ZappEntry)
|
|
39
|
+
).toBeUndefined();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -43,6 +43,9 @@ export const parseTimeToSeconds = (timeStr: string): number => {
|
|
|
43
43
|
export const retrieveFeedUrl = (entry: ZappEntry) =>
|
|
44
44
|
entry?.extensions?.play_next_feed_url;
|
|
45
45
|
|
|
46
|
+
export const retrievePlayPreviousFeedUrl = (entry: ZappEntry) =>
|
|
47
|
+
entry?.extensions?.play_previous_feed_url;
|
|
48
|
+
|
|
46
49
|
export const retrieveOverlayDuration = (plugin) =>
|
|
47
50
|
toNumberWithDefault(plugin?.configuration?.overlay_duration, 15);
|
|
48
51
|
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const { player } = require("../defaultManifestConfigurations/player");
|
|
2
|
+
|
|
3
|
+
const manifestFor = (platform) => player({ platform, version: "0.0.0" });
|
|
4
|
+
|
|
5
|
+
const findGroup = (manifest, label) =>
|
|
6
|
+
manifest.styles.fields.find((group) => group.group && group.label === label);
|
|
7
|
+
|
|
8
|
+
const findFieldInGroup = (manifest, label, key) =>
|
|
9
|
+
findGroup(manifest, label)?.fields?.find((field) => field.key === key);
|
|
10
|
+
|
|
11
|
+
const findLocalization = (manifest, key) =>
|
|
12
|
+
manifest.localizations.fields.find((field) => field.key === key);
|
|
13
|
+
|
|
14
|
+
describe("player manifest — audio playlist controls", () => {
|
|
15
|
+
const manifest = manifestFor("ios_for_quickbrick");
|
|
16
|
+
|
|
17
|
+
describe("Player Controls Layout", () => {
|
|
18
|
+
const field = () =>
|
|
19
|
+
findFieldInGroup(manifest, "Audio Player", "player_controls_layout");
|
|
20
|
+
|
|
21
|
+
it("is declared in the Audio Player group", () => {
|
|
22
|
+
expect(field()).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("is a select defaulting to the existing controls", () => {
|
|
26
|
+
expect(field().type).toBe("select");
|
|
27
|
+
expect(field().initial_value).toBe("playback_speed_and_sleep_timer");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("only shows when the audio player layout is in use", () => {
|
|
31
|
+
expect(field()).toMatchObject({
|
|
32
|
+
rules: "conditional",
|
|
33
|
+
conditional_fields: [
|
|
34
|
+
{ key: "styles/full_screen_audio_player", condition_value: true },
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("offers exactly the two documented options", () => {
|
|
40
|
+
expect(field().options).toEqual([
|
|
41
|
+
{
|
|
42
|
+
text: "Playback Speed & Sleep Timer (Default)",
|
|
43
|
+
value: "playback_speed_and_sleep_timer",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
text: "Playlist Controls (Previous & Next)",
|
|
47
|
+
value: "playlist_controls",
|
|
48
|
+
},
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("control icons", () => {
|
|
54
|
+
it.each(["previous_track", "next_track"])(
|
|
55
|
+
"declares %s as an uploader",
|
|
56
|
+
(key) => {
|
|
57
|
+
expect(findFieldInGroup(manifest, "Assets", key)).toMatchObject({
|
|
58
|
+
key,
|
|
59
|
+
type: "uploader",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
it.each(["previous_track", "next_track"])(
|
|
65
|
+
"only shows %s when the playlist controls layout is selected",
|
|
66
|
+
(key) => {
|
|
67
|
+
expect(findFieldInGroup(manifest, "Assets", key)).toMatchObject({
|
|
68
|
+
rules: "conditional",
|
|
69
|
+
conditional_fields: [
|
|
70
|
+
{
|
|
71
|
+
key: "styles/player_controls_layout",
|
|
72
|
+
condition_value: "playlist_controls",
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
it.each([
|
|
80
|
+
"speed_0_8",
|
|
81
|
+
"speed_1",
|
|
82
|
+
"speed_1_2",
|
|
83
|
+
"speed_1_5",
|
|
84
|
+
"speed_2",
|
|
85
|
+
"sleep_timer",
|
|
86
|
+
"sleep_timer_active",
|
|
87
|
+
])(
|
|
88
|
+
"only shows %s when the playback speed and sleep timer layout is selected",
|
|
89
|
+
(key) => {
|
|
90
|
+
expect(findFieldInGroup(manifest, "Assets", key)).toMatchObject({
|
|
91
|
+
rules: "conditional",
|
|
92
|
+
conditional_fields: [
|
|
93
|
+
{
|
|
94
|
+
key: "styles/player_controls_layout",
|
|
95
|
+
condition_value: "playback_speed_and_sleep_timer",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("accessibility strings", () => {
|
|
104
|
+
it.each([
|
|
105
|
+
"accessibility_previous_track_label",
|
|
106
|
+
"accessibility_previous_track_hint",
|
|
107
|
+
"accessibility_next_track_label",
|
|
108
|
+
"accessibility_next_track_hint",
|
|
109
|
+
])("declares %s with a default", (key) => {
|
|
110
|
+
expect(findLocalization(manifest, key)).toMatchObject({
|
|
111
|
+
type: "text_input",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(findLocalization(manifest, key).initial_value).toBeTruthy();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("tv platforms", () => {
|
|
119
|
+
// The audio player layout is mobile only, so nothing this feature adds —
|
|
120
|
+
// including its screen reader strings — belongs in a tv manifest.
|
|
121
|
+
it.each(["tvos_for_quickbrick", "android_tv_for_quickbrick", "lg_tv"])(
|
|
122
|
+
"adds no audio controls layout field on %s",
|
|
123
|
+
(platform) => {
|
|
124
|
+
expect(
|
|
125
|
+
findFieldInGroup(
|
|
126
|
+
manifestFor(platform),
|
|
127
|
+
"Audio Player",
|
|
128
|
+
"player_controls_layout"
|
|
129
|
+
)
|
|
130
|
+
).toBeUndefined();
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
it.each([
|
|
135
|
+
"accessibility_previous_track_label",
|
|
136
|
+
"accessibility_previous_track_hint",
|
|
137
|
+
"accessibility_next_track_label",
|
|
138
|
+
"accessibility_next_track_hint",
|
|
139
|
+
])("does not declare %s on tv", (key) => {
|
|
140
|
+
expect(
|
|
141
|
+
findLocalization(manifestFor("tvos_for_quickbrick"), key)
|
|
142
|
+
).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("still declares the tv-only localizations", () => {
|
|
146
|
+
expect(
|
|
147
|
+
findLocalization(
|
|
148
|
+
manifestFor("tvos_for_quickbrick"),
|
|
149
|
+
"back_to_live_label"
|
|
150
|
+
)
|
|
151
|
+
).toBeDefined();
|
|
152
|
+
|
|
153
|
+
expect(
|
|
154
|
+
findLocalization(manifestFor("tvos_for_quickbrick"), "start_over_label")
|
|
155
|
+
).toBeDefined();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|