audit input
This commit is contained in:
+163
-2
@@ -3,12 +3,15 @@ use gpui::{
|
||||
MouseUpEvent, ParentElement as _, Render, ScrollDelta, ScrollEvent, Styled as _,
|
||||
TestAppContext, VisualTestContext, Window, div, point, px,
|
||||
};
|
||||
use gpui_native_core::button::{Button, ButtonVariant};
|
||||
use gpui_native_core::common::Sizable as _;
|
||||
use gpui_native_core::input::{
|
||||
INPUT_CONTEXT, Input, InputBindings, InputState, InputStateEvent, Submit, SubmitOn,
|
||||
bind_input_keys,
|
||||
};
|
||||
use gpui_native_theme::Theme;
|
||||
use std::cell::RefCell;
|
||||
use gpui_native_icons::IconRole;
|
||||
use gpui_native_theme::{BORDER_WIDTH, Size, Theme};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -763,6 +766,36 @@ fn the_reveal_toggle_flips_masking() {
|
||||
assert!(!revealed, "the toggle should mask the field again");
|
||||
}
|
||||
|
||||
/// The reveal pill sits with equal insets on its three free sides; the
|
||||
/// small control metric must stay whole-pixel or snapping skews it.
|
||||
#[test]
|
||||
fn the_reveal_pill_insets_equally() {
|
||||
let (cx, _state, mut visual) = reveal_window();
|
||||
|
||||
let field = visual.debug_bounds("input-field").expect("field missing");
|
||||
let pill = visual
|
||||
.debug_bounds("input-reveal-button")
|
||||
.expect("reveal pill missing");
|
||||
|
||||
cx.update(|cx| {
|
||||
let theme = Theme::global(cx);
|
||||
let small = theme.sizes.resolve(Size::Small);
|
||||
let border = BORDER_WIDTH;
|
||||
let docked = small.control_height + border * 2.0;
|
||||
let inset = ((field.size.height - border * 2.0 - docked) / 2.0).max(px(0.));
|
||||
|
||||
let top_gap = pill.top() - (field.top() + border);
|
||||
let bottom_gap = (field.bottom() - border) - pill.bottom();
|
||||
let right_gap = (field.right() - border) - pill.right();
|
||||
assert!(
|
||||
(top_gap - inset).abs() < px(0.01)
|
||||
&& (bottom_gap - inset).abs() < px(0.01)
|
||||
&& (right_gap - inset).abs() < px(0.01),
|
||||
"pill gaps top {top_gap:?} bottom {bottom_gap:?} right {right_gap:?} vs inset {inset:?}"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_caret_can_rest_on_a_line_break() {
|
||||
let (cx, state, mut visual) = multiline_window("ab\ncdef");
|
||||
@@ -906,3 +939,131 @@ fn vertical_scroll_keeps_the_caret_in_view() {
|
||||
.unwrap_or_default();
|
||||
assert!(first_row_top < px(0.), "first row top: {first_row_top:?}");
|
||||
}
|
||||
|
||||
struct EdgeHarness {
|
||||
state: Entity<InputState>,
|
||||
clicked: Rc<Cell<bool>>,
|
||||
}
|
||||
|
||||
impl Render for EdgeHarness {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut Window,
|
||||
_cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
let clicked = self.clicked.clone();
|
||||
div().w(px(400.)).p_4().child(
|
||||
Input::new("edge-input", self.state.clone())
|
||||
.placeholder("Search")
|
||||
.prefix(
|
||||
Button::new("edge-input-prefix-button")
|
||||
.icon(IconRole::Search)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.small(),
|
||||
)
|
||||
.suffix(
|
||||
Button::new("edge-input-suffix-button")
|
||||
.debug_selector(|| "edge-suffix-button".to_string())
|
||||
.icon(IconRole::Plus)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.small()
|
||||
.on_click(move |_, _, _| clicked.set(true)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn edge_input_window(
|
||||
initial: &str,
|
||||
) -> (
|
||||
TestAppContext,
|
||||
Entity<InputState>,
|
||||
VisualTestContext,
|
||||
Rc<Cell<bool>>,
|
||||
) {
|
||||
let clicked = Rc::new(Cell::new(false));
|
||||
let clicked_for_window = clicked.clone();
|
||||
let mut cx = TestAppContext::single();
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
bind_input_keys(cx, InputBindings::default());
|
||||
});
|
||||
let state = cx.update(|cx| InputState::new_with_text(initial, cx));
|
||||
let state_for_window = state.clone();
|
||||
let window = cx.add_window(move |_, _| EdgeHarness {
|
||||
state: state_for_window,
|
||||
clicked: clicked_for_window,
|
||||
});
|
||||
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
||||
visual.update(|window, cx| {
|
||||
window.draw(cx).clear(cx);
|
||||
});
|
||||
(cx, state, visual, clicked)
|
||||
}
|
||||
|
||||
/// Prefix and suffix dock flush inside the field, prefix leading, and a
|
||||
/// small button in a zone centers with equal insets like the reveal pill.
|
||||
#[test]
|
||||
fn prefix_and_suffix_dock_inside_the_field() {
|
||||
let (cx, _state, mut visual, _clicked) = edge_input_window("abc");
|
||||
|
||||
let field = visual.debug_bounds("input-field").expect("field missing");
|
||||
let prefix = visual
|
||||
.debug_bounds("input-prefix")
|
||||
.expect("prefix zone missing");
|
||||
let suffix = visual
|
||||
.debug_bounds("input-suffix")
|
||||
.expect("suffix zone missing");
|
||||
let pill = visual
|
||||
.debug_bounds("edge-suffix-button")
|
||||
.expect("suffix button missing");
|
||||
|
||||
assert!(prefix.left() >= field.left());
|
||||
assert!(prefix.right() <= suffix.left());
|
||||
assert!(suffix.right() <= field.right());
|
||||
|
||||
cx.update(|cx| {
|
||||
let theme = Theme::global(cx);
|
||||
let small = theme.sizes.resolve(Size::Small);
|
||||
let border = BORDER_WIDTH;
|
||||
let docked = small.control_height + border * 2.0;
|
||||
let inset = ((field.size.height - border * 2.0 - docked) / 2.0).max(px(0.));
|
||||
|
||||
let top_gap = pill.top() - (field.top() + border);
|
||||
let bottom_gap = (field.bottom() - border) - pill.bottom();
|
||||
let right_gap = (field.right() - border) - pill.right();
|
||||
assert!(
|
||||
(top_gap - inset).abs() < px(0.01)
|
||||
&& (bottom_gap - inset).abs() < px(0.01)
|
||||
&& (right_gap - inset).abs() < px(0.01),
|
||||
"suffix pill gaps top {top_gap:?} bottom {bottom_gap:?} right {right_gap:?} vs inset {inset:?}"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// A click on a docked control activates it without running the field's
|
||||
/// caret handler underneath.
|
||||
#[test]
|
||||
fn a_suffix_click_activates_without_focusing_the_field() {
|
||||
let (cx, state, mut visual, clicked) = edge_input_window("abc");
|
||||
|
||||
let bounds = visual
|
||||
.debug_bounds("input-suffix")
|
||||
.expect("suffix zone missing");
|
||||
click(
|
||||
&mut visual,
|
||||
point(
|
||||
bounds.left() + bounds.size.width / 2.0,
|
||||
bounds.top() + bounds.size.height / 2.0,
|
||||
),
|
||||
);
|
||||
|
||||
assert!(clicked.get(), "the suffix button should activate");
|
||||
let focused = visual.update(|window, cx| state.read(cx).focus_handle().is_focused(window));
|
||||
assert!(!focused, "the suffix click must not focus the field");
|
||||
|
||||
// The zone must not swallow text interaction either.
|
||||
focus(&mut visual, &state);
|
||||
visual.simulate_keystrokes("x");
|
||||
assert_eq!(value(&cx, &state), "abcx");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user