154 lines
4.3 KiB
TypeScript
154 lines
4.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { z } from "zod";
|
|
import {
|
|
cargoTypeSchema,
|
|
gameConfigSchema,
|
|
locationSchema,
|
|
onboardingGoalSchema,
|
|
scriptedTutorialContractSchema,
|
|
shipModelSchema,
|
|
skillSchema,
|
|
} from "../src/index";
|
|
|
|
const validLocation = {
|
|
id: "earth",
|
|
name: "Earth",
|
|
type: "atmospheric-world",
|
|
landingRequirement: "atmospheric",
|
|
body: "earth",
|
|
launchCostMultiplier: 3,
|
|
startsDiscovered: true,
|
|
bayLeasePrice: 5000,
|
|
produces: ["standard"],
|
|
demands: ["heavy"],
|
|
};
|
|
|
|
const validShipModel = {
|
|
id: "courier",
|
|
name: "Courier",
|
|
archetype: "courier",
|
|
price: 20000,
|
|
cruiseSpeedKmPerGameHour: 300000,
|
|
cargoCapacityTonnes: 10,
|
|
fuelRangeMillionKm: 5,
|
|
hullRating: 1,
|
|
landingClass: "atmospheric",
|
|
overheadGameHours: 2,
|
|
fuelCostPerMillionKm: 100,
|
|
purchasable: true,
|
|
};
|
|
|
|
const validCargoType = {
|
|
id: "standard",
|
|
name: "Standard",
|
|
basePayPerTonnePerMillionKm: 10,
|
|
tierPayMultiplier: 1,
|
|
requiredSkillId: null,
|
|
};
|
|
|
|
const validSkill = {
|
|
id: "long-haul",
|
|
name: "Long Haul",
|
|
maxLevel: 5,
|
|
description: "Access to longer routes.",
|
|
};
|
|
|
|
const validGameConfig = {
|
|
timeCompression: 60,
|
|
epoch: { realStart: "2026-01-01T00:00:00Z", ephemerisDate: "2100-01-01T00:00:00Z" },
|
|
startingCredits: 0,
|
|
tutorialPayoutShare: 0.2,
|
|
contractBoardSize: 8,
|
|
tutorialBoardSize: 50,
|
|
contractBoardPeriodRealMinutes: 60,
|
|
firstContactChance: 0.1,
|
|
firstContactPayoutMultiplier: 2,
|
|
contractMassBands: [
|
|
{ min: 1, max: 20, count: 4 },
|
|
{ min: 21, max: 100, count: 4 },
|
|
],
|
|
longHaulCutoffsMillionKm: [100, 200, 300, 400, 500],
|
|
xpPerDelivery: { base: 10, perMillionKm: 0.1, perCargoTier: 5 },
|
|
pilotLevelXpThresholds: [100, 250, 500],
|
|
skillPointsPerLevel: 1,
|
|
pilotHireCost: 1000,
|
|
pilotPayPerDelivery: 100,
|
|
tutorialLoanerShipModelId: "company-courier",
|
|
tutorialRunsBeforeDeal: 4,
|
|
tutorialMassTonnes: { light: 5, medium: 12, full: 20 },
|
|
handlingGameHoursPerTonne: 0.02,
|
|
loanRepaymentShare: 0.3,
|
|
dealLoanAmount: 5000,
|
|
};
|
|
|
|
const validScripted = {
|
|
id: "unmarked-crates",
|
|
run: 3,
|
|
name: "Unmarked crates",
|
|
cargoTypeId: "standard",
|
|
payMultiplier: 2,
|
|
flavorMessage: "Nobody asked.",
|
|
};
|
|
|
|
const validGoal = {
|
|
id: "first-hire",
|
|
title: "Hire your first pilot",
|
|
condition: { kind: "pilots-hired", count: 1 },
|
|
reward: 1000,
|
|
};
|
|
|
|
const cases: [string, z.ZodType, Record<string, unknown>][] = [
|
|
["Location", locationSchema, validLocation],
|
|
["ShipModel", shipModelSchema, validShipModel],
|
|
["CargoType", cargoTypeSchema, validCargoType],
|
|
["Skill", skillSchema, validSkill],
|
|
["GameConfig", gameConfigSchema, validGameConfig],
|
|
["ScriptedTutorialContract", scriptedTutorialContractSchema, validScripted],
|
|
["OnboardingGoal", onboardingGoalSchema, validGoal],
|
|
];
|
|
|
|
describe.each(cases)("%s schema", (_name, schema, valid) => {
|
|
it("accepts a valid example", () => {
|
|
expect(schema.safeParse(valid).success).toBe(true);
|
|
});
|
|
|
|
it.each(Object.keys(valid))("rejects an object missing %s", (field) => {
|
|
const missing = { ...valid };
|
|
delete missing[field];
|
|
expect(schema.safeParse(missing).success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("GameConfig refinements", () => {
|
|
it("rejects tutorial mass tiers that do not increase", () => {
|
|
const config = { ...validGameConfig, tutorialMassTonnes: { light: 12, medium: 12, full: 20 } };
|
|
expect(gameConfigSchema.safeParse(config).success).toBe(false);
|
|
});
|
|
|
|
it("rejects a goal condition of an unknown kind", () => {
|
|
const goal = { ...validGoal, condition: { kind: "win-the-game" } };
|
|
expect(onboardingGoalSchema.safeParse(goal).success).toBe(false);
|
|
});
|
|
|
|
it("rejects a mass band with min above max", () => {
|
|
const config = {
|
|
...validGameConfig,
|
|
contractMassBands: [
|
|
{ min: 50, max: 10, count: 4 },
|
|
{ min: 21, max: 100, count: 4 },
|
|
],
|
|
};
|
|
expect(gameConfigSchema.safeParse(config).success).toBe(false);
|
|
});
|
|
|
|
it("rejects mass band counts that do not add up to the board size", () => {
|
|
const config = { ...validGameConfig, contractMassBands: [{ min: 1, max: 20, count: 3 }] };
|
|
expect(gameConfigSchema.safeParse(config).success).toBe(false);
|
|
});
|
|
|
|
it("rejects Long Haul cut-offs that are not increasing", () => {
|
|
const config = { ...validGameConfig, longHaulCutoffsMillionKm: [100, 100, 300] };
|
|
expect(gameConfigSchema.safeParse(config).success).toBe(false);
|
|
});
|
|
});
|