The close affordance reuses Button (ghost, small) at the panel's top right and routes through on_change like escape and scrim clicks; it is hidden in alert mode. The dialog feature now depends on button, and AGENTS.md codifies preferring existing components over custom elements.
184 lines
6.5 KiB
Rust
184 lines
6.5 KiB
Rust
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
|
|
use gpui::{
|
|
Context, IntoElement, Modifiers, MouseButton, ParentElement as _, Render, Styled as _,
|
|
TestAppContext, VisualTestContext, Window, div, point, px,
|
|
};
|
|
use gpui_native_core::button::{Button, ButtonVariant};
|
|
use gpui_native_core::dialog::Dialog;
|
|
use gpui_native_theme::Theme;
|
|
|
|
struct Harness {
|
|
open: bool,
|
|
alert: bool,
|
|
changes: Rc<RefCell<Vec<bool>>>,
|
|
}
|
|
|
|
impl Render for Harness {
|
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
let changes = self.changes.clone();
|
|
let view = cx.entity();
|
|
let on_change_view = view.clone();
|
|
let close_changes = self.changes.clone();
|
|
let close_view = view.clone();
|
|
div().size_full().child(
|
|
Dialog::new("test-dialog")
|
|
.open(self.open)
|
|
.alert(self.alert)
|
|
.title("Delete file?")
|
|
.on_change(move |next, _, cx| {
|
|
eprintln!("HARNESS on_change: {next}");
|
|
changes.borrow_mut().push(next);
|
|
on_change_view.update(cx, |harness, cx| {
|
|
harness.open = next;
|
|
cx.notify();
|
|
});
|
|
})
|
|
.content(|_, _| {
|
|
div()
|
|
.w(px(300.))
|
|
.h(px(80.))
|
|
.child("This cannot be undone.")
|
|
.into_any_element()
|
|
})
|
|
.actions(move |_, _| {
|
|
vec![
|
|
Button::new("dialog-cancel")
|
|
.label("Cancel")
|
|
.variant(ButtonVariant::Secondary)
|
|
.on_click({
|
|
let close_changes = close_changes.clone();
|
|
let close_view = close_view.clone();
|
|
move |_, _, cx| {
|
|
close_changes.borrow_mut().push(false);
|
|
close_view.update(cx, |harness, cx| {
|
|
harness.open = false;
|
|
cx.notify();
|
|
});
|
|
}
|
|
})
|
|
.into_any_element(),
|
|
Button::new("dialog-delete")
|
|
.label("Delete")
|
|
.variant(ButtonVariant::Destructive)
|
|
.on_click({
|
|
let close_changes = close_changes.clone();
|
|
let close_view = close_view.clone();
|
|
move |_, _, cx| {
|
|
close_changes.borrow_mut().push(false);
|
|
close_view.update(cx, |harness, cx| {
|
|
harness.open = false;
|
|
cx.notify();
|
|
});
|
|
}
|
|
})
|
|
.into_any_element(),
|
|
]
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// An open 332px-wide dialog centered in the window: the panel spans
|
|
/// roughly `(794..1126, 448..640)`, so `(100, 100)` is on the scrim and
|
|
/// the window center is on the panel.
|
|
fn dialog_window(
|
|
open: bool,
|
|
alert: bool,
|
|
) -> (TestAppContext, VisualTestContext, Rc<RefCell<Vec<bool>>>) {
|
|
let mut cx = TestAppContext::single();
|
|
cx.update(Theme::init);
|
|
let changes = Rc::new(RefCell::new(Vec::new()));
|
|
let harness_changes = changes.clone();
|
|
let window = cx.add_window(move |_, _| Harness {
|
|
open,
|
|
alert,
|
|
changes: harness_changes,
|
|
});
|
|
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
|
visual.update(|window, cx| {
|
|
window.draw(cx).clear(cx);
|
|
});
|
|
(cx, visual, changes)
|
|
}
|
|
|
|
fn draw(visual: &mut VisualTestContext) {
|
|
visual.update(|window, cx| {
|
|
eprintln!("DRAW");
|
|
window.draw(cx).clear(cx);
|
|
});
|
|
}
|
|
|
|
fn click(visual: &mut VisualTestContext, position: gpui::Point<gpui::Pixels>) {
|
|
visual.simulate_mouse_down(position, MouseButton::Left, Modifiers::default());
|
|
visual.simulate_mouse_up(position, MouseButton::Left, Modifiers::default());
|
|
}
|
|
|
|
#[test]
|
|
fn escape_closes_a_modal_dialog_and_stays_closed() {
|
|
let (_cx, mut visual, changes) = dialog_window(true, false);
|
|
|
|
visual.simulate_keystrokes("escape");
|
|
assert_eq!(*changes.borrow(), vec![false]);
|
|
draw(&mut visual);
|
|
|
|
// Closed: escape on the inert remainder reports nothing further.
|
|
visual.simulate_keystrokes("escape");
|
|
assert_eq!(*changes.borrow(), vec![false]);
|
|
}
|
|
|
|
#[test]
|
|
fn escape_is_inert_on_an_alert() {
|
|
let (_cx, mut visual, changes) = dialog_window(true, true);
|
|
visual.simulate_keystrokes("escape");
|
|
assert!(changes.borrow().is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn scrim_click_closes_a_modal_but_not_an_alert() {
|
|
let (_cx, mut visual, changes) = dialog_window(true, false);
|
|
click(&mut visual, point(px(100.), px(100.)));
|
|
assert_eq!(*changes.borrow(), vec![false]);
|
|
|
|
draw(&mut visual);
|
|
click(&mut visual, point(px(100.), px(100.)));
|
|
assert_eq!(*changes.borrow(), vec![false]);
|
|
|
|
// The same click on an alert's scrim answers nothing.
|
|
let (_cx, mut visual, changes) = dialog_window(true, true);
|
|
click(&mut visual, point(px(100.), px(100.)));
|
|
assert!(changes.borrow().is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn clicking_the_panel_does_not_dismiss() {
|
|
// The window center is where the centered panel sits.
|
|
let (_cx, mut visual, changes) = dialog_window(true, false);
|
|
click(&mut visual, point(px(960.), px(540.)));
|
|
assert!(changes.borrow().is_empty());
|
|
}
|
|
|
|
/// Clicking the close button reports close like escape and the scrim; the
|
|
/// panel then unmounts the button with the rest of the dialog.
|
|
#[test]
|
|
fn the_close_button_reports_close_and_disappears() {
|
|
let (_cx, mut visual, changes) = dialog_window(true, false);
|
|
|
|
let bounds = visual
|
|
.debug_bounds("dialog-close")
|
|
.expect("the close button rendered");
|
|
click(&mut visual, bounds.center());
|
|
assert_eq!(*changes.borrow(), vec![false]);
|
|
|
|
draw(&mut visual);
|
|
assert!(visual.debug_bounds("dialog-close").is_none());
|
|
}
|
|
|
|
/// An alert answers only through its actions, so it renders no close button.
|
|
#[test]
|
|
fn an_alert_hides_the_close_button() {
|
|
let (_cx, mut visual, _changes) = dialog_window(true, true);
|
|
assert!(visual.debug_bounds("dialog-close").is_none());
|
|
}
|