From 542d94dc88c7fa33ca2dd2e1e79f25616cbed3c8 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Mon, 30 Mar 2026 22:26:06 -0700 Subject: [PATCH] test: fix BTW TUI snapshots and blob size Co-authored-by: Codex --- ...hatwidget__tests__btw_footer_override.snap | 4 +- ...verride_preserves_general_footer_hint.snap | 4 +- ...rked_side_question_while_task_running.snap | 5 +- codex-rs/tui/src/chatwidget/tests.rs | 199 ++--------------- codex-rs/tui/src/chatwidget/tests/btw.rs | 206 ++++++++++++++++++ 5 files changed, 227 insertions(+), 191 deletions(-) create mode 100644 codex-rs/tui/src/chatwidget/tests/btw.rs diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__btw_footer_override.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__btw_footer_override.snap index bc3e6284cf..ed7aee0372 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__btw_footer_override.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__btw_footer_override.snap @@ -1,9 +1,9 @@ --- source: tui/src/chatwidget/tests.rs -expression: terminal.backend() +expression: rendered --- " " " " "› Ask Codex to do anything " " " -" BTW from main thread · Esc to return 100% context left " +" BTW from main thread · Esc to return " diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__clearing_thread_footer_override_preserves_general_footer_hint.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__clearing_thread_footer_override_preserves_general_footer_hint.snap index c88153b2a3..04e09ed69c 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__clearing_thread_footer_override_preserves_general_footer_hint.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__clearing_thread_footer_override_preserves_general_footer_hint.snap @@ -1,9 +1,9 @@ --- source: tui/src/chatwidget/tests.rs -expression: terminal.backend() +expression: rendered --- " " " " "› Ask Codex to do anything " " " -" Save and close external editor to continue. 100% context left " +" Save and close external editor to continue. " diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_btw_requests_forked_side_question_while_task_running.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_btw_requests_forked_side_question_while_task_running.snap index ad8b947610..d5ec18b5ce 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_btw_requests_forked_side_question_while_task_running.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__slash_btw_requests_forked_side_question_while_task_running.snap @@ -1,7 +1,6 @@ --- source: tui/src/chatwidget/tests.rs -assertion_line: 6666 -expression: terminal.backend() +expression: rendered --- " " "• Working (0s • esc to interrupt) " @@ -9,4 +8,4 @@ expression: terminal.backend() " " "› Ask Codex to do anything " " " -" BTW starting... 100% context left " +" BTW starting... " diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 9de92cb643..8e62096b9c 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -199,6 +199,8 @@ use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::unbounded_channel; use toml::Value as TomlValue; +mod btw; + async fn test_config() -> Config { // Use base defaults to avoid depending on host state. let codex_home = std::env::temp_dir(); @@ -682,25 +684,7 @@ async fn replayed_user_message_with_only_local_images_does_not_render_history_ce #[tokio::test] async fn forked_thread_history_line_includes_name_and_id_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; - chat.set_next_fork_banner_parent_label(Some("named parent".to_string())); - - let forked_from_id = - ThreadId::from_string("e9f18a88-8081-4e51-9d4e-8af5cde2d8dd").expect("forked id"); - chat.emit_forked_thread_event(forked_from_id); - - let history_cell = tokio::time::timeout(std::time::Duration::from_secs(2), async { - loop { - match rx.recv().await { - Some(AppEvent::InsertHistoryCell(cell)) => break cell, - Some(_) => continue, - None => panic!("app event channel closed before forked thread history was emitted"), - } - } - }) - .await - .expect("timed out waiting for forked thread history"); - let combined = lines_to_single_string(&history_cell.display_lines(/*width*/ 80)); + let combined = btw::forked_thread_history_line_includes_name_and_id_snapshot().await; assert!( combined.contains("Thread forked from"), @@ -711,24 +695,7 @@ async fn forked_thread_history_line_includes_name_and_id_snapshot() { #[tokio::test] async fn forked_thread_history_line_without_name_shows_id_once_snapshot() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; - - let forked_from_id = - ThreadId::from_string("019c2d47-4935-7423-a190-05691f566092").expect("forked id"); - chat.emit_forked_thread_event(forked_from_id); - - let history_cell = tokio::time::timeout(std::time::Duration::from_secs(2), async { - loop { - match rx.recv().await { - Some(AppEvent::InsertHistoryCell(cell)) => break cell, - Some(_) => continue, - None => panic!("app event channel closed before forked thread history was emitted"), - } - } - }) - .await - .expect("timed out waiting for forked thread history"); - let combined = lines_to_single_string(&history_cell.display_lines(/*width*/ 80)); + let combined = btw::forked_thread_history_line_without_name_shows_id_once_snapshot().await; assert_snapshot!("forked_thread_history_line_without_name", combined); } @@ -5908,23 +5875,7 @@ queued draft" #[tokio::test] async fn suppressed_interrupted_turn_notice_skips_history_warning() { - let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; - chat.thread_id = Some(ThreadId::new()); - chat.set_interrupted_turn_notice_mode(InterruptedTurnNoticeMode::Suppress); - chat.on_task_started(); - chat.on_agent_message_delta("partial output".to_string()); - - chat.on_interrupted_turn(TurnAbortReason::Interrupted); - - let inserted = drain_insert_history(&mut rx); - assert!( - inserted.iter().all(|cell| { - let rendered = lines_to_single_string(cell); - !rendered.contains("Conversation interrupted - tell the model what to do differently.") - && !rendered.contains("Model interrupted to submit steer instructions.") - }), - "unexpected interrupted-turn notice in BTW thread: {inserted:?}" - ); + btw::suppressed_interrupted_turn_notice_skips_history_warning().await; } #[tokio::test] @@ -7507,113 +7458,27 @@ async fn slash_fork_requests_current_fork() { assert_matches!(rx.try_recv(), Ok(AppEvent::ForkCurrentSession)); } -fn assert_btw_rename_rejected( - rx: &mut tokio::sync::mpsc::UnboundedReceiver, - op_rx: &mut tokio::sync::mpsc::UnboundedReceiver, -) { - let event = rx.try_recv().expect("expected BTW rename error"); - match event { - AppEvent::InsertHistoryCell(cell) => { - let rendered = lines_to_single_string(&cell.display_lines(80)); - assert!( - rendered.contains("BTW threads are ephemeral and cannot be renamed."), - "expected BTW rename error, got {rendered:?}" - ); - } - other => panic!("expected InsertHistoryCell error, got {other:?}"), - } - assert!(rx.try_recv().is_err(), "expected no follow-up events"); - assert!(op_rx.try_recv().is_err(), "expected no rename op"); -} - #[tokio::test] async fn slash_rename_is_rejected_for_btw_threads() { - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; - chat.set_thread_rename_block_message( - "BTW threads are ephemeral and cannot be renamed.".to_string(), - ); - - chat.dispatch_command(SlashCommand::Rename); - assert_btw_rename_rejected(&mut rx, &mut op_rx); + btw::slash_rename_is_rejected_for_btw_threads().await; } #[tokio::test] async fn slash_rename_with_args_is_rejected_for_btw_threads() { - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; - chat.set_thread_rename_block_message( - "BTW threads are ephemeral and cannot be renamed.".to_string(), - ); - - chat.dispatch_command_with_args(SlashCommand::Rename, "investigate".to_string(), Vec::new()); - assert_btw_rename_rejected(&mut rx, &mut op_rx); + btw::slash_rename_with_args_is_rejected_for_btw_threads().await; } #[tokio::test] async fn submit_user_message_as_plain_user_turn_does_not_run_shell_commands() { - let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await; - chat.thread_id = Some(ThreadId::new()); - - chat.submit_user_message_as_plain_user_turn("!echo hello".into()); - - match next_submit_op(&mut op_rx) { - Op::UserTurn { items, .. } => assert_eq!( - items, - vec![UserInput::Text { - text: "!echo hello".to_string(), - text_elements: Vec::new(), - }] - ), - other => panic!("expected Op::UserTurn for BTW shell-like input, got {other:?}"), - } + btw::submit_user_message_as_plain_user_turn_does_not_run_shell_commands().await; } #[tokio::test] async fn slash_btw_requests_forked_side_question_while_task_running() { - use ratatui::Terminal; - use ratatui::backend::TestBackend; - - let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await; - let parent_thread_id = ThreadId::new(); - chat.thread_id = Some(parent_thread_id); - chat.on_task_started(); - chat.show_welcome_banner = false; - chat.bottom_pane.set_composer_text( - "/btw explore the codebase".to_string(), - Vec::new(), - Vec::new(), - ); - - chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); - - assert_matches!( - rx.try_recv(), - Ok(AppEvent::StartBtw { - parent_thread_id: emitted_parent_thread_id, - user_message, - }) if emitted_parent_thread_id == parent_thread_id - && user_message - == UserMessage { - text: "explore the codebase".to_string(), - local_images: Vec::new(), - remote_image_urls: Vec::new(), - text_elements: Vec::new(), - mention_bindings: Vec::new(), - } - ); - assert!( - op_rx.try_recv().is_err(), - "expected no op on the parent thread" - ); - - let width = 80; - let height = chat.desired_height(width); - let mut terminal = Terminal::new(TestBackend::new(width, height)).expect("create terminal"); - terminal - .draw(|f| chat.render(f.area(), f.buffer_mut())) - .expect("draw BTW footer"); + let rendered = btw::slash_btw_requests_forked_side_question_while_task_running().await; assert_snapshot!( "slash_btw_requests_forked_side_question_while_task_running", - terminal.backend() + rendered ); } @@ -13358,51 +13223,17 @@ async fn status_line_fast_mode_footer_snapshot() { #[tokio::test] async fn btw_footer_override_snapshot() { - use ratatui::Terminal; - use ratatui::backend::TestBackend; - - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; - chat.show_welcome_banner = false; - chat.set_thread_footer_hint_override(Some(vec![( - "BTW".to_string(), - "from main thread · Esc to return".to_string(), - )])); - - let width = 80; - let height = chat.desired_height(width); - let mut terminal = Terminal::new(TestBackend::new(width, height)).expect("create terminal"); - terminal - .draw(|f| chat.render(f.area(), f.buffer_mut())) - .expect("draw BTW footer"); - assert_snapshot!("btw_footer_override", terminal.backend()); + let rendered = btw::btw_footer_override_snapshot().await; + assert_snapshot!("btw_footer_override", rendered); } #[tokio::test] async fn clearing_thread_footer_override_preserves_general_footer_hint_snapshot() { - use ratatui::Terminal; - use ratatui::backend::TestBackend; - - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; - chat.show_welcome_banner = false; - chat.set_footer_hint_override(Some(vec![( - "Save and close external editor to continue.".to_string(), - String::new(), - )])); - chat.set_thread_footer_hint_override(Some(vec![( - "BTW".to_string(), - "from main thread · Esc to return".to_string(), - )])); - chat.set_thread_footer_hint_override(/*items*/ None); - - let width = 80; - let height = chat.desired_height(width); - let mut terminal = Terminal::new(TestBackend::new(width, height)).expect("create terminal"); - terminal - .draw(|f| chat.render(f.area(), f.buffer_mut())) - .expect("draw preserved footer hint"); + let rendered = + btw::clearing_thread_footer_override_preserves_general_footer_hint_snapshot().await; assert_snapshot!( "clearing_thread_footer_override_preserves_general_footer_hint", - terminal.backend() + rendered ); } diff --git a/codex-rs/tui/src/chatwidget/tests/btw.rs b/codex-rs/tui/src/chatwidget/tests/btw.rs new file mode 100644 index 0000000000..b619f7fec0 --- /dev/null +++ b/codex-rs/tui/src/chatwidget/tests/btw.rs @@ -0,0 +1,206 @@ +use super::*; +use ratatui::Terminal; +use ratatui::backend::TestBackend; + +pub(super) async fn forked_thread_history_line_includes_name_and_id_snapshot() -> String { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.set_next_fork_banner_parent_label(Some("named parent".to_string())); + + let forked_from_id = + ThreadId::from_string("e9f18a88-8081-4e51-9d4e-8af5cde2d8dd").expect("forked id"); + chat.emit_forked_thread_event(forked_from_id); + + let history_cell = tokio::time::timeout(std::time::Duration::from_secs(2), async { + loop { + match rx.recv().await { + Some(AppEvent::InsertHistoryCell(cell)) => break cell, + Some(_) => continue, + None => panic!("app event channel closed before forked thread history was emitted"), + } + } + }) + .await + .expect("timed out waiting for forked thread history"); + lines_to_single_string(&history_cell.display_lines(/*width*/ 80)) +} + +pub(super) async fn forked_thread_history_line_without_name_shows_id_once_snapshot() -> String { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + + let forked_from_id = + ThreadId::from_string("019c2d47-4935-7423-a190-05691f566092").expect("forked id"); + chat.emit_forked_thread_event(forked_from_id); + + let history_cell = tokio::time::timeout(std::time::Duration::from_secs(2), async { + loop { + match rx.recv().await { + Some(AppEvent::InsertHistoryCell(cell)) => break cell, + Some(_) => continue, + None => panic!("app event channel closed before forked thread history was emitted"), + } + } + }) + .await + .expect("timed out waiting for forked thread history"); + lines_to_single_string(&history_cell.display_lines(/*width*/ 80)) +} + +pub(super) async fn suppressed_interrupted_turn_notice_skips_history_warning() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.thread_id = Some(ThreadId::new()); + chat.set_interrupted_turn_notice_mode(InterruptedTurnNoticeMode::Suppress); + chat.on_task_started(); + chat.on_agent_message_delta("partial output".to_string()); + + chat.on_interrupted_turn(TurnAbortReason::Interrupted); + + let inserted = drain_insert_history(&mut rx); + assert!( + inserted.iter().all(|cell| { + let rendered = lines_to_single_string(cell); + !rendered.contains("Conversation interrupted - tell the model what to do differently.") + && !rendered.contains("Model interrupted to submit steer instructions.") + }), + "unexpected interrupted-turn notice in BTW thread: {inserted:?}" + ); +} + +fn assert_btw_rename_rejected( + rx: &mut tokio::sync::mpsc::UnboundedReceiver, + op_rx: &mut tokio::sync::mpsc::UnboundedReceiver, +) { + let event = rx.try_recv().expect("expected BTW rename error"); + match event { + AppEvent::InsertHistoryCell(cell) => { + let rendered = lines_to_single_string(&cell.display_lines(/*width*/ 80)); + assert!( + rendered.contains("BTW threads are ephemeral and cannot be renamed."), + "expected BTW rename error, got {rendered:?}" + ); + } + other => panic!("expected InsertHistoryCell error, got {other:?}"), + } + assert!(rx.try_recv().is_err(), "expected no follow-up events"); + assert!(op_rx.try_recv().is_err(), "expected no rename op"); +} + +pub(super) async fn slash_rename_is_rejected_for_btw_threads() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.set_thread_rename_block_message( + "BTW threads are ephemeral and cannot be renamed.".to_string(), + ); + + chat.dispatch_command(SlashCommand::Rename); + assert_btw_rename_rejected(&mut rx, &mut op_rx); +} + +pub(super) async fn slash_rename_with_args_is_rejected_for_btw_threads() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.set_thread_rename_block_message( + "BTW threads are ephemeral and cannot be renamed.".to_string(), + ); + + chat.dispatch_command_with_args(SlashCommand::Rename, "investigate".to_string(), Vec::new()); + assert_btw_rename_rejected(&mut rx, &mut op_rx); +} + +pub(super) async fn submit_user_message_as_plain_user_turn_does_not_run_shell_commands() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.thread_id = Some(ThreadId::new()); + + chat.submit_user_message_as_plain_user_turn("!echo hello".into()); + + match next_submit_op(&mut op_rx) { + Op::UserTurn { items, .. } => pretty_assertions::assert_eq!( + items, + vec![UserInput::Text { + text: "!echo hello".to_string(), + text_elements: Vec::new(), + }] + ), + other => panic!("expected Op::UserTurn for BTW shell-like input, got {other:?}"), + } +} + +pub(super) async fn slash_btw_requests_forked_side_question_while_task_running() -> String { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + let parent_thread_id = ThreadId::new(); + chat.thread_id = Some(parent_thread_id); + chat.on_task_started(); + chat.show_welcome_banner = false; + chat.bottom_pane.set_composer_text( + "/btw explore the codebase".to_string(), + Vec::new(), + Vec::new(), + ); + + chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); + + assert_matches::assert_matches!( + rx.try_recv(), + Ok(AppEvent::StartBtw { + parent_thread_id: emitted_parent_thread_id, + user_message, + }) if emitted_parent_thread_id == parent_thread_id + && user_message + == UserMessage { + text: "explore the codebase".to_string(), + local_images: Vec::new(), + remote_image_urls: Vec::new(), + text_elements: Vec::new(), + mention_bindings: Vec::new(), + } + ); + assert!( + op_rx.try_recv().is_err(), + "expected no op on the parent thread" + ); + + let width = 80; + let height = chat.desired_height(width); + let mut terminal = Terminal::new(TestBackend::new(width, height)).expect("create terminal"); + terminal + .draw(|f| chat.render(f.area(), f.buffer_mut())) + .expect("draw BTW footer"); + terminal.backend().to_string() +} + +pub(super) async fn btw_footer_override_snapshot() -> String { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.show_welcome_banner = false; + chat.set_thread_footer_hint_override(Some(vec![( + "BTW".to_string(), + "from main thread · Esc to return".to_string(), + )])); + + let width = 80; + let height = chat.desired_height(width); + let mut terminal = Terminal::new(TestBackend::new(width, height)).expect("create terminal"); + terminal + .draw(|f| chat.render(f.area(), f.buffer_mut())) + .expect("draw BTW footer"); + terminal.backend().to_string() +} + +pub(super) async fn clearing_thread_footer_override_preserves_general_footer_hint_snapshot() +-> String { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.show_welcome_banner = false; + chat.set_footer_hint_override(Some(vec![( + "Save and close external editor to continue.".to_string(), + String::new(), + )])); + chat.set_thread_footer_hint_override(Some(vec![( + "BTW".to_string(), + "from main thread · Esc to return".to_string(), + )])); + chat.set_thread_footer_hint_override(/*items*/ None); + + let width = 80; + let height = chat.desired_height(width); + let mut terminal = Terminal::new(TestBackend::new(width, height)).expect("create terminal"); + terminal + .draw(|f| chat.render(f.area(), f.buffer_mut())) + .expect("draw preserved footer hint"); + terminal.backend().to_string() +}