Split core and TUI crates
This commit is contained in:
167
crates/tele-tui/tests/e2e_termwright.rs
Normal file
167
crates/tele-tui/tests/e2e_termwright.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
#![cfg(feature = "test-support")]
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
use termwright::prelude::*;
|
||||
|
||||
fn fixture_path() -> &'static str {
|
||||
env!("CARGO_BIN_EXE_tele-tui-test-fixture")
|
||||
}
|
||||
|
||||
async fn spawn_fixture(scenario: &str) -> Result<Terminal> {
|
||||
let mut builder = Terminal::builder()
|
||||
.size(100, 30)
|
||||
.working_dir(env!("CARGO_MANIFEST_DIR"));
|
||||
|
||||
if let Some(lib_path) = tdlib_library_path() {
|
||||
builder = builder
|
||||
.env("DYLD_LIBRARY_PATH", &lib_path)
|
||||
.env("LD_LIBRARY_PATH", &lib_path);
|
||||
}
|
||||
let command = format!(
|
||||
"stty -echo -ixon; exec {} --scenario {}",
|
||||
shell_quote(fixture_path()),
|
||||
shell_quote(scenario)
|
||||
);
|
||||
builder.spawn("/bin/sh", &["-lc", &command]).await
|
||||
}
|
||||
|
||||
fn shell_quote(value: &str) -> String {
|
||||
format!("'{}'", value.replace('\'', "'\\''"))
|
||||
}
|
||||
|
||||
fn tdlib_library_path() -> Option<String> {
|
||||
let build_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("target")
|
||||
.join("debug")
|
||||
.join("build");
|
||||
let entries = std::fs::read_dir(build_dir).ok()?;
|
||||
|
||||
let mut paths = Vec::new();
|
||||
for entry in entries.flatten() {
|
||||
let lib_dir = entry.path().join("out").join("tdlib").join("lib");
|
||||
if lib_dir.join("libtdjson.1.8.29.dylib").exists() || lib_dir.join("libtdjson.so").exists()
|
||||
{
|
||||
paths.push(lib_dir);
|
||||
}
|
||||
}
|
||||
|
||||
(!paths.is_empty()).then(|| {
|
||||
paths
|
||||
.into_iter()
|
||||
.map(|path| path.to_string_lossy().into_owned())
|
||||
.collect::<Vec<_>>()
|
||||
.join(":")
|
||||
})
|
||||
}
|
||||
|
||||
async fn stop_fixture(term: &mut Terminal) {
|
||||
let _ = tokio::time::timeout(Duration::from_millis(500), term.send_key(Key::F(10))).await;
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
let _ = std::process::Command::new("pkill")
|
||||
.arg("-f")
|
||||
.arg("tele-tui-test-fixture")
|
||||
.status();
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
let _ = tokio::time::timeout(Duration::from_secs(1), term.kill()).await;
|
||||
}
|
||||
|
||||
async fn wait_for_text(term: &Terminal, needle: &str) -> Result<()> {
|
||||
let started = Instant::now();
|
||||
let mut last_screen = String::new();
|
||||
for _ in 0..100 {
|
||||
let Ok(screen) = screen_text(term).await else {
|
||||
continue;
|
||||
};
|
||||
if screen.contains(needle) {
|
||||
return Ok(());
|
||||
}
|
||||
last_screen = screen;
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
|
||||
let elapsed = started.elapsed();
|
||||
Err(TermwrightError::Timeout {
|
||||
condition: format!("text '{needle}' to appear\n\n{last_screen}"),
|
||||
timeout: elapsed,
|
||||
})
|
||||
}
|
||||
|
||||
async fn screen_text(term: &Terminal) -> Result<String> {
|
||||
tokio::time::timeout(Duration::from_millis(500), term.screen())
|
||||
.await
|
||||
.map(|screen| screen.text())
|
||||
.map_err(|_| TermwrightError::Timeout {
|
||||
condition: "terminal screen snapshot".to_string(),
|
||||
timeout: Duration::from_millis(500),
|
||||
})
|
||||
}
|
||||
|
||||
async fn enter_insert_mode(term: &Terminal) -> Result<()> {
|
||||
for _ in 0..5 {
|
||||
term.send_key(Key::Char('i')).await?;
|
||||
std::thread::sleep(Duration::from_millis(150));
|
||||
if !screen_text(term).await?.contains("Press i to type") {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let screen = screen_text(term).await?;
|
||||
Err(TermwrightError::Timeout {
|
||||
condition: format!("insert mode to start\n\n{screen}"),
|
||||
timeout: Duration::from_millis(750),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e2e_termwright_user_flows() -> Result<()> {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(2)
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("failed to build e2e runtime");
|
||||
|
||||
let result = runtime.block_on(async {
|
||||
tokio::time::timeout(Duration::from_secs(15), compose_and_send_message()).await
|
||||
});
|
||||
kill_fixture_processes();
|
||||
|
||||
match result {
|
||||
Ok(result) => result,
|
||||
Err(_) => Err(TermwrightError::Timeout {
|
||||
condition: "termwright e2e user flow".to_string(),
|
||||
timeout: Duration::from_secs(15),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
async fn compose_and_send_message() -> Result<()> {
|
||||
let mut term = spawn_fixture("compose-draft").await?;
|
||||
let result = async {
|
||||
wait_for_text(&term, "Work Group").await?;
|
||||
wait_for_text(&term, "Standup notes are ready").await?;
|
||||
wait_for_text(&term, "hello from e2e").await?;
|
||||
enter_insert_mode(&term).await?;
|
||||
wait_for_text(&term, "hello from e2e").await?;
|
||||
term.send_key(Key::Enter).await?;
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
|
||||
let screen = screen_text(&term).await?;
|
||||
assert!(screen.contains("hello from e2e"), "sent message should appear\n\n{}", screen);
|
||||
assert!(
|
||||
!screen.contains("Сообщение: hello from e2e"),
|
||||
"compose input should clear after send"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
.await;
|
||||
|
||||
stop_fixture(&mut term).await;
|
||||
result
|
||||
}
|
||||
|
||||
fn kill_fixture_processes() {
|
||||
let _ = std::process::Command::new("pkill")
|
||||
.arg("-f")
|
||||
.arg("tele-tui-test-fixture")
|
||||
.status();
|
||||
}
|
||||
Reference in New Issue
Block a user