openpencil/crates/op-host-native/tests/resize_smoke.rs
Kayshen-X 1e0b3cab7a refactor(rust): rename native + desktop hosts to op-host-* crates
Phase 7.3 strangler reorg — rename the native widget host and the
desktop runner crates to the op- prefix. The desktop+native merge was
declined: keeping the library / binary split preserves the mobile-
checkable op-host-native lib (cargo check -p op-host-native on iOS /
Android, relied on by check-jian-boundaries.sh + the CI mobile job),
which a folded-in winit binary would break. A clean separate rename is
purely mechanical and the brief permits it.

- openpencil-shell-native -> op-host-native (lib op_host_native)
- openpencil-desktop -> op-host-desktop crate; the shipped executable
  keeps the stable openpencil-desktop [[bin]] name so release
  artifacts + external CLI integrations are unaffected
- every openpencil_shell_core:: path -> op_editor_ui::
- every openpencil_shell_native:: path -> op_host_native::
- doc-comment / manual-smoke note refs updated
2026-05-16 23:49:58 +08:00

126 lines
4.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Spec v19 acceptance #5 (resize / DPI no-flicker) / Phase A Gate
//! round 2 CONCERN 2.
//!
//! Round 1 only exercised the `resize` tracing span on an inert
//! context — that proves the span fires but doesn't pin the
//! invariant the acceptance is actually about (after a resize, a
//! draw issued through `NativeBackend` must land on a surface of
//! the new dimensions without panic). This test fills that gap
//! using a raster Skia surface as a stand-in for the GL FBO that
//! `SharedSkiaContext::resize` rebuilds — `cargo test` cannot drive
//! a real GL surface from a worker thread, but the
//! `NativeBackend::fill_rect` translation layer is identical
//! whether the canvas is raster- or GPU-backed.
//!
//! Two passes:
//! 1. Grow 400×300 → 800×600, paint at centre, assert red.
//! 2. Shrink 800×600 → 400×300, paint at centre, assert red.
//!
//! Plus: a tracing-span pin on `SharedSkiaContext::resize` itself
//! (already covered structurally by `tracing_spans.rs`, repeated
//! here at the new size cadence to lock the resize-grow / resize-
//! shrink invariant on round-2's stricter spec).
//!
//! Windows is `#[ignore]`d at the test level — CI runners ship
//! without a usable GL stack, the raster path is identical across
//! macOS / Linux, and CONCERN 2 only requires non-Windows coverage.
use op_editor_ui::{Color, Point2D, Rect};
use op_host_native::{NativeBackend, SharedSkiaContext};
use tracing_test::traced_test;
/// Helper: build a raster Skia surface of `(w, h)`, paint a
/// `NativeBackend::fill_rect` covering most of it, return the
/// resulting pixel at the surface centre. Mirrors the chain
/// `SharedSkiaContext::resize` → `with_frame` →
/// `NativeBackend::fill_rect` minus the GL stack.
fn paint_at_size(backend: &mut NativeBackend, w: i32, h: i32) -> [u8; 4] {
let mut surface = skia_safe::surfaces::raster_n32_premul((w, h))
.expect("raster_n32_premul allocated for resize test");
surface.canvas().clear(skia_safe::Color::WHITE);
backend.fill_rect(
surface.canvas(),
Rect {
origin: Point2D::new(10.0, 10.0),
size: Point2D::new((w - 20) as f32, (h - 20) as f32),
},
Color::RED,
);
let stride = (w * 4) as usize;
let mut pixels = vec![0u8; stride * (h as usize)];
let info = skia_safe::ImageInfo::new(
(w, h),
skia_safe::ColorType::RGBA8888,
skia_safe::AlphaType::Premul,
None,
);
let read_ok = surface.read_pixels(&info, &mut pixels, stride, (0, 0));
assert!(
read_ok,
"raster Surface::read_pixels must succeed at {}×{}",
w, h
);
let cx = (w / 2) as usize;
let cy = (h / 2) as usize;
let offset = cy * stride + cx * 4;
[
pixels[offset],
pixels[offset + 1],
pixels[offset + 2],
pixels[offset + 3],
]
}
#[cfg_attr(target_os = "windows", ignore)]
#[test]
fn resize_grow_then_shrink_paints_through() {
let mut backend = NativeBackend::with_dpi(1.0);
// Initial 400×300.
let small_pixel = paint_at_size(&mut backend, 400, 300);
assert!(
small_pixel[0] > 200 && small_pixel[1] < 60 && small_pixel[2] < 60,
"400×300 centre expected red, got {:?}",
small_pixel
);
// Grow to 800×600 — same `NativeBackend` instance must paint
// through to a fresh-larger surface (matches the realistic
// resize path: window expanded, `SharedSkiaContext::resize`
// rebuilt the Skia surface, next frame's `fill_rect` lands on
// it).
let grown_pixel = paint_at_size(&mut backend, 800, 600);
assert!(
grown_pixel[0] > 200 && grown_pixel[1] < 60 && grown_pixel[2] < 60,
"800×600 centre expected red, got {:?}",
grown_pixel
);
// Shrink back to 400×300 — same invariant, opposite direction.
let shrunk_pixel = paint_at_size(&mut backend, 400, 300);
assert!(
shrunk_pixel[0] > 200 && shrunk_pixel[1] < 60 && shrunk_pixel[2] < 60,
"shrunk 400×300 centre expected red, got {:?}",
shrunk_pixel
);
}
/// Pin the `resize` span emission on a `SharedSkiaContext` whose
/// fields are at the post-teardown shape (surface = None, dc =
/// None). Complements `tracing_spans.rs` by exercising both grow
/// (800×600) and shrink (400×300) paths plus the 0×0 no-op clamp.
#[cfg_attr(target_os = "windows", ignore)]
#[traced_test]
#[test]
fn resize_emits_span_grow_and_shrink() {
let mut ctx = SharedSkiaContext::inert_for_lifecycle_test();
ctx.resize(800, 600).expect("resize 800x600");
ctx.resize(400, 300).expect("resize 400x300");
ctx.resize(0, 0).expect("resize 0x0 no-op clamp");
assert!(logs_contain("resize"), "resize span missing");
}