add password field
This commit is contained in:
@@ -590,3 +590,146 @@ fn enter_emits_submitted() {
|
||||
// The content is left in place for the subscriber to read or clear.
|
||||
assert_eq!(value(&cx, &state), "draft");
|
||||
}
|
||||
|
||||
fn password_window(initial: &str) -> (TestAppContext, Entity<InputState>, VisualTestContext) {
|
||||
let (cx, state, visual) = input_window(initial, false);
|
||||
cx.update(|cx| {
|
||||
state.update(cx, |state, cx| state.set_password(true, cx));
|
||||
});
|
||||
(cx, state, visual)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn password_fields_keep_the_real_buffer_while_masked() {
|
||||
let (cx, state, mut visual) = password_window("");
|
||||
focus(&mut visual, &state);
|
||||
visual.simulate_keystrokes("s e c r e t");
|
||||
// The buffer holds the real secret; masking is paint-only.
|
||||
assert_eq!(value(&cx, &state), "secret");
|
||||
|
||||
// Editing still works through the mask.
|
||||
visual.simulate_keystrokes("backspace");
|
||||
assert_eq!(value(&cx, &state), "secre");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn password_fields_refuse_copy_and_cut() {
|
||||
let (cx, state, mut visual) = password_window("secret");
|
||||
focus(&mut visual, &state);
|
||||
|
||||
// Select everything and try to copy.
|
||||
visual.simulate_keystrokes("cmd-a");
|
||||
visual.simulate_keystrokes("cmd-c");
|
||||
let clipboard = cx.update(|cx| {
|
||||
cx.read_from_clipboard()
|
||||
.and_then(|item| item.text())
|
||||
.unwrap_or_default()
|
||||
});
|
||||
assert_eq!(clipboard, "");
|
||||
|
||||
// Cut neither leaks the secret nor deletes it.
|
||||
visual.simulate_keystrokes("cmd-x");
|
||||
assert_eq!(value(&cx, &state), "secret");
|
||||
let clipboard = cx.update(|cx| {
|
||||
cx.read_from_clipboard()
|
||||
.and_then(|item| item.text())
|
||||
.unwrap_or_default()
|
||||
});
|
||||
assert_eq!(clipboard, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn password_fields_still_allow_paste() {
|
||||
let (cx, state, mut visual) = password_window("");
|
||||
focus(&mut visual, &state);
|
||||
cx.update(|cx| {
|
||||
cx.write_to_clipboard(gpui::ClipboardItem::new_string("hunter2".to_string()));
|
||||
});
|
||||
visual.simulate_keystrokes("cmd-v");
|
||||
assert_eq!(value(&cx, &state), "hunter2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clicks_in_password_fields_map_to_real_offsets() {
|
||||
let (cx, state, mut visual) = password_window("abcdefgh");
|
||||
focus(&mut visual, &state);
|
||||
|
||||
// Clicking at the text origin moves the caret to the start, so typing
|
||||
// lands there: the masked display maps clicks back onto real offsets.
|
||||
click(&mut visual, point(px(12.), px(15.)));
|
||||
visual.simulate_keystrokes("x");
|
||||
assert_eq!(value(&cx, &state), "xabcdefgh");
|
||||
|
||||
// Clicking past the last bullet clamps to the end.
|
||||
click(&mut visual, point(px(500.), px(15.)));
|
||||
visual.simulate_keystrokes("y");
|
||||
assert_eq!(value(&cx, &state), "xabcdefghy");
|
||||
}
|
||||
|
||||
fn click(visual: &mut VisualTestContext, position: gpui::Point<gpui::Pixels>) {
|
||||
visual.simulate_mouse_down(position, MouseButton::Left, Modifiers::default());
|
||||
visual.simulate_mouse_up(position, MouseButton::Left, gpui::Modifiers::default());
|
||||
}
|
||||
|
||||
struct RevealHarness {
|
||||
state: Entity<InputState>,
|
||||
}
|
||||
|
||||
impl Render for RevealHarness {
|
||||
fn render(
|
||||
&mut self,
|
||||
_window: &mut Window,
|
||||
_cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
div()
|
||||
.w(px(400.))
|
||||
.p_4()
|
||||
.child(Input::new("reveal-input", self.state.clone()).placeholder("Password"))
|
||||
}
|
||||
}
|
||||
|
||||
fn reveal_window() -> (TestAppContext, Entity<InputState>, VisualTestContext) {
|
||||
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("", cx));
|
||||
cx.update(|cx| {
|
||||
state.update(cx, |state, cx| state.set_password(true, cx));
|
||||
});
|
||||
let state_for_window = state.clone();
|
||||
let window = cx.add_window(move |_, _| RevealHarness {
|
||||
state: state_for_window,
|
||||
});
|
||||
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
||||
visual.update(|window, cx| {
|
||||
window.draw(cx).clear(cx);
|
||||
});
|
||||
(cx, state, visual)
|
||||
}
|
||||
|
||||
/// Clicking the reveal toggle flips masking on and off, so apps can reveal
|
||||
/// a secret without a custom control.
|
||||
#[test]
|
||||
fn the_reveal_toggle_flips_masking() {
|
||||
let (cx, state, mut visual) = reveal_window();
|
||||
|
||||
let bounds = visual
|
||||
.debug_bounds("input-reveal")
|
||||
.expect("reveal toggle missing");
|
||||
let center = point(
|
||||
bounds.left() + bounds.size.width / 2.0,
|
||||
bounds.top() + bounds.size.height / 2.0,
|
||||
);
|
||||
|
||||
// Masked: clicking reveals.
|
||||
click(&mut visual, center);
|
||||
let revealed = cx.update(|cx| state.read(cx).is_revealed());
|
||||
assert!(revealed, "the toggle should reveal the field");
|
||||
|
||||
// Revealed: clicking masks again.
|
||||
click(&mut visual, center);
|
||||
let revealed = cx.update(|cx| state.read(cx).is_revealed());
|
||||
assert!(!revealed, "the toggle should mask the field again");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user