@applicaster/zapp-react-native-ui-components 16.0.0-rc.8 → 16.0.0-rc.81

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.
Files changed (132) hide show
  1. package/Components/AudioPlayer/tv/__tests__/audioPlayer.test.js +19 -0
  2. package/Components/AudioPlayer/tv/__tests__/index.test.tsx +120 -0
  3. package/Components/AudioPlayer/tv/index.tsx +5 -1
  4. package/Components/Cell/Cell.tsx +3 -6
  5. package/Components/Cell/TvOSCellComponent.tsx +0 -1
  6. package/Components/CellRendererResolver/index.ts +2 -2
  7. package/Components/ComponentResolver/__tests__/componentResolver.test.js +1 -1
  8. package/Components/FocusableGroup/FocusableTvOS.tsx +11 -7
  9. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +39 -0
  10. package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
  11. package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
  12. package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
  13. package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
  14. package/Components/GeneralContentScreen/hookAdapter/__tests__/skipExpression.test.ts +294 -0
  15. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +188 -0
  16. package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
  17. package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
  18. package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
  19. package/Components/GeneralContentScreen/hookAdapter/skipExpression.ts +252 -0
  20. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +90 -0
  21. package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
  22. package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
  23. package/Components/GeneralContentScreen/index.ts +2 -0
  24. package/Components/Layout/FullWidthRow.tsx +38 -0
  25. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +16 -5
  26. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +47 -21
  27. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
  28. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/__tests__/withDimensions.test.ts +420 -125
  29. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/index.ts +114 -19
  30. package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +19 -8
  31. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  32. package/Components/MasterCell/DefaultComponents/PressableView.tsx +29 -9
  33. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +4 -0
  34. package/Components/MasterCell/DefaultComponents/Toggle.tsx +36 -14
  35. package/Components/MasterCell/DefaultComponents/__tests__/Toggle.test.tsx +167 -0
  36. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +154 -100
  37. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +15 -4
  38. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +56 -22
  39. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +6 -4
  40. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +3 -1
  41. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +32 -21
  42. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +15 -4
  43. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +23 -16
  44. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +8 -3
  45. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +2 -2
  46. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  47. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +2 -2
  48. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  49. package/Components/MasterCell/dataAdapter.ts +15 -5
  50. package/Components/MasterCell/hooks/index.ts +2 -0
  51. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  52. package/Components/MasterCell/index.tsx +30 -5
  53. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +169 -132
  54. package/Components/MasterCell/utils/__tests__/resolveColorForProp.test.js +92 -0
  55. package/Components/MasterCell/utils/index.ts +85 -46
  56. package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +54 -0
  57. package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +21 -0
  58. package/Components/ModalComponent/ActionsBottomSheet/index.ts +9 -0
  59. package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +58 -0
  60. package/Components/ModalComponent/AudioPlayer/Components/Action.tsx +314 -0
  61. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +407 -0
  62. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +763 -0
  63. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +653 -0
  64. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +960 -0
  65. package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +89 -0
  66. package/Components/ModalComponent/AudioPlayer/Components/index.ts +11 -0
  67. package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +230 -0
  68. package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +332 -0
  69. package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
  70. package/Components/ModalComponent/BottomSheetModalContent.tsx +383 -42
  71. package/Components/ModalComponent/Button/index.tsx +5 -3
  72. package/Components/ModalComponent/Header/index.tsx +20 -7
  73. package/Components/ModalComponent/Header/utils.ts +1 -1
  74. package/Components/ModalComponent/ModalBlocksStyleContext.tsx +10 -0
  75. package/Components/ModalComponent/SortableList.tsx +142 -0
  76. package/Components/ModalComponent/SortableList.web.tsx +34 -0
  77. package/Components/ModalComponent/TextInputContent.tsx +100 -0
  78. package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
  79. package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +238 -0
  80. package/Components/ModalComponent/__tests__/SortableList.web.test.tsx +16 -0
  81. package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +120 -0
  82. package/Components/ModalComponent/__tests__/showTextInput.test.ts +95 -0
  83. package/Components/ModalComponent/index.tsx +11 -0
  84. package/Components/ModalComponent/presets/__tests__/getCell.test.ts +66 -0
  85. package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +78 -0
  86. package/Components/ModalComponent/presets/index.ts +27 -0
  87. package/Components/ModalComponent/presets/selectableCell.tsx +28 -0
  88. package/Components/ModalComponent/presets/standardCell.tsx +41 -0
  89. package/Components/ModalComponent/presets/types.ts +35 -0
  90. package/Components/ModalComponent/showTextInput.ts +43 -0
  91. package/Components/ModalComponent/utils.ts +82 -21
  92. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
  93. package/Components/OfflineHandler/__tests__/index.test.tsx +78 -18
  94. package/Components/OfflineHandler/hooks.ts +111 -0
  95. package/Components/OfflineHandler/index.tsx +51 -73
  96. package/Components/OfflineHandler/utils/index.ts +1 -13
  97. package/Components/PlayerContainer/PlayerContainer.tsx +25 -17
  98. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -4
  99. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +337 -0
  100. package/Components/PreloaderWrapper/index.tsx +1 -1
  101. package/Components/River/ComponentsMap/ComponentsMap.tsx +2 -2
  102. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +3 -15
  103. package/Components/Screen/index.tsx +8 -4
  104. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
  105. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
  106. package/Components/ScreenRevealManager/ScreenRevealManager.ts +18 -2
  107. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +36 -0
  108. package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +56 -0
  109. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +20 -3
  110. package/Components/TopCutoffOverlay/index.tsx +2 -2
  111. package/Components/Transitioner/Transitioner.tsx +28 -17
  112. package/Components/VideoLive/LiveImageManager.ts +1 -1
  113. package/Components/VideoModal/utils.ts +6 -1
  114. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +12 -5
  115. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  116. package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
  117. package/Contexts/CachedDimensionsContext/index.ts +17 -2
  118. package/Helpers/ComponentCellSelectionHelper/index.js +0 -6
  119. package/Helpers/index.js +0 -39
  120. package/Hooks/useEntryActionsExpander.ts +63 -0
  121. package/package.json +6 -5
  122. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  123. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  124. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  125. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -66
  126. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  127. package/Components/Screen/utils.ts +0 -16
  128. package/Helpers/Analytics/index.js +0 -95
  129. /package/Components/BackgroundImage/{BackgroundImage.tv.android.tsx → BackgroundImage.android.tv.tsx} +0 -0
  130. /package/Components/BackgroundImage/{BackgroundImage.tv.ios.tsx → BackgroundImage.ios.tv.tsx} +0 -0
  131. /package/Components/MasterCell/DefaultComponents/ButtonContainerView/{index.tv.android.tsx → index.android.tv.tsx} +0 -0
  132. /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.js → Hero.tsx} +0 -0
