use crate::harness;
use std::time::Duration;
use gpui::{
prelude::*, px, Font, FontFeatures, FontStyle, FontWeight, TestAppContext, VisualTestContext,
};
use herogpui_components::{Alert, Avatar, Badge, BadgeAnchor, BadgeLabel, Button};
use harness::{click, events, open_host, press};
fn flush_frame(cx: &mut VisualTestContext) {
cx.update(|window, _| window.refresh());
}
fn text_width(system: &gpui::WindowTextSystem, text: &str) -> f32 {
let run = gpui::TextRun {
len: text.len(),
font: Font {
family: ".SystemUIFont".into(),
features: FontFeatures::default(),
weight: FontWeight::MEDIUM,
style: FontStyle::default(),
fallbacks: None,
},
color: gpui::black(),
background_color: None,
underline: None,
strikethrough: None,
};
let line = system.shape_line(text.to_owned().into(), px(14.), &[run], None);
f32::from(line.width)
}
#[gpui::test]
fn avatar_src_failure_fires_on_error_once(cx: &mut TestAppContext) {
let errors = events();
let recorded = errors.clone();
let cx = open_host(cx, move || {
let errors = errors.clone();
Avatar::new("src-failure")
.name("Jane Doe")
.src("images/avatar-broken.png")
.on_error(move |_, _| errors.borrow_mut().push("error".into()))
.into_any_element()
});
cx.executor().advance_clock(Duration::from_millis(1));
flush_frame(cx);
flush_frame(cx);
cx.executor().advance_clock(Duration::from_millis(1));
assert_eq!(
recorded.borrow().as_slice(),
["error"],
"a failing src must fire on_error once when the load errors"
);
flush_frame(cx);
cx.executor().advance_clock(Duration::from_millis(300));
flush_frame(cx);
assert_eq!(
recorded.borrow().as_slice(),
["error"],
"on_error must fire exactly once, not once per frame"
);
}
#[gpui::test]
fn avatar_delay_ms_holds_the_fallback_until_the_window_elapses(cx: &mut TestAppContext) {
let errors = events();
let recorded = errors.clone();
let cx = open_host(cx, move || {
let errors = errors.clone();
Avatar::new("delay")
.name("NA")
.src("images/avatar-broken.png")
.delay_ms(600)
.on_error(move |_, _| errors.borrow_mut().push("error".into()))
.into_any_element()
});
cx.executor().advance_clock(Duration::from_millis(1));
flush_frame(cx);
flush_frame(cx);
cx.executor().advance_clock(Duration::from_millis(1));
assert_eq!(
recorded.borrow().as_slice(),
["error"],
"the error must be reported as soon as the load fails, before the delay elapses"
);
cx.executor().advance_clock(Duration::from_millis(400));
flush_frame(cx);
assert_eq!(
recorded.borrow().as_slice(),
["error"],
"advancing within the delay window must not re-fire on_error"
);
cx.executor().advance_clock(Duration::from_millis(200));
flush_frame(cx);
flush_frame(cx);
assert_eq!(
recorded.borrow().as_slice(),
["error"],
"the ready frame and everything after it must not re-fire on_error"
);
}
#[gpui::test]
fn alert_composed_button_reports_and_takes_focus(cx: &mut TestAppContext) {
let presses = events();
let recorded = presses.clone();
let cx = open_host(cx, move || {
let presses = presses.clone();
Alert::new("You have no credits left")
.description("Upgrade to a paid plan to continue")
.child(
Button::new("alert-upgrade")
.label("Upgrade")
.on_press(move |_, _, _| presses.borrow_mut().push("upgrade".into())),
)
.into_any_element()
});
let w = cx.update(|window, _| text_width(window.text_system(), "Upgrade"));
let centre_x = 1904. - 16. - w / 2.;
press(cx, "tab");
flush_frame(cx);
press(cx, "space");
assert_eq!(
recorded.borrow().as_slice(),
["upgrade"],
"a Button composed into an Alert must be reachable and activatable by keyboard"
);
click(cx, centre_x, 30.);
assert_eq!(
recorded.borrow().as_slice(),
["upgrade", "upgrade"],
"a Button composed into an Alert must report its press"
);
click(cx, 500., 30.);
assert_eq!(
recorded.borrow().as_slice(),
["upgrade", "upgrade"],
"the alert body itself must not report a press"
);
}
#[gpui::test]
fn badge_default_color_stays_behind_the_anchor_and_is_inert(cx: &mut TestAppContext) {
let recorded = events();
let cx = open_host(cx, move || {
gpui::div()
.flex()
.child(
BadgeAnchor::new()
.child(gpui::div().w(px(64.)).h(px(64.)).child("anchor"))
.child(Badge::new().child(BadgeLabel::new().child("5"))),
)
.into_any_element()
});
click(cx, 57., 7.);
click(cx, 32., 32.);
assert!(
recorded.borrow().is_empty(),
"a default badge and its anchor must record nothing when clicked"
);
}