Files
Ely-GPUI-Components/src/calendar/tests/editing.rs
T
ZacharyZhang-NY c99465153d Calendar · open times start over on new days
AvailabilityPicker kept the day chosen in keyed state whatever days the
owner passed next, and the slots lookup expected it among them: an
owner paging to the next week crashed the app once a day was chosen.
The choice is now seeded on the owner's days and pick, so a new run of
days starts it over at the picked day or the first open one.
2026-09-27 04:31:14 -04:00

195 lines
6.0 KiB
Rust

use gpui::{Entity, IntoElement, Modifiers, TestAppContext, VisualTestContext, point, px};
use jiff::{civil::date, tz::TimeZone};
use super::{Planning, at, heard, note, planning, press, settle, tab_to};
use crate::calendar::{
AvailabilityPicker, EventDraft, EventEditor, RecurrenceEditor, ReminderPicker, When,
};
fn draft(title: &str, from: i8, to: i8) -> EventDraft {
EventDraft {
title: title.to_string().into(),
when: When::Timed {
start: at(22, from),
end: at(22, to),
},
place: "".into(),
hue: 0,
repeat: None,
reminders: Vec::new(),
notes: "".into(),
}
}
fn said(draft: &EventDraft) -> String {
match draft.when {
When::Timed { start, end } => format!("save {} {start} {end}", draft.title),
When::AllDay { first, last } => format!("save {} {first} to {last}", draft.title),
}
}
fn editor(host: &Planning, owner: Entity<Planning>) -> gpui::AnyElement {
let title = match host.month.day() {
1 => "",
_ => "Crit",
};
EventEditor::new("editor", draft(title, 9, 10))
.zone(TimeZone::UTC)
.on_save(move |draft, _, cx| note(&owner, said(draft), cx))
.into_any_element()
}
/// Presses the editor's last action, Save, at the right end of its row.
fn save(cx: &mut VisualTestContext) {
let row = cx.debug_bounds("editor-actions").expect("the actions draw");
cx.simulate_click(
point(row.right() - px(8.0), row.center().y),
Modifiers::none(),
);
settle(cx);
}
#[gpui::test]
fn save_waits_for_a_title_then_hands_on_the_draft(cx: &mut TestAppContext) {
let (host, cx) = planning(editor, cx);
save(cx);
assert!(heard(&host, cx).is_empty(), "no title, no save");
tab_to(1, cx);
cx.simulate_input(" Model review ");
settle(cx);
save(cx);
assert_eq!(
heard(&host, cx),
[format!("save Model review {} {}", at(22, 9), at(22, 10))]
);
}
#[gpui::test]
fn all_day_turns_the_hours_into_their_day(cx: &mut TestAppContext) {
let (host, cx) = planning(editor, cx);
tab_to(1, cx);
cx.simulate_input("Fair");
settle(cx);
tab_to(2, cx);
press("space", cx);
save(cx);
assert_eq!(heard(&host, cx), ["save Fair 2026-09-22 to 2026-09-22"]);
}
fn backward(_: &Planning, owner: Entity<Planning>) -> gpui::AnyElement {
EventEditor::new("editor", draft("Crit", 10, 9))
.zone(TimeZone::UTC)
.on_save(move |draft, _, cx| note(&owner, said(draft), cx))
.into_any_element()
}
#[gpui::test]
fn an_end_before_the_start_says_so_and_holds_save(cx: &mut TestAppContext) {
let (host, cx) = planning(backward, cx);
assert!(cx.debug_bounds("editor-fault").is_some());
save(cx);
assert!(heard(&host, cx).is_empty());
}
#[gpui::test]
fn a_new_draft_from_the_owner_starts_the_editor_over(cx: &mut TestAppContext) {
let (host, cx) = planning(editor, cx);
host.update(cx, |host, cx| {
host.month = date(2026, 10, 2);
cx.notify();
});
settle(cx);
save(cx);
assert_eq!(
heard(&host, cx),
[format!("save Crit {} {}", at(22, 9), at(22, 10))]
);
}
fn reminders(_: &Planning, owner: Entity<Planning>) -> gpui::AnyElement {
ReminderPicker::new("reminders", [10])
.on_change(move |minutes, _, cx| note(&owner, format!("{minutes:?}"), cx))
.into_any_element()
}
#[gpui::test]
fn add_brings_the_nearest_reminder_not_yet_set(cx: &mut TestAppContext) {
let (host, cx) = planning(reminders, cx);
tab_to(3, cx);
press("space", cx);
tab_to(2, cx);
press("space", cx);
assert_eq!(heard(&host, cx), ["[0, 10]", "[]"], "Add, then the way off");
}
fn repeat(_: &Planning, owner: Entity<Planning>) -> gpui::AnyElement {
RecurrenceEditor::new("repeat", date(2026, 9, 27), None)
.on_change(move |rule, _, cx| {
let words = rule.map(|rule| rule.describe(date(2026, 9, 27)));
note(&owner, format!("{words:?}"), cx)
})
.into_any_element()
}
#[gpui::test]
fn picking_a_frequency_starts_a_rule(cx: &mut TestAppContext) {
let (host, cx) = planning(repeat, cx);
tab_to(1, cx);
cx.simulate_keystrokes("down down down enter");
settle(cx);
assert_eq!(heard(&host, cx), ["Some(\"Every week on Sunday\")"]);
}
fn slots(_: &Planning, owner: Entity<Planning>) -> gpui::AnyElement {
AvailabilityPicker::new("slots", [date(2026, 9, 28), date(2026, 9, 29)])
.zone(TimeZone::UTC)
.hours(9, 12)
.busy([(at(28, 9), at(28, 10))])
.now(at(27, 12))
.on_pick(move |start, _, cx| note(&owner, format!("pick {start}"), cx))
.into_any_element()
}
#[gpui::test]
fn a_slot_pick_hands_on_its_start(cx: &mut TestAppContext) {
let (host, cx) = planning(slots, cx);
tab_to(3, cx);
press("space", cx);
assert_eq!(
heard(&host, cx),
[format!("pick {}", at(28, 10))],
"09:00 to 10:00 is taken"
);
}
fn paged(host: &Planning, owner: Entity<Planning>) -> gpui::AnyElement {
let first = host.month;
let dawn = date(2026, 8, 31).at(0, 0, 0, 0).to_zoned(TimeZone::UTC);
AvailabilityPicker::new("paged", [first, first.tomorrow().expect("a day ahead")])
.zone(TimeZone::UTC)
.hours(9, 10)
.now(dawn.expect("a UTC time").timestamp())
.on_pick(move |start, _, cx| note(&owner, format!("pick {start}"), cx))
.into_any_element()
}
#[gpui::test]
fn new_days_from_the_owner_start_the_choice_over(cx: &mut TestAppContext) {
let (host, cx) = planning(paged, cx);
tab_to(2, cx);
press("space", cx);
host.update(cx, |host, cx| {
host.month = date(2026, 10, 5);
cx.notify();
});
settle(cx);
tab_to(3, cx);
press("space", cx);
let nine = date(2026, 10, 5).at(9, 0, 0, 0).to_zoned(TimeZone::UTC);
assert_eq!(
heard(&host, cx),
[format!("pick {}", nine.expect("a UTC time").timestamp())],
"the chosen day of the old run is gone; the first open day shows"
);
}