Files
mechanic-pwa/driver-pwa/frontend/src/lib/push.test.ts
T
tremble7681andClaude Opus 5 562a6d35fe feat(pwa): свой сервис-воркер и включение уведомлений
Воркер теперь собирается через injectManifest: generateSW умеет только кэш, а
уведомление показывает именно воркер — страница при этом может быть закрыта.
Нажатие на уведомление переиспользует уже открытое окно, а не плодит второе.

Включение — строкой на главном экране, по нажатию. Не всплывающим окном на
входе: разрешение спрашивается один раз за установку, отказ браузер запоминает
навсегда, и потраченная впустую попытка закрывает канал. Если сервер уведомления
не настроил — разрешение не запрашиваем вовсе.

Отдельные тексты вместо молчания: на iPhone без установки на домашний экран
уведомления невозможны в принципе, и об этом сказано прямо.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 10:29:43 +10:00

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();
});
});