Add data model types, datetime utils, and dictionary hook
Port Angular flight types (ISimpleFlight, IFlightLeg, ITimesSet, etc.) to minimal React-friendly interfaces. Add formatDuration/formatTime/ formatDate/isDayChange as pure functions. Stub useCityName hook as passthrough pending customer dictionary API endpoint.
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import type {
|
||||
FlightStatus,
|
||||
FlightRequestType,
|
||||
ITimesSet,
|
||||
IAirportInfo,
|
||||
IFlightLeg,
|
||||
IFlightLegDepartureStation,
|
||||
IFlightLegArrivalStation,
|
||||
IParsedFlightId,
|
||||
IDirectFlight,
|
||||
IMultiLegFlight,
|
||||
ISimpleFlight,
|
||||
IBoardResponse,
|
||||
IDaysResponse,
|
||||
} from "./types.js";
|
||||
|
||||
/**
|
||||
* Type-level satisfaction tests. These verify that our interfaces
|
||||
* accept the shapes the API actually sends. If the types are wrong,
|
||||
* these assignments fail at compile time.
|
||||
*/
|
||||
|
||||
describe("online-board types", () => {
|
||||
it("FlightStatus accepts all known API values", () => {
|
||||
const statuses: FlightStatus[] = [
|
||||
"Scheduled",
|
||||
"Sent",
|
||||
"InFlight",
|
||||
"Landed",
|
||||
"Arrived",
|
||||
"Delayed",
|
||||
"Cancelled",
|
||||
"Unknown",
|
||||
];
|
||||
expect(statuses).toHaveLength(8);
|
||||
});
|
||||
|
||||
it("FlightRequestType accepts all search modes", () => {
|
||||
const modes: FlightRequestType[] = [
|
||||
"flight",
|
||||
"departure",
|
||||
"arrival",
|
||||
"route",
|
||||
];
|
||||
expect(modes).toHaveLength(4);
|
||||
});
|
||||
|
||||
it("ITimesSet satisfies the API shape", () => {
|
||||
const times: ITimesSet = {
|
||||
dayChange: { value: 0, title: "" },
|
||||
local: "2025-01-15T10:30:00",
|
||||
localTime: "10:30",
|
||||
tzOffset: 3,
|
||||
utc: "2025-01-15T07:30:00Z",
|
||||
};
|
||||
expect(times.localTime).toBe("10:30");
|
||||
});
|
||||
|
||||
it("IAirportInfo satisfies the API shape", () => {
|
||||
const info: IAirportInfo = {
|
||||
airport: "Sheremetyevo",
|
||||
airportCode: "SVO",
|
||||
city: "Moscow",
|
||||
cityCode: "MOW",
|
||||
countryCode: "RU",
|
||||
};
|
||||
expect(info.airportCode).toBe("SVO");
|
||||
});
|
||||
|
||||
it("IParsedFlightId holds parsed URL params", () => {
|
||||
const id: IParsedFlightId = {
|
||||
carrier: "SU",
|
||||
flightNumber: "100",
|
||||
date: "20250115",
|
||||
};
|
||||
expect(id.carrier).toBe("SU");
|
||||
});
|
||||
|
||||
it("IDirectFlight satisfies a direct flight shape", () => {
|
||||
const depStation: IFlightLegDepartureStation = {
|
||||
scheduled: {
|
||||
airport: "Sheremetyevo",
|
||||
airportCode: "SVO",
|
||||
city: "Moscow",
|
||||
cityCode: "MOW",
|
||||
countryCode: "RU",
|
||||
},
|
||||
checkingStatus: "Scheduled",
|
||||
times: {
|
||||
scheduledDeparture: {
|
||||
dayChange: { value: 0, title: "" },
|
||||
local: "2025-01-15T10:30:00",
|
||||
localTime: "10:30",
|
||||
tzOffset: 3,
|
||||
utc: "2025-01-15T07:30:00Z",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const arrStation: IFlightLegArrivalStation = {
|
||||
scheduled: {
|
||||
airport: "Pulkovo",
|
||||
airportCode: "LED",
|
||||
city: "St Petersburg",
|
||||
cityCode: "LED",
|
||||
countryCode: "RU",
|
||||
},
|
||||
times: {
|
||||
scheduledArrival: {
|
||||
dayChange: { value: 0, title: "" },
|
||||
local: "2025-01-15T12:00:00",
|
||||
localTime: "12:00",
|
||||
tzOffset: 3,
|
||||
utc: "2025-01-15T09:00:00Z",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const leg: IFlightLeg = {
|
||||
arrival: arrStation,
|
||||
dayChange: 0,
|
||||
departure: depStation,
|
||||
equipment: { name: "Airbus A320", code: "320" },
|
||||
flags: {
|
||||
checkinAvailable: false,
|
||||
returnToAirport: false,
|
||||
routeChanged: false,
|
||||
},
|
||||
flyingTime: "01:30",
|
||||
index: 0,
|
||||
operatingBy: { carrier: "SU", flightNumber: "100" },
|
||||
status: "Scheduled",
|
||||
updated: "2025-01-15T10:00:00Z",
|
||||
};
|
||||
|
||||
const flight: IDirectFlight = {
|
||||
flightId: {
|
||||
carrier: "SU",
|
||||
date: "2025-01-15",
|
||||
flightNumber: "100",
|
||||
suffix: "",
|
||||
},
|
||||
flyingTime: "01:30",
|
||||
operatingBy: { carrier: "SU", flightNumber: "100" },
|
||||
id: "su-100-20250115",
|
||||
status: "Scheduled",
|
||||
routeType: "Direct",
|
||||
leg,
|
||||
};
|
||||
|
||||
expect(flight.routeType).toBe("Direct");
|
||||
});
|
||||
|
||||
it("IMultiLegFlight satisfies a multi-leg shape", () => {
|
||||
const flight: IMultiLegFlight = {
|
||||
flightId: {
|
||||
carrier: "SU",
|
||||
date: "2025-01-15",
|
||||
flightNumber: "200",
|
||||
suffix: "",
|
||||
},
|
||||
flyingTime: "05:00",
|
||||
operatingBy: {},
|
||||
id: "su-200-20250115",
|
||||
status: "Scheduled",
|
||||
routeType: "MultiLeg",
|
||||
legs: [],
|
||||
};
|
||||
expect(flight.routeType).toBe("MultiLeg");
|
||||
});
|
||||
|
||||
it("ISimpleFlight is a union of direct and multi-leg", () => {
|
||||
const direct: ISimpleFlight = {
|
||||
flightId: {
|
||||
carrier: "SU",
|
||||
date: "2025-01-15",
|
||||
flightNumber: "100",
|
||||
suffix: "",
|
||||
},
|
||||
flyingTime: "01:30",
|
||||
operatingBy: {},
|
||||
id: "su-100",
|
||||
status: "Scheduled",
|
||||
routeType: "Direct",
|
||||
leg: {} as IFlightLeg,
|
||||
};
|
||||
expect(direct.status).toBe("Scheduled");
|
||||
});
|
||||
|
||||
it("IBoardResponse matches API response shape", () => {
|
||||
const resp: IBoardResponse = {
|
||||
data: {
|
||||
partners: ["SU"],
|
||||
routes: [],
|
||||
daysOfFlight: ["2025-01-15"],
|
||||
},
|
||||
};
|
||||
expect(resp.data.partners).toContain("SU");
|
||||
});
|
||||
|
||||
it("IDaysResponse matches API response shape", () => {
|
||||
const resp: IDaysResponse = {
|
||||
days: "1,2,3,4,5",
|
||||
};
|
||||
expect(resp.days).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Data model types for the Online Board feature.
|
||||
*
|
||||
* Ported from Angular typings (ClientApp/src/typings/) — flattened into
|
||||
* minimal, UI-oriented interfaces with no Angular dependencies.
|
||||
*/
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Enums & literals
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Flight status values returned by the API */
|
||||
export type FlightStatus =
|
||||
| "Scheduled"
|
||||
| "Sent"
|
||||
| "InFlight"
|
||||
| "Landed"
|
||||
| "Arrived"
|
||||
| "Delayed"
|
||||
| "Cancelled"
|
||||
| "Unknown";
|
||||
|
||||
/** Route shape discriminator */
|
||||
export type RouteType = "Direct" | "MultiLeg" | "Connecting";
|
||||
|
||||
/** Search request type — how the user is searching */
|
||||
export type FlightRequestType = "flight" | "departure" | "arrival" | "route";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Time & location primitives
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Day-change indicator (e.g. +1, -1 day vs scheduled) */
|
||||
export interface IDayChange {
|
||||
value: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/** A single point-in-time with timezone info, as returned by the API */
|
||||
export interface ITimesSet {
|
||||
dayChange: IDayChange;
|
||||
local: string;
|
||||
localTime: string;
|
||||
tzOffset: number;
|
||||
utc: string;
|
||||
}
|
||||
|
||||
/** Airport/city info from the API */
|
||||
export interface IAirportInfo {
|
||||
airport: string;
|
||||
airportCode: string;
|
||||
city: string;
|
||||
cityCode: string;
|
||||
countryCode: string;
|
||||
country?: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Station (departure / arrival)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IFlightLegStation {
|
||||
scheduled: IAirportInfo;
|
||||
latest?: IAirportInfo;
|
||||
dispatch?: string;
|
||||
gate?: string;
|
||||
terminal?: string;
|
||||
}
|
||||
|
||||
export interface IDepartureStationTimes {
|
||||
scheduledDeparture: ITimesSet;
|
||||
estimatedBlockOff?: ITimesSet;
|
||||
actualBlockOff?: ITimesSet;
|
||||
}
|
||||
|
||||
export interface IArrivalStationTimes {
|
||||
scheduledArrival: ITimesSet;
|
||||
estimatedBlockOn?: ITimesSet;
|
||||
actualBlockOn?: ITimesSet;
|
||||
}
|
||||
|
||||
export interface IFlightLegDepartureStation extends IFlightLegStation {
|
||||
checkingStatus: string;
|
||||
parkingStand?: string;
|
||||
times: IDepartureStationTimes;
|
||||
}
|
||||
|
||||
export interface IFlightLegArrivalStation extends IFlightLegStation {
|
||||
times: IArrivalStationTimes;
|
||||
bagBelt?: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Flight leg
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IFlightLegFlags {
|
||||
checkinAvailable: boolean;
|
||||
returnToAirport: boolean;
|
||||
routeChanged: boolean;
|
||||
}
|
||||
|
||||
export interface IFlightLeg {
|
||||
arrival: IFlightLegArrivalStation;
|
||||
dayChange: number;
|
||||
departure: IFlightLegDepartureStation;
|
||||
equipment: { name?: string; code?: string };
|
||||
flags: IFlightLegFlags;
|
||||
flyingTime: string;
|
||||
index: number;
|
||||
operatingBy: { carrier?: string; flightNumber?: string };
|
||||
status: FlightStatus;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Flight ID
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IFlightId {
|
||||
carrier: string;
|
||||
date: string;
|
||||
flightNumber: string;
|
||||
suffix: string;
|
||||
dateLT?: string;
|
||||
}
|
||||
|
||||
export interface IParsedFlightId {
|
||||
carrier: string;
|
||||
flightNumber: string;
|
||||
suffix?: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Flight (union of route shapes)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface IFlightBase {
|
||||
flightId: IFlightId;
|
||||
flyingTime: string;
|
||||
operatingBy: { carrier?: string; flightNumber?: string };
|
||||
id: string;
|
||||
status: FlightStatus;
|
||||
}
|
||||
|
||||
export interface IDirectFlight extends IFlightBase {
|
||||
routeType: "Direct";
|
||||
leg: IFlightLeg;
|
||||
}
|
||||
|
||||
export interface IMultiLegFlight extends IFlightBase {
|
||||
routeType: "MultiLeg";
|
||||
legs: IFlightLeg[];
|
||||
}
|
||||
|
||||
export interface IConnectingFlight {
|
||||
flights: ISimpleFlight[];
|
||||
routeType: "Connecting";
|
||||
flyingTime: string;
|
||||
status: FlightStatus;
|
||||
}
|
||||
|
||||
/** A single flight (direct or multi-leg) — the main UI display unit */
|
||||
export type ISimpleFlight = IDirectFlight | IMultiLegFlight;
|
||||
|
||||
/** Any flight shape including connecting */
|
||||
export type IFlight = ISimpleFlight | IConnectingFlight;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// API response shapes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IBoardResponse {
|
||||
data: {
|
||||
partners: string[];
|
||||
routes: ISimpleFlight[];
|
||||
daysOfFlight: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface IDaysResponse {
|
||||
days: string;
|
||||
}
|
||||
Reference in New Issue
Block a user