ElyShow: typed notes survive a refused send and a read while typing (S-08)
This commit is contained in:
7 files changed
+106
-30
No files matched your search
@@ -20,7 +20,7 @@ pub struct NotesField {
|
||||
field: Option<Entity<TextInput>>,
|
||||
shown: Option<(u64, usize, String)>,
|
||||
/// The state the shown slide number was read in.
|
||||
at: Option<ely_lok::Doc>,
|
||||
pub(crate) at: Option<ely_lok::Doc>,
|
||||
_events: Option<gpui::Subscription>,
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ impl Deck {
|
||||
return log::warn!("show: notes for slide {part} have no read state");
|
||||
};
|
||||
log::info!("show: notes for slide {part} changed");
|
||||
self.notes.shown = Some((tab, part, text.clone()));
|
||||
// `shown` stays the kernel's until a read confirms the send.
|
||||
self.run_on(tab, &at, "notes.set", &[&text, &part.to_string()], cx);
|
||||
self.force_reread(tab, cx);
|
||||
}
|
||||
@@ -90,6 +90,9 @@ impl Deck {
|
||||
if !typing && self.notes.shown.as_ref() != Some(&read) {
|
||||
field.update(cx, |field, cx| field.set_text(notes.text, cx));
|
||||
self.notes.shown = Some(read);
|
||||
} else if let Some(shown) = self.notes.shown.as_mut().filter(|shown| (shown.0, shown.1) == (id, part)) {
|
||||
// A read while typing moves the base, not the draft.
|
||||
shown.2 = notes.text;
|
||||
}
|
||||
Some(field)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use gpui::{AppContext as _, Entity, TestAppContext};
|
||||
use crate::deck::Deck;
|
||||
|
||||
mod motion;
|
||||
mod notes;
|
||||
mod objects;
|
||||
mod output;
|
||||
mod play;
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
//! The notes field under the slide (SHOW-08).
|
||||
|
||||
use ely_office::prelude::*;
|
||||
use gpui::TestAppContext;
|
||||
|
||||
use super::objects::blank;
|
||||
use super::slides::{in_window, type_in};
|
||||
use super::{settle, update};
|
||||
|
||||
#[test]
|
||||
fn typed_notes_reach_the_slide() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let mut cx = TestAppContext::single();
|
||||
let Some(deck) = blank(&mut cx, dir.path()) else {
|
||||
eprintln!("SKIP: no LOK build");
|
||||
return;
|
||||
};
|
||||
let notes = |app: &gpui::App| {
|
||||
deck.read(app)
|
||||
.active_tab()
|
||||
.and_then(|tab| tab.extra.notes.clone())
|
||||
};
|
||||
settle(&cx, |app| notes(app).is_some());
|
||||
let field = in_window(&mut cx, &deck, |deck, window, cx| {
|
||||
deck.notes_input(window, cx)
|
||||
});
|
||||
let field = field.expect("the field");
|
||||
type_in(&mut cx, &field, "Start with the totals");
|
||||
update(&mut cx, &deck, |deck, cx| deck.commit_notes(cx));
|
||||
settle(&cx, |app| {
|
||||
notes(app).is_some_and(|notes| notes.text == "Start with the totals")
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notes_refused_as_stale_stay_typed_and_go_next_time() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let mut cx = TestAppContext::single();
|
||||
let Some(deck) = blank(&mut cx, dir.path()) else {
|
||||
eprintln!("SKIP: no LOK build");
|
||||
return;
|
||||
};
|
||||
let notes = |app: &gpui::App| deck.read(app).active_tab().and_then(|tab| tab.extra.notes.clone());
|
||||
let text = |app: &gpui::App| notes(app).map(|notes| notes.text);
|
||||
settle(&cx, |app| notes(app).is_some());
|
||||
let field = in_window(&mut cx, &deck, |deck, window, cx| deck.notes_input(window, cx)).expect("the field");
|
||||
let stale = cx.update(|app| deck.read(app).notes.at.clone());
|
||||
type_in(&mut cx, &field, "first");
|
||||
update(&mut cx, &deck, |deck, cx| deck.commit_notes(cx));
|
||||
settle(&cx, |app| text(app).as_deref() == Some("first"));
|
||||
in_window(&mut cx, &deck, |deck, window, cx| deck.notes_input(window, cx));
|
||||
// Typing began before the edit above was read back.
|
||||
type_in(&mut cx, &field, "second");
|
||||
update(&mut cx, &deck, |deck, cx| {
|
||||
deck.notes.at = stale;
|
||||
deck.commit_notes(cx);
|
||||
});
|
||||
settle(&cx, |app| deck.read(app).active_tab().is_some_and(|tab| tab.view.read(app).settled()));
|
||||
cx.run_until_parked();
|
||||
in_window(&mut cx, &deck, |deck, window, cx| deck.notes_input(window, cx));
|
||||
assert_eq!(cx.update(|app| field.read(app).text().to_string()), "second", "the typing stays");
|
||||
assert_eq!(cx.update(|app| text(app)).as_deref(), Some("first"), "the stale send was refused");
|
||||
update(&mut cx, &deck, |deck, cx| deck.commit_notes(cx));
|
||||
settle(&cx, |app| text(app).as_deref() == Some("second"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_read_while_typing_keeps_the_new_draft() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let mut cx = TestAppContext::single();
|
||||
let Some(deck) = blank(&mut cx, dir.path()) else {
|
||||
eprintln!("SKIP: no LOK build");
|
||||
return;
|
||||
};
|
||||
let text = |app: &gpui::App| deck.read(app).active_tab().and_then(|tab| tab.extra.notes.clone()).map(|notes| notes.text);
|
||||
settle(&cx, |app| text(app).is_some());
|
||||
let field = in_window(&mut cx, &deck, |deck, window, cx| deck.notes_input(window, cx)).expect("the field");
|
||||
type_in(&mut cx, &field, "first");
|
||||
update(&mut cx, &deck, |deck, cx| deck.commit_notes(cx));
|
||||
// Back in the field before the send is read back.
|
||||
in_window(&mut cx, &deck, |_, window, cx| window.focus(&gpui::Focusable::focus_handle(field.read(cx), cx), cx));
|
||||
type_in(&mut cx, &field, "second");
|
||||
settle(&cx, |app| text(app).as_deref() == Some("first"));
|
||||
in_window(&mut cx, &deck, |deck, window, cx| deck.notes_input(window, cx));
|
||||
// The blur redraws before its commit runs.
|
||||
in_window(&mut cx, &deck, |deck, window, cx| {
|
||||
window.blur(cx);
|
||||
deck.notes_input(window, cx)
|
||||
});
|
||||
assert_eq!(cx.update(|app| field.read(app).text().to_string()), "second", "the draft stays");
|
||||
update(&mut cx, &deck, |deck, cx| deck.commit_notes(cx));
|
||||
settle(&cx, |app| text(app).as_deref() == Some("second"));
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use crate::deck::Deck;
|
||||
use crate::dialog::Dialog;
|
||||
|
||||
/// Runs `f` on the deck inside its window.
|
||||
fn in_window<R>(
|
||||
pub(super) fn in_window<R>(
|
||||
cx: &mut TestAppContext,
|
||||
deck: &Entity<Deck>,
|
||||
f: impl FnOnce(&mut Deck, &mut Window, &mut gpui::Context<Deck>) -> R,
|
||||
@@ -25,7 +25,7 @@ fn in_window<R>(
|
||||
.expect("the window lives")
|
||||
}
|
||||
|
||||
fn type_in(cx: &mut TestAppContext, field: &Entity<TextInput>, text: &str) {
|
||||
pub(super) fn type_in(cx: &mut TestAppContext, field: &Entity<TextInput>, text: &str) {
|
||||
let text = text.to_string();
|
||||
cx.update(|app| field.update(app, |field, cx| field.set_text(text, cx)));
|
||||
}
|
||||
@@ -121,31 +121,6 @@ fn an_open_dialog_is_not_pushed_aside() {
|
||||
assert!(kept, "the table dialog stays");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typed_notes_reach_the_slide() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let mut cx = TestAppContext::single();
|
||||
let Some(deck) = blank(&mut cx, dir.path()) else {
|
||||
eprintln!("SKIP: no LOK build");
|
||||
return;
|
||||
};
|
||||
let notes = |app: &gpui::App| {
|
||||
deck.read(app)
|
||||
.active_tab()
|
||||
.and_then(|tab| tab.extra.notes.clone())
|
||||
};
|
||||
settle(&cx, |app| notes(app).is_some());
|
||||
let field = in_window(&mut cx, &deck, |deck, window, cx| {
|
||||
deck.notes_input(window, cx)
|
||||
});
|
||||
let field = field.expect("the field");
|
||||
type_in(&mut cx, &field, "Start with the totals");
|
||||
update(&mut cx, &deck, |deck, cx| deck.commit_notes(cx));
|
||||
settle(&cx, |app| {
|
||||
notes(app).is_some_and(|notes| notes.text == "Start with the totals")
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_comment_lands_on_the_shown_slide() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
|
||||
Reference in new issue
Block a user