193 lines
6.0 KiB
TypeScript
193 lines
6.0 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
|
type MockPlatform = "web" | "ios" | "android";
|
|
|
|
type GlobalSnapshot = {
|
|
Notification: unknown;
|
|
navigatorDescriptor?: PropertyDescriptor;
|
|
paseoDesktop: unknown;
|
|
};
|
|
|
|
const originalGlobals: GlobalSnapshot = {
|
|
Notification: (globalThis as { Notification?: unknown }).Notification,
|
|
navigatorDescriptor: Object.getOwnPropertyDescriptor(globalThis, "navigator"),
|
|
paseoDesktop:
|
|
typeof window === "undefined" ? undefined : (window as { paseoDesktop?: unknown }).paseoDesktop,
|
|
};
|
|
|
|
function setNavigator(value: unknown): void {
|
|
Object.defineProperty(globalThis, "navigator", {
|
|
configurable: true,
|
|
writable: true,
|
|
value,
|
|
});
|
|
}
|
|
|
|
function restoreGlobals(): void {
|
|
(globalThis as { Notification?: unknown }).Notification = originalGlobals.Notification;
|
|
|
|
if (originalGlobals.navigatorDescriptor) {
|
|
Object.defineProperty(globalThis, "navigator", originalGlobals.navigatorDescriptor);
|
|
} else {
|
|
delete (globalThis as { navigator?: unknown }).navigator;
|
|
}
|
|
|
|
if (typeof window !== "undefined") {
|
|
(window as { paseoDesktop?: unknown }).paseoDesktop = originalGlobals.paseoDesktop;
|
|
}
|
|
}
|
|
|
|
async function loadModuleForPlatform(platform: MockPlatform) {
|
|
vi.resetModules();
|
|
vi.doMock("react-native", () => ({ Platform: { OS: platform } }));
|
|
return import("./desktop-permissions");
|
|
}
|
|
|
|
describe("desktop-permissions", () => {
|
|
afterEach(() => {
|
|
vi.doUnmock("react-native");
|
|
vi.restoreAllMocks();
|
|
vi.resetModules();
|
|
restoreGlobals();
|
|
});
|
|
|
|
it("shows section only in desktop web runtime", async () => {
|
|
const { shouldShowDesktopPermissionSection } = await loadModuleForPlatform("web");
|
|
|
|
expect(shouldShowDesktopPermissionSection()).toBe(false);
|
|
|
|
(window as { paseoDesktop?: unknown }).paseoDesktop = {};
|
|
expect(shouldShowDesktopPermissionSection()).toBe(true);
|
|
});
|
|
|
|
it("reads notification and microphone status", async () => {
|
|
class MockNotification {
|
|
static permission = "default";
|
|
}
|
|
(globalThis as { Notification?: unknown }).Notification = MockNotification;
|
|
setNavigator({
|
|
permissions: {
|
|
query: vi.fn(async () => ({ state: "granted" })),
|
|
},
|
|
mediaDevices: {
|
|
getUserMedia: vi.fn(),
|
|
},
|
|
});
|
|
|
|
const { getDesktopPermissionSnapshot } = await loadModuleForPlatform("web");
|
|
const snapshot = await getDesktopPermissionSnapshot();
|
|
|
|
expect(snapshot.notifications.state).toBe("prompt");
|
|
expect(snapshot.microphone.state).toBe("granted");
|
|
expect(snapshot.checkedAt).toBeTypeOf("number");
|
|
});
|
|
|
|
it("queries microphone permission with correct Permissions instance binding", async () => {
|
|
const permissions = {
|
|
query(this: unknown, _descriptor: { name: string }) {
|
|
if (this !== permissions) {
|
|
throw new TypeError("Can only call Permissions.query on instances of Permissions");
|
|
}
|
|
return Promise.resolve({ state: "granted" as const });
|
|
},
|
|
};
|
|
|
|
setNavigator({
|
|
permissions,
|
|
mediaDevices: {
|
|
getUserMedia: vi.fn(),
|
|
},
|
|
});
|
|
|
|
const { getDesktopPermissionSnapshot } = await loadModuleForPlatform("web");
|
|
const snapshot = await getDesktopPermissionSnapshot();
|
|
|
|
expect(snapshot.microphone.state).toBe("granted");
|
|
});
|
|
|
|
it("returns a fallback message when runtime blocks Permissions.query", async () => {
|
|
setNavigator({
|
|
permissions: {
|
|
query: vi.fn(async () => {
|
|
throw new TypeError("Can only call Permissions.query on instances of Permissions");
|
|
}),
|
|
},
|
|
mediaDevices: {
|
|
getUserMedia: vi.fn(),
|
|
},
|
|
});
|
|
|
|
const { getDesktopPermissionSnapshot } = await loadModuleForPlatform("web");
|
|
const snapshot = await getDesktopPermissionSnapshot();
|
|
|
|
expect(snapshot.microphone.state).toBe("unknown");
|
|
expect(snapshot.microphone.detail).toContain(
|
|
"Microphone status API is unavailable in this runtime.",
|
|
);
|
|
});
|
|
|
|
it("requests notification permission via the browser Notification API", async () => {
|
|
class MockNotification {
|
|
static permission = "default";
|
|
static requestPermission = vi.fn(async () => "granted");
|
|
}
|
|
(globalThis as { Notification?: unknown }).Notification = MockNotification;
|
|
|
|
const { requestDesktopPermission } = await loadModuleForPlatform("web");
|
|
const result = await requestDesktopPermission({ kind: "notifications" });
|
|
|
|
expect(result.state).toBe("granted");
|
|
expect(MockNotification.requestPermission).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("reads browser Notification permission when available", async () => {
|
|
class MockNotification {
|
|
static permission = "denied";
|
|
}
|
|
(globalThis as { Notification?: unknown }).Notification = MockNotification;
|
|
setNavigator({});
|
|
|
|
const { getDesktopPermissionSnapshot } = await loadModuleForPlatform("web");
|
|
const snapshot = await getDesktopPermissionSnapshot();
|
|
|
|
expect(snapshot.notifications.state).toBe("denied");
|
|
});
|
|
|
|
it("requests microphone permission and stops acquired tracks", async () => {
|
|
const stop = vi.fn();
|
|
const getUserMedia = vi.fn(async () => ({
|
|
getTracks: () => [{ stop }],
|
|
}));
|
|
setNavigator({
|
|
permissions: {
|
|
query: vi.fn(async () => ({ state: "granted" })),
|
|
},
|
|
mediaDevices: {
|
|
getUserMedia,
|
|
},
|
|
});
|
|
|
|
const { requestDesktopPermission } = await loadModuleForPlatform("web");
|
|
const result = await requestDesktopPermission({ kind: "microphone" });
|
|
|
|
expect(result.state).toBe("granted");
|
|
expect(getUserMedia).toHaveBeenCalledWith({ audio: true });
|
|
expect(stop).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("maps microphone request denial to denied status", async () => {
|
|
setNavigator({
|
|
mediaDevices: {
|
|
getUserMedia: vi.fn(async () => {
|
|
throw { name: "NotAllowedError", message: "denied" };
|
|
}),
|
|
},
|
|
});
|
|
|
|
const { requestDesktopPermission } = await loadModuleForPlatform("web");
|
|
const result = await requestDesktopPermission({ kind: "microphone" });
|
|
|
|
expect(result.state).toBe("denied");
|
|
});
|
|
});
|