Spaces:
Running
Running
File size: 6,332 Bytes
6c3af4e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
REQUEST_PERMISSION_TIMEOUT_MS,
requestNotificationPermission,
showAiCompleteNotification,
} from "./notifications";
const addLogEntryMock = vi.fn();
vi.mock("./logEntries", () => ({
addLogEntry: (...args: unknown[]) => addLogEntryMock(...args),
}));
class MockNotification {
static permission: NotificationPermission = "default";
static requestPermission = vi.fn<() => Promise<NotificationPermission>>();
static lastInstance: MockNotification | undefined;
title: string;
options?: NotificationOptions;
onclick: (() => void) | null = null;
close = vi.fn();
constructor(title: string, options?: NotificationOptions) {
this.title = title;
this.options = options;
MockNotification.lastInstance = this;
}
}
describe("notifications module", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.unstubAllGlobals();
MockNotification.permission = "default";
MockNotification.lastInstance = undefined;
});
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
describe("requestNotificationPermission", () => {
it("returns denied when the Notification API is unsupported", async () => {
const result = await requestNotificationPermission();
expect(result).toBe("denied");
});
it("returns granted without prompting when already granted", async () => {
MockNotification.permission = "granted";
vi.stubGlobal("Notification", MockNotification);
const result = await requestNotificationPermission();
expect(result).toBe("granted");
expect(MockNotification.requestPermission).not.toHaveBeenCalled();
});
it("returns denied without prompting when already denied", async () => {
MockNotification.permission = "denied";
vi.stubGlobal("Notification", MockNotification);
const result = await requestNotificationPermission();
expect(result).toBe("denied");
expect(MockNotification.requestPermission).not.toHaveBeenCalled();
});
it("prompts the user when permission has not been decided yet", async () => {
MockNotification.permission = "default";
MockNotification.requestPermission.mockResolvedValue("granted");
vi.stubGlobal("Notification", MockNotification);
const result = await requestNotificationPermission();
expect(result).toBe("granted");
expect(MockNotification.requestPermission).toHaveBeenCalledOnce();
});
it("falls back to default if the browser never responds to the request", async () => {
vi.useFakeTimers();
try {
MockNotification.permission = "default";
MockNotification.requestPermission.mockReturnValue(
new Promise(() => {}),
);
vi.stubGlobal("Notification", MockNotification);
const resultPromise = requestNotificationPermission();
await vi.advanceTimersByTimeAsync(REQUEST_PERMISSION_TIMEOUT_MS);
expect(await resultPromise).toBe("default");
} finally {
vi.useRealTimers();
}
});
});
describe("showAiCompleteNotification", () => {
it("does nothing when the Notification API is unsupported", () => {
expect(() => showAiCompleteNotification("test query")).not.toThrow();
});
it("does nothing when permission is not granted", () => {
MockNotification.permission = "default";
vi.stubGlobal("Notification", MockNotification);
showAiCompleteNotification("test query");
expect(MockNotification.lastInstance).toBeUndefined();
});
it("does nothing when the page already has focus", () => {
MockNotification.permission = "granted";
vi.stubGlobal("Notification", MockNotification);
vi.spyOn(document, "hasFocus").mockReturnValue(true);
showAiCompleteNotification("test query");
expect(MockNotification.lastInstance).toBeUndefined();
});
it("shows a notification with the query as the title when granted and unfocused", () => {
MockNotification.permission = "granted";
vi.stubGlobal("Notification", MockNotification);
vi.spyOn(document, "hasFocus").mockReturnValue(false);
showAiCompleteNotification("capital of France");
expect(MockNotification.lastInstance?.title).toBe("capital of France");
expect(MockNotification.lastInstance?.options?.body).toBe(
"AI response is ready on MiniSearch.",
);
expect(MockNotification.lastInstance?.options?.icon).toBe("/favicon.png");
});
it("truncates very long queries in the notification title", () => {
MockNotification.permission = "granted";
vi.stubGlobal("Notification", MockNotification);
vi.spyOn(document, "hasFocus").mockReturnValue(false);
const longQuery = "a".repeat(200);
showAiCompleteNotification(longQuery);
const title = MockNotification.lastInstance?.title ?? "";
expect(title.length).toBeLessThan(longQuery.length);
});
it("focuses the window and closes the notification when clicked", () => {
MockNotification.permission = "granted";
vi.stubGlobal("Notification", MockNotification);
vi.spyOn(document, "hasFocus").mockReturnValue(false);
const focusSpy = vi.spyOn(window, "focus").mockImplementation(() => {});
showAiCompleteNotification("test query");
const instance = MockNotification.lastInstance;
expect(instance?.onclick).toBeTypeOf("function");
instance?.onclick?.();
expect(focusSpy).toHaveBeenCalledOnce();
expect(instance?.close).toHaveBeenCalledOnce();
});
it("logs instead of throwing when the browser rejects the constructor", () => {
class ThrowingNotification extends MockNotification {
constructor(title: string, options?: NotificationOptions) {
super(title, options);
throw new TypeError("Illegal constructor");
}
}
ThrowingNotification.permission = "granted";
vi.stubGlobal("Notification", ThrowingNotification);
vi.spyOn(document, "hasFocus").mockReturnValue(false);
expect(() => showAiCompleteNotification("test query")).not.toThrow();
expect(addLogEntryMock).toHaveBeenCalledWith(
expect.stringContaining("Illegal constructor"),
);
});
});
});
|