/** * UAT-3: Browser gate (hard stop) — unit tests for the pure logic in * src/lib/core/browserGate.js. * * CAPTURE.md §Browsers: * Phone: iOS Safari, Android Chrome. * PC: Chrome, Edge, Firefox. * Other clients get a hard stop. */ import { describe, it, expect } from 'vitest'; import { detectBrowser, isAllowed, checkBrowserGate } from '$lib/core/browserGate.js'; // --- representative user agents ------------------------------------ const UAs = { iosSafari: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/605.1.15', ipadSafari: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15', iosChrome: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/124.0.6336.69 Mobile/15E148 Safari/605.1.15', androidChrome: 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6331.115 Mobile Safari/537.36', desktopChrome: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', desktopEdge: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0', desktopFirefox: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0', desktopOpera: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 OPR/110.0.0.0', androidOpera: 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 OPR/110.0.0.0', androidSamsung: 'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/24.0 Chrome/124.0.0.0 Mobile Safari/537.36', iosSamsung: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/124.0.0.0 Mobile/15E148 SamsungBrowser/124.0.0.0', androidBrave: 'Mozilla/5.0 (Linux; Android 14; SM-A546E) AppleWebKit/537.36 (KHTML, like Gecko) Version/14.0.0.5336 Mobile Safari/537.36', unknown: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) CustomBrowser/9.9.0 Safari/537.36', blank: '', }; describe('detectBrowser', () => { it('identifies iOS Safari on iPhone', () => { const b = detectBrowser(UAs.iosSafari); expect(b.name).toBe('iOS Safari'); expect(b.platform).toBe('phone'); }); it('identifies iPad Safari (iPadOS 13+ reports as Macintosh)', () => { const b = detectBrowser(UAs.ipadSafari); expect(b.name).toBe('Safari'); expect(b.platform).toBe('pc'); }); it('identifies Android Chrome', () => { const b = detectBrowser(UAs.androidChrome); expect(b.name).toBe('Android Chrome'); expect(b.platform).toBe('phone'); }); it('identifies desktop Chrome', () => { const b = detectBrowser(UAs.desktopChrome); expect(b.name).toBe('Chrome'); expect(b.platform).toBe('pc'); }); it('identifies desktop Edge (not Chrome)', () => { const b = detectBrowser(UAs.desktopEdge); expect(b.name).toBe('Microsoft Edge'); expect(b.platform).toBe('pc'); }); it('identifies desktop Firefox', () => { const b = detectBrowser(UAs.desktopFirefox); expect(b.name).toBe('Firefox'); expect(b.platform).toBe('pc'); }); it('identifies desktop Opera as its own browser (not Chrome)', () => { const b = detectBrowser(UAs.desktopOpera); expect(b.name).toBe('Opera'); expect(b.platform).toBe('pc'); }); it('identifies Android Opera (OPR beats Chrome)', () => { const b = detectBrowser(UAs.androidOpera); expect(b.name).toBe('Opera'); expect(b.platform).toBe('phone'); }); it('identifies Samsung Internet (SamsungBrowser beats Chrome)', () => { const b = detectBrowser(UAs.androidSamsung); expect(b.name).toBe('Samsung Internet'); expect(b.platform).toBe('phone'); }); it('identifies iOS Samsung Internet as unsupported', () => { const b = detectBrowser(UAs.iosSamsung); expect(b.name).toBe('Samsung Internet'); expect(b.platform).toBe('phone'); }); it('treats an unrecognized UA as Unknown', () => { const b = detectBrowser(UAs.unknown); expect(b.name).toBe('Unknown'); expect(b.platform).toBe('pc'); }); it('handles blank / undefined user agents without throwing', () => { expect(detectBrowser(UAs.blank).name).toBe('Unknown'); expect(detectBrowser(undefined).name).toBe('Unknown'); }); }); describe('isAllowed', () => { it('allows iOS Safari on phone', () => { expect(isAllowed({ name: 'iOS Safari', platform: 'phone' })).toBe(true); }); it('allows Android Chrome on phone', () => { expect(isAllowed({ name: 'Android Chrome', platform: 'phone' })).toBe(true); }); it('allows Chrome on PC', () => { expect(isAllowed({ name: 'Chrome', platform: 'pc' })).toBe(true); }); it('allows Edge on PC', () => { expect(isAllowed({ name: 'Microsoft Edge', platform: 'pc' })).toBe(true); }); it('allows Firefox on PC', () => { expect(isAllowed({ name: 'Firefox', platform: 'pc' })).toBe(true); }); it('rejects Opera', () => { expect(isAllowed({ name: 'Opera', platform: 'pc' })).toBe(false); }); it('rejects Android Opera', () => { expect(isAllowed({ name: 'Opera', platform: 'phone' })).toBe(false); }); it('rejects Samsung Internet', () => { expect(isAllowed({ name: 'Samsung Internet', platform: 'phone' })).toBe(false); }); it('rejects Samsung Internet on iOS', () => { expect(isAllowed({ name: 'Samsung Internet', platform: 'phone' })).toBe(false); }); it('rejects Brave', () => { expect(isAllowed({ name: 'Brave', platform: 'phone' })).toBe(false); }); it('rejects Unknown', () => { expect(isAllowed({ name: 'Unknown', platform: 'pc' })).toBe(false); }); it('rejects phone browsers on PC (impossible but rejected)', () => { expect(isAllowed({ name: 'iOS Safari', platform: 'pc' })).toBe(false); expect(isAllowed({ name: 'Android Chrome', platform: 'pc' })).toBe(false); }); }); describe('checkBrowserGate', () => { it('supports exactly the five spec browsers + Safari (iPad)', () => { for (const ua of [ UAs.iosSafari, UAs.ipadSafari, UAs.androidChrome, UAs.desktopChrome, UAs.desktopEdge, UAs.desktopFirefox, ]) { expect(checkBrowserGate(ua).supported).toBe(true); } }); it('hard-stops Opera, Samsung Internet, unknown, blank', () => { for (const ua of [ UAs.desktopOpera, UAs.androidOpera, UAs.androidSamsung, UAs.iosSamsung, UAs.androidBrave, UAs.unknown, UAs.blank, ]) { expect(checkBrowserGate(ua).supported).toBe(false); } }); it('returns the detected browser for the UI', () => { const r = checkBrowserGate(UAs.desktopOpera); expect(r.browser.name).toBe('Opera'); expect(r.browser.platform).toBe('pc'); }); });