Add test coverage retrofit for Phase 1/2 (worker, api, frontend)

Requested after Phase 2: from here on, MCMapper development follows
TDD (test-first) — this retrofits the pieces already built before that
request landed.

worker: unit tests for the tile rasterizer (background fill, exact
upscaled-block boundaries, full-grid painting, out-of-bounds columns) and
the block-color palette (distinctness checks, including that the
"unmapped block" placeholder never accidentally collides with a real
block's color). 16 tests total alongside the existing mesher tests.

api: wired up `bun test`. Unit tests for chunkOf's coordinate math.
Integration tests (real Postgres/Redis/MinIO, see README's new "Running
tests" section) for wsGateway.message() — auth accept/reject, upsert +
dedup on columns/sections, not-authenticated/invalid-JSON handling — and
for the tile/mesh/servers HTTP routes, driven through Elysia's in-process
`.handle()` rather than a bound port (sidesteps the stale dev-server
port-collision issue hit repeatedly this session). index.ts now exports
`app` and only calls `.listen()` when run directly, specifically so tests
can drive it this way.

frontend: extracted mesh.js's binary-format parser into its own ESM
module (mesh-format.js) so it's unit-testable without a browser/Babylon;
mesh.js now imports it. Tests build a buffer independently of the parser
(mirroring worker's encoder layout) so a mismatch in either direction —
Rust producer or JS consumer drifting — would be caught.
This commit is contained in:
2026-08-08 16:39:27 +02:00
parent 5ed4d32a56
commit dc7185c15e
16 changed files with 623 additions and 38 deletions
+55
View File
@@ -78,3 +78,58 @@ fn wool_color(meta: u8) -> [u8; 3] {
_ => UNKNOWN_COLOR,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn air_is_the_dedicated_air_color() {
assert_eq!(color_for(0, 0), AIR_COLOR);
}
#[test]
fn known_block_ignores_meta_unless_the_block_uses_it() {
// Stone (id 1) has no meta variants — any meta should map to the same color.
assert_eq!(color_for(1, 0), color_for(1, 15));
}
#[test]
fn oak_log_and_planks_differ() {
assert_ne!(color_for(5, 0), color_for(17, 0));
}
#[test]
fn planks_vary_by_meta() {
let oak = color_for(5, 0);
let spruce = color_for(5, 1);
let birch = color_for(5, 2);
assert_ne!(oak, spruce);
assert_ne!(oak, birch);
assert_ne!(spruce, birch);
}
#[test]
fn wool_all_sixteen_colors_are_distinct() {
let colors: Vec<[u8; 3]> = (0..16).map(|meta| color_for(35, meta)).collect();
for i in 0..colors.len() {
for j in (i + 1)..colors.len() {
assert_ne!(colors[i], colors[j], "wool meta {i} and {j} share a color");
}
}
}
#[test]
fn unmapped_block_id_falls_back_to_unknown_color() {
assert_eq!(color_for(9999, 0), UNKNOWN_COLOR);
}
#[test]
fn unknown_color_is_distinct_from_every_real_terrain_color() {
// The whole point of UNKNOWN_COLOR is to be visually obvious — assert it doesn't
// silently collide with a real block's color and blend in.
for id in [1u16, 2, 3, 4, 7, 8, 12, 17, 24, 41, 49, 87, 121] {
assert_ne!(color_for(id, 0), UNKNOWN_COLOR, "block {id} accidentally matches UNKNOWN_COLOR");
}
}
}
+61
View File
@@ -56,3 +56,64 @@ impl RenderBackend for CpuRenderBackend {
Ok(bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::palette::color_for;
fn decode(bytes: &[u8]) -> RgbImage {
image::load_from_memory(bytes).expect("worker must always produce a decodable PNG").to_rgb8()
}
#[test]
fn empty_columns_produce_a_full_background_tile() {
let backend = CpuRenderBackend::new();
let bytes = backend.rasterize_tile(&[]).unwrap();
let img = decode(&bytes);
assert_eq!(img.dimensions(), (256, 256));
for pixel in img.pixels() {
assert_eq!(*pixel, Rgb([30, 30, 40]));
}
}
#[test]
fn a_single_column_paints_exactly_its_upscaled_16x16_block() {
let backend = CpuRenderBackend::new();
let stone = color_for(1, 0);
let columns = vec![ColumnPixel { local_x: 0, local_z: 0, block_id: 1, block_meta: 0 }];
let img = decode(&backend.rasterize_tile(&columns).unwrap());
// Inside the upscaled region for local (0,0): pixels 0..16 on both axes.
assert_eq!(*img.get_pixel(0, 0), Rgb(stone));
assert_eq!(*img.get_pixel(15, 15), Rgb(stone));
// Just outside that region must still be background — proves UPSCALE didn't bleed.
assert_eq!(*img.get_pixel(16, 0), Rgb([30, 30, 40]));
assert_eq!(*img.get_pixel(0, 16), Rgb([30, 30, 40]));
}
#[test]
fn a_full_grid_paints_every_upscaled_pixel() {
let backend = CpuRenderBackend::new();
let grass = color_for(2, 0);
let mut columns = Vec::with_capacity(256);
for x in 0..16u8 {
for z in 0..16u8 {
columns.push(ColumnPixel { local_x: x, local_z: z, block_id: 2, block_meta: 0 });
}
}
let img = decode(&backend.rasterize_tile(&columns).unwrap());
assert_eq!(img.dimensions(), (256, 256));
for pixel in img.pixels() {
assert_eq!(*pixel, Rgb(grass));
}
}
#[test]
fn out_of_bounds_columns_are_ignored_rather_than_panicking() {
let backend = CpuRenderBackend::new();
let columns = vec![ColumnPixel { local_x: 200, local_z: 0, block_id: 1, block_meta: 0 }];
let result = backend.rasterize_tile(&columns);
assert!(result.is_ok());
}
}