add input state events and key handling to InputState
This commit is contained in:
+127
-1
@@ -2,8 +2,10 @@ use gpui::{
|
||||
Entity, Modifiers, ParentElement as _, Render, Styled as _, TestAppContext, VisualTestContext,
|
||||
Window, div, point, px,
|
||||
};
|
||||
use gpui_native_core::input::{Input, InputState};
|
||||
use gpui_native_core::input::{Input, InputState, InputStateEvent};
|
||||
use gpui_native_theme::Theme;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
struct Harness {
|
||||
state: Entity<InputState>,
|
||||
@@ -136,3 +138,127 @@ fn set_value_replaces_the_buffer() {
|
||||
});
|
||||
assert_eq!(value(&cx, &state), "preset");
|
||||
}
|
||||
|
||||
/// Subscribes to `state`'s events, recording each variant into a sink that
|
||||
/// lives for the rest of the test. The returned `Subscription` must stay
|
||||
/// bound for the same lifetime — dropping it unsubscribes.
|
||||
fn record_events(
|
||||
cx: &mut TestAppContext,
|
||||
state: &Entity<InputState>,
|
||||
) -> (Rc<RefCell<Vec<InputStateEvent>>>, gpui::Subscription) {
|
||||
let sink = Rc::new(RefCell::new(Vec::new()));
|
||||
let sink_for_sub = sink.clone();
|
||||
let subscription = cx.update(|cx| {
|
||||
cx.subscribe(state, move |_, event, _| {
|
||||
sink_for_sub.borrow_mut().push(event.clone());
|
||||
})
|
||||
});
|
||||
// Subscriptions activate through a deferred effect.
|
||||
cx.run_until_parked();
|
||||
(sink, subscription)
|
||||
}
|
||||
|
||||
fn count(sink: &Rc<RefCell<Vec<InputStateEvent>>>, event: &InputStateEvent) -> usize {
|
||||
sink.borrow().iter().filter(|e| e == &event).count()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_changed_emits_for_edits_but_not_for_caret_moves() {
|
||||
let (mut cx, state, mut visual) = input_window("", false);
|
||||
let (sink, _subscription) = record_events(&mut cx, &state);
|
||||
focus(&mut visual, &state);
|
||||
|
||||
visual.simulate_keystrokes("a space b");
|
||||
assert_eq!(count(&sink, &InputStateEvent::TextChanged), 3);
|
||||
|
||||
// Caret movement repaints but is not a text change.
|
||||
visual.simulate_keystrokes("left");
|
||||
assert_eq!(count(&sink, &InputStateEvent::TextChanged), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_value_no_op_does_not_emit_changed() {
|
||||
let (mut cx, state, mut visual) = input_window("same", false);
|
||||
let (sink, _subscription) = record_events(&mut cx, &state);
|
||||
|
||||
visual.update(|window, cx| {
|
||||
state.update(cx, |state, cx| state.set_value("same", window, cx));
|
||||
});
|
||||
visual.run_until_parked();
|
||||
assert_eq!(count(&sink, &InputStateEvent::TextChanged), 0);
|
||||
|
||||
visual.update(|window, cx| {
|
||||
state.update(cx, |state, cx| state.set_value("other", window, cx));
|
||||
});
|
||||
visual.run_until_parked();
|
||||
assert_eq!(count(&sink, &InputStateEvent::TextChanged), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_value_silent_updates_without_emitting() {
|
||||
let (mut cx, state, mut visual) = input_window("same", false);
|
||||
let (sink, _subscription) = record_events(&mut cx, &state);
|
||||
|
||||
visual.update(|window, cx| {
|
||||
state.update(cx, |state, cx| state.set_value_silent("other", window, cx));
|
||||
});
|
||||
visual.run_until_parked();
|
||||
assert_eq!(value(&cx, &state), "other");
|
||||
assert_eq!(count(&sink, &InputStateEvent::TextChanged), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn focus_and_blur_emit_events() {
|
||||
let (mut cx, state, mut visual) = input_window("", false);
|
||||
let (sink, _subscription) = record_events(&mut cx, &state);
|
||||
|
||||
focus(&mut visual, &state);
|
||||
visual.update(|window, cx| {
|
||||
window.draw(cx).clear(cx);
|
||||
});
|
||||
visual.run_until_parked();
|
||||
assert_eq!(count(&sink, &InputStateEvent::Focused), 1);
|
||||
|
||||
visual.update(|window, cx| window.blur(cx));
|
||||
visual.update(|window, cx| {
|
||||
window.draw(cx).clear(cx);
|
||||
});
|
||||
visual.run_until_parked();
|
||||
assert_eq!(count(&sink, &InputStateEvent::Blurred), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pasted_newlines_become_spaces() {
|
||||
let (cx, state, mut visual) = input_window("", false);
|
||||
focus(&mut visual, &state);
|
||||
cx.update(|cx| {
|
||||
cx.write_to_clipboard(gpui::ClipboardItem::new_string("a\nb\rc".to_string()));
|
||||
});
|
||||
visual.simulate_keystrokes("cmd-v");
|
||||
// `\r` is dropped and `\n` becomes a space, matching the sanitizer.
|
||||
assert_eq!(value(&cx, &state), "a bc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_value_sanitizes_newlines() {
|
||||
let (cx, state, mut visual) = input_window("", false);
|
||||
visual.update(|window, cx| {
|
||||
state.update(cx, |state, cx| state.set_value("a\nb", window, cx));
|
||||
});
|
||||
assert_eq!(value(&cx, &state), "a b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enter_emits_submitted() {
|
||||
let (mut cx, state, mut visual) = input_window("draft", false);
|
||||
let (sink, _subscription) = record_events(&mut cx, &state);
|
||||
focus(&mut visual, &state);
|
||||
visual.update(|window, cx| {
|
||||
window.draw(cx).clear(cx);
|
||||
});
|
||||
|
||||
visual.simulate_keystrokes("enter");
|
||||
assert_eq!(count(&sink, &InputStateEvent::Submitted), 1);
|
||||
// The content is left in place for the subscriber to read or clear.
|
||||
assert_eq!(value(&cx, &state), "draft");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user