import type { AlarmPermissions } from '../src/services/alarmNative'; import { READINESS_COPY, canDeliverRealAlarm, deliverySummary, takeoverReadiness, } from '../src/lib/takeoverReadiness'; const ALL_GRANTED: AlarmPermissions = { notifications: true, fullScreenIntent: true, exactAlarm: true, batteryUnrestricted: true, canForceVolume: true, }; function perms(patch: Partial): AlarmPermissions { return { ...ALL_GRANTED, ...patch }; } describe('takeover readiness', () => { it('is unknown (and renders nothing) while permissions are still loading', () => { const readiness = takeoverReadiness(null); expect(readiness.known).toBe(false); expect(readiness.blocked).toBe(false); expect(readiness.blockers).toHaveLength(0); }); it('is complete when every grant is present', () => { const readiness = takeoverReadiness(ALL_GRANTED); expect(readiness.known).toBe(true); expect(readiness.blocked).toBe(false); expect(readiness.blockers).toHaveLength(0); }); it('treats missing exact alarms as blocking, because the alarm clock is the delivery', () => { const readiness = takeoverReadiness(perms({ exactAlarm: false })); expect(readiness.blocked).toBe(true); const exact = readiness.blockers.find(b => b.key === 'exactAlarm'); expect(exact).toBeDefined(); expect(exact?.blocking).toBe(true); expect(exact?.body).toContain('cannot fire a real alarm'); }); it('treats a missing full-screen intent as blocking, never as a notification fallback', () => { const readiness = takeoverReadiness(perms({ fullScreenIntent: false })); expect(readiness.blocked).toBe(true); const fsi = readiness.blockers.find(b => b.key === 'fullScreenIntent'); expect(fsi).toBeDefined(); expect(fsi?.blocking).toBe(true); expect(fsi?.body).toContain('cannot take over the screen'); // The old design promised a notification instead. That outcome is gone. expect(fsi?.body).not.toMatch(/instead of taking over/i); expect(fsi?.body).toContain('not usable for alerts'); expect(fsi?.actionLabel).toBe('Open settings'); }); it('treats missing notifications as blocking (the foreground service needs one)', () => { const readiness = takeoverReadiness(perms({ notifications: false })); expect(readiness.blocked).toBe(true); const notifications = readiness.blockers.find(b => b.key === 'notifications'); expect(notifications?.blocking).toBe(true); expect(notifications?.body).toContain('cannot keep listening while the app is closed'); }); it('keeps the battery exemption advisory, not blocking', () => { const readiness = takeoverReadiness(perms({ batteryUnrestricted: false })); expect(readiness.known).toBe(true); expect(readiness.blocked).toBe(false); const battery = readiness.blockers.find(b => b.key === 'battery'); expect(battery?.blocking).toBe(false); expect(battery?.body).toBe(READINESS_COPY.battery.body); }); it('reports every blocker in priority order when nothing is granted', () => { const readiness = takeoverReadiness( perms({ exactAlarm: false, notifications: false, fullScreenIntent: false, batteryUnrestricted: false, }), ); expect(readiness.blocked).toBe(true); expect(readiness.blockers.map(b => b.key)).toEqual([ 'exactAlarm', 'fullScreenIntent', 'notifications', 'battery', ]); }); }); describe('canDeliverRealAlarm', () => { it('is true only when every blocking grant is present', () => { expect(canDeliverRealAlarm(ALL_GRANTED)).toBe(true); expect(canDeliverRealAlarm(perms({ exactAlarm: false }))).toBe(false); expect(canDeliverRealAlarm(perms({ fullScreenIntent: false }))).toBe(false); expect(canDeliverRealAlarm(perms({ notifications: false }))).toBe(false); // Advice, not a gate. expect(canDeliverRealAlarm(perms({ batteryUnrestricted: false }))).toBe(true); expect(canDeliverRealAlarm(null)).toBe(false); }); }); describe('delivery summary', () => { it('never claims a real alarm while the takeover is blocked', () => { const summary = deliverySummary(perms({ fullScreenIntent: false })); expect(summary).toBe( 'This device cannot take over the screen — the full-screen takeover is not allowed.', ); expect(summary).not.toMatch(/maximum volume/); expect(summary).not.toMatch(/notification/i); }); it('names the exact-alarm gap first, since that is the delivery mechanism', () => { expect(deliverySummary(perms({ exactAlarm: false }))).toBe( 'This device cannot fire a real alarm — exact alarms are not allowed.', ); }); it('says it cannot keep listening without notifications', () => { expect(deliverySummary(perms({ notifications: false }))).toBe( 'This device cannot keep listening while the app is closed — notifications are not allowed.', ); }); it('claims a real alarm only when every blocking grant is present', () => { expect(deliverySummary(ALL_GRANTED)).toBe( 'This device fires a real alarm at maximum volume when the owner triggers the alert.', ); // Battery is advisory: it does not change the promise. expect(deliverySummary(perms({ batteryUnrestricted: false }))).toBe( 'This device fires a real alarm at maximum volume when the owner triggers the alert.', ); }); it('does not claim readiness while permissions are unread', () => { expect(deliverySummary(null)).toMatch(/Checking/); }); });