Add MealPanel component with meal-type icons and aeroflot.ru links

This commit is contained in:
2026-04-16 22:35:17 +03:00
parent 6dbcc38081
commit d7ff79b967
3 changed files with 133 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
declare module "*.svg" {
const src: string;
export default src;
}
declare module "*.png" {
const src: string;
export default src;
}
declare module "*.jpg" {
const src: string;
export default src;
}
declare module "*.jpeg" {
const src: string;
export default src;
}
declare module "*.gif" {
const src: string;
export default src;
}
declare module "*.webp" {
const src: string;
export default src;
}
@@ -0,0 +1,48 @@
// @vitest-environment jsdom
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { MealPanel } from "./MealPanel.js";
import type { IMealItem } from "../../types.js";
vi.mock("@/i18n/provider.js", () => ({
useTranslation: () => ({ t: (k: string) => k }),
}));
describe("MealPanel", () => {
it("renders an icon for Economy meal", () => {
const meals: IMealItem[] = [{ type: "Economy" }];
render(<MealPanel meals={meals} />);
expect(screen.getByTestId("meal-icon-Economy")).toBeTruthy();
});
it("renders icons for multiple meal types", () => {
const meals: IMealItem[] = [
{ type: "Economy" }, { type: "Comfort" }, { type: "Business" },
];
render(<MealPanel meals={meals} />);
expect(screen.getByTestId("meal-icon-Economy")).toBeTruthy();
expect(screen.getByTestId("meal-icon-Comfort")).toBeTruthy();
expect(screen.getByTestId("meal-icon-Business")).toBeTruthy();
});
it("skips Special type (no link defined)", () => {
const meals: IMealItem[] = [{ type: "Special" }, { type: "Economy" }];
render(<MealPanel meals={meals} />);
expect(screen.queryByTestId("meal-icon-Special")).toBeNull();
expect(screen.getByTestId("meal-icon-Economy")).toBeTruthy();
});
it("meal icon is wrapped in a link to aeroflot.ru", () => {
const meals: IMealItem[] = [{ type: "Economy" }];
render(<MealPanel meals={meals} />);
const link = screen.getByTestId("meal-icon-Economy").closest("a");
expect(link?.getAttribute("href")).toContain("aeroflot.ru");
expect(link?.getAttribute("href")).toContain("meal-type_0");
});
it("has panel data-testid", () => {
const meals: IMealItem[] = [{ type: "Economy" }];
render(<MealPanel meals={meals} />);
expect(screen.getByTestId("meal-panel")).toBeTruthy();
});
});
@@ -0,0 +1,56 @@
import type { FC } from "react";
import { useTranslation } from "@/i18n/provider.js";
import type { IMealItem, MealType } from "../../types.js";
import { MEAL_LINKS } from "./shared.js";
import econoIcon from "./icons/econom.svg";
import comfortIcon from "./icons/comfort.svg";
import businessIcon from "./icons/business.svg";
import "./panels.scss";
const MEAL_ICON_URL: Record<Exclude<MealType, "Special">, string> = {
Economy: econoIcon,
Comfort: comfortIcon,
Business: businessIcon,
};
const MEAL_LABEL_KEYS: Record<Exclude<MealType, "Special">, string> = {
Economy: "DETAILS.MEAL_ECONOMY",
Comfort: "DETAILS.MEAL_COMFORT",
Business: "DETAILS.MEAL_BUSINESS",
};
const MEAL_ORDER = ["Economy", "Comfort", "Business"] as const;
export interface MealPanelProps {
meals: IMealItem[];
}
export const MealPanel: FC<MealPanelProps> = ({ meals }) => {
const { t } = useTranslation();
const types = new Set(meals.map((m) => m.type));
return (
<div className="details-panel" data-testid="meal-panel">
<div className="details-panel__icons">
{MEAL_ORDER.map((type) =>
types.has(type) ? (
<a
key={type}
href={MEAL_LINKS[type]}
target="_blank"
rel="noopener noreferrer"
className="details-panel__icon"
>
<img
src={MEAL_ICON_URL[type]}
alt={t(MEAL_LABEL_KEYS[type])}
data-testid={`meal-icon-${type}`}
/>
<span>{t(MEAL_LABEL_KEYS[type])}</span>
</a>
) : null,
)}
</div>
</div>
);
};