diff --git a/src/features/online-board/components/BoardDetailsHeader/BoardDetailsHeader.tsx b/src/features/online-board/components/BoardDetailsHeader/BoardDetailsHeader.tsx index fa716e7d..f3ce4655 100644 --- a/src/features/online-board/components/BoardDetailsHeader/BoardDetailsHeader.tsx +++ b/src/features/online-board/components/BoardDetailsHeader/BoardDetailsHeader.tsx @@ -35,7 +35,6 @@ export const BoardDetailsHeader: FC = ({ flight, locale diff --git a/src/features/online-board/components/BoardDetailsHeader/FlightActions.test.tsx b/src/features/online-board/components/BoardDetailsHeader/FlightActions.test.tsx index 48958f51..c6b14119 100644 --- a/src/features/online-board/components/BoardDetailsHeader/FlightActions.test.tsx +++ b/src/features/online-board/components/BoardDetailsHeader/FlightActions.test.tsx @@ -1,6 +1,6 @@ // @vitest-environment jsdom import { describe, it, expect, vi } from "vitest"; -import { fireEvent, render, screen } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import { FlightActions } from "./FlightActions.js"; import type { ISimpleFlight } from "../../types.js"; @@ -78,9 +78,9 @@ function makeFlight(): ISimpleFlight { } describe("FlightActions", () => { - it("renders share, buy, register by default (status hidden)", () => { + it("renders buy and register by default, without share or status", () => { render(); - expect(screen.getByTestId("share-button")).toBeTruthy(); + expect(screen.queryByTestId("share-button")).toBeNull(); expect(screen.getByTestId("buy-ticket-button")).toBeTruthy(); expect(screen.getByTestId("registration-button")).toBeTruthy(); expect(screen.queryByTestId("flight-status-button")).toBeNull(); @@ -91,11 +91,6 @@ describe("FlightActions", () => { expect(screen.getByTestId("flight-status-button")).toBeTruthy(); }); - it("hides share button when showShare=false", () => { - render(); - expect(screen.queryByTestId("share-button")).toBeNull(); - }); - it("hides buy ticket when canBuyTicket returns false", async () => { const mod = await import("./visibility/buyTicketVisibility.js"); vi.mocked(mod.canBuyTicket).mockReturnValue(false); @@ -108,15 +103,4 @@ describe("FlightActions", () => { render(); expect(screen.getByTestId("flight-actions")).toBeTruthy(); }); - - it("shares the flight details URL instead of the current page URL", () => { - window.history.pushState({}, "", "/ru-ru/schedule/SVO/SU0022-20260417/LED?request=schedule-route-MOW-LED-20260413-20260419"); - render(); - fireEvent.click(screen.getByTestId("share-button")); - const link = screen.getByTestId("share-vk") as HTMLAnchorElement; - expect(decodeURIComponent(link.href)).toContain( - `${window.location.origin}/ru-ru/schedule/SVO/SU0022-20260417/LED`, - ); - expect(decodeURIComponent(link.href)).not.toContain("request=schedule-route"); - }); }); diff --git a/src/features/online-board/components/BoardDetailsHeader/FlightActions.tsx b/src/features/online-board/components/BoardDetailsHeader/FlightActions.tsx index 8b175618..aae051b9 100644 --- a/src/features/online-board/components/BoardDetailsHeader/FlightActions.tsx +++ b/src/features/online-board/components/BoardDetailsHeader/FlightActions.tsx @@ -8,16 +8,13 @@ import { canViewFlightStatus } from "./visibility/flightStatusVisibility.js"; import { BuyTicketButton } from "./BuyTicketButton.js"; import { RegistrationButton } from "./RegistrationButton.js"; import { FlightStatusButton } from "./FlightStatusButton.js"; -import { ShareButton } from "./ShareButton.js"; import { PrintButton } from "./PrintButton.js"; -import { buildFlightShareUrl, type FlightShareViewType } from "./shareUrl.js"; export interface FlightActionsProps { flight: ISimpleFlight; locale: string; showStatus?: boolean; showPrint?: boolean; - showShare?: boolean; showRegister?: boolean; showBuy?: boolean; /** @@ -28,7 +25,6 @@ export interface FlightActionsProps { * particular day's "Cancelled" status would hide a useful action. */ forceBuy?: boolean; - viewType?: FlightShareViewType; } export const FlightActions: FC = ({ @@ -36,11 +32,9 @@ export const FlightActions: FC = ({ locale, showStatus = false, showPrint = false, - showShare = true, showRegister = true, showBuy = true, forceBuy = false, - viewType = "Onlineboard", }) => { const { flightStatusAvailableFromHours, buyTicketMinHours, buyTicketMaxHours } = useAppSettings(); const now = new Date(); @@ -52,12 +46,9 @@ export const FlightActions: FC = ({ showStatus && canViewFlightStatus(flight, now, flightStatusAvailableFromHours, AIRLINES_WITH_STATUS); - const shareUrl = buildFlightShareUrl(flight, locale, viewType); - return (
{showPrint && } - {showShare && } {canBuy && } {canReg && } {canStatus && } diff --git a/src/features/online-board/components/BoardDetailsHeader/LastUpdate.test.tsx b/src/features/online-board/components/BoardDetailsHeader/LastUpdate.test.tsx index 586646e7..61417d55 100644 --- a/src/features/online-board/components/BoardDetailsHeader/LastUpdate.test.tsx +++ b/src/features/online-board/components/BoardDetailsHeader/LastUpdate.test.tsx @@ -87,8 +87,8 @@ describe("LastUpdate", () => { expect(ts.textContent).toMatch(/\d{2}:\d{2}\s+\d{2}\.\d{2}\.\d{4}/); }); - it("renders share button", () => { + it("does not render a share button", () => { render(); - expect(screen.getByTestId("share-button")).toBeTruthy(); + expect(screen.queryByTestId("share-button")).toBeNull(); }); }); diff --git a/src/features/online-board/components/BoardDetailsHeader/LastUpdate.tsx b/src/features/online-board/components/BoardDetailsHeader/LastUpdate.tsx index 5c097e51..79f6389b 100644 --- a/src/features/online-board/components/BoardDetailsHeader/LastUpdate.tsx +++ b/src/features/online-board/components/BoardDetailsHeader/LastUpdate.tsx @@ -2,13 +2,10 @@ import { type FC, useEffect, useRef, useState } from "react"; import { format } from "date-fns"; import { useTranslation } from "@/i18n/provider.js"; import type { ISimpleFlight } from "../../types.js"; -import { ShareButton } from "./ShareButton.js"; -import { buildFlightShareUrl, type FlightShareViewType } from "./shareUrl.js"; export interface LastUpdateProps { flight: ISimpleFlight; locale: string; - viewType?: FlightShareViewType; } function formatStamp(d: Date): string { @@ -23,7 +20,7 @@ function formatStamp(d: Date): string { * timestamp. We mirror that here: capture `Date.now()` the first time we * see a given flight.id, then re-capture whenever the id changes. */ -export const LastUpdate: FC = ({ flight, locale, viewType = "Onlineboard" }) => { +export const LastUpdate: FC = ({ flight }) => { const { t } = useTranslation(); const [loadedAt, setLoadedAt] = useState(() => new Date()); const seenFlightIdRef = useRef(null); @@ -36,11 +33,9 @@ export const LastUpdate: FC = ({ flight, locale, viewType = "On }, [flight.id]); const timestamp = formatStamp(loadedAt); - const shareUrl = buildFlightShareUrl(flight, locale, viewType); return (
- {t("SHARED.LAST-UPDATE")}: diff --git a/src/features/online-board/components/BoardDetailsHeader/ShareButton.test.tsx b/src/features/online-board/components/BoardDetailsHeader/ShareButton.test.tsx deleted file mode 100644 index 4415d2f2..00000000 --- a/src/features/online-board/components/BoardDetailsHeader/ShareButton.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -// @vitest-environment jsdom -import { describe, it, expect, vi } from "vitest"; -import { render, screen, fireEvent } from "@testing-library/react"; -import { ShareButton } from "./ShareButton.js"; - -vi.mock("@/i18n/provider.js", () => ({ - useTranslation: () => ({ t: (k: string) => k }), -})); - -describe("ShareButton", () => { - it("renders button with testid share-button", () => { - render(); - expect(screen.getByTestId("share-button")).toBeTruthy(); - }); - - it("panel is closed by default", () => { - render(); - expect(screen.queryByTestId("share-panel")).toBeNull(); - }); - - it("click opens the share panel", () => { - render(); - fireEvent.click(screen.getByTestId("share-button")); - expect(screen.getByTestId("share-panel")).toBeTruthy(); - }); - - it("second click closes the share panel", () => { - render(); - const btn = screen.getByTestId("share-button"); - fireEvent.click(btn); - expect(screen.getByTestId("share-panel")).toBeTruthy(); - fireEvent.click(btn); - expect(screen.queryByTestId("share-panel")).toBeNull(); - }); -}); diff --git a/src/features/online-board/components/BoardDetailsHeader/ShareButton.tsx b/src/features/online-board/components/BoardDetailsHeader/ShareButton.tsx deleted file mode 100644 index 40e1d0ef..00000000 --- a/src/features/online-board/components/BoardDetailsHeader/ShareButton.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { type FC, useState } from "react"; -import { useTranslation } from "@/i18n/provider.js"; -import { SharePanel } from "./SharePanel.js"; -import "./actions.scss"; - -export interface ShareButtonProps { - url: string; - locale: string; -} - -export const ShareButton: FC = ({ url, locale }) => { - const { t } = useTranslation(); - const [open, setOpen] = useState(false); - - return ( -
- - {open && setOpen(false)} />} -
- ); -}; diff --git a/src/features/online-board/components/BoardDetailsHeader/SharePanel.test.tsx b/src/features/online-board/components/BoardDetailsHeader/SharePanel.test.tsx deleted file mode 100644 index 6c196561..00000000 --- a/src/features/online-board/components/BoardDetailsHeader/SharePanel.test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -// @vitest-environment jsdom -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { render, screen, fireEvent, waitFor } from "@testing-library/react"; -import { SharePanel } from "./SharePanel.js"; - -vi.mock("@/i18n/provider.js", () => ({ - useTranslation: () => ({ t: (k: string) => k }), -})); - -describe("SharePanel", () => { - let writeTextMock: ReturnType; - - beforeEach(() => { - writeTextMock = vi.fn().mockResolvedValue(undefined); - Object.defineProperty(navigator, "clipboard", { - value: { writeText: writeTextMock }, - writable: true, - configurable: true, - }); - }); - - it("renders 3 social links for en locale (no Weibo)", () => { - render( {}} />); - expect(screen.getByTestId("share-facebook")).toBeTruthy(); - expect(screen.getByTestId("share-vk")).toBeTruthy(); - expect(screen.getByTestId("share-twitter")).toBeTruthy(); - expect(screen.queryByTestId("share-weibo")).toBeNull(); - }); - - it("renders only Weibo social link for zh locale", () => { - render( {}} />); - expect(screen.queryByTestId("share-facebook")).toBeNull(); - expect(screen.queryByTestId("share-vk")).toBeNull(); - expect(screen.queryByTestId("share-twitter")).toBeNull(); - expect(screen.getByTestId("share-weibo")).toBeTruthy(); - }); - - it("renders only Weibo social link for cn country locale", () => { - render( {}} />); - expect(screen.queryByTestId("share-facebook")).toBeNull(); - expect(screen.queryByTestId("share-vk")).toBeNull(); - expect(screen.queryByTestId("share-twitter")).toBeNull(); - expect(screen.getByTestId("share-weibo")).toBeTruthy(); - }); - - it("Facebook href contains encoded URL", () => { - render( {}} />); - const link = screen.getByTestId("share-facebook") as HTMLAnchorElement; - expect(link.href).toContain(encodeURIComponent("https://example.com/flight?a=1")); - }); - - it("copy button calls navigator.clipboard.writeText and onClose", async () => { - const onClose = vi.fn(); - render(); - fireEvent.click(screen.getByTestId("share-copy")); - await waitFor(() => { - expect(writeTextMock).toHaveBeenCalledWith("https://example.com/flight"); - }); - await waitFor(() => { - expect(onClose).toHaveBeenCalled(); - }); - }); -}); diff --git a/src/features/online-board/components/BoardDetailsHeader/SharePanel.tsx b/src/features/online-board/components/BoardDetailsHeader/SharePanel.tsx deleted file mode 100644 index b4878e2d..00000000 --- a/src/features/online-board/components/BoardDetailsHeader/SharePanel.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { type FC, type MouseEvent, useEffect } from "react"; -import { useTranslation } from "@/i18n/provider.js"; -import "./actions.scss"; - -export interface SharePanelProps { - url: string; - locale: string; - onClose: () => void; -} - -function isChinaLocale(locale: string): boolean { - const normalized = locale.toLowerCase(); - const [country, language] = normalized.split("-"); - return country === "cn" || language === "zh" || normalized === "zh"; -} - -export const SharePanel: FC = ({ url, locale, onClose }) => { - const { t } = useTranslation(); - const encoded = encodeURIComponent(url); - const chinaLocale = isChinaLocale(locale); - - // Close on Escape — matches Angular's PrimeNG p-overlayPanel dismissable behaviour. - useEffect(() => { - const onKey = (e: KeyboardEvent) => { - if (e.key === "Escape") onClose(); - }; - window.addEventListener("keydown", onKey); - return () => window.removeEventListener("keydown", onKey); - }, [onClose]); - - const handleCopy = async (e: MouseEvent) => { - e.preventDefault(); - try { - await navigator.clipboard.writeText(url); - onClose(); - } catch { - // silent - } - }; - - return ( -
-
- {!chinaLocale && ( - <> - - - - - )} - {chinaLocale && ( - - )} - -
-
- ); -}; diff --git a/src/features/online-board/components/BoardDetailsHeader/shareUrl.test.ts b/src/features/online-board/components/BoardDetailsHeader/shareUrl.test.ts deleted file mode 100644 index 436e782e..00000000 --- a/src/features/online-board/components/BoardDetailsHeader/shareUrl.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -// @vitest-environment jsdom -import { describe, expect, it } from "vitest"; -import type { IFlightLeg, ISimpleFlight } from "../../types.js"; -import { buildFlightShareUrl } from "./shareUrl.js"; - -function makeLeg( - departure: string, - arrival: string, - local: string, -): IFlightLeg { - return { - arrival: { - scheduled: { airport: "", airportCode: arrival, city: "", cityCode: "", countryCode: "" }, - times: { - scheduledArrival: { - dayChange: { value: 0, title: "" }, - local: "", - localTime: "", - tzOffset: 0, - utc: "", - }, - }, - }, - dayChange: 0, - departure: { - scheduled: { airport: "", airportCode: departure, city: "", cityCode: "", countryCode: "" }, - checkingStatus: "Scheduled", - times: { - scheduledDeparture: { - dayChange: { value: 0, title: "" }, - local, - localTime: "", - tzOffset: 0, - utc: "", - }, - }, - }, - equipment: {}, - flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false }, - flyingTime: "1h", - index: 0, - operatingBy: {}, - status: "Scheduled", - updated: "", - } as IFlightLeg; -} - -function makeDirectFlight(): ISimpleFlight { - return { - id: "direct", - routeType: "Direct", - flightId: { carrier: "SU", flightNumber: "0022", suffix: "", date: "20260416" }, - flyingTime: "1h", - operatingBy: {}, - status: "Scheduled", - leg: makeLeg("SVO", "LED", "2026-04-17T10:00:00"), - } as ISimpleFlight; -} - -describe("buildFlightShareUrl", () => { - it("builds an online-board details URL from the flight local departure date", () => { - expect(buildFlightShareUrl(makeDirectFlight(), "ru-ru")).toBe( - `${window.location.origin}/ru-ru/onlineboard/SU0022-20260417`, - ); - }); - - it("builds a clean schedule details URL without request query", () => { - expect(buildFlightShareUrl(makeDirectFlight(), "ru-ru", "Schedule")).toBe( - `${window.location.origin}/ru-ru/schedule/SVO/SU0022-20260417/LED`, - ); - }); - - it("builds an airport-interleaved schedule URL for connecting itineraries", () => { - const flight = { - ...makeDirectFlight(), - id: "connecting", - routeType: "MultiLeg", - flightId: { carrier: "SU", flightNumber: "5752", suffix: "", date: "20260529" }, - legs: [ - makeLeg("VVO", "KJA", "2026-05-29T11:00:00"), - makeLeg("KJA", "MJZ", "2026-05-30T08:00:00"), - ], - _childFlightIds: [ - { carrier: "SU", flightNumber: "5752", suffix: "", date: "20260529" }, - { carrier: "SU", flightNumber: "6837", suffix: "", date: "20260530" }, - ], - } as unknown as ISimpleFlight; - - expect(buildFlightShareUrl(flight, "ru-ru", "Schedule")).toBe( - `${window.location.origin}/ru-ru/schedule/VVO/SU5752-20260529/KJA/SU6837-20260530/MJZ`, - ); - }); -}); diff --git a/src/features/online-board/components/BoardDetailsHeader/shareUrl.ts b/src/features/online-board/components/BoardDetailsHeader/shareUrl.ts deleted file mode 100644 index 8b9acc5f..00000000 --- a/src/features/online-board/components/BoardDetailsHeader/shareUrl.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { buildFlightUrlParams } from "../../url.js"; -import { getFlightSearchDate } from "../../flightSearchDate.js"; -import type { IFlightLeg, ISimpleFlight } from "../../types.js"; - -export type FlightShareViewType = "Onlineboard" | "Schedule"; - -function getOrigin(): string { - if (typeof window === "undefined") return ""; - return window.location.origin; -} - -function getLegs(flight: ISimpleFlight): IFlightLeg[] { - return flight.routeType === "Direct" ? [flight.leg] : flight.legs; -} - -function compactLocalDate(leg: IFlightLeg | undefined, fallback: string): string { - const local = leg?.departure.times.scheduledDeparture.local; - if (local) { - const date = local.slice(0, 10).replace(/-/g, ""); - if (/^\d{8}$/.test(date)) return date; - } - return fallback.replace(/-/g, ""); -} - -function buildScheduleSegment(flight: ISimpleFlight): string { - const legs = getLegs(flight); - const childIds = (flight as ISimpleFlight & { - _childFlightIds?: Array<{ - carrier: string; - flightNumber: string; - suffix?: string; - date?: string; - }>; - })._childFlightIds; - const ids = childIds && childIds.length > 0 ? childIds : [flight.flightId]; - const parts: string[] = []; - - ids.forEach((id, index) => { - const leg = legs[index] ?? legs[legs.length - 1]; - if (index === 0 && leg) parts.push(leg.departure.scheduled.airportCode); - parts.push( - buildFlightUrlParams({ - carrier: id.carrier, - flightNumber: id.flightNumber, - ...(id.suffix ? { suffix: id.suffix } : {}), - date: compactLocalDate(leg, id.date ?? flight.flightId.date), - }), - ); - if (leg) parts.push(leg.arrival.scheduled.airportCode); - }); - - return parts.join("/"); -} - -export function buildFlightShareUrl( - flight: ISimpleFlight, - locale: string, - viewType: FlightShareViewType = "Onlineboard", -): string { - const origin = getOrigin(); - - if (viewType === "Schedule") { - return `${origin}/${locale}/schedule/${buildScheduleSegment(flight)}`; - } - - return `${origin}/${locale}/onlineboard/${buildFlightUrlParams({ - carrier: flight.flightId.carrier, - flightNumber: flight.flightId.flightNumber, - ...(flight.flightId.suffix ? { suffix: flight.flightId.suffix } : {}), - date: getFlightSearchDate(flight), - })}`; -} diff --git a/src/features/online-board/components/OnlineBoardSearchPage.tsx b/src/features/online-board/components/OnlineBoardSearchPage.tsx index 50c55112..064a2c4e 100644 --- a/src/features/online-board/components/OnlineBoardSearchPage.tsx +++ b/src/features/online-board/components/OnlineBoardSearchPage.tsx @@ -38,7 +38,6 @@ import { buildOnlineBoardUrl } from "../url.js"; import { buildFlightListJsonLd } from "../json-ld.js"; import { sortFlights } from "../sortFlights.js"; import { getFlightSearchDate } from "../flightSearchDate.js"; -import { buildFlightShareUrl } from "./BoardDetailsHeader/shareUrl.js"; import { PobedaAuroraBanner, shouldShowPobedaAuroraBanner, @@ -620,7 +619,6 @@ export const OnlineBoardSearchPage: FC = ({ onFlightClick={handleFlightClick} initialCurrentFlightId={initialCurrentFlightId} direction={params.type} - shareUrlFor={(flight) => buildFlightShareUrl(flight, locale)} renderActions={(flight) => ( // Mirrors Angular's board search expansion: each row // shows [Купить] [Онлайн регистрация] alongside the @@ -630,7 +628,6 @@ export const OnlineBoardSearchPage: FC = ({ )} /> diff --git a/src/features/schedule/components/DayGroupedFlightList.tsx b/src/features/schedule/components/DayGroupedFlightList.tsx index bdfcb30c..67cfc654 100644 --- a/src/features/schedule/components/DayGroupedFlightList.tsx +++ b/src/features/schedule/components/DayGroupedFlightList.tsx @@ -16,7 +16,6 @@ import { FlightList } from "@/ui/flights/FlightList.js"; import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js"; import { useLocale } from "@/i18n/useLocale.js"; import type { ISimpleFlight, IFlightLeg } from "@/features/online-board/types.js"; -import { buildFlightShareUrl } from "@/features/online-board/components/BoardDetailsHeader/shareUrl.js"; import { ScheduleFlightBody } from "./ScheduleFlightBody.js"; import type { ScheduleSortMode } from "./ScheduleColumnHeaders.js"; import "./DayGroupedFlightList.scss"; @@ -287,7 +286,6 @@ export const DayGroupedFlightList: FC = ({ loading={false} direction="schedule" renderExpandedBody={renderScheduleBody} - shareUrlFor={(flight) => buildFlightShareUrl(flight, locale, "Schedule")} {...(onFlightClick ? { onFlightClick } : {})} {...(buyUrlFor ? { buyUrlFor } : {})} {...(resolvedInitialFlightId @@ -364,7 +362,6 @@ export const DayGroupedFlightList: FC = ({ loading={false} direction="schedule" renderExpandedBody={renderScheduleBody} - shareUrlFor={(flight) => buildFlightShareUrl(flight, locale, "Schedule")} {...(onFlightClick ? { onFlightClick } : {})} {...(buyUrlFor ? { buyUrlFor } : {})} {...(resolvedInitialFlightId diff --git a/src/features/schedule/components/ScheduleDetailsPage.tsx b/src/features/schedule/components/ScheduleDetailsPage.tsx index 5b283718..5548f06b 100644 --- a/src/features/schedule/components/ScheduleDetailsPage.tsx +++ b/src/features/schedule/components/ScheduleDetailsPage.tsx @@ -414,12 +414,11 @@ export const ScheduleDetailsPage: FC = ({ - +
{miniListCurrentFlight.routeType !== "Direct" && ( diff --git a/src/features/schedule/components/ScheduleFlightBody.tsx b/src/features/schedule/components/ScheduleFlightBody.tsx index 3303348a..dcd42f8a 100644 --- a/src/features/schedule/components/ScheduleFlightBody.tsx +++ b/src/features/schedule/components/ScheduleFlightBody.tsx @@ -397,8 +397,6 @@ export const ScheduleFlightBody: FC = ({ { expect(screen.getByTestId("transition-gate").textContent).toContain("C7"); }); - it("4.1.13.4-R: share button always present in expanded actions row", () => { + it("4.1.13.4-R: share button is not rendered in expanded actions row", () => { render( {}} />); - expect(screen.getByTestId("flight-share-button")).toBeTruthy(); - }); - - it("4.1.13.4-R: share button copies caller-provided details URL", async () => { - const writeText = vi.fn().mockResolvedValue(undefined); - Object.defineProperty(navigator, "clipboard", { - value: { writeText }, - writable: true, - configurable: true, - }); - render( - {}} - shareUrl="https://example.test/ru-ru/onlineboard/SU0022-20260417" - />, - ); - - fireEvent.click(screen.getByTestId("flight-share-button")); - - await waitFor(() => { - expect(writeText).toHaveBeenCalledWith( - "https://example.test/ru-ru/onlineboard/SU0022-20260417", - ); - }); + expect(screen.queryByTestId("flight-share-button")).toBeNull(); }); it("4.1.13.4-R: Details button present in expanded actions row", () => { diff --git a/src/ui/flights/FlightCard.tsx b/src/ui/flights/FlightCard.tsx index 14b25658..b708325f 100644 --- a/src/ui/flights/FlightCard.tsx +++ b/src/ui/flights/FlightCard.tsx @@ -60,8 +60,6 @@ export interface FlightCardProps { * buttons Angular shows on the search results expansion. */ renderActions?: (flight: ISimpleFlight) => ReactNode; - /** Absolute URL copied/shared by the row share button. */ - shareUrl?: string; /** * Aeroflot booking URL for the per-row "Купить билет" hover link * (TIRREDESIGN-6 / TZ §4.1.14.4.4). Rendered inside the collapsed row @@ -163,7 +161,6 @@ export const FlightCard: FC = ({ direction = "route", renderExpandedBody, renderActions, - shareUrl, inlineBuyUrl, }) => { const { t } = useTranslation(); @@ -667,30 +664,6 @@ export const FlightCard: FC = ({ {onViewDetails && (
- {/* Extra actions (Купить / Онлайн регистрация). Angular renders Buy + Registration + Details here on every expanded board row; the caller wires up FlightActions diff --git a/src/ui/flights/FlightList.tsx b/src/ui/flights/FlightList.tsx index 8dc26b29..61a64198 100644 --- a/src/ui/flights/FlightList.tsx +++ b/src/ui/flights/FlightList.tsx @@ -40,11 +40,6 @@ export interface FlightListProps { * Купить / Онлайн регистрация alongside Детали рейса. */ renderActions?: (flight: ISimpleFlight) => ReactNode; - /** - * Absolute URL for each row-level share button. Callers provide this so - * feature-specific route semantics stay outside shared card rendering. - */ - shareUrlFor?: (flight: ISimpleFlight) => string; /** * Aeroflot booking URL per flight for the hover-reveal inline * "Купить билет" link (TIRREDESIGN-6). Return null to hide. @@ -67,7 +62,6 @@ export const FlightList: FC = ({ direction = "route", renderExpandedBody, renderActions, - shareUrlFor, buyUrlFor, }) => { const { t } = useTranslation(); @@ -138,10 +132,6 @@ export const FlightList: FC = ({ : {})} {...(renderExpandedBody ? { renderExpandedBody } : {})} {...(renderActions ? { renderActions } : {})} - {...(() => { - const url = shareUrlFor?.(flight); - return url ? { shareUrl: url } : {}; - })()} {...(() => { const url = buyUrlFor?.(flight); return url ? { inlineBuyUrl: url } : {}; diff --git a/tests/e2e/share-button-parity.spec.ts b/tests/e2e/share-button-parity.spec.ts deleted file mode 100644 index e3ebf4ed..00000000 --- a/tests/e2e/share-button-parity.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { test, expect } from "./fixtures/console-gate"; -import { routeScheduleVvoMjzFixtures } from "./helpers/api-fixtures"; -import { vvoMjzDetailsUrl } from "./helpers/dates"; - -test("TIRREDESIGN-32: schedule share button uses clean details URL", async ({ - page, - consoleMessages, -}) => { - const detailsUrl = vvoMjzDetailsUrl(); - const cleanPath = detailsUrl.split("?")[0] ?? detailsUrl; - - await routeScheduleVvoMjzFixtures(page); - await page.goto(detailsUrl); - await expect(page.getByTestId("schedule-details")).toBeVisible({ - timeout: 15000, - }); - - await page.getByTestId("share-button").first().click(); - const vk = page.getByTestId("share-vk"); - await expect(vk).toBeVisible(); - - const href = await vk.getAttribute("href"); - expect(href).toBeTruthy(); - const decoded = decodeURIComponent(href ?? ""); - expect(decoded).toContain(`${new URL(page.url()).origin}${cleanPath}`); - expect(decoded).not.toContain("request=schedule-route"); -}); - -test("TIRREDESIGN-32: China locale shows Weibo-only share panel", async ({ - page, - consoleMessages, -}) => { - const detailsUrl = vvoMjzDetailsUrl().replace("/ru-ru/", "/cn-zh/"); - - await routeScheduleVvoMjzFixtures(page); - await page.goto(detailsUrl); - await expect(page.getByTestId("schedule-details")).toBeVisible({ - timeout: 15000, - }); - - await page.getByTestId("share-button").first().click(); - await expect(page.getByTestId("share-weibo")).toBeVisible(); - await expect(page.getByTestId("share-facebook")).toHaveCount(0); - await expect(page.getByTestId("share-vk")).toHaveCount(0); - await expect(page.getByTestId("share-twitter")).toHaveCount(0); -}); diff --git a/tests/e2e/share-button-removed.spec.ts b/tests/e2e/share-button-removed.spec.ts new file mode 100644 index 00000000..ed8d5f26 --- /dev/null +++ b/tests/e2e/share-button-removed.spec.ts @@ -0,0 +1,68 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { test, expect } from "./fixtures/console-gate"; +import { + routeOnlineboardRouteFixtures, + routeScheduleVvoMjzFixtures, +} from "./helpers/api-fixtures"; +import { formatYmd, vvoMjzDetailsUrl } from "./helpers/dates"; +import { nextOnlineboardDetailsFixture } from "./helpers/onlineboard-fixtures"; + +const FIXTURE_DIR = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "../fixtures/api", +); +const onlineboardDetails = fs.readFileSync( + path.join(FIXTURE_DIR, "onlineboard-details.json"), + "utf8", +); + +test("TIRREDESIGN-32: onlineboard details page does not render share buttons", async ({ + page, + consoleMessages, +}) => { + const details = nextOnlineboardDetailsFixture(onlineboardDetails); + await page.route("**/api/flights/v1.1/ru/onlineboard/details?**", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: details.body, + }); + }); + + await page.goto(`/ru-ru/onlineboard/SU6951-${details.compactDate}`); + await expect(page.getByTestId("board-details-header")).toBeVisible({ + timeout: 15000, + }); + await expect(page.getByTestId("share-button")).toHaveCount(0); + await expect(page.getByTestId("share-panel")).toHaveCount(0); +}); + +test("TIRREDESIGN-32: schedule details page does not render share buttons", async ({ + page, + consoleMessages, +}) => { + await routeScheduleVvoMjzFixtures(page); + await page.goto(vvoMjzDetailsUrl()); + await expect(page.getByTestId("schedule-details")).toBeVisible({ + timeout: 15000, + }); + await expect(page.getByTestId("share-button")).toHaveCount(0); + await expect(page.getByTestId("share-panel")).toHaveCount(0); +}); + +test("TIRREDESIGN-32: expanded flight rows do not render row share buttons", async ({ + page, + consoleMessages, +}) => { + await routeOnlineboardRouteFixtures(page); + await page.goto(`/ru-ru/onlineboard/route/MOW-LED-${formatYmd(new Date())}`); + await expect(page.locator(".flight-card").first()).toBeVisible({ + timeout: 15000, + }); + + await page.locator(".flight-card--clickable").first().click(); + await expect(page.locator(".flight-card--expanded").first()).toBeVisible(); + await expect(page.getByTestId("flight-share-button")).toHaveCount(0); +});