diff --git a/lib/scrapers/queuetimes.ts b/lib/scrapers/queuetimes.ts index 6180807..5e6cdda 100644 --- a/lib/scrapers/queuetimes.ts +++ b/lib/scrapers/queuetimes.ts @@ -106,7 +106,7 @@ export async function fetchLiveRides( isOpen: r.is_open, waitMinutes: r.wait_time ?? 0, lastUpdated: r.last_updated, - isCoaster: coasterNames ? isCoaster(r.name, coasterNames) : false, + isCoaster: coasterNames ? isCoasterMatch(r.name, coasterNames) : false, }); } diff --git a/tests/coaster-matching.test.ts b/tests/coaster-matching.test.ts index 2b0646f..1617ec7 100644 --- a/tests/coaster-matching.test.ts +++ b/tests/coaster-matching.test.ts @@ -1,90 +1,51 @@ /** * Coaster name matching tests. * - * Each case documents a real mismatch found between Queue-Times ride names - * and RCDB coaster names, along with the park where it was observed. + * Each entry is a real case found between Queue-Times and RCDB names. + * Add new cases here when fixing a mismatch or false positive. * - * Run with: npm test + * Run with: npm test */ import { test } from "node:test"; import assert from "node:assert/strict"; import { isCoasterMatch, normalizeForMatch } from "../lib/coaster-match"; -// ── Helper ────────────────────────────────────────────────────────────────── - -function makeSet(...rcdbNames: string[]): Set { +function set(...rcdbNames: string[]): Set { return new Set(rcdbNames.map(normalizeForMatch)); } -// ── Should MATCH (Queue-Times name → RCDB name) ────────────────────────────── +// ── Should match ───────────────────────────────────────────────────────────── -test("exact match after lowercasing", () => { - assert.ok(isCoasterMatch("Goliath", makeSet("Goliath"))); -}); +const SHOULD_MATCH: [qtName: string, rcdbName: string, park: string][] = [ + ["BATMAN™ The Ride", "Batman The Ride", "Over Georgia / Magic Mountain"], + ["THE RIDDLER Mindbender", "Riddler Mindbender", "Over Georgia"], + ["THE RIDDLER™'s Revenge", "Riddler's Revenge", "Magic Mountain"], + ["CATWOMAN™ Whip", "Catwoman's Whip", "New England"], + ["SUPERMAN™: Ultimate Flight", "Superman - Ultimate Flight", "Over Georgia"], + ["THE JOKER™ Funhouse Coaster", "Joker Funhouse Coaster", "Over Georgia"], + ["The Great American Scream Machine", "Great American Scream Machine", "Over Georgia"], + ["Apocalypse", "Apocalypse the Ride", "Magic Mountain"], + ["The New Revolution - Classic", "New Revolution", "Magic Mountain"], + ["SCREAM", "Scream!", "Magic Mountain"], + ["BAT GIRL™: Coaster Chase", "Batgirl Coaster Chase", "Fiesta Texas"], + ["THE JOKER™ 4D Free Fly Coaster", "Joker", "New England"], +]; -test("trademark symbol stripped — BATMAN™ The Ride (Over Georgia, Magic Mountain)", () => { - assert.ok(isCoasterMatch("BATMAN™ The Ride", makeSet("Batman The Ride"))); -}); +for (const [qt, rcdb, park] of SHOULD_MATCH) { + test(`match: "${qt}" = "${rcdb}" (${park})`, () => { + assert.ok(isCoasterMatch(qt, set(rcdb)), `Expected match`); + }); +} -test("leading THE stripped — THE RIDDLER Mindbender (Over Georgia)", () => { - assert.ok(isCoasterMatch("THE RIDDLER Mindbender", makeSet("Riddler Mindbender"))); -}); +// ── Should NOT match (false positives) ─────────────────────────────────────── -test("trademark + leading THE — THE RIDDLER™'s Revenge (Magic Mountain)", () => { - assert.ok(isCoasterMatch("THE RIDDLER™'s Revenge", makeSet("Riddler's Revenge"))); -}); +const SHOULD_NOT_MATCH: [qtName: string, rcdbName: string, park: string][] = [ + ["Joker y Harley Quinn", "Joker", "Six Flags Mexico"], +]; -test("curly apostrophe possessive stripped — CATWOMAN™ Whip (New England)", () => { - assert.ok(isCoasterMatch("CATWOMAN™ Whip", makeSet("Catwoman's Whip"))); -}); - -test("straight apostrophe possessive stripped", () => { - assert.ok(isCoasterMatch("Riddler's Revenge", makeSet("Riddler's Revenge"))); -}); - -test("trademark + colon punctuation — SUPERMAN™: Ultimate Flight (Over Georgia)", () => { - assert.ok(isCoasterMatch("SUPERMAN™: Ultimate Flight", makeSet("Superman - Ultimate Flight"))); -}); - -test("QT drops subtitle — Apocalypse (Magic Mountain)", () => { - assert.ok(isCoasterMatch("Apocalypse", makeSet("Apocalypse the Ride"))); -}); - -test("QT adds subtitle — The New Revolution - Classic (Magic Mountain)", () => { - assert.ok(isCoasterMatch("The New Revolution - Classic", makeSet("New Revolution"))); -}); - -test("QT exclamation stripped — SCREAM (Magic Mountain)", () => { - assert.ok(isCoasterMatch("SCREAM", makeSet("Scream!"))); -}); - -test("space-split word — BAT GIRL™: Coaster Chase (Fiesta Texas)", () => { - assert.ok(isCoasterMatch("BAT GIRL™: Coaster Chase", makeSet("Batgirl Coaster Chase"))); -}); - -test("trademark + 4D subtitle — THE JOKER™ 4D Free Fly Coaster (New England)", () => { - assert.ok(isCoasterMatch("THE JOKER™ 4D Free Fly Coaster", makeSet("Joker"))); -}); - -test("Great American Scream Machine — top-level QT rides array (Over Georgia)", () => { - assert.ok(isCoasterMatch("The Great American Scream Machine", makeSet("Great American Scream Machine"))); -}); - -test("THE JOKER™ Funhouse Coaster — top-level QT rides array (Over Georgia)", () => { - assert.ok(isCoasterMatch("THE JOKER™ Funhouse Coaster", makeSet("Joker Funhouse Coaster"))); -}); - -// ── Should NOT MATCH (false positives) ────────────────────────────────────── - -test("false positive: Joker y Harley Quinn ≠ Joker (Six Flags Mexico)", () => { - assert.ok(!isCoasterMatch("Joker y Harley Quinn", makeSet("Joker"))); -}); - -test("false positive: unrelated ride does not match", () => { - assert.ok(!isCoasterMatch("SkyScreamer", makeSet("Goliath"))); -}); - -test("false positive: short prefix with conjunction — de connector", () => { - assert.ok(!isCoasterMatch("Batman de Gotham", makeSet("Batman"))); -}); +for (const [qt, rcdb, park] of SHOULD_NOT_MATCH) { + test(`no match: "${qt}" ≠ "${rcdb}" (${park})`, () => { + assert.ok(!isCoasterMatch(qt, set(rcdb)), `Expected no match`); + }); +}