import { afterEach, describe, expect, it, vi } from 'vitest'; import { isIosSafari, isStandalone, resolveInstallMode } from './pwa'; describe('resolveInstallMode', () => { const base = { deferredAvailable: false, standalone: false, iosSafari: false, inMiniApp: false }; it('hides inside a Mini App even with a captured prompt', () => { expect(resolveInstallMode({ ...base, inMiniApp: true, deferredAvailable: true })).toBe('hidden'); }); it('hides once the app is installed (standalone)', () => { expect(resolveInstallMode({ ...base, standalone: true, deferredAvailable: true })).toBe('hidden'); }); it('offers one-tap when a Chromium prompt was captured', () => { expect(resolveInstallMode({ ...base, deferredAvailable: true })).toBe('oneTap'); }); it('offers iOS instructions on iOS Safari without a prompt', () => { expect(resolveInstallMode({ ...base, iosSafari: true })).toBe('iosInstructions'); }); it('prefers one-tap over iOS instructions when both apply', () => { expect(resolveInstallMode({ ...base, deferredAvailable: true, iosSafari: true })).toBe('oneTap'); }); it('hides on an unsupported browser (no prompt, not iOS)', () => { expect(resolveInstallMode(base)).toBe('hidden'); }); }); describe('isStandalone', () => { afterEach(() => vi.unstubAllGlobals()); it('is true when the display-mode media query matches', () => { vi.stubGlobal('window', { matchMedia: (q: string) => ({ matches: q.includes('standalone') }), navigator: {}, }); expect(isStandalone()).toBe(true); }); it('is true for the iOS navigator.standalone flag', () => { vi.stubGlobal('window', { matchMedia: () => ({ matches: false }), navigator: { standalone: true }, }); expect(isStandalone()).toBe(true); }); it('is false in an ordinary browser tab', () => { vi.stubGlobal('window', { matchMedia: () => ({ matches: false }), navigator: { standalone: false }, }); expect(isStandalone()).toBe(false); }); }); describe('isIosSafari', () => { afterEach(() => vi.unstubAllGlobals()); const IPHONE_SAFARI = 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1'; const IPHONE_CHROME = 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/120.0 Mobile/15E148 Safari/604.1'; const ANDROID_CHROME = 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Mobile Safari/537.36'; const IPAD_SAFARI = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15'; it('is true for Safari on iPhone', () => { vi.stubGlobal('navigator', { userAgent: IPHONE_SAFARI, maxTouchPoints: 5 }); expect(isIosSafari()).toBe(true); }); it('is true for Safari on iPadOS (Mac UA + touch points)', () => { vi.stubGlobal('navigator', { userAgent: IPAD_SAFARI, maxTouchPoints: 5 }); expect(isIosSafari()).toBe(true); }); it('is false for a desktop Mac (no touch points)', () => { vi.stubGlobal('navigator', { userAgent: IPAD_SAFARI, maxTouchPoints: 0 }); expect(isIosSafari()).toBe(false); }); it('is false for Chrome on iOS (CriOS)', () => { vi.stubGlobal('navigator', { userAgent: IPHONE_CHROME, maxTouchPoints: 5 }); expect(isIosSafari()).toBe(false); }); it('is false for Android Chrome', () => { vi.stubGlobal('navigator', { userAgent: ANDROID_CHROME, maxTouchPoints: 5 }); expect(isIosSafari()).toBe(false); }); });