use map2fig::scale::{Scale, scale_value};
use map2fig::{NegMode, PixelValue};
use proptest::prelude::*;
#[test]
fn prop_linear_scaling_bounded() {
proptest!(|(
value in -1e6f64..1e6f64,
min in -1e6f64..0f64,
max in 0f64..1e6f64,
)| {
let result = scale_value(value, min, max, Scale::Linear, NegMode::Zero, None, None);
if let PixelValue::Color(t) = result {
prop_assert!(
(0.0..=1.0).contains(&t),
"Linear scale produced out-of-bounds value: {} (value={}, min={}, max={})",
t, value, min, max
);
}
});
}
#[test]
fn prop_linear_min_value_scales_to_zero() {
proptest!(|(
min in -1e6f64..1e6f64,
max in -1e6f64..1e6f64,
)| {
let min = min.min(max);
let max = max.max(min + 1e-6);
let result = scale_value(min, min, max, Scale::Linear, NegMode::Zero, None, None);
if let PixelValue::Color(t) = result {
prop_assert!(
(t - 0.0).abs() < 1e-10,
"Value at min should scale to 0.0, got {} (min={}, max={})",
t, min, max
);
}
});
}
#[test]
fn prop_linear_max_value_scales_to_one() {
proptest!(|(
min in -1e6f64..1e6f64,
max in -1e6f64..1e6f64,
)| {
let min = min.min(max);
let max = max.max(min + 1e-6);
let result = scale_value(max, min, max, Scale::Linear, NegMode::Zero, None, None);
if let PixelValue::Color(t) = result {
prop_assert!(
(t - 1.0).abs() < 1e-10,
"Value at max should scale to 1.0, got {} (min={}, max={})",
t, min, max
);
}
});
}
#[test]
fn prop_log_scaling_bounded() {
proptest!(|(
value in 1e-6f64..1e6f64, // Log scale requires positive values
min in 1e-6f64..1e3f64,
max in 1e-6f64..1e6f64,
)| {
let min = min.min(max);
let max = max.max(min * 1.0001);
let result = scale_value(value, min, max, Scale::Log, NegMode::Zero, None, None);
if let PixelValue::Color(t) = result {
prop_assert!(
(0.0..=1.0).contains(&t),
"Log scale produced out-of-bounds value: {} (value={}, min={}, max={})",
t, value, min, max
);
}
});
}
#[test]
fn prop_symlog_scaling_bounded() {
proptest!(|(
value in -1e6f64..1e6f64,
min in -1e6f64..1e6f64,
max in -1e6f64..1e6f64,
)| {
let min = min.min(max);
let max = max.max(min + 1e-6);
let result = scale_value(
value, min, max,
Scale::Symlog { linthresh: 1.0 },
NegMode::Zero,
None,
None
);
if let PixelValue::Color(t) = result {
prop_assert!(
(0.0..=1.0).contains(&t),
"SymLog scale produced out-of-bounds value: {} (value={}, min={}, max={})",
t, value, min, max
);
}
});
}
#[test]
fn prop_asinh_scaling_bounded() {
proptest!(|(
value in -1e6f64..1e6f64,
min in -1e6f64..1e6f64,
max in -1e6f64..1e6f64,
)| {
let min = min.min(max);
let max = max.max(min + 1e-6);
let result = scale_value(
value, min, max,
Scale::Asinh { scale: 1.0 },
NegMode::Zero,
None,
None
);
if let PixelValue::Color(t) = result {
prop_assert!(
(0.0..=1.0).contains(&t),
"Asinh scale produced out-of-bounds value: {} (value={}, min={}, max={})",
t, value, min, max
);
}
});
}
#[test]
fn prop_nan_always_bad() {
proptest!(|(
min in -1e6f64..1e6f64,
max in -1e6f64..1e6f64,
)| {
let min = min.min(max);
let max = max.max(min + 1e-6);
let result = scale_value(f64::NAN, min, max, Scale::Linear, NegMode::Zero, None, None);
match result {
PixelValue::Bad => {},
other => prop_assert!(false, "NaN should produce PixelValue::Bad, got {:?}", other),
}
});
}
#[test]
fn prop_infinity_always_bad() {
proptest!(|(
min in -1e6f64..1e6f64,
max in -1e6f64..1e6f64,
)| {
let min = min.min(max);
let max = max.max(min + 1e-6);
let pos_inf = scale_value(f64::INFINITY, min, max, Scale::Linear, NegMode::Zero, None, None);
let neg_inf = scale_value(f64::NEG_INFINITY, min, max, Scale::Linear, NegMode::Zero, None, None);
match pos_inf {
PixelValue::Bad => {},
other => prop_assert!(false, "Positive infinity should produce PixelValue::Bad, got {:?}", other),
}
match neg_inf {
PixelValue::Bad => {},
other => prop_assert!(false, "Negative infinity should produce PixelValue::Bad, got {:?}", other),
}
});
}
#[test]
fn prop_linear_monotonic() {
proptest!(|(
v1 in -1e6f64..1e6f64,
v2 in -1e6f64..1e6f64,
min in -1e6f64..0f64,
max in 0f64..1e6f64,
)| {
let (v1, v2) = if v1 < v2 { (v1, v2) } else { (v2, v1) };
let r1 = scale_value(v1, min, max, Scale::Linear, NegMode::Zero, None, None);
let r2 = scale_value(v2, min, max, Scale::Linear, NegMode::Zero, None, None);
if let (PixelValue::Color(t1), PixelValue::Color(t2)) = (r1, r2) {
prop_assert!(
t1 <= t2,
"Monotonicity violated: {} scaled to {}, {} scaled to {}",
v1, t1, v2, t2
);
}
});
}
#[test]
fn prop_clamping() {
proptest!(|(
offset in 1e3f64..1e6f64,
min in -1e3f64..0f64,
max in 0f64..1e3f64,
)| {
let below_min = min - offset;
let result_below = scale_value(below_min, min, max, Scale::Linear, NegMode::Zero, None, None);
if let PixelValue::Color(t) = result_below {
prop_assert!((t - 0.0).abs() < 1e-10, "Values below min should clamp to 0.0, got {}", t);
}
let above_max = max + offset;
let result_above = scale_value(above_max, min, max, Scale::Linear, NegMode::Zero, None, None);
if let PixelValue::Color(t) = result_above {
prop_assert!((t - 1.0).abs() < 1e-10, "Values above max should clamp to 1.0, got {}", t);
}
});
}
#[test]
fn prop_min_equals_max_scales_to_half() {
proptest!(|(
val in -1e6f64..1e6f64,
same_val in -1e6f64..1e6f64,
)| {
prop_assume!(same_val.is_finite());
let result = scale_value(val, same_val, same_val, Scale::Linear, NegMode::Zero, None, None);
if let PixelValue::Color(t) = result {
prop_assert!(
(t - 0.5).abs() < 1e-10,
"When min == max, valid values should scale to 0.5, got {}",
t
);
}
});
}
#[test]
fn prop_negative_range() {
proptest!(|(
value in -100f64..-1f64,
min in -100f64..-50f64,
max in -50f64..-1f64,
)| {
let result = scale_value(value, min, max, Scale::Linear, NegMode::Zero, None, None);
if let PixelValue::Color(t) = result {
prop_assert!(
(0.0..=1.0).contains(&t),
"Negative range scaling should still produce normalized output"
);
}
});
}
#[test]
fn prop_tiny_range_safe() {
proptest!(|(
min in -1e6f64..1e6f64,
)| {
let max = min + 1e-12; let value = (min + max) / 2.0;
let result = scale_value(value, min, max, Scale::Linear, NegMode::Zero, None, None);
if let PixelValue::Color(t) = result {
prop_assert!(
t.is_finite(),
"Very small ranges should not produce NaN/Inf"
);
}
});
}
#[test]
fn prop_colormap_sampling_bounds() {
use map2fig::get_colormap;
proptest!(|(
t in 0.0f64..=1.0f64,
)| {
let cmap = get_colormap("viridis");
let _ = cmap.sample(t);
});
}
#[test]
fn prop_all_colormaps_valid() {
use map2fig::available_colormaps;
use map2fig::get_colormap;
let cmap_names = available_colormaps();
for name in cmap_names {
let cmap = get_colormap(name);
for i in 0..=10 {
let t = i as f64 / 10.0;
let _ = cmap.sample(t);
}
}
}