import { describe, test, expect } from "bun:test"; import { isLikelyMobile, isModelAllowedOnDevice, modelsForDevice, shouldWarnBeforeLoad, } from "../src/lib/mobileMemoryGuard"; import { MODELS, PARAKEET_MODEL_ID } from "../src/lib/models"; // Pure UA/heuristic logic (D-027) — no browser, no navigator. const ANDROID_UA = "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Mobile Safari/537.36"; const IPHONE_UA = "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 IPAD_DESKTOP_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"; const DESKTOP_MAC_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"; const DESKTOP_WINDOWS_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"; describe("isLikelyMobile", () => { test("detects Android and iPhone user agents", () => { expect(isLikelyMobile({ userAgent: ANDROID_UA, maxTouchPoints: 5 })).toBe(true); expect(isLikelyMobile({ userAgent: IPHONE_UA, maxTouchPoints: 5 })).toBe(true); }); test("detects iPadOS's desktop-Safari UA via touch points", () => { expect(isLikelyMobile({ userAgent: IPAD_DESKTOP_UA, maxTouchPoints: 5 })).toBe(true); // Same UA, no touch support reported — treat as a real desktop Mac. expect(isLikelyMobile({ userAgent: IPAD_DESKTOP_UA, maxTouchPoints: 0 })).toBe(false); expect(isLikelyMobile({ userAgent: IPAD_DESKTOP_UA, maxTouchPoints: undefined })).toBe( false, ); }); test("does not flag real desktop browsers", () => { expect(isLikelyMobile({ userAgent: DESKTOP_MAC_UA, maxTouchPoints: 0 })).toBe(false); expect(isLikelyMobile({ userAgent: DESKTOP_WINDOWS_UA, maxTouchPoints: 0 })).toBe(false); }); test("undefined userAgent (SSR/tests) is never mobile", () => { expect(isLikelyMobile({ userAgent: undefined, maxTouchPoints: 5 })).toBe(false); }); }); describe("shouldWarnBeforeLoad", () => { const MEDIUM_PEAK_BYTES = 555_000_000; // ~medium q4's estimated peak test("never warns on desktop regardless of size or reported memory", () => { expect( shouldWarnBeforeLoad( { userAgent: DESKTOP_WINDOWS_UA, maxTouchPoints: 0, deviceMemoryGB: 0.5 }, MEDIUM_PEAK_BYTES, ), ).toBe(false); }); test("warns on mobile with low reported deviceMemory", () => { // Safe budget = 1 GB * 0.5 = 500 MB, below the ~555 MB medium peak. expect( shouldWarnBeforeLoad( { userAgent: ANDROID_UA, maxTouchPoints: 5, deviceMemoryGB: 1 }, MEDIUM_PEAK_BYTES, ), ).toBe(true); }); test("does not warn on mobile with ample reported deviceMemory", () => { expect( shouldWarnBeforeLoad( { userAgent: ANDROID_UA, maxTouchPoints: 5, deviceMemoryGB: 8 }, MEDIUM_PEAK_BYTES, ), ).toBe(false); }); // D-029: deviceMemoryGB 2-4 is the ordinary (not just budget) Android // range today — these must still warn for Medium+, the exact class of // phone the original D-027 crash report was about. test("warns on mobile mid-range devices (deviceMemory 2-4) for Medium+", () => { for (const deviceMemoryGB of [2, 4]) { expect( shouldWarnBeforeLoad( { userAgent: ANDROID_UA, maxTouchPoints: 5, deviceMemoryGB }, MEDIUM_PEAK_BYTES, ), ).toBe(true); } }); test("falls back to a flat threshold when deviceMemory is unreported (iOS)", () => { expect( shouldWarnBeforeLoad( { userAgent: IPHONE_UA, maxTouchPoints: 5, deviceMemoryGB: undefined }, MEDIUM_PEAK_BYTES, ), ).toBe(true); expect( shouldWarnBeforeLoad( { userAgent: IPHONE_UA, maxTouchPoints: 5, deviceMemoryGB: undefined }, 50_000_000, // tiny model — well under the fallback threshold ), ).toBe(false); }); }); describe("desktop-only models (D-051)", () => { const parakeet = MODELS.find((model) => model.id === PARAKEET_MODEL_ID)!; const phone = { userAgent: ANDROID_UA, maxTouchPoints: 5, deviceMemoryGB: 8 }; const ipad = { userAgent: IPAD_DESKTOP_UA, maxTouchPoints: 5, deviceMemoryGB: undefined }; const desktop = { userAgent: DESKTOP_WINDOWS_UA, maxTouchPoints: 0, deviceMemoryGB: 8 }; test("Parakeet is never offered on phones or tablets, even high-memory ones", () => { expect(isModelAllowedOnDevice(parakeet, phone)).toBe(false); expect(isModelAllowedOnDevice(parakeet, ipad)).toBe(false); expect(isModelAllowedOnDevice(parakeet, desktop)).toBe(true); }); test("Whisper models stay allowed everywhere (they get the D-027 confirm instead)", () => { for (const model of MODELS.filter((m) => m.id !== PARAKEET_MODEL_ID)) { expect(isModelAllowedOnDevice(model, phone)).toBe(true); } }); test("modelsForDevice filters the list, keeping order", () => { expect(modelsForDevice(MODELS, phone)).toEqual(MODELS.filter((m) => m !== parakeet)); expect(modelsForDevice(MODELS, desktop)).toEqual([...MODELS]); }); });