fix(pwa): tighten install support detection
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { PwaUtil } from "../pwa";
|
||||
|
||||
const ANDROID_CHROME_UA =
|
||||
"Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36";
|
||||
const ANDROID_FACEBOOK_UA =
|
||||
"Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.0.0 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/566.0.0.48.73;]";
|
||||
const ANDROID_SAMSUNG_UA =
|
||||
"Mozilla/5.0 (Linux; Android 14; SM-S928B) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/26.0 Chrome/122.0.0.0 Mobile Safari/537.36";
|
||||
const IOS_SAFARI_UA =
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1";
|
||||
const DESKTOP_CHROME_UA =
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36";
|
||||
const DESKTOP_FIREFOX_UA =
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.5; rv:126.0) Gecko/20100101 Firefox/126.0";
|
||||
|
||||
describe("PwaUtil", () => {
|
||||
beforeEach(() => {
|
||||
setUserAgent(DESKTOP_CHROME_UA);
|
||||
setServiceWorkerSupported(true);
|
||||
setStandalone(false);
|
||||
});
|
||||
|
||||
it("supports native install prompt on desktop Chrome", () => {
|
||||
setUserAgent(DESKTOP_CHROME_UA);
|
||||
|
||||
expect(new PwaUtil().isSupported()).toBe(true);
|
||||
});
|
||||
|
||||
it("supports native install prompt on Android Chrome", () => {
|
||||
setUserAgent(ANDROID_CHROME_UA);
|
||||
|
||||
expect(new PwaUtil().isSupported()).toBe(true);
|
||||
});
|
||||
|
||||
it("supports native install prompt on Samsung Internet", () => {
|
||||
setUserAgent(ANDROID_SAMSUNG_UA);
|
||||
|
||||
expect(new PwaUtil().isSupported()).toBe(true);
|
||||
});
|
||||
|
||||
it("does not support native install prompt on iOS Safari", () => {
|
||||
setUserAgent(IOS_SAFARI_UA);
|
||||
|
||||
expect(new PwaUtil().isSupported()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not support install prompt in Facebook in-app browser", () => {
|
||||
setUserAgent(ANDROID_FACEBOOK_UA);
|
||||
|
||||
expect(new PwaUtil().isSupported()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not support native install prompt on desktop Firefox", () => {
|
||||
setUserAgent(DESKTOP_FIREFOX_UA);
|
||||
|
||||
expect(new PwaUtil().isSupported()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not show install entry before beforeinstallprompt is captured", () => {
|
||||
const util = new PwaUtil();
|
||||
util.prepareInstallPrompt();
|
||||
|
||||
expect(util.canShowInstallEntry()).toBe(false);
|
||||
});
|
||||
|
||||
it("shows install entry after beforeinstallprompt is captured", () => {
|
||||
const util = new PwaUtil();
|
||||
const listener = vi.fn();
|
||||
util.subscribe(listener);
|
||||
util.prepareInstallPrompt();
|
||||
|
||||
dispatchBeforeInstallPrompt();
|
||||
|
||||
expect(listener).toHaveBeenCalledTimes(1);
|
||||
expect(util.canShowInstallEntry()).toBe(true);
|
||||
});
|
||||
|
||||
it("does not show install entry when already installed", () => {
|
||||
const util = new PwaUtil();
|
||||
setStandalone(true);
|
||||
util.prepareInstallPrompt();
|
||||
|
||||
dispatchBeforeInstallPrompt();
|
||||
|
||||
expect(util.canShowInstallEntry()).toBe(false);
|
||||
});
|
||||
|
||||
it("prompts native install when a deferred event is available", async () => {
|
||||
const util = new PwaUtil();
|
||||
const prompt = vi.fn().mockResolvedValue(undefined);
|
||||
util.prepareInstallPrompt();
|
||||
dispatchBeforeInstallPrompt({
|
||||
prompt,
|
||||
outcome: "accepted",
|
||||
});
|
||||
|
||||
await expect(util.install()).resolves.toBe("accepted");
|
||||
expect(prompt).toHaveBeenCalledTimes(1);
|
||||
expect(util.canShowInstallEntry()).toBe(false);
|
||||
});
|
||||
|
||||
it("returns unavailable when no deferred prompt is available", async () => {
|
||||
const util = new PwaUtil();
|
||||
util.prepareInstallPrompt();
|
||||
|
||||
await expect(util.install()).resolves.toBe("unavailable");
|
||||
});
|
||||
});
|
||||
|
||||
function setUserAgent(userAgent: string): void {
|
||||
Object.defineProperty(window.navigator, "userAgent", {
|
||||
value: userAgent,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
function setServiceWorkerSupported(supported: boolean): void {
|
||||
if (supported) {
|
||||
Object.defineProperty(window.navigator, "serviceWorker", {
|
||||
value: {},
|
||||
configurable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty(window.navigator, "serviceWorker", {
|
||||
value: undefined,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
function setStandalone(installed: boolean): void {
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
value: vi.fn().mockImplementation((query: string) => ({
|
||||
matches: installed && query === "(display-mode: standalone)",
|
||||
media: query,
|
||||
onchange: null,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
addListener: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(window.navigator, "standalone", {
|
||||
value: installed,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
function dispatchBeforeInstallPrompt({
|
||||
prompt = vi.fn().mockResolvedValue(undefined),
|
||||
outcome = "dismissed",
|
||||
}: {
|
||||
prompt?: () => Promise<void>;
|
||||
outcome?: "accepted" | "dismissed";
|
||||
} = {}): void {
|
||||
const event = new Event("beforeinstallprompt", {
|
||||
cancelable: true,
|
||||
}) as Event & {
|
||||
prompt: () => Promise<void>;
|
||||
userChoice: Promise<{ outcome: "accepted" | "dismissed" }>;
|
||||
};
|
||||
event.prompt = prompt;
|
||||
event.userChoice = Promise.resolve({ outcome });
|
||||
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
Reference in New Issue
Block a user