Add canBuyTicket visibility logic

This commit is contained in:
2026-04-17 01:21:33 +03:00
parent 35760aefa2
commit 5d3792f304
2 changed files with 98 additions and 0 deletions
@@ -0,0 +1,68 @@
import { describe, it, expect } from "vitest";
import { canBuyTicket } from "./buyTicketVisibility.js";
import type { ISimpleFlight, FlightStatus } from "../../../types.js";
function makeFlight(status: FlightStatus, depUtc: string): ISimpleFlight {
return {
id: "SU0022-X",
routeType: "Direct",
flyingTime: "1h",
status,
flightId: { carrier: "SU", flightNumber: "0022", suffix: "", date: "20260417" },
operatingBy: { carrier: "SU", flightNumber: "0022" },
leg: {
arrival: {
scheduled: { airport: "", airportCode: "", city: "", cityCode: "", countryCode: "" },
latest: { airport: "", airportCode: "", city: "", cityCode: "", countryCode: "" },
dispatch: "", gate: "", terminal: "",
times: { scheduledArrival: { dayChange: { value: 0, title: "" }, local: "", localTime: "", tzOffset: 0, utc: "" } },
},
dayChange: 0,
departure: {
scheduled: { airport: "", airportCode: "", city: "", cityCode: "", countryCode: "" },
latest: { airport: "", airportCode: "", city: "", cityCode: "", countryCode: "" },
dispatch: "", gate: "", terminal: "", checkingStatus: "Scheduled", parkingStand: "",
times: { scheduledDeparture: { dayChange: { value: 0, title: "" }, local: "", localTime: "", tzOffset: 0, utc: depUtc } },
},
equipment: {},
flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
flyingTime: "1h",
index: 0,
operatingBy: {},
status,
updated: "",
},
} as ISimpleFlight;
}
describe("canBuyTicket", () => {
it("returns false when status is Cancelled", () => {
const now = new Date("2026-04-17T10:00:00Z");
expect(canBuyTicket(makeFlight("Cancelled", "2026-04-18T10:00:00Z"), now, 2, 72)).toBe(false);
});
it("returns false when status is InFlight", () => {
const now = new Date("2026-04-17T10:00:00Z");
expect(canBuyTicket(makeFlight("InFlight", "2026-04-18T10:00:00Z"), now, 2, 72)).toBe(false);
});
it("returns true when now is in window (24h before)", () => {
const now = new Date("2026-04-17T10:00:00Z");
expect(canBuyTicket(makeFlight("Scheduled", "2026-04-18T10:00:00Z"), now, 2, 72)).toBe(true);
});
it("returns false when now is too close (<minHours before)", () => {
const now = new Date("2026-04-17T09:00:00Z");
expect(canBuyTicket(makeFlight("Scheduled", "2026-04-17T10:00:00Z"), now, 2, 72)).toBe(false);
});
it("returns false when now is too far (>maxHours before)", () => {
const now = new Date("2026-04-17T00:00:00Z");
expect(canBuyTicket(makeFlight("Scheduled", "2026-04-25T00:00:00Z"), now, 2, 72)).toBe(false);
});
it("returns false when UTC departure is empty", () => {
const now = new Date();
expect(canBuyTicket(makeFlight("Scheduled", ""), now, 2, 72)).toBe(false);
});
});
@@ -0,0 +1,30 @@
import { parseISO, subHours, isAfter, isBefore } from "date-fns";
import type { ISimpleFlight } from "../../../types.js";
/**
* Buy Ticket button is visible when:
* - flight is NOT Cancelled or InFlight
* - now falls within [departure - maxHours, departure - minHours]
*/
export function canBuyTicket(
flight: ISimpleFlight,
now: Date,
minHours: number,
maxHours: number,
): boolean {
if (flight.status === "Cancelled" || flight.status === "InFlight") return false;
const leg = flight.routeType === "Direct" ? flight.leg : flight.legs[0];
if (!leg) return false;
const depUtc = leg.departure.times.scheduledDeparture.utc;
if (!depUtc) return false;
const departure = parseISO(depUtc);
if (isNaN(departure.getTime())) return false;
const showFrom = subHours(departure, maxHours);
const showUntil = subHours(departure, minHours);
return isAfter(now, showFrom) && isBefore(now, showUntil);
}