import assert from "node:assert/strict"; import { test } from "node:test"; import { customDifficultySchema, scoreGuess } from "../shared/protocol"; import { mergeDifficultyPresets } from "../shared/difficulty-presets"; const rules = { seconds: 90, zoom: true }; test("expanded difficulty rules preserve defaults and validate controls", () => { const preset = customDifficultySchema.parse({ name: "Classic", ...rules }); assert.deepEqual( { pan: preset.pan, compass: preset.compass, coordinates: preset.coordinates, mapZoom: preset.mapZoom, scoring: preset.scoring, }, { pan: true, compass: true, coordinates: true, mapZoom: true, scoring: "normal", }, ); for (const key of ["pan", "compass", "coordinates", "mapZoom"]) assert.equal( customDifficultySchema.safeParse({ name: "Fixture", ...rules, [key]: "false", }).success, false, ); assert.equal( customDifficultySchema.safeParse({ name: "Fixture", ...rules, scoring: "extreme", }).success, false, ); assert.equal( customDifficultySchema.safeParse({ name: "Fixture", ...rules, rounds: 10 }) .success, false, ); }); test("scoring strictness changes distance decay while perfect guesses remain 5000", () => { const bounds = { minX: 0, minZ: 0, maxX: 100, maxZ: 100 }; const actual = { x: 10, z: 10 }, guess = { x: 40, z: 40 }; const scores = ["strict", "normal", "gentle"].map( (mode) => scoreGuess(actual, guess, bounds, mode as "strict" | "normal" | "gentle") .score, ); assert.ok(scores[0] < scores[1] && scores[1] < scores[2]); for (const mode of ["strict", "normal", "gentle"] as const) assert.equal(scoreGuess(actual, actual, bounds, mode).score, 5000); }); test("difficulty names accept normal text and count Unicode characters consistently with PostgreSQL", () => { for (const name of [ "Normal", "Speed 90", "Тест", "中文", "😎😎", "😎".repeat(40), "a".repeat(40), "a\\b", ]) assert.equal( customDifficultySchema.safeParse({ name, ...rules }).success, true, name, ); assert.equal( customDifficultySchema.parse({ name: " My challenge ", ...rules }).name, "My challenge", ); for (const name of [ "", "a", "😎", "a".repeat(41), "😎".repeat(41), "x\nname", "x\u0085name", "x\u202ename", "x\u2066name", ]) assert.equal( customDifficultySchema.safeParse({ name, ...rules }).success, false, ); }); test("difficulty rules enforce their supported range and strict request shape", () => { for (const seconds of [15, 90, 600]) assert.equal( customDifficultySchema.safeParse({ name: "Fixture", ...rules, seconds }) .success, true, ); for (const seconds of [0, 14, 16, 601, 15.5, NaN, Infinity, "90"]) assert.equal( customDifficultySchema.safeParse({ name: "Fixture", ...rules, seconds }) .success, false, ); assert.equal( customDifficultySchema.safeParse({ name: "Fixture", ...rules, owner: "forged", }).success, false, ); }); test("a delayed preset listing cannot erase a newly saved preset or duplicate it", async () => { const old = { id: "old", name: "Old", ...rules }; const saved = { id: "saved", name: "New", ...rules }; let release!: (presets: (typeof old)[]) => void; const response = new Promise<(typeof old)[]>((resolve) => { release = resolve; }); let presets = [old]; const loading = response.then((snapshot) => { presets = mergeDifficultyPresets(presets, snapshot); }); presets = mergeDifficultyPresets(presets, [saved]); release([old]); await loading; assert.deepEqual( presets.map((p) => p.id), ["saved", "old"], ); assert.equal(mergeDifficultyPresets(presets, [saved]).length, 2); });