use fenestra_core::{
App, Element, Fonts, Frame, FrameState, MAIN_WINDOW, Theme, build_frame, by, col, div,
responsive, row, text,
};
use fenestra_shell::Harness;
struct Tier1;
impl App for Tier1 {
type Msg = ();
fn update(&mut self, (): ()) {}
fn view(&self) -> Element<()> {
col()
}
fn view_at(&self, _key: &str, size: (f32, f32)) -> Element<()> {
let (w, _h) = size;
let kids = [div().id("a").w(40.0).h(40.0), div().id("b").w(40.0).h(40.0)];
if w >= 700.0 {
row().children(kids)
} else {
col().children(kids)
}
}
}
fn stacked_vertically(f: &Frame) -> bool {
let a = f.get(&by::id("a")).rect;
let b = f.get(&by::id("b")).rect;
b.y0 >= a.y1 - 0.5 && (a.x0 - b.x0).abs() < 0.5
}
fn side_by_side(f: &Frame) -> bool {
let a = f.get(&by::id("a")).rect;
let b = f.get(&by::id("b")).rect;
b.x0 >= a.x1 - 0.5 && (a.y0 - b.y0).abs() < 0.5
}
#[test]
fn view_at_switches_layout_on_window_resize() {
let mut h = Harness::new(Tier1, Theme::light(), (400, 600));
assert!(
stacked_vertically(h.frame()),
"narrow (400px) should lay out as a column:\n{}",
h.frame().access_yaml()
);
assert!(!side_by_side(h.frame()));
h.resize(MAIN_WINDOW, 900, 600);
assert!(
side_by_side(h.frame()),
"wide (900px) should lay out as a row:\n{}",
h.frame().access_yaml()
);
assert!(!stacked_vertically(h.frame()));
h.resize(MAIN_WINDOW, 500, 600);
assert!(stacked_vertically(h.frame()), "narrow again is a column");
}
fn container(width: f32) -> Element<()> {
div().w(width).h(300.0).child(
responsive(|(w, _h)| {
let kids = [div().id("x").w(40.0).h(40.0), div().id("y").w(40.0).h(40.0)];
if w >= 400.0 {
row().w_full().children(kids)
} else {
col().w_full().children(kids)
}
})
.id("resp"),
)
}
fn xy_column(f: &Frame) -> bool {
let x = f.get(&by::id("x")).rect;
let y = f.get(&by::id("y")).rect;
y.y0 >= x.y1 - 0.5 && (x.x0 - y.x0).abs() < 0.5
}
fn xy_row(f: &Frame) -> bool {
let x = f.get(&by::id("x")).rect;
let y = f.get(&by::id("y")).rect;
y.x0 >= x.x1 - 0.5 && (x.y0 - y.y0).abs() < 0.5
}
#[test]
fn responsive_container_query_converges_one_frame_after_resize() {
let theme = Theme::light();
let mut fonts = Fonts::embedded();
let mut state = FrameState::new();
let canvas = (800.0, 600.0);
let wide = container(500.0);
let f1 = build_frame(&wide, &theme, &mut fonts, &mut state, canvas, 1.0);
assert!(
xy_column(&f1),
"frame 1 uses the hint (0,0) → column branch:\n{}",
f1.access_yaml()
);
let f2 = build_frame(&wide, &theme, &mut fonts, &mut state, canvas, 1.0);
assert!(
xy_row(&f2),
"frame 2 reads last frame's 500px measurement → row branch:\n{}",
f2.access_yaml()
);
let f3 = build_frame(&wide, &theme, &mut fonts, &mut state, canvas, 1.0);
assert!(xy_row(&f3), "row branch is stable at a held width");
let narrow = container(300.0);
let f4 = build_frame(&narrow, &theme, &mut fonts, &mut state, canvas, 1.0);
assert!(
xy_row(&f4),
"frame after shrink still reads the stale 500px width → still a row"
);
let f5 = build_frame(&narrow, &theme, &mut fonts, &mut state, canvas, 1.0);
assert!(
xy_column(&f5),
"re-converges to the column branch one frame later:\n{}",
f5.access_yaml()
);
}
#[test]
fn responsive_hint_avoids_the_first_frame_flash() {
let theme = Theme::light();
let mut fonts = Fonts::embedded();
let mut state = FrameState::new();
let view: Element<()> = div().w(500.0).h(300.0).child(
fenestra_core::responsive_hinted((500.0, 300.0), |(w, _h)| {
let kids = [div().id("x").w(40.0).h(40.0), div().id("y").w(40.0).h(40.0)];
if w >= 400.0 {
row().w_full().children(kids)
} else {
col().w_full().children(kids)
}
})
.id("resp"),
);
let f1 = build_frame(&view, &theme, &mut fonts, &mut state, (800.0, 600.0), 1.0);
assert!(
xy_row(&f1),
"a (500,300) hint lands on the row branch on the very first frame:\n{}",
f1.access_yaml()
);
}
#[test]
fn responsive_self_wrapping_is_capped_not_a_stack_overflow() {
fn forever() -> Element<()> {
responsive(|_avail| forever())
}
let theme = Theme::light();
let mut fonts = Fonts::embedded();
let mut state = FrameState::new();
let view = div()
.w(500.0)
.h(300.0)
.children([div().id("sib").w(10.0).h(10.0), forever().id("loop")]);
let _f1 = build_frame(&view, &theme, &mut fonts, &mut state, (800.0, 600.0), 1.0);
let f2 = build_frame(&view, &theme, &mut fonts, &mut state, (800.0, 600.0), 1.0);
assert!(
f2.query(&by::id("sib")).is_some(),
"the self-wrapping responsive was capped and the frame built — no stack overflow"
);
}
struct DefaultsToView;
impl App for DefaultsToView {
type Msg = ();
fn update(&mut self, (): ()) {}
fn view(&self) -> Element<()> {
col().child(text("always"))
}
}
#[test]
fn view_at_defaults_to_view_at_every_size() {
let mut h = Harness::new(DefaultsToView, Theme::light(), (320, 240));
assert!(
h.query(&by::label("always")).is_some(),
"small window shows the view's content"
);
h.resize(MAIN_WINDOW, 1400, 900);
assert!(
h.query(&by::label("always")).is_some(),
"a 4x larger window shows exactly the same content (size ignored)"
);
}