Files
alarm-client/__tests__/takeoverReadiness.render.test.tsx
T
Admin a78c4cea14 [feat](client): show what stands between this phone and a real alarm
The blocking grants (exact alarms, full-screen intent, notifications) and the advisory battery exemption are surfaced as readiness, not as warnings that still allow arming. Listening state is read from the persisted native record on resume so a process-killed service cannot leave a stale armed claim on screen.
2026-10-19 02:34:02 +01:00

117 lines
4.0 KiB
TypeScript

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(
<TakeoverReadiness
perms={{ ...ALL_GRANTED, fullScreenIntent: false }}
onFix={onFix}
nativeAvailable
/>,
);
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(
<TakeoverReadiness perms={ALL_GRANTED} onFix={jest.fn()} nativeAvailable />,
);
expect(tree.toJSON()).toBeNull();
});
it('renders nothing while permissions are still being read', () => {
const tree = render(<TakeoverReadiness perms={null} onFix={jest.fn()} nativeAvailable />);
expect(tree.toJSON()).toBeNull();
});
it('renders nothing off-Android, where the grants do not apply', () => {
const tree = render(
<TakeoverReadiness
perms={{ ...ALL_GRANTED, fullScreenIntent: false }}
onFix={jest.fn()}
nativeAvailable={false}
/>,
);
expect(tree.toJSON()).toBeNull();
});
it('keeps the battery exemption advisory but still offers the fix', () => {
const tree = render(
<TakeoverReadiness
perms={{ ...ALL_GRANTED, batteryUnrestricted: false }}
onFix={jest.fn()}
nativeAvailable
/>,
);
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');
});
});