T09b overlays: lightbox, spotlight and tour, floating toolbar, peek; images fill their box; overlays take focus back when its element leaves the tree
This commit is contained in:
18 files changed
+1754
-228
No files matched your search
@@ -0,0 +1,335 @@
|
||||
use std::path::Path;
|
||||
|
||||
use gpui::{
|
||||
AppContext as _, Bounds, Context, Entity, FocusHandle, InteractiveElement, IntoElement,
|
||||
KeyBinding, ParentElement, Render, Styled, TestAppContext, VisualTestContext, Window, div,
|
||||
point, px, size,
|
||||
};
|
||||
|
||||
use super::{click, press, settle};
|
||||
use crate::{
|
||||
buttons::{Button, IconButton},
|
||||
forms::{Input, TextInput},
|
||||
overlays::{FloatingToolbar, Lightbox, Peek, Slide, Spotlight, Tour, TourStep},
|
||||
primitives::{FocusNext, FocusScope, IconName},
|
||||
theme::Theme,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum Open {
|
||||
Lightbox,
|
||||
Spotlight,
|
||||
SpotField,
|
||||
Tour,
|
||||
Peek,
|
||||
}
|
||||
|
||||
/// Opens one guide at a time over a page with a target button, recording what happens.
|
||||
struct Guides {
|
||||
root: FocusHandle,
|
||||
before: FocusHandle,
|
||||
field: Entity<TextInput>,
|
||||
open: Option<Open>,
|
||||
at: usize,
|
||||
log: Vec<String>,
|
||||
lower: bool,
|
||||
}
|
||||
|
||||
fn target() -> Bounds<gpui::Pixels> {
|
||||
Bounds::new(point(px(0.0), px(0.0)), size(px(90.0), px(28.0)))
|
||||
}
|
||||
|
||||
impl Render for Guides {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let note = |what: &'static str, cx: &mut Context<Self>| {
|
||||
let view = cx.entity();
|
||||
move |_: &mut Window, cx: &mut gpui::App| {
|
||||
view.update(cx, |guides, _| guides.log.push(what.into()))
|
||||
}
|
||||
};
|
||||
let (shut, stepped) = (cx.entity(), cx.entity());
|
||||
let shut = move |_: &mut Window, cx: &mut gpui::App| {
|
||||
shut.update(cx, |guides, cx| {
|
||||
guides.open = None;
|
||||
cx.notify();
|
||||
})
|
||||
};
|
||||
let step = move |to: usize, _: &mut Window, cx: &mut gpui::App| {
|
||||
stepped.update(cx, |guides, cx| {
|
||||
guides.at = to;
|
||||
cx.notify();
|
||||
})
|
||||
};
|
||||
let slides = ["a", "b", "c"].map(|name| Slide::new(Path::new("/missing/ely.png"), name));
|
||||
let steps = [
|
||||
TourStep::new(target(), "Here", "The first stop"),
|
||||
TourStep::new(
|
||||
Bounds::new(point(px(200.0), px(300.0)), size(px(90.0), px(28.0))),
|
||||
"There",
|
||||
"The second stop",
|
||||
),
|
||||
];
|
||||
let (pressed, bold) = (note("target", cx), note("bold", cx));
|
||||
let overlay = self.open.map(|open| match open {
|
||||
Open::Lightbox => Lightbox::new("light", slides, self.at, shut)
|
||||
.on_step(step)
|
||||
.into_any_element(),
|
||||
Open::Spotlight => Spotlight::new("spot", steps[0].clone(), shut).into_any_element(),
|
||||
Open::SpotField => Spotlight::new(
|
||||
"spot",
|
||||
TourStep::new(
|
||||
Bounds::new(point(px(0.0), px(188.0)), size(px(300.0), px(28.0))),
|
||||
"Type here",
|
||||
"The field takes focus",
|
||||
),
|
||||
shut,
|
||||
)
|
||||
.into_any_element(),
|
||||
Open::Tour => Tour::new("tour", steps, self.at, shut)
|
||||
.on_step(step)
|
||||
.into_any_element(),
|
||||
Open::Peek => Peek::new("peek", "Definition", true, shut)
|
||||
.child("fn peek()")
|
||||
.into_any_element(),
|
||||
});
|
||||
FocusScope::new(&self.root)
|
||||
.size_full()
|
||||
.child(
|
||||
Button::new("target", "Target").on_click(move |_, window, cx| pressed(window, cx)),
|
||||
)
|
||||
.child(div().id("before").track_focus(&self.before))
|
||||
.child(div().h(px(if self.lower { 260.0 } else { 160.0 })))
|
||||
.child(
|
||||
div().w(px(300.0)).child(Input::new(&self.field)).child(
|
||||
FloatingToolbar::new("tools", &self.field).child(
|
||||
IconButton::new("bold", IconName::Bold)
|
||||
.on_click(move |_, window, cx| bold(window, cx)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.children(overlay)
|
||||
}
|
||||
}
|
||||
|
||||
fn guides(cx: &mut TestAppContext) -> (Entity<Guides>, &mut VisualTestContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
crate::forms::bind_keys(cx);
|
||||
cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]);
|
||||
});
|
||||
let (view, cx) = cx.add_window_view(|window, cx| Guides {
|
||||
root: cx.focus_handle(),
|
||||
before: cx.focus_handle(),
|
||||
field: cx.new(|cx| {
|
||||
let mut input = TextInput::new(window, cx);
|
||||
input.set_text("hello world", cx);
|
||||
input
|
||||
}),
|
||||
open: None,
|
||||
at: 0,
|
||||
log: Vec::new(),
|
||||
lower: false,
|
||||
});
|
||||
settle(cx);
|
||||
(view, cx)
|
||||
}
|
||||
|
||||
fn open(view: &Entity<Guides>, which: Open, cx: &mut VisualTestContext) {
|
||||
let before = view.read_with(cx, |guides, _| guides.before.clone());
|
||||
cx.update(|window, _| window.focus(&before));
|
||||
view.update(cx, |guides, cx| {
|
||||
guides.open = Some(which);
|
||||
guides.at = 0;
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
fn state(view: &Entity<Guides>, cx: &mut VisualTestContext) -> (Option<Open>, usize, Vec<String>) {
|
||||
view.read_with(cx, |guides, _| (guides.open, guides.at, guides.log.clone()))
|
||||
}
|
||||
|
||||
fn before_focused(view: &Entity<Guides>, cx: &mut VisualTestContext) -> bool {
|
||||
let before = view.read_with(cx, |guides, _| guides.before.clone());
|
||||
cx.update(|window, _| before.is_focused(window))
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_lightbox_steps_with_arrows_and_escape_hands_focus_back(cx: &mut TestAppContext) {
|
||||
let (view, cx) = guides(cx);
|
||||
open(&view, Open::Lightbox, cx);
|
||||
for key in ["right", "right", "right", "left"] {
|
||||
press(key, cx);
|
||||
}
|
||||
assert_eq!(state(&view, cx).1, 1);
|
||||
press("escape", cx);
|
||||
assert!(state(&view, cx).0.is_none());
|
||||
assert!(before_focused(&view, cx));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_dark_closes_the_lightbox_and_the_picture_does_not(cx: &mut TestAppContext) {
|
||||
let (view, cx) = guides(cx);
|
||||
open(&view, Open::Lightbox, cx);
|
||||
let middle = cx.update(|window, _| window.viewport_size());
|
||||
click(
|
||||
f32::from(middle.width) / 2.0,
|
||||
f32::from(middle.height) / 2.0,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(state(&view, cx).0, Some(Open::Lightbox));
|
||||
click(4.0, f32::from(middle.height) - 4.0, cx);
|
||||
assert!(state(&view, cx).0.is_none());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_tour_walks_by_arrows_and_buttons_and_done_closes(cx: &mut TestAppContext) {
|
||||
let (view, cx) = guides(cx);
|
||||
open(&view, Open::Tour, cx);
|
||||
press("right", cx);
|
||||
assert_eq!(state(&view, cx).1, 1);
|
||||
press("left", cx);
|
||||
assert_eq!(state(&view, cx).1, 0);
|
||||
press("tab", cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(state(&view, cx).1, 1, "Next moved on");
|
||||
press("enter", cx);
|
||||
assert!(
|
||||
state(&view, cx).0.is_none(),
|
||||
"focus stayed on the button, now Done"
|
||||
);
|
||||
assert!(before_focused(&view, cx));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_spotlight_passes_presses_through_its_hole_only(cx: &mut TestAppContext) {
|
||||
let (view, cx) = guides(cx);
|
||||
open(&view, Open::Spotlight, cx);
|
||||
click(40.0, 14.0, cx);
|
||||
assert_eq!(
|
||||
state(&view, cx).2,
|
||||
["target"],
|
||||
"the lit target took the press"
|
||||
);
|
||||
click(40.0, 200.0, cx);
|
||||
assert_eq!(state(&view, cx).0, Some(Open::Spotlight), "the dim held");
|
||||
press("escape", cx);
|
||||
assert!(state(&view, cx).0.is_none());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_toolbar_floats_over_a_selection_only(cx: &mut TestAppContext) {
|
||||
let (view, cx) = guides(cx);
|
||||
let field = view.read_with(cx, |guides, _| guides.field.clone());
|
||||
let focus = field.read_with(cx, |input, _| input.focus().clone());
|
||||
cx.update(|window, _| window.focus(&focus));
|
||||
settle(cx);
|
||||
let start = field
|
||||
.read_with(cx, |input, _| input.bounds_for(0))
|
||||
.expect("the field laid out");
|
||||
let bold = (
|
||||
f32::from(start.left()) + 16.0,
|
||||
f32::from(start.top()) - 22.0,
|
||||
);
|
||||
click(bold.0, bold.1, cx);
|
||||
assert!(state(&view, cx).2.is_empty(), "no selection, no toolbar");
|
||||
cx.update(|window, _| window.focus(&focus));
|
||||
press("secondary-a", cx);
|
||||
click(bold.0, bold.1, cx);
|
||||
assert_eq!(state(&view, cx).2, ["bold"]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_peek_closes_from_its_button(cx: &mut TestAppContext) {
|
||||
let (view, cx) = guides(cx);
|
||||
open(&view, Open::Peek, cx);
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
for _ in 0..3 {
|
||||
window.focus_next();
|
||||
}
|
||||
});
|
||||
press("enter", cx);
|
||||
assert!(state(&view, cx).0.is_none());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn escape_closes_a_spotlight_while_its_target_holds_focus(cx: &mut TestAppContext) {
|
||||
let (view, cx) = guides(cx);
|
||||
open(&view, Open::SpotField, cx);
|
||||
click(40.0, 200.0, cx);
|
||||
press("escape", cx);
|
||||
assert!(state(&view, cx).0.is_none());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_lightbox_keeps_focus_when_its_button_disables(cx: &mut TestAppContext) {
|
||||
let (view, cx) = guides(cx);
|
||||
open(&view, Open::Lightbox, cx);
|
||||
press("tab", cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(state(&view, cx).1, 2, "Next walked to the end");
|
||||
press("escape", cx);
|
||||
assert!(state(&view, cx).0.is_none());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_press_on_a_dead_arrow_keeps_the_lightbox(cx: &mut TestAppContext) {
|
||||
let (view, cx) = guides(cx);
|
||||
open(&view, Open::Lightbox, cx);
|
||||
let screen = cx.update(|window, _| window.viewport_size());
|
||||
click(28.0, f32::from(screen.height) / 2.0, cx);
|
||||
assert_eq!(state(&view, cx).0, Some(Open::Lightbox));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_toolbar_follows_its_field(cx: &mut TestAppContext) {
|
||||
let (view, cx) = guides(cx);
|
||||
let field = view.read_with(cx, |guides, _| guides.field.clone());
|
||||
let focus = field.read_with(cx, |input, _| input.focus().clone());
|
||||
cx.update(|window, _| window.focus(&focus));
|
||||
press("secondary-a", cx);
|
||||
view.update(cx, |guides, cx| {
|
||||
guides.lower = true;
|
||||
cx.notify();
|
||||
});
|
||||
cx.run_until_parked();
|
||||
let start = field
|
||||
.read_with(cx, |input, _| input.bounds_for(0))
|
||||
.expect("the field laid out");
|
||||
click(
|
||||
f32::from(start.left()) + 16.0,
|
||||
f32::from(start.top()) - 22.0,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(state(&view, cx).2, ["bold"]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn back_to_the_first_step_keeps_the_tour_keys(cx: &mut TestAppContext) {
|
||||
let (view, cx) = guides(cx);
|
||||
open(&view, Open::Tour, cx);
|
||||
press("right", cx);
|
||||
press("tab", cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(state(&view, cx).1, 0, "Back went to the first step");
|
||||
press("right", cx);
|
||||
assert_eq!(state(&view, cx).1, 1);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn left_from_back_keeps_the_tour_keys(cx: &mut TestAppContext) {
|
||||
let (view, cx) = guides(cx);
|
||||
open(&view, Open::Tour, cx);
|
||||
press("right", cx);
|
||||
press("tab", cx);
|
||||
press("tab", cx);
|
||||
press("left", cx);
|
||||
assert_eq!(state(&view, cx).1, 0);
|
||||
press("right", cx);
|
||||
assert_eq!(state(&view, cx).1, 1);
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use gpui::{
|
||||
AppContext as _, Context, Entity, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
|
||||
KeyUpEvent, Keystroke, Modifiers, ParentElement, Render, Styled, TestAppContext,
|
||||
VisualTestContext, Window, div, point, px,
|
||||
};
|
||||
|
||||
mod guides;
|
||||
|
||||
use super::{AlertDialog, ConfirmDialog, Dialog, HoverCard, Popover, PromptDialog};
|
||||
use crate::{
|
||||
buttons::Button,
|
||||
forms::TextInput,
|
||||
primitives::{FocusNext, FocusScope, Severity},
|
||||
theme::Theme,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Open {
|
||||
Plain,
|
||||
Alert,
|
||||
Confirm,
|
||||
Prompt,
|
||||
}
|
||||
|
||||
/// A popover, a hover card and one dialog at a time, recording what they do.
|
||||
struct Stage {
|
||||
root: FocusHandle,
|
||||
before: FocusHandle,
|
||||
field: Entity<TextInput>,
|
||||
open: Option<Open>,
|
||||
log: Vec<String>,
|
||||
}
|
||||
|
||||
impl Stage {
|
||||
fn note(
|
||||
&self,
|
||||
cx: &mut Context<Self>,
|
||||
what: &'static str,
|
||||
) -> impl Fn(&mut Window, &mut gpui::App) + Clone + 'static {
|
||||
let view = cx.entity();
|
||||
move |_, cx| view.update(cx, |stage, _| stage.log.push(what.into()))
|
||||
}
|
||||
|
||||
fn shut(&self, cx: &mut Context<Self>) -> impl Fn(&mut Window, &mut gpui::App) + 'static {
|
||||
let view = cx.entity();
|
||||
move |_, cx| {
|
||||
view.update(cx, |stage, cx| {
|
||||
stage.open = None;
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Stage {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (copy, mail, follow) = (
|
||||
self.note(cx, "copy"),
|
||||
self.note(cx, "mail"),
|
||||
self.note(cx, "follow"),
|
||||
);
|
||||
let popover = Popover::new("share", "Share", move |_, _| {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.child(div().h(px(60.0)))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new("copy", "Copy link")
|
||||
.on_click(move |_, window, cx| copy(window, cx)),
|
||||
)
|
||||
.child(
|
||||
Button::new("mail", "Email")
|
||||
.on_click(move |_, window, cx| mail(window, cx)),
|
||||
),
|
||||
)
|
||||
});
|
||||
let beneath = self.note(cx, "beneath");
|
||||
let card = HoverCard::new(
|
||||
"ada",
|
||||
div().id("ada-name").w(px(120.0)).h(px(24.0)).child("Ada"),
|
||||
move |_, _| {
|
||||
let follow = follow.clone();
|
||||
Button::new("follow", "Follow").on_click(move |_, window, cx| follow(window, cx))
|
||||
},
|
||||
);
|
||||
let (shut, confirmed) = (self.shut(cx), self.note(cx, "confirmed"));
|
||||
let sent = cx.entity();
|
||||
let overlay = self.open.map(|open| match open {
|
||||
Open::Plain => Dialog::new("plain", "Plain", shut)
|
||||
.detail("A line of detail")
|
||||
.into_any_element(),
|
||||
Open::Alert => AlertDialog::new("alert", Severity::Warning, "Alert", "Look", shut)
|
||||
.into_any_element(),
|
||||
Open::Confirm => ConfirmDialog::new("confirm", "Delete", "Gone for good", shut)
|
||||
.confirm("Delete")
|
||||
.destructive()
|
||||
.on_confirm(move |window, cx| confirmed(window, cx))
|
||||
.into_any_element(),
|
||||
Open::Prompt => PromptDialog::new("prompt", "Rename", &self.field, shut)
|
||||
.check(|text| match text {
|
||||
"" => Err("Needs a name".into()),
|
||||
text if text.contains('/') => Err("No slashes".into()),
|
||||
_ => Ok(()),
|
||||
})
|
||||
.on_submit(move |text, _, cx| {
|
||||
let text = format!("sent {text}");
|
||||
sent.update(cx, |stage, _| stage.log.push(text));
|
||||
})
|
||||
.into_any_element(),
|
||||
});
|
||||
FocusScope::new(&self.root)
|
||||
.size_full()
|
||||
.child(popover)
|
||||
.child(div().h(px(52.0)))
|
||||
.child(
|
||||
Button::new("beneath", "Beneath")
|
||||
.on_click(move |_, window, cx| beneath(window, cx)),
|
||||
)
|
||||
.child(div().h(px(80.0)))
|
||||
.child(Button::new("elsewhere", "Elsewhere"))
|
||||
.child(div().h(px(12.0)))
|
||||
.child(card)
|
||||
.child(div().id("before").track_focus(&self.before))
|
||||
.children(overlay)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn settle(cx: &mut VisualTestContext) {
|
||||
cx.run_until_parked();
|
||||
cx.update(|window, _| window.refresh());
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
pub(super) fn press(key: &str, cx: &mut VisualTestContext) {
|
||||
cx.simulate_keystrokes(key);
|
||||
settle(cx);
|
||||
cx.simulate_event(KeyUpEvent {
|
||||
keystroke: Keystroke::parse(key).expect("a key"),
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
pub(super) fn click(x: f32, y: f32, cx: &mut VisualTestContext) {
|
||||
let at = point(px(x), px(y));
|
||||
cx.simulate_mouse_move(at, None, Modifiers::none());
|
||||
cx.simulate_click(at, Modifiers::none());
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
fn stage(cx: &mut TestAppContext) -> (Entity<Stage>, &mut VisualTestContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
crate::forms::bind_keys(cx);
|
||||
cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]);
|
||||
});
|
||||
let (view, cx) = cx.add_window_view(|window, cx| Stage {
|
||||
root: cx.focus_handle(),
|
||||
before: cx.focus_handle(),
|
||||
field: cx.new(|cx| TextInput::new(window, cx)),
|
||||
open: None,
|
||||
log: Vec::new(),
|
||||
});
|
||||
settle(cx);
|
||||
(view, cx)
|
||||
}
|
||||
|
||||
fn log(view: &Entity<Stage>, cx: &mut VisualTestContext) -> Vec<String> {
|
||||
view.read_with(cx, |stage, _| stage.log.clone())
|
||||
}
|
||||
|
||||
fn show(view: &Entity<Stage>, open: Open, cx: &mut VisualTestContext) {
|
||||
let before = view.read_with(cx, |stage, _| stage.before.clone());
|
||||
cx.update(|window, _| window.focus(&before));
|
||||
view.update(cx, |stage, cx| {
|
||||
stage.open = Some(open);
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn tab_walks_inside_an_open_popover(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
press("enter", cx);
|
||||
for _ in 0..3 {
|
||||
press("tab", cx);
|
||||
}
|
||||
press("enter", cx);
|
||||
assert_eq!(log(&view, cx), ["copy"]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn escape_closes_the_popover_and_hands_focus_back(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
press("enter", cx);
|
||||
press("escape", cx);
|
||||
press("enter", cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(log(&view, cx), ["copy"], "focus came back to the trigger");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_press_on_a_button_outside_closes_the_popover(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
click(12.0, 12.0, cx);
|
||||
click(20.0, 200.0, cx);
|
||||
click(40.0, 127.0, cx);
|
||||
assert!(log(&view, cx).is_empty());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_card_opens_after_a_rest_and_holds_while_on_it(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
cx.simulate_mouse_move(point(px(20.0), px(240.0)), None, Modifiers::none());
|
||||
settle(cx);
|
||||
cx.executor().advance_clock(Duration::from_millis(600));
|
||||
settle(cx);
|
||||
click(40.0, 290.0, cx);
|
||||
assert_eq!(log(&view, cx), ["follow"]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn escape_closes_a_dialog_and_hands_focus_back(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
show(&view, Open::Plain, cx);
|
||||
press("escape", cx);
|
||||
assert!(view.read_with(cx, |stage, _| stage.open.is_none()));
|
||||
let before = view.read_with(cx, |stage, _| stage.before.clone());
|
||||
assert!(cx.update(|window, _| before.is_focused(window)));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_scrim_closes_a_dialog_but_not_an_alert(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
show(&view, Open::Alert, cx);
|
||||
click(8.0, 8.0, cx);
|
||||
assert!(view.read_with(cx, |stage, _| stage.open == Some(Open::Alert)));
|
||||
view.update(cx, |stage, cx| {
|
||||
stage.open = None;
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
show(&view, Open::Plain, cx);
|
||||
click(8.0, 8.0, cx);
|
||||
assert!(view.read_with(cx, |stage, _| stage.open.is_none()));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn confirm_runs_from_the_keyboard(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
show(&view, Open::Confirm, cx);
|
||||
press("tab", cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(log(&view, cx), ["confirmed"]);
|
||||
assert!(view.read_with(cx, |stage, _| stage.open.is_none()));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_prompt_sends_only_what_passes(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
show(&view, Open::Prompt, cx);
|
||||
cx.simulate_input("a/b");
|
||||
settle(cx);
|
||||
press("enter", cx);
|
||||
assert!(log(&view, cx).is_empty(), "a slash went through");
|
||||
press("secondary-a", cx);
|
||||
cx.simulate_input("notes");
|
||||
settle(cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(log(&view, cx), ["sent notes"]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_prompt_opens_with_its_text_selected(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
let field = view.read_with(cx, |stage, _| stage.field.clone());
|
||||
field.update(cx, |input, cx| input.set_text("Draft", cx));
|
||||
show(&view, Open::Prompt, cx);
|
||||
cx.simulate_input("notes");
|
||||
settle(cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(log(&view, cx), ["sent notes"]);
|
||||
}
|
||||
|
||||
fn before_focused(view: &Entity<Stage>, cx: &mut VisualTestContext) -> bool {
|
||||
let before = view.read_with(cx, |stage, _| stage.before.clone());
|
||||
cx.update(|window, _| before.is_focused(window))
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn every_dialog_button_hands_focus_back(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
show(&view, Open::Alert, cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert!(before_focused(&view, cx), "alert button");
|
||||
show(&view, Open::Confirm, cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert!(before_focused(&view, cx), "confirm cancel");
|
||||
show(&view, Open::Confirm, cx);
|
||||
press("tab", cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert!(before_focused(&view, cx), "confirm confirm");
|
||||
show(&view, Open::Prompt, cx);
|
||||
cx.simulate_input("notes");
|
||||
settle(cx);
|
||||
press("enter", cx);
|
||||
assert!(before_focused(&view, cx), "prompt submit");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_press_on_the_panel_stays_off_what_lies_beneath(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
click(12.0, 12.0, cx);
|
||||
click(30.0, 94.0, cx);
|
||||
assert!(
|
||||
log(&view, cx).is_empty(),
|
||||
"the press went through the panel"
|
||||
);
|
||||
click(40.0, 127.0, cx);
|
||||
assert_eq!(log(&view, cx), ["copy"], "the panel stayed open");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn enter_on_a_prompt_button_runs_that_button_alone(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
show(&view, Open::Prompt, cx);
|
||||
cx.simulate_input("notes");
|
||||
settle(cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert!(log(&view, cx).is_empty(), "cancel submitted");
|
||||
show(&view, Open::Prompt, cx);
|
||||
cx.simulate_input("notes");
|
||||
settle(cx);
|
||||
press("tab", cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(log(&view, cx), ["sent notes"]);
|
||||
}
|
||||
Reference in new issue
Block a user