import React from 'react'; import renderer, { act, type ReactTestInstance, type ReactTestRenderer } from 'react-test-renderer'; import { TakeoverReadiness } from '../src/components/TakeoverReadiness'; import type { AlarmPermissions } from '../src/services/alarmNative'; // The banner's logic and copy are what matter here; the house visual primitives // need their native-ish modules stubbed so the component can render under Jest. jest.mock('heroui-native/text', () => ({ Text: require('react-native').Text })); jest.mock('heroui-native/spinner', () => ({ Spinner: require('react-native').View })); jest.mock('heroui-native/surface', () => ({ Surface: require('react-native').View })); jest.mock('lucide-react-native', () => { const ReactMock = require('react'); const { View: RNView } = require('react-native'); const Icon = () => ReactMock.createElement(RNView); return { BellOff: Icon, CircleAlert: Icon, CircleDashed: Icon, ShieldAlert: Icon, ShieldCheck: Icon, Siren: Icon, TriangleAlert: Icon, WifiOff: Icon, RefreshCw: Icon, }; }); const ALL_GRANTED: AlarmPermissions = { notifications: true, fullScreenIntent: true, exactAlarm: true, batteryUnrestricted: true, canForceVolume: true, }; function render(node: React.ReactElement): ReactTestRenderer { let tree: ReactTestRenderer | undefined; act(() => { tree = renderer.create(node); }); return tree as ReactTestRenderer; } /** Concatenates every rendered string so assertions can match the visible copy. */ function flatText(node: ReactTestInstance | string): string { if (typeof node === 'string') return node; return node.children.map(child => flatText(child)).join(' '); } describe('TakeoverReadiness banner', () => { it('renders the explanation and a settings action when the takeover is not granted', () => { const onFix = jest.fn(); const tree = render( , ); const text = flatText(tree.root); expect(text).toContain('This device cannot deliver a real alarm yet'); expect(text).toContain( 'Without this, this device cannot take over the screen when the alert fires.', ); expect(text).toContain('Open settings'); // Blocking, not advisory: it must say the device is not armed meanwhile. expect(text).toContain('this device is not armed and must not be relied on for alerts'); const action = tree.root.findAll( node => node.props?.accessibilityLabel === 'Open settings — Full-screen takeover', )[0]; expect(action).toBeDefined(); act(() => { action.props.onPress(); }); expect(onFix).toHaveBeenCalledWith('fullScreenIntent'); }); it('renders nothing once the takeover path is complete', () => { const tree = render( , ); expect(tree.toJSON()).toBeNull(); }); it('renders nothing while permissions are still being read', () => { const tree = render(); expect(tree.toJSON()).toBeNull(); }); it('renders nothing off-Android, where the grants do not apply', () => { const tree = render( , ); expect(tree.toJSON()).toBeNull(); }); it('keeps the battery exemption advisory but still offers the fix', () => { const tree = render( , ); const text = flatText(tree.root); expect(text).toContain('This device may miss a trigger in the background'); expect(text).toContain('Background reliability'); expect(text).not.toContain('cannot deliver a real alarm yet'); }); });