Add FlightStatusButton component

This commit is contained in:
2026-04-17 01:28:54 +03:00
parent d0c50e81c5
commit a70dbd2546
2 changed files with 121 additions and 0 deletions
@@ -0,0 +1,65 @@
// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { FlightStatusButton } from "./FlightStatusButton.js";
import type { ISimpleFlight } from "../../types.js";
vi.mock("@/i18n/provider.js", () => ({
useTranslation: () => ({ t: (k: string) => k }),
}));
function makeFlight(carrier = "SU"): ISimpleFlight {
return {
id: "X",
routeType: "Direct",
flyingTime: "1h",
status: "Scheduled",
flightId: { carrier, flightNumber: "0022", suffix: "", date: "20260417" },
operatingBy: { carrier, flightNumber: "0022" },
leg: {} as never,
} as ISimpleFlight;
}
describe("FlightStatusButton", () => {
beforeEach(() => {
Object.defineProperty(window, "open", { value: vi.fn(), writable: true });
});
it("renders label SHARED.DETAILS", () => {
render(<FlightStatusButton flight={makeFlight()} locale="ru" />);
expect(screen.getByText("SHARED.DETAILS")).toBeTruthy();
});
it("has data-testid flight-status-button", () => {
render(<FlightStatusButton flight={makeFlight()} locale="ru" />);
expect(screen.getByTestId("flight-status-button")).toBeTruthy();
});
it("SU click opens native onlineboard details URL", () => {
render(<FlightStatusButton flight={makeFlight("SU")} locale="ru" />);
fireEvent.click(screen.getByTestId("flight-status-button"));
const openMock = window.open as ReturnType<typeof vi.fn>;
expect(openMock).toHaveBeenCalledTimes(1);
expect(openMock.mock.calls[0]![0]).toBe("/ru/onlineboard/SU0022-20260417");
});
it("HZ click opens flyaurora URL", () => {
render(<FlightStatusButton flight={makeFlight("HZ")} locale="ru" />);
fireEvent.click(screen.getByTestId("flight-status-button"));
const openMock = window.open as ReturnType<typeof vi.fn>;
expect(openMock).toHaveBeenCalledTimes(1);
expect(openMock.mock.calls[0]![0]).toContain("flyaurora.ru");
});
it("small prop adds --small modifier class", () => {
render(<FlightStatusButton flight={makeFlight()} locale="ru" small />);
const btn = screen.getByTestId("flight-status-button");
expect(btn.className).toContain("flight-action-btn--small");
});
it("default (small=false) does not add --small class", () => {
render(<FlightStatusButton flight={makeFlight()} locale="ru" />);
const btn = screen.getByTestId("flight-status-button");
expect(btn.className).not.toContain("flight-action-btn--small");
});
});
@@ -0,0 +1,56 @@
import type { FC } from "react";
import { useTranslation } from "@/i18n/provider.js";
import type { ISimpleFlight } from "../../types.js";
import { AIRLINES } from "./airlines.js";
import { buildOnlineBoardUrl } from "../../url.js";
import "./actions.scss";
export interface FlightStatusButtonProps {
flight: ISimpleFlight;
locale: string;
small?: boolean;
}
export const FlightStatusButton: FC<FlightStatusButtonProps> = ({ flight, locale, small }) => {
const { t } = useTranslation();
const handleClick = () => {
const carrier = flight.operatingBy.carrier;
if (!carrier) return;
const config = AIRLINES[carrier];
if (!config) return;
if (config.hasNativeStatus) {
const path = buildOnlineBoardUrl({
type: "details",
carrier: flight.flightId.carrier,
flightNumber: flight.flightId.flightNumber,
...(flight.flightId.suffix ? { suffix: flight.flightId.suffix } : {}),
date: flight.flightId.date,
});
window.open(`/${locale}/${path}`, "_blank", "noopener,noreferrer");
} else if (config.statusUrl) {
window.open(config.statusUrl, "_blank", "noopener,noreferrer");
}
};
const classes = [
"flight-action-btn",
"flight-action-btn--blue-light",
small ? "flight-action-btn--small" : "",
]
.filter(Boolean)
.join(" ");
return (
<button
type="button"
className={classes}
data-testid="flight-status-button"
title={t("SHARED.DETAILS-TOOLTIP")}
onClick={handleClick}
>
{t("SHARED.DETAILS")}
</button>
);
};