Воркер теперь собирается через injectManifest: generateSW умеет только кэш, а уведомление показывает именно воркер — страница при этом может быть закрыта. Нажатие на уведомление переиспользует уже открытое окно, а не плодит второе. Включение — строкой на главном экране, по нажатию. Не всплывающим окном на входе: разрешение спрашивается один раз за установку, отказ браузер запоминает навсегда, и потраченная впустую попытка закрывает канал. Если сервер уведомления не настроил — разрешение не запрашиваем вовсе. Отдельные тексты вместо молчания: на iPhone без установки на домашний экран уведомления невозможны в принципе, и об этом сказано прямо. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
92 lines
3.3 KiB
TypeScript
92 lines
3.3 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
|
|
vi.mock("@/api/driver", () => ({
|
|
getPushKey: vi.fn(async () => ({ key: "BIIh_RELLY", enabled: true })),
|
|
subscribePush: vi.fn(async () => ({ ok: true })),
|
|
unsubscribePush: vi.fn(async () => ({ ok: true })),
|
|
}));
|
|
|
|
import { currentState, enable, supported } from "./push";
|
|
import * as api from "@/api/driver";
|
|
|
|
const SUB = {
|
|
endpoint: "https://fcm.googleapis.com/fcm/send/abc",
|
|
toJSON: () => ({
|
|
endpoint: "https://fcm.googleapis.com/fcm/send/abc",
|
|
keys: { p256dh: "p256", auth: "auth" },
|
|
}),
|
|
unsubscribe: vi.fn(async () => true),
|
|
};
|
|
|
|
function stubBrowser(opts: {
|
|
permission?: NotificationPermission;
|
|
existing?: unknown;
|
|
ios?: boolean;
|
|
standalone?: boolean;
|
|
} = {}) {
|
|
const perm = opts.permission ?? "default";
|
|
const registration = {
|
|
pushManager: {
|
|
getSubscription: vi.fn(async () => opts.existing ?? null),
|
|
subscribe: vi.fn(async () => SUB),
|
|
},
|
|
};
|
|
vi.stubGlobal("navigator", {
|
|
serviceWorker: {
|
|
getRegistration: vi.fn(async () => registration),
|
|
ready: Promise.resolve(registration),
|
|
},
|
|
userAgent: opts.ios ? "iPhone" : "Android Chrome",
|
|
standalone: opts.standalone,
|
|
});
|
|
const notification = {
|
|
permission: perm,
|
|
requestPermission: vi.fn(async () => (opts.permission === "denied" ? "denied" : "granted")),
|
|
};
|
|
// Notification обязан лежать И в window: supported() проверяет именно его —
|
|
// так же, как это делает браузер.
|
|
vi.stubGlobal("window", {
|
|
PushManager: function () {},
|
|
Notification: notification,
|
|
matchMedia: () => ({ matches: !!opts.standalone }),
|
|
});
|
|
vi.stubGlobal("Notification", notification);
|
|
return registration;
|
|
}
|
|
|
|
describe("уведомления в приложении", () => {
|
|
beforeEach(() => vi.clearAllMocks());
|
|
afterEach(() => vi.unstubAllGlobals());
|
|
|
|
it("iPhone без установки на экран: объясняем, а не молчим", async () => {
|
|
vi.stubGlobal("navigator", { userAgent: "iPhone", standalone: false });
|
|
vi.stubGlobal("window", { matchMedia: () => ({ matches: false }) });
|
|
expect(supported()).toBe(false);
|
|
expect(await currentState()).toBe("needs-install");
|
|
});
|
|
|
|
it("разрешение отклонено — переспросить нечем", async () => {
|
|
stubBrowser({ permission: "denied" });
|
|
expect(await currentState()).toBe("denied");
|
|
});
|
|
|
|
it("подписка уходит на сервер с ключами из браузера", async () => {
|
|
stubBrowser();
|
|
expect(await enable()).toBe("on");
|
|
expect(api.subscribePush).toHaveBeenCalledWith({
|
|
endpoint: "https://fcm.googleapis.com/fcm/send/abc",
|
|
p256dh: "p256",
|
|
auth: "auth",
|
|
});
|
|
});
|
|
|
|
it("сервер не настроен — разрешение НЕ спрашиваем", async () => {
|
|
// Единственная попытка на установку: потратить её впустую нельзя.
|
|
vi.mocked(api.getPushKey).mockResolvedValueOnce({ key: "", enabled: false });
|
|
stubBrowser();
|
|
expect(await enable()).toBe("off");
|
|
expect(Notification.requestPermission).not.toHaveBeenCalled();
|
|
expect(api.subscribePush).not.toHaveBeenCalled();
|
|
});
|
|
});
|