Add ServicesPanel component for on-board service icons

This commit is contained in:
2026-04-16 22:36:58 +03:00
parent d7ff79b967
commit c125322078
2 changed files with 124 additions and 0 deletions
@@ -0,0 +1,57 @@
// @vitest-environment jsdom
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { ServicesPanel } from "./ServicesPanel.js";
import type { IOnBoardService } from "../../types.js";
vi.mock("@/i18n/provider.js", () => ({
useTranslation: () => ({ t: (k: string) => k }),
}));
describe("ServicesPanel", () => {
it("renders an icon for each service", () => {
const services: IOnBoardService[] = [{ id: 1 }, { id: 4 }, { id: 8 }];
render(<ServicesPanel services={services} />);
expect(screen.getByTestId("service-icon-1")).toBeTruthy();
expect(screen.getByTestId("service-icon-4")).toBeTruthy();
expect(screen.getByTestId("service-icon-8")).toBeTruthy();
});
it("maps service id 1 to shopping icon", () => {
render(<ServicesPanel services={[{ id: 1 }]} />);
const img = screen.getByTestId("service-icon-1") as HTMLImageElement;
expect(img.src).toContain("shopping");
});
it("maps service id 4 to wifi icon", () => {
render(<ServicesPanel services={[{ id: 4 }]} />);
const img = screen.getByTestId("service-icon-4") as HTMLImageElement;
expect(img.src).toContain("wifi");
});
it("uses fallback icon for unknown id", () => {
render(<ServicesPanel services={[{ id: 99 }]} />);
const img = screen.getByTestId("service-icon-99") as HTMLImageElement;
expect(img.src).toContain("comfort-plus");
});
it("wraps icon in a link when service has url", () => {
const services: IOnBoardService[] = [
{ id: 4, url: "https://wifi.example" },
];
render(<ServicesPanel services={services} />);
const link = screen.getByTestId("service-icon-4").closest("a");
expect(link?.getAttribute("href")).toBe("https://wifi.example");
});
it("does not wrap icon in link when url is missing", () => {
render(<ServicesPanel services={[{ id: 4 }]} />);
const link = screen.getByTestId("service-icon-4").closest("a");
expect(link).toBeNull();
});
it("has panel data-testid", () => {
render(<ServicesPanel services={[{ id: 1 }]} />);
expect(screen.getByTestId("services-panel")).toBeTruthy();
});
});
@@ -0,0 +1,67 @@
import type { FC } from "react";
import type { IOnBoardService } from "../../types.js";
import { SERVICE_ICON_MAP, SERVICE_ICON_FALLBACK } from "./shared.js";
import shoppingIcon from "./icons/shopping.svg";
import spaceIcon from "./icons/space.svg";
import taxiIcon from "./icons/taxi.svg";
import wifiIcon from "./icons/wifi.svg";
import gsmIcon from "./icons/gsm.svg";
import entertainmentIcon from "./icons/entertaintment.svg";
import seatReservationIcon from "./icons/seat_reservation.svg";
import comfortPlusIcon from "./icons/comfort-plus.svg";
import "./panels.scss";
const ICON_BY_NAME: Record<string, string> = {
shopping: shoppingIcon,
space: spaceIcon,
taxi: taxiIcon,
wifi: wifiIcon,
gsm: gsmIcon,
entertaintment: entertainmentIcon,
seat_reservation: seatReservationIcon,
"comfort-plus": comfortPlusIcon,
};
export interface ServicesPanelProps {
services: IOnBoardService[];
}
export const ServicesPanel: FC<ServicesPanelProps> = ({ services }) => {
return (
<div className="details-panel" data-testid="services-panel">
<div className="details-panel__icons">
{services.map((svc) => {
const iconName = SERVICE_ICON_MAP[svc.id] ?? SERVICE_ICON_FALLBACK;
const iconSrc =
ICON_BY_NAME[iconName] ?? ICON_BY_NAME[SERVICE_ICON_FALLBACK]!;
const alt = svc.title ?? `service-${svc.id}`;
const img = (
<img
src={iconSrc}
alt={alt}
data-testid={`service-icon-${svc.id}`}
/>
);
return svc.url ? (
<a
key={svc.id}
href={svc.url}
target="_blank"
rel="noopener noreferrer"
className="details-panel__icon"
>
{img}
{svc.title && <span>{svc.title}</span>}
</a>
) : (
<span key={svc.id} className="details-panel__icon">
{img}
{svc.title && <span>{svc.title}</span>}
</span>
);
})}
</div>
</div>
);
};