Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
use hcie_filter::apply_filter;
|
||||
|
||||
/// Value-verification tests for hcie-filter.
|
||||
///
|
||||
/// Upgrades existing "does not panic" smoke tests to actual
|
||||
/// correctness assertions with known input → known output checks.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: create a small test layer with known pixel data
|
||||
// ---------------------------------------------------------------------------
|
||||
fn make_gradient_layer() -> hcie_protocol::Layer {
|
||||
let mut layer = hcie_protocol::Layer::new_blank("gradient", 4, 4);
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
layer.set_pixel(x, y, [x as u8 * 64, y as u8 * 64, 128, 255]);
|
||||
}
|
||||
}
|
||||
layer
|
||||
}
|
||||
|
||||
fn make_solid_layer(r: u8, g: u8, b: u8, a: u8) -> hcie_protocol::Layer {
|
||||
let mut layer = hcie_protocol::Layer::new_blank("solid", 4, 4);
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
layer.set_pixel(x, y, [r, g, b, a]);
|
||||
}
|
||||
}
|
||||
layer
|
||||
}
|
||||
|
||||
fn make_1x1_layer(r: u8, g: u8, b: u8, a: u8) -> hcie_protocol::Layer {
|
||||
let mut layer = hcie_protocol::Layer::new_blank("tiny", 1, 1);
|
||||
layer.set_pixel(0, 0, [r, g, b, a]);
|
||||
layer
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Invert filter — exact value verification
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn invert_white_becomes_black() {
|
||||
let mut layer = make_solid_layer(255, 255, 255, 255);
|
||||
apply_filter(&mut layer, "invert", &serde_json::json!({}));
|
||||
let pixel = layer.get_pixel(0, 0);
|
||||
assert_eq!(pixel[0], 0, "invert of white R should be 0");
|
||||
assert_eq!(pixel[1], 0, "invert of white G should be 0");
|
||||
assert_eq!(pixel[2], 0, "invert of white B should be 0");
|
||||
// Alpha typically unchanged
|
||||
assert_eq!(pixel[3], 255, "invert should leave alpha unchanged");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_black_becomes_white() {
|
||||
let mut layer = make_solid_layer(0, 0, 0, 255);
|
||||
apply_filter(&mut layer, "invert", &serde_json::json!({}));
|
||||
let pixel = layer.get_pixel(0, 0);
|
||||
assert_eq!(pixel[0], 255);
|
||||
assert_eq!(pixel[1], 255);
|
||||
assert_eq!(pixel[2], 255);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_red_becomes_cyan() {
|
||||
let mut layer = make_solid_layer(255, 0, 0, 255);
|
||||
apply_filter(&mut layer, "invert", &serde_json::json!({}));
|
||||
let pixel = layer.get_pixel(0, 0);
|
||||
assert_eq!(pixel[0], 0, "invert red: R should be 0");
|
||||
assert_eq!(pixel[1], 255, "invert red: G should be 255");
|
||||
assert_eq!(pixel[2], 255, "invert red: B should be 255");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_preserves_transparency() {
|
||||
let mut layer = make_solid_layer(100, 150, 200, 0);
|
||||
apply_filter(&mut layer, "invert", &serde_json::json!({}));
|
||||
let pixel = layer.get_pixel(0, 0);
|
||||
assert_eq!(pixel[3], 0, "invert should preserve zero alpha");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_invert_is_identity() {
|
||||
let original = make_gradient_layer();
|
||||
let mut layer = make_gradient_layer();
|
||||
apply_filter(&mut layer, "invert", &serde_json::json!({}));
|
||||
apply_filter(&mut layer, "invert", &serde_json::json!({}));
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
assert_eq!(
|
||||
layer.get_pixel(x, y),
|
||||
original.get_pixel(x, y),
|
||||
"double invert should restore original at ({},{})", x, y
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Grayscale filter — value verification
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn grayscale_makes_rgb_equal() {
|
||||
let mut layer = make_gradient_layer();
|
||||
apply_filter(&mut layer, "grayscale", &serde_json::json!({}));
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
let p = layer.get_pixel(x, y);
|
||||
let diff_rg = (p[0] as i16 - p[1] as i16).abs();
|
||||
let diff_rb = (p[0] as i16 - p[2] as i16).abs();
|
||||
let diff_gb = (p[1] as i16 - p[2] as i16).abs();
|
||||
// Allow small tolerance for integer rounding
|
||||
assert!(
|
||||
diff_rg <= 3 && diff_rb <= 3 && diff_gb <= 3,
|
||||
"grayscale should produce R≈G≈B at ({},{}): got {:?}",
|
||||
x, y, p
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grayscale_identity_on_gray_input() {
|
||||
let mut layer = make_solid_layer(128, 128, 128, 255);
|
||||
apply_filter(&mut layer, "grayscale", &serde_json::json!({}));
|
||||
let pixel = layer.get_pixel(0, 0);
|
||||
assert_eq!(pixel[0], 128);
|
||||
assert_eq!(pixel[1], 128);
|
||||
assert_eq!(pixel[2], 128);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grayscale_preserves_alpha() {
|
||||
let mut layer = make_solid_layer(100, 150, 200, 128);
|
||||
apply_filter(&mut layer, "grayscale", &serde_json::json!({}));
|
||||
let pixel = layer.get_pixel(0, 0);
|
||||
assert_eq!(pixel[3], 128, "grayscale should preserve alpha");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Brightness/Contrast filter
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn brightness_zero_is_identity() {
|
||||
let original = make_gradient_layer();
|
||||
let mut layer = make_gradient_layer();
|
||||
// PS-style brightness/contrast: 0 brightness, 0 contrast should be identity
|
||||
apply_filter(&mut layer, "brightness_contrast", &serde_json::json!({
|
||||
"brightness": 0.0,
|
||||
"contrast": 0.0
|
||||
}));
|
||||
for y in 0..4 {
|
||||
for x in 0..4 {
|
||||
let o = original.get_pixel(x, y);
|
||||
let p = layer.get_pixel(x, y);
|
||||
// Allow ±1 for rounding
|
||||
assert!(
|
||||
(o[0] as i16 - p[0] as i16).abs() <= 1 &&
|
||||
(o[1] as i16 - p[1] as i16).abs() <= 1 &&
|
||||
(o[2] as i16 - p[2] as i16).abs() <= 1,
|
||||
"identity params should preserve pixel at ({},{}): orig={:?}, got={:?}",
|
||||
x, y, o, p
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn brightness_changes_pixels() {
|
||||
let mut layer = make_solid_layer(64, 64, 64, 255);
|
||||
let before = layer.get_pixel(0, 0);
|
||||
// PS-style brightness uses 0-100 scale
|
||||
apply_filter(&mut layer, "brightness_contrast", &serde_json::json!({
|
||||
"brightness": 50.0,
|
||||
"contrast": 0.0
|
||||
}));
|
||||
let after = layer.get_pixel(0, 0);
|
||||
assert!(
|
||||
after[0] != before[0] || after[1] != before[1] || after[2] != before[2],
|
||||
"brightness=50 should change at least one channel"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn brightness_negative_changes_pixels() {
|
||||
let mut layer = make_solid_layer(128, 128, 128, 255);
|
||||
let before = layer.get_pixel(0, 0);
|
||||
// PS-style brightness uses 0-100 scale
|
||||
apply_filter(&mut layer, "brightness_contrast", &serde_json::json!({
|
||||
"brightness": -50.0,
|
||||
"contrast": 0.0
|
||||
}));
|
||||
let after = layer.get_pixel(0, 0);
|
||||
assert!(
|
||||
after[0] != before[0] || after[1] != before[1] || after[2] != before[2],
|
||||
"brightness=-50 should change at least one channel"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Edge cases: 1×1 images (minimal boundary)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn invert_works_on_1x1() {
|
||||
let mut layer = make_1x1_layer(128, 64, 32, 255);
|
||||
apply_filter(&mut layer, "invert", &serde_json::json!({}));
|
||||
let pixel = layer.get_pixel(0, 0);
|
||||
assert_eq!(pixel[0], 127); // 255 - 128
|
||||
assert_eq!(pixel[1], 191); // 255 - 64
|
||||
assert_eq!(pixel[2], 223); // 255 - 32
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grayscale_works_on_1x1() {
|
||||
let mut layer = make_1x1_layer(100, 50, 200, 255);
|
||||
apply_filter(&mut layer, "grayscale", &serde_json::json!({}));
|
||||
let pixel = layer.get_pixel(0, 0);
|
||||
// Grayscale: R, G, B should be approximately equal
|
||||
let diff = (pixel[0] as i16 - pixel[1] as i16).abs()
|
||||
.max((pixel[0] as i16 - pixel[2] as i16).abs())
|
||||
.max((pixel[1] as i16 - pixel[2] as i16).abs());
|
||||
assert!(diff <= 3, "grayscale on 1x1 should make R≈G≈B: {:?}", pixel);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Unknown / missing parameters do not crash
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn invert_with_empty_params_works() {
|
||||
let mut layer = make_solid_layer(100, 100, 100, 255);
|
||||
apply_filter(&mut layer, "invert", &serde_json::json!({}));
|
||||
let pixel = layer.get_pixel(0, 0);
|
||||
assert_eq!(pixel[0], 155);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_with_extra_params_does_not_crash() {
|
||||
let mut layer = make_solid_layer(0, 0, 0, 255);
|
||||
apply_filter(&mut layer, "invert", &serde_json::json!({
|
||||
"unknown_param": 42,
|
||||
"another_one": "hello"
|
||||
}));
|
||||
let pixel = layer.get_pixel(0, 0);
|
||||
assert_eq!(pixel[0], 255, "invert with extra params should still work");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Filters preserve layer dimensions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn filter_preserves_dimensions() {
|
||||
for filter_name in &["invert", "grayscale", "brightness_contrast", "box_blur"] {
|
||||
let mut layer = make_gradient_layer();
|
||||
apply_filter(&mut layer, filter_name, &serde_json::json!({}));
|
||||
assert_eq!(layer.width, 4, "{} should preserve width", filter_name);
|
||||
assert_eq!(layer.height, 4, "{} should preserve height", filter_name);
|
||||
assert_eq!(layer.pixels.len(), 4 * 4 * 4, "{} should preserve pixel count", filter_name);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Multiple filters in sequence do not crash
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn sequence_of_filters_does_not_crash() {
|
||||
let mut layer = make_gradient_layer();
|
||||
for &filter in &["invert", "grayscale", "brightness_contrast", "sharpen", "box_blur"] {
|
||||
apply_filter(&mut layer, filter, &serde_json::json!({}));
|
||||
}
|
||||
// If we got here without panic, the test passes
|
||||
assert_eq!(layer.width, 4);
|
||||
}
|
||||
Reference in New Issue
Block a user