diff --git a/driver-pwa/frontend/src/components/NotificationsRow.test.tsx b/driver-pwa/frontend/src/components/NotificationsRow.test.tsx new file mode 100644 index 0000000..ab019f2 --- /dev/null +++ b/driver-pwa/frontend/src/components/NotificationsRow.test.tsx @@ -0,0 +1,40 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +vi.mock("@/lib/push", () => ({ + currentState: vi.fn(async () => "off"), + enable: vi.fn(async () => "on"), +})); + +import { NotificationsRow } from "./NotificationsRow"; +import * as push from "@/lib/push"; + +describe("строка уведомлений", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(push.currentState).mockResolvedValue("off"); + }); + + it("выключенные уведомления предлагает включить", async () => { + render(); + await userEvent.click(await screen.findByRole("button", { name: /Включить уведомления/ })); + expect(push.enable).toHaveBeenCalled(); + }); + + it("включённые — строки нет: отказаться от них мы не предлагаем", async () => { + // Отключение остаётся системным (настройки телефона или сайта в браузере) — + // рядом с балансом кнопка «выключить» только подталкивала бы к отказу. + vi.mocked(push.currentState).mockResolvedValue("on"); + const { container } = render(); + await vi.waitFor(() => expect(container.textContent).toBe("")); + expect(screen.queryByText(/Выключить/)).not.toBeInTheDocument(); + }); + + it("запрещённые в браузере — объясняем, а не предлагаем кнопку", async () => { + vi.mocked(push.currentState).mockResolvedValue("denied"); + render(); + expect(await screen.findByText(/запрещены в настройках браузера/)).toBeInTheDocument(); + expect(screen.queryByRole("button")).not.toBeInTheDocument(); + }); +}); diff --git a/driver-pwa/frontend/src/components/NotificationsRow.tsx b/driver-pwa/frontend/src/components/NotificationsRow.tsx index a9161a2..74fc0a6 100644 --- a/driver-pwa/frontend/src/components/NotificationsRow.tsx +++ b/driver-pwa/frontend/src/components/NotificationsRow.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; -import { Bell, BellOff } from "lucide-react"; +import { Bell } from "lucide-react"; import { toast } from "sonner"; -import { currentState, disable, enable, type PushState } from "@/lib/push"; +import { currentState, enable, type PushState } from "@/lib/push"; /** * Включение уведомлений — одной строкой на главном экране. @@ -9,6 +9,13 @@ import { currentState, disable, enable, type PushState } from "@/lib/push"; * Не всплывающим окном на входе: разрешение спрашивается один раз за установку, * и потраченная впустую попытка закрывает канал навсегда. Здесь водитель жмёт * сам, увидев, ради чего. + * + * Выключения здесь НЕТ намеренно (решение владельца 19.08.2026). Включённые + * уведомления — это ровно те два события, ради которых приложение и открывают; + * кнопка «выключить» рядом с балансом только предлагает от них отказаться. + * Способ отключить остаётся системный: настройки уведомлений телефона или + * настройки сайта в браузере — там же, где человек управляет всеми остальными + * приложениями. */ export function NotificationsRow() { const [state, setState] = useState(null); @@ -20,14 +27,15 @@ export function NotificationsRow() { return () => { alive = false; }; }, []); - if (state === null || state === "unsupported") return null; + // Включено — строки нет: предлагать нечего. + if (state === null || state === "unsupported" || state === "on") return null; - async function toggle() { + async function turnOn() { setBusy(true); try { - setState(state === "on" ? await disable() : await enable()); + setState(await enable()); } catch { - toast.error("Не удалось изменить настройку уведомлений"); + toast.error("Не удалось включить уведомления"); } finally { setBusy(false); } @@ -53,10 +61,9 @@ export function NotificationsRow() { ); }