use super::*;
use crate::ecs::layout::{BoxSize, BoxStyle, Dimension};
use crate::ecs::window::DPI;
use crate::ecs::{Point, SizeI};
#[test]
fn test_center_correction_size_decrease() {
let old = SizeI {
width: 800,
height: 600,
};
let new = SizeI {
width: 500,
height: 375,
};
let (dx, dy) = calculate_center_correction(old, new);
assert_eq!(dx, 150);
assert_eq!(dy, 112); }
#[test]
fn test_center_correction_size_increase() {
let old = SizeI {
width: 500,
height: 375,
};
let new = SizeI {
width: 800,
height: 600,
};
let (dx, dy) = calculate_center_correction(old, new);
assert_eq!(dx, -150);
assert_eq!(dy, -112);
}
#[test]
fn test_center_correction_same_size() {
let size = SizeI {
width: 600,
height: 400,
};
let (dx, dy) = calculate_center_correction(size, size);
assert_eq!(dx, 0);
assert_eq!(dy, 0);
}
#[test]
fn test_center_correction_preserves_center() {
let old_pos = Point { x: 100, y: 200 };
let old_size = SizeI {
width: 800,
height: 600,
};
let new_size = SizeI {
width: 500,
height: 375,
};
let (dx, dy) = calculate_center_correction(old_size, new_size);
let new_pos = Point {
x: old_pos.x + dx,
y: old_pos.y + dy,
};
let old_center_x = old_pos.x + old_size.width / 2;
let old_center_y = old_pos.y + old_size.height / 2;
let new_center_x = new_pos.x + new_size.width / 2;
let new_center_y = new_pos.y + new_size.height / 2;
assert_eq!(old_center_x, new_center_x);
assert!(
(old_center_y - new_center_y).abs() <= 1,
"center Y drift exceeds 1px: old={}, new={}",
old_center_y,
new_center_y
);
}
#[test]
fn test_physical_size_from_box_style_125pct() {
let bs = BoxStyle {
size: Some(BoxSize {
width: Some(Dimension::Px(400.0)),
height: Some(Dimension::Px(300.0)),
}),
..Default::default()
};
let dpi = DPI::from_dpi(120, 120);
let result = calculate_physical_size_from_box_style(&bs, &dpi).unwrap();
assert_eq!(result.width, 500);
assert_eq!(result.height, 375);
}
#[test]
fn test_physical_size_from_box_style_200pct() {
let bs = BoxStyle {
size: Some(BoxSize {
width: Some(Dimension::Px(400.0)),
height: Some(Dimension::Px(300.0)),
}),
..Default::default()
};
let dpi = DPI::from_dpi(192, 192);
let result = calculate_physical_size_from_box_style(&bs, &dpi).unwrap();
assert_eq!(result.width, 800);
assert_eq!(result.height, 600);
}
#[test]
fn test_physical_size_from_box_style_none() {
let bs = BoxStyle {
size: None,
..Default::default()
};
let dpi = DPI::from_dpi(120, 120);
assert!(calculate_physical_size_from_box_style(&bs, &dpi).is_none());
}
#[test]
fn test_physical_size_from_box_style_ceiling() {
let bs = BoxStyle {
size: Some(BoxSize {
width: Some(Dimension::Px(333.0)),
height: Some(Dimension::Px(250.0)),
}),
..Default::default()
};
let dpi = DPI::from_dpi(144, 144); let result = calculate_physical_size_from_box_style(&bs, &dpi).unwrap();
assert_eq!(result.width, (333.0_f32 * 1.5).ceil() as i32); assert_eq!(result.height, (250.0_f32 * 1.5).ceil() as i32); }
#[test]
fn test_physical_size_from_box_style_non_px() {
let bs = BoxStyle {
size: Some(BoxSize {
width: Some(Dimension::Percent(100.0)),
height: Some(Dimension::Px(300.0)),
}),
..Default::default()
};
let dpi = DPI::from_dpi(120, 120);
assert!(calculate_physical_size_from_box_style(&bs, &dpi).is_none());
}
use crate::ecs::window::DpiChangeContext;
fn make_box_style_px(w: f32, h: f32) -> BoxStyle {
BoxStyle {
size: Some(BoxSize {
width: Some(Dimension::Px(w)),
height: Some(Dimension::Px(h)),
}),
..Default::default()
}
}
fn make_dpi_context(dpi: DPI) -> DpiChangeContext {
DpiChangeContext::new(dpi, RECT::default())
}
#[test]
fn test_correct_position_returns_input_when_dpi_context_none() {
let client_pos = Point { x: 100, y: 200 };
let client_size = SizeI {
width: 800,
height: 600,
};
let bs = make_box_style_px(400.0, 300.0);
let dpi = DPI::from_dpi(120, 120);
let result =
correct_position_for_dpi_center_preserve(client_pos, client_size, &None, Some(&bs), &dpi);
assert_eq!(result.x, 100);
assert_eq!(result.y, 200);
}
#[test]
fn test_correct_position_returns_input_when_box_style_none() {
let client_pos = Point { x: 50, y: 75 };
let client_size = SizeI {
width: 800,
height: 600,
};
let dpi = DPI::from_dpi(192, 192);
let ctx = Some(make_dpi_context(dpi));
let result =
correct_position_for_dpi_center_preserve(client_pos, client_size, &ctx, None, &dpi);
assert_eq!(result.x, 50);
assert_eq!(result.y, 75);
}
#[test]
fn test_correct_position_returns_input_when_size_not_px() {
let client_pos = Point { x: 10, y: 20 };
let client_size = SizeI {
width: 800,
height: 600,
};
let dpi = DPI::from_dpi(192, 192);
let ctx = Some(make_dpi_context(dpi));
let bs = BoxStyle {
size: Some(BoxSize {
width: Some(Dimension::Percent(100.0)),
height: Some(Dimension::Px(300.0)),
}),
..Default::default()
};
let result =
correct_position_for_dpi_center_preserve(client_pos, client_size, &ctx, Some(&bs), &dpi);
assert_eq!(result.x, 10);
assert_eq!(result.y, 20);
}
#[test]
fn test_correct_position_returns_input_when_correction_is_zero() {
let client_pos = Point { x: 100, y: 200 };
let client_size = SizeI {
width: 800,
height: 600,
};
let dpi = DPI::from_dpi(192, 192);
let ctx = Some(make_dpi_context(dpi));
let bs = make_box_style_px(400.0, 300.0);
let result =
correct_position_for_dpi_center_preserve(client_pos, client_size, &ctx, Some(&bs), &dpi);
assert_eq!(result.x, 100);
assert_eq!(result.y, 200);
}
#[test]
fn test_correct_position_applies_center_preserving_correction() {
let client_pos = Point { x: 100, y: 200 };
let client_size = SizeI {
width: 800,
height: 600,
};
let dpi = DPI::from_dpi(120, 120);
let ctx = Some(make_dpi_context(dpi));
let bs = make_box_style_px(400.0, 300.0);
let result =
correct_position_for_dpi_center_preserve(client_pos, client_size, &ctx, Some(&bs), &dpi);
assert_eq!(result.x, 250);
assert_eq!(result.y, 312);
let old_center_x = client_pos.x + client_size.width / 2;
let new_center_x = result.x + 500 / 2;
assert_eq!(old_center_x, new_center_x);
}
use log_capture_kit::{LineFormat, capture_lines};
const BOX_STYLE_NOT_FOUND_TAG: &str =
"[WM_WINDOWPOSCHANGED] DPI center correction skipped: BoxStyle not found";
fn capture_logs<F: FnOnce()>(f: F) -> Vec<String> {
let ((), lines) = capture_lines(LineFormat::LevelFields, f);
lines
}
#[test]
fn box_style_not_found_fallback_keeps_its_literal_and_warn_level() {
let client_pos = Point { x: 50, y: 75 };
let client_size = SizeI {
width: 800,
height: 600,
};
for dpi_value in [96u16, 120, 192] {
let dpi = DPI::from_dpi(dpi_value, dpi_value);
let ctx = Some(make_dpi_context(dpi));
let lines = capture_logs(|| {
let out =
correct_position_for_dpi_center_preserve(client_pos, client_size, &ctx, None, &dpi);
assert_eq!(
(out.x, out.y),
(client_pos.x, client_pos.y),
"dpi={dpi_value}: BoxStyle 欠落は client_pos 素通し(フォールバック)"
);
});
let hits: Vec<&String> = lines
.iter()
.filter(|l| l.contains(BOX_STYLE_NOT_FOUND_TAG))
.collect();
assert_eq!(
hits.len(),
1,
"dpi={dpi_value}: 判定語 `{BOX_STYLE_NOT_FOUND_TAG}` がちょうど 1 行でない: {lines:?}"
);
assert!(
hits[0].starts_with("level=WARN"),
"dpi={dpi_value}: 判定語の水準が WARN でない\
(手順書 §6.5 の `boxstyle_warn` は warn として計数される): {}",
hits[0]
);
let bs = make_box_style_px(400.0, 300.0);
let lines = capture_logs(|| {
correct_position_for_dpi_center_preserve(
client_pos,
client_size,
&ctx,
Some(&bs),
&dpi,
);
});
assert!(
!lines.iter().any(|l| l.contains(BOX_STYLE_NOT_FOUND_TAG)),
"dpi={dpi_value}: BoxStyle があるのに判定語が出ている: {lines:?}"
);
assert!(
!lines.is_empty(),
"dpi={dpi_value}: 捕捉ハーネスが 1 行も拾っていない(陰性 assert が空虚)"
);
let lines = capture_logs(|| {
correct_position_for_dpi_center_preserve(client_pos, client_size, &None, None, &dpi);
});
assert!(
lines.is_empty(),
"dpi={dpi_value}: dpi_context 無しの早期 return が喋っている: {lines:?}"
);
}
}
fn make_suggested_rect(left: i32, top: i32, w: i32, h: i32) -> RECT {
RECT {
left,
top,
right: left + w,
bottom: top + h,
}
}
fn final_position(decision: Option<(i32, i32)>, current: (i32, i32)) -> (i32, i32) {
decision.unwrap_or(current)
}
#[test]
fn test_dpi_decision_absent_policy_applies_suggested_position() {
let suggested = make_suggested_rect(1920, 240, 400, 300);
let decision = dpi_suggested_position_decision(None, &suggested);
assert_eq!(decision, Some((1920, 240)));
}
#[test]
fn test_dpi_decision_apply_position_applies_suggested_position() {
let suggested = make_suggested_rect(1920, 240, 400, 300);
let decision =
dpi_suggested_position_decision(Some(&DpiSuggestedRectPolicy::ApplyPosition), &suggested);
assert_eq!(decision, Some((1920, 240)));
}
#[test]
fn test_dpi_decision_external_authority_does_not_write() {
let suggested = make_suggested_rect(1920, 240, 400, 300);
let decision = dpi_suggested_position_decision(
Some(&DpiSuggestedRectPolicy::ExternalAuthority),
&suggested,
);
assert_eq!(decision, None);
}
#[test]
fn test_dpi_decision_default_policy_equals_absent_policy() {
assert_eq!(
DpiSuggestedRectPolicy::default(),
DpiSuggestedRectPolicy::ApplyPosition
);
let suggested = make_suggested_rect(-1024, 96, 400, 300);
assert_eq!(
dpi_suggested_position_decision(Some(&DpiSuggestedRectPolicy::default()), &suggested),
dpi_suggested_position_decision(None, &suggested)
);
}
#[test]
fn test_dpi_decision_uses_rect_origin_not_extent() {
let small = make_suggested_rect(300, 400, 10, 10);
let large = make_suggested_rect(300, 400, 5000, 5000);
assert_eq!(
dpi_suggested_position_decision(None, &small),
Some((300, 400))
);
assert_eq!(
dpi_suggested_position_decision(None, &large),
dpi_suggested_position_decision(None, &small)
);
}
#[test]
fn test_dpi_decision_at_96_hides_the_branch_difference() {
let current = (1200, 400);
let ratio = 96.0_f32 / 96.0;
let suggested = make_suggested_rect(
(current.0 as f32 * ratio).round() as i32,
(current.1 as f32 * ratio).round() as i32,
400,
300,
);
for policy in [
None,
Some(&DpiSuggestedRectPolicy::ApplyPosition),
Some(&DpiSuggestedRectPolicy::ExternalAuthority),
] {
let final_pos =
final_position(dpi_suggested_position_decision(policy, &suggested), current);
assert_eq!(
final_pos, current,
"dpi=96 では policy={policy:?} でも現位置が保存されるはず"
);
}
}
#[test]
fn test_dpi_decision_at_120_and_192_separates_apply_from_external_authority() {
let current = (1200, 400);
for dpi in [120_u16, 192_u16] {
let ratio = dpi as f32 / 96.0;
let shifted_x = (current.0 as f32 * ratio).round() as i32;
let shifted_y = (current.1 as f32 * ratio).round() as i32;
let suggested = make_suggested_rect(shifted_x, shifted_y, 400, 300);
assert_ne!(
shifted_x, current.0,
"dpi={dpi} では提案 X が現位置から動いている前提"
);
for policy in [None, Some(&DpiSuggestedRectPolicy::ApplyPosition)] {
let final_pos =
final_position(dpi_suggested_position_decision(policy, &suggested), current);
assert_eq!(
final_pos,
(shifted_x, shifted_y),
"dpi={dpi} policy={policy:?}: 提案位置が最終位置になるはず"
);
assert_ne!(
final_pos.0, current.0,
"dpi={dpi} policy={policy:?}: 接地点 X が破壊されることを明示する"
);
}
let final_pos = final_position(
dpi_suggested_position_decision(
Some(&DpiSuggestedRectPolicy::ExternalAuthority),
&suggested,
),
current,
);
assert_eq!(
final_pos, current,
"dpi={dpi}: ExternalAuthority は現接地点を保存するはず"
);
}
}