add vitest and regression tests for arc 1
This commit is contained in:
parent
0074f0e10c
commit
9f538b1543
9 changed files with 1561 additions and 4 deletions
1157
package-lock.json
generated
1157
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
|
@ -12,6 +12,8 @@
|
|||
"analyze:requests": "node scripts/request-analysis-worker.mjs",
|
||||
"validate:content": "node scripts/validate-content-schemas.mjs",
|
||||
"build": "tsc -b && vite build",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
|
|
@ -23,6 +25,9 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^24.12.3",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
|
|
@ -31,9 +36,10 @@
|
|||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.6.0",
|
||||
"jsdom": "^29.1.1",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.59.2",
|
||||
"vite": "^8.0.12"
|
||||
"vite": "^8.0.12",
|
||||
"vitest": "^4.1.9"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
81
src/components/worldshaperShared.test.ts
Normal file
81
src/components/worldshaperShared.test.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import {
|
||||
getMapBackgroundTileId,
|
||||
parseHeightLayers,
|
||||
parseRoomLayers,
|
||||
} from "./worldshaperShared";
|
||||
import type { JsonObject } from "../editorCore";
|
||||
|
||||
describe("worldshaperShared", () => {
|
||||
it("prefers top-level background tile ids and falls back to the legacy nested shape", () => {
|
||||
expect(getMapBackgroundTileId({ backgroundTileId: "grass" })).toBe("grass");
|
||||
expect(getMapBackgroundTileId({ tiles: { backgroundTileId: "water" } as unknown as JsonObject })).toBe("water");
|
||||
expect(getMapBackgroundTileId({})).toBe("");
|
||||
});
|
||||
|
||||
it("synthesizes and resizes room layers from the current map payload", () => {
|
||||
const result = parseRoomLayers({
|
||||
rows: ["##", "#"],
|
||||
roomLayers: [
|
||||
{
|
||||
layer: 2,
|
||||
name: "Objects",
|
||||
rows: ["A"],
|
||||
instanceIds: ["npc_1", ""],
|
||||
},
|
||||
],
|
||||
}, 3, 2);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
layer: 0,
|
||||
name: undefined,
|
||||
zIndex: 0,
|
||||
rows: ["##.", "#.."],
|
||||
instanceIds: [],
|
||||
},
|
||||
{
|
||||
layer: 2,
|
||||
name: "Objects",
|
||||
zIndex: 0,
|
||||
rows: ["A ", " "],
|
||||
instanceIds: ["npc_1"],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("normalizes, trims, and bounds height patches while dropping duplicate ids", () => {
|
||||
const result = parseHeightLayers({
|
||||
heightLayers: [
|
||||
{
|
||||
id: "ridge",
|
||||
z: 3,
|
||||
x: -1,
|
||||
y: -1,
|
||||
rows: [
|
||||
"...",
|
||||
".9.",
|
||||
"..8",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "ridge",
|
||||
z: 9,
|
||||
x: 0,
|
||||
y: 0,
|
||||
rows: ["1"],
|
||||
},
|
||||
],
|
||||
}, 4, 4);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: "ridge",
|
||||
name: undefined,
|
||||
z: 3,
|
||||
x: 0,
|
||||
y: 0,
|
||||
rows: ["9", " 8"],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
106
src/editorCore.test.ts
Normal file
106
src/editorCore.test.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import {
|
||||
getSpriteRows,
|
||||
normalizeImageRecordForSave,
|
||||
normalizeImagesPayloadForSave,
|
||||
normalizeTileRecordForSave,
|
||||
resolveUnifiedColorSymbol,
|
||||
setUnifiedColorEntries,
|
||||
} from "./editorCore";
|
||||
import type { JsonObject, JsonValue } from "./editorCore";
|
||||
|
||||
describe("editorCore", () => {
|
||||
it("normalizes tile records with padded rows and compatibility symbols", () => {
|
||||
const result = normalizeTileRecordForSave({
|
||||
id: "tile_grass",
|
||||
rows: ["A", "BC", "DROP"],
|
||||
width: 2,
|
||||
height: 2,
|
||||
});
|
||||
|
||||
expect(result).toEqual(expect.objectContaining({
|
||||
id: "tile_grass",
|
||||
symbol: "t",
|
||||
rows: ["A.", "BC"],
|
||||
width: 2,
|
||||
height: 2,
|
||||
}));
|
||||
});
|
||||
|
||||
it("normalizes image records into frame-based storage while preserving tile compatibility", () => {
|
||||
const result = normalizeImageRecordForSave({
|
||||
id: "tile_tree",
|
||||
roles: ["tile", "sprite", "other", "tile"],
|
||||
rows: ["A", "BC"],
|
||||
width: 2,
|
||||
height: 2,
|
||||
speed: -10,
|
||||
playback: "invalid",
|
||||
tileSymbol: "$",
|
||||
});
|
||||
|
||||
expect(result.rows).toBeUndefined();
|
||||
expect(result.symbol).toBeUndefined();
|
||||
expect(result.graphicRole).toBeUndefined();
|
||||
expect(result.roles).toEqual(["tile", "sprite"]);
|
||||
expect(result.defaultFrame).toBe("frame_0");
|
||||
expect(result.speed).toBe(0);
|
||||
expect(result.playback).toBe("normal");
|
||||
expect(result.tileSymbol).toBe("$");
|
||||
expect(result.frames).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "frame_0",
|
||||
rows: ["A.", "BC"],
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves sprite rows from the default enabled frame", () => {
|
||||
const record: JsonObject = {
|
||||
defaultFrame: "walk_1",
|
||||
frames: [
|
||||
{ id: "idle", rows: ["II"], enabled: true },
|
||||
{ id: "walk_1", rows: ["WW"], enabled: true },
|
||||
{ id: "disabled", rows: ["DD"], enabled: false },
|
||||
],
|
||||
};
|
||||
|
||||
expect(getSpriteRows(record)).toEqual(["WW"]);
|
||||
});
|
||||
|
||||
it("normalizes image payload arrays without changing non-record entries", () => {
|
||||
const payload: JsonValue = {
|
||||
schemaVersion: 1,
|
||||
images: [
|
||||
{
|
||||
id: "sprite_hero",
|
||||
roles: ["sprite"],
|
||||
rows: ["X"],
|
||||
},
|
||||
"leave-me-alone",
|
||||
],
|
||||
};
|
||||
|
||||
expect(normalizeImagesPayloadForSave(payload)).toEqual({
|
||||
schemaVersion: 1,
|
||||
images: [
|
||||
expect.objectContaining({
|
||||
id: "sprite_hero",
|
||||
roles: ["sprite"],
|
||||
defaultFrame: "frame_0",
|
||||
}),
|
||||
"leave-me-alone",
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("updates the unified color lookup from catalog entries", () => {
|
||||
setUnifiedColorEntries([
|
||||
{ key: "A", color: "#123456" },
|
||||
{ key: "!", color: "#FFFFFF" },
|
||||
]);
|
||||
|
||||
expect(resolveUnifiedColorSymbol("A")).toBe("#123456");
|
||||
expect(resolveUnifiedColorSymbol(".")).toBe("#00000000");
|
||||
expect(resolveUnifiedColorSymbol("Z", "#ABCDEF")).toBe("#ABCDEF");
|
||||
});
|
||||
});
|
||||
1
src/test/setup.ts
Normal file
1
src/test/setup.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
import "@testing-library/jest-dom/vitest";
|
||||
77
src/worldChunking.test.ts
Normal file
77
src/worldChunking.test.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import {
|
||||
DEFAULT_WORLD_CHUNK_SIZE,
|
||||
buildChunkFileName,
|
||||
buildChunkKey,
|
||||
createEmptyChunk,
|
||||
localToWorldCoord,
|
||||
normalizeChunkDimension,
|
||||
resolveWorldChunkAddress,
|
||||
worldToChunkCoord,
|
||||
worldToLocalCoord,
|
||||
} from "./worldChunking";
|
||||
|
||||
describe("worldChunking", () => {
|
||||
it("normalizes chunk dimensions with flooring and fallback behavior", () => {
|
||||
expect(normalizeChunkDimension(12.9)).toBe(12);
|
||||
expect(normalizeChunkDimension(0, 24)).toBe(24);
|
||||
expect(normalizeChunkDimension(-5, 24)).toBe(1);
|
||||
expect(normalizeChunkDimension("bad", 18)).toBe(18);
|
||||
});
|
||||
|
||||
it("converts world coordinates into chunk and local coordinates across the origin", () => {
|
||||
expect(worldToChunkCoord(0, 32)).toBe(0);
|
||||
expect(worldToChunkCoord(31, 32)).toBe(0);
|
||||
expect(worldToChunkCoord(32, 32)).toBe(1);
|
||||
expect(worldToChunkCoord(-1, 32)).toBe(-1);
|
||||
expect(worldToChunkCoord(-33, 32)).toBe(-2);
|
||||
|
||||
expect(worldToLocalCoord(31, 32)).toBe(31);
|
||||
expect(worldToLocalCoord(32, 32)).toBe(0);
|
||||
expect(worldToLocalCoord(-1, 32)).toBe(31);
|
||||
expect(worldToLocalCoord(-33, 32)).toBe(31);
|
||||
|
||||
expect(localToWorldCoord(-2, 31, 32)).toBe(-33);
|
||||
});
|
||||
|
||||
it("resolves stable chunk addressing metadata", () => {
|
||||
expect(buildChunkKey(-3, 4)).toBe("-3:4");
|
||||
expect(buildChunkFileName(-3, 4)).toBe("-3_4.json");
|
||||
expect(resolveWorldChunkAddress(-33, 64, 32, 16)).toEqual({
|
||||
chunkX: -2,
|
||||
chunkY: 4,
|
||||
localX: 31,
|
||||
localY: 0,
|
||||
chunkKey: "-2:4",
|
||||
fileName: "-2_4.json",
|
||||
});
|
||||
});
|
||||
|
||||
it("creates empty chunks with the current compatibility layer defaults", () => {
|
||||
const chunk = createEmptyChunk("overworld", 2, -1, "grass", 4, 3);
|
||||
|
||||
expect(chunk).toEqual({
|
||||
schemaVersion: 1,
|
||||
worldId: "overworld",
|
||||
chunkX: 2,
|
||||
chunkY: -1,
|
||||
width: 4,
|
||||
height: 3,
|
||||
backgroundTileId: "grass",
|
||||
roomLayers: [
|
||||
{
|
||||
layer: 0,
|
||||
rows: ["....", "....", "...."],
|
||||
instanceIds: [],
|
||||
},
|
||||
{
|
||||
layer: 1,
|
||||
rows: [" ", " ", " "],
|
||||
instanceIds: [],
|
||||
},
|
||||
],
|
||||
heightLayers: [],
|
||||
instances: [],
|
||||
});
|
||||
expect(createEmptyChunk("world", 0, 0).width).toBe(DEFAULT_WORLD_CHUNK_SIZE);
|
||||
});
|
||||
});
|
||||
121
src/worldshaperStudio/graphicsDocumentHelpers.test.ts
Normal file
121
src/worldshaperStudio/graphicsDocumentHelpers.test.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import {
|
||||
buildImageRecordFromSpriteRecord,
|
||||
buildImageRecordFromTileRecord,
|
||||
buildTileRecordFromImageRecord,
|
||||
getImageRecordFromPayload,
|
||||
normalizeGraphicRoles,
|
||||
normalizeImagesPayloadSnapshot,
|
||||
} from "./graphicsDocumentHelpers";
|
||||
import type { JsonObject } from "../editorCore";
|
||||
|
||||
describe("graphicsDocumentHelpers", () => {
|
||||
it("keeps only supported graphic roles and de-duplicates them", () => {
|
||||
expect(normalizeGraphicRoles(["tile", "sprite", "tile", "other", "", null])).toEqual(["tile", "sprite"]);
|
||||
expect(normalizeGraphicRoles("tile")).toEqual([]);
|
||||
});
|
||||
|
||||
it("builds tile-backed image records without inventing new storage rules", () => {
|
||||
const existingRecord: JsonObject = {
|
||||
id: "tile_grass",
|
||||
roles: ["sprite"],
|
||||
tileSymbol: "g",
|
||||
tags: ["existing"],
|
||||
};
|
||||
|
||||
const result = buildImageRecordFromTileRecord({
|
||||
id: "tile_grass",
|
||||
name: "Grass",
|
||||
symbol: "#",
|
||||
rows: ["AB", "C"],
|
||||
width: 2,
|
||||
height: 2,
|
||||
pixelScale: 3,
|
||||
opacity: 2,
|
||||
tags: ["terrain", "terrain"],
|
||||
}, existingRecord);
|
||||
|
||||
expect(result.roles).toEqual(["sprite", "tile"]);
|
||||
expect(result.tileSymbol).toBe("#");
|
||||
expect(result.rows).toBeUndefined();
|
||||
expect(result.defaultFrame).toBe("frame_0");
|
||||
expect(result.frames).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "frame_0",
|
||||
rows: ["AB", "C."],
|
||||
width: 2,
|
||||
height: 2,
|
||||
pixelScale: 3,
|
||||
opacity: 1,
|
||||
}),
|
||||
]);
|
||||
expect(result.tags).toEqual(["terrain"]);
|
||||
});
|
||||
|
||||
it("updates sprite roles while preserving compatibility fields from existing images", () => {
|
||||
const existingRecord: JsonObject = {
|
||||
id: "hero",
|
||||
roles: ["tile", "sprite"],
|
||||
tileSymbol: "@",
|
||||
};
|
||||
|
||||
const result = buildImageRecordFromSpriteRecord({
|
||||
id: "hero",
|
||||
rows: ["X"],
|
||||
width: 1,
|
||||
height: 1,
|
||||
}, "other", existingRecord);
|
||||
|
||||
expect(result.roles).toEqual(["tile"]);
|
||||
expect(result.tileSymbol).toBe("@");
|
||||
});
|
||||
|
||||
it("hydrates image rows from the resolved default frame", () => {
|
||||
const payload: JsonObject = {
|
||||
schemaVersion: 1,
|
||||
images: [
|
||||
{
|
||||
id: "tile_grass",
|
||||
roles: ["tile"],
|
||||
tileSymbol: "G",
|
||||
width: 2,
|
||||
height: 2,
|
||||
defaultFrame: "alt",
|
||||
frames: [
|
||||
{ id: "base", rows: ["AA", "AA"], enabled: true },
|
||||
{ id: "alt", rows: ["BB", "BB"], enabled: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const snapshot = normalizeImagesPayloadSnapshot(payload);
|
||||
const imageRecord = getImageRecordFromPayload(snapshot, "tile_grass");
|
||||
|
||||
expect(imageRecord).toEqual(expect.objectContaining({
|
||||
id: "tile_grass",
|
||||
rows: ["BB", "BB"],
|
||||
}));
|
||||
});
|
||||
|
||||
it("projects tile records from image records through the current compatibility adapter", () => {
|
||||
const tileRecord = buildTileRecordFromImageRecord({
|
||||
id: "tile_grass",
|
||||
tileSymbol: "G",
|
||||
name: "Grass",
|
||||
description: "Ground",
|
||||
width: 2,
|
||||
height: 2,
|
||||
frames: [
|
||||
{ id: "frame_0", rows: ["AB", "CD"] },
|
||||
],
|
||||
});
|
||||
|
||||
expect(tileRecord).toEqual(expect.objectContaining({
|
||||
id: "tile_grass",
|
||||
symbol: "G",
|
||||
rows: ["AB", "CD"],
|
||||
width: 2,
|
||||
height: 2,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
"target": "es2023",
|
||||
"lib": ["ES2023", "DOM"],
|
||||
"module": "esnext",
|
||||
"types": ["vite/client"],
|
||||
"types": ["vite/client", "vitest/globals"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
|
|
|
|||
10
vitest.config.ts
Normal file
10
vitest.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
globals: true,
|
||||
setupFiles: "./src/test/setup.ts",
|
||||
include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue