Onboarding · Help: help panel, cheatsheet, what's new, feedback, support, inline help
HelpPanel searches articles and opens one, its body in Markdown, with a way back; focus follows to All articles and back to the search, a link to help:<key> opens another, and Contact support and Keyboard shortcuts wait at its foot. KeyboardShortcutCheatsheet puts settings::KeyboardShortcutsList in a dialog with its search focused; WhatsNewDialog puts data_display::Changelog in one with Got it. FeedbackWidget asks how it is going with four faces and a note, and thanks. ContactSupport sends a topic and a message, spins while the owner sends, and empties the message with focus back in it. InlineHelp is a quiet line with Learn more. HelpTooltip moves to primitives, where FormLabel's help mark shows it; ToggleGroup keys its toggles by its own id. EmptyStateWithAction points to feedback::EmptyState.
This commit is contained in:
1 parent
d4ba8f7037
commit
fe1b00d414
20 files changed
+1662
-122
No files matched your search
@@ -0,0 +1,216 @@
|
||||
use gpui::{AnyElement, Entity, IntoElement, ParentElement, TestAppContext, div, px};
|
||||
|
||||
use super::{Bench, bench, edit, said, say, tab, tap};
|
||||
use crate::{
|
||||
buttons::Button,
|
||||
onboarding::{
|
||||
FeatureHighlight, Hotspot, OnboardingStep, OnboardingWizard, SetupChecklist, SetupTask,
|
||||
},
|
||||
primitives::{FocusScope, IconName},
|
||||
};
|
||||
|
||||
fn wizard(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let steps = ["name", "invite", "theme"].map(|key| OnboardingStep {
|
||||
key: key.into(),
|
||||
title: format!("About {key}").into(),
|
||||
body: "A line on it".into(),
|
||||
});
|
||||
let [stepper, finisher, skipper] = [(); 3].map(|_| owner.clone());
|
||||
OnboardingWizard::new("welcome", steps, bench.step)
|
||||
.ready(bench.ready)
|
||||
.content(div().child("Fields"))
|
||||
.on_step(move |to, _, cx| {
|
||||
stepper.update(cx, |bench, cx| {
|
||||
bench.step = to;
|
||||
bench.said.push(format!("step {to}"));
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.on_finish(move |_, cx| say(&finisher, "finish".into(), cx))
|
||||
.on_skip(move |_, cx| say(&skipper, "skip".into(), cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops: Skip, then Next on the first step; Skip, Back, then Next or Finish after it.
|
||||
#[gpui::test]
|
||||
fn the_wizard_walks_on_and_back_and_finishes(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(wizard, cx);
|
||||
assert!(cx.debug_bounds("onboarding-Step 1 of 3").is_some());
|
||||
let (first, second) = (
|
||||
cx.debug_bounds("progress-fill-0")
|
||||
.expect("the bar's first part"),
|
||||
cx.debug_bounds("progress-fill-1")
|
||||
.expect("the bar's second part"),
|
||||
);
|
||||
assert!(
|
||||
first.size.width > px(0.0) && second.size.width == px(0.0),
|
||||
"the first of three parts is full: {first:?}, {second:?}"
|
||||
);
|
||||
tab(2, cx);
|
||||
tap("space", cx);
|
||||
tab(2, cx);
|
||||
tap("space", cx);
|
||||
edit(&host, cx, |bench| bench.step = 2);
|
||||
assert!(cx.debug_bounds("onboarding-Step 3 of 3").is_some());
|
||||
tab(3, cx);
|
||||
tap("space", cx);
|
||||
tab(1, cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(said(&host, cx), ["step 1", "step 0", "finish", "skip"]);
|
||||
}
|
||||
|
||||
/// While the step's fields are not ready, Next rests, so the second stop wraps to Skip.
|
||||
#[gpui::test]
|
||||
fn next_rests_until_the_step_is_ready(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(wizard, cx);
|
||||
edit(&host, cx, |bench| bench.ready = false);
|
||||
tab(2, cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(said(&host, cx), ["skip"]);
|
||||
}
|
||||
|
||||
/// On the step before last, the stops are Skip, Back and Next: Next turns to Finish under focus, so a second press finishes.
|
||||
#[gpui::test]
|
||||
fn next_keeps_focus_as_it_turns_to_finish(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(wizard, cx);
|
||||
edit(&host, cx, |bench| bench.step = 1);
|
||||
tab(3, cx);
|
||||
tap("space", cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(said(&host, cx), ["step 2", "finish"]);
|
||||
}
|
||||
|
||||
/// Back from the second step leaves Back at rest on the first, so focus moves to Next, and a press walks on again.
|
||||
#[gpui::test]
|
||||
fn back_to_the_first_step_hands_focus_to_next(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(wizard, cx);
|
||||
edit(&host, cx, |bench| bench.step = 1);
|
||||
tab(2, cx);
|
||||
tap("space", cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(said(&host, cx), ["step 0", "step 1"]);
|
||||
}
|
||||
|
||||
fn hotspot(_: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
Hotspot::new("pins", "Pin a view", "Keep it one press away.")
|
||||
.on_dismiss(move |_, cx| say(&owner, "seen".into(), cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops: the dot; Enter opens the tip, where Got it is the one stop.
|
||||
#[gpui::test]
|
||||
fn a_hotspot_opens_its_tip_from_the_keyboard_and_is_dismissed(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(hotspot, cx);
|
||||
assert!(
|
||||
cx.debug_bounds("hotspot-tip").is_none(),
|
||||
"the tip rests shut"
|
||||
);
|
||||
tab(1, cx);
|
||||
tap("enter", cx);
|
||||
assert!(cx.debug_bounds("hotspot-tip").is_some());
|
||||
tap("escape", cx);
|
||||
tap("enter", cx);
|
||||
tap("tab", cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(
|
||||
said(&host, cx),
|
||||
["seen"],
|
||||
"Escape handed focus back to the dot"
|
||||
);
|
||||
}
|
||||
|
||||
fn hidden_hotspot(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let after = owner.clone();
|
||||
let spot = (!bench.hidden).then(|| {
|
||||
Hotspot::new("pins", "Pin a view", "Keep it one press away.").on_dismiss(move |_, cx| {
|
||||
owner.update(cx, |bench, cx| {
|
||||
bench.said.push("seen".into());
|
||||
bench.hidden = true;
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
});
|
||||
FocusScope::new(&bench.root)
|
||||
.root()
|
||||
.children(spot)
|
||||
.child(
|
||||
Button::new("after", "After").on_click(move |_, _, cx| say(&after, "after".into(), cx)),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Got it closes the tip before the owner hides the dot, so the root keeps focus and Tab reaches the next stop.
|
||||
#[gpui::test]
|
||||
fn after_got_it_tab_reaches_the_next_stop(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(hidden_hotspot, cx);
|
||||
cx.update(|window, _| window.activate_window());
|
||||
tab(1, cx);
|
||||
tap("enter", cx);
|
||||
tap("tab", cx);
|
||||
tap("space", cx);
|
||||
tap("tab", cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(said(&host, cx), ["seen", "after"]);
|
||||
}
|
||||
|
||||
fn highlight(_: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let later = owner.clone();
|
||||
FeatureHighlight::new(
|
||||
"split",
|
||||
IconName::Columns2,
|
||||
"Split view",
|
||||
"Work on two files side by side.",
|
||||
)
|
||||
.on_try(move |_, cx| say(&owner, "try".into(), cx))
|
||||
.on_dismiss(move |_, cx| say(&later, "later".into(), cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops: Try it, then Not now.
|
||||
#[gpui::test]
|
||||
fn a_feature_highlight_is_tried_or_put_off(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(highlight, cx);
|
||||
tab(1, cx);
|
||||
tap("space", cx);
|
||||
tab(2, cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(said(&host, cx), ["try", "later"]);
|
||||
}
|
||||
|
||||
fn checklist(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let task = |key: &str, done: bool| SetupTask {
|
||||
key: key.to_string().into(),
|
||||
title: format!("Do {key}").into(),
|
||||
body: "Why it helps".into(),
|
||||
done: done || bench.all_done,
|
||||
};
|
||||
let hidden = owner.clone();
|
||||
SetupChecklist::new(
|
||||
"setup",
|
||||
"Get started",
|
||||
[
|
||||
task("profile", true),
|
||||
task("invite", false),
|
||||
task("connect", false),
|
||||
],
|
||||
)
|
||||
.on_start(move |key, _, cx| say(&owner, format!("start {key}"), cx))
|
||||
.on_dismiss(move |_, cx| say(&hidden, "hide".into(), cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops: Start on each task not done, so the third wraps to the first; once all are done, Hide.
|
||||
#[gpui::test]
|
||||
fn a_setup_checklist_starts_what_is_left_and_hides_when_done(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(checklist, cx);
|
||||
assert!(cx.debug_bounds("setup-1 of 3 done").is_some());
|
||||
tab(2, cx);
|
||||
tap("space", cx);
|
||||
tab(3, cx);
|
||||
tap("space", cx);
|
||||
edit(&host, cx, |bench| bench.all_done = true);
|
||||
assert!(cx.debug_bounds("setup-3 of 3 done").is_some());
|
||||
tab(1, cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(said(&host, cx), ["start connect", "start invite", "hide"]);
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
use gpui::{AnyElement, Entity, IntoElement, ParentElement, Styled, TestAppContext, div, px};
|
||||
|
||||
use super::{Bench, bench, edit, said, say, tab, tap, write};
|
||||
use crate::{
|
||||
data_display::{Change, Release},
|
||||
forms::Choice,
|
||||
onboarding::{
|
||||
ContactSupport, FeedbackWidget, HelpArticle, HelpPanel, InlineHelp,
|
||||
KeyboardShortcutCheatsheet, WhatsNewDialog,
|
||||
},
|
||||
settings::Shortcut,
|
||||
};
|
||||
|
||||
fn article(key: &str, title: &str, body: &str) -> HelpArticle {
|
||||
HelpArticle {
|
||||
key: key.to_string().into(),
|
||||
title: title.to_string().into(),
|
||||
summary: "A line on it".into(),
|
||||
body: body.to_string().into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn panel(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let articles = [
|
||||
article("start", "Getting started", "Make a project."),
|
||||
article("team", "Invite your team", "Send an invitation by email."),
|
||||
];
|
||||
let [opener, contact, keys, close] = [(); 4].map(|_| owner.clone());
|
||||
div()
|
||||
.h(px(420.0))
|
||||
.child(
|
||||
HelpPanel::new("help", articles, &bench.search, bench.open.clone())
|
||||
.on_open(move |key, _, cx| {
|
||||
let key = key.cloned();
|
||||
opener.update(cx, |bench, cx| {
|
||||
bench.said.push(format!(
|
||||
"open {}",
|
||||
key.as_ref().map_or("none", |key| key.as_ref())
|
||||
));
|
||||
bench.open = key;
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.on_contact(move |_, cx| say(&contact, "contact".into(), cx))
|
||||
.on_shortcuts(move |_, cx| say(&keys, "shortcuts".into(), cx))
|
||||
.on_close(move |_, cx| say(&close, "close".into(), cx)),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops on the list: close, the search, each article, then the foot; on an article: close, All articles, then the foot. An article opened by key takes focus to All articles, and the way back takes it to the search, where words narrow the list.
|
||||
#[gpui::test]
|
||||
fn an_article_opens_from_the_list_and_focus_follows(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(panel, cx);
|
||||
tab(3, cx);
|
||||
tap("space", cx);
|
||||
tap("space", cx);
|
||||
write("email", cx);
|
||||
tab(3, cx);
|
||||
tap("space", cx);
|
||||
tab(3, cx);
|
||||
tap("space", cx);
|
||||
tab(4, cx);
|
||||
tap("space", cx);
|
||||
tab(1, cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(
|
||||
said(&host, cx),
|
||||
[
|
||||
"open start",
|
||||
"open none",
|
||||
"open team",
|
||||
"contact",
|
||||
"shortcuts",
|
||||
"close"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
fn cheatsheet(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let shortcut = |name: &str, keys: &str| Shortcut {
|
||||
group: "General".into(),
|
||||
name: name.to_string().into(),
|
||||
keys: keys.to_string().into(),
|
||||
};
|
||||
let dialog = bench.dialog.then(|| {
|
||||
KeyboardShortcutCheatsheet::new(
|
||||
"keys",
|
||||
[shortcut("Undo", "cmd-z"), shortcut("Find", "cmd-f")],
|
||||
&bench.search,
|
||||
move |_, cx| say(&owner, "closed".into(), cx),
|
||||
)
|
||||
});
|
||||
div().children(dialog).into_any_element()
|
||||
}
|
||||
|
||||
/// The search takes focus as the dialog opens, and Escape closes it.
|
||||
#[gpui::test]
|
||||
fn the_cheatsheet_opens_on_its_search_and_escape_closes_it(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(cheatsheet, cx);
|
||||
edit(&host, cx, |bench| bench.dialog = true);
|
||||
write("undo", cx);
|
||||
let typed = host.read_with(cx, |bench, cx| bench.search.read(cx).text().to_string());
|
||||
assert_eq!(typed, "undo");
|
||||
tap("escape", cx);
|
||||
assert_eq!(said(&host, cx), ["closed"]);
|
||||
}
|
||||
|
||||
fn whats_new(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let dialog = bench.dialog.then(|| {
|
||||
WhatsNewDialog::new("new", move |_, cx| say(&owner, "closed".into(), cx))
|
||||
.release(Release::new("2.4", "27 Sep 2026").change(Change::Added, "Split view"))
|
||||
});
|
||||
div().children(dialog).into_any_element()
|
||||
}
|
||||
|
||||
/// Stops: the close button, then Got it.
|
||||
#[gpui::test]
|
||||
fn whats_new_lists_the_releases_and_got_it_closes(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(whats_new, cx);
|
||||
edit(&host, cx, |bench| bench.dialog = true);
|
||||
assert!(cx.debug_bounds("whats-new-releases").is_some());
|
||||
assert!(cx.debug_bounds("whats-new-done").is_some());
|
||||
tap("tab", cx);
|
||||
tap("tab", cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(said(&host, cx), ["closed"]);
|
||||
}
|
||||
|
||||
fn feedback(_: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
FeedbackWidget::new("feedback")
|
||||
.on_send(move |sentiment, note, _, cx| {
|
||||
say(&owner, format!("{} {note}", sentiment.key()), cx)
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops in the form: the four faces, the note, then Send once a face is picked.
|
||||
#[gpui::test]
|
||||
fn feedback_waits_for_a_face_and_says_thanks(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(feedback, cx);
|
||||
tab(1, cx);
|
||||
tap("enter", cx);
|
||||
for _ in 0..5 {
|
||||
tap("tab", cx);
|
||||
}
|
||||
write(" Fast ", cx);
|
||||
for _ in 0..3 {
|
||||
tap("tab", cx);
|
||||
}
|
||||
tap("space", cx);
|
||||
for _ in 0..3 {
|
||||
tap("tab", cx);
|
||||
}
|
||||
tap("space", cx);
|
||||
assert_eq!(
|
||||
said(&host, cx),
|
||||
["good Fast"],
|
||||
"Send rested past the note, so the third Tab reached Good"
|
||||
);
|
||||
assert!(cx.debug_bounds("feedback-thanks").is_some());
|
||||
}
|
||||
|
||||
fn support(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let topics = [
|
||||
Choice::new("billing", "Billing"),
|
||||
Choice::new("bug", "Something broke"),
|
||||
];
|
||||
ContactSupport::new("support", topics)
|
||||
.busy(bench.busy)
|
||||
.address("help@example.com")
|
||||
.on_send(move |topic, message, _, cx| say(&owner, format!("{topic}: {message}"), cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops: the topic, the message, then Send once both are given; the message empties once sent, with focus back in it.
|
||||
#[gpui::test]
|
||||
fn a_message_goes_with_its_topic_and_empties(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(support, cx);
|
||||
tab(2, cx);
|
||||
write("It froze", cx);
|
||||
tab(3, cx);
|
||||
tap("space", cx);
|
||||
assert!(
|
||||
said(&host, cx).is_empty(),
|
||||
"no topic yet, so Send rests and the third stop wraps to the topic"
|
||||
);
|
||||
tab(1, cx);
|
||||
for key in ["escape", "enter", "down", "enter"] {
|
||||
tap(key, cx);
|
||||
}
|
||||
tab(3, cx);
|
||||
tap("space", cx);
|
||||
write("Again", cx);
|
||||
tab(3, cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(said(&host, cx), ["bug: It froze", "bug: Again"]);
|
||||
}
|
||||
|
||||
/// While the owner sends, Send spins and takes no press, yet stays the third stop and keeps its focus: once sending ends, the same press sends.
|
||||
#[gpui::test]
|
||||
fn a_message_waits_while_busy(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(support, cx);
|
||||
edit(&host, cx, |bench| bench.busy = true);
|
||||
tab(1, cx);
|
||||
for key in ["enter", "down", "enter"] {
|
||||
tap(key, cx);
|
||||
}
|
||||
tab(2, cx);
|
||||
write("It froze", cx);
|
||||
tab(3, cx);
|
||||
tap("space", cx);
|
||||
assert!(said(&host, cx).is_empty(), "Send takes no press while busy");
|
||||
edit(&host, cx, |bench| bench.busy = false);
|
||||
tap("space", cx);
|
||||
assert_eq!(said(&host, cx), ["bug: It froze"]);
|
||||
}
|
||||
|
||||
fn inline(_: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
InlineHelp::new("hint", "Only admins can change billing.")
|
||||
.on_more(move |_, cx| say(&owner, "more".into(), cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// Stops: Learn more.
|
||||
#[gpui::test]
|
||||
fn inline_help_offers_more(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(inline, cx);
|
||||
tab(1, cx);
|
||||
tap("space", cx);
|
||||
assert_eq!(said(&host, cx), ["more"]);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
use gpui::{
|
||||
AnyElement, App, AppContext, Context, Entity, FocusHandle, IntoElement, KeyBinding,
|
||||
ParentElement, Render, SharedString, Styled, TestAppContext, VisualTestContext, Window, div,
|
||||
px,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
forms::{self, TextInput},
|
||||
primitives::FocusNext,
|
||||
theme::Theme,
|
||||
};
|
||||
|
||||
mod flows;
|
||||
mod help;
|
||||
|
||||
type Part = fn(&Bench, Entity<Bench>) -> AnyElement;
|
||||
|
||||
/// A view that shows one onboarding part, keeps what it heard, and holds what the owner decides: the step, readiness, what it hid, the open article and dialog, and a find field, with the root focus an app keeps.
|
||||
struct Bench {
|
||||
part: Part,
|
||||
said: Vec<String>,
|
||||
root: FocusHandle,
|
||||
hidden: bool,
|
||||
step: usize,
|
||||
ready: bool,
|
||||
all_done: bool,
|
||||
open: Option<SharedString>,
|
||||
dialog: bool,
|
||||
busy: bool,
|
||||
search: Entity<TextInput>,
|
||||
}
|
||||
|
||||
impl Render for Bench {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.w(px(360.0))
|
||||
.p_4()
|
||||
.child((self.part)(self, cx.entity()))
|
||||
}
|
||||
}
|
||||
|
||||
fn bench(part: Part, cx: &mut TestAppContext) -> (Entity<Bench>, &mut VisualTestContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
Theme::update(cx, |theme| theme.reduced_motion = true);
|
||||
forms::bind_keys(cx);
|
||||
cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]);
|
||||
});
|
||||
let (host, cx) = cx.add_window_view(|window, cx| Bench {
|
||||
part,
|
||||
said: Vec::new(),
|
||||
root: cx.focus_handle(),
|
||||
hidden: false,
|
||||
step: 0,
|
||||
ready: true,
|
||||
all_done: false,
|
||||
open: None,
|
||||
dialog: false,
|
||||
busy: false,
|
||||
search: cx.new(|cx| TextInput::new(window, cx)),
|
||||
});
|
||||
settle(cx);
|
||||
(host, cx)
|
||||
}
|
||||
|
||||
fn settle(cx: &mut VisualTestContext) {
|
||||
for _ in 0..3 {
|
||||
cx.run_until_parked();
|
||||
cx.update(|window, _| window.refresh());
|
||||
}
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
fn say(owner: &Entity<Bench>, words: String, cx: &mut App) {
|
||||
owner.update(cx, |bench, cx| {
|
||||
bench.said.push(words);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
||||
fn said(host: &Entity<Bench>, cx: &mut VisualTestContext) -> Vec<String> {
|
||||
host.read_with(cx, |bench, _| bench.said.clone())
|
||||
}
|
||||
|
||||
fn edit(host: &Entity<Bench>, cx: &mut VisualTestContext, change: impl FnOnce(&mut Bench)) {
|
||||
host.update(cx, |bench, cx| {
|
||||
change(bench);
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
/// Focus on the `stops`th Tab stop from the top.
|
||||
fn tab(stops: usize, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
(0..stops).for_each(|_| window.focus_next());
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
fn tap(key: &str, cx: &mut VisualTestContext) {
|
||||
cx.simulate_keystrokes(key);
|
||||
cx.simulate_event(gpui::KeyUpEvent {
|
||||
keystroke: gpui::Keystroke::parse(key).expect("a key"),
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
fn write(text: &str, cx: &mut VisualTestContext) {
|
||||
cx.simulate_input(text);
|
||||
settle(cx);
|
||||
}
|
||||
Reference in new issue
Block a user