diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 494405d531..64a03d2ee2 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -118,6 +118,15 @@ use crate::text_formatting::truncate_text; use crate::tui::FrameRequester; mod interrupts; use self::interrupts::InterruptManager; + +#[cfg(test)] +use crate::version::CODEX_CLI_VERSION; +#[cfg(test)] +use codex_core::version::VERSION_FILENAME; +#[cfg(test)] +use codex_core::version::is_newer; +#[cfg(test)] +use codex_core::version::read_version_info; mod agent; use self::agent::spawn_agent; use self::agent::spawn_agent_from_existing; @@ -696,7 +705,19 @@ impl ChatWidget { crate::updates::get_upgrade_version(&self.config).is_some() } - #[cfg(debug_assertions)] + #[cfg(test)] + fn should_show_update_nudge(&self) -> bool { + if !self.config.check_for_update_on_startup { + return false; + } + let version_file = self.config.codex_home.join(VERSION_FILENAME); + read_version_info(&version_file) + .ok() + .and_then(|info| is_newer(&info.latest_version, CODEX_CLI_VERSION)) + .unwrap_or(false) + } + + #[cfg(all(debug_assertions, not(test)))] fn should_show_update_nudge(&self) -> bool { false } diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__error_event_history.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__error_event_history.snap new file mode 100644 index 0000000000..8eb3922d78 --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__error_event_history.snap @@ -0,0 +1,6 @@ +--- +source: tui/src/chatwidget/tests.rs +expression: last +--- +■ Something failed. +Update available. See https://github.com/openai/codex for installation options. diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__stream_error_status_header.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__stream_error_status_header.snap new file mode 100644 index 0000000000..570c4087aa --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__stream_error_status_header.snap @@ -0,0 +1,6 @@ +--- +source: tui/src/chatwidget/tests.rs +expression: status.header() +--- +Reconnecting... 2/5 +Update available. See https://github.com/openai/codex for installation options. diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 362b1678f4..21b65b9e9b 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -4,6 +4,7 @@ use crate::app_event_sender::AppEventSender; use crate::test_backend::VT100Backend; use crate::tui::FrameRequester; use assert_matches::assert_matches; +use chrono::Utc; use codex_common::approval_presets::builtin_approval_presets; use codex_core::AuthManager; use codex_core::CodexAuth; @@ -18,6 +19,7 @@ use codex_core::protocol::AgentReasoningEvent; use codex_core::protocol::ApplyPatchApprovalRequestEvent; use codex_core::protocol::BackgroundEventEvent; use codex_core::protocol::CreditsSnapshot; +use codex_core::protocol::ErrorEvent; use codex_core::protocol::Event; use codex_core::protocol::EventMsg; use codex_core::protocol::ExecApprovalRequestEvent; @@ -49,6 +51,8 @@ use codex_core::protocol::UndoCompletedEvent; use codex_core::protocol::UndoStartedEvent; use codex_core::protocol::ViewImageToolCallEvent; use codex_core::protocol::WarningEvent; +use codex_core::version::VERSION_FILENAME; +use codex_core::version::VersionInfo; use codex_protocol::ConversationId; use codex_protocol::account::PlanType; use codex_protocol::openai_models::ModelPreset; @@ -64,6 +68,7 @@ use crossterm::event::KeyEvent; use crossterm::event::KeyModifiers; use insta::assert_snapshot; use pretty_assertions::assert_eq; +use serde_json; use std::collections::HashSet; use std::path::PathBuf; use tempfile::NamedTempFile; @@ -492,6 +497,24 @@ fn lines_to_single_string(lines: &[ratatui::text::Line<'static>]) -> String { s } +fn set_update_available(config: &mut Config) -> tempfile::TempDir { + let codex_home = tempdir().expect("tempdir"); + config.codex_home = codex_home.path().to_path_buf(); + config.check_for_update_on_startup = true; + let info = VersionInfo { + latest_version: "9999.0.0".to_string(), + last_checked_at: Utc::now(), + dismissed_version: None, + }; + let json_line = format!( + "{}\n", + serde_json::to_string(&info).expect("serialize version info") + ); + std::fs::write(codex_home.path().join(VERSION_FILENAME), json_line) + .expect("write version info"); + codex_home +} + fn make_token_info(total_tokens: i64, context_window: i64) -> TokenUsageInfo { fn usage(total_tokens: i64) -> TokenUsage { TokenUsage { @@ -2924,6 +2947,7 @@ fn plan_update_renders_history_cell() { #[test] fn stream_error_updates_status_indicator() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); + let _tempdir = set_update_available(&mut chat.config); chat.bottom_pane.set_task_running(true); let msg = "Reconnecting... 2/5"; chat.handle_codex_event(Event { @@ -2943,7 +2967,10 @@ fn stream_error_updates_status_indicator() { .bottom_pane .status_widget() .expect("status indicator should be visible"); - assert_eq!(status.header(), msg); + let nudge = crate::update_action::update_available_nudge(); + let expected = format!("{msg}\n{nudge}"); + assert_eq!(status.header(), expected); + assert_snapshot!("stream_error_status_header", status.header()); } #[test] @@ -2965,6 +2992,23 @@ fn warning_event_adds_warning_history_cell() { ); } +#[test] +fn error_event_renders_history_snapshot() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None); + let _tempdir = set_update_available(&mut chat.config); + chat.handle_codex_event(Event { + id: "sub-1".into(), + msg: EventMsg::Error(ErrorEvent { + message: "Something failed.".to_string(), + codex_error_info: Some(CodexErrorInfo::Other), + }), + }); + + let cells = drain_insert_history(&mut rx); + let last = lines_to_single_string(cells.last().expect("error history cell")); + assert_snapshot!("error_event_history", last); +} + #[test] fn stream_recovery_restores_previous_status_header() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None);