/** * Fast Lane name-join tests. * * The Six Flags /wait-times endpoint and Queue-Times use slightly different * ride name conventions, so Fast Lane waits are joined onto Queue-Times rides * by normalized name. These cases lock that join behaviour. * * Run with: npm test */ import { test } from "node:test"; import assert from "node:assert/strict"; import { parseWaitTimes, lookupFastLane } from "../lib/scrapers/sixflags-waittimes"; import type { WTResponse } from "../lib/scrapers/sixflags-waittimes"; function ride( name: string, isFastLane: boolean, fastLaneMinutes: number | null, regularMinutes: number | null = 20, ): Record { return { id: 1, name, isFastLane, regularWaittime: regularMinutes === null ? { createdDateTime: "", waitTime: 0 } : { createdDateTime: "May 29, 2026 19:00:00", waitTime: regularMinutes }, fastlaneWaittime: fastLaneMinutes === null ? { createdDateTime: "", waitTime: 0 } : { createdDateTime: "May 29, 2026 19:00:00", waitTime: fastLaneMinutes }, fimsId: "RIDE-906-00001", }; } function result(...rides: Record[]) { const json: WTResponse = { parkId: 906, venues: [{ venueId: 1, venueName: "Rides", details: rides as never }], }; const r = parseWaitTimes(json); assert.ok(r, "expected parseWaitTimes to return a result"); return r; } // ── Name joins across QT ↔ SF naming quirks ────────────────────────────────── test("matches across trademark symbols, THE prefix, possessives", () => { const r = result( ride("Batman: The Ride", true, 5), ride("Riddler's Revenge", true, 10), ride("Apocalypse the Ride", true, 15), ); // Queue-Times-style names on the left should resolve to the SF entries. assert.deepEqual(lookupFastLane("BATMAN™ The Ride", r), { hasFastLane: true, fastLaneMinutes: 5, regularMinutes: 20, }); assert.deepEqual(lookupFastLane("THE RIDDLER™'s Revenge", r), { hasFastLane: true, fastLaneMinutes: 10, regularMinutes: 20, }); // Prefix match: "Apocalypse" is a prefix of "Apocalypse the Ride". assert.deepEqual(lookupFastLane("Apocalypse", r), { hasFastLane: true, fastLaneMinutes: 15, regularMinutes: 20, }); }); test("a non-Fast-Lane ride resolves to hasFastLane: false", () => { const r = result(ride("Bucaneer", false, null)); assert.deepEqual(lookupFastLane("Bucaneer", r), { hasFastLane: false, fastLaneMinutes: null, regularMinutes: 20, }); }); test("empty fastlane createdDateTime yields fastLaneMinutes: null", () => { const r = result(ride("Batman: The Ride", true, null)); assert.deepEqual(lookupFastLane("Batman: The Ride", r), { hasFastLane: true, fastLaneMinutes: null, regularMinutes: 20, }); }); test("regular wait is surfaced when regularWaittime.createdDateTime is fresh", () => { const r = result(ride("Goliath", true, null, 35)); assert.deepEqual(lookupFastLane("Goliath", r), { hasFastLane: true, fastLaneMinutes: null, regularMinutes: 35, }); }); test("walk-on regular wait (0) is surfaced, not null", () => { const r = result(ride("Buccaneer", false, null, 0)); assert.deepEqual(lookupFastLane("Buccaneer", r), { hasFastLane: false, fastLaneMinutes: null, regularMinutes: 0, }); }); test("empty regular createdDateTime yields regularMinutes: null", () => { const r = result(ride("Tatsu", true, 10, null)); assert.deepEqual(lookupFastLane("Tatsu", r), { hasFastLane: true, fastLaneMinutes: 10, regularMinutes: null, }); }); test("a ride absent from SF data returns null", () => { const r = result(ride("Apocalypse the Ride", true, 15)); assert.equal(lookupFastLane("Some Other Coaster", r), null); }); test("conjunction guard: compound name does not match a single ride", () => { const r = result(ride("Joker", true, 25)); // "Joker y Harley Quinn" is a different (compound) ride, not a Joker subtitle. assert.equal(lookupFastLane("Joker y Harley Quinn", r), null); }); test("parseWaitTimes returns null when no ride rows present", () => { assert.equal(parseWaitTimes({ parkId: 1, venues: [] }), null); assert.equal( parseWaitTimes({ parkId: 1, venues: [{ venueId: 9, venueName: "Restaurants", details: [] }] }), null, ); });