Files
ElyOffice/apps/ely-show/src/tests/play.rs
T
ZacharyZhang-NY b7fca0146d Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git),
ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
2026-10-08 21:20:08 -04:00

148 lines
6.2 KiB
Rust

//! The show: keys, hidden slides, black, laser (C10.5).
use ely_office::prelude::*;
use gpui::{Entity, Keystroke, TestAppContext};
use super::objects::{act, blank};
use super::{settle, update};
use crate::deck::Deck;
use crate::slideshow::{Show, steps::Place};
fn press(cx: &mut TestAppContext, show: &Entity<Show>, key: &str) -> bool {
let key = Keystroke::parse(key).expect("a key");
cx.update(|app| show.update(app, |show, cx| show.key(&key, cx)))
}
/// Keys typed into the show's own window.
pub(super) fn typed(cx: &mut TestAppContext, show: &Entity<Show>, keys: &str) {
let window = cx.update(|app| show.read(app).windows[0]);
cx.simulate_keystrokes(window, keys);
}
pub(super) fn place(cx: &mut TestAppContext, show: &Entity<Show>) -> Place {
cx.update(|app| show.read(app).place)
}
/// Slide two: click effect, notes. Three: hidden.
pub(super) fn three_slides(cx: &mut TestAppContext, deck: &Entity<Deck>) {
let slides = |app: &gpui::App| {
let tab = deck.read(app).active_tab();
tab.map(|tab| tab.extra.slides.list.iter().map(|s| s.visible).collect::<Vec<_>>())
};
update(cx, deck, |deck, cx| deck.design_command("slide.insert", &[], cx));
settle(cx, |app| slides(app).is_some_and(|list| list.len() == 2));
act(cx, deck, ("draw.shape", &["rectangle", "true"]), |o| {
o.objects.len() == 3
});
act(cx, deck, ("object.select", &["2", "false"]), |o| {
o.selection.is_some()
});
let add = serde_json::json!({ "preset": "ooo-entrance-fade-in" }).to_string();
let effects = |app: &gpui::App| {
let tab = deck.read(app).active_tab();
tab.and_then(|tab| tab.extra.motion.as_ref().map(|(_, e)| e.len()))
};
update(cx, deck, |deck, cx| deck.design_command("animation.add", &[&add], cx));
settle(cx, |app| effects(app) == Some(1));
update(cx, deck, |deck, cx| {
deck.design_command("notes.set", &["Say hello", "1"], cx);
deck.design_command("slide.insert", &[], cx);
});
settle(cx, |app| slides(app).is_some_and(|list| list.len() == 3));
update(cx, deck, |deck, cx| deck.design_command("slide.hide", &[], cx));
settle(cx, |app| slides(app) == Some(vec![true, true, false]));
}
#[test]
fn a_show_plays_clicks_skips_hidden_and_ends() {
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;
};
three_slides(&mut cx, &deck);
update(&mut cx, &deck, |deck, cx| deck.start_show(false, cx));
settle(&cx, |app| deck.read(app).show.is_some());
let show = cx.update(|app| deck.read(app).show.clone().expect("playing"));
settle(&cx, |app| {
let show = show.read(app);
show.stack(0).is_some() && show.stack(1).is_some()
});
let windows = cx.update(|app| show.read(app).windows.len());
assert_eq!(windows, 1, "one screen: no presenter view");
assert_eq!(place(&mut cx, &show), Place::Slide { part: 0, clicks: 0 });
typed(&mut cx, &show, "right");
assert_eq!(place(&mut cx, &show), Place::Slide { part: 1, clicks: 0 });
let notes = cx.update(|app| show.read(app).slides[1].notes.clone());
assert_eq!(notes, "Say hello");
press(&mut cx, &show, "space");
assert_eq!(place(&mut cx, &show), Place::Slide { part: 1, clicks: 1 });
press(&mut cx, &show, "right");
assert_eq!(place(&mut cx, &show), Place::End, "the hidden slide is passed");
press(&mut cx, &show, "left");
assert_eq!(place(&mut cx, &show), Place::Slide { part: 1, clicks: 1 });
press(&mut cx, &show, "1");
press(&mut cx, &show, "enter");
assert_eq!(place(&mut cx, &show), Place::Slide { part: 0, clicks: 0 });
press(&mut cx, &show, "b");
press(&mut cx, &show, "l");
assert!(cx.update(|app| show.read(app).black && show.read(app).laser));
assert!(!press(&mut cx, &show, "cmd-q"), "app keys pass through");
let before = cx.update(|app| app.windows().len());
typed(&mut cx, &show, "escape");
settle(&cx, |app| deck.read(app).show.is_none());
assert_eq!(cx.update(|app| app.windows().len()), before - 1, "the show window closes");
}
#[test]
fn from_here_starts_on_the_shown_slide_even_hidden() {
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;
};
three_slides(&mut cx, &deck);
update(&mut cx, &deck, |deck, cx| deck.start_show(true, cx));
settle(&cx, |app| deck.read(app).show.is_some());
let show = cx.update(|app| deck.read(app).show.clone().expect("playing"));
assert_eq!(place(&mut cx, &show), Place::Slide { part: 2, clicks: 0 });
press(&mut cx, &show, "right");
assert_eq!(place(&mut cx, &show), Place::End);
press(&mut cx, &show, "right");
settle(&cx, |app| deck.read(app).show.is_none());
}
#[test]
fn a_click_on_media_finds_it_and_elsewhere_does_not() {
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 clip = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../tests/fixtures/media/clip.mp4");
let url = ely_lok::file_url(&clip);
update(&mut cx, &deck, |deck, cx| {
deck.design_command("media.insert", &[&url, "false", "8000", "4500"], cx)
});
settle(&cx, |app| {
deck.read(app).active_tab().is_some_and(|tab| tab.extra.media.len() == 1)
});
update(&mut cx, &deck, |deck, cx| deck.start_show(true, cx));
settle(&cx, |app| deck.read(app).show.is_some());
let show = cx.update(|app| deck.read(app).show.clone().expect("playing"));
let (bounds, size) = cx.update(|app| {
let show = show.read(app);
(show.slides[0].media[0].bounds, show.slide)
});
let centre = (
(bounds.0 + bounds.2 / 2) as f32 / size.0,
(bounds.1 + bounds.3 / 2) as f32 / size.1,
);
assert_eq!(cx.update(|app| show.read(app).media_at(centre)), Some(0));
assert_eq!(cx.update(|app| show.read(app).media_at((0.01, 0.01))), None);
}