use gpui::{ AppContext as _, Context, Entity, InteractiveElement, IntoElement, KeyUpEvent, Keystroke, ParentElement, Render, Styled, TestAppContext, VisualTestContext, Window, div, px, }; use super::setup; use crate::{ buttons::Button, forms::{FieldArray, Form, FormField, Input, InputEvent, TextInput}, }; struct Signup { name: Entity, sent: usize, field_submits: usize, } impl Render for Signup { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); Form::new("signup") .on_submit(move |_, cx| { view.update(cx, |view, cx| { view.sent += 1; cx.notify(); }) }) .child( FormField::new("signup-name", "Name") .required() .child(Input::new(&self.name)), ) .child(Button::new("signup-save", "Save")) } } fn signup(cx: &mut TestAppContext) -> (Entity, &mut VisualTestContext) { setup(cx); cx.add_window_view(|window, cx| { let name = cx.new(|cx| TextInput::new(window, cx)); cx.subscribe(&name, |view: &mut Signup, _, event, _| { if *event == InputEvent::Submit { view.field_submits += 1; } }) .detach(); Signup { name, sent: 0, field_submits: 0, } }) } #[gpui::test] fn cmd_enter_in_a_field_submits_the_form_once(cx: &mut TestAppContext) { let (view, cx) = signup(cx); cx.update(|window, _| window.focus_next()); cx.simulate_input("Ada"); cx.simulate_keystrokes("cmd-enter"); let counts = view.read_with(cx, |view, _| (view.sent, view.field_submits)); assert_eq!(counts, (1, 0)); } #[gpui::test] fn cmd_enter_on_any_control_in_a_form_submits_it(cx: &mut TestAppContext) { let (view, cx) = signup(cx); cx.update(|window, _| { window.focus_next(); window.focus_next(); }); cx.simulate_keystrokes("cmd-enter"); assert_eq!( view.read_with(cx, |view, _| view.sent), 1, "the button holds focus" ); } struct Rows { rows: Vec<&'static str>, } impl Render for Rows { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { let (grow, shrink) = (cx.entity(), cx.entity()); self.rows.iter().fold( FieldArray::new("rows") .on_add(move |_, cx| { grow.update(cx, |view, cx| { view.rows.push("new"); cx.notify(); }) }) .on_remove(move |ix, _, cx| { shrink.update(cx, |view, cx| { view.rows.remove(ix); cx.notify(); }) }), |array, row| array.row(div().child(*row)), ) } } fn press(key: &str, cx: &mut VisualTestContext) { cx.simulate_keystrokes(key); cx.simulate_event(KeyUpEvent { keystroke: Keystroke::parse(key).unwrap(), }); } #[gpui::test] fn field_array_buttons_remove_their_own_row_and_add_one(cx: &mut TestAppContext) { setup(cx); let (view, cx) = cx.add_window_view(|_, _| Rows { rows: vec!["a", "b", "c"], }); cx.update(|window, _| { window.focus_next(); window.focus_next(); }); press("space", cx); assert_eq!(view.read_with(cx, |view, _| view.rows.clone()), ["a", "c"]); cx.update(|window, _| { window.blur(); for _ in 0..3 { window.focus_next(); } }); press("space", cx); assert_eq!( view.read_with(cx, |view, _| view.rows.clone()), ["a", "c", "new"] ); } /// A field over a long line in a column that a padded block measures by content. struct Narrow; impl Render for Narrow { fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { let control = div().debug_selector(|| "narrow-control".into()).h_2(); div().w(px(280.)).flex().flex_col().child( div().p_5().child( div() .flex() .flex_col() .child(FormField::new("narrow", "Name").child(control)) .child("A line long enough to wrap in a narrow card, so the column fills it."), ), ) } } #[gpui::test] fn a_field_fills_a_column_its_block_measures_by_content(cx: &mut TestAppContext) { setup(cx); let (_, cx) = cx.add_window_view(|_, _| Narrow); cx.run_until_parked(); let control = cx .debug_bounds("narrow-control") .expect("the control draws"); assert_eq!( control.size.width, px(240.), "the field spans the card inside its padding" ); }