Mail · Writing: the composer, recipients that suggest, a signature, send later
MailComposer writes a message: To, and Cc and Bcc once asked for, as tag fields that suggest contacts and mark what is not an address; the subject and the words; the files, each with a way off; the signature under the words. Send wakes once there is someone to send to and every address is one; it hands over the draft now or at a time, and Discard lets it go. A new start from the owner begins the draft over. forms::TagInput now suggests: the rows that hold the typed words float under it through the list Combobox uses; Up, Down and Enter pick, and Escape sets them aside. RecipientInput points to it. ScheduleSend offers tomorrow morning, tomorrow afternoon, Monday morning, or a day and hour picked in a dialog. SignatureEditor writes the lines and shows how they sit under a message; it tells the owner's echo from a new signature, so the caret stays.
This commit is contained in:
1 parent
1ad72bc7aa
commit
ccd48f9ba2
14 files changed
+1425
-12
No files matched your search
@@ -3,6 +3,7 @@ mod colors;
|
||||
mod dates;
|
||||
mod other;
|
||||
mod structure;
|
||||
mod tags;
|
||||
mod upload;
|
||||
|
||||
use gpui::{
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
use gpui::{
|
||||
Context, Entity, IntoElement, KeyUpEvent, Keystroke, Render, SharedString, TestAppContext,
|
||||
VisualTestContext, Window,
|
||||
};
|
||||
|
||||
use super::setup;
|
||||
use crate::forms::{Choice, TagInput};
|
||||
|
||||
/// A tag field offering two people, and the tags it asked for.
|
||||
struct Recipients {
|
||||
tags: Vec<SharedString>,
|
||||
asked: Vec<Vec<SharedString>>,
|
||||
}
|
||||
|
||||
impl Render for Recipients {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let owner = cx.entity();
|
||||
TagInput::new("to", self.tags.clone())
|
||||
.suggestions([
|
||||
Choice::new("ana@atrium.studio", "Ana Lima"),
|
||||
Choice::new("ben@atrium.studio", "Ben Ito"),
|
||||
])
|
||||
.on_change(move |next, _, cx| {
|
||||
owner.update(cx, |host, cx| {
|
||||
host.asked.push(next.clone());
|
||||
host.tags = next;
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn settle(cx: &mut VisualTestContext) {
|
||||
for _ in 0..3 {
|
||||
cx.run_until_parked();
|
||||
cx.update(|window, _| window.refresh());
|
||||
}
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
fn typing<'a>(
|
||||
words: &str,
|
||||
cx: &'a mut TestAppContext,
|
||||
) -> (Entity<Recipients>, &'a mut VisualTestContext) {
|
||||
setup(cx);
|
||||
let (host, cx) = cx.add_window_view(|_, _| Recipients {
|
||||
tags: Vec::new(),
|
||||
asked: Vec::new(),
|
||||
});
|
||||
cx.update(|window, _| {
|
||||
window.activate_window();
|
||||
window.focus_next();
|
||||
});
|
||||
settle(cx);
|
||||
cx.simulate_input(words);
|
||||
settle(cx);
|
||||
(host, cx)
|
||||
}
|
||||
|
||||
fn asked(host: &Entity<Recipients>, cx: &mut VisualTestContext) -> Vec<Vec<SharedString>> {
|
||||
host.read_with(cx, |host, _| host.asked.clone())
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn enter_adds_the_suggestion_the_words_find(cx: &mut TestAppContext) {
|
||||
let (host, cx) = typing("be", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
asked(&host, cx),
|
||||
[vec![SharedString::from("ben@atrium.studio")]]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn down_moves_to_the_next_suggestion(cx: &mut TestAppContext) {
|
||||
let (host, cx) = typing("i", cx);
|
||||
press("down", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
asked(&host, cx),
|
||||
[vec![SharedString::from("ben@atrium.studio")]],
|
||||
"Ana Lima and Ben Ito both hold an i; Down reaches Ben"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn words_with_no_suggestion_are_added_as_typed(cx: &mut TestAppContext) {
|
||||
let (host, cx) = typing("dev@atrium.studio", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
asked(&host, cx),
|
||||
[vec![SharedString::from("dev@atrium.studio")]]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn escape_sets_the_suggestions_aside(cx: &mut TestAppContext) {
|
||||
let (host, cx) = typing("be", cx);
|
||||
press("escape", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(asked(&host, cx), [vec![SharedString::from("be")]]);
|
||||
}
|
||||
Reference in new issue
Block a user