use gpui::{Modifiers, TestAppContext, VisualTestContext, div, point, prelude::*, px, size};
use ui::popover::{self, Popup};
const CARD: &str = "trigger-card";
const TRIGGER: (f32, f32, f32, f32) = (20.0, 20.0, 60.0, 24.0);
const CARD_AT: (f32, f32) = (20.0, 60.0);
struct Toggling {
menu: Popup<()>,
dismisses: bool,
}
impl gpui::Render for Toggling {
fn render(&mut self, _: &mut gpui::Window, cx: &mut gpui::Context<Self>) -> impl IntoElement {
let theme = theme::Theme::of(cx).clone();
let (x, y, w, h) = TRIGGER;
let trigger = popover::menu_trigger(
div()
.id("trigger")
.absolute()
.left(px(x))
.top(px(y))
.w(px(w))
.h(px(h)),
|this: &mut Self| &mut this.menu,
|_| (),
cx,
);
let card = popover::popover_card(&theme)
.debug_selector(|| CARD.to_string())
.w(px(120.0))
.h(px(80.0));
let card = if self.dismisses {
popover::dismiss_on_out(card, |this: &mut Self| &mut this.menu, cx)
} else {
card
};
div()
.size_full()
.relative()
.child(trigger)
.children(self.menu.get().map(|()| {
popover::menu_at(
"trigger-menu",
point(px(CARD_AT.0), px(CARD_AT.1)),
card.into_any_element(),
self.menu.closing_since(),
)
}))
}
}
fn open(dismisses: bool, cx: &mut TestAppContext) -> VisualTestContext {
cx.update(|cx| theme::Theme::install(theme::Appearance::Dark, cx));
let window = cx.add_window(|_, _| Toggling {
menu: Popup::default(),
dismisses,
});
let visual = VisualTestContext::from_window(window.into(), cx);
visual.simulate_resize(size(px(400.0), px(300.0)));
visual.run_until_parked();
visual
}
fn click_trigger(cx: &mut VisualTestContext) {
let (x, y, w, h) = TRIGGER;
cx.simulate_click(
point(px(x + w / 2.0), px(y + h / 2.0)),
Modifiers::default(),
);
cx.executor()
.advance_clock(std::time::Duration::from_secs(1));
cx.run_until_parked();
}
fn card_up(cx: &mut VisualTestContext) -> bool {
cx.debug_bounds(CARD).is_some()
}
#[gpui::test]
fn a_second_click_on_the_trigger_leaves_the_menu_shut(cx: &mut TestAppContext) {
let mut cx = open(true, cx);
click_trigger(&mut cx);
assert!(card_up(&mut cx), "the first click opens it");
click_trigger(&mut cx);
assert!(
!card_up(&mut cx),
"the card's out-press shut it; the release must not reopen it"
);
click_trigger(&mut cx);
assert!(
card_up(&mut cx),
"and it is a toggle, not a one-way latch — the next click opens it again"
);
}
#[gpui::test]
fn a_card_that_does_not_dismiss_itself_still_toggles(cx: &mut TestAppContext) {
let mut cx = open(false, cx);
click_trigger(&mut cx);
assert!(card_up(&mut cx), "the first click opens it");
click_trigger(&mut cx);
assert!(
!card_up(&mut cx),
"nothing closed it on the press, so the release has to"
);
}
#[gpui::test]
fn an_old_reaper_cannot_finish_a_new_close(cx: &mut TestAppContext) {
use std::time::Duration;
cx.update(|cx| theme::Theme::install(theme::Appearance::Dark, cx));
let window = cx.add_window(|_, _| Toggling {
menu: Popup::default(),
dismisses: false,
});
let entity = window.root(cx).unwrap();
let mut visual = VisualTestContext::from_window(window.into(), cx);
let duration =
motion::MENU_OUT.total().mul_f32(motion::speed_scale()) + Duration::from_millis(20);
let half = duration / 2;
visual.update(|_, cx| {
entity.update(cx, |view, cx| {
view.menu.open(());
popover::close_popup(view, cx, |view| &mut view.menu);
})
});
visual.run_until_parked();
visual.executor().advance_clock(half);
visual.update(|_, cx| {
entity.update(cx, |view, cx| {
view.menu.open(());
popover::close_popup(view, cx, |view| &mut view.menu);
})
});
visual.run_until_parked();
visual.executor().advance_clock(duration - half);
visual.run_until_parked();
assert!(visual.update(|_, cx| entity.read(cx).menu.is_closing()));
visual.executor().advance_clock(half);
visual.run_until_parked();
assert!(visual.update(|_, cx| entity.read(cx).menu.get().is_none()));
}