@@ -0,0 +1,142 @@
1
+ import React, { useCallback, useEffect, useState } from "react";
2
+ import Sortable from "react-native-sortables";
3
+ import { Text, View, ViewStyle } from "react-native";
4
+ import Animated, { useAnimatedRef } from "react-native-reanimated";
5
+
6
+ const DEFAULT_HEADERS: any[] = [];
7
+ const DEFAULT_SORTABLE_DATA: any[] = [];
8
+
9
+ const renderHeaderComponent = (component: any) => {
10
+ if (!component) return null;
11
+ if (React.isValidElement(component)) return component;
12
+
13
+ if (typeof component === "function") {
14
+ const HeaderComponent = component;
15
+
16
+ return <HeaderComponent />;
17
+ }
18
+
19
+ return null;
20
+ };
21
+
22
+ const SortableListHeader = ({
23
+ title,
24
+ style,
25
+ }: {
26
+ title?: string;
27
+ style?: ViewStyle;
28
+ }) => {
29
+ const displayTitle = typeof title === "string" ? title : String(title ?? "");
30
+
31
+ return (
32
+ <View style={style}>
33
+ {displayTitle ? <Text>{displayTitle}</Text> : null}
34
+ </View>
35
+ );
36
+ };
37
+
38
+ export type SortableListProps = {
39
+ scrollViewStyle?: ViewStyle;
40
+ contentContainerStyle?: ViewStyle;
41
+ itemStyle?: ViewStyle;
42
+ headerStyle?: ViewStyle;
43
+ columns?: number;
44
+ rowGap?: number;
45
+ columnGap?: number;
46
+ spacing?: number;
47
+ keyExtractor?: (item: any, index?: number) => string;
48
+ renderItem: ({
49
+ item,
50
+ index,
51
+ renderHandle,
52
+ }: {
53
+ item: any;
54
+ index: number;
55
+ renderHandle: (children: React.ReactElement) => React.ReactElement;
56
+ }) => React.ReactNode;
57
+ sortableData?: any[];
58
+ headers?: { index: number; title?: string; component: any }[];
59
+ onDragEnd?: (params: {
60
+ fromIndex?: number;
61
+ toIndex?: number;
62
+ from?: number;
63
+ to?: number;
64
+ data: any[];
65
+ }) => void;
66
+ };
67
+
68
+ export const SortableList = ({
69
+ scrollViewStyle,
70
+ contentContainerStyle,
71
+ itemStyle,
72
+ headerStyle,
73
+ columns = 1,
74
+ rowGap = 0,
75
+ columnGap = 0,
76
+ keyExtractor = (item, index) =>
77
+ `sortable-item-${item?.id ?? item?.title ?? index}`,
78
+ renderItem,
79
+ sortableData = DEFAULT_SORTABLE_DATA,
80
+ headers = DEFAULT_HEADERS,
81
+ onDragEnd,
82
+ }: SortableListProps) => {
83
+ const scrollableRef = useAnimatedRef<Animated.ScrollView>();
84
+
85
+ const [localData, setLocalData] = useState(sortableData);
86
+
87
+ useEffect(() => {
88
+ setLocalData(sortableData);
89
+ }, [sortableData]);
90
+
91
+ const handleDragEnd = useCallback(
92
+ (params: any) => {
93
+ if (params?.data) {
94
+ setLocalData(params.data);
95
+ }
96
+
97
+ if (onDragEnd) {
98
+ onDragEnd(params);
99
+ }
100
+ },
101
+ [onDragEnd]
102
+ );
103
+
104
+ return (
105
+ <Animated.ScrollView
106
+ ref={scrollableRef}
107
+ style={scrollViewStyle}
108
+ contentContainerStyle={contentContainerStyle}
109
+ >
110
+ {(headers || []).map((header, idx) => (
111
+ <View key={`static-header-${idx}`} style={headerStyle}>
112
+ {header.title ? (
113
+ <SortableListHeader title={header.title} />
114
+ ) : (
115
+ renderHeaderComponent(header.component)
116
+ )}
117
+ </View>
118
+ ))}
119
+ <Sortable.Grid
120
+ scrollableRef={scrollableRef}
121
+ customHandle
122
+ columns={columns}
123
+ rowGap={rowGap}
124
+ columnGap={columnGap}
125
+ data={localData}
126
+ keyExtractor={keyExtractor}
127
+ onDragEnd={handleDragEnd}
128
+ renderItem={({ item, index }) => (
129
+ <View key={keyExtractor(item, index)} style={itemStyle}>
130
+ {renderItem({
131
+ item,
132
+ index,
133
+ renderHandle: (children) => (
134
+ <Sortable.Handle>{children}</Sortable.Handle>
135
+ ),
136
+ })}
137
+ </View>
138
+ )}
139
+ />
140
+ </Animated.ScrollView>
141
+ );
142
+ };
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import type { ViewStyle } from "react-native";
3
+
4
+ export type SortableListProps = {
5
+ scrollViewStyle?: ViewStyle;
6
+ contentContainerStyle?: ViewStyle;
7
+ itemStyle?: ViewStyle;
8
+ headerStyle?: ViewStyle;
9
+ columns?: number;
10
+ rowGap?: number;
11
+ columnGap?: number;
12
+ spacing?: number;
13
+ keyExtractor?: (item: any, index?: number) => string;
14
+ renderItem: ({
15
+ item,
16
+ index,
17
+ renderHandle,
18
+ }: {
19
+ item: any;
20
+ index: number;
21
+ renderHandle: (children: React.ReactElement) => React.ReactElement;
22
+ }) => React.ReactNode;
23
+ sortableData?: any[];
24
+ headers?: { index: number; title?: string; component: any }[];
25
+ onDragEnd?: (params: {
26
+ fromIndex?: number;
27
+ toIndex?: number;
28
+ from?: number;
29
+ to?: number;
30
+ data: any[];
31
+ }) => void;
32
+ };
33
+
34
+ export const SortableList = (_props: SortableListProps) => null;
@@ -0,0 +1,100 @@
1
+ import React, { useCallback, useState } from "react";
2
+ import { StyleSheet, View } from "react-native";
3
+ import { actionExecutor } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutor";
4
+ import {
5
+ AudioPlayerInput,
6
+ VARIANT_NAME_PLAYLIST_INPUT,
7
+ } from "./AudioPlayer/Components/Input";
8
+ import {
9
+ AudioPlayerButton,
10
+ VARIANT_TEXT_ONLY,
11
+ } from "./AudioPlayer/Components/Button";
12
+
13
+ export type TextInputContentProps = {
14
+ /** Injected by the sheet host; unused for layout (header is owned by ModalComponent). */
15
+ dismiss?: () => void;
16
+ width?: number;
17
+ maxHeight?: number;
18
+ currentRoute?: boolean;
19
+ inputLabel?: string;
20
+ defaultValue?: string;
21
+ buttonLabel: string;
22
+ actions: ActionType[];
23
+ };
24
+
25
+ const styles = StyleSheet.create({
26
+ container: {
27
+ paddingHorizontal: 20,
28
+ paddingBottom: 8,
29
+ },
30
+ inputWrap: {
31
+ marginTop: 8,
32
+ marginBottom: 24,
33
+ },
34
+ });
35
+
36
+ /**
37
+ * Modal body only — input + submit.
38
+ * Standard dismiss header comes from BottomSheetModalContent / ModalHeader.
39
+ */
40
+ export function TextInputContent(props: TextInputContentProps) {
41
+ const { inputLabel, defaultValue = "", buttonLabel, actions } = props;
42
+
43
+ const [textValue, setTextValue] = useState(defaultValue);
44
+ const [submitting, setSubmitting] = useState(false);
45
+
46
+ const onSubmit = useCallback(async () => {
47
+ if (submitting) {
48
+ return;
49
+ }
50
+
51
+ setSubmitting(true);
52
+
53
+ try {
54
+ const updatedActions = (actions || []).map((item) => {
55
+ if (item.type === "sendCloudEvent" || item.options?.data) {
56
+ return {
57
+ ...item,
58
+ options: {
59
+ ...item.options,
60
+ data: {
61
+ ...(item.options?.data || {}),
62
+ name: textValue,
63
+ },
64
+ },
65
+ };
66
+ }
67
+
68
+ return item;
69
+ });
70
+
71
+ await actionExecutor.handleActions(updatedActions);
72
+ await actionExecutor.handleAction({ type: "dismissBottomSheet" });
73
+ } finally {
74
+ setSubmitting(false);
75
+ }
76
+ }, [submitting, actions, textValue]);
77
+
78
+ return (
79
+ <View style={styles.container}>
80
+ <View style={styles.inputWrap}>
81
+ <AudioPlayerInput
82
+ configuration={VARIANT_NAME_PLAYLIST_INPUT}
83
+ data={{
84
+ placeholder: inputLabel || "",
85
+ label: inputLabel,
86
+ }}
87
+ value={textValue}
88
+ onChangeText={setTextValue}
89
+ onClearPress={() => setTextValue("")}
90
+ />
91
+ </View>
92
+ <AudioPlayerButton
93
+ configuration={VARIANT_TEXT_ONLY}
94
+ data={{ title: buttonLabel }}
95
+ onPress={onSubmit}
96
+ disabled={submitting}
97
+ />
98
+ </View>
99
+ );
100
+ }
@@ -0,0 +1,74 @@
1
+ import React from "react";
2
+ import { Text } from "react-native";
3
+ import { render, screen } from "@testing-library/react-native";
4
+ import { BottomSheetDragArea } from "../BottomSheetDragArea";
5
+ import { BottomSheetContext } from "@applicaster/zapp-react-native-utils/bottomSheet";
6
+
7
+ // Reveal PanGestureHandler and capture its props.
8
+ jest.mock("react-native-gesture-handler", () => {
9
+ const { View } = require("react-native");
10
+ const actual = jest.requireActual("react-native-gesture-handler");
11
+
12
+ return {
13
+ __esModule: true,
14
+ ...actual,
15
+ PanGestureHandler: ({
16
+ children,
17
+ onGestureEvent,
18
+ onHandlerStateChange,
19
+ simultaneousHandlers,
20
+ }: any) => (
21
+ <View
22
+ testID="drag-pan"
23
+ onGestureEvent={onGestureEvent}
24
+ onHandlerStateChange={onHandlerStateChange}
25
+ simultaneousHandlers={simultaneousHandlers}
26
+ >
27
+ {children}
28
+ </View>
29
+ ),
30
+ };
31
+ });
32
+
33
+ const makeValue = () => ({
34
+ scrollRef: React.createRef<any>(),
35
+ masterDrawerRef: React.createRef<any>(),
36
+ drawerRef: React.createRef<any>(),
37
+ onScroll: jest.fn(),
38
+ setBodyDragEnabled: jest.fn(),
39
+ onDragGestureEvent: jest.fn(),
40
+ onDragStateChange: jest.fn(),
41
+ });
42
+
43
+ describe("BottomSheetDragArea", () => {
44
+ it("wraps children in a sheet-wired PanGestureHandler when inside a sheet", () => {
45
+ const value = makeValue();
46
+
47
+ render(
48
+ <BottomSheetContext.Provider value={value}>
49
+ <BottomSheetDragArea>
50
+ <Text testID="child">hi</Text>
51
+ </BottomSheetDragArea>
52
+ </BottomSheetContext.Provider>
53
+ );
54
+
55
+ const pan = screen.getByTestId("drag-pan");
56
+ expect(pan.props.onGestureEvent).toBe(value.onDragGestureEvent);
57
+ expect(pan.props.onHandlerStateChange).toBe(value.onDragStateChange);
58
+
59
+ expect(pan.props.simultaneousHandlers).toEqual([value.masterDrawerRef]);
60
+
61
+ expect(screen.getByTestId("child")).toBeTruthy();
62
+ });
63
+
64
+ it("renders children bare when used outside a sheet", () => {
65
+ render(
66
+ <BottomSheetDragArea>
67
+ <Text testID="child">hi</Text>
68
+ </BottomSheetDragArea>
69
+ );
70
+
71
+ expect(screen.queryByTestId("drag-pan")).toBeNull();
72
+ expect(screen.getByTestId("child")).toBeTruthy();
73
+ });
74
+ });
@@ -0,0 +1,238 @@
1
+ import React from "react";
2
+ import { render, screen } from "@testing-library/react-native";
3
+ import { BottomSheetModalContent } from "../BottomSheetModalContent";
4
+ import { ContentViewModel } from "@applicaster/zapp-react-native-utils/modalState";
5
+
6
+ jest.mock("react-native-sortables", () => {
7
+ const { View } = require("react-native");
8
+
9
+ const Grid = ({ data, renderItem }: any) => (
10
+ <View testID="sortable-grid">
11
+ {(data || []).map((item: any, index: number) =>
12
+ renderItem({
13
+ item,
14
+ index,
15
+ renderHandle: (children: any) => (
16
+ <View testID={`sortable-handle-${index}`}>{children}</View>
17
+ ),
18
+ })
19
+ )}
20
+ </View>
21
+ );
22
+
23
+ const Handle = ({ children }: any) => (
24
+ <View testID="sortable-handle-wrapper">{children}</View>
25
+ );
26
+
27
+ return {
28
+ __esModule: true,
29
+ default: {
30
+ Grid,
31
+ Handle,
32
+ },
33
+ Grid,
34
+ Handle,
35
+ };
36
+ });
37
+
38
+ jest.mock("@applicaster/zapp-react-native-utils/theme", () => ({
39
+ useTheme: () => ({
40
+ modal_bottom_sheet_padding_top: 0,
41
+ modal_bottom_sheet_padding_bottom: 0,
42
+ modal_bottom_sheet_background_color: "#000",
43
+ modal_bottom_sheet_item_selected_icon: null,
44
+ }),
45
+ }));
46
+
47
+ jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
48
+ usePlugins: () => [],
49
+ }));
50
+
51
+ jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
52
+ useLocalizedStrings: () => ({}),
53
+ }));
54
+
55
+ jest.mock("react-native-safe-area-context", () => ({
56
+ useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
57
+ }));
58
+
59
+ jest.mock("../Header", () => ({
60
+ ModalHeader: ({ title }: any) => {
61
+ const { Text } = require("react-native");
62
+
63
+ return <Text testID="default-header">{title}</Text>;
64
+ },
65
+ }));
66
+
67
+ jest.mock("../Button", () => ({
68
+ Button: ({ label }: any) => {
69
+ const { Text } = require("react-native");
70
+
71
+ return <Text testID="generic-button">{label}</Text>;
72
+ },
73
+ }));
74
+
75
+ jest.mock("../AudioPlayer/Components/Item", () => ({
76
+ AudioPlayerTrackItem: ({ data }: any) => {
77
+ const { Text } = require("react-native");
78
+
79
+ return <Text testID="selectable-row">{data.textLabel1}</Text>;
80
+ },
81
+ mapMenuItemToPlaylistData: (item: any) => ({ textLabel1: item.title }),
82
+ VARIANT_PLAYLIST_ITEM: {},
83
+ }));
84
+
85
+ jest.mock("../AudioPlayer/Components", () => ({
86
+ NowPlayingHeaderSection: () => null,
87
+ AudioPlayerButton: ({ data, onPress }: any) => {
88
+ const { Text } = require("react-native");
89
+
90
+ return (
91
+ <Text testID="create-playlist-button" onPress={onPress}>
92
+ {data.title}
93
+ </Text>
94
+ );
95
+ },
96
+ VARIANT_WITH_ICON: {},
97
+ }));
98
+
99
+ const baseProps = {
100
+ items: [{ id: "track-a", title: "Track A", label: "Track A" }],
101
+ onPress: jest.fn(),
102
+ width: 320,
103
+ currentRoute: true,
104
+ dismiss: jest.fn(),
105
+ title: "Sheet Title",
106
+ maxHeight: 400,
107
+ };
108
+
109
+ describe("BottomSheetModalContent role/behavior cells", () => {
110
+ it("renders standard Button rows and default header when no viewModel/role", () => {
111
+ render(<BottomSheetModalContent {...baseProps} />);
112
+
113
+ expect(screen.getByTestId("default-header")).toBeTruthy();
114
+ expect(screen.getByTestId("generic-button")).toBeTruthy();
115
+ expect(screen.queryByTestId("selectable-row")).toBeNull();
116
+ });
117
+
118
+ it("renders selectable rows when viewModel has collection_selector role", () => {
119
+ const viewModel = new ContentViewModel({
120
+ title: "Playlists",
121
+ items: [{ id: "fav-1", title: "Favorites" }],
122
+ role: "collection_selector",
123
+ behavior: { selectMode: "multi", currentSelection: [] },
124
+ });
125
+
126
+ render(<BottomSheetModalContent {...baseProps} viewModel={viewModel} />);
127
+
128
+ expect(screen.getByTestId("default-header")).toBeTruthy();
129
+ expect(screen.getByTestId("selectable-row")).toBeTruthy();
130
+ expect(screen.queryByTestId("generic-button")).toBeNull();
131
+ });
132
+
133
+ it("does not accept styleVariant as a rendering input", () => {
134
+ render(
135
+ <BottomSheetModalContent
136
+ {...baseProps}
137
+ {...({ styleVariant: "audio_player" } as any)}
138
+ />
139
+ );
140
+
141
+ expect(screen.getByTestId("generic-button")).toBeTruthy();
142
+ expect(screen.queryByTestId("selectable-row")).toBeNull();
143
+ });
144
+
145
+ it("renders SortableList when viewModel has dynamic_collection role with reorder operation", () => {
146
+ const moveMock = jest.fn();
147
+ const { BehaviorSubject } = require("rxjs");
148
+
149
+ const viewModel = new ContentViewModel({
150
+ title: "Queue",
151
+ items: [
152
+ { id: "t1", title: "Track 1" },
153
+ { id: "t2", title: "Track 2" },
154
+ ],
155
+ role: "dynamic_collection",
156
+ dynamicCollection: { operations: ["remove", "reorder"] },
157
+ });
158
+
159
+ viewModel.editableCollection = {
160
+ items$: new BehaviorSubject([
161
+ { id: "t1", title: "Track 1" },
162
+ { id: "t2", title: "Track 2" },
163
+ ]),
164
+ operations: ["remove", "reorder"],
165
+ move: moveMock,
166
+ };
167
+
168
+ render(<BottomSheetModalContent {...baseProps} viewModel={viewModel} />);
169
+
170
+ expect(screen.getByTestId("sortable-grid")).toBeTruthy();
171
+ });
172
+
173
+ it("renders create playlist button when operations include add and calls editableCollection.add on press", () => {
174
+ const addMock = jest.fn();
175
+ const { BehaviorSubject } = require("rxjs");
176
+
177
+ const viewModel = new ContentViewModel({
178
+ title: "Playlists",
179
+ items: [{ id: "p1", title: "Playlist 1" }],
180
+ role: "collection_selector",
181
+ dynamicCollection: { operations: ["add"] },
182
+ });
183
+
184
+ viewModel.editableCollection = {
185
+ items$: new BehaviorSubject([{ id: "p1", title: "Playlist 1" }]),
186
+ operations: ["add"],
187
+ add: addMock,
188
+ };
189
+
190
+ render(<BottomSheetModalContent {...baseProps} viewModel={viewModel} />);
191
+
192
+ const createBtn = screen.getByTestId("create-playlist-button");
193
+ expect(createBtn).toBeTruthy();
194
+
195
+ const { fireEvent } = require("@testing-library/react-native");
196
+ fireEvent.press(createBtn);
197
+
198
+ expect(addMock).toHaveBeenCalledWith({
199
+ id: "create_new_playlist",
200
+ title: "Create New Playlist",
201
+ });
202
+ });
203
+
204
+ it("honors disableScrollViewWrap in contentComponentProps to bypass ScrollView wrapping", () => {
205
+ const CustomComponent = () => (
206
+ <React.Fragment>Custom Content</React.Fragment>
207
+ );
208
+
209
+ const { UNSAFE_getByType } = render(
210
+ <BottomSheetModalContent
211
+ {...baseProps}
212
+ contentComponent={CustomComponent}
213
+ contentComponentProps={{ disableScrollViewWrap: true }}
214
+ />
215
+ );
216
+
217
+ const { ScrollView } = require("react-native");
218
+ expect(() => UNSAFE_getByType(ScrollView)).toThrow();
219
+ });
220
+
221
+ it("honors static disableScrollViewWrap property on ContentComponent", () => {
222
+ const CustomSelfScrollingComponent: any = () => (
223
+ <React.Fragment>Self Scrolling</React.Fragment>
224
+ );
225
+
226
+ CustomSelfScrollingComponent.disableScrollViewWrap = true;
227
+
228
+ const { UNSAFE_getByType } = render(
229
+ <BottomSheetModalContent
230
+ {...baseProps}
231
+ contentComponent={CustomSelfScrollingComponent}
232
+ />
233
+ );
234
+
235
+ const { ScrollView } = require("react-native");
236
+ expect(() => UNSAFE_getByType(ScrollView)).toThrow();
237
+ });
238
+ });
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react-native";
3
+ import { SortableList } from "../SortableList.web";
4
+
5
+ describe("SortableList.web", () => {
6
+ it("renders nothing", () => {
7
+ const { toJSON } = render(
8
+ <SortableList
9
+ renderItem={() => null}
10
+ sortableData={[{ id: "1", title: "Track 1" }]}
11
+ />
12
+ );
13
+
14
+ expect(toJSON()).toBeNull();
15
+ });
16
+ });