use super::*;
use crate::app::App;
use crate::app_runtime::LocalApp;
use platform_headless::HeadlessWindow;
struct Unchanging;
impl App for Unchanging {
fn root(&self) -> Box<dyn ui_tree::Component> {
ui_core::reset_layout_runtime();
Box::new(
ui_core::Rectangle::new(
layout_core::LayoutStyle::new().width(10.0).height(10.0),
|| renderer_core::RectStyle::filled(renderer_core::Color::BLACK, 0.0),
)
.expect("a rectangle builds"),
)
}
}
fn handler() -> AppHandler<HeadlessWindow, ()> {
build_app_handler::<HeadlessWindow, ()>(
Box::new(LocalApp(Unchanging)),
Arc::new(services_core::NoPaths),
crate::runner::font_config::FontSetup::default(),
RendererBackend::Software,
UserPrefs::default(),
"generation-test".to_string(),
SurfaceRenderer::builtin(),
)
}
#[test]
fn the_gpu_is_held_awake_for_whoever_is_there_to_notice() {
let mut handler = handler();
handler.pacer.renderer_keepalive = true;
assert!(
handler.keepalive_due(),
"a focused window keeps the GPU warm however long it has been still"
);
handler.pacer.focused = false;
assert!(
!handler.keepalive_due(),
"a window nobody is looking at will not be typed into"
);
handler.pacer.focused = true;
handler.pacer.last_input = web_time::Instant::now() - IDLE_GRACE - FRAME_BUDGET;
assert!(
!handler.keepalive_due(),
"a window left focused and abandoned sleeps on the backstop"
);
}
#[test]
fn the_rasteriser_never_asks_for_an_idle_frame() {
let mut handler = handler();
handler.pacer.renderer_keepalive = false;
assert!(
!handler.keepalive_due(),
"a rasterising run draws on demand, never on a timer"
);
}
struct Tinted(reactive_core::RwSignal<f32>);
impl App for Tinted {
fn root(&self) -> Box<dyn ui_tree::Component> {
ui_core::reset_layout_runtime();
let tint = self.0;
Box::new(
ui_core::Rectangle::new(
layout_core::LayoutStyle::new().width(10.0).height(10.0),
move || {
renderer_core::RectStyle::filled(
renderer_core::Color::rgba(tint.get(), 0.0, 0.0, 1.0),
0.0,
)
},
)
.expect("a rectangle builds"),
)
}
}
#[test]
fn new_commands_never_go_out_under_the_previous_generation() {
let tint = reactive_core::signal(0.0f32);
let mut handler = build_app_handler::<HeadlessWindow, ()>(
Box::new(LocalApp(Tinted(tint))),
Arc::new(services_core::NoPaths),
crate::runner::font_config::FontSetup::default(),
RendererBackend::Software,
UserPrefs::default(),
"generation-test".to_string(),
SurfaceRenderer::builtin(),
);
handler.tree = Some(handler.app.mount());
let first = handler.frame_generation();
assert_eq!(
handler.frame_generation(),
first,
"a frame nothing changed for must keep the number, or the renderer redraws for nothing"
);
tint.set(0.5);
assert_ne!(
handler.frame_generation(),
first,
"the changed frame reused the previous number, so the renderer re-presents the old one"
);
}
#[test]
fn a_remounted_tree_never_reuses_a_generation_the_renderer_has_drawn() {
let mut handler = handler();
let window = HeadlessWindow::new(120, 80);
handler.tree = Some(handler.app.mount());
let first = handler.frame_generation();
handler.remount(&window);
let composed = handler.tree.as_ref().map(|t| t.generation()).unwrap_or(0);
let second = handler.frame_generation();
assert!(
second > first,
"a rebuilt tree reported generation {second} after {first} was already drawn, so the renderer \
would blit the frame from before the rebuild"
);
assert!(
composed <= first,
"this test proves nothing unless the new tree's own counter really is back in drawn territory: \
it reported {composed} against {first}"
);
assert_eq!(
handler.frame_generation(),
second,
"an unchanged tree must keep reporting the same generation, or every idle frame re-renders"
);
}
#[test]
fn a_continuous_region_moves_the_generation_though_its_commands_never_change() {
let mut handler = handler();
let window = HeadlessWindow::new(120, 80);
handler.tree = Some(handler.app.mount());
let at_rest = handler.frame_generation();
assert_eq!(
handler.frame_generation(),
at_rest,
"this app's commands are fixed, so without a region nothing should move"
);
let region = motion_core::Continuous::new();
let first = handler.frame_generation();
let second = handler.frame_generation();
assert!(
first > at_rest && second > first,
"the generation stalled at {at_rest}/{first}/{second}, so the renderer would blit a stale frame"
);
drop(region);
let after = handler.frame_generation();
assert_eq!(
handler.frame_generation(),
after,
"with the region gone the surface must go back to skipping idle frames"
);
let _ = window;
}
#[test]
fn a_clean_tree_with_a_continuous_region_is_still_worth_a_frame() {
let mut handler = handler();
let window = HeadlessWindow::new(120, 80);
assert!(handler.on_resume(&window), "a headless resume builds one");
begin_batch();
let opened = || web_time::Instant::now() - FRAME_BUDGET * 2;
handler.pacer.last_tick = opened();
handler.on_redraw(&window);
let first = handler.pacer.last_submit;
handler.pacer.last_tick = opened();
handler.on_redraw(&window);
assert_eq!(
handler.pacer.last_submit, first,
"a clean tree with nothing else to say must not submit a frame"
);
let _awake = motion_core::Continuous::new();
handler.pacer.last_tick = opened();
handler.on_redraw(&window);
assert!(
handler.pacer.last_submit > first,
"the region says the picture changed even though the tree cannot, so this frame had to go out"
);
}
#[test]
fn the_runner_feeds_the_keyboard_registry() {
ui_core::reset_keyboard();
let mut handler = handler();
let window = HeadlessWindow::new(120, 80);
assert!(
!ui_core::modifiers().is_shift,
"shift is not down before the event"
);
handler.on_event(
Event::ModifiersChanged {
modifiers: platform_core::ModifiersState {
is_shift: true,
..Default::default()
},
},
&window,
);
assert!(
ui_core::modifiers().is_shift,
"a bare Shift must reach the registry, since it maps to no Key at all"
);
handler.on_event(
Event::KeyPressed {
key: platform_core::Key::Named(platform_core::NamedKey::ArrowUp),
modifiers: Default::default(),
},
&window,
);
let up = platform_core::Key::Named(platform_core::NamedKey::ArrowUp);
assert!(
ui_core::key_held(&up),
"the runner handed the press to the keyboard registry"
);
assert!(
ui_core::key_pressed(&up),
"and it reads as pressed this frame"
);
}
#[test]
fn the_runner_feeds_the_pointer_button_registry() {
ui_core::reset_pointer();
let mut handler = handler();
let window = HeadlessWindow::new(120, 80);
assert!(
!ui_core::pointer_buttons().any(),
"no button is down before the event"
);
handler.on_event(
Event::PointerPressed {
x: 10.0,
y: 10.0,
button: platform_core::PointerButton::Secondary,
source: platform_core::PointerSource::Mouse,
},
&window,
);
assert!(
ui_core::pointer_buttons().secondary,
"the runner recorded which button went down"
);
handler.on_event(
Event::PointerReleased {
x: 10.0,
y: 10.0,
button: platform_core::PointerButton::Secondary,
source: platform_core::PointerSource::Mouse,
},
&window,
);
assert!(
!ui_core::pointer_buttons().any(),
"and the release cleared it"
);
}