use super::*;
fn rect(left: i32, top: i32, right: i32, bottom: i32) -> RectPx {
RectPx {
left,
top,
right,
bottom,
}
}
fn pt(x: i32, y: i32) -> PointPx {
PointPx { x, y }
}
fn input(anchor: PointPx, work_area: RectPx, width: i32, height: i32) -> PlaceInput {
PlaceInput {
anchor,
tip: SizePx { width, height },
work_area,
offset_above: 20,
offset_below: 32,
}
}
const FHD: RectPx = RectPx {
left: 0,
top: 0,
right: 1920,
bottom: 1080,
};
#[test]
fn place_directly_above_centred_with_offset() {
let p = place(&input(pt(500, 500), FHD, 100, 40));
assert_eq!(p, pt(450, 440));
assert_eq!(p.y + 40, 500 - 20);
}
#[test]
fn place_flips_below_when_above_does_not_fit() {
let p = place(&input(pt(500, 50), FHD, 100, 40));
assert_eq!(p, pt(450, 50 + 32));
}
#[test]
fn place_clamps_left_and_right_edges() {
assert_eq!(place(&input(pt(10, 500), FHD, 100, 40)), pt(0, 440));
assert_eq!(place(&input(pt(1910, 500), FHD, 100, 40)), pt(1820, 440));
}
#[test]
fn place_neither_side_fits_uses_wider_side() {
let short = rect(0, 0, 1920, 100);
assert_eq!(place(&input(pt(500, 60), short, 100, 60)), pt(450, 0));
assert_eq!(place(&input(pt(500, 40), short, 100, 60)), pt(450, 40));
}
#[test]
fn place_boundaries_prefer_above() {
assert_eq!(place(&input(pt(500, 60), FHD, 100, 40)), pt(450, 0));
let short = rect(0, 0, 1920, 100);
assert_eq!(place(&input(pt(500, 44), short, 100, 60)), pt(450, 0));
}
#[test]
fn place_too_large_aligns_top_left() {
assert_eq!(place(&input(pt(500, 500), FHD, 2000, 1200)), pt(0, 0));
assert_eq!(place(&input(pt(500, 500), FHD, 2000, 40)), pt(0, 440));
let left_screen = rect(-1920, -200, 0, 880);
assert_eq!(
place(&input(pt(-900, 300), left_screen, 2000, 1200)),
pt(-1920, -200)
);
}
#[test]
fn place_on_screen_with_negative_coordinates() {
let left_screen = rect(-1920, -200, 0, 880);
assert_eq!(
place(&input(pt(-1915, -150), left_screen, 100, 40)),
pt(-1920, -150 + 32)
);
assert_eq!(
place(&input(pt(-960, 300), left_screen, 100, 40)),
pt(-1010, 240)
);
}
#[test]
fn place_always_inside_work_area_when_smaller() {
let wa = rect(-1920, -200, 0, 880);
for x in (-2100..=200).step_by(37) {
for y in (-400..=1100).step_by(29) {
let p = place(&input(pt(x, y), wa, 300, 120));
assert!(
p.x >= wa.left && p.x + 300 <= wa.right && p.y >= wa.top && p.y + 120 <= wa.bottom,
"anchor=({x},{y}) -> {p:?}"
);
}
}
}
#[test]
fn safe_zone_tip_to_upper_right() {
let range = rect(100, 100, 200, 150);
let tip = Some(rect(300, 20, 400, 60));
assert!(in_safe_zone(pt(150, 125), range, tip), "範囲の中");
assert!(in_safe_zone(pt(350, 40), range, tip), "ツールチップの中");
assert!(in_safe_zone(pt(250, 80), range, tip), "通り道の中");
assert!(!in_safe_zone(pt(250, 30), range, tip), "通り道の上");
assert!(!in_safe_zone(pt(100, 20), range, tip), "左上の角");
assert!(!in_safe_zone(pt(399, 149), range, tip), "右下の角");
assert!(!in_safe_zone(pt(500, 500), range, tip), "遠く");
}
#[test]
fn safe_zone_tip_to_lower_left() {
let range = rect(100, 100, 200, 150);
let tip = Some(rect(0, 200, 80, 240));
assert!(in_safe_zone(pt(90, 170), range, tip), "通り道の中");
assert!(in_safe_zone(pt(40, 220), range, tip), "ツールチップの中");
assert!(!in_safe_zone(pt(0, 100), range, tip), "左上の角");
assert!(!in_safe_zone(pt(199, 239), range, tip), "右下の角");
}
#[test]
fn safe_zone_tip_to_lower_right() {
let range = rect(100, 100, 200, 150);
let tip = Some(rect(300, 200, 400, 240));
assert!(in_safe_zone(pt(250, 175), range, tip), "通り道の中");
assert!(!in_safe_zone(pt(399, 100), range, tip), "右上の角");
assert!(!in_safe_zone(pt(100, 239), range, tip), "左下の角");
}
#[test]
fn safe_zone_without_tip_is_range_only() {
let range = rect(100, 100, 200, 150);
assert!(in_safe_zone(pt(100, 100), range, None));
assert!(in_safe_zone(pt(199, 149), range, None));
assert!(!in_safe_zone(pt(200, 125), range, None));
assert!(!in_safe_zone(pt(150, 150), range, None));
assert!(!in_safe_zone(pt(250, 80), range, None), "通り道は無い");
}
#[test]
fn force_break_splits_by_fitting_count() {
let fit = |_: &str| 5;
assert_eq!(force_break("abcdefghijkl", &fit), "abcde\r\nfghij\r\nkl");
assert_eq!(force_break("abc", &fit), "abc");
}
#[test]
fn force_break_asks_fit_from_each_chunk_start() {
let fit = |s: &str| {
let mut width = 0;
s.chars()
.take_while(|c| {
width += if c.is_ascii() { 1 } else { 2 };
width <= 6
})
.count()
};
assert_eq!(
force_break("abあいうcdefgh", &fit),
"abあい\r\nうcdef\r\ngh"
);
}
#[test]
fn force_break_keeps_existing_newlines() {
let fit = |_: &str| 5;
assert_eq!(
force_break("abcdefg\r\nxy\r\n\r\n1234567\r\n", &fit),
"abcde\r\nfg\r\nxy\r\n\r\n12345\r\n67\r\n"
);
assert_eq!(
force_break("abcdefg\rxy\nabcdefg", &fit),
"abcde\r\nfg\rxy\nabcde\r\nfg"
);
}
#[test]
fn force_break_does_not_cut_multibyte_chars() {
let fit = |_: &str| 4;
assert_eq!(
force_break("あいうえおかきくけこさ", &fit),
"あいうえ\r\nおかきく\r\nけこさ"
);
assert_eq!(
force_break("😀😀😀😀😀", &|_: &str| 2),
"😀😀\r\n😀😀\r\n😀"
);
}
#[test]
fn force_break_zero_fit_still_advances() {
assert_eq!(force_break("abc", &|_: &str| 0), "a\r\nb\r\nc");
}
#[test]
fn normalize_newlines_to_crlf() {
assert_eq!(normalize_newlines("a\nb\r\nc\rd"), "a\r\nb\r\nc\r\nd");
assert_eq!(normalize_newlines("\r\r\n"), "\r\n\r\n");
assert_eq!(normalize_newlines("abc"), "abc");
}
#[test]
fn max_tip_width_scales_with_dpi_and_caps_at_work_area() {
assert_eq!(max_tip_width(96, FHD), 320);
assert_eq!(max_tip_width(144, FHD), 480);
assert_eq!(max_tip_width(96, rect(0, 0, 300, 1080)), 300);
assert_eq!(max_tip_width(144, rect(-1920, 0, -1500, 1080)), 420);
}
#[test]
fn logical_max_width_divides_back_without_exceeding_physical() {
assert_eq!(logical_max_width(640, 192), 320);
assert_eq!(logical_max_width(480, 144), 320);
assert_eq!(logical_max_width(320, 96), 320);
assert_eq!(logical_max_width(420, 144), 280);
assert_eq!(logical_max_width(421, 144), 280);
for (physical, dpi) in [(421, 144), (999, 120), (1001, 168), (333, 240)] {
let back = (i64::from(logical_max_width(physical, dpi)) * i64::from(dpi) + 48) / 96;
assert!(
back <= i64::from(physical),
"{physical}@{dpi}: 掛け戻して {back}"
);
}
}