#[allow(unused_imports)]
pub use super::*;
#[cfg(test)]
mod tests {
use super::*;
use core::cmp::Ordering;
#[test]
fn hit_test_edges_match_contains() {
let r = LogicalRect::new(
LogicalPosition::new(10.0, 20.0),
LogicalSize::new(30.0, 40.0),
);
let tl = LogicalPosition::new(10.0, 20.0);
assert!(r.contains(tl));
assert!(r.hit_test(&tl).is_some());
let inside = LogicalPosition::new(11.0, 21.0);
assert!(r.contains(inside));
assert!(r.hit_test(&inside).is_some());
let br = LogicalPosition::new(40.0, 60.0);
assert!(!r.contains(br));
assert!(r.hit_test(&br).is_none());
let out = LogicalPosition::new(9.0, 20.0);
assert!(!r.contains(out));
assert!(r.hit_test(&out).is_none());
}
#[test]
fn hit_test_offset_is_from_top_left() {
let r = LogicalRect::new(
LogicalPosition::new(10.0, 20.0),
LogicalSize::new(30.0, 40.0),
);
let hit = r.hit_test(&LogicalPosition::new(15.0, 25.0)).unwrap();
assert_eq!(hit, LogicalPosition::new(5.0, 5.0));
}
#[test]
fn quantize_nan_is_distinct_from_zero() {
assert_eq!(quantize(f32::NAN), i64::MIN);
assert_ne!(quantize(f32::NAN), quantize(0.0));
}
#[test]
fn partial_eq_agrees_with_ord_and_hash() {
use core::hash::{Hash, Hasher};
let a = LogicalPosition::new(1.00000, 2.00000);
let b = LogicalPosition::new(1.00004, 2.00004); assert_eq!(a, b);
assert_eq!(a.cmp(&b), Ordering::Equal);
let hash_of = |p: &LogicalPosition| {
let mut h = std::collections::hash_map::DefaultHasher::new();
p.hash(&mut h);
h.finish()
};
assert_eq!(hash_of(&a), hash_of(&b));
let n1 = LogicalSize::new(f32::NAN, 1.0);
let n2 = LogicalSize::new(f32::NAN, 1.0);
assert_eq!(n1, n2);
}
#[test]
fn quantize_saturates_instead_of_wrapping() {
assert_eq!(quantize(f32::INFINITY), i64::MAX);
assert_eq!(quantize(f32::NEG_INFINITY), i64::MIN);
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod autotest_generated {
use core::{
cmp::Ordering,
hash::{Hash, Hasher},
};
use azul_css::props::layout::LayoutWritingMode;
use super::*;
use proptest::prelude::*;
use proptest::proptest;
const HOSTILE: [f32; 8] = [
f32::NAN,
f32::NEG_INFINITY,
f32::MIN,
-1.0,
0.0,
1.0,
f32::MAX,
f32::INFINITY,
];
const WMS: [LayoutWritingMode; 3] = [
LayoutWritingMode::HorizontalTb,
LayoutWritingMode::VerticalRl,
LayoutWritingMode::VerticalLr,
];
fn hash_of<T: Hash>(v: &T) -> u64 {
let mut h = std::collections::hash_map::DefaultHasher::new();
v.hash(&mut h);
h.finish()
}
#[test]
fn quantize_zero_and_negative_zero_share_a_bucket() {
assert_eq!(quantize(0.0), 0);
assert_eq!(quantize(-0.0), 0);
assert_eq!(quantize(0.0), quantize(-0.0));
}
#[test]
fn quantize_applies_the_decimal_multiplier() {
assert_eq!(quantize(1.0), DECIMAL_MULTIPLIER as i64);
assert_eq!(quantize(-1.0), -(DECIMAL_MULTIPLIER as i64));
assert_eq!(quantize(1.5), 1500);
assert_eq!(quantize(-1.5), -1500);
}
#[test]
fn quantize_truncates_toward_zero_below_precision() {
assert_eq!(quantize(0.0004), 0);
assert_eq!(quantize(-0.0004), 0);
assert_eq!(quantize(1.0004), 1000);
assert_eq!(quantize(-1.0004), -1000);
}
#[test]
fn quantize_extremes_saturate_and_never_wrap() {
assert_eq!(quantize(f32::MAX), i64::MAX);
assert_eq!(quantize(f32::MIN), i64::MIN);
assert_eq!(quantize(f32::INFINITY), i64::MAX);
assert_eq!(quantize(f32::NEG_INFINITY), i64::MIN);
assert_eq!(quantize(f32::MIN_POSITIVE), 0);
assert_eq!(quantize(-f32::MIN_POSITIVE), 0);
}
#[test]
fn quantize_nan_never_aliases_the_origin() {
assert_eq!(quantize(f32::NAN), i64::MIN);
assert_eq!(quantize(-f32::NAN), i64::MIN);
assert_ne!(quantize(f32::NAN), quantize(0.0));
}
#[test]
fn quantize_saturation_aliases_nan_with_the_bottom_of_the_range() {
assert_eq!(quantize(f32::NAN), quantize(f32::NEG_INFINITY));
assert_eq!(quantize(f32::NAN), quantize(f32::MIN));
assert_eq!(
LogicalPosition::new(f32::NAN, 0.0),
LogicalPosition::new(f32::NEG_INFINITY, 0.0)
);
}
#[test]
fn quantize_is_monotonic_over_finite_inputs() {
let ascending = [-1.0e6_f32, -1.0, -0.001, 0.0, 0.001, 1.0, 1.0e6];
for w in ascending.windows(2) {
assert!(
quantize(w[0]) <= quantize(w[1]),
"quantize inverted the order of {} and {}",
w[0],
w[1]
);
}
}
proptest! {
#[test]
fn quantize_is_deterministic_across_calls(v in proptest::num::f32::ANY) {
assert_eq!(quantize(v), quantize(v));
}
}
fn hostile_positions() -> [LogicalPosition; 64] {
let mut out = [LogicalPosition::zero(); 64];
let mut i = 0;
for x in HOSTILE {
for y in HOSTILE {
out[i] = LogicalPosition::new(x, y);
i += 1;
}
}
out
}
proptest! {
#[test]
fn ord_is_reflexive_and_antisymmetric_even_with_nan(
ax in proptest::num::f32::ANY, ay in proptest::num::f32::ANY,
bx in proptest::num::f32::ANY, by in proptest::num::f32::ANY
) {
let a = LogicalPosition::new(ax, ay);
let b = LogicalPosition::new(bx, by);
assert_eq!(a.cmp(&a), Ordering::Equal);
assert_eq!(a, a);
assert_eq!(a.cmp(&b), b.cmp(&a).reverse());
}
}
proptest! {
#[test]
fn ord_is_transitive_over_the_hostile_grid(
ax in proptest::num::f32::ANY, ay in proptest::num::f32::ANY,
bx in proptest::num::f32::ANY, by in proptest::num::f32::ANY,
cx in proptest::num::f32::ANY, cy in proptest::num::f32::ANY
) {
let a = LogicalPosition::new(ax, ay);
let b = LogicalPosition::new(bx, by);
let c = LogicalPosition::new(cx, cy);
if a.cmp(&b) == Ordering::Less && b.cmp(&c) == Ordering::Less {
assert_eq!(a.cmp(&c), Ordering::Less);
}
}
}
proptest! {
#[test]
fn partial_eq_ord_and_hash_agree_over_the_hostile_grid(
ax in proptest::num::f32::ANY, ay in proptest::num::f32::ANY,
bx in proptest::num::f32::ANY, by in proptest::num::f32::ANY
) {
let a = LogicalPosition::new(ax, ay);
let b = LogicalPosition::new(bx, by);
let eq = a == b;
assert_eq!(eq, a.cmp(&b) == Ordering::Equal);
assert_eq!(Some(a.cmp(&b)), a.partial_cmp(&b));
if eq {
assert_eq!(hash_of(&a), hash_of(&b));
}
}
}
proptest! {
#[test]
fn logical_size_eq_and_hash_agree_including_nan(
w in proptest::num::f32::ANY, h in proptest::num::f32::ANY
) {
let a = LogicalSize::new(w, h);
let b = LogicalSize::new(w, h);
assert_eq!(a, b);
assert_eq!(a.cmp(&b), Ordering::Equal);
assert_eq!(hash_of(&a), hash_of(&b));
}
}
#[test]
fn logical_rect_eq_and_hash_are_quantized_through_its_fields() {
let a = LogicalRect::new(
LogicalPosition::new(f32::NAN, 1.0),
LogicalSize::new(f32::NAN, 2.0),
);
let b = a;
assert_eq!(a, b);
assert_eq!(hash_of(&a), hash_of(&b));
let c = LogicalRect::new(LogicalPosition::new(1.0, 2.0), LogicalSize::new(3.0, 4.0));
let d = LogicalRect::new(
LogicalPosition::new(1.00004, 2.00004),
LogicalSize::new(3.00004, 4.00004),
);
assert_eq!(c, d);
assert_eq!(hash_of(&c), hash_of(&d));
}
proptest! {
#[test]
fn constructors_preserve_fields_for_extreme_arguments(
x in proptest::num::f32::ANY, y in proptest::num::f32::ANY
) {
let p = LogicalPosition::new(x, y);
assert_eq!(p.x.to_bits(), x.to_bits());
assert_eq!(p.y.to_bits(), y.to_bits());
let s = LogicalSize::new(x, y);
assert_eq!(s.width.to_bits(), x.to_bits());
assert_eq!(s.height.to_bits(), y.to_bits());
let r = LogicalRect::new(p, s);
assert_eq!(r.origin.x.to_bits(), x.to_bits());
assert_eq!(r.size.height.to_bits(), y.to_bits());
assert_eq!(ScreenPosition::new(x, y).x.to_bits(), x.to_bits());
assert_eq!(CursorNodePosition::new(x, y).y.to_bits(), y.to_bits());
assert_eq!(PhysicalPosition::new(x, y).x.to_bits(), x.to_bits());
assert_eq!(PhysicalSize::new(x, y).height.to_bits(), y.to_bits());
}
}
#[test]
fn zero_constructors_are_neutral_and_match_default() {
assert_eq!(LogicalPosition::zero(), LogicalPosition::default());
assert_eq!(LogicalSize::zero(), LogicalSize::default());
assert_eq!(LogicalRect::zero(), LogicalRect::default());
assert_eq!(LogicalRect::zero().origin, LogicalPosition::zero());
assert_eq!(LogicalRect::zero().size, LogicalSize::zero());
assert_eq!(ScreenPosition::zero(), ScreenPosition::default());
assert_eq!(CursorNodePosition::zero(), CursorNodePosition::default());
assert_eq!(PhysicalPosition::<i32>::zero(), PhysicalPosition::new(0, 0));
assert_eq!(
PhysicalPosition::<f64>::zero(),
PhysicalPosition::new(0.0_f64, 0.0_f64)
);
assert_eq!(PhysicalSize::<u32>::zero(), PhysicalSize::new(0, 0));
let z = LogicalRect::zero();
assert!(!z.contains(LogicalPosition::zero()));
assert!(!z.intersects(z));
assert_eq!(z.min_x(), 0.0);
assert_eq!(z.max_x(), 0.0);
assert_eq!(z.min_y(), 0.0);
assert_eq!(z.max_y(), 0.0);
}
#[test]
fn rect_getters_return_the_constructed_edges() {
let r = LogicalRect::new(
LogicalPosition::new(10.0, 20.0),
LogicalSize::new(30.0, 40.0),
);
assert_eq!(r.min_x(), 10.0);
assert_eq!(r.max_x(), 40.0);
assert_eq!(r.min_y(), 20.0);
assert_eq!(r.max_y(), 60.0);
}
proptest! {
#[test]
fn rect_getters_do_not_panic_on_extreme_geometry(
x in proptest::num::f32::ANY, w in proptest::num::f32::ANY
) {
let r = LogicalRect::new(LogicalPosition::new(x, x), LogicalSize::new(w, w));
let _ = r.min_x();
let _ = r.max_x();
let _ = r.min_y();
let _ = r.max_y();
}
}
#[test]
fn rect_getters_nan_propagation() {
let r = LogicalRect::new(
LogicalPosition::new(f32::INFINITY, f32::INFINITY),
LogicalSize::new(f32::NEG_INFINITY, f32::NEG_INFINITY),
);
assert!(r.max_x().is_nan());
assert!(r.max_y().is_nan());
}
#[test]
fn contains_is_half_open_left_top_inclusive_right_bottom_exclusive() {
let r = LogicalRect::new(
LogicalPosition::new(10.0, 20.0),
LogicalSize::new(30.0, 40.0),
);
assert!(r.contains(LogicalPosition::new(10.0, 20.0))); assert!(!r.contains(LogicalPosition::new(40.0, 59.0))); assert!(!r.contains(LogicalPosition::new(39.0, 60.0))); assert!(!r.contains(LogicalPosition::new(40.0, 60.0))); assert!(r.contains(LogicalPosition::new(39.999, 59.999)));
}
proptest! {
#[test]
fn contains_and_hit_test_agree_on_the_hostile_grid(
x in proptest::num::f32::ANY, y in proptest::num::f32::ANY
) {
let rects = [
LogicalRect::zero(),
LogicalRect::new(
LogicalPosition::new(10.0, 20.0),
LogicalSize::new(30.0, 40.0),
),
LogicalRect::new(
LogicalPosition::new(-5.0, -5.0),
LogicalSize::new(10.0, 10.0),
),
LogicalRect::new(
LogicalPosition::new(0.0, 0.0),
LogicalSize::new(-10.0, -10.0),
),
LogicalRect::new(
LogicalPosition::new(f32::NAN, f32::NAN),
LogicalSize::new(f32::NAN, f32::NAN),
),
LogicalRect::new(
LogicalPosition::zero(),
LogicalSize::new(f32::INFINITY, f32::INFINITY),
),
];
for r in rects {
let p = LogicalPosition::new(x, y);
assert_eq!(
r.contains(p),
r.hit_test(&p).is_some(),
"contains/hit_test disagree for {r:?} at {p:?}"
);
}
}
}
#[test]
fn contains_rejects_nan_points_and_nan_rects() {
let r = LogicalRect::new(
LogicalPosition::new(0.0, 0.0),
LogicalSize::new(100.0, 100.0),
);
assert!(!r.contains(LogicalPosition::new(f32::NAN, 50.0)));
assert!(!r.contains(LogicalPosition::new(50.0, f32::NAN)));
assert!(!r.contains(LogicalPosition::new(f32::NAN, f32::NAN)));
let nan_rect = LogicalRect::new(
LogicalPosition::new(f32::NAN, f32::NAN),
LogicalSize::new(f32::NAN, f32::NAN),
);
assert!(!nan_rect.contains(LogicalPosition::zero()));
assert!(nan_rect.hit_test(&LogicalPosition::zero()).is_none());
}
#[test]
fn contains_handles_negative_extent_rects_without_panicking() {
let r = LogicalRect::new(
LogicalPosition::new(0.0, 0.0),
LogicalSize::new(-10.0, -10.0),
);
assert!(!r.contains(LogicalPosition::zero()));
assert!(!r.contains(LogicalPosition::new(-5.0, -5.0)));
assert!(r.hit_test(&LogicalPosition::new(-5.0, -5.0)).is_none());
}
#[test]
fn contains_at_the_coordinate_extremes() {
let huge = LogicalRect::new(
LogicalPosition::new(f32::MIN, f32::MIN),
LogicalSize::new(f32::MAX, f32::MAX),
);
assert_eq!(huge.max_x(), 0.0);
assert!(huge.contains(LogicalPosition::new(-1.0, -1.0)));
assert!(!huge.contains(LogicalPosition::zero()));
let unbounded = LogicalRect::new(
LogicalPosition::new(f32::NEG_INFINITY, f32::NEG_INFINITY),
LogicalSize::new(f32::INFINITY, f32::INFINITY),
);
assert!(unbounded.max_x().is_nan());
assert!(!unbounded.contains(LogicalPosition::zero()));
}
#[test]
fn hit_test_returns_the_offset_from_the_top_left_corner() {
let r = LogicalRect::new(
LogicalPosition::new(10.0, 20.0),
LogicalSize::new(30.0, 40.0),
);
assert_eq!(
r.hit_test(&LogicalPosition::new(10.0, 20.0)),
Some(LogicalPosition::new(0.0, 0.0))
);
assert_eq!(
r.hit_test(&LogicalPosition::new(25.0, 45.0)),
Some(LogicalPosition::new(15.0, 25.0))
);
assert_eq!(r.hit_test(&LogicalPosition::new(40.0, 30.0)), None);
assert_eq!(r.hit_test(&LogicalPosition::new(30.0, 60.0)), None);
}
#[test]
fn hit_test_offset_is_always_non_negative_when_it_hits() {
let r = LogicalRect::new(
LogicalPosition::new(-100.0, -100.0),
LogicalSize::new(200.0, 200.0),
);
for x in [-100.0_f32, -50.0, 0.0, 50.0, 99.5] {
for y in [-100.0_f32, -50.0, 0.0, 50.0, 99.5] {
let hit = r.hit_test(&LogicalPosition::new(x, y)).expect("inside");
assert!(hit.x >= 0.0 && hit.y >= 0.0, "negative offset {hit:?}");
assert_eq!(r.origin.x + hit.x, x);
assert_eq!(r.origin.y + hit.y, y);
}
}
}
#[test]
fn intersects_is_symmetric_even_for_degenerate_and_nan_rects() {
let rects = [
LogicalRect::zero(),
LogicalRect::new(LogicalPosition::new(0.0, 0.0), LogicalSize::new(10.0, 10.0)),
LogicalRect::new(LogicalPosition::new(5.0, 5.0), LogicalSize::new(10.0, 10.0)),
LogicalRect::new(
LogicalPosition::new(10.0, 0.0),
LogicalSize::new(10.0, 10.0),
),
LogicalRect::new(
LogicalPosition::new(0.0, 0.0),
LogicalSize::new(-10.0, -10.0),
),
LogicalRect::new(
LogicalPosition::new(f32::NAN, f32::NAN),
LogicalSize::new(f32::NAN, f32::NAN),
),
LogicalRect::new(
LogicalPosition::new(f32::MIN, f32::MIN),
LogicalSize::new(f32::MAX, f32::MAX),
),
];
for a in rects {
for b in rects {
assert_eq!(
a.intersects(b),
b.intersects(a),
"intersects is asymmetric for {a:?} / {b:?}"
);
}
}
}
#[test]
fn intersects_touching_edges_do_not_count_as_overlap() {
let a = LogicalRect::new(LogicalPosition::new(0.0, 0.0), LogicalSize::new(10.0, 10.0));
let touching = LogicalRect::new(
LogicalPosition::new(10.0, 0.0),
LogicalSize::new(10.0, 10.0),
);
let overlapping = LogicalRect::new(
LogicalPosition::new(9.99, 0.0),
LogicalSize::new(10.0, 10.0),
);
assert!(!a.intersects(touching));
assert!(a.intersects(overlapping));
assert!(a.intersects(a));
assert!(!LogicalRect::zero().intersects(a));
}
#[test]
fn intersects_with_nan_rect_is_permissive_current_behavior() {
let nan_rect = LogicalRect::new(
LogicalPosition::new(f32::NAN, f32::NAN),
LogicalSize::new(f32::NAN, f32::NAN),
);
let normal = LogicalRect::new(LogicalPosition::new(0.0, 0.0), LogicalSize::new(10.0, 10.0));
assert!(nan_rect.intersects(normal));
assert!(normal.intersects(nan_rect));
assert!(!nan_rect.contains(LogicalPosition::zero()));
}
#[test]
fn scale_for_dpi_by_one_is_the_identity() {
let mut p = LogicalPosition::new(1.5, -2.5);
p.scale_for_dpi(1.0);
assert_eq!(p, LogicalPosition::new(1.5, -2.5));
let mut s = LogicalSize::new(3.5, 4.5);
assert_eq!(s.scale_for_dpi(1.0), LogicalSize::new(3.5, 4.5));
let mut r = LogicalRect::new(LogicalPosition::new(1.0, 2.0), LogicalSize::new(3.0, 4.0));
r.scale_for_dpi(1.0);
assert_eq!(
r,
LogicalRect::new(LogicalPosition::new(1.0, 2.0), LogicalSize::new(3.0, 4.0))
);
}
#[test]
fn scale_for_dpi_by_zero_collapses_to_the_origin() {
let mut r = LogicalRect::new(
LogicalPosition::new(10.0, 20.0),
LogicalSize::new(30.0, 40.0),
);
r.scale_for_dpi(0.0);
assert_eq!(r, LogicalRect::zero());
}
#[test]
fn scale_for_dpi_by_negative_factor_mirrors_deterministically() {
let mut r = LogicalRect::new(
LogicalPosition::new(10.0, 20.0),
LogicalSize::new(30.0, 40.0),
);
r.scale_for_dpi(-2.0);
assert_eq!(
r,
LogicalRect::new(
LogicalPosition::new(-20.0, -40.0),
LogicalSize::new(-60.0, -80.0)
)
);
assert!(!r.contains(LogicalPosition::new(-30.0, -50.0)));
}
#[test]
fn scale_for_dpi_overflows_to_infinity_rather_than_panicking() {
let mut s = LogicalSize::new(f32::MAX, f32::MAX);
let out = s.scale_for_dpi(2.0);
assert!(out.width.is_infinite() && out.width.is_sign_positive());
assert!(out.height.is_infinite());
assert_eq!(out, s);
}
proptest! {
#[test]
fn scale_for_dpi_with_nan_or_inf_does_not_panic(factor in proptest::num::f32::ANY) {
let mut p = LogicalPosition::new(1.0, -1.0);
p.scale_for_dpi(factor);
let mut s = LogicalSize::new(1.0, -1.0);
let _ = s.scale_for_dpi(factor);
let mut r =
LogicalRect::new(LogicalPosition::new(1.0, -1.0), LogicalSize::new(2.0, -2.0));
r.scale_for_dpi(factor);
}
}
#[test]
fn scale_for_dpi_with_inf_produces_nan() {
let mut r = LogicalRect::new(LogicalPosition::zero(), LogicalSize::new(1.0, 1.0));
r.scale_for_dpi(f32::INFINITY);
assert!(r.origin.x.is_nan());
assert!(r.size.width.is_infinite());
}
#[test]
fn to_physical_rounds_half_away_from_zero() {
assert_eq!(
LogicalPosition::new(0.5, 1.5).to_physical(1.0),
PhysicalPosition::new(1, 2)
);
assert_eq!(
LogicalSize::new(2.5, 3.5).to_physical(1.0),
PhysicalSize::new(3, 4)
);
}
#[test]
fn to_physical_clamps_negatives_to_zero_instead_of_wrapping() {
assert_eq!(
LogicalPosition::new(-1.0, -1000.0).to_physical(1.0),
PhysicalPosition::new(0, 0)
);
assert_eq!(
LogicalSize::new(-0.6, -1.0).to_physical(2.0),
PhysicalSize::new(0, 0)
);
assert_eq!(
LogicalPosition::new(1.0, 1.0).to_physical(-1.0),
PhysicalPosition::new(0, 0)
);
}
#[test]
fn to_physical_saturates_at_u32_max_on_overflow() {
assert_eq!(
LogicalSize::new(f32::MAX, f32::INFINITY).to_physical(1.0),
PhysicalSize::new(u32::MAX, u32::MAX)
);
assert_eq!(
LogicalPosition::new(1.0e30, 0.0).to_physical(1.0e30),
PhysicalPosition::new(u32::MAX, 0)
);
}
#[test]
fn to_physical_maps_nan_to_zero() {
assert_eq!(
LogicalPosition::new(f32::NAN, f32::NAN).to_physical(1.0),
PhysicalPosition::new(0, 0)
);
assert_eq!(
LogicalSize::new(f32::NAN, 5.0).to_physical(f32::NAN),
PhysicalSize::new(0, 0)
);
assert_eq!(
LogicalSize::new(0.0, 0.0).to_physical(f32::INFINITY),
PhysicalSize::new(0, 0)
);
}
proptest! {
#[test]
fn to_physical_never_panics_on_the_hostile_grid(
v in proptest::num::f32::ANY, f in proptest::num::f32::ANY
) {
let _ = LogicalPosition::new(v, v).to_physical(f);
let _ = LogicalSize::new(v, v).to_physical(f);
}
}
#[test]
fn to_logical_divides_by_the_dpi_factor() {
assert_eq!(
PhysicalSize::new(200_u32, 100).to_logical(2.0),
LogicalSize::new(100.0, 50.0)
);
assert_eq!(
PhysicalPosition::new(-10_i32, 20).to_logical(2.0),
LogicalPosition::new(-5.0, 10.0)
);
assert_eq!(
PhysicalPosition::new(-10.0_f64, 20.0).to_logical(2.0),
LogicalPosition::new(-5.0, 10.0)
);
}
#[test]
fn to_logical_with_zero_dpi_yields_infinity_not_a_panic() {
let s = PhysicalSize::new(100_u32, 100).to_logical(0.0);
assert!(s.width.is_infinite() && s.width.is_sign_positive());
let z = PhysicalSize::<u32>::zero().to_logical(0.0);
assert!(z.width.is_nan() && z.height.is_nan());
let p = PhysicalPosition::new(-5_i32, 5).to_logical(0.0);
assert!(p.x.is_infinite() && p.x.is_sign_negative());
assert!(p.y.is_infinite() && p.y.is_sign_positive());
}
#[test]
fn to_logical_at_the_integer_limits() {
let p = PhysicalPosition::new(i32::MIN, i32::MAX).to_logical(1.0);
assert_eq!(p.x, i32::MIN as f32);
assert_eq!(p.y, i32::MAX as f32);
let s = PhysicalSize::new(u32::MAX, 0_u32).to_logical(1.0);
assert_eq!(s.width, u32::MAX as f32);
assert_eq!(s.height, 0.0);
let big = PhysicalPosition::new(f64::MAX, f64::MIN).to_logical(1.0);
assert!(big.x.is_infinite() && big.x.is_sign_positive());
assert!(big.y.is_infinite() && big.y.is_sign_negative());
}
proptest! {
#[test]
fn to_logical_never_panics_for_hostile_dpi_factors(f in proptest::num::f32::ANY) {
let _ = PhysicalPosition::new(i32::MIN, i32::MAX).to_logical(f);
let _ = PhysicalPosition::new(f64::MAX, f64::MIN).to_logical(f);
let _ = PhysicalSize::new(u32::MAX, 0_u32).to_logical(f);
}
}
#[test]
fn logical_size_physical_round_trip_is_lossless_for_integral_pixels() {
for factor in [1.0_f32, 2.0, 4.0] {
for (w, h) in [
(0.0_f32, 0.0_f32),
(1.0, 1.0),
(100.0, 50.0),
(1920.0, 1080.0),
] {
let original = LogicalSize::new(w, h);
let round_tripped = original.to_physical(factor).to_logical(factor);
assert_eq!(
original, round_tripped,
"round-trip lost {original:?} at dpi {factor}"
);
}
}
}
#[test]
fn physical_size_logical_round_trip_preserves_the_pixel_count() {
for factor in [1.0_f32, 1.5, 2.0, 3.0] {
for (w, h) in [(0_u32, 0_u32), (1, 1), (1920, 1080), (3840, 2160)] {
let original = PhysicalSize::new(w, h);
let round_tripped = original.to_logical(factor).to_physical(factor);
assert_eq!(
original, round_tripped,
"round-trip lost {original:?} at dpi {factor}"
);
}
}
}
proptest! {
#[test]
fn screen_and_cursor_position_logical_round_trip_bit_for_bit(
x in proptest::num::f32::ANY, y in proptest::num::f32::ANY
) {
let p = LogicalPosition::new(x, y);
let screen = ScreenPosition::from_logical(p).to_logical();
assert_eq!(screen.x.to_bits(), x.to_bits());
assert_eq!(screen.y.to_bits(), y.to_bits());
let cursor = CursorNodePosition::from_logical(p).to_logical();
assert_eq!(cursor.x.to_bits(), x.to_bits());
assert_eq!(cursor.y.to_bits(), y.to_bits());
}
}
#[test]
fn add_sub_are_inverse_for_finite_positions() {
let a = LogicalPosition::new(10.0, -20.0);
let b = LogicalPosition::new(2.5, 7.5);
assert_eq!((a + b) - b, a);
let mut c = a;
c += b;
assert_eq!(c, a + b);
c -= b;
assert_eq!(c, a);
}
proptest! {
#[test]
fn position_main_cross_round_trip_for_every_writing_mode(
main in proptest::num::f32::ANY, cross in proptest::num::f32::ANY
) {
for wm in WMS {
let p = LogicalPosition::from_main_cross(main, cross, wm);
assert_eq!(p.main(wm).to_bits(), main.to_bits());
assert_eq!(p.cross(wm).to_bits(), cross.to_bits());
}
}
#[test]
fn size_main_cross_round_trip_for_every_writing_mode(
main in proptest::num::f32::ANY, cross in proptest::num::f32::ANY
) {
for wm in WMS {
let s = LogicalSize::from_main_cross(main, cross, wm);
assert_eq!(s.main(wm).to_bits(), main.to_bits());
assert_eq!(s.cross(wm).to_bits(), cross.to_bits());
}
}
}
#[test]
fn horizontal_tb_maps_main_to_the_block_axis() {
let wm = LayoutWritingMode::HorizontalTb;
let p = LogicalPosition::new(3.0, 7.0);
assert_eq!(p.main(wm), 7.0);
assert_eq!(p.cross(wm), 3.0);
let s = LogicalSize::new(30.0, 70.0);
assert_eq!(s.main(wm), 70.0);
assert_eq!(s.cross(wm), 30.0);
}
#[test]
fn vertical_modes_map_main_to_the_horizontal_axis() {
for wm in [LayoutWritingMode::VerticalRl, LayoutWritingMode::VerticalLr] {
let p = LogicalPosition::new(3.0, 7.0);
assert_eq!(p.main(wm), 3.0);
assert_eq!(p.cross(wm), 7.0);
let s = LogicalSize::new(30.0, 70.0);
assert_eq!(s.main(wm), 30.0);
assert_eq!(s.cross(wm), 70.0);
}
}
proptest! {
#[test]
fn with_main_and_with_cross_only_touch_their_own_axis(v in proptest::num::f32::ANY) {
for wm in WMS {
let s = LogicalSize::new(10.0, 20.0);
let m = s.with_main(wm, v);
assert_eq!(m.main(wm).to_bits(), v.to_bits());
assert_eq!(
m.cross(wm),
s.cross(wm),
"with_main clobbered the cross axis"
);
let c = s.with_cross(wm, v);
assert_eq!(c.cross(wm).to_bits(), v.to_bits());
assert_eq!(c.main(wm), s.main(wm), "with_cross clobbered the main axis");
}
}
}
#[test]
fn with_main_then_with_cross_reconstructs_from_main_cross() {
for wm in WMS {
let built = LogicalSize::zero().with_main(wm, 5.0).with_cross(wm, 9.0);
assert_eq!(built, LogicalSize::from_main_cross(5.0, 9.0, wm));
}
}
#[test]
fn display_formats_are_well_formed_for_representative_values() {
let p = LogicalPosition::new(1.5, -2.5);
assert_eq!(format!("{p}"), "(1.5, -2.5)");
assert_eq!(format!("{p:?}"), "(1.5, -2.5)");
let s = LogicalSize::new(30.0, 40.0);
assert_eq!(format!("{s}"), "30x40");
assert_eq!(format!("{s:?}"), "30x40");
let r = LogicalRect::new(p, s);
assert_eq!(format!("{r}"), "30x40 @ (1.5, -2.5)");
assert_eq!(format!("{r:?}"), "30x40 @ (1.5, -2.5)");
assert_eq!(format!("{:?}", PhysicalPosition::new(1_i32, 2)), "(1, 2)");
assert_eq!(format!("{:?}", PhysicalSize::new(1_u32, 2)), "1x2");
}
#[test]
fn display_of_zero_values_is_non_empty() {
assert!(!format!("{}", LogicalPosition::zero()).is_empty());
assert!(!format!("{}", LogicalSize::zero()).is_empty());
assert!(!format!("{}", LogicalRect::zero()).is_empty());
assert_eq!(format!("{}", LogicalRect::zero()), "0x0 @ (0, 0)");
}
proptest! {
#[test]
fn display_does_not_panic_on_nan_or_infinite_coordinates(
x in proptest::num::f32::ANY, y in proptest::num::f32::ANY
) {
let r = LogicalRect::new(LogicalPosition::new(x, y), LogicalSize::new(x, y));
let shown = format!("{r}");
assert!(!shown.is_empty());
assert_eq!(shown, format!("{r:?}"));
}
}
#[test]
fn display_nan_explicit_formatting() {
let nan = LogicalRect::new(
LogicalPosition::new(f32::NAN, f32::INFINITY),
LogicalSize::new(f32::NEG_INFINITY, f32::NAN),
);
assert_eq!(format!("{nan}"), "-infxNaN @ (NaN, inf)");
}
}