/** * The pilot tiles that replaced the pill chips on the challenge screen, and * the drag-and-drop shortcut onto a slot they added. * * Named pilotTile rather than pilotCard on purpose: pilot.ts already exports * a `pilotCard()` for the Home stats card, and the two are unrelated — * reusing the name would make every future grep for one turn up the other. * * Structural, like the screen's other checks: there is no DOM under `npm * test`, and none of this fails at runtime — a tile that lost its rank row * or a drop handler that stopped calling placeInSlot still renders, it just * silently stops doing what this file exists to hold in place. * * Run with `npm test`. */ import { test } from "node:test"; import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; const web = fileURLToPath(new URL("../", import.meta.url)); const source = await readFile(join(web, "src/screens/challenge.ts"), "utf8"); test("pilotTile's rank defaults to n/a and always renders", () => { const fn = source.match( /function pilotTile\([^)]*\): HTMLElement \{[\s\S]*?\n {2}\}\n/, ); assert.ok(fn, "pilotTile() moved or was renamed"); assert.match( fn[0], /rank = "n\/a"/, "rank should default to n/a — there is no real rank data yet", ); assert.match( fn[0], /className: "pilot-tile-rank"/, "the rank row must always render, not be hidden or left out — a real " + "value has to slot in without a redesign", ); }); test("the pill shape is gone", () => { assert.doesNotMatch( source, /lineup-chip|chip-avatar/, "the old pill-chip classes are still referenced; pilotTile() replaced them", ); }); test("pilot tiles render in the lineup and in both opponent strips", () => { assert.match( source, /pilotTile\(handle, handle\)/, "the player's own lineup no longer builds a pilot tile", ); assert.match( source, /pilotTile\(did, handle\)/, "opponentChip (recent and suggested) no longer builds a pilot tile", ); }); test("a handle-bearing opponent card is draggable onto a slot, on top of click-to-add", () => { const fn = source.match( /function opponentChip\([^)]*\): HTMLElement \{[\s\S]*?\n {2}\}\n/, ); assert.ok(fn, "opponentChip() moved or was renamed"); const body = fn[0]; assert.match( body, /addEventListener\("click", \(\) =>\s*\n?\s*addPill\.say\(addToLineup\(handle\) \?\? ""\),?\s*\)/, "click-to-add must still work — drag is an addition, not a replacement", ); assert.match( body, /chip\.draggable = true/, "recent/suggested cards should be draggable onto a slot", ); assert.match( body, /dragstart.*\n.*setData\("text\/plain", handle\)/, "dragstart should hand the slot row the dragged opponent's handle", ); // Both handlers live inside `if (handle) { ... }`, so an inert card (no // handle to line up) gets neither. assert.match( body, /if \(handle\) \{[\s\S]*addEventListener\("click"[\s\S]*chip\.draggable = true[\s\S]*\}/, "an inert card (no handle) must stay undraggable, not just unclickable", ); }); test("a slot row accepts a drop and hands it to placeInSlot", () => { assert.match( source, /row\.addEventListener\("dragover"/, "the slot row lost its drop target", ); assert.match( source, /row\.addEventListener\("drop", \(event\) => \{[\s\S]*?placeInSlot\(slot\.name, handle\)/, "a drop on a slot row should call placeInSlot for that slot", ); }); test("placeInSlot lines up an opponent who was not already in the lineup", () => { const fn = source.match( /function placeInSlot\([^)]*\): void \{[\s\S]*?\n {2}\}\n/, ); assert.ok(fn, "placeInSlot() moved or was renamed"); assert.match( fn[0], /if \(!lineup\.includes\(bare\)\) \{[\s\S]*addToLineup\(bare\)/, "dropping a not-yet-lined-up opponent should still add them to the lineup", ); assert.match( fn[0], /seats\.set\(slotName, `p:\$\{bare\}`\)/, "placeInSlot should seat the dropped opponent in the slot dropped onto, " + "not wherever addToLineup happened to put them", ); });