feat(pwa): свой сервис-воркер и включение уведомлений
Воркер теперь собирается через injectManifest: generateSW умеет только кэш, а уведомление показывает именно воркер — страница при этом может быть закрыта. Нажатие на уведомление переиспользует уже открытое окно, а не плодит второе. Включение — строкой на главном экране, по нажатию. Не всплывающим окном на входе: разрешение спрашивается один раз за установку, отказ браузер запоминает навсегда, и потраченная впустую попытка закрывает канал. Если сервер уведомления не настроил — разрешение не запрашиваем вовсе. Отдельные тексты вместо молчания: на iPhone без установки на домашний экран уведомления невозможны в принципе, и об этом сказано прямо. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user