Files
Ely-GPUI-Components/src/forms/tests/structure.rs
T
ZacharyZhang-NYandClaude Opus 5.5 07a577b574 Calendar · Editing: an event's editor, its repeats, reminders and open times
EventEditor puts an event's parts in a form: its title, whole days or
hours, where, its color, how it repeats, its reminders and notes. Save
waits for a title and an end after the start and hands on an
EventDraft; Cancel and Delete show when the owner handles them. A new
draft from the owner starts the editor over.

RecurrenceEditor sets a rule after RFC 5545: daily, weekly on its days,
monthly or yearly, every so many, until a day or a count. A line reads
the rule back with the next days it falls on. ReminderPicker keeps
reminders from a fixed list, soonest first, and Add brings the nearest
one not yet set. AvailabilityPicker counts the open slots of each day
and lists the chosen day's, around what is busy, inside working hours
and after now.

FormField filled its column with a percent width, which taffy 0.9
leaves at content width when a block measures the column first and the
width comes out the same: at 280px the editor's fields shrank to their
labels. It now fills with min_w_full, as new roots do; older roots move
over in T44.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 04:22:02 -04:00

170 lines
4.8 KiB
Rust

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<TextInput>,
sent: usize,
field_submits: usize,
}
impl Render for Signup {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> 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<Signup>, &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<Self>) -> 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<Self>) -> 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"
);
}