diff --git a/src/features/popular-requests/components/PopularRequestsPanel.test.tsx b/src/features/popular-requests/components/PopularRequestsPanel.test.tsx new file mode 100644 index 00000000..829663ab --- /dev/null +++ b/src/features/popular-requests/components/PopularRequestsPanel.test.tsx @@ -0,0 +1,287 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { PopularRequestsPanel } from "./PopularRequestsPanel"; +import type { PopularRequest } from "../types"; + +// --------------------------------------------------------------------------- +// Mocks +// --------------------------------------------------------------------------- + +// Mock the usePopularRequests hook +const mockUsePopularRequests = vi.fn< + () => { requests: PopularRequest[]; loading: boolean; error: Error | null } +>(); + +vi.mock("../hooks/usePopularRequests.js", () => ({ + usePopularRequests: () => mockUsePopularRequests(), +})); + +// Mock i18n +vi.mock("@/i18n/provider.js", () => ({ + useTranslation: () => ({ + t: (key: string) => key, + i18n: { language: "en" }, + }), +})); + +// Mock useCityName +vi.mock("@/shared/hooks/useDictionaries.js", () => ({ + useCityName: (code: string) => code, +})); + +// --------------------------------------------------------------------------- +// Test data +// --------------------------------------------------------------------------- + +const mockRequests: PopularRequest[] = [ + { + mode: "FlightNumber", + carrier: "SU", + flightNumber: "9027", + type: "Onlineboard", + }, + { + mode: "FlightNumber", + carrier: "SU", + flightNumber: "9006", + type: "Onlineboard", + }, + { + mode: "Route", + departure: "KUF", + arrival: "ABA", + type: "Onlineboard", + }, + { + mode: "Route", + departure: "RTW", + arrival: "ABA", + type: "Onlineboard", + }, +]; + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("PopularRequestsPanel", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders nothing while loading", () => { + mockUsePopularRequests.mockReturnValue({ + requests: [], + loading: true, + error: null, + }); + + const { container } = render( + , + ); + + expect(container.innerHTML).toBe(""); + }); + + it("renders nothing on error (graceful degradation)", () => { + mockUsePopularRequests.mockReturnValue({ + requests: [], + loading: false, + error: new Error("API failure"), + }); + + const { container } = render( + , + ); + + expect(container.innerHTML).toBe(""); + }); + + it("renders nothing when requests array is empty", () => { + mockUsePopularRequests.mockReturnValue({ + requests: [], + loading: false, + error: null, + }); + + const { container } = render( + , + ); + + expect(container.innerHTML).toBe(""); + }); + + it("renders the title and up to 4 request items", () => { + mockUsePopularRequests.mockReturnValue({ + requests: mockRequests, + loading: false, + error: null, + }); + + render(); + + expect(screen.getByText("BOARD.POPULAR-CHAPTERS")).toBeInTheDocument(); + // Should render 4 items + const items = screen.getAllByRole("button"); + expect(items).toHaveLength(4); + }); + + it("calls onRequestClick when a flight number item is clicked", async () => { + mockUsePopularRequests.mockReturnValue({ + requests: [mockRequests[0]!], + loading: false, + error: null, + }); + + const onRequestClick = vi.fn(); + render(); + + const button = screen.getByRole("button"); + await userEvent.click(button); + + expect(onRequestClick).toHaveBeenCalledWith(mockRequests[0]); + }); + + it("renders flight number with carrier and number", () => { + mockUsePopularRequests.mockReturnValue({ + requests: [mockRequests[0]!], + loading: false, + error: null, + }); + + render(); + + // SU\u00a09027 (non-breaking space) + expect(screen.getByText("SU\u00a09027")).toBeInTheDocument(); + }); + + it("renders route request with departure and arrival", () => { + mockUsePopularRequests.mockReturnValue({ + requests: [mockRequests[2]!], + loading: false, + error: null, + }); + + render(); + + expect(screen.getByText("KUF - ABA")).toBeInTheDocument(); + }); + + it("limits display to 4 items even with more data", () => { + const manyRequests: PopularRequest[] = [ + ...mockRequests, + { + mode: "Arrival", + arrival: "SVO", + type: "Onlineboard", + }, + ]; + + mockUsePopularRequests.mockReturnValue({ + requests: manyRequests, + loading: false, + error: null, + }); + + render(); + + const items = screen.getAllByRole("button"); + expect(items).toHaveLength(4); + }); + + it("renders arrival request mode", () => { + mockUsePopularRequests.mockReturnValue({ + requests: [ + { + mode: "Arrival", + arrival: "SVO", + type: "Onlineboard", + }, + ], + loading: false, + error: null, + }); + + render(); + + expect(screen.getByText("BOARD.ARRIVAL")).toBeInTheDocument(); + expect(screen.getByText("SVO")).toBeInTheDocument(); + }); + + it("renders departure request mode", () => { + mockUsePopularRequests.mockReturnValue({ + requests: [ + { + mode: "Departure", + departure: "DME", + type: "Onlineboard", + }, + ], + loading: false, + error: null, + }); + + render(); + + expect(screen.getByText("BOARD.DEPARTURE")).toBeInTheDocument(); + expect(screen.getByText("DME")).toBeInTheDocument(); + }); + + it("renders schedule route with correct label", () => { + mockUsePopularRequests.mockReturnValue({ + requests: [ + { + mode: "Route", + departure: "SVO", + arrival: "LED", + type: "Schedule", + }, + ], + loading: false, + error: null, + }); + + render(); + + expect(screen.getByText("SCHEDULE.SCHEDULE-OUTBOUND")).toBeInTheDocument(); + }); + + it("renders RouteWithBack with full-route label", () => { + mockUsePopularRequests.mockReturnValue({ + requests: [ + { + mode: "RouteWithBack", + departure: "SVO", + arrival: "JFK", + type: "Schedule", + }, + ], + loading: false, + error: null, + }); + + render(); + + expect( + screen.getByText("SCHEDULE.SCHEDULE-FULL-ROUTE"), + ).toBeInTheDocument(); + }); + + it("supports keyboard activation (Enter key)", async () => { + mockUsePopularRequests.mockReturnValue({ + requests: [mockRequests[0]!], + loading: false, + error: null, + }); + + const onRequestClick = vi.fn(); + render(); + + const button = screen.getByRole("button"); + button.focus(); + await userEvent.keyboard("{Enter}"); + + expect(onRequestClick).toHaveBeenCalledWith(mockRequests[0]); + }); +});