import React from "react"; import { render, screen } from "@testing-library/react-native"; import { BottomSheetModalContent } from "../BottomSheetModalContent"; import { ContentViewModel } from "@applicaster/zapp-react-native-utils/modalState"; jest.mock("react-native-sortables", () => { const { View } = require("react-native"); const Grid = ({ data, renderItem }: any) => ( {(data || []).map((item: any, index: number) => renderItem({ item, index, renderHandle: (children: any) => ( {children} ), }) )} ); const Handle = ({ children }: any) => ( {children} ); return { __esModule: true, default: { Grid, Handle, }, Grid, Handle, }; }); jest.mock("@applicaster/zapp-react-native-utils/theme", () => ({ useTheme: () => ({ modal_bottom_sheet_padding_top: 0, modal_bottom_sheet_padding_bottom: 0, modal_bottom_sheet_background_color: "#000", modal_bottom_sheet_item_selected_icon: null, }), })); jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({ usePlugins: () => [], })); jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({ useLocalizedStrings: () => ({}), })); jest.mock("react-native-safe-area-context", () => ({ useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), })); jest.mock("../Header", () => ({ ModalHeader: ({ title }: any) => { const { Text } = require("react-native"); return {title}; }, })); jest.mock("../Button", () => ({ Button: ({ label }: any) => { const { Text } = require("react-native"); return {label}; }, })); jest.mock("../AudioPlayer/Components/Item", () => ({ AudioPlayerTrackItem: ({ data }: any) => { const { Text } = require("react-native"); return {data.textLabel1}; }, mapMenuItemToPlaylistData: (item: any) => ({ textLabel1: item.title }), VARIANT_PLAYLIST_ITEM: {}, })); jest.mock("../AudioPlayer/Components", () => ({ NowPlayingHeaderSection: () => null, AudioPlayerButton: ({ data, onPress }: any) => { const { Text } = require("react-native"); return ( {data.title} ); }, VARIANT_WITH_ICON: {}, })); const baseProps = { items: [{ id: "track-a", title: "Track A", label: "Track A" }], onPress: jest.fn(), width: 320, currentRoute: true, dismiss: jest.fn(), title: "Sheet Title", maxHeight: 400, }; describe("BottomSheetModalContent role/behavior cells", () => { it("renders standard Button rows and default header when no viewModel/role", () => { render(); expect(screen.getByTestId("default-header")).toBeTruthy(); expect(screen.getByTestId("generic-button")).toBeTruthy(); expect(screen.queryByTestId("selectable-row")).toBeNull(); }); it("renders selectable rows when viewModel has collection_selector role", () => { const viewModel = new ContentViewModel({ title: "Playlists", items: [{ id: "fav-1", title: "Favorites" }], role: "collection_selector", behavior: { selectMode: "multi", currentSelection: [] }, }); render(); expect(screen.getByTestId("default-header")).toBeTruthy(); expect(screen.getByTestId("selectable-row")).toBeTruthy(); expect(screen.queryByTestId("generic-button")).toBeNull(); }); it("does not accept styleVariant as a rendering input", () => { render( ); expect(screen.getByTestId("generic-button")).toBeTruthy(); expect(screen.queryByTestId("selectable-row")).toBeNull(); }); it("renders SortableList when viewModel has dynamic_collection role with reorder operation", () => { const moveMock = jest.fn(); const { BehaviorSubject } = require("rxjs"); const viewModel = new ContentViewModel({ title: "Queue", items: [ { id: "t1", title: "Track 1" }, { id: "t2", title: "Track 2" }, ], role: "dynamic_collection", dynamicCollection: { operations: ["remove", "reorder"] }, }); viewModel.editableCollection = { items$: new BehaviorSubject([ { id: "t1", title: "Track 1" }, { id: "t2", title: "Track 2" }, ]), operations: ["remove", "reorder"], move: moveMock, }; render(); expect(screen.getByTestId("sortable-grid")).toBeTruthy(); }); it("renders create playlist button when operations include add and calls editableCollection.add on press", () => { const addMock = jest.fn(); const { BehaviorSubject } = require("rxjs"); const viewModel = new ContentViewModel({ title: "Playlists", items: [{ id: "p1", title: "Playlist 1" }], role: "collection_selector", dynamicCollection: { operations: ["add"] }, }); viewModel.editableCollection = { items$: new BehaviorSubject([{ id: "p1", title: "Playlist 1" }]), operations: ["add"], add: addMock, }; render(); const createBtn = screen.getByTestId("create-playlist-button"); expect(createBtn).toBeTruthy(); const { fireEvent } = require("@testing-library/react-native"); fireEvent.press(createBtn); expect(addMock).toHaveBeenCalledWith({ id: "create_new_playlist", title: "Create New Playlist", }); }); it("honors disableScrollViewWrap in contentComponentProps to bypass ScrollView wrapping", () => { const CustomComponent = () => ( Custom Content ); const { UNSAFE_getByType } = render( ); const { ScrollView } = require("react-native"); expect(() => UNSAFE_getByType(ScrollView)).toThrow(); }); it("honors static disableScrollViewWrap property on ContentComponent", () => { const CustomSelfScrollingComponent: any = () => ( Self Scrolling ); CustomSelfScrollingComponent.disableScrollViewWrap = true; const { UNSAFE_getByType } = render( ); const { ScrollView } = require("react-native"); expect(() => UNSAFE_getByType(ScrollView)).toThrow(); }); it("sets keyboardShouldPersistTaps='handled' on ScrollView wrapping contentComponent", () => { const CustomComponent = () => ( Custom Content ); const { UNSAFE_getByType } = render( ); const { ScrollView } = require("react-native"); const scrollView = UNSAFE_getByType(ScrollView); expect(scrollView.props.keyboardShouldPersistTaps).toBe("handled"); }); });