Files
Ely-GPUI-Components/src/calendar/tests/mod.rs
T
ZacharyZhang-NYandClaude Opus 5.5 a506ddc98c Calendar · Agenda and people: an agenda, an event's details, its guests
AgendaView lists the days ahead under their names, whole days first,
then by the hour, and leaves out days with nothing; it rides on
lists::Sections, so a press or an arrow selects and Enter opens. An
empty stretch says nothing is planned.

EventPopover is an event's chip that opens its details: its title in
its hue, when and where, who is asked, and Edit and Delete when the
owner handles them. It opens through Popover::with_opener, which now
hands any element the toggle and keeps focus, placement and every way
out in the popover. AttendeeList counts the answers and lists each
guest with their answer, the organizer first. A hue worn in a row is
now data_display::color_mark, for mail labels and events alike.

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

271 lines
8.0 KiB
Rust

use gpui::{
Context, Entity, IntoElement, KeyBinding, KeyUpEvent, Keystroke, Modifiers, MouseButton,
MouseDownEvent, MouseUpEvent, ParentElement, Render, Styled, TestAppContext, VisualTestContext,
Window, div, px,
};
use jiff::{Timestamp, civil::date, tz::TimeZone};
use super::{CalendarMonthView, Event, YearView};
mod people;
mod times;
use crate::{primitives::FocusNext, theme::Theme};
/// A view that shows one calendar part, keeps the month it was told, and what it heard.
struct Planning {
part: fn(&Planning, Entity<Planning>) -> gpui::AnyElement,
month: jiff::civil::Date,
heard: Vec<String>,
}
impl Render for Planning {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div().w(px(900.0)).child((self.part)(self, cx.entity()))
}
}
fn settle(cx: &mut VisualTestContext) {
for _ in 0..3 {
cx.run_until_parked();
cx.update(|window, _| window.refresh());
}
cx.run_until_parked();
}
fn planning(
part: fn(&Planning, Entity<Planning>) -> gpui::AnyElement,
cx: &mut TestAppContext,
) -> (Entity<Planning>, &mut VisualTestContext) {
cx.update(|cx| {
Theme::init(cx);
Theme::update(cx, |theme| theme.reduced_motion = true);
cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]);
});
let (host, cx) = cx.add_window_view(|_, _| Planning {
part,
month: date(2026, 9, 1),
heard: Vec::new(),
});
cx.update(|window, _| window.activate_window());
settle(cx);
(host, cx)
}
fn note(owner: &Entity<Planning>, what: String, cx: &mut gpui::App) {
owner.update(cx, |host, cx| {
host.heard.push(what);
cx.notify();
});
}
fn heard(host: &Entity<Planning>, cx: &mut VisualTestContext) -> Vec<String> {
host.read_with(cx, |host, _| host.heard.clone())
}
fn press(key: &str, cx: &mut VisualTestContext) {
cx.simulate_keystrokes(key);
settle(cx);
cx.simulate_event(KeyUpEvent {
keystroke: Keystroke::parse(key).expect("a key"),
});
settle(cx);
}
/// Moves focus to the `nth` Tab stop from none.
fn tab_to(nth: usize, cx: &mut VisualTestContext) {
cx.update(|window, _| {
window.blur();
for _ in 0..nth {
window.focus_next();
}
});
settle(cx);
}
fn at(day: i8, hour: i8) -> Timestamp {
date(2026, 9, day)
.at(hour, 0, 0, 0)
.to_zoned(TimeZone::UTC)
.expect("a UTC time")
.timestamp()
}
/// Five meetings on the 16th, and a trip across the weekend after it.
fn events() -> Vec<Event> {
let mut events: Vec<Event> = (0..5)
.map(|ix| {
let hour = 9 + ix as i8;
Event::timed(
format!("m{ix}"),
format!("Meeting {ix}"),
at(16, hour),
at(16, hour + 1),
ix,
)
})
.collect();
events.push(Event::all_day(
"trip",
"Trip",
date(2026, 9, 18),
date(2026, 9, 22),
5,
));
events
}
fn month(_: &Planning, owner: Entity<Planning>) -> gpui::AnyElement {
let (days, keys, months) = (owner.clone(), owner.clone(), owner);
CalendarMonthView::new("month", date(2026, 9, 1), events())
.zone(TimeZone::UTC)
.today(date(2026, 9, 16))
.on_day(move |day, _, cx| note(&days, format!("open {day}"), cx))
.on_event(move |key, _, cx| note(&keys, format!("event {key}"), cx))
.on_month(move |first, _, cx| note(&months, format!("month {first}"), cx))
.into_any_element()
}
#[gpui::test]
fn arrows_move_the_day_and_enter_opens_it(cx: &mut TestAppContext) {
let (host, cx) = planning(month, cx);
tab_to(4, cx);
press("right", cx);
press("down", cx);
press("enter", cx);
assert_eq!(
heard(&host, cx),
["open 2026-09-24"],
"from today, the 16th"
);
}
#[gpui::test]
fn page_down_turns_the_month(cx: &mut TestAppContext) {
let (host, cx) = planning(month, cx);
tab_to(4, cx);
press("pagedown", cx);
press("enter", cx);
assert_eq!(heard(&host, cx), ["month 2026-10-01", "open 2026-10-16"]);
}
#[gpui::test]
fn a_crowded_day_opens_from_its_more(cx: &mut TestAppContext) {
let (host, cx) = planning(month, cx);
let more = cx
.debug_bounds("more 2026-09-16")
.expect("the 16th holds more than fits");
cx.simulate_click(more.center(), Modifiers::none());
settle(cx);
assert_eq!(heard(&host, cx), ["open 2026-09-16"]);
}
#[gpui::test]
fn a_chip_names_its_event_and_leaves_the_day(cx: &mut TestAppContext) {
let (host, cx) = planning(month, cx);
let chip = cx
.debug_bounds("event-chip m0")
.expect("the first meeting shows");
let origin = chip.origin + gpui::point(px(4.0), px(4.0));
for click_count in [1, 2] {
cx.simulate_event(MouseDownEvent {
button: MouseButton::Left,
position: origin,
modifiers: Modifiers::none(),
click_count,
first_mouse: false,
});
cx.simulate_event(MouseUpEvent {
button: MouseButton::Left,
position: origin,
modifiers: Modifiers::none(),
click_count,
});
settle(cx);
}
assert_eq!(
heard(&host, cx),
["event m0", "event m0"],
"a double press on a chip does not open its day"
);
}
#[test]
#[should_panic(expected = "event m0 twice")]
fn an_event_is_listed_once() {
let twin = Event::timed("m0", "Again", at(1, 9), at(1, 10), 0);
let _ = CalendarMonthView::new("month", date(2026, 9, 1), [events()[0].clone(), twin]);
}
fn year(_: &Planning, owner: Entity<Planning>) -> gpui::AnyElement {
let (days, years) = (owner.clone(), owner);
YearView::new("year", 2026, events())
.zone(TimeZone::UTC)
.today(date(2026, 9, 16))
.on_day(move |day, _, cx| note(&days, format!("open {day}"), cx))
.on_year(move |year, _, cx| note(&years, format!("year {year}"), cx))
.into_any_element()
}
#[gpui::test]
fn a_year_walks_by_day_and_month_into_the_next(cx: &mut TestAppContext) {
let (host, cx) = planning(year, cx);
tab_to(4, cx);
for key in [
"pagedown", "pagedown", "pagedown", "pagedown", "right", "enter",
] {
press(key, cx);
}
assert_eq!(heard(&host, cx), ["year 2027", "open 2027-01-17"]);
}
/// A month view whose owner keeps the month and hands each turn back.
fn echoing(host: &Planning, owner: Entity<Planning>) -> gpui::AnyElement {
let (days, months) = (owner.clone(), owner);
CalendarMonthView::new("month", host.month, events())
.zone(TimeZone::UTC)
.today(date(2026, 9, 16))
.on_day(move |day, _, cx| note(&days, format!("open {day}"), cx))
.on_month(move |first, _, cx| {
months.update(cx, |host, cx| {
host.month = first;
cx.notify();
})
})
.into_any_element()
}
#[gpui::test]
fn the_owners_echo_of_a_turn_keeps_the_day(cx: &mut TestAppContext) {
let (host, cx) = planning(echoing, cx);
tab_to(4, cx);
press("pagedown", cx);
press("enter", cx);
assert_eq!(heard(&host, cx), ["open 2026-10-16"]);
}
/// A year view whose owner keeps the year and hands each turn back.
fn echoing_year(host: &Planning, owner: Entity<Planning>) -> gpui::AnyElement {
let (days, years) = (owner.clone(), owner);
YearView::new("year", host.month.year(), events())
.zone(TimeZone::UTC)
.today(date(2026, 9, 16))
.on_day(move |day, _, cx| note(&days, format!("open {day}"), cx))
.on_year(move |year, _, cx| {
years.update(cx, |host, cx| {
host.month = date(year, 1, 1);
cx.notify();
})
})
.into_any_element()
}
#[gpui::test]
fn the_owners_echo_of_a_year_keeps_the_day(cx: &mut TestAppContext) {
let (host, cx) = planning(echoing_year, cx);
tab_to(4, cx);
for key in ["pagedown", "pagedown", "pagedown", "pagedown", "enter"] {
press(key, cx);
}
assert_eq!(heard(&host, cx), ["open 2027-01-16"]);
}