use hcie_tile::*; /// Integration tests for hcie-tile sparse tile storage. /// /// Covers: tile creation, pixel read/write, tile key mapping, /// sparse storage behavior, from_dense / to_dense roundtrip, /// update_tiles_in_region, composite_into, edge cases. // --------------------------------------------------------------------------- // Tile (single 256×256 block) tests // --------------------------------------------------------------------------- #[test] fn new_transparent_tile_is_all_zeros() { let tile = Tile::new_transparent(); assert_eq!(tile.pixels.len(), TILE_BYTES); // Spot-check a few positions assert_eq!(tile.pixels[0], 0); assert_eq!(tile.pixels[3], 0); assert_eq!(tile.pixels[TILE_BYTES - 1], 0); } #[test] fn new_blank_tile_is_white_and_opaque() { let tile = Tile::new_blank(); assert_eq!(tile.pixels.len(), TILE_BYTES); // Every pixel should be [255, 255, 255, 255] for i in 0..10 { let base = i * 4; assert_eq!(tile.pixels[base], 255, "R should be 255 at pixel {}", i); assert_eq!(tile.pixels[base + 1], 255, "G should be 255 at pixel {}", i); assert_eq!(tile.pixels[base + 2], 255, "B should be 255 at pixel {}", i); assert_eq!(tile.pixels[base + 3], 255, "A should be 255 at pixel {}", i); } } // --------------------------------------------------------------------------- // TiledLayer creation // --------------------------------------------------------------------------- #[test] fn new_tiled_layer_has_correct_dimensions() { let tl = TiledLayer::new(512, 256); assert_eq!(tl.width(), 512); assert_eq!(tl.height(), 256); } #[test] fn new_tiled_layer_has_zero_tiles() { let tl = TiledLayer::new(512, 256); assert_eq!(tl.tile_count(), 0, "empty layer should have no allocated tiles"); } #[test] fn from_dense_size_matches() { let pixels = vec![255u8; 64 * 64 * 4]; let tl = TiledLayer::from_dense(&pixels, 64, 64); assert_eq!(tl.width(), 64); assert_eq!(tl.height(), 64); } #[test] fn from_dense_creates_tiles_for_non_transparent_data() { let pixels = vec![255u8; 256 * 256 * 4]; // fully opaque, exactly one tile let tl = TiledLayer::from_dense(&pixels, 256, 256); assert_eq!(tl.tile_count(), 1, "fully opaque 256x256 should produce exactly 1 tile"); } #[test] fn from_dense_prunes_transparent_tiles() { let pixels = vec![0u8; 256 * 256 * 4]; // fully transparent, exactly one tile let tl = TiledLayer::from_dense(&pixels, 256, 256); assert_eq!( tl.tile_count(), 0, "fully transparent layer should have no allocated tiles" ); } // --------------------------------------------------------------------------- // Tile key mapping // --------------------------------------------------------------------------- #[test] fn tile_key_maps_pixel_zero_to_zero_zero() { let key = TiledLayer::tile_key(0, 0); assert_eq!(key, (0, 0)); } #[test] fn tile_key_maps_pixel_255_to_0() { let key = TiledLayer::tile_key(255, 255); assert_eq!(key, (0, 0)); } #[test] fn tile_key_maps_pixel_256_to_1() { let key = TiledLayer::tile_key(256, 256); assert_eq!(key, (1, 1)); } #[test] fn tile_key_maps_large_coordinates() { let key = TiledLayer::tile_key(1000, 2000); assert_eq!(key, (1000 / TILE_SIZE, 2000 / TILE_SIZE)); } // --------------------------------------------------------------------------- // Pixel read/write // --------------------------------------------------------------------------- #[test] fn get_pixel_outside_allocated_tile_returns_transparent() { let tl = TiledLayer::new(512, 512); let pixel = tl.get_pixel(100, 100); assert_eq!(pixel, [0, 0, 0, 0], "unwritten pixel should be transparent"); } #[test] fn set_pixel_and_get_pixel_roundtrip() { let mut tl = TiledLayer::new(64, 64); tl.set_pixel(10, 10, [255, 128, 64, 200]); let pixel = tl.get_pixel(10, 10); assert_eq!(pixel, [255, 128, 64, 200]); } #[test] fn set_pixel_out_of_bounds_is_noop() { let mut tl = TiledLayer::new(64, 64); tl.set_pixel(100, 100, [255, 0, 0, 255]); // outside layer bounds assert_eq!(tl.tile_count(), 0, "out-of-bounds set should not create tiles"); } #[test] fn set_pixel_auto_creates_tile() { let mut tl = TiledLayer::new(256, 256); tl.set_pixel(0, 0, [255, 0, 0, 255]); assert_eq!(tl.tile_count(), 1, "setting a pixel should create the containing tile"); } #[test] fn get_pixel_written_twice_returns_last_value() { let mut tl = TiledLayer::new(64, 64); tl.set_pixel(5, 5, [10, 20, 30, 40]); tl.set_pixel(5, 5, [50, 60, 70, 80]); assert_eq!(tl.get_pixel(5, 5), [50, 60, 70, 80]); } // --------------------------------------------------------------------------- // to_dense roundtrip // --------------------------------------------------------------------------- #[test] fn to_dense_returns_correct_size() { let tl = TiledLayer::new(64, 64); let dense = tl.to_dense(); assert_eq!(dense.len(), (64 * 64 * 4) as usize); } #[test] fn to_dense_after_set_pixel_preserves_value() { let mut tl = TiledLayer::new(16, 16); tl.set_pixel(7, 8, [100, 150, 200, 250]); let dense = tl.to_dense(); let idx = ((8 * 16 + 7) * 4) as usize; assert_eq!(dense[idx], 100); assert_eq!(dense[idx + 1], 150); assert_eq!(dense[idx + 2], 200); assert_eq!(dense[idx + 3], 250); } #[test] fn from_dense_to_dense_roundtrip() { let original = vec![42u8; 128 * 128 * 4]; let tl = TiledLayer::from_dense(&original, 128, 128); let result = tl.to_dense(); assert_eq!(original, result, "from_dense → to_dense should be identity"); } #[test] fn from_dense_to_dense_roundtrip_transparent() { let original = vec![0u8; 128 * 128 * 4]; let tl = TiledLayer::from_dense(&original, 128, 128); let result = tl.to_dense(); assert_eq!(original, result, "transparent from_dense → to_dense should be identity"); } // --------------------------------------------------------------------------- // update_tiles_in_region // --------------------------------------------------------------------------- #[test] fn update_tiles_in_region_writes_pixels() { let mut tl = TiledLayer::new(64, 64); let mut pixels = vec![0u8; 64 * 64 * 4]; // Set a block of pixels to red for y in 10..20 { for x in 10..20 { let idx = ((y * 64 + x) * 4) as usize; pixels[idx] = 255; pixels[idx + 3] = 255; } } tl.update_tiles_in_region(&pixels, 64, 0, 0, 64, 64); assert_eq!(tl.get_pixel(15, 15), [255, 0, 0, 255], "updated pixel should be red"); assert_eq!(tl.get_pixel(0, 0), [0, 0, 0, 0], "pixel outside update region stays unchanged"); } #[test] fn update_tiles_in_region_partial_update() { let mut tl = TiledLayer::new(256, 256); let mut pixels = vec![0u8; 64 * 64 * 4]; for i in (0..pixels.len()).step_by(4) { pixels[i] = 255; pixels[i + 3] = 255; } // Update only the sub-region (0,0)-(64,64) tl.update_tiles_in_region(&pixels, 64, 0, 0, 64, 64); assert_eq!(tl.get_pixel(32, 32), [255, 0, 0, 255]); assert_eq!(tl.get_pixel(100, 100), [0, 0, 0, 0], "pixels outside region should be zero"); } // --------------------------------------------------------------------------- // composite_into // --------------------------------------------------------------------------- #[test] fn composite_into_copies_pixels() { let mut tl = TiledLayer::new(32, 32); tl.set_pixel(5, 5, [100, 150, 200, 255]); let mut output = vec![0u8; 32 * 32 * 4]; tl.composite_into(&mut output, 32, 32, 0, 0, 32, 32); let idx = ((5 * 32 + 5) * 4) as usize; assert_eq!(output[idx], 100); assert_eq!(output[idx + 1], 150); assert_eq!(output[idx + 2], 200); assert_eq!(output[idx + 3], 255); } #[test] fn composite_into_respects_region_bounds() { let mut tl = TiledLayer::new(64, 64); tl.set_pixel(30, 30, [255, 0, 0, 255]); let mut output = vec![0u8; 64 * 64 * 4]; // Composite only the top-left 16×16 region tl.composite_into(&mut output, 64, 64, 0, 0, 16, 16); // Pixel at (30,30) is outside the region and should not be copied let idx = ((30 * 64 + 30) * 4) as usize; assert_eq!( output[idx..idx + 4], [0, 0, 0, 0], "pixel outside region should not appear in output" ); } // --------------------------------------------------------------------------- // Edge cases // --------------------------------------------------------------------------- #[test] fn zero_sized_layer_has_no_tiles() { let tl = TiledLayer::new(0, 0); assert_eq!(tl.tile_count(), 0); assert_eq!(tl.width(), 0); assert_eq!(tl.height(), 0); } #[test] fn very_large_layer_does_not_panic() { let tl = TiledLayer::new(4096, 4096); assert_eq!(tl.width(), 4096); assert_eq!(tl.height(), 4096); } #[test] fn tile_count_increases_with_written_area() { let mut tl = TiledLayer::new(512, 512); assert_eq!(tl.tile_count(), 0); // Write one pixel in two different tiles tl.set_pixel(0, 0, [1, 1, 1, 1]); assert_eq!(tl.tile_count(), 1); tl.set_pixel(300, 300, [2, 2, 2, 2]); assert_eq!(tl.tile_count(), 2, "pixels in different tiles should create two tiles"); } #[test] fn tiles_are_independent() { let mut tl = TiledLayer::new(512, 512); tl.set_pixel(0, 0, [10, 20, 30, 40]); tl.set_pixel(300, 300, [50, 60, 70, 80]); assert_eq!(tl.get_pixel(0, 0), [10, 20, 30, 40]); assert_eq!(tl.get_pixel(300, 300), [50, 60, 70, 80]); // neighboring pixel should be untouched assert_eq!(tl.get_pixel(1, 0), [0, 0, 0, 0]); } // --------------------------------------------------------------------------- // Serde roundtrip (if Tile is Serialize/Deserialize) // --------------------------------------------------------------------------- #[test] fn tile_serde_json_roundtrip() { let tile = Tile::new_blank(); let json = serde_json::to_string(&tile).expect("serialize tile"); let restored: Tile = serde_json::from_str(&json).expect("deserialize tile"); assert_eq!(tile.pixels, restored.pixels, "JSON serde roundtrip should preserve pixels"); } #[test] fn tile_bincode_roundtrip() { let tile = Tile::new_blank(); let bytes = bincode::serialize(&tile).expect("serialize tile via bincode"); let restored: Tile = bincode::deserialize(&bytes).expect("deserialize tile via bincode"); assert_eq!(tile.pixels, restored.pixels, "bincode serde roundtrip should preserve pixels"); }