Add LastUpdate component with timestamp and mobile share

This commit is contained in:
2026-04-17 01:36:07 +03:00
parent 0efd19ed32
commit 44af796678
2 changed files with 130 additions and 0 deletions
@@ -0,0 +1,90 @@
// @vitest-environment jsdom
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { LastUpdate } from "./LastUpdate.js";
import type { ISimpleFlight, IFlightLeg } from "../../types.js";
vi.mock("@/i18n/provider.js", () => ({ useTranslation: () => ({ t: (k: string) => k }) }));
function makeLeg(updated: string): IFlightLeg {
return {
arrival: {
scheduled: { airport: "", airportCode: "LED", city: "", cityCode: "", countryCode: "" },
latest: { airport: "", airportCode: "LED", city: "", cityCode: "", countryCode: "" },
dispatch: "",
gate: "",
terminal: "",
times: {
scheduledArrival: {
dayChange: { value: 0, title: "" },
local: "",
localTime: "",
tzOffset: 0,
utc: "",
},
},
},
dayChange: 0,
departure: {
scheduled: { airport: "", airportCode: "SVO", city: "", cityCode: "", countryCode: "" },
latest: { airport: "", airportCode: "SVO", city: "", cityCode: "", countryCode: "" },
dispatch: "",
gate: "",
terminal: "",
checkingStatus: "Scheduled",
parkingStand: "",
times: {
scheduledDeparture: {
dayChange: { value: 0, title: "" },
local: "",
localTime: "",
tzOffset: 0,
utc: "2026-04-20T10:00:00Z",
},
},
},
equipment: {},
flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
flyingTime: "1h",
index: 0,
operatingBy: {},
status: "Scheduled",
updated,
} as IFlightLeg;
}
function makeFlight(updated: string): ISimpleFlight {
return {
id: "X",
routeType: "Direct",
flyingTime: "1h",
status: "Scheduled",
flightId: { carrier: "SU", flightNumber: "0022", suffix: "", date: "20260417" },
operatingBy: { carrier: "SU", flightNumber: "0022" },
leg: makeLeg(updated),
} as ISimpleFlight;
}
describe("LastUpdate", () => {
it("renders LAST-UPDATE label", () => {
render(<LastUpdate flight={makeFlight("2026-04-20T12:34:00Z")} locale="ru" />);
expect(screen.getByText(/SHARED\.LAST-UPDATE/)).toBeTruthy();
});
it("renders timestamp in HH:mm dd.MM.yyyy format", () => {
render(<LastUpdate flight={makeFlight("2026-04-20T12:34:00Z")} locale="ru" />);
const ts = screen.getByTestId("last-update-timestamp");
expect(ts.textContent).toMatch(/\d{2}:\d{2}\s+\d{2}\.\d{2}\.\d{4}/);
});
it("renders empty timestamp when leg.updated is empty", () => {
render(<LastUpdate flight={makeFlight("")} locale="ru" />);
const ts = screen.getByTestId("last-update-timestamp");
expect(ts.textContent?.trim()).toBe("");
});
it("renders share button", () => {
render(<LastUpdate flight={makeFlight("2026-04-20T12:34:00Z")} locale="ru" />);
expect(screen.getByTestId("share-button")).toBeTruthy();
});
});
@@ -0,0 +1,40 @@
import type { FC } from "react";
import { parseISO, format, isValid } from "date-fns";
import { useTranslation } from "@/i18n/provider.js";
import type { ISimpleFlight } from "../../types.js";
import { ShareButton } from "./ShareButton.js";
export interface LastUpdateProps {
flight: ISimpleFlight;
locale: string;
}
function getUpdated(flight: ISimpleFlight): string | undefined {
const leg = flight.routeType === "Direct" ? flight.leg : flight.legs[0];
return leg?.updated;
}
function formatUpdated(updated: string | undefined): string {
if (!updated) return "";
const d = parseISO(updated);
if (!isValid(d)) return "";
return format(d, "HH:mm dd.MM.yyyy");
}
export const LastUpdate: FC<LastUpdateProps> = ({ flight, locale }) => {
const { t } = useTranslation();
const timestamp = formatUpdated(getUpdated(flight));
const shareUrl = typeof window !== "undefined" ? window.location.href : "";
return (
<div className="last-update">
<ShareButton url={shareUrl} locale={locale} />
<span className="last-update__description">
<span>{t("SHARED.LAST-UPDATE")}:</span>
<span className="last-update__time" data-testid="last-update-timestamp">
&nbsp;{timestamp}
</span>
</span>
</div>
);
};