Some checks failed
Deploy MineGeo to Dokploy / deploy (push) Failing after 3m40s
211 lines
5.6 KiB
TypeScript
211 lines
5.6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import {
|
|
customDifficultySchema,
|
|
difficulties,
|
|
scoreGuess,
|
|
} from "../shared/protocol";
|
|
import { createRoomSchema } from "../shared/multiplayer";
|
|
import { tournamentSettings } from "../shared/tournaments";
|
|
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,
|
|
mapPan: preset.mapPan,
|
|
timerVisible: preset.timerVisible,
|
|
scoring: preset.scoring,
|
|
},
|
|
{
|
|
pan: true,
|
|
compass: true,
|
|
coordinates: true,
|
|
mapZoom: true,
|
|
mapPan: true,
|
|
timerVisible: true,
|
|
scoring: "normal",
|
|
},
|
|
);
|
|
for (const key of [
|
|
"pan",
|
|
"compass",
|
|
"coordinates",
|
|
"mapZoom",
|
|
"mapPan",
|
|
"timerVisible",
|
|
])
|
|
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 [5, 10, 15, 20, 90, 600])
|
|
assert.equal(
|
|
customDifficultySchema.safeParse({ name: "Fixture", ...rules, seconds })
|
|
.success,
|
|
true,
|
|
);
|
|
for (const seconds of [0, 4, 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("Void is the shortest built-in challenge and disables navigation aids", () => {
|
|
assert.ok(
|
|
Object.entries(difficulties).every(
|
|
([id, rule]) => id === "void" || difficulties.void.seconds < rule.seconds,
|
|
),
|
|
);
|
|
for (const rule of [
|
|
"zoom",
|
|
"pan",
|
|
"compass",
|
|
"coordinates",
|
|
"mapZoom",
|
|
"mapPan",
|
|
] as const)
|
|
assert.equal(difficulties.void[rule], false);
|
|
assert.equal(difficulties.void.scoring, "strict");
|
|
assert.equal(
|
|
createRoomSchema.safeParse({
|
|
datasetId: "11111111-1111-4111-8111-111111111111",
|
|
difficulty: "void",
|
|
playerLimit: 4,
|
|
}).success,
|
|
true,
|
|
);
|
|
});
|
|
|
|
test("Void tournaments preserve ten seconds per mini-round", () => {
|
|
const settings = {
|
|
name: "Void event",
|
|
datasetId: "11111111-1111-4111-8111-111111111111",
|
|
difficulty: "void",
|
|
playerLimit: 4,
|
|
roundCount: 3,
|
|
roundTimeSeconds: 10,
|
|
timerMode: "perMini",
|
|
startsAt: "2026-10-25T12:00:00Z",
|
|
announceDiscord: false,
|
|
};
|
|
assert.equal(tournamentSettings.safeParse(settings).success, true);
|
|
assert.equal(
|
|
tournamentSettings.safeParse({ ...settings, roundTimeSeconds: 90 }).success,
|
|
false,
|
|
);
|
|
assert.equal(
|
|
tournamentSettings.safeParse({ ...settings, timerMode: "shared" }).success,
|
|
false,
|
|
);
|
|
assert.equal(
|
|
tournamentSettings.safeParse({ ...settings, difficulty: "normal" }).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);
|
|
});
|