Misc · Terms and notices: tests for every way out; switch labels wrap
Tests now press Decline and the scrim, see focus return to the opener after Accept, see necessary cookies stay on and off the Tab path, and see the license list mark its row and read each version and license. lists::ListItem marks its description and selected state for tests. forms::Switch's label wraps in its own block, as Checkbox's does. Ely's own row in the gallery shows both of its license texts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6c589c3115
commit
067ef3f122
7 files changed
+123
-24
No files matched your search
@@ -63,6 +63,7 @@ pub const PAGE: Page = Page {
|
||||
Step::Key("escape"),
|
||||
Step::DownAt("cookies", 230.0, 84.0),
|
||||
Step::UpAt("cookies", 230.0, 84.0),
|
||||
Step::HoverAt("cookies", 20.0, 20.0),
|
||||
Step::Shot("cookies-choosing"),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -17,7 +17,11 @@ fn packages() -> [Package; 4] {
|
||||
"Ely GPUI Component",
|
||||
"0.1.0",
|
||||
"MIT OR Apache-2.0",
|
||||
include_str!("../../../../LICENSE-MIT"),
|
||||
concat!(
|
||||
include_str!("../../../../LICENSE-MIT"),
|
||||
"\n\n",
|
||||
include_str!("../../../../LICENSE-APACHE")
|
||||
),
|
||||
),
|
||||
Package::new(
|
||||
"Lucide",
|
||||
|
||||
+7
-1
@@ -140,6 +140,12 @@ impl RenderOnce for Switch {
|
||||
})
|
||||
})
|
||||
.child(rail)
|
||||
.children(self.label)
|
||||
.children(self.label.map(|label| {
|
||||
div()
|
||||
.debug_selector(|| "switch-label".into())
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.child(label)
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use gpui::{
|
||||
|
||||
use super::setup;
|
||||
use crate::{
|
||||
forms::{Checkbox, Choice, IconPicker, InlineEdit, RadioGroup, SignaturePad, Stroke},
|
||||
forms::{Checkbox, Choice, IconPicker, InlineEdit, RadioGroup, SignaturePad, Stroke, Switch},
|
||||
primitives::{FocusNext, IconName},
|
||||
};
|
||||
|
||||
@@ -145,7 +145,7 @@ fn an_inline_edit_takes_tab_opens_on_enter_and_takes_focus_back(cx: &mut TestApp
|
||||
);
|
||||
}
|
||||
|
||||
/// A checkbox and a radio whose labels run past their narrow box.
|
||||
/// A checkbox, a radio and a switch whose labels run past their narrow box.
|
||||
struct Worded;
|
||||
|
||||
impl Render for Worded {
|
||||
@@ -158,6 +158,7 @@ impl Render for Worded {
|
||||
"worded-radios",
|
||||
[Choice::new("one", label)],
|
||||
))
|
||||
.child(Switch::new("worded-switch", false).label(label))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +167,7 @@ fn long_labels_wrap_inside_their_box(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (_, cx) = cx.add_window_view(|_, _| Worded);
|
||||
cx.run_until_parked();
|
||||
for selector in ["checkbox-label", "radio-label"] {
|
||||
for selector in ["checkbox-label", "radio-label", "switch-label"] {
|
||||
let label = cx.debug_bounds(selector).expect("the label draws");
|
||||
assert!(label.right() <= px(200.0), "{selector}: {label:?}");
|
||||
}
|
||||
|
||||
+6
-2
@@ -153,7 +153,10 @@ impl RenderOnce for ListItem {
|
||||
} else {
|
||||
gpui::transparent_black()
|
||||
})
|
||||
.when(self.selected, |row| row.bg(colors.active))
|
||||
.when(self.selected, |row| {
|
||||
row.debug_selector(|| format!("item-selected-{}", self.title))
|
||||
.bg(colors.active)
|
||||
})
|
||||
.when(self.disabled, |row| row.opacity(0.5))
|
||||
.when_some(self.on_click.filter(|_| pressable), |row, on_click| {
|
||||
row.cursor_pointer()
|
||||
@@ -182,7 +185,8 @@ impl RenderOnce for ListItem {
|
||||
}),
|
||||
)
|
||||
.children(self.description.map(|text| {
|
||||
line(text)
|
||||
line(text.clone())
|
||||
.debug_selector(|| format!("item-description-{text}"))
|
||||
.text_size(theme.text_size(TextSize::Sm))
|
||||
.text_color(colors.fg_muted)
|
||||
}))
|
||||
|
||||
+14
-8
@@ -1,7 +1,8 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
App, ElementId, IntoElement, ParentElement, RenderOnce, SharedString, Styled, Window, div,
|
||||
App, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce, SharedString,
|
||||
Styled, Window, div,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -104,13 +105,18 @@ impl RenderOnce for CookieBanner {
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.child(row(
|
||||
Switch::new((id.clone(), "necessary"), true)
|
||||
.label("Necessary")
|
||||
.disabled(true),
|
||||
"Keeps the site working; always on.".into(),
|
||||
cx,
|
||||
))
|
||||
.child({
|
||||
let (on, rests) = (true, true);
|
||||
div()
|
||||
.debug_selector(|| format!("cookie-necessary-{on}-{rests}"))
|
||||
.child(row(
|
||||
Switch::new((id.clone(), "necessary"), on)
|
||||
.label("Necessary")
|
||||
.disabled(rests),
|
||||
"Keeps the site working; always on.".into(),
|
||||
cx,
|
||||
))
|
||||
})
|
||||
.children(rows)
|
||||
});
|
||||
let (all, none, saved) = (answer.clone(), answer.clone(), answer);
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
|
||||
use gpui::{AnyElement, IntoElement, TestAppContext};
|
||||
use gpui::{
|
||||
AnyElement, App, Context, FocusHandle, IntoElement, Modifiers, ParentElement, Render, Styled,
|
||||
TestAppContext, Window, div, point, px,
|
||||
};
|
||||
|
||||
use super::{at_root, press, shown, stage};
|
||||
use super::{at_root, press, settle, shown, stage};
|
||||
use crate::misc::{ConsentDialog, CookieBanner, LicenseViewer, Package};
|
||||
use crate::{buttons::Button, primitives::FocusScope, theme::Theme};
|
||||
|
||||
thread_local! {
|
||||
static SAID: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
|
||||
@@ -24,20 +28,25 @@ fn licenses() -> AnyElement {
|
||||
[
|
||||
Package::new("Alpha", "1.0", "MIT", "Permission is hereby granted"),
|
||||
Package::new("Beta", "2.3", "ISC", "Permission to use, copy, modify"),
|
||||
Package::new("Gamma", "0.9", "OFL-1.1", "Permission is hereby granted"),
|
||||
],
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// The first package's license shows; a move down the list shows the next one's.
|
||||
/// Each row reads its version and license; the first license shows, and moves down the list show the next ones, the list marking each.
|
||||
#[gpui::test]
|
||||
fn a_license_viewer_shows_the_chosen_license(cx: &mut TestAppContext) {
|
||||
let (_, cx) = stage(licenses, cx);
|
||||
assert!(shown("license-Alpha", cx));
|
||||
assert!(shown("license-Alpha", cx) && shown("item-description-2.3 · ISC", cx));
|
||||
press("tab", cx);
|
||||
press("down", cx);
|
||||
press("space", cx);
|
||||
assert!(shown("license-Beta", cx));
|
||||
assert!(shown("license-Beta", cx) && shown("item-selected-Beta", cx));
|
||||
press("down", cx);
|
||||
assert!(
|
||||
shown("license-Gamma", cx),
|
||||
"the list kept its mark on Beta, so its cursor went on from there"
|
||||
);
|
||||
}
|
||||
|
||||
fn consent() -> AnyElement {
|
||||
@@ -78,13 +87,77 @@ fn consent_waits_for_the_box(cx: &mut TestAppContext) {
|
||||
assert_eq!(said(), ["accepted"], "Accept answers alone");
|
||||
}
|
||||
|
||||
/// Escape declines, once.
|
||||
/// Escape, Decline and a press on the scrim each decline, once.
|
||||
#[gpui::test]
|
||||
fn escape_declines_consent(cx: &mut TestAppContext) {
|
||||
fn every_other_way_out_declines(cx: &mut TestAppContext) {
|
||||
let (_, cx) = stage(consent, cx);
|
||||
press("tab", cx);
|
||||
press("escape", cx);
|
||||
assert_eq!(said(), ["declined"]);
|
||||
OPEN.set(true);
|
||||
cx.update(|window, _| window.refresh());
|
||||
settle(cx);
|
||||
press("tab", cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
OPEN.set(true);
|
||||
cx.update(|window, _| window.refresh());
|
||||
settle(cx);
|
||||
cx.simulate_click(point(px(3.0), px(3.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
assert_eq!(said(), ["declined", "declined", "declined"]);
|
||||
}
|
||||
|
||||
/// A page with a button that opens the consent dialog, which answers into `said`.
|
||||
struct Opener {
|
||||
root: FocusHandle,
|
||||
button: FocusHandle,
|
||||
}
|
||||
|
||||
impl Render for Opener {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.entity();
|
||||
FocusScope::new(&self.root)
|
||||
.root()
|
||||
.size_full()
|
||||
.child(
|
||||
Button::new("open", "Terms")
|
||||
.focus_handle(&self.button)
|
||||
.on_click(move |_, _, cx| {
|
||||
OPEN.set(true);
|
||||
view.update(cx, |_, cx| cx.notify());
|
||||
}),
|
||||
)
|
||||
.child(div().child(consent()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept hands focus back to the button that opened the terms.
|
||||
#[gpui::test]
|
||||
fn consent_hands_focus_back_to_its_opener(cx: &mut TestAppContext) {
|
||||
OPEN.set(false);
|
||||
cx.update(|cx: &mut App| {
|
||||
Theme::init(cx);
|
||||
crate::forms::bind_keys(cx);
|
||||
cx.bind_keys([
|
||||
gpui::KeyBinding::new("tab", crate::primitives::FocusNext, None),
|
||||
gpui::KeyBinding::new("shift-tab", crate::primitives::FocusPrev, None),
|
||||
]);
|
||||
});
|
||||
let (view, cx) = cx.add_window_view(|_, cx| Opener {
|
||||
root: cx.focus_handle(),
|
||||
button: cx.focus_handle(),
|
||||
});
|
||||
let button = view.read_with(cx, |opener, _| opener.button.clone());
|
||||
cx.update(|window, _| window.focus(&button));
|
||||
settle(cx);
|
||||
press("enter", cx);
|
||||
press("tab", cx);
|
||||
press("space", cx);
|
||||
press("tab", cx);
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(said(), ["accepted"]);
|
||||
assert!(cx.update(|window, _| button.is_focused(window)));
|
||||
}
|
||||
|
||||
fn cookies() -> AnyElement {
|
||||
@@ -106,6 +179,10 @@ fn a_cookie_banner_hands_over_the_kinds_chosen(cx: &mut TestAppContext) {
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert!(!at_root(cx), "Save choices took Choose's focus");
|
||||
assert!(
|
||||
shown("cookie-necessary-true-true", cx),
|
||||
"necessary cookies stay on"
|
||||
);
|
||||
for _ in 0..3 {
|
||||
press("shift-tab", cx);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user