use std::time::Duration;
use crate::env::Env;
use crate::keymap::{KeyChord, Scope};
use crate::runtime::{App, Command, Harness};
use crate::widget::View;
use crate::widgets::TextInput;
const ARRIVING: [&str; 2] = ["late", "later"];
struct Late {
fields: Vec<(&'static str, String)>,
arrived: bool,
}
impl Default for Late {
fn default() -> Self {
let fields = ["other", "second"].into_iter().map(|name| (name, String::new())).collect();
Self { fields, arrived: false }
}
}
impl Late {
fn text(&self, name: &str) -> Option<&str> {
self.fields.iter().find(|(known, _)| *known == name).map(|(_, value)| value.as_str())
}
}
#[derive(Clone)]
enum Msg {
Ask(&'static str),
Arrive,
Typed(&'static str, String),
}
impl App for Late {
type Msg = Msg;
fn update(&mut self, msg: Msg) -> Command<Msg> {
match msg {
Msg::Ask(name) => Command::focus(name),
Msg::Arrive => {
self.arrived = true;
for name in ARRIVING {
if !self.fields.iter().any(|(known, _)| *known == name) {
self.fields.push((name, String::new()));
}
}
Command::none()
}
Msg::Typed(name, text) => {
if let Some(field) = self.fields.iter_mut().find(|(known, _)| *known == name) {
field.1 = text;
}
Command::none()
}
}
}
fn action(&self, name: &str) -> Option<Msg> {
Some(match name {
"ask-late" => Msg::Ask("late"),
"ask-later" => Msg::Ask("later"),
"arrive" => Msg::Arrive,
_ => return None,
})
}
fn view(&self, ui: &mut View<'_, Msg>) {
for (name, value) in &self.fields {
let name = *name;
if !self.arrived && ARRIVING.contains(&name) {
continue;
}
ui.add(TextInput::new(value).placeholder(name).on_change(move |text| Msg::Typed(name, text))).id(name);
}
}
}
fn chord(text: &str) -> KeyChord {
text.parse().expect("valid chord")
}
fn window() -> Harness<Late> {
let mut env = Env::builtin();
for (text, action) in [("f6", "ask-late"), ("f7", "ask-later"), ("f8", "arrive")] {
env.keymap_mut().bind(Scope::App, action, &[chord(text)]);
}
let mut h = Harness::with_env(Late::default(), env, 40, 8);
h.press("tab");
assert!(h.is_focused("other"), "the keyboard starts in the field that is drawn:\n{}", h.screen());
h
}
fn frames_passing(h: &mut Harness<Late>) {
for _ in 0..3 {
h.advance(Duration::from_millis(500));
}
}
#[test]
fn a_focus_asked_before_a_field_is_drawn_reaches_it_when_it_arrives() {
let mut h = window();
h.press("f6");
frames_passing(&mut h);
assert!(!h.is_focused("late"), "the field is not on screen yet:\n{}", h.screen());
h.press("f8");
h.type_text("abc");
assert!(h.is_focused("late"), "the keys went to the field the command named:\n{}", h.screen());
assert_eq!(h.app().text("late"), Some("abc"));
}
#[test]
fn a_press_while_a_focus_waits_ends_the_wait() {
let mut h = window();
h.press("f6");
h.click_text("second");
frames_passing(&mut h);
h.press("f8");
h.type_text("abc");
assert!(h.is_focused("second"), "the press decided where the keyboard goes:\n{}", h.screen());
assert_eq!(h.app().text("second"), Some("abc"));
assert!(!h.is_focused("late"), "the field that arrived later took nothing:\n{}", h.screen());
}
#[test]
fn tab_while_a_focus_waits_ends_the_wait() {
let mut h = window();
h.press("f6");
h.press("tab");
frames_passing(&mut h);
h.press("f8");
h.type_text("abc");
assert!(h.is_focused("second"), "the keys went where Tab put them:\n{}", h.screen());
assert_eq!(h.app().text("second"), Some("abc"));
}
#[test]
fn a_later_request_replaces_the_one_still_waiting() {
let mut h = window();
h.press("f6");
h.press("f7");
frames_passing(&mut h);
h.press("f8");
h.type_text("abc");
assert!(h.is_focused("later"), "the last request is the one that waited:\n{}", h.screen());
assert_eq!(h.app().text("later"), Some("abc"));
assert!(!h.is_focused("late"), "the request it replaced took nothing:\n{}", h.screen());
}