| 1 | //! Press the advertised work-bar chord through a real terminal decoder, |
| 2 | //! including the fresh-session screen where the hint first appears. |
| 3 | |
| 4 | use std::time::Duration; |
| 5 | |
| 6 | use super::qa_harness::{ |
| 7 | harness::{Harness, make_sealed_workspace}, |
| 8 | keys, |
| 9 | }; |
| 10 | |
| 11 | #[test] |
| 12 | fn work_bar_opens_from_launch_with_legacy_and_enhanced_keys() { |
| 13 | for (rows, cols) in [(12, 40), (16, 60), (24, 80), (32, 100), (40, 140)] { |
| 14 | let workspace = make_sealed_workspace().expect("sealed workspace"); |
| 15 | std::fs::write(workspace.home().join(".codewhale/.onboarded"), "").unwrap(); |
| 16 | let trust = workspace.workspace().join(".deepseek"); |
| 17 | std::fs::create_dir_all(&trust).unwrap(); |
| 18 | std::fs::write(trust.join("trusted"), "").unwrap(); |
| 19 | let mut tui = Harness::builder(Harness::cargo_bin("codewhale-tui")) |
| 20 | .cwd(workspace.workspace()) |
| 21 | .clear_env() |
| 22 | .seal_home(workspace.home()) |
| 23 | .env("CODEWHALE_DISABLE_MODELS_DEV_FETCH", "1") |
| 24 | .env("CODEWHALE_NO_UPDATE_CHECK", "1") |
| 25 | .env("NO_ANIMATIONS", "1") |
| 26 | .env("RUST_LOG", "warn") |
| 27 | .args([ |
| 28 | "--workspace", |
| 29 | workspace.workspace().to_str().unwrap(), |
| 30 | "--no-project-config", |
| 31 | "--mouse-capture", |
| 32 | ]) |
| 33 | .size(rows, cols) |
| 34 | .spawn() |
| 35 | .expect("start TUI"); |
| 36 | tui.wait_for_text("Choose your model provider", Duration::from_secs(15)) |
| 37 | .unwrap(); |
| 38 | tui.send(keys::key::ctrl('o')).unwrap(); |
| 39 | tui.wait_for_text("You're ready.", Duration::from_secs(5)) |
| 40 | .unwrap(); |
| 41 | tui.send(keys::key::enter()).unwrap(); |
| 42 | tui.wait_for_text("New session", Duration::from_secs(15)) |
| 43 | .unwrap(); |
| 44 | tui.wait_for_idle(Duration::from_millis(250), Duration::from_secs(5)) |
| 45 | .unwrap(); |
| 46 | |
| 47 | super::launch_card_pty::capture(&mut tui, "workbar-closed"); |
| 48 | |
| 49 | // Legacy Ctrl+] is one byte, decoded by crossterm as Ctrl+5. |
| 50 | tui.send([0x1d]).unwrap(); |
| 51 | tui.wait_for_text("no agents have run this session", Duration::from_secs(5)) |
| 52 | .unwrap_or_else(|error| panic!("{cols}x{rows}: legacy Ctrl+] failed: {error}")); |
| 53 | |
| 54 | super::launch_card_pty::capture(&mut tui, "workbar-fleet"); |
| 55 | |
| 56 | // The enhanced backward chord must work on the same launch screen. |
| 57 | tui.send(b"\x1b[9;6u").unwrap(); |
| 58 | tui.wait_for_text("no to-dos yet", Duration::from_secs(5)) |
| 59 | .unwrap(); |
| 60 | super::launch_card_pty::capture(&mut tui, "workbar-tasks"); |
| 61 | // The visible close control and keyboard Escape share the close action. |
| 62 | let (row, col) = tui.frame().find_text("×").expect("visible close control"); |
| 63 | tui.send(keys::mouse::click(row, col)).unwrap(); |
| 64 | tui.wait_for( |
| 65 | |frame| !frame.contains("no to-dos yet"), |
| 66 | Duration::from_secs(5), |
| 67 | ) |
| 68 | .unwrap(); |
| 69 | |
| 70 | tui.send(b"\x1b[93;5u").unwrap(); |
| 71 | tui.wait_for_text("no agents have run this session", Duration::from_secs(5)) |
| 72 | .unwrap_or_else(|error| panic!("{cols}x{rows}: enhanced Ctrl+] failed: {error}")); |
| 73 | |
| 74 | // Ordinary bracket/digit text still reaches the composer; the legacy |
| 75 | // chord also works after typing has dismissed the launch card. |
| 76 | tui.send("draft ]5").unwrap(); |
| 77 | tui.wait_for_text("draft ]5", Duration::from_secs(5)) |
| 78 | .unwrap(); |
| 79 | tui.wait_for_idle(Duration::from_millis(250), Duration::from_secs(5)) |
| 80 | .unwrap(); |
| 81 | tui.send([0x1d]).unwrap(); |
| 82 | tui.wait_for_text("nothing running in the background", Duration::from_secs(5)) |
| 83 | .unwrap(); |
| 84 | tui.shutdown(); |
| 85 | } |
| 86 | } |
| 87 |