Primitives · raise: what opens inside a dialog lies over it
gpui 0.2.2 asserts on a deferred draw inside another, and every dialog, sheet and popover draws inside one, so opening a date field in the snooze or schedule dialog panicked, as would any list, menu or picker opened in an overlay. primitives::raise replaces each deferred draw: at the top it defers as before; inside something raised it leaves a placeholder in the tree, and its layer lays it out on its own, then draws it last. check.sh refuses gpui's deferred anywhere else. The time dialog's rule is now times::ahead, tested for a moment behind, now itself, a moment ahead and another zone; a gpui test picks yesterday in the snooze dialog, sees that it has passed and that nothing is sent, then picks two days ahead and hears that moment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5b0a8443a7
commit
9abab5f2ba
20 files changed
+405
-49
No files matched your search
+34
-2
@@ -2,12 +2,13 @@ use gpui::{
|
||||
Context, Entity, IntoElement, Modifiers, ParentElement, Render, SharedString, Styled,
|
||||
TestAppContext, VisualTestContext, Window, div, px,
|
||||
};
|
||||
use jiff::{Timestamp, tz::TimeZone};
|
||||
use jiff::{Timestamp, ToSpan, tz::TimeZone};
|
||||
|
||||
use super::{Mailing, heard, mailing, note, press, settle, setup, tab_to};
|
||||
use crate::{
|
||||
forms,
|
||||
mail::{Label, LabelPicker, SnoozePicker, snooze::snoozes},
|
||||
mail::{Label, LabelPicker, SnoozePicker, snooze::snoozes, times::at_hour},
|
||||
typography::format::system_zone,
|
||||
};
|
||||
|
||||
/// Labels the host keeps, the keys on, and what it heard.
|
||||
@@ -166,3 +167,34 @@ fn the_last_row_asks_for_a_time_of_ones_own(cx: &mut TestAppContext) {
|
||||
assert!(cx.debug_bounds("time-dialog").is_some(), "the dialog shows");
|
||||
assert!(heard(&host, cx).is_empty(), "no time yet");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_dialog_takes_only_a_time_ahead_of_now(cx: &mut TestAppContext) {
|
||||
let (host, cx) = mailing(snoozing, cx);
|
||||
tab_to(1, cx);
|
||||
for key in [
|
||||
"enter", "up", "enter", "tab", "enter", "left", "enter", "escape",
|
||||
] {
|
||||
press(key, cx);
|
||||
}
|
||||
assert!(
|
||||
cx.debug_bounds("time-late").is_some(),
|
||||
"yesterday has passed"
|
||||
);
|
||||
for key in ["tab", "tab", "enter"] {
|
||||
press(key, cx);
|
||||
}
|
||||
assert!(heard(&host, cx).is_empty(), "Snooze waits for a time ahead");
|
||||
for key in [
|
||||
"right", "right", "right", "enter", "escape", "tab", "tab", "enter",
|
||||
] {
|
||||
press(key, cx);
|
||||
}
|
||||
let today = Timestamp::now().to_zoned(system_zone("a test")).date();
|
||||
let ahead = at_hour(
|
||||
today.checked_add(2.days()).expect("a day"),
|
||||
0,
|
||||
&TimeZone::UTC,
|
||||
);
|
||||
assert_eq!(heard(&host, cx), [format!("until {ahead}")]);
|
||||
}
|
||||
+53
-9
@@ -40,6 +40,15 @@ pub(super) fn named(name: &str, at: Timestamp, zone: &TimeZone) -> (String, Time
|
||||
(format!("{name} · {hour}"), at)
|
||||
}
|
||||
|
||||
/// The moment `chosen` names in `zone`, once it lies after `now`.
|
||||
pub(super) fn ahead(chosen: DateTime, zone: &TimeZone, now: Timestamp) -> Option<Timestamp> {
|
||||
let at = chosen
|
||||
.to_zoned(zone.clone())
|
||||
.expect("the zone holds the time")
|
||||
.timestamp();
|
||||
(at > now).then_some(at)
|
||||
}
|
||||
|
||||
/// `times` less each moment an earlier one names, as tomorrow names next Monday on a Sunday.
|
||||
pub(super) fn distinct(
|
||||
times: impl IntoIterator<Item = (String, Timestamp)>,
|
||||
@@ -99,16 +108,16 @@ pub(super) fn time_dialog(
|
||||
return None;
|
||||
}
|
||||
let chosen = now.chosen;
|
||||
let at = chosen.map(|chosen| {
|
||||
chosen
|
||||
.to_zoned(zone.clone())
|
||||
.expect("the zone holds the time")
|
||||
.timestamp()
|
||||
});
|
||||
let ahead = at.filter(|at| *at > Timestamp::now());
|
||||
let ahead = chosen.and_then(|chosen| ahead(chosen, zone, Timestamp::now()));
|
||||
let (closing, choosing, id) = (picking.clone(), picking.clone(), id.clone());
|
||||
let late = (at.is_some() && ahead.is_none())
|
||||
.then(|| FormError::new((id.clone(), "late"), "That time has passed."));
|
||||
let late = (chosen.is_some() && ahead.is_none()).then(|| {
|
||||
div()
|
||||
.debug_selector(|| "time-late".into())
|
||||
.child(FormError::new(
|
||||
(id.clone(), "late"),
|
||||
"That time has passed.",
|
||||
))
|
||||
});
|
||||
let dialog = Dialog::new((id.clone(), "dialog"), ask.title, move |_, cx| {
|
||||
closing.update(cx, |picking, cx| {
|
||||
*picking = Picking::default();
|
||||
@@ -155,3 +164,38 @@ pub(super) fn time_dialog(
|
||||
});
|
||||
Some(dialog)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use jiff::{
|
||||
SignedDuration,
|
||||
civil::date,
|
||||
tz::{TimeZone, offset},
|
||||
};
|
||||
|
||||
use super::{ahead, at_hour};
|
||||
|
||||
#[test]
|
||||
fn a_picked_time_counts_once_it_lies_ahead_of_now() {
|
||||
let day = date(2026, 9, 27);
|
||||
let now = at_hour(day, 9, &TimeZone::UTC);
|
||||
let utc = &TimeZone::UTC;
|
||||
assert_eq!(
|
||||
ahead(day.at(8, 59, 0, 0), utc, now),
|
||||
None,
|
||||
"a minute behind"
|
||||
);
|
||||
assert_eq!(ahead(day.at(9, 0, 0, 0), utc, now), None, "now itself");
|
||||
assert_eq!(
|
||||
ahead(day.at(9, 1, 0, 0), utc, now),
|
||||
Some(now + SignedDuration::from_mins(1)),
|
||||
"a minute ahead"
|
||||
);
|
||||
let tokyo = TimeZone::fixed(offset(9));
|
||||
assert_eq!(
|
||||
ahead(day.at(10, 0, 0, 0), &tokyo, now),
|
||||
None,
|
||||
"10:00 in Tokyo is 01:00 here"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user