Files
hexifyer/tests/core/edge-connectivity.test.ts
Axel Meyer f302932ea8 Phase 1: Core hex engine, Leaflet overlay, terrain painting UI
- core/: Pure TS hex engine (axial coords, hex grid, terrain types,
  edge connectivity with constraint solver, HexMap state model)
- src/map/: Leaflet L.CRS.Simple map init, Canvas-based hex overlay
  layer (L.GridLayer), click/edge interaction detection
- src/ui/: Sidebar with toolbar (Select/Paint/Feature modes),
  terrain picker, hex inspector, map settings (hex size, grid, opacity)
- pipeline/: Tile pyramid generator (sharp, from source image)
- tests/: 32 passing tests for coords, hex-grid, edge-connectivity
- Uses Kiepenkerl tiles (symlinked) for development

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:32:52 +00:00

94 lines
2.9 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
edgeMask,
hasEdge,
toggleEdge,
setEdge,
clearEdge,
edgeCount,
connectedEdges,
rotateMask,
enforceEdgeConstraints,
} from '@core/edge-connectivity';
import { HexEdge } from '@core/types';
import { HexMap } from '@core/hex-map';
describe('edgeMask operations', () => {
it('creates mask from edges', () => {
const mask = edgeMask(HexEdge.NE, HexEdge.SW);
expect(hasEdge(mask, HexEdge.NE)).toBe(true);
expect(hasEdge(mask, HexEdge.SW)).toBe(true);
expect(hasEdge(mask, HexEdge.E)).toBe(false);
expect(edgeCount(mask)).toBe(2);
});
it('toggles edges', () => {
let mask = edgeMask(HexEdge.E);
mask = toggleEdge(mask, HexEdge.E);
expect(hasEdge(mask, HexEdge.E)).toBe(false);
mask = toggleEdge(mask, HexEdge.E);
expect(hasEdge(mask, HexEdge.E)).toBe(true);
});
it('sets and clears edges', () => {
let mask = 0;
mask = setEdge(mask, HexEdge.NW);
expect(hasEdge(mask, HexEdge.NW)).toBe(true);
mask = clearEdge(mask, HexEdge.NW);
expect(hasEdge(mask, HexEdge.NW)).toBe(false);
});
it('connectedEdges returns correct edges', () => {
const mask = edgeMask(HexEdge.NE, HexEdge.SE, HexEdge.W);
const edges = connectedEdges(mask);
expect(edges).toEqual([HexEdge.NE, HexEdge.SE, HexEdge.W]);
});
});
describe('rotateMask', () => {
it('rotating 6 steps returns original', () => {
const mask = edgeMask(HexEdge.NE, HexEdge.E);
expect(rotateMask(mask, 6)).toBe(mask);
});
it('rotates single edge clockwise by 1', () => {
const mask = edgeMask(HexEdge.NE); // bit 0
const rotated = rotateMask(mask, 1);
expect(hasEdge(rotated, HexEdge.E)).toBe(true); // bit 1
expect(edgeCount(rotated)).toBe(1);
});
it('rotates opposite edges correctly', () => {
const mask = edgeMask(HexEdge.NE, HexEdge.SW); // bits 0,3
const rotated = rotateMask(mask, 1);
expect(hasEdge(rotated, HexEdge.E)).toBe(true);
expect(hasEdge(rotated, HexEdge.W)).toBe(true);
expect(edgeCount(rotated)).toBe(2);
});
});
describe('enforceEdgeConstraints', () => {
it('detects missing continuation on neighbor', () => {
const hexMap = new HexMap();
const coord = { q: 0, r: 0 };
const mask = edgeMask(HexEdge.E);
const actions = enforceEdgeConstraints(hexMap, coord, 'road', mask);
expect(actions).toHaveLength(1);
expect(actions[0].coord).toEqual({ q: 1, r: 0 }); // E neighbor
expect(actions[0].edge).toBe(HexEdge.W); // opposite edge
expect(actions[0].terrainId).toBe('road');
});
it('no action needed when neighbor already has feature', () => {
const hexMap = new HexMap();
// Set up neighbor with road on W edge
hexMap.setFeature({ q: 1, r: 0 }, 'road', edgeMask(HexEdge.W));
const actions = enforceEdgeConstraints(
hexMap, { q: 0, r: 0 }, 'road', edgeMask(HexEdge.E),
);
expect(actions).toHaveLength(0);
});
});