chore: clean up argument-comment lint and roll out all-target CI on macOS (#16054)

## Why

`argument-comment-lint` was green in CI even though the repo still had
many uncommented literal arguments. The main gap was target coverage:
the repo wrapper did not force Cargo to inspect test-only call sites, so
examples like the `latest_session_lookup_params(true, ...)` tests in
`codex-rs/tui_app_server/src/lib.rs` never entered the blocking CI path.

This change cleans up the existing backlog, makes the default repo lint
path cover all Cargo targets, and starts rolling that stricter CI
enforcement out on the platform where it is currently validated.

## What changed

- mechanically fixed existing `argument-comment-lint` violations across
the `codex-rs` workspace, including tests, examples, and benches
- updated `tools/argument-comment-lint/run-prebuilt-linter.sh` and
`tools/argument-comment-lint/run.sh` so non-`--fix` runs default to
`--all-targets` unless the caller explicitly narrows the target set
- fixed both wrappers so forwarded cargo arguments after `--` are
preserved with a single separator
- documented the new default behavior in
`tools/argument-comment-lint/README.md`
- updated `rust-ci` so the macOS lint lane keeps the plain wrapper
invocation and therefore enforces `--all-targets`, while Linux and
Windows temporarily pass `-- --lib --bins`

That temporary CI split keeps the stricter all-targets check where it is
already cleaned up, while leaving room to finish the remaining Linux-
and Windows-specific target-gated cleanup before enabling
`--all-targets` on those runners. The Linux and Windows failures on the
intermediate revision were caused by the wrapper forwarding bug, not by
additional lint findings in those lanes.

## Validation

- `bash -n tools/argument-comment-lint/run.sh`
- `bash -n tools/argument-comment-lint/run-prebuilt-linter.sh`
- shell-level wrapper forwarding check for `-- --lib --bins`
- shell-level wrapper forwarding check for `-- --tests`
- `just argument-comment-lint`
- `cargo test` in `tools/argument-comment-lint`
- `cargo test -p codex-terminal-detection`

## Follow-up

- Clean up remaining Linux-only target-gated callsites, then switch the
Linux lint lane back to the plain wrapper invocation.
- Clean up remaining Windows-only target-gated callsites, then switch
the Windows lint lane back to the plain wrapper invocation.
This commit is contained in:
Michael Bolin
2026-03-27 19:00:44 -07:00
committed by GitHub
parent ed977b42ac
commit 61dfe0b86c
307 changed files with 7724 additions and 4710 deletions
+205 -114
View File
@@ -5971,12 +5971,18 @@ mod tests {
App::should_wait_for_initial_session(&SessionSelection::StartFresh);
assert_eq!(wait_for_initial_session, true);
assert_eq!(
App::should_handle_active_thread_events(wait_for_initial_session, true),
App::should_handle_active_thread_events(
wait_for_initial_session,
/*has_active_thread_receiver*/ true
),
false
);
assert_eq!(
App::should_stop_waiting_for_initial_session(wait_for_initial_session, None),
App::should_stop_waiting_for_initial_session(
wait_for_initial_session,
/*primary_thread_id*/ None
),
false
);
if App::should_stop_waiting_for_initial_session(
@@ -5988,7 +5994,10 @@ mod tests {
assert_eq!(wait_for_initial_session, false);
assert_eq!(
App::should_handle_active_thread_events(wait_for_initial_session, true),
App::should_handle_active_thread_events(
wait_for_initial_session,
/*has_active_thread_receiver*/ true
),
true
);
}
@@ -6002,7 +6011,10 @@ mod tests {
},
));
assert_eq!(
App::should_handle_active_thread_events(wait_for_resume, true),
App::should_handle_active_thread_events(
wait_for_resume,
/*has_active_thread_receiver*/ true
),
true
);
let wait_for_fork = App::should_wait_for_initial_session(&SessionSelection::Fork(
@@ -6012,7 +6024,10 @@ mod tests {
},
));
assert_eq!(
App::should_handle_active_thread_events(wait_for_fork, true),
App::should_handle_active_thread_events(
wait_for_fork,
/*has_active_thread_receiver*/ true
),
true
);
}
@@ -6021,7 +6036,8 @@ mod tests {
async fn enqueue_primary_thread_session_replays_buffered_approval_after_attach() -> Result<()> {
let (mut app, mut app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let approval_request = exec_approval_request(thread_id, "turn-1", "call-1", None);
let approval_request =
exec_approval_request(thread_id, "turn-1", "call-1", /*approval_id*/ None);
app.enqueue_primary_thread_request(approval_request).await?;
app.enqueue_primary_thread_session(
@@ -6118,7 +6134,7 @@ mod tests {
while let Ok(event) = app_event_rx.try_recv() {
match event {
AppEvent::InsertHistoryCell(cell) => {
let transcript = lines_to_single_string(&cell.transcript_lines(80));
let transcript = lines_to_single_string(&cell.transcript_lines(/*width*/ 80));
saw_replayed_answer |= transcript.contains("earlier prompt");
}
AppEvent::SubmitThreadOp {
@@ -6227,8 +6243,8 @@ mod tests {
let mut app = make_test_app().await;
let thread_id = ThreadId::new();
app.thread_event_channels
.insert(thread_id, ThreadEventChannel::new(1));
app.set_thread_active(thread_id, true).await;
.insert(thread_id, ThreadEventChannel::new(/*capacity*/ 1));
app.set_thread_active(thread_id, /*active*/ true).await;
let event = thread_closed_notification(thread_id);
@@ -6310,7 +6326,7 @@ mod tests {
make_chatwidget_manual_with_sender().await;
app.chat_widget = chat_widget;
app.replay_thread_snapshot(snapshot, true);
app.replay_thread_snapshot(snapshot, /*resume_restored_queue*/ true);
assert_eq!(app.chat_widget.composer_text_with_pending(), "draft prompt");
assert!(app.chat_widget.queued_user_message_texts().is_empty());
@@ -6348,11 +6364,13 @@ mod tests {
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget
.handle_server_notification(turn_started_notification(thread_id, "turn-1"), None);
app.chat_widget.handle_server_notification(
turn_started_notification(thread_id, "turn-1"),
/*replay_kind*/ None,
);
app.chat_widget.handle_server_notification(
agent_message_delta_notification(thread_id, "turn-1", "agent-1", "streaming"),
None,
/*replay_kind*/ None,
);
app.chat_widget
.apply_external_edit("queued follow-up".to_string());
@@ -6377,7 +6395,7 @@ mod tests {
)],
input_state: Some(input_state),
},
true,
/*resume_restored_queue*/ true,
);
match next_user_turn_op(&mut new_op_rx) {
@@ -6398,11 +6416,13 @@ mod tests {
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget
.handle_server_notification(turn_started_notification(thread_id, "turn-1"), None);
app.chat_widget.handle_server_notification(
turn_started_notification(thread_id, "turn-1"),
/*replay_kind*/ None,
);
app.chat_widget.handle_server_notification(
agent_message_delta_notification(thread_id, "turn-1", "agent-1", "streaming"),
None,
/*replay_kind*/ None,
);
app.chat_widget
.apply_external_edit("queued follow-up".to_string());
@@ -6428,7 +6448,7 @@ mod tests {
)],
input_state: Some(input_state),
},
false,
/*resume_restored_queue*/ false,
);
assert_eq!(
@@ -6447,11 +6467,13 @@ mod tests {
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget
.handle_server_notification(turn_started_notification(thread_id, "turn-1"), None);
app.chat_widget.handle_server_notification(
turn_started_notification(thread_id, "turn-1"),
/*replay_kind*/ None,
);
app.chat_widget.handle_server_notification(
agent_message_delta_notification(thread_id, "turn-1", "agent-1", "streaming"),
None,
/*replay_kind*/ None,
);
app.chat_widget
.apply_external_edit("queued follow-up".to_string());
@@ -6475,7 +6497,7 @@ mod tests {
events: vec![],
input_state: Some(input_state),
},
true,
/*resume_restored_queue*/ true,
);
assert_eq!(
@@ -6494,11 +6516,13 @@ mod tests {
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget
.handle_server_notification(turn_started_notification(thread_id, "turn-1"), None);
app.chat_widget.handle_server_notification(
turn_started_notification(thread_id, "turn-1"),
/*replay_kind*/ None,
);
app.chat_widget.handle_server_notification(
agent_message_delta_notification(thread_id, "turn-1", "agent-1", "streaming"),
None,
/*replay_kind*/ None,
);
app.chat_widget
.apply_external_edit("queued follow-up".to_string());
@@ -6522,7 +6546,7 @@ mod tests {
events: Vec::new(),
input_state: Some(input_state),
},
true,
/*resume_restored_queue*/ true,
);
assert_eq!(
@@ -6552,7 +6576,7 @@ mod tests {
events: Vec::new(),
input_state: None,
},
false,
/*resume_restored_queue*/ false,
);
assert!(app.chat_widget.is_task_running_for_test());
@@ -6564,11 +6588,13 @@ mod tests {
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget
.handle_server_notification(turn_started_notification(thread_id, "turn-1"), None);
app.chat_widget.handle_server_notification(
turn_started_notification(thread_id, "turn-1"),
/*replay_kind*/ None,
);
app.chat_widget.handle_server_notification(
agent_message_delta_notification(thread_id, "turn-1", "agent-1", "streaming"),
None,
/*replay_kind*/ None,
);
app.chat_widget
.apply_external_edit("queued follow-up".to_string());
@@ -6601,7 +6627,7 @@ mod tests {
],
input_state: Some(input_state),
},
true,
/*resume_restored_queue*/ true,
);
assert!(
@@ -6615,7 +6641,7 @@ mod tests {
app.chat_widget.handle_server_notification(
turn_completed_notification(thread_id, "turn-1", TurnStatus::Completed),
None,
/*replay_kind*/ None,
);
match next_user_turn_op(&mut new_op_rx) {
@@ -6668,7 +6694,7 @@ mod tests {
let (chat_widget, _app_event_tx, _rx, mut new_op_rx) =
make_chatwidget_manual_with_sender().await;
app.chat_widget = chat_widget;
app.replay_thread_snapshot(snapshot, true);
app.replay_thread_snapshot(snapshot, /*resume_restored_queue*/ true);
assert_eq!(app.chat_widget.composer_text_with_pending(), large);
@@ -6733,7 +6759,7 @@ mod tests {
events: vec![],
input_state: Some(input_state),
},
true,
/*resume_restored_queue*/ true,
);
app.chat_widget
.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
@@ -6814,7 +6840,7 @@ mod tests {
events: vec![],
input_state: Some(input_state),
},
true,
/*resume_restored_queue*/ true,
);
assert_eq!(
@@ -6834,11 +6860,13 @@ mod tests {
let thread_id = ThreadId::new();
let session = test_thread_session(thread_id, PathBuf::from("/tmp/project"));
app.chat_widget.handle_thread_session(session.clone());
app.chat_widget
.handle_server_notification(turn_started_notification(thread_id, "turn-1"), None);
app.chat_widget.handle_server_notification(
turn_started_notification(thread_id, "turn-1"),
/*replay_kind*/ None,
);
app.chat_widget.handle_server_notification(
agent_message_delta_notification(thread_id, "turn-1", "agent-1", "streaming"),
None,
/*replay_kind*/ None,
);
app.chat_widget
.apply_external_edit("queued follow-up".to_string());
@@ -6864,7 +6892,7 @@ mod tests {
)],
input_state: Some(input_state),
},
true,
/*resume_restored_queue*/ true,
);
assert_eq!(
@@ -6907,7 +6935,7 @@ mod tests {
.expect("embedded app server");
let thread_id = ThreadId::new();
app.thread_event_channels
.insert(thread_id, ThreadEventChannel::new(1));
.insert(thread_id, ThreadEventChannel::new(/*capacity*/ 1));
app.open_agent_picker(&mut app_server).await;
@@ -6933,12 +6961,12 @@ mod tests {
.expect("embedded app server");
let thread_id = ThreadId::new();
app.thread_event_channels
.insert(thread_id, ThreadEventChannel::new(1));
.insert(thread_id, ThreadEventChannel::new(/*capacity*/ 1));
app.agent_navigation.upsert(
thread_id,
Some("Robie".to_string()),
Some("explorer".to_string()),
true,
/*is_closed*/ true,
);
app.open_agent_picker(&mut app_server).await;
@@ -6964,12 +6992,12 @@ mod tests {
.expect("embedded app server");
let thread_id = ThreadId::new();
app.thread_event_channels
.insert(thread_id, ThreadEventChannel::new(1));
.insert(thread_id, ThreadEventChannel::new(/*capacity*/ 1));
app.agent_navigation.upsert(
thread_id,
Some("Robie".to_string()),
Some("explorer".to_string()),
false,
/*is_closed*/ false,
);
app.open_agent_picker(&mut app_server).await;
@@ -7037,7 +7065,7 @@ mod tests {
.await?;
let thread_id = started.session.thread_id;
app.thread_event_channels
.insert(thread_id, ThreadEventChannel::new(1));
.insert(thread_id, ThreadEventChannel::new(/*capacity*/ 1));
app.open_agent_picker(&mut app_server).await;
@@ -7074,7 +7102,7 @@ mod tests {
other => panic!("expected InsertHistoryCell event, got {other:?}"),
};
let rendered = cell
.display_lines(120)
.display_lines(/*width*/ 120)
.into_iter()
.map(|line| line.to_string())
.collect::<Vec<_>>()
@@ -7151,7 +7179,7 @@ mod tests {
other => panic!("expected InsertHistoryCell event, got {other:?}"),
};
let rendered = cell
.display_lines(120)
.display_lines(/*width*/ 120)
.into_iter()
.map(|line| line.to_string())
.collect::<Vec<_>>()
@@ -7182,9 +7210,9 @@ mod tests {
.with_user_config(&config_toml_path, user_config);
app.config
.features
.set_enabled(Feature::GuardianApproval, true)?;
.set_enabled(Feature::GuardianApproval, /*enabled*/ true)?;
app.chat_widget
.set_feature_enabled(Feature::GuardianApproval, true);
.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ true);
app.config.approvals_reviewer = ApprovalsReviewer::GuardianSubagent;
app.chat_widget
.set_approvals_reviewer(ApprovalsReviewer::GuardianSubagent);
@@ -7242,7 +7270,7 @@ mod tests {
other => panic!("expected InsertHistoryCell event, got {other:?}"),
};
let rendered = cell
.display_lines(120)
.display_lines(/*width*/ 120)
.into_iter()
.map(|line| line.to_string())
.collect::<Vec<_>>()
@@ -7341,9 +7369,9 @@ mod tests {
.with_user_config(&config_toml_path, user_config);
app.config
.features
.set_enabled(Feature::GuardianApproval, true)?;
.set_enabled(Feature::GuardianApproval, /*enabled*/ true)?;
app.chat_widget
.set_feature_enabled(Feature::GuardianApproval, true);
.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ true);
app.config.approvals_reviewer = ApprovalsReviewer::User;
app.chat_widget
.set_approvals_reviewer(ApprovalsReviewer::User);
@@ -7481,9 +7509,9 @@ guardian_approval = true
.with_user_config(&config_toml_path, user_config);
app.config
.features
.set_enabled(Feature::GuardianApproval, true)?;
.set_enabled(Feature::GuardianApproval, /*enabled*/ true)?;
app.chat_widget
.set_feature_enabled(Feature::GuardianApproval, true);
.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ true);
app.config.approvals_reviewer = ApprovalsReviewer::GuardianSubagent;
app.chat_widget
.set_approvals_reviewer(ApprovalsReviewer::GuardianSubagent);
@@ -7524,7 +7552,7 @@ guardian_approval = true
other => panic!("expected InsertHistoryCell event, got {other:?}"),
};
let rendered = cell
.display_lines(120)
.display_lines(/*width*/ 120)
.into_iter()
.map(|line| line.to_string())
.collect::<Vec<_>>()
@@ -7560,9 +7588,9 @@ guardian_approval = true
.with_user_config(&config_toml_path, user_config);
app.config
.features
.set_enabled(Feature::GuardianApproval, true)?;
.set_enabled(Feature::GuardianApproval, /*enabled*/ true)?;
app.chat_widget
.set_feature_enabled(Feature::GuardianApproval, true);
.set_feature_enabled(Feature::GuardianApproval, /*enabled*/ true);
app.config.approvals_reviewer = ApprovalsReviewer::GuardianSubagent;
app.chat_widget
.set_approvals_reviewer(ApprovalsReviewer::GuardianSubagent);
@@ -7593,7 +7621,7 @@ guardian_approval = true
assert!(
!app_events.iter().any(|event| match event {
AppEvent::InsertHistoryCell(cell) => cell
.display_lines(120)
.display_lines(/*width*/ 120)
.iter()
.any(|line| line.to_string().contains("Permissions updated to")),
_ => false,
@@ -7622,7 +7650,7 @@ guardian_approval = true
.expect("embedded app server");
let thread_id = ThreadId::new();
app.thread_event_channels
.insert(thread_id, ThreadEventChannel::new(1));
.insert(thread_id, ThreadEventChannel::new(/*capacity*/ 1));
app.open_agent_picker(&mut app_server).await;
app.chat_widget
@@ -7646,16 +7674,16 @@ guardian_approval = true
app.primary_thread_id = Some(main_thread_id);
app.active_thread_id = Some(main_thread_id);
app.thread_event_channels
.insert(main_thread_id, ThreadEventChannel::new(1));
.insert(main_thread_id, ThreadEventChannel::new(/*capacity*/ 1));
let agent_channel = ThreadEventChannel::new(1);
let agent_channel = ThreadEventChannel::new(/*capacity*/ 1);
{
let mut store = agent_channel.store.lock().await;
store.push_request(exec_approval_request(
agent_thread_id,
"turn-1",
"call-1",
None,
/*approval_id*/ None,
));
}
app.thread_event_channels
@@ -7664,7 +7692,7 @@ guardian_approval = true
agent_thread_id,
Some("Robie".to_string()),
Some("explorer".to_string()),
false,
/*is_closed*/ false,
);
app.refresh_pending_thread_approvals().await;
@@ -7689,11 +7717,11 @@ guardian_approval = true
app.primary_thread_id = Some(main_thread_id);
app.active_thread_id = Some(main_thread_id);
app.thread_event_channels
.insert(main_thread_id, ThreadEventChannel::new(1));
.insert(main_thread_id, ThreadEventChannel::new(/*capacity*/ 1));
app.thread_event_channels.insert(
agent_thread_id,
ThreadEventChannel::new_with_session(
1,
/*capacity*/ 1,
ThreadSessionState {
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
@@ -7707,12 +7735,17 @@ guardian_approval = true
agent_thread_id,
Some("Robie".to_string()),
Some("explorer".to_string()),
false,
/*is_closed*/ false,
);
app.enqueue_thread_request(
agent_thread_id,
exec_approval_request(agent_thread_id, "turn-approval", "call-approval", None),
exec_approval_request(
agent_thread_id,
"turn-approval",
"call-approval",
/*approval_id*/ None,
),
)
.await?;
@@ -7729,7 +7762,12 @@ guardian_approval = true
async fn inactive_thread_exec_approval_preserves_context() {
let app = make_test_app().await;
let thread_id = ThreadId::new();
let mut request = exec_approval_request(thread_id, "turn-approval", "call-approval", None);
let mut request = exec_approval_request(
thread_id,
"turn-approval",
"call-approval",
/*approval_id*/ None,
);
let ServerRequest::CommandExecutionRequestApproval { params, .. } = &mut request else {
panic!("expected exec approval request");
};
@@ -7797,7 +7835,12 @@ guardian_approval = true
let app = make_test_app().await;
let thread_id = ThreadId::new();
let script = r#"python3 -c 'print("Hello, world!")'"#;
let mut request = exec_approval_request(thread_id, "turn-approval", "call-approval", None);
let mut request = exec_approval_request(
thread_id,
"turn-approval",
"call-approval",
/*approval_id*/ None,
);
let ServerRequest::CommandExecutionRequestApproval { params, .. } = &mut request else {
panic!("expected exec approval request");
};
@@ -7834,11 +7877,11 @@ guardian_approval = true
app.primary_thread_id = Some(main_thread_id);
app.active_thread_id = Some(main_thread_id);
app.thread_event_channels
.insert(main_thread_id, ThreadEventChannel::new(1));
.insert(main_thread_id, ThreadEventChannel::new(/*capacity*/ 1));
app.thread_event_channels.insert(
agent_thread_id,
ThreadEventChannel::new_with_session(
4,
/*capacity*/ 4,
ThreadSessionState {
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
@@ -7852,12 +7895,17 @@ guardian_approval = true
agent_thread_id,
Some("Robie".to_string()),
Some("explorer".to_string()),
false,
/*is_closed*/ false,
);
app.enqueue_thread_request(
agent_thread_id,
exec_approval_request(agent_thread_id, "turn-approval", "call-approval", None),
exec_approval_request(
agent_thread_id,
"turn-approval",
"call-approval",
/*approval_id*/ None,
),
)
.await?;
assert_eq!(
@@ -7898,7 +7946,11 @@ guardian_approval = true
app.primary_session_configured = Some(primary_session.clone());
app.thread_event_channels.insert(
main_thread_id,
ThreadEventChannel::new_with_session(4, primary_session.clone(), Vec::new()),
ThreadEventChannel::new_with_session(
/*capacity*/ 4,
primary_session.clone(),
Vec::new(),
),
);
let rollout_path = temp_dir.path().join("agent-rollout.jsonl");
@@ -8003,7 +8055,11 @@ guardian_approval = true
app.primary_session_configured = Some(primary_session.clone());
app.thread_event_channels.insert(
main_thread_id,
ThreadEventChannel::new_with_session(4, primary_session.clone(), Vec::new()),
ThreadEventChannel::new_with_session(
/*capacity*/ 4,
primary_session.clone(),
Vec::new(),
),
);
app.enqueue_thread_notification(
@@ -8052,27 +8108,46 @@ guardian_approval = true
let snapshot = [
format!(
"{} | {}",
format_agent_picker_item_name(Some("Robie"), Some("explorer"), true),
format_agent_picker_item_name(
Some("Robie"),
Some("explorer"),
/*is_primary*/ true
),
thread_id
),
format!(
"{} | {}",
format_agent_picker_item_name(Some("Robie"), Some("explorer"), false),
format_agent_picker_item_name(
Some("Robie"),
Some("explorer"),
/*is_primary*/ false
),
thread_id
),
format!(
"{} | {}",
format_agent_picker_item_name(Some("Robie"), None, false),
format_agent_picker_item_name(
Some("Robie"),
/*agent_role*/ None,
/*is_primary*/ false
),
thread_id
),
format!(
"{} | {}",
format_agent_picker_item_name(None, Some("explorer"), false),
format_agent_picker_item_name(
/*agent_nickname*/ None,
Some("explorer"),
/*is_primary*/ false
),
thread_id
),
format!(
"{} | {}",
format_agent_picker_item_name(None, None, false),
format_agent_picker_item_name(
/*agent_nickname*/ None, /*agent_role*/ None,
/*is_primary*/ false
),
thread_id
),
]
@@ -8202,7 +8277,7 @@ guardian_approval = true
let agent_cell = |text: &str| -> Arc<dyn HistoryCell> {
Arc::new(AgentMessageCell::new(
vec![Line::from(text.to_string())],
true,
/*is_first_line*/ true,
)) as Arc<dyn HistoryCell>
};
let make_header = |is_first| -> Arc<dyn HistoryCell> {
@@ -8229,9 +8304,9 @@ guardian_approval = true
app.chat_widget.current_model(),
event,
is_first,
None,
None,
false,
/*tooltip_override*/ None,
/*auth_plan*/ None,
/*show_fast_status*/ false,
)) as Arc<dyn HistoryCell>
};
@@ -8239,7 +8314,7 @@ guardian_approval = true
make_header(true),
Arc::new(crate::history_cell::new_info_event(
"startup tip that used to replay".to_string(),
None,
/*hint*/ None,
)) as Arc<dyn HistoryCell>,
user_cell("Tell me a long story about a town with a dark lighthouse."),
agent_cell(story_part_one),
@@ -8251,7 +8326,7 @@ guardian_approval = true
app.has_emitted_history_lines = true;
let rendered = app
.clear_ui_header_lines_with_version(80, "<VERSION>")
.clear_ui_header_lines_with_version(/*width*/ 80, "<VERSION>")
.iter()
.map(|line| {
line.spans
@@ -8309,7 +8384,7 @@ guardian_approval = true
set_chatgpt_auth(&mut app.chat_widget);
let rendered = app
.clear_ui_header_lines_with_version(80, "<VERSION>")
.clear_ui_header_lines_with_version(/*width*/ 80, "<VERSION>")
.iter()
.map(|line| {
line.spans
@@ -8605,7 +8680,7 @@ guardian_approval = true
#[test]
fn thread_event_store_tracks_active_turn_lifecycle() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
assert_eq!(store.active_turn_id(), None);
let thread_id = ThreadId::new();
@@ -8636,17 +8711,18 @@ guardian_approval = true
test_turn("turn-2", TurnStatus::InProgress, Vec::new()),
];
let store = ThreadEventStore::new_with_session(8, session.clone(), turns.clone());
let store =
ThreadEventStore::new_with_session(/*capacity*/ 8, session.clone(), turns.clone());
assert_eq!(store.active_turn_id(), Some("turn-2"));
let mut refreshed_store = ThreadEventStore::new(8);
let mut refreshed_store = ThreadEventStore::new(/*capacity*/ 8);
refreshed_store.set_session(session, turns);
assert_eq!(refreshed_store.active_turn_id(), Some("turn-2"));
}
#[test]
fn thread_event_store_clear_active_turn_id_resets_cached_turn() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
let thread_id = ThreadId::new();
store.push_notification(turn_started_notification(thread_id, "turn-1"));
@@ -8658,12 +8734,12 @@ guardian_approval = true
#[test]
fn thread_event_store_rebase_preserves_resolved_request_state() {
let thread_id = ThreadId::new();
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
store.push_request(exec_approval_request(
thread_id,
"turn-approval",
"call-approval",
None,
/*approval_id*/ None,
));
store.push_notification(ServerNotification::ServerRequestResolved(
codex_app_server_protocol::ServerRequestResolvedNotification {
@@ -8682,7 +8758,7 @@ guardian_approval = true
#[test]
fn thread_event_store_rebase_preserves_hook_notifications() {
let thread_id = ThreadId::new();
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
store.push_notification(hook_started_notification(thread_id, "turn-hook"));
store.push_notification(hook_completed_notification(thread_id, "turn-hook"));
@@ -8740,11 +8816,11 @@ guardian_approval = true
ThreadId::new(),
model,
model_info.slug.as_str(),
None,
None,
None,
/*account_id*/ None,
/*account_email*/ None,
/*auth_mode*/ None,
"test_originator".to_string(),
false,
/*log_user_prompts*/ false,
"test".to_string(),
SessionSource::Cli,
)
@@ -9301,7 +9377,7 @@ guardian_approval = true
let agent_cell = |text: &str| -> Arc<dyn HistoryCell> {
Arc::new(AgentMessageCell::new(
vec![Line::from(text.to_string())],
true,
/*is_first_line*/ true,
)) as Arc<dyn HistoryCell>
};
@@ -9329,16 +9405,19 @@ guardian_approval = true
app.chat_widget.current_model(),
event,
is_first,
None,
None,
false,
/*tooltip_override*/ None,
/*auth_plan*/ None,
/*show_fast_status*/ false,
)) as Arc<dyn HistoryCell>
};
let placeholder = "[Image #1]";
let edited_text = format!("follow-up (edited) {placeholder}");
let edited_range = edited_text.len().saturating_sub(placeholder.len())..edited_text.len();
let edited_text_elements = vec![TextElement::new(edited_range.into(), None)];
let edited_text_elements = vec![TextElement::new(
edited_range.into(),
/*placeholder*/ None,
)];
let edited_local_image_paths = vec![PathBuf::from("/tmp/fake-image.png")];
// Simulate a transcript with duplicated history (e.g., from prior backtracks)
@@ -9565,7 +9644,7 @@ guardian_approval = true
events: Vec::new(),
input_state: None,
},
false,
/*resume_restored_queue*/ false,
);
while let Ok(event) = app_event_rx.try_recv() {
@@ -9599,7 +9678,7 @@ guardian_approval = true
receiver_thread_id,
Some("Robie".to_string()),
Some("explorer".to_string()),
false,
/*is_closed*/ false,
);
let replacement = ChatWidget::new_with_app_event(ChatWidgetInit {
@@ -9646,13 +9725,13 @@ guardian_approval = true
)],
input_state: None,
},
false,
/*resume_restored_queue*/ false,
);
let mut saw_named_wait = false;
while let Ok(event) = app_event_rx.try_recv() {
if let AppEvent::InsertHistoryCell(cell) = event {
let transcript = lines_to_single_string(&cell.transcript_lines(80));
let transcript = lines_to_single_string(&cell.transcript_lines(/*width*/ 80));
saw_named_wait |= transcript.contains("Robie [explorer]");
}
}
@@ -9670,7 +9749,11 @@ guardian_approval = true
let initial_session = test_thread_session(thread_id, PathBuf::from("/tmp/original"));
app.thread_event_channels.insert(
thread_id,
ThreadEventChannel::new_with_session(4, initial_session.clone(), Vec::new()),
ThreadEventChannel::new_with_session(
/*capacity*/ 4,
initial_session.clone(),
Vec::new(),
),
);
let resumed_turns = vec![test_turn(
@@ -9732,7 +9815,7 @@ guardian_approval = true
}) as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(
vec![Line::from("after first")],
false,
/*is_first_line*/ false,
)) as Arc<dyn HistoryCell>,
Arc::new(UserHistoryCell {
message: "second".to_string(),
@@ -9742,7 +9825,7 @@ guardian_approval = true
}) as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(
vec![Line::from("after second")],
false,
/*is_first_line*/ false,
)) as Arc<dyn HistoryCell>,
];
app.overlay = Some(Overlay::new_transcript(app.transcript_cells.clone()));
@@ -9750,7 +9833,7 @@ guardian_approval = true
app.backtrack.overlay_preview_active = true;
app.backtrack.nth_user_message = 1;
let changed = app.apply_non_pending_thread_rollback(1);
let changed = app.apply_non_pending_thread_rollback(/*num_turns*/ 1);
assert!(changed);
assert!(app.backtrack_render_pending);
@@ -9793,7 +9876,7 @@ guardian_approval = true
app.handle_thread_rollback_response(
thread_id,
1,
/*num_turns*/ 1,
&ThreadRollbackResponse {
thread: Thread {
id: thread_id.to_string(),
@@ -9971,7 +10054,14 @@ guardian_approval = true
#[tokio::test]
async fn session_summary_skip_zero_usage() {
assert!(session_summary(TokenUsage::default(), None, None).is_none());
assert!(
session_summary(
TokenUsage::default(),
/*thread_id*/ None,
/*thread_name*/ None
)
.is_none()
);
}
#[tokio::test]
@@ -9984,7 +10074,8 @@ guardian_approval = true
};
let conversation = ThreadId::from_string("123e4567-e89b-12d3-a456-426614174000").unwrap();
let summary = session_summary(usage, Some(conversation), None).expect("summary");
let summary =
session_summary(usage, Some(conversation), /*thread_name*/ None).expect("summary");
assert_eq!(
summary.usage_line,
"Token usage: total=12 input=10 output=2"
@@ -253,18 +253,23 @@ mod tests {
let second_agent_id =
ThreadId::from_string("00000000-0000-0000-0000-000000000103").expect("valid thread");
state.upsert(main_thread_id, None, None, false);
state.upsert(
main_thread_id,
/*agent_nickname*/ None,
/*agent_role*/ None,
/*is_closed*/ false,
);
state.upsert(
first_agent_id,
Some("Robie".to_string()),
Some("explorer".to_string()),
false,
/*is_closed*/ false,
);
state.upsert(
second_agent_id,
Some("Bob".to_string()),
Some("worker".to_string()),
false,
/*is_closed*/ false,
);
(state, main_thread_id, first_agent_id, second_agent_id)
@@ -278,7 +283,7 @@ mod tests {
first_agent_id,
Some("Robie".to_string()),
Some("worker".to_string()),
true,
/*is_closed*/ true,
);
assert_eq!(
@@ -695,7 +695,7 @@ mod tests {
#[test]
fn thread_event_snapshot_keeps_pending_request_user_input() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
let request = request_user_input_request("call-1", "turn-1");
store.push_request(request);
@@ -711,7 +711,7 @@ mod tests {
#[test]
fn thread_event_snapshot_drops_resolved_request_user_input_after_user_answer() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
store.push_request(request_user_input_request("call-1", "turn-1"));
store.note_outbound_op(&Op::UserInputAnswer {
@@ -730,7 +730,7 @@ mod tests {
#[test]
fn thread_event_snapshot_drops_resolved_request_user_input_after_server_resolution() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
store.push_request(request_user_input_request("call-1", "turn-1"));
store.push_notification(request_resolved(AppServerRequestId::Integer(1)));
@@ -749,7 +749,7 @@ mod tests {
#[test]
fn thread_event_snapshot_drops_resolved_exec_approval_after_outbound_approval_id() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
store.push_request(exec_approval_request(
"call-1",
Some("approval-1"),
@@ -771,7 +771,7 @@ mod tests {
#[test]
fn thread_event_snapshot_drops_resolved_exec_approval_after_server_resolution() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
store.push_request(exec_approval_request(
"call-1",
Some("approval-1"),
@@ -796,7 +796,7 @@ mod tests {
#[test]
fn thread_event_snapshot_drops_answered_request_user_input_for_multi_prompt_turn() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
store.push_request(request_user_input_request("call-1", "turn-1"));
store.note_outbound_op(&Op::UserInputAnswer {
@@ -819,7 +819,7 @@ mod tests {
#[test]
fn thread_event_snapshot_keeps_newer_request_user_input_pending_when_same_turn_has_queue() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
store.push_request(request_user_input_request("call-1", "turn-1"));
store.push_request(request_user_input_request("call-2", "turn-1"));
@@ -841,7 +841,7 @@ mod tests {
#[test]
fn thread_event_snapshot_drops_resolved_patch_approval_after_outbound_approval() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
store.push_request(patch_approval_request("call-1", "turn-1"));
store.note_outbound_op(&Op::PatchApproval {
@@ -858,7 +858,7 @@ mod tests {
#[test]
fn thread_event_snapshot_drops_pending_approvals_when_turn_completes() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
store.push_request(exec_approval_request(
"exec-call-1",
Some("approval-1"),
@@ -879,7 +879,7 @@ mod tests {
#[test]
fn thread_event_snapshot_drops_resolved_elicitation_after_outbound_resolution() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
let request_id = codex_protocol::mcp::RequestId::String("request-1".to_string());
store.push_request(elicitation_request("server-1", "request-1", "turn-1"));
@@ -900,10 +900,12 @@ mod tests {
#[test]
fn thread_event_store_reports_pending_thread_approvals() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
assert_eq!(store.has_pending_thread_approvals(), false);
store.push_request(exec_approval_request("call-1", None, "turn-1"));
store.push_request(exec_approval_request(
"call-1", /*approval_id*/ None, "turn-1",
));
assert_eq!(store.has_pending_thread_approvals(), true);
@@ -918,7 +920,7 @@ mod tests {
#[test]
fn request_user_input_does_not_count_as_pending_thread_approval() {
let mut store = ThreadEventStore::new(8);
let mut store = ThreadEventStore::new(/*capacity*/ 8);
store.push_request(request_user_input_request("call-1", "turn-1"));
assert_eq!(store.has_pending_thread_approvals(), false);
@@ -926,8 +928,10 @@ mod tests {
#[test]
fn thread_event_snapshot_drops_pending_requests_when_thread_closes() {
let mut store = ThreadEventStore::new(8);
store.push_request(exec_approval_request("call-1", None, "turn-1"));
let mut store = ThreadEventStore::new(/*capacity*/ 8);
store.push_request(exec_approval_request(
"call-1", /*approval_id*/ None, "turn-1",
));
store.push_notification(thread_closed());
assert!(store.snapshot().events.iter().all(|event| {
+39 -22
View File
@@ -652,10 +652,12 @@ mod tests {
local_image_paths: Vec::new(),
remote_image_urls: Vec::new(),
}) as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(vec![Line::from("assistant")], true))
as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(
vec![Line::from("assistant")],
/*is_first_line*/ true,
)) as Arc<dyn HistoryCell>,
];
trim_transcript_cells_to_nth_user(&mut cells, 0);
trim_transcript_cells_to_nth_user(&mut cells, /*nth_user_message*/ 0);
assert!(cells.is_empty());
}
@@ -663,18 +665,22 @@ mod tests {
#[test]
fn trim_transcript_preserves_cells_before_selected_user() {
let mut cells: Vec<Arc<dyn HistoryCell>> = vec![
Arc::new(AgentMessageCell::new(vec![Line::from("intro")], true))
as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(
vec![Line::from("intro")],
/*is_first_line*/ true,
)) as Arc<dyn HistoryCell>,
Arc::new(UserHistoryCell {
message: "first".to_string(),
text_elements: Vec::new(),
local_image_paths: Vec::new(),
remote_image_urls: Vec::new(),
}) as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(vec![Line::from("after")], false))
as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(
vec![Line::from("after")],
/*is_first_line*/ false,
)) as Arc<dyn HistoryCell>,
];
trim_transcript_cells_to_nth_user(&mut cells, 0);
trim_transcript_cells_to_nth_user(&mut cells, /*nth_user_message*/ 0);
assert_eq!(cells.len(), 1);
let agent = cells[0]
@@ -694,26 +700,32 @@ mod tests {
#[test]
fn trim_transcript_for_later_user_keeps_prior_history() {
let mut cells: Vec<Arc<dyn HistoryCell>> = vec![
Arc::new(AgentMessageCell::new(vec![Line::from("intro")], true))
as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(
vec![Line::from("intro")],
/*is_first_line*/ true,
)) as Arc<dyn HistoryCell>,
Arc::new(UserHistoryCell {
message: "first".to_string(),
text_elements: Vec::new(),
local_image_paths: Vec::new(),
remote_image_urls: Vec::new(),
}) as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(vec![Line::from("between")], false))
as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(
vec![Line::from("between")],
/*is_first_line*/ false,
)) as Arc<dyn HistoryCell>,
Arc::new(UserHistoryCell {
message: "second".to_string(),
text_elements: Vec::new(),
local_image_paths: Vec::new(),
remote_image_urls: Vec::new(),
}) as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(vec![Line::from("tail")], false))
as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(
vec![Line::from("tail")],
/*is_first_line*/ false,
)) as Arc<dyn HistoryCell>,
];
trim_transcript_cells_to_nth_user(&mut cells, 1);
trim_transcript_cells_to_nth_user(&mut cells, /*nth_user_message*/ 1);
assert_eq!(cells.len(), 3);
let agent_intro = cells[0]
@@ -758,7 +770,7 @@ mod tests {
}) as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(
vec![Line::from("after first")],
false,
/*is_first_line*/ false,
)) as Arc<dyn HistoryCell>,
Arc::new(UserHistoryCell {
message: "second".to_string(),
@@ -768,11 +780,12 @@ mod tests {
}) as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(
vec![Line::from("after second")],
false,
/*is_first_line*/ false,
)) as Arc<dyn HistoryCell>,
];
let changed = trim_transcript_cells_drop_last_n_user_turns(&mut cells, 1);
let changed =
trim_transcript_cells_drop_last_n_user_turns(&mut cells, /*num_turns*/ 1);
assert!(changed);
assert_eq!(cells.len(), 2);
@@ -786,16 +799,20 @@ mod tests {
#[test]
fn trim_drop_last_n_user_turns_allows_overflow() {
let mut cells: Vec<Arc<dyn HistoryCell>> = vec![
Arc::new(AgentMessageCell::new(vec![Line::from("intro")], true))
as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(
vec![Line::from("intro")],
/*is_first_line*/ true,
)) as Arc<dyn HistoryCell>,
Arc::new(UserHistoryCell {
message: "first".to_string(),
text_elements: Vec::new(),
local_image_paths: Vec::new(),
remote_image_urls: Vec::new(),
}) as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(vec![Line::from("after")], false))
as Arc<dyn HistoryCell>,
Arc::new(AgentMessageCell::new(
vec![Line::from("after")],
/*is_first_line*/ false,
)) as Arc<dyn HistoryCell>,
];
let changed = trim_transcript_cells_drop_last_n_user_turns(&mut cells, u32::MAX);
@@ -1235,15 +1235,15 @@ mod tests {
let session = thread_session_state_from_thread_response(
&thread_id.to_string(),
Some("restore".to_string()),
None,
/*rollout_path*/ None,
"gpt-5.4".to_string(),
"openai".to_string(),
None,
/*service_tier*/ None,
AskForApproval::Never,
codex_protocol::config_types::ApprovalsReviewer::User,
SandboxPolicy::new_read_only_policy(),
PathBuf::from("/tmp/project"),
None,
/*reasoning_effort*/ None,
&config,
)
.await
@@ -669,7 +669,7 @@ mod tests {
view.screen = AppLinkScreen::InstallConfirmation;
let rendered: Vec<String> = view
.content_lines(40)
.content_lines(/*width*/ 40)
.into_iter()
.map(|line| {
line.spans
@@ -911,7 +911,10 @@ mod tests {
assert_snapshot!(
"app_link_view_install_suggestion_with_reason",
render_snapshot(&view, Rect::new(0, 0, 72, view.desired_height(72)))
render_snapshot(
&view,
Rect::new(0, 0, 72, view.desired_height(/*width*/ 72))
)
);
}
@@ -937,7 +940,10 @@ mod tests {
assert_snapshot!(
"app_link_view_enable_suggestion_with_reason",
render_snapshot(&view, Rect::new(0, 0, 72, view.desired_height(72)))
render_snapshot(
&view,
Rect::new(0, 0, 72, view.desired_height(/*width*/ 72))
)
);
}
}
@@ -997,7 +997,7 @@ mod tests {
assert_snapshot!(
"approval_overlay_cross_thread_prompt",
render_overlay_lines(&view, 80)
render_overlay_lines(&view, /*width*/ 80)
);
}
@@ -1109,8 +1109,11 @@ mod tests {
};
let view = ApprovalOverlay::new(exec_request, tx, Features::with_defaults());
let mut buf = Buffer::empty(Rect::new(0, 0, 80, view.desired_height(80)));
view.render(Rect::new(0, 0, 80, view.desired_height(80)), &mut buf);
let mut buf = Buffer::empty(Rect::new(0, 0, 80, view.desired_height(/*width*/ 80)));
view.render(
Rect::new(0, 0, 80, view.desired_height(/*width*/ 80)),
&mut buf,
);
let rendered: Vec<String> = (0..buf.area.height)
.map(|row| {
@@ -1146,7 +1149,7 @@ mod tests {
ReviewDecision::Abort,
],
Some(&network_context),
None,
/*additional_permissions*/ None,
);
let labels: Vec<String> = options.into_iter().map(|option| option.label).collect();
@@ -1169,8 +1172,8 @@ mod tests {
ReviewDecision::ApprovedForSession,
ReviewDecision::Abort,
],
None,
None,
/*network_approval_context*/ None,
/*additional_permissions*/ None,
);
let labels: Vec<String> = options.into_iter().map(|option| option.label).collect();
@@ -1195,7 +1198,7 @@ mod tests {
};
let options = exec_options(
&[ReviewDecision::Approved, ReviewDecision::Abort],
None,
/*network_approval_context*/ None,
Some(&additional_permissions),
);
@@ -1276,8 +1279,11 @@ mod tests {
};
let view = ApprovalOverlay::new(exec_request, tx, Features::with_defaults());
let mut buf = Buffer::empty(Rect::new(0, 0, 120, view.desired_height(120)));
view.render(Rect::new(0, 0, 120, view.desired_height(120)), &mut buf);
let mut buf = Buffer::empty(Rect::new(0, 0, 120, view.desired_height(/*width*/ 120)));
view.render(
Rect::new(0, 0, 120, view.desired_height(/*width*/ 120)),
&mut buf,
);
let rendered: Vec<String> = (0..buf.area.height)
.map(|row| {
@@ -1325,7 +1331,7 @@ mod tests {
let view = ApprovalOverlay::new(exec_request, tx, Features::with_defaults());
assert_snapshot!(
"approval_overlay_additional_permissions_prompt",
normalize_snapshot_paths(render_overlay_lines(&view, 120))
normalize_snapshot_paths(render_overlay_lines(&view, /*width*/ 120))
);
}
@@ -1336,7 +1342,7 @@ mod tests {
let view = ApprovalOverlay::new(make_permissions_request(), tx, Features::with_defaults());
assert_snapshot!(
"approval_overlay_permissions_prompt",
normalize_snapshot_paths(render_overlay_lines(&view, 120))
normalize_snapshot_paths(render_overlay_lines(&view, /*width*/ 120))
);
}
@@ -1369,8 +1375,11 @@ mod tests {
};
let view = ApprovalOverlay::new(exec_request, tx, Features::with_defaults());
let mut buf = Buffer::empty(Rect::new(0, 0, 100, view.desired_height(100)));
view.render(Rect::new(0, 0, 100, view.desired_height(100)), &mut buf);
let mut buf = Buffer::empty(Rect::new(0, 0, 100, view.desired_height(/*width*/ 100)));
view.render(
Rect::new(0, 0, 100, view.desired_height(/*width*/ 100)),
&mut buf,
);
assert_snapshot!("network_exec_prompt", format!("{buf:?}"));
let rendered: Vec<String> = (0..buf.area.height)
@@ -1409,7 +1418,7 @@ mod tests {
ReviewDecision::Approved,
history_cell::ApprovalDecisionActor::User,
);
let lines = cell.display_lines(28);
let lines = cell.display_lines(/*width*/ 28);
let rendered: Vec<String> = lines
.iter()
.map(|line| {
File diff suppressed because it is too large Load Diff
@@ -330,10 +330,10 @@ mod tests {
let mut history = ChatComposerHistory::new();
// Pretend there are 3 persistent entries.
history.set_metadata(1, 3);
history.set_metadata(/*log_id*/ 1, /*entry_count*/ 3);
// First Up should request offset 2 (latest) and await async data.
assert!(history.should_handle_navigation("", 0));
assert!(history.should_handle_navigation("", /*cursor*/ 0));
assert!(history.navigate_up(&tx).is_none()); // don't replace the text yet
// Verify that a history lookup request was sent.
@@ -352,7 +352,7 @@ mod tests {
// Inject the async response.
assert_eq!(
Some(HistoryEntry::new("latest".to_string())),
history.on_entry_response(1, 2, Some("latest".into()))
history.on_entry_response(/*log_id*/ 1, /*offset*/ 2, Some("latest".into()))
);
// Next Up should move to offset 1.
@@ -373,7 +373,7 @@ mod tests {
assert_eq!(
Some(HistoryEntry::new("older".to_string())),
history.on_entry_response(1, 1, Some("older".into()))
history.on_entry_response(/*log_id*/ 1, /*offset*/ 1, Some("older".into()))
);
}
@@ -383,7 +383,7 @@ mod tests {
let tx = AppEventSender::new(tx);
let mut history = ChatComposerHistory::new();
history.set_metadata(1, 3);
history.set_metadata(/*log_id*/ 1, /*entry_count*/ 3);
history
.fetched_history
.insert(1, HistoryEntry::new("command2".to_string()));
@@ -416,9 +416,9 @@ mod tests {
history.record_local_submission(HistoryEntry::new("hello".to_string()));
history.last_history_text = Some("hello".to_string());
assert!(history.should_handle_navigation("hello", 0));
assert!(history.should_handle_navigation("hello", /*cursor*/ 0));
assert!(history.should_handle_navigation("hello", "hello".len()));
assert!(!history.should_handle_navigation("hello", 1));
assert!(!history.should_handle_navigation("other", 0));
assert!(!history.should_handle_navigation("hello", /*cursor*/ 1));
assert!(!history.should_handle_navigation("other", /*cursor*/ 0));
}
}
@@ -628,13 +628,13 @@ mod tests {
fn make_view(category: FeedbackCategory) -> FeedbackNoteView {
let (tx_raw, _rx) = tokio::sync::mpsc::unbounded_channel::<AppEvent>();
let tx = AppEventSender::new(tx_raw);
let snapshot = codex_feedback::CodexFeedback::new().snapshot(None);
let snapshot = codex_feedback::CodexFeedback::new().snapshot(/*session_id*/ None);
FeedbackNoteView::new(
category,
snapshot,
None,
/*rollout_path*/ None,
tx,
true,
/*include_logs*/ true,
FeedbackAudience::External,
)
}
@@ -642,35 +642,35 @@ mod tests {
#[test]
fn feedback_view_bad_result() {
let view = make_view(FeedbackCategory::BadResult);
let rendered = render(&view, 60);
let rendered = render(&view, /*width*/ 60);
insta::assert_snapshot!("feedback_view_bad_result", rendered);
}
#[test]
fn feedback_view_good_result() {
let view = make_view(FeedbackCategory::GoodResult);
let rendered = render(&view, 60);
let rendered = render(&view, /*width*/ 60);
insta::assert_snapshot!("feedback_view_good_result", rendered);
}
#[test]
fn feedback_view_bug() {
let view = make_view(FeedbackCategory::Bug);
let rendered = render(&view, 60);
let rendered = render(&view, /*width*/ 60);
insta::assert_snapshot!("feedback_view_bug", rendered);
}
#[test]
fn feedback_view_other() {
let view = make_view(FeedbackCategory::Other);
let rendered = render(&view, 60);
let rendered = render(&view, /*width*/ 60);
insta::assert_snapshot!("feedback_view_other", rendered);
}
#[test]
fn feedback_view_safety_check() {
let view = make_view(FeedbackCategory::SafetyCheck);
let rendered = render(&view, 60);
let rendered = render(&view, /*width*/ 60);
insta::assert_snapshot!("feedback_view_safety_check", rendered);
}
@@ -690,17 +690,17 @@ mod tests {
},
]);
let snapshot = codex_feedback::CodexFeedback::new()
.snapshot(None)
.snapshot(/*session_id*/ None)
.with_feedback_diagnostics(diagnostics);
let view = FeedbackNoteView::new(
FeedbackCategory::Bug,
snapshot,
None,
/*rollout_path*/ None,
tx,
false,
/*include_logs*/ false,
FeedbackAudience::External,
);
let rendered = render(&view, 60);
let rendered = render(&view, /*width*/ 60);
insta::assert_snapshot!("feedback_view_with_connectivity_diagnostics", rendered);
}
@@ -1068,7 +1068,9 @@ mod tests {
use ratatui::backend::TestBackend;
fn snapshot_footer(name: &str, props: FooterProps) {
snapshot_footer_with_mode_indicator(name, 80, &props, None);
snapshot_footer_with_mode_indicator(
name, /*width*/ 80, &props, /*collaboration_mode_indicator*/ None,
);
}
fn draw_footer_frame<B: Backend>(
@@ -1135,7 +1137,10 @@ mod tests {
};
let right_line = if status_line_active {
let full = mode_indicator_line(collaboration_mode_indicator, show_cycle_hint);
let compact = mode_indicator_line(collaboration_mode_indicator, false);
let compact = mode_indicator_line(
collaboration_mode_indicator,
/*show_cycle_hint*/ false,
);
let full_width = full.as_ref().map(|line| line.width() as u16).unwrap_or(0);
if can_show_left_with_context(area, left_width, full_width) {
full
@@ -1455,14 +1460,14 @@ mod tests {
snapshot_footer_with_mode_indicator(
"footer_mode_indicator_wide",
120,
/*width*/ 120,
&props,
Some(CollaborationModeIndicator::Plan),
);
snapshot_footer_with_mode_indicator(
"footer_mode_indicator_narrow_overlap_hides",
50,
/*width*/ 50,
&props,
Some(CollaborationModeIndicator::Plan),
);
@@ -1484,7 +1489,7 @@ mod tests {
snapshot_footer_with_mode_indicator(
"footer_mode_indicator_running_hides_hint",
120,
/*width*/ 120,
&props,
Some(CollaborationModeIndicator::Plan),
);
@@ -1557,7 +1562,7 @@ mod tests {
snapshot_footer_with_mode_indicator(
"footer_status_line_enabled_mode_right",
120,
/*width*/ 120,
&props,
Some(CollaborationModeIndicator::Plan),
);
@@ -1579,7 +1584,7 @@ mod tests {
snapshot_footer_with_mode_indicator(
"footer_status_line_disabled_context_right",
120,
/*width*/ 120,
&props,
Some(CollaborationModeIndicator::Plan),
);
@@ -1602,9 +1607,9 @@ mod tests {
// has status line and no collaboration mode
snapshot_footer_with_mode_indicator(
"footer_status_line_enabled_no_mode_right",
120,
/*width*/ 120,
&props,
None,
/*collaboration_mode_indicator*/ None,
);
let props = FooterProps {
@@ -1626,7 +1631,7 @@ mod tests {
snapshot_footer_with_mode_indicator(
"footer_status_line_truncated_with_gap",
40,
/*width*/ 40,
&props,
Some(CollaborationModeIndicator::Plan),
);
@@ -1686,8 +1691,11 @@ mod tests {
active_agent_label: None,
};
let screen =
render_footer_with_mode_indicator(80, &props, Some(CollaborationModeIndicator::Plan));
let screen = render_footer_with_mode_indicator(
/*width*/ 80,
&props,
Some(CollaborationModeIndicator::Plan),
);
let collapsed = screen.split_whitespace().collect::<Vec<_>>().join(" ");
assert!(
collapsed.contains("Plan mode"),
@@ -1073,7 +1073,7 @@ mod tests {
}
fn render_lines(view: &ListSelectionView) -> String {
render_lines_with_width(view, 48)
render_lines_with_width(view, /*width*/ 48)
}
fn render_lines_with_width(view: &ListSelectionView, width: u16) -> String {
@@ -1153,7 +1153,7 @@ mod tests {
#[test]
fn renders_blank_line_between_title_and_items_without_subtitle() {
let view = make_selection_view(None);
let view = make_selection_view(/*subtitle*/ None);
assert_snapshot!(
"list_selection_spacing_without_subtitle",
render_lines(&view)
@@ -1172,17 +1172,24 @@ mod tests {
let tx = AppEventSender::new(tx_raw);
let home = dirs::home_dir().expect("home directory should be available");
let codex_home = home.join(".codex");
let params =
crate::theme_picker::build_theme_picker_params(None, Some(&codex_home), Some(94));
let params = crate::theme_picker::build_theme_picker_params(
/*current_name*/ None,
Some(&codex_home),
Some(94),
);
let view = ListSelectionView::new(params, tx);
let rendered = render_lines_in_area(&view, 94, 35);
let rendered = render_lines_in_area(&view, /*width*/ 94, /*height*/ 35);
assert!(rendered.contains("Move up/down to live preview themes"));
}
#[test]
fn theme_picker_enables_side_content_background_preservation() {
let params = crate::theme_picker::build_theme_picker_params(None, None, Some(120));
let params = crate::theme_picker::build_theme_picker_params(
/*current_name*/ None,
/*codex_home*/ None,
Some(120),
);
assert!(
params.preserve_side_content_bg,
"theme picker should preserve side-content backgrounds to keep diff preview styling",
@@ -1260,7 +1267,7 @@ mod tests {
);
assert_snapshot!(
"list_selection_footer_note_wraps",
render_lines_with_width(&view, 40)
render_lines_with_width(&view, /*width*/ 40)
);
}
@@ -1380,7 +1387,7 @@ mod tests {
tx,
);
let rendered = render_lines_with_width(&view, 60);
let rendered = render_lines_with_width(&view, /*width*/ 60);
let command_line = rendered
.lines()
.find(|line| line.contains("python -mpre_commit run"))
@@ -1471,7 +1478,7 @@ mod tests {
},
tx,
);
let rendered = render_lines_with_width(&view, 24);
let rendered = render_lines_with_width(&view, /*width*/ 24);
assert!(
rendered.contains("3."),
"third option missing for width 24:\n{rendered}"
@@ -1521,7 +1528,7 @@ mod tests {
);
assert_snapshot!(
"list_selection_model_picker_width_80",
render_lines_with_width(&view, 80)
render_lines_with_width(&view, /*width*/ 80)
);
}
@@ -1548,7 +1555,7 @@ mod tests {
);
assert_snapshot!(
"list_selection_narrow_width_preserves_rows",
render_lines_with_width(&view, 24)
render_lines_with_width(&view, /*width*/ 24)
);
}
@@ -1556,7 +1563,7 @@ mod tests {
fn snapshot_auto_visible_col_width_mode_scroll_behavior() {
assert_snapshot!(
"list_selection_col_width_mode_auto_visible_scroll",
render_before_after_scroll_snapshot(ColumnWidthMode::AutoVisible, 96)
render_before_after_scroll_snapshot(ColumnWidthMode::AutoVisible, /*width*/ 96)
);
}
@@ -1564,7 +1571,7 @@ mod tests {
fn snapshot_auto_all_rows_col_width_mode_scroll_behavior() {
assert_snapshot!(
"list_selection_col_width_mode_auto_all_rows_scroll",
render_before_after_scroll_snapshot(ColumnWidthMode::AutoAllRows, 96)
render_before_after_scroll_snapshot(ColumnWidthMode::AutoAllRows, /*width*/ 96)
);
}
@@ -1572,7 +1579,7 @@ mod tests {
fn snapshot_fixed_col_width_mode_scroll_behavior() {
assert_snapshot!(
"list_selection_col_width_mode_fixed_scroll",
render_before_after_scroll_snapshot(ColumnWidthMode::Fixed, 96)
render_before_after_scroll_snapshot(ColumnWidthMode::Fixed, /*width*/ 96)
);
}
@@ -1591,11 +1598,11 @@ mod tests {
tx,
);
let before_scroll = render_lines_with_width(&view, 96);
let before_scroll = render_lines_with_width(&view, /*width*/ 96);
for _ in 0..8 {
view.handle_key_event(KeyEvent::from(KeyCode::Down));
}
let after_scroll = render_lines_with_width(&view, 96);
let after_scroll = render_lines_with_width(&view, /*width*/ 96);
assert!(
after_scroll.contains("9. Item 9 with an intentionally much longer name"),
@@ -1693,7 +1700,7 @@ mod tests {
tx,
);
assert_eq!(view.side_layout_width(80), None);
assert_eq!(view.side_layout_width(/*content_width*/ 80), None);
}
#[test]
@@ -1723,7 +1730,7 @@ mod tests {
tx,
);
let rendered = render_lines_with_width(&view, 70);
let rendered = render_lines_with_width(&view, /*width*/ 70);
assert!(
rendered.contains('N'),
"expected stacked marker to be rendered:\n{rendered}"
@@ -1800,7 +1800,7 @@ mod tests {
},
"required": ["confirmed"],
}),
None,
/*meta*/ None,
),
)
.expect("expected supported form");
@@ -1855,7 +1855,7 @@ mod tests {
}
},
}),
None,
/*meta*/ None,
),
);
@@ -1867,7 +1867,7 @@ mod tests {
let thread_id = ThreadId::default();
let request = McpServerElicitationFormRequest::from_event(
thread_id,
form_request("Allow this request?", Value::Null, None),
form_request("Allow this request?", Value::Null, /*meta*/ None),
)
.expect("expected approval fallback");
@@ -1921,7 +1921,11 @@ mod tests {
form_request(
"Allow this request?",
empty_object_schema(),
tool_approval_meta(&[], None, None),
tool_approval_meta(
&[],
/*tool_params*/ None,
/*tool_params_display*/ None,
),
),
)
.expect("expected approval fallback");
@@ -2030,7 +2034,7 @@ mod tests {
fn empty_unmarked_schema_falls_back() {
let request = McpServerElicitationFormRequest::from_event(
ThreadId::default(),
form_request("Empty form", empty_object_schema(), None),
form_request("Empty form", empty_object_schema(), /*meta*/ None),
);
assert_eq!(request, None);
@@ -2102,13 +2106,16 @@ mod tests {
},
"required": ["confirmed"],
}),
None,
/*meta*/ None,
),
)
.expect("expected supported form");
let mut overlay = McpServerElicitationOverlay::new(request, tx, true, false, false);
let mut overlay = McpServerElicitationOverlay::new(
request, tx, /*has_input_focus*/ true, /*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.select_current_option(true);
overlay.select_current_option(/*committed*/ true);
overlay.submit_answers();
let event = rx.try_recv().expect("expected resolution");
@@ -2148,18 +2155,21 @@ mod tests {
APPROVAL_PERSIST_SESSION_VALUE,
APPROVAL_PERSIST_ALWAYS_VALUE,
],
None,
None,
/*tool_params*/ None,
/*tool_params_display*/ None,
),
),
)
.expect("expected approval fallback");
let mut overlay = McpServerElicitationOverlay::new(request, tx, true, false, false);
let mut overlay = McpServerElicitationOverlay::new(
request, tx, /*has_input_focus*/ true, /*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
if let Some(answer) = overlay.current_answer_mut() {
answer.selection.selected_idx = Some(1);
}
overlay.select_current_option(true);
overlay.select_current_option(/*committed*/ true);
overlay.submit_answers();
let event = rx.try_recv().expect("expected resolution");
@@ -2199,18 +2209,21 @@ mod tests {
APPROVAL_PERSIST_SESSION_VALUE,
APPROVAL_PERSIST_ALWAYS_VALUE,
],
None,
None,
/*tool_params*/ None,
/*tool_params_display*/ None,
),
),
)
.expect("expected approval fallback");
let mut overlay = McpServerElicitationOverlay::new(request, tx, true, false, false);
let mut overlay = McpServerElicitationOverlay::new(
request, tx, /*has_input_focus*/ true, /*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
if let Some(answer) = overlay.current_answer_mut() {
answer.selection.selected_idx = Some(2);
}
overlay.select_current_option(true);
overlay.select_current_option(/*committed*/ true);
overlay.submit_answers();
let event = rx.try_recv().expect("expected resolution");
@@ -2255,11 +2268,14 @@ mod tests {
},
"required": ["confirmed"],
}),
None,
/*meta*/ None,
),
)
.expect("expected supported form");
let mut overlay = McpServerElicitationOverlay::new(request, tx, true, false, false);
let mut overlay = McpServerElicitationOverlay::new(
request, tx, /*has_input_focus*/ true, /*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
assert_eq!(overlay.on_ctrl_c(), CancellationEvent::Handled);
@@ -2300,7 +2316,7 @@ mod tests {
}
},
}),
None,
/*meta*/ None,
),
)
.expect("expected supported form");
@@ -2317,7 +2333,7 @@ mod tests {
}
},
}),
None,
/*meta*/ None,
),
)
.expect("expected supported form");
@@ -2334,20 +2350,23 @@ mod tests {
}
},
}),
None,
/*meta*/ None,
),
)
.expect("expected supported form");
let mut overlay = McpServerElicitationOverlay::new(first, tx, true, false, false);
let mut overlay = McpServerElicitationOverlay::new(
first, tx, /*has_input_focus*/ true, /*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.try_consume_mcp_server_elicitation_request(second);
overlay.try_consume_mcp_server_elicitation_request(third);
overlay.select_current_option(true);
overlay.select_current_option(/*committed*/ true);
overlay.submit_answers();
assert_eq!(overlay.request.message, "Second");
overlay.select_current_option(true);
overlay.select_current_option(/*committed*/ true);
overlay.submit_answers();
assert_eq!(overlay.request.message, "Third");
@@ -2371,11 +2390,14 @@ mod tests {
},
"required": ["confirmed"],
}),
None,
/*meta*/ None,
),
)
.expect("expected supported form");
let overlay = McpServerElicitationOverlay::new(request, tx, true, false, false);
let overlay = McpServerElicitationOverlay::new(
request, tx, /*has_input_focus*/ true, /*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
insta::assert_snapshot!(
"mcp_server_elicitation_boolean_form",
@@ -2391,11 +2413,18 @@ mod tests {
form_request(
"Allow this request?",
empty_object_schema(),
tool_approval_meta(&[], None, None),
tool_approval_meta(
&[],
/*tool_params*/ None,
/*tool_params_display*/ None,
),
),
)
.expect("expected approval fallback");
let overlay = McpServerElicitationOverlay::new(request, tx, true, false, false);
let overlay = McpServerElicitationOverlay::new(
request, tx, /*has_input_focus*/ true, /*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
insta::assert_snapshot!(
"mcp_server_elicitation_approval_form_without_schema",
@@ -2416,13 +2445,16 @@ mod tests {
APPROVAL_PERSIST_SESSION_VALUE,
APPROVAL_PERSIST_ALWAYS_VALUE,
],
None,
None,
/*tool_params*/ None,
/*tool_params_display*/ None,
),
),
)
.expect("expected approval fallback");
let overlay = McpServerElicitationOverlay::new(request, tx, true, false, false);
let overlay = McpServerElicitationOverlay::new(
request, tx, /*has_input_focus*/ true, /*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
insta::assert_snapshot!(
"mcp_server_elicitation_approval_form_with_session_persist",
@@ -2472,7 +2504,10 @@ mod tests {
),
)
.expect("expected approval fallback");
let overlay = McpServerElicitationOverlay::new(request, tx, true, false, false);
let overlay = McpServerElicitationOverlay::new(
request, tx, /*has_input_focus*/ true, /*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
insta::assert_snapshot!(
"mcp_server_elicitation_approval_form_with_param_summary",
+14 -14
View File
@@ -1369,7 +1369,7 @@ mod tests {
});
// Start a running task so the status indicator is active above the composer.
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
// Push an approval modal (e.g., command approval) which should hide the status view.
pane.push_approval_request(exec_request(), &features);
@@ -1436,7 +1436,7 @@ mod tests {
});
// Begin a task: show initial status.
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
// Use a height that allows the status line to be visible above the composer.
let area = Rect::new(0, 0, 40, 6);
@@ -1463,10 +1463,10 @@ mod tests {
});
// Activate spinner (status view replaces composer) with no live ring.
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
// Use height == desired_height; expect spacer + status + composer rows without trailing padding.
let height = pane.desired_height(30);
let height = pane.desired_height(/*width*/ 30);
assert!(
height >= 3,
"expected at least 3 rows to render spacer, status, and composer; got {height}"
@@ -1493,7 +1493,7 @@ mod tests {
skills: Some(Vec::new()),
});
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
let width = 48;
let height = pane.desired_height(width);
@@ -1516,7 +1516,7 @@ mod tests {
skills: Some(Vec::new()),
});
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
let width = 120;
let before = pane.desired_height(width);
@@ -1545,7 +1545,7 @@ mod tests {
skills: Some(Vec::new()),
});
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
pane.update_status(
"Working".to_string(),
Some("First detail line\nSecond detail line".to_string()),
@@ -1582,7 +1582,7 @@ mod tests {
skills: Some(Vec::new()),
});
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
pane.set_pending_input_preview(
vec!["Queued follow-up question".to_string()],
Vec::new(),
@@ -1614,7 +1614,7 @@ mod tests {
skills: Some(Vec::new()),
});
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
pane.set_pending_input_preview(
vec!["Queued follow-up question".to_string()],
Vec::new(),
@@ -1706,7 +1706,7 @@ mod tests {
}]),
});
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
// Repro: a running task + skill popup + Esc should dismiss the popup, not interrupt.
pane.insert_str("$");
@@ -1744,7 +1744,7 @@ mod tests {
skills: Some(Vec::new()),
});
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
// Repro: a running task + slash-command popup + Esc should not interrupt the task.
pane.insert_str("/");
@@ -1779,7 +1779,7 @@ mod tests {
skills: Some(Vec::new()),
});
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
// Repro: `/agent ` hides the popup (cursor past command name). Esc should
// keep editing command text instead of interrupting the running task.
@@ -1815,7 +1815,7 @@ mod tests {
skills: Some(Vec::new()),
});
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
pane.show_selection_view(SelectionViewParams {
title: Some("Agents".to_string()),
items: vec![SelectionItem {
@@ -1863,7 +1863,7 @@ mod tests {
skills: Some(Vec::new()),
});
pane.set_task_running(true);
pane.set_task_running(/*running*/ true);
pane.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
@@ -532,11 +532,15 @@ mod tests {
let mut burst = PasteBurst::default();
let now = Instant::now();
assert!(burst.decide_begin_buffer(now, "ab", 2).is_none());
assert!(
burst
.decide_begin_buffer(now, "ab", /*retro_chars*/ 2)
.is_none()
);
assert!(!burst.is_active());
let grab = burst
.decide_begin_buffer(now, "a b", 2)
.decide_begin_buffer(now, "a b", /*retro_chars*/ 2)
.expect("whitespace should be considered paste-like");
assert_eq!(grab.start_byte, 1);
assert_eq!(grab.grabbed, " b");
@@ -183,14 +183,14 @@ mod tests {
#[test]
fn desired_height_empty() {
let queue = PendingInputPreview::new();
assert_eq!(queue.desired_height(40), 0);
assert_eq!(queue.desired_height(/*width*/ 40), 0);
}
#[test]
fn desired_height_one_message() {
let mut queue = PendingInputPreview::new();
queue.queued_messages.push("Hello, world!".to_string());
assert_eq!(queue.desired_height(40), 3);
assert_eq!(queue.desired_height(/*width*/ 40), 3);
}
#[test]
@@ -108,7 +108,7 @@ mod tests {
#[test]
fn desired_height_empty() {
let widget = PendingThreadApprovals::new();
assert_eq!(widget.desired_height(40), 0);
assert_eq!(widget.desired_height(/*width*/ 40), 0);
}
#[test]
@@ -117,7 +117,7 @@ mod tests {
widget.set_threads(vec!["Robie [explorer]".to_string()]);
assert_snapshot!(
snapshot_rows(&widget, 40).replace(' ', "."),
snapshot_rows(&widget, /*width*/ 40).replace(' ', "."),
@r"
..!.Approval.needed.in.Robie.[explorer].
..../agent.to.switch.threads............
@@ -136,7 +136,7 @@ mod tests {
]);
assert_snapshot!(
snapshot_rows(&widget, 44).replace(' ', "."),
snapshot_rows(&widget, /*width*/ 44).replace(' ', "."),
@r"
..!.Approval.needed.in.Main.[default].......
..!.Approval.needed.in.Robie.[explorer].....
@@ -1485,9 +1485,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "First")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.try_consume_user_input_request(request_event(
"turn-2",
@@ -1511,9 +1511,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "First")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.try_consume_user_input_request(RequestUserInputEvent {
call_id: "call-2".to_string(),
@@ -1538,9 +1538,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.submit_answers();
@@ -1560,9 +1560,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.handle_key_event(KeyEvent::from(KeyCode::Enter));
@@ -1587,9 +1587,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.handle_key_event(KeyEvent::from(KeyCode::Enter));
@@ -1629,9 +1629,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.handle_key_event(KeyEvent::from(KeyCode::Char('2')));
@@ -1650,9 +1650,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let answer = overlay.current_answer().expect("answer missing");
assert_eq!(answer.options_state.selected_idx, Some(0));
@@ -1678,9 +1678,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
assert_eq!(overlay.current_index(), 0);
@@ -1704,9 +1704,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
assert_eq!(overlay.current_index(), 0);
@@ -1728,9 +1728,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
assert_eq!(overlay.current_index(), 0);
@@ -1752,9 +1752,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let tips = overlay.footer_tips();
let tip_texts = tips.iter().map(|tip| tip.text.as_str()).collect::<Vec<_>>();
@@ -1789,11 +1789,11 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.move_question(true);
overlay.move_question(/*next*/ true);
let tips = overlay.footer_tips();
let tip_texts = tips.iter().map(|tip| tip.text.as_str()).collect::<Vec<_>>();
@@ -1813,9 +1813,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let answer = overlay.current_answer_mut().expect("answer missing");
answer.options_state.selected_idx = Some(1);
@@ -1838,13 +1838,13 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
assert!(matches!(overlay.focus, Focus::Notes));
overlay.move_question(true);
overlay.move_question(/*next*/ true);
assert!(matches!(overlay.focus, Focus::Options));
assert_eq!(overlay.notes_ui_visible(), false);
@@ -1862,9 +1862,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay
@@ -1872,7 +1872,7 @@ mod tests {
.set_text_content("freeform notes".to_string(), Vec::new(), Vec::new());
overlay.composer.move_cursor_to_end();
overlay.move_question(true);
overlay.move_question(/*next*/ true);
assert!(matches!(overlay.focus, Focus::Options));
assert_eq!(overlay.notes_ui_visible(), false);
@@ -1902,9 +1902,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_without_options("q1", "Notes")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.handle_key_event(KeyEvent::from(KeyCode::Esc));
@@ -1919,9 +1919,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.handle_key_event(KeyEvent::from(KeyCode::Esc));
@@ -1936,9 +1936,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let answer = overlay.current_answer_mut().expect("answer missing");
answer.options_state.selected_idx = Some(0);
@@ -1964,9 +1964,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let answer = overlay.current_answer_mut().expect("answer missing");
answer.options_state.selected_idx = Some(0);
@@ -1999,9 +1999,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.handle_key_event(KeyEvent::from(KeyCode::Enter));
@@ -2021,9 +2021,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let answer = overlay.current_answer_mut().expect("answer missing");
answer.options_state.selected_idx = Some(1);
@@ -2042,9 +2042,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let answer = overlay.current_answer_mut().expect("answer missing");
answer.options_state.selected_idx = Some(0);
@@ -2068,9 +2068,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let answer = overlay.current_answer_mut().expect("answer missing");
answer.options_state.selected_idx = Some(0);
@@ -2097,9 +2097,9 @@ mod tests {
let overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
assert_eq!(overlay.unanswered_count(), 1);
@@ -2111,9 +2111,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let answer = overlay.current_answer_mut().expect("answer missing");
answer.options_state.selected_idx = Some(0);
@@ -2133,9 +2133,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay
@@ -2162,9 +2162,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.handle_key_event(KeyEvent::from(KeyCode::Enter));
@@ -2179,9 +2179,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_without_options("q1", "Notes")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.submit_answers();
@@ -2200,9 +2200,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_without_options("q1", "Notes")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay
.composer
@@ -2231,9 +2231,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay
@@ -2244,12 +2244,12 @@ mod tests {
assert_eq!(overlay.answers[0].answer_committed, true);
let _ = rx.try_recv();
overlay.move_question(false);
overlay.move_question(/*next*/ false);
overlay
.composer
.set_text_content("Edited".to_string(), Vec::new(), Vec::new());
overlay.composer.move_cursor_to_end();
overlay.move_question(true);
overlay.move_question(/*next*/ true);
assert_eq!(overlay.answers[0].answer_committed, false);
overlay.submit_answers();
@@ -2268,16 +2268,16 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
{
let answer = overlay.current_answer_mut().expect("answer missing");
answer.options_state.selected_idx = Some(1);
}
overlay.select_current_option(false);
overlay.select_current_option(/*committed*/ false);
overlay
.composer
.set_text_content("Notes for option 2".to_string(), Vec::new(), Vec::new());
@@ -2316,9 +2316,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.handle_key_event(KeyEvent::from(KeyCode::Down));
@@ -2345,9 +2345,9 @@ mod tests {
vec![question_with_options_and_other("q1", "Pick one")],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let rows = overlay.option_rows();
@@ -2401,14 +2401,14 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let large = "x".repeat(1_500);
overlay.composer.handle_paste(large.clone());
overlay.move_question(true);
overlay.move_question(/*next*/ true);
let draft = &overlay.answers[0].draft;
assert_eq!(draft.pending_pastes.len(), 1);
@@ -2429,9 +2429,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let large = "x".repeat(1_200);
@@ -2454,9 +2454,9 @@ mod tests {
let overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Area")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let area = Rect::new(0, 0, 120, 16);
insta::assert_snapshot!(
@@ -2471,9 +2471,9 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Area")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
{
let answer = overlay.current_answer_mut().expect("answer missing");
@@ -2494,9 +2494,9 @@ mod tests {
let overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Area")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let area = Rect::new(0, 0, 120, 10);
insta::assert_snapshot!(
@@ -2514,9 +2514,9 @@ mod tests {
vec![question_with_wrapped_options("q1", "Next Step")],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let width = 48u16;
@@ -2542,9 +2542,9 @@ mod tests {
vec![question_with_wrapped_options("q1", "Next Step")],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let width = 110u16;
@@ -2574,9 +2574,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let answer = overlay.current_answer_mut().expect("answer missing");
answer.options_state.selected_idx = Some(0);
@@ -2608,9 +2608,9 @@ mod tests {
vec![question_with_wrapped_options("q1", "Next Step")],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
{
@@ -2641,9 +2641,9 @@ mod tests {
vec![question_with_very_long_option_text("q1", "Status")],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let area = Rect::new(0, 0, 120, 18);
insta::assert_snapshot!(
@@ -2661,9 +2661,9 @@ mod tests {
vec![question_with_long_scroll_options("q1", "Scroll")],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let answer = overlay.current_answer_mut().expect("answer missing");
answer.options_state.selected_idx = Some(2);
@@ -2687,9 +2687,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let answer = overlay.current_answer_mut().expect("answer missing");
answer.options_state.selected_idx = Some(1);
@@ -2740,9 +2740,9 @@ mod tests {
}],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
{
let answer = overlay.current_answer_mut().expect("answer missing");
@@ -2792,9 +2792,9 @@ mod tests {
}],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
{
let answer = overlay.current_answer_mut().expect("answer missing");
@@ -2813,9 +2813,9 @@ mod tests {
let overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_without_options("q1", "Goal")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let area = Rect::new(0, 0, 120, 10);
insta::assert_snapshot!(
@@ -2836,9 +2836,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
let area = Rect::new(0, 0, 120, 15);
insta::assert_snapshot!(
@@ -2859,11 +2859,11 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.move_question(true);
overlay.move_question(/*next*/ true);
let area = Rect::new(0, 0, 120, 12);
insta::assert_snapshot!(
"request_user_input_multi_question_last",
@@ -2883,9 +2883,9 @@ mod tests {
],
),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.open_unanswered_confirmation();
@@ -2903,11 +2903,11 @@ mod tests {
let mut overlay = RequestUserInputOverlay::new(
request_event("turn-1", vec![question_with_options("q1", "Pick one")]),
tx,
true,
false,
false,
/*has_input_focus*/ true,
/*enhanced_keys_supported*/ false,
/*disable_paste_burst*/ false,
);
overlay.select_current_option(false);
overlay.select_current_option(/*committed*/ false);
overlay.focus = Focus::Notes;
overlay
.composer
@@ -863,7 +863,7 @@ mod tests {
..Default::default()
};
let two_col = wrap_two_column_row(&row, 0, 1);
let two_col = wrap_two_column_row(&row, /*desc_col*/ 0, /*width*/ 1);
assert_eq!(two_col.len(), 0);
}
}
@@ -429,6 +429,6 @@ mod tests {
},
];
let view = SkillsToggleView::new(items, tx);
assert_snapshot!("skills_toggle_basic", render_lines(&view, 72));
assert_snapshot!("skills_toggle_basic", render_lines(&view, /*width*/ 72));
}
}
@@ -366,7 +366,7 @@ mod tests {
AppEventSender::new(tx_raw),
);
assert_snapshot!(render_lines(&view, 72));
assert_snapshot!(render_lines(&view, /*width*/ 72));
}
fn render_lines(view: &StatusLineSetupView, width: u16) -> String {
@@ -1468,17 +1468,17 @@ mod tests {
fn insert_and_replace_update_cursor_and_text() {
// insert helpers
let mut t = ta_with("hello");
t.set_cursor(5);
t.set_cursor(/*pos*/ 5);
t.insert_str("!");
assert_eq!(t.text(), "hello!");
assert_eq!(t.cursor(), 6);
t.insert_str_at(0, "X");
t.insert_str_at(/*pos*/ 0, "X");
assert_eq!(t.text(), "Xhello!");
assert_eq!(t.cursor(), 7);
// Insert after the cursor should not move it
t.set_cursor(1);
t.set_cursor(/*pos*/ 1);
let end = t.text().len();
t.insert_str_at(end, "Y");
assert_eq!(t.text(), "Xhello!Y");
@@ -1487,21 +1487,21 @@ mod tests {
// replace_range cases
// 1) cursor before range
let mut t = ta_with("abcd");
t.set_cursor(1);
t.set_cursor(/*pos*/ 1);
t.replace_range(2..3, "Z");
assert_eq!(t.text(), "abZd");
assert_eq!(t.cursor(), 1);
// 2) cursor inside range
let mut t = ta_with("abcd");
t.set_cursor(2);
t.set_cursor(/*pos*/ 2);
t.replace_range(1..3, "Q");
assert_eq!(t.text(), "aQd");
assert_eq!(t.cursor(), 2);
// 3) cursor after range with shifted by diff
let mut t = ta_with("abcd");
t.set_cursor(4);
t.set_cursor(/*pos*/ 4);
t.replace_range(0..1, "AA");
assert_eq!(t.text(), "AAbcd");
assert_eq!(t.cursor(), 5);
@@ -1511,8 +1511,8 @@ mod tests {
fn insert_str_at_clamps_to_char_boundary() {
let mut t = TextArea::new();
t.insert_str("");
t.set_cursor(0);
t.insert_str_at(1, "A");
t.set_cursor(/*pos*/ 0);
t.insert_str_at(/*pos*/ 1, "A");
assert_eq!(t.text(), "A你");
assert_eq!(t.cursor(), 1);
}
@@ -1521,7 +1521,7 @@ mod tests {
fn set_text_clamps_cursor_to_char_boundary() {
let mut t = TextArea::new();
t.insert_str("abcd");
t.set_cursor(1);
t.set_cursor(/*pos*/ 1);
t.set_text_clearing_elements("");
assert_eq!(t.cursor(), 0);
t.insert_str("a");
@@ -1531,26 +1531,26 @@ mod tests {
#[test]
fn delete_backward_and_forward_edges() {
let mut t = ta_with("abc");
t.set_cursor(1);
t.delete_backward(1);
t.set_cursor(/*pos*/ 1);
t.delete_backward(/*n*/ 1);
assert_eq!(t.text(), "bc");
assert_eq!(t.cursor(), 0);
// deleting backward at start is a no-op
t.set_cursor(0);
t.delete_backward(1);
t.set_cursor(/*pos*/ 0);
t.delete_backward(/*n*/ 1);
assert_eq!(t.text(), "bc");
assert_eq!(t.cursor(), 0);
// forward delete removes next grapheme
t.set_cursor(1);
t.delete_forward(1);
t.set_cursor(/*pos*/ 1);
t.delete_forward(/*n*/ 1);
assert_eq!(t.text(), "b");
assert_eq!(t.cursor(), 1);
// forward delete at end is a no-op
t.set_cursor(t.text().len());
t.delete_forward(1);
t.delete_forward(/*n*/ 1);
assert_eq!(t.text(), "b");
}
@@ -1563,7 +1563,7 @@ mod tests {
let elem_start = t.elements[0].range.start;
t.set_cursor(elem_start);
t.delete_forward(1);
t.delete_forward(/*n*/ 1);
assert_eq!(t.text(), "ab");
assert_eq!(t.cursor(), elem_start);
@@ -1580,7 +1580,7 @@ mod tests {
// From inside a word, delete from word start to cursor
let mut t = ta_with("foo bar");
t.set_cursor(6); // inside "bar" (after 'a')
t.set_cursor(/*pos*/ 6); // inside "bar" (after 'a')
t.delete_backward_word();
assert_eq!(t.text(), "foo r");
assert_eq!(t.cursor(), 4);
@@ -1594,27 +1594,27 @@ mod tests {
// kill_to_end_of_line when not at EOL
let mut t = ta_with("abc\ndef");
t.set_cursor(1); // on first line, middle
t.set_cursor(/*pos*/ 1); // on first line, middle
t.kill_to_end_of_line();
assert_eq!(t.text(), "a\ndef");
assert_eq!(t.cursor(), 1);
// kill_to_end_of_line when at EOL deletes newline
let mut t = ta_with("abc\ndef");
t.set_cursor(3); // EOL of first line
t.set_cursor(/*pos*/ 3); // EOL of first line
t.kill_to_end_of_line();
assert_eq!(t.text(), "abcdef");
assert_eq!(t.cursor(), 3);
// kill_to_beginning_of_line from middle of line
let mut t = ta_with("abc\ndef");
t.set_cursor(5); // on second line, after 'e'
t.set_cursor(/*pos*/ 5); // on second line, after 'e'
t.kill_to_beginning_of_line();
assert_eq!(t.text(), "abc\nef");
// kill_to_beginning_of_line at beginning of non-first line removes the previous newline
let mut t = ta_with("abc\ndef");
t.set_cursor(4); // beginning of second line
t.set_cursor(/*pos*/ 4); // beginning of second line
t.kill_to_beginning_of_line();
assert_eq!(t.text(), "abcdef");
assert_eq!(t.cursor(), 3);
@@ -1623,13 +1623,13 @@ mod tests {
#[test]
fn delete_forward_word_variants() {
let mut t = ta_with("hello world ");
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
t.delete_forward_word();
assert_eq!(t.text(), " world ");
assert_eq!(t.cursor(), 0);
let mut t = ta_with("hello world ");
t.set_cursor(1);
t.set_cursor(/*pos*/ 1);
t.delete_forward_word();
assert_eq!(t.text(), "h world ");
assert_eq!(t.cursor(), 1);
@@ -1641,13 +1641,13 @@ mod tests {
assert_eq!(t.cursor(), t.text().len());
let mut t = ta_with("foo \nbar");
t.set_cursor(3);
t.set_cursor(/*pos*/ 3);
t.delete_forward_word();
assert_eq!(t.text(), "foo");
assert_eq!(t.cursor(), 3);
let mut t = ta_with("foo\nbar");
t.set_cursor(3);
t.set_cursor(/*pos*/ 3);
t.delete_forward_word();
assert_eq!(t.text(), "foo");
assert_eq!(t.cursor(), 3);
@@ -1665,7 +1665,7 @@ mod tests {
t.insert_element("<element>");
t.insert_str(" tail");
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
t.delete_forward_word();
assert_eq!(t.text(), " tail");
assert_eq!(t.cursor(), 0);
@@ -1675,7 +1675,7 @@ mod tests {
t.insert_element("<element>");
t.insert_str(" tail");
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
t.delete_forward_word();
assert_eq!(t.text(), " tail");
assert_eq!(t.cursor(), 0);
@@ -1721,7 +1721,7 @@ mod tests {
#[test]
fn delete_forward_word_respects_word_separators() {
let mut t = ta_with("path/to/file");
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
t.delete_forward_word();
assert_eq!(t.text(), "/to/file");
assert_eq!(t.cursor(), 0);
@@ -1731,13 +1731,13 @@ mod tests {
assert_eq!(t.cursor(), 0);
let mut t = ta_with("/ foo");
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
t.delete_forward_word();
assert_eq!(t.text(), " foo");
assert_eq!(t.cursor(), 0);
let mut t = ta_with(" /foo");
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
t.delete_forward_word();
assert_eq!(t.text(), "foo");
assert_eq!(t.cursor(), 0);
@@ -1746,7 +1746,7 @@ mod tests {
#[test]
fn yank_restores_last_kill() {
let mut t = ta_with("hello");
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
t.kill_to_end_of_line();
assert_eq!(t.text(), "");
assert_eq!(t.cursor(), 0);
@@ -1766,7 +1766,7 @@ mod tests {
assert_eq!(t.cursor(), 11);
let mut t = ta_with("hello");
t.set_cursor(5);
t.set_cursor(/*pos*/ 5);
t.kill_to_beginning_of_line();
assert_eq!(t.text(), "");
assert_eq!(t.cursor(), 0);
@@ -1779,7 +1779,7 @@ mod tests {
#[test]
fn kill_buffer_persists_across_set_text() {
let mut t = ta_with("restore me");
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
t.kill_to_end_of_line();
assert!(t.text().is_empty());
@@ -1817,7 +1817,7 @@ mod tests {
#[test]
fn control_b_and_f_move_cursor() {
let mut t = ta_with("abcd");
t.set_cursor(1);
t.set_cursor(/*pos*/ 1);
t.input(KeyEvent::new(KeyCode::Char('f'), KeyModifiers::CONTROL));
assert_eq!(t.cursor(), 2);
@@ -1829,7 +1829,7 @@ mod tests {
#[test]
fn control_b_f_fallback_control_chars_move_cursor() {
let mut t = ta_with("abcd");
t.set_cursor(2);
t.set_cursor(/*pos*/ 2);
// Simulate terminals that send C0 control chars without CONTROL modifier.
// ^B (U+0002) should move left
@@ -1873,13 +1873,13 @@ mod tests {
#[test]
fn delete_forward_word_with_without_alt_modifier() {
let mut t = ta_with("hello world");
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
t.input(KeyEvent::new(KeyCode::Delete, KeyModifiers::ALT));
assert_eq!(t.text(), " world");
assert_eq!(t.cursor(), 0);
let mut t = ta_with("hello");
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
t.input(KeyEvent::new(KeyCode::Delete, KeyModifiers::NONE));
assert_eq!(t.text(), "ello");
assert_eq!(t.cursor(), 0);
@@ -1888,7 +1888,7 @@ mod tests {
#[test]
fn delete_forward_word_alt_d() {
let mut t = ta_with("hello world");
t.set_cursor(6);
t.set_cursor(/*pos*/ 6);
t.input(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::ALT));
pretty_assertions::assert_eq!(t.text(), "hello ");
pretty_assertions::assert_eq!(t.cursor(), 6);
@@ -1898,13 +1898,13 @@ mod tests {
fn control_h_backspace() {
// Test Ctrl+H as backspace
let mut t = ta_with("12345");
t.set_cursor(3); // cursor after '3'
t.set_cursor(/*pos*/ 3); // cursor after '3'
t.input(KeyEvent::new(KeyCode::Char('h'), KeyModifiers::CONTROL));
assert_eq!(t.text(), "1245");
assert_eq!(t.cursor(), 2);
// Test Ctrl+H at beginning (should be no-op)
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
t.input(KeyEvent::new(KeyCode::Char('h'), KeyModifiers::CONTROL));
assert_eq!(t.text(), "1245");
assert_eq!(t.cursor(), 0);
@@ -1967,19 +1967,19 @@ mod tests {
let second_line_start = t.text().find("two").unwrap();
t.set_cursor(second_line_start + 1);
t.move_cursor_to_beginning_of_line(false);
t.move_cursor_to_beginning_of_line(/*move_up_at_bol*/ false);
assert_eq!(t.cursor(), second_line_start);
// Ctrl-A behavior: if at BOL, go to beginning of previous line
t.move_cursor_to_beginning_of_line(true);
t.move_cursor_to_beginning_of_line(/*move_up_at_bol*/ true);
assert_eq!(t.cursor(), 0); // beginning of first line
// Move to EOL of first line
t.move_cursor_to_end_of_line(false);
t.move_cursor_to_end_of_line(/*move_down_at_eol*/ false);
assert_eq!(t.cursor(), 3);
// Ctrl-E: if at EOL, go to end of next line
t.move_cursor_to_end_of_line(true);
t.move_cursor_to_end_of_line(/*move_down_at_eol*/ true);
// end of second line ("two") is right before its '\n'
let end_second_nl = t.text().find("\nthree").unwrap();
assert_eq!(t.cursor(), end_second_nl);
@@ -1991,13 +1991,13 @@ mod tests {
// Place cursor at absolute end of the text
t.set_cursor(t.text().len());
// Should remain at end without panicking
t.move_cursor_to_end_of_line(true);
t.move_cursor_to_end_of_line(/*move_down_at_eol*/ true);
assert_eq!(t.cursor(), t.text().len());
// Also verify behavior when at EOL of a non-final line:
let eol_first_line = 3; // index of '\n' in "one\ntwo"
t.set_cursor(eol_first_line);
t.move_cursor_to_end_of_line(true);
t.move_cursor_to_end_of_line(/*move_down_at_eol*/ true);
assert_eq!(t.cursor(), t.text().len()); // moves to end of next (last) line
}
@@ -2052,7 +2052,7 @@ mod tests {
fn cursor_pos_with_state_basic_and_scroll_behaviors() {
// Case 1: No wrapping needed, height fits — scroll ignored, y maps directly.
let mut t = ta_with("hello world");
t.set_cursor(3);
t.set_cursor(/*pos*/ 3);
let area = Rect::new(2, 5, 20, 3);
// Even if an absurd scroll is provided, when content fits the area the
// effective scroll is 0 and the cursor position matches cursor_pos.
@@ -2080,7 +2080,7 @@ mod tests {
let wrap_width = 5;
let lines = t.desired_height(wrap_width);
// Place cursor near start so an excessive scroll moves it to top row.
t.set_cursor(1);
t.set_cursor(/*pos*/ 1);
let area = Rect::new(0, 0, wrap_width, 3);
let state = TextAreaState {
scroll: lines.saturating_mul(2),
@@ -2093,15 +2093,15 @@ mod tests {
fn wrapped_navigation_across_visual_lines() {
let mut t = ta_with("abcdefghij");
// Force wrapping at width 4: lines -> ["abcd", "efgh", "ij"]
let _ = t.desired_height(4);
let _ = t.desired_height(/*width*/ 4);
// From the very start, moving down should go to the start of the next wrapped line (index 4)
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
t.move_cursor_down();
assert_eq!(t.cursor(), 4);
// Cursor at boundary index 4 should be displayed at start of second wrapped line
t.set_cursor(4);
t.set_cursor(/*pos*/ 4);
let area = Rect::new(0, 0, 4, 10);
let (x, y) = t.cursor_pos(area).unwrap();
assert_eq!((x, y), (0, 1));
@@ -2113,7 +2113,7 @@ mod tests {
assert_eq!((x, y), (0, 0));
// Place cursor in the middle of the second wrapped line ("efgh"), at 'g'
t.set_cursor(6);
t.set_cursor(/*pos*/ 6);
// Move up should go to same column on previous wrapped line -> index 2 ('c')
t.move_cursor_up();
assert_eq!(t.cursor(), 2);
@@ -2131,13 +2131,13 @@ mod tests {
fn cursor_pos_with_state_after_movements() {
let mut t = ta_with("abcdefghij");
// Wrap width 4 -> visual lines: abcd | efgh | ij
let _ = t.desired_height(4);
let _ = t.desired_height(/*width*/ 4);
let area = Rect::new(0, 0, 4, 2);
let mut state = TextAreaState::default();
let mut buf = Buffer::empty(area);
// Start at beginning
t.set_cursor(0);
t.set_cursor(/*pos*/ 0);
ratatui::widgets::StatefulWidgetRef::render_ref(&(&t), area, &mut buf, &mut state);
let (x, y) = t.cursor_pos_with_state(area, state).unwrap();
assert_eq!((x, y), (0, 0));
@@ -2161,7 +2161,7 @@ mod tests {
assert_eq!((x, y), (0, 0));
// Column preservation across moves: set to col 2 on first line, move down
t.set_cursor(2);
t.set_cursor(/*pos*/ 2);
ratatui::widgets::StatefulWidgetRef::render_ref(&(&t), area, &mut buf, &mut state);
let (x0, y0) = t.cursor_pos_with_state(area, state).unwrap();
assert_eq!((x0, y0), (2, 0));
@@ -2176,7 +2176,7 @@ mod tests {
// Include spaces and an explicit newline to exercise boundaries
let mut t = ta_with("word1 word2\nword3");
// Width 6 will wrap "word1 " and then "word2" before the newline
let _ = t.desired_height(6);
let _ = t.desired_height(/*width*/ 6);
// Put cursor on the second wrapped line before the newline, at column 1 of "word2"
let start_word2 = t.text().find("word2").unwrap();
@@ -2200,7 +2200,7 @@ mod tests {
fn wrapped_navigation_with_wide_graphemes() {
// Four thumbs up, each of display width 2, with width 3 to force wrapping inside grapheme boundaries
let mut t = ta_with("👍👍👍👍");
let _ = t.desired_height(3);
let _ = t.desired_height(/*width*/ 3);
// Put cursor after the second emoji (which should be on first wrapped line)
t.set_cursor("👍👍".len());
@@ -2320,8 +2320,8 @@ mod tests {
8 => ta.move_cursor_right(),
9 => ta.move_cursor_up(),
10 => ta.move_cursor_down(),
11 => ta.move_cursor_to_beginning_of_line(true),
12 => ta.move_cursor_to_end_of_line(true),
11 => ta.move_cursor_to_beginning_of_line(/*move_up_at_bol*/ true),
12 => ta.move_cursor_to_end_of_line(/*move_down_at_eol*/ true),
13 => {
// Insert an element with a unique sentinel payload
let payload =
@@ -281,7 +281,10 @@ mod tests {
"thread".to_string(),
];
let view = TerminalTitleSetupView::new(Some(&selected), tx);
assert_snapshot!("terminal_title_setup_basic", render_lines(&view, 84));
assert_snapshot!(
"terminal_title_setup_basic",
render_lines(&view, /*width*/ 84)
);
}
#[test]
@@ -90,7 +90,7 @@ mod tests {
#[test]
fn desired_height_empty() {
let footer = UnifiedExecFooter::new();
assert_eq!(footer.desired_height(40), 0);
assert_eq!(footer.desired_height(/*width*/ 40), 0);
}
#[test]
File diff suppressed because it is too large Load Diff
@@ -202,13 +202,16 @@ mod tests {
#[test]
fn osc52_sequence_encodes_text_for_terminal_clipboard() {
assert_eq!(osc52_sequence("hello", false), "\u{1b}]52;c;aGVsbG8=\u{7}");
assert_eq!(
osc52_sequence("hello", /*tmux*/ false),
"\u{1b}]52;c;aGVsbG8=\u{7}"
);
}
#[test]
fn osc52_sequence_wraps_tmux_passthrough() {
assert_eq!(
osc52_sequence("hello", true),
osc52_sequence("hello", /*tmux*/ true),
"\u{1b}Ptmux;\u{1b}\u{1b}]52;c;aGVsbG8=\u{7}\u{1b}\\"
);
}
+4 -2
View File
@@ -268,7 +268,8 @@ mod tests {
#[test]
fn cwd_prompt_snapshot() {
let screen = new_prompt();
let mut terminal = Terminal::new(VT100Backend::new(80, 14)).expect("terminal");
let mut terminal =
Terminal::new(VT100Backend::new(/*width*/ 80, /*height*/ 14)).expect("terminal");
terminal
.draw(|frame| frame.render_widget_ref(&screen, frame.area()))
.expect("render cwd prompt");
@@ -283,7 +284,8 @@ mod tests {
"/Users/example/current".to_string(),
"/Users/example/session".to_string(),
);
let mut terminal = Terminal::new(VT100Backend::new(80, 14)).expect("terminal");
let mut terminal =
Terminal::new(VT100Backend::new(/*width*/ 80, /*height*/ 14)).expect("terminal");
terminal
.draw(|frame| frame.render_widget_ref(&screen, frame.area()))
.expect("render cwd prompt");
+10 -2
View File
@@ -752,7 +752,11 @@ approval_policy = "never"
#[test]
fn session_all_proxy_url_uses_socks_when_enabled() {
assert_eq!(
session_all_proxy_url("127.0.0.1:3128", "127.0.0.1:8081", true),
session_all_proxy_url(
"127.0.0.1:3128",
"127.0.0.1:8081",
/*socks_enabled*/ true
),
"socks5h://127.0.0.1:8081".to_string()
);
}
@@ -760,7 +764,11 @@ approval_policy = "never"
#[test]
fn session_all_proxy_url_uses_http_when_socks_disabled() {
assert_eq!(
session_all_proxy_url("127.0.0.1:3128", "127.0.0.1:8081", false),
session_all_proxy_url(
"127.0.0.1:3128",
"127.0.0.1:8081",
/*socks_enabled*/ false
),
"http://127.0.0.1:3128".to_string()
);
}
+121 -67
View File
@@ -1356,7 +1356,7 @@ mod tests {
assert_eq!(del_sign.bg, None);
}
fn diff_summary_for_tests(changes: &HashMap<PathBuf, FileChange>) -> Vec<RtLine<'static>> {
create_diff_summary(changes, &PathBuf::from("/"), 80)
create_diff_summary(changes, &PathBuf::from("/"), /*wrap_cols*/ 80)
}
fn snapshot_lines(name: &str, lines: Vec<RtLine<'static>>, width: u16, height: u16) {
@@ -1493,16 +1493,21 @@ mod tests {
// Call the wrapping function directly so we can precisely control the width
let lines = push_wrapped_diff_line_with_style_context(
1,
/*line_number*/ 1,
DiffLineType::Insert,
long_line,
80,
line_number_width(1),
/*width*/ 80,
line_number_width(/*max_line_number*/ 1),
current_diff_render_style_context(),
);
// Render into a small terminal to capture the visual layout
snapshot_lines("wrap_behavior_insert", lines, 90, 8);
snapshot_lines(
"wrap_behavior_insert",
lines,
/*width*/ 90,
/*height*/ 8,
);
}
#[test]
@@ -1522,7 +1527,12 @@ mod tests {
let lines = diff_summary_for_tests(&changes);
snapshot_lines("apply_update_block", lines, 80, 12);
snapshot_lines(
"apply_update_block",
lines,
/*width*/ 80,
/*height*/ 12,
);
}
#[test]
@@ -1542,7 +1552,12 @@ mod tests {
let lines = diff_summary_for_tests(&changes);
snapshot_lines("apply_update_with_rename_block", lines, 80, 12);
snapshot_lines(
"apply_update_with_rename_block",
lines,
/*width*/ 80,
/*height*/ 12,
);
}
#[test]
@@ -1570,7 +1585,12 @@ mod tests {
let lines = diff_summary_for_tests(&changes);
snapshot_lines("apply_multiple_files_block", lines, 80, 14);
snapshot_lines(
"apply_multiple_files_block",
lines,
/*width*/ 80,
/*height*/ 14,
);
}
#[test]
@@ -1585,7 +1605,12 @@ mod tests {
let lines = diff_summary_for_tests(&changes);
snapshot_lines("apply_add_block", lines, 80, 10);
snapshot_lines(
"apply_add_block",
lines,
/*width*/ 80,
/*height*/ 10,
);
}
#[test]
@@ -1599,7 +1624,12 @@ mod tests {
);
let lines = diff_summary_for_tests(&changes);
snapshot_lines("apply_delete_block", lines, 80, 12);
snapshot_lines(
"apply_delete_block",
lines,
/*width*/ 80,
/*height*/ 12,
);
}
#[test]
@@ -1618,10 +1648,15 @@ mod tests {
},
);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), 72);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), /*wrap_cols*/ 72);
// Render with backend width wider than wrap width to avoid Paragraph auto-wrap.
snapshot_lines("apply_update_block_wraps_long_lines", lines, 80, 12);
snapshot_lines(
"apply_update_block_wraps_long_lines",
lines,
/*width*/ 80,
/*height*/ 12,
);
}
#[test]
@@ -1641,7 +1676,7 @@ mod tests {
},
);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), 28);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), /*wrap_cols*/ 28);
snapshot_lines_text("apply_update_block_wraps_long_lines_text", &lines);
}
@@ -1668,7 +1703,7 @@ mod tests {
},
);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), 80);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), /*wrap_cols*/ 80);
snapshot_lines_text("apply_update_block_line_numbers_three_digits_text", &lines);
}
@@ -1691,9 +1726,14 @@ mod tests {
},
);
let lines = create_diff_summary(&changes, &cwd, 80);
let lines = create_diff_summary(&changes, &cwd, /*wrap_cols*/ 80);
snapshot_lines("apply_update_block_relativizes_path", lines, 80, 10);
snapshot_lines(
"apply_update_block_relativizes_path",
lines,
/*width*/ 80,
/*height*/ 10,
);
}
#[test]
@@ -1707,11 +1747,11 @@ mod tests {
let spans = &syntax_spans[0];
let lines = push_wrapped_diff_line_with_syntax_and_style_context(
1,
/*line_number*/ 1,
DiffLineType::Insert,
long_rust,
80,
line_number_width(1),
/*width*/ 80,
line_number_width(/*max_line_number*/ 1),
spans,
current_diff_render_style_context(),
);
@@ -1722,7 +1762,12 @@ mod tests {
lines.len()
);
snapshot_lines("syntax_highlighted_insert_wraps", lines, 90, 10);
snapshot_lines(
"syntax_highlighted_insert_wraps",
lines,
/*width*/ 90,
/*height*/ 10,
);
}
#[test]
@@ -1734,11 +1779,11 @@ mod tests {
let spans = &syntax_spans[0];
let lines = push_wrapped_diff_line_with_syntax_and_style_context(
1,
/*line_number*/ 1,
DiffLineType::Insert,
long_rust,
80,
line_number_width(1),
/*width*/ 80,
line_number_width(/*max_line_number*/ 1),
spans,
current_diff_render_style_context(),
);
@@ -1748,45 +1793,54 @@ mod tests {
#[test]
fn ui_snapshot_diff_gallery_80x24() {
snapshot_diff_gallery("diff_gallery_80x24", 80, 24);
snapshot_diff_gallery("diff_gallery_80x24", /*width*/ 80, /*height*/ 24);
}
#[test]
fn ui_snapshot_diff_gallery_94x35() {
snapshot_diff_gallery("diff_gallery_94x35", 94, 35);
snapshot_diff_gallery("diff_gallery_94x35", /*width*/ 94, /*height*/ 35);
}
#[test]
fn ui_snapshot_diff_gallery_120x40() {
snapshot_diff_gallery("diff_gallery_120x40", 120, 40);
snapshot_diff_gallery(
"diff_gallery_120x40",
/*width*/ 120,
/*height*/ 40,
);
}
#[test]
fn ui_snapshot_ansi16_insert_delete_no_background() {
let mut lines = push_wrapped_diff_line_inner_with_theme_and_color_level(
1,
/*line_number*/ 1,
DiffLineType::Insert,
"added in ansi16 mode",
80,
line_number_width(2),
None,
/*width*/ 80,
line_number_width(/*max_line_number*/ 2),
/*syntax_spans*/ None,
DiffTheme::Dark,
DiffColorLevel::Ansi16,
fallback_diff_backgrounds(DiffTheme::Dark, DiffColorLevel::Ansi16),
);
lines.extend(push_wrapped_diff_line_inner_with_theme_and_color_level(
2,
/*line_number*/ 2,
DiffLineType::Delete,
"deleted in ansi16 mode",
80,
line_number_width(2),
None,
/*width*/ 80,
line_number_width(/*max_line_number*/ 2),
/*syntax_spans*/ None,
DiffTheme::Dark,
DiffColorLevel::Ansi16,
fallback_diff_backgrounds(DiffTheme::Dark, DiffColorLevel::Ansi16),
));
snapshot_lines("ansi16_insert_delete_no_background", lines, 40, 4);
snapshot_lines(
"ansi16_insert_delete_no_background",
lines,
/*width*/ 40,
/*height*/ 4,
);
}
#[test]
@@ -1884,7 +1938,7 @@ mod tests {
);
assert_eq!(
style_line_bg_for(DiffLineType::Insert, backgrounds),
Style::default().bg(indexed_color(22))
Style::default().bg(indexed_color(/*index*/ 22))
);
assert_eq!(
style_line_bg_for(DiffLineType::Delete, backgrounds),
@@ -2001,12 +2055,12 @@ mod tests {
#[test]
fn light_theme_wrapped_lines_keep_number_gutter_contrast() {
let lines = push_wrapped_diff_line_inner_with_theme_and_color_level(
12,
/*line_number*/ 12,
DiffLineType::Insert,
"abcdefghij",
8,
line_number_width(12),
None,
/*width*/ 8,
line_number_width(/*max_line_number*/ 12),
/*syntax_spans*/ None,
DiffTheme::Light,
DiffColorLevel::TrueColor,
fallback_diff_backgrounds(DiffTheme::Light, DiffColorLevel::TrueColor),
@@ -2038,8 +2092,8 @@ mod tests {
diff_color_level_for_terminal(
StdoutColorLevel::Ansi16,
TerminalName::WindowsTerminal,
false,
false,
/*has_wt_session*/ false,
/*has_force_color_override*/ false,
),
DiffColorLevel::TrueColor
);
@@ -2051,8 +2105,8 @@ mod tests {
diff_color_level_for_terminal(
StdoutColorLevel::Ansi16,
TerminalName::Unknown,
true,
false,
/*has_wt_session*/ true,
/*has_force_color_override*/ false,
),
DiffColorLevel::TrueColor
);
@@ -2064,8 +2118,8 @@ mod tests {
diff_color_level_for_terminal(
StdoutColorLevel::Ansi16,
TerminalName::WezTerm,
false,
false,
/*has_wt_session*/ false,
/*has_force_color_override*/ false,
),
DiffColorLevel::Ansi16
);
@@ -2077,8 +2131,8 @@ mod tests {
diff_color_level_for_terminal(
StdoutColorLevel::Unknown,
TerminalName::WindowsTerminal,
true,
false,
/*has_wt_session*/ true,
/*has_force_color_override*/ false,
),
DiffColorLevel::TrueColor
);
@@ -2090,8 +2144,8 @@ mod tests {
diff_color_level_for_terminal(
StdoutColorLevel::Unknown,
TerminalName::WindowsTerminal,
false,
false,
/*has_wt_session*/ false,
/*has_force_color_override*/ false,
),
DiffColorLevel::Ansi16
);
@@ -2103,8 +2157,8 @@ mod tests {
diff_color_level_for_terminal(
StdoutColorLevel::Ansi16,
TerminalName::WindowsTerminal,
false,
true,
/*has_wt_session*/ false,
/*has_force_color_override*/ true,
),
DiffColorLevel::Ansi16
);
@@ -2116,8 +2170,8 @@ mod tests {
diff_color_level_for_terminal(
StdoutColorLevel::Ansi256,
TerminalName::WindowsTerminal,
true,
true,
/*has_wt_session*/ true,
/*has_force_color_override*/ true,
),
DiffColorLevel::Ansi256
);
@@ -2133,7 +2187,7 @@ mod tests {
},
);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), 80);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), /*wrap_cols*/ 80);
let has_rgb = lines.iter().any(|line| {
line.spans
.iter()
@@ -2155,7 +2209,7 @@ mod tests {
},
);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), 80);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), /*wrap_cols*/ 80);
let has_rgb = lines.iter().any(|line| {
line.spans
.iter()
@@ -2183,7 +2237,7 @@ mod tests {
fn wrap_styled_spans_single_line() {
// Content that fits in one line should produce exactly one chunk.
let spans = vec![RtSpan::raw("short")];
let result = wrap_styled_spans(&spans, 80);
let result = wrap_styled_spans(&spans, /*max_cols*/ 80);
assert_eq!(result.len(), 1);
}
@@ -2192,7 +2246,7 @@ mod tests {
// Content wider than max_cols should produce multiple chunks.
let long_text = "a".repeat(100);
let spans = vec![RtSpan::raw(long_text)];
let result = wrap_styled_spans(&spans, 40);
let result = wrap_styled_spans(&spans, /*max_cols*/ 40);
assert!(
result.len() >= 3,
"100 chars at 40 cols should produce at least 3 lines, got {}",
@@ -2211,7 +2265,7 @@ mod tests {
RtSpan::styled("aaaa", style_a), // 4 cols, fills line exactly at max_cols=4
RtSpan::styled("bb", style_b), // should start on a new line
];
let result = wrap_styled_spans(&spans, 4);
let result = wrap_styled_spans(&spans, /*max_cols*/ 4);
assert_eq!(
result.len(),
2,
@@ -2231,7 +2285,7 @@ mod tests {
let style = Style::default().fg(Color::Green);
let text = "x".repeat(50);
let spans = vec![RtSpan::styled(text, style)];
let result = wrap_styled_spans(&spans, 20);
let result = wrap_styled_spans(&spans, /*max_cols*/ 20);
for chunk in &result {
for span in chunk {
assert_eq!(span.style, style, "style should be preserved across wraps");
@@ -2244,7 +2298,7 @@ mod tests {
// A tab should count as TAB_WIDTH columns, not zero.
// With max_cols=8, a tab (4 cols) + "abcde" (5 cols) = 9 cols → must wrap.
let spans = vec![RtSpan::raw("\tabcde")];
let result = wrap_styled_spans(&spans, 8);
let result = wrap_styled_spans(&spans, /*max_cols*/ 8);
assert!(
result.len() >= 2,
"tab + 5 chars should exceed 8 cols and wrap, got {} line(s): {result:?}",
@@ -2255,7 +2309,7 @@ mod tests {
#[test]
fn wrap_styled_spans_wraps_before_first_overflowing_char() {
let spans = vec![RtSpan::raw("abcd\t")];
let result = wrap_styled_spans(&spans, 5);
let result = wrap_styled_spans(&spans, /*max_cols*/ 5);
let line_text: Vec<String> = result
.iter()
@@ -2285,11 +2339,11 @@ mod tests {
fn fallback_wrapping_uses_display_width_for_tabs_and_wide_chars() {
let width = 8;
let lines = push_wrapped_diff_line_with_style_context(
1,
/*line_number*/ 1,
DiffLineType::Insert,
"abcd\t界🙂",
width,
line_number_width(1),
line_number_width(/*max_line_number*/ 1),
current_diff_render_style_context(),
);
@@ -2330,7 +2384,7 @@ mod tests {
// Should complete quickly (no per-line parser init). If guardrails
// are bypassed this would be extremely slow.
let lines = create_diff_summary(&changes, &PathBuf::from("/"), 80);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), /*wrap_cols*/ 80);
// The diff rendered without timing out — the guardrails prevented
// thousands of per-line parser initializations. Verify we actually
@@ -2374,7 +2428,7 @@ mod tests {
},
);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), 80);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), /*wrap_cols*/ 80);
let has_rgb = lines.iter().any(|line| {
line.spans
.iter()
@@ -2413,7 +2467,7 @@ mod tests {
.map(|span| span.style)
.expect("expected highlighted span for second multiline string line");
let lines = create_diff_summary(&changes, &PathBuf::from("/"), 120);
let lines = create_diff_summary(&changes, &PathBuf::from("/"), /*wrap_cols*/ 120);
let actual_style = lines
.iter()
.flat_map(|line| line.spans.iter())
+19 -11
View File
@@ -758,7 +758,7 @@ mod tests {
interaction_input: None,
};
let cell = ExecCell::new(call, false);
let cell = ExecCell::new(call, /*animations_enabled*/ false);
// Use a narrow width so each logical line wraps into many on-screen lines.
let lines = cell.command_display_lines(width);
@@ -796,8 +796,13 @@ mod tests {
Line::from(" tail"),
];
let truncated =
ExecCell::truncate_lines_middle(&lines, 2, 12, Some(4), Some(Line::from(" ".dim())));
let truncated = ExecCell::truncate_lines_middle(
&lines,
/*max_rows*/ 2,
/*width*/ 12,
Some(4),
Some(Line::from(" ".dim())),
);
let rendered: Vec<String> = truncated
.iter()
.map(|line| {
@@ -820,7 +825,10 @@ mod tests {
lines.extend(std::iter::repeat_n(Line::from(" "), 26));
lines.push(Line::from(" end"));
let truncated = ExecCell::truncate_lines_middle(&lines, 28, 80, None, None);
let truncated = ExecCell::truncate_lines_middle(
&lines, /*max_rows*/ 28, /*width*/ 80, /*omitted_hint*/ None,
/*ellipsis_prefix*/ None,
);
assert_eq!(truncated, lines);
}
@@ -840,9 +848,9 @@ mod tests {
interaction_input: None,
};
let cell = ExecCell::new(call, false);
let cell = ExecCell::new(call, /*animations_enabled*/ false);
let rendered: Vec<String> = cell
.command_display_lines(36)
.command_display_lines(/*width*/ 36)
.iter()
.map(|line| {
line.spans
@@ -877,9 +885,9 @@ mod tests {
interaction_input: None,
};
let cell = ExecCell::new(call, false);
let cell = ExecCell::new(call, /*animations_enabled*/ false);
let rendered: Vec<String> = cell
.display_lines(36)
.display_lines(/*width*/ 36)
.iter()
.map(|line| {
line.spans
@@ -918,9 +926,9 @@ mod tests {
interaction_input: None,
};
let cell = ExecCell::new(call, false);
let cell = ExecCell::new(call, /*animations_enabled*/ false);
let rendered: Vec<String> = cell
.command_display_lines(36)
.command_display_lines(/*width*/ 36)
.iter()
.map(|line| {
line.spans
@@ -955,7 +963,7 @@ mod tests {
interaction_input: None,
};
let cell = ExecCell::new(call, false);
let cell = ExecCell::new(call, /*animations_enabled*/ false);
let width: u16 = 36;
let logical_height = cell.transcript_lines(width).len() as u16;
let wrapped_height = cell.desired_transcript_height(width);
+127 -90
View File
@@ -2948,7 +2948,7 @@ mod tests {
);
assert_eq!(
render_lines(&cell.display_lines(80)),
render_lines(&cell.display_lines(/*width*/ 80)),
vec![
"• Generated Image:".to_string(),
" └ A tiny blue square".to_string(),
@@ -2995,7 +2995,7 @@ mod tests {
#[test]
fn unified_exec_interaction_cell_renders_wait() {
let cell = new_unified_exec_interaction(None, String::new());
let cell = new_unified_exec_interaction(/*command_display*/ None, String::new());
let lines = render_transcript(&cell);
assert_eq!(lines, vec!["• Waited for background terminal"]);
}
@@ -3033,7 +3033,7 @@ mod tests {
turn_ttfm_ms: 0,
};
let cell = FinalMessageSeparator::new(Some(12), Some(summary));
let rendered = render_lines(&cell.display_lines(600));
let rendered = render_lines(&cell.display_lines(/*width*/ 600));
assert_eq!(rendered.len(), 1);
assert!(!rendered[0].contains("Worked for"));
@@ -3050,8 +3050,8 @@ mod tests {
#[test]
fn final_message_separator_includes_worked_label_after_one_minute() {
let cell = FinalMessageSeparator::new(Some(61), None);
let rendered = render_lines(&cell.display_lines(200));
let cell = FinalMessageSeparator::new(Some(61), /*runtime_metrics*/ None);
let rendered = render_lines(&cell.display_lines(/*width*/ 200));
assert_eq!(rendered.len(), 1);
assert!(rendered[0].contains("Worked for"));
@@ -3060,7 +3060,7 @@ mod tests {
#[test]
fn ps_output_empty_snapshot() {
let cell = new_unified_exec_processes_output(Vec::new());
let rendered = render_lines(&cell.display_lines(60)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 60)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3071,10 +3071,10 @@ mod tests {
&config,
"gpt-5",
session_configured_event("gpt-5"),
false,
/*is_first_event*/ false,
Some("Model just became available".to_string()),
Some(PlanType::Free),
false,
/*show_fast_status*/ false,
);
let rendered = render_transcript(&cell).join("\n");
@@ -3093,10 +3093,10 @@ mod tests {
&config,
"gpt-5",
session_configured_event("gpt-5"),
false,
/*is_first_event*/ false,
Some("Model just became available".to_string()),
Some(PlanType::Free),
false,
/*show_fast_status*/ false,
);
let rendered = render_transcript(&cell).join("\n");
@@ -3110,10 +3110,10 @@ mod tests {
&config,
"gpt-5",
session_configured_event("gpt-5"),
true,
/*is_first_event*/ true,
Some("Model just became available".to_string()),
Some(PlanType::Free),
false,
/*show_fast_status*/ false,
);
let rendered = render_transcript(&cell).join("\n");
@@ -3129,10 +3129,10 @@ mod tests {
&config,
"gpt-5",
session_configured_event("gpt-5"),
false,
/*is_first_event*/ false,
Some("Model just became available".to_string()),
Some(PlanType::Free),
false,
/*show_fast_status*/ false,
);
let rendered = render_transcript(&cell).join("\n");
@@ -3151,7 +3151,7 @@ mod tests {
recent_chunks: vec!["src/main.rs:12:foo".to_string()],
},
]);
let rendered = render_lines(&cell.display_lines(40)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 40)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3163,7 +3163,7 @@ mod tests {
),
recent_chunks: vec!["searching...".to_string()],
}]);
let rendered = render_lines(&cell.display_lines(36)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 36)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3177,7 +3177,7 @@ mod tests {
})
.collect(),
);
let rendered = render_lines(&cell.display_lines(32)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 32)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3190,7 +3190,7 @@ mod tests {
" more indented".to_string(),
],
}]);
let rendered = render_lines(&cell.display_lines(60)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 60)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3200,7 +3200,7 @@ mod tests {
"Message exceeds the maximum length of 1048576 characters (1048577 provided)."
.to_string(),
);
let rendered = render_lines(&cell.display_lines(120)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 120)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3265,7 +3265,7 @@ mod tests {
HashMap::new(),
&auth_statuses,
);
let rendered = render_lines(&cell.display_lines(120)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 120)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3276,7 +3276,7 @@ mod tests {
let mut servers = config.mcp_servers.get().clone();
servers.insert(
"some-server".to_string(),
stdio_server_config("docs-server", vec!["--stdio"], None, vec![]),
stdio_server_config("docs-server", vec!["--stdio"], /*env*/ None, vec![]),
);
config
.mcp_servers
@@ -3305,7 +3305,7 @@ mod tests {
HashMap::new(),
&auth_statuses,
);
let rendered = render_lines(&cell.display_lines(120)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 120)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3313,7 +3313,8 @@ mod tests {
#[tokio::test]
async fn mcp_tools_output_from_statuses_renders_status_only_servers() {
let mut config = test_config().await;
let mut plugin_docs = stdio_server_config("docs-server", vec!["--stdio"], None, vec![]);
let mut plugin_docs =
stdio_server_config("docs-server", vec!["--stdio"], /*env*/ None, vec![]);
plugin_docs.enabled = false;
plugin_docs.disabled_reason = Some(McpServerDisabledReason::Unknown);
let servers = HashMap::from([("plugin_docs".to_string(), plugin_docs)]);
@@ -3343,16 +3344,16 @@ mod tests {
}];
let cell = new_mcp_tools_output_from_statuses(&config, &statuses);
let rendered = render_lines(&cell.display_lines(120)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 120)).join("\n");
insta::assert_snapshot!(rendered);
}
#[test]
fn empty_agent_message_cell_transcript() {
let cell = AgentMessageCell::new(vec![Line::default()], false);
assert_eq!(cell.transcript_lines(80), vec![Line::from(" ")]);
assert_eq!(cell.desired_transcript_height(80), 1);
let cell = AgentMessageCell::new(vec![Line::default()], /*is_first_line*/ false);
assert_eq!(cell.transcript_lines(/*width*/ 80), vec![Line::from(" ")]);
assert_eq!(cell.desired_transcript_height(/*width*/ 80), 1);
}
#[test]
@@ -3365,7 +3366,7 @@ mod tests {
" this time".bold(),
]);
let cell = PrefixedWrappedHistoryCell::new(summary, "".green(), " ");
let rendered = render_lines(&cell.display_lines(24));
let rendered = render_lines(&cell.display_lines(/*width*/ 24));
assert_eq!(
rendered,
vec![
@@ -3383,7 +3384,7 @@ mod tests {
let url_like =
"example.test/api/v1/projects/alpha-team/releases/2026-02-17/builds/1234567890";
let cell = PrefixedWrappedHistoryCell::new(Line::from(url_like), "".green(), " ");
let rendered = render_lines(&cell.display_lines(24));
let rendered = render_lines(&cell.display_lines(/*width*/ 24));
assert_eq!(
rendered
@@ -3400,7 +3401,7 @@ mod tests {
let url_like =
"example.test/api/v1/projects/alpha-team/releases/2026-02-17/builds/1234567890";
let cell = UnifiedExecInteractionCell::new(Some("true".to_string()), url_like.to_string());
let rendered = render_lines(&cell.display_lines(24));
let rendered = render_lines(&cell.display_lines(/*width*/ 24));
assert_eq!(
rendered
@@ -3497,7 +3498,7 @@ mod tests {
queries: None,
},
);
let rendered = render_lines(&cell.display_lines(64)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 64)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3514,7 +3515,7 @@ mod tests {
queries: None,
},
);
let rendered = render_lines(&cell.display_lines(64));
let rendered = render_lines(&cell.display_lines(/*width*/ 64));
assert_eq!(
rendered,
@@ -3536,7 +3537,7 @@ mod tests {
queries: None,
},
);
let rendered = render_lines(&cell.display_lines(64));
let rendered = render_lines(&cell.display_lines(/*width*/ 64));
assert_eq!(rendered, vec!["• Searched short query".to_string()]);
}
@@ -3553,7 +3554,7 @@ mod tests {
queries: None,
},
);
let rendered = render_lines(&cell.transcript_lines(64)).join("\n");
let rendered = render_lines(&cell.transcript_lines(/*width*/ 64)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3569,8 +3570,12 @@ mod tests {
})),
};
let cell = new_active_mcp_tool_call("call-1".into(), invocation, true);
let rendered = render_lines(&cell.display_lines(80)).join("\n");
let cell = new_active_mcp_tool_call(
"call-1".into(),
invocation,
/*animations_enabled*/ true,
);
let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3578,7 +3583,7 @@ mod tests {
#[test]
fn mcp_inventory_loading_snapshot() {
let cell = new_mcp_inventory_loading(/*animations_enabled*/ true);
let rendered = render_lines(&cell.display_lines(80)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3601,13 +3606,17 @@ mod tests {
meta: None,
};
let mut cell = new_active_mcp_tool_call("call-2".into(), invocation, true);
let mut cell = new_active_mcp_tool_call(
"call-2".into(),
invocation,
/*animations_enabled*/ true,
);
assert!(
cell.complete(Duration::from_millis(1420), Ok(result))
.is_none()
);
let rendered = render_lines(&cell.display_lines(80)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3632,12 +3641,16 @@ mod tests {
meta: None,
};
let mut cell = new_active_mcp_tool_call("call-image".into(), invocation, true);
let mut cell = new_active_mcp_tool_call(
"call-image".into(),
invocation,
/*animations_enabled*/ true,
);
let extra_cell = cell
.complete(Duration::from_millis(25), Ok(result))
.expect("expected image cell");
let rendered = render_lines(&extra_cell.display_lines(80));
let rendered = render_lines(&extra_cell.display_lines(/*width*/ 80));
assert_eq!(rendered, vec!["tool result (image output)"]);
}
@@ -3659,12 +3672,16 @@ mod tests {
meta: None,
};
let mut cell = new_active_mcp_tool_call("call-image-data-url".into(), invocation, true);
let mut cell = new_active_mcp_tool_call(
"call-image-data-url".into(),
invocation,
/*animations_enabled*/ true,
);
let extra_cell = cell
.complete(Duration::from_millis(25), Ok(result))
.expect("expected image cell");
let rendered = render_lines(&extra_cell.display_lines(80));
let rendered = render_lines(&extra_cell.display_lines(/*width*/ 80));
assert_eq!(rendered, vec!["tool result (image output)"]);
}
@@ -3685,12 +3702,16 @@ mod tests {
meta: None,
};
let mut cell = new_active_mcp_tool_call("call-image-2".into(), invocation, true);
let mut cell = new_active_mcp_tool_call(
"call-image-2".into(),
invocation,
/*animations_enabled*/ true,
);
let extra_cell = cell
.complete(Duration::from_millis(25), Ok(result))
.expect("expected image cell");
let rendered = render_lines(&extra_cell.display_lines(80));
let rendered = render_lines(&extra_cell.display_lines(/*width*/ 80));
assert_eq!(rendered, vec!["tool result (image output)"]);
}
@@ -3705,13 +3726,17 @@ mod tests {
})),
};
let mut cell = new_active_mcp_tool_call("call-3".into(), invocation, true);
let mut cell = new_active_mcp_tool_call(
"call-3".into(),
invocation,
/*animations_enabled*/ true,
);
assert!(
cell.complete(Duration::from_secs(2), Err("network timeout".into()))
.is_none()
);
let rendered = render_lines(&cell.display_lines(80)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3744,13 +3769,17 @@ mod tests {
meta: None,
};
let mut cell = new_active_mcp_tool_call("call-4".into(), invocation, true);
let mut cell = new_active_mcp_tool_call(
"call-4".into(),
invocation,
/*animations_enabled*/ true,
);
assert!(
cell.complete(Duration::from_millis(640), Ok(result))
.is_none()
);
let rendered = render_lines(&cell.display_lines(48)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 48)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3775,13 +3804,17 @@ mod tests {
meta: None,
};
let mut cell = new_active_mcp_tool_call("call-5".into(), invocation, true);
let mut cell = new_active_mcp_tool_call(
"call-5".into(),
invocation,
/*animations_enabled*/ true,
);
assert!(
cell.complete(Duration::from_millis(1280), Ok(result))
.is_none()
);
let rendered = render_lines(&cell.display_lines(40)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 40)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3807,13 +3840,17 @@ mod tests {
meta: None,
};
let mut cell = new_active_mcp_tool_call("call-6".into(), invocation, true);
let mut cell = new_active_mcp_tool_call(
"call-6".into(),
invocation,
/*animations_enabled*/ true,
);
assert!(
cell.complete(Duration::from_millis(320), Ok(result))
.is_none()
);
let rendered = render_lines(&cell.display_lines(120)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 120)).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3823,12 +3860,12 @@ mod tests {
let cell = SessionHeaderHistoryCell::new(
"gpt-4o".to_string(),
Some(ReasoningEffortConfig::High),
true,
/*show_fast_status*/ true,
std::env::temp_dir(),
"test",
);
let lines = render_lines(&cell.display_lines(80));
let lines = render_lines(&cell.display_lines(/*width*/ 80));
let model_line = lines
.iter()
.find(|line| line.contains("model:"))
@@ -3843,12 +3880,12 @@ mod tests {
let cell = SessionHeaderHistoryCell::new(
"gpt-4o".to_string(),
Some(ReasoningEffortConfig::High),
false,
/*show_fast_status*/ false,
std::env::temp_dir(),
"test",
);
let lines = render_lines(&cell.display_lines(80));
let lines = render_lines(&cell.display_lines(/*width*/ 80));
let model_line = lines
.iter()
.find(|line| line.contains("model:"))
@@ -3913,12 +3950,12 @@ mod tests {
duration: None,
interaction_input: None,
},
true,
/*animations_enabled*/ true,
);
// Mark call complete so markers are ✓
cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));
let lines = cell.display_lines(80);
let lines = cell.display_lines(/*width*/ 80);
let rendered = render_lines(&lines).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -3940,7 +3977,7 @@ mod tests {
duration: None,
interaction_input: None,
},
true,
/*animations_enabled*/ true,
);
// Call 1: Search only
cell.complete_call("c1", CommandOutput::default(), Duration::from_millis(1));
@@ -3955,7 +3992,7 @@ mod tests {
path: "shimmer.rs".into(),
}],
ExecCommandSource::Agent,
None,
/*interaction_input*/ None,
)
.unwrap();
cell.complete_call("c2", CommandOutput::default(), Duration::from_millis(1));
@@ -3970,12 +4007,12 @@ mod tests {
path: "status_indicator_widget.rs".into(),
}],
ExecCommandSource::Agent,
None,
/*interaction_input*/ None,
)
.unwrap();
cell.complete_call("c3", CommandOutput::default(), Duration::from_millis(1));
let lines = cell.display_lines(80);
let lines = cell.display_lines(/*width*/ 80);
let rendered = render_lines(&lines).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -4009,10 +4046,10 @@ mod tests {
duration: None,
interaction_input: None,
},
true,
/*animations_enabled*/ true,
);
cell.complete_call("c1", CommandOutput::default(), Duration::from_millis(1));
let lines = cell.display_lines(80);
let lines = cell.display_lines(/*width*/ 80);
let rendered = render_lines(&lines).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -4033,7 +4070,7 @@ mod tests {
duration: None,
interaction_input: None,
},
true,
/*animations_enabled*/ true,
);
// Mark call complete so it renders as "Ran"
cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));
@@ -4059,11 +4096,11 @@ mod tests {
duration: None,
interaction_input: None,
},
true,
/*animations_enabled*/ true,
);
cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));
// Wide enough that it fits inline
let lines = cell.display_lines(80);
let lines = cell.display_lines(/*width*/ 80);
let rendered = render_lines(&lines).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -4083,10 +4120,10 @@ mod tests {
duration: None,
interaction_input: None,
},
true,
/*animations_enabled*/ true,
);
cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));
let lines = cell.display_lines(24);
let lines = cell.display_lines(/*width*/ 24);
let rendered = render_lines(&lines).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -4106,10 +4143,10 @@ mod tests {
duration: None,
interaction_input: None,
},
true,
/*animations_enabled*/ true,
);
cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));
let lines = cell.display_lines(80);
let lines = cell.display_lines(/*width*/ 80);
let rendered = render_lines(&lines).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -4130,10 +4167,10 @@ mod tests {
duration: None,
interaction_input: None,
},
true,
/*animations_enabled*/ true,
);
cell.complete_call(&call_id, CommandOutput::default(), Duration::from_millis(1));
let lines = cell.display_lines(28);
let lines = cell.display_lines(/*width*/ 28);
let rendered = render_lines(&lines).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -4154,7 +4191,7 @@ mod tests {
duration: None,
interaction_input: None,
},
true,
/*animations_enabled*/ true,
);
let stderr: String = (1..=10)
.map(|n| n.to_string())
@@ -4171,7 +4208,7 @@ mod tests {
);
let rendered = cell
.display_lines(80)
.display_lines(/*width*/ 80)
.iter()
.map(|l| {
l.spans
@@ -4204,7 +4241,7 @@ mod tests {
duration: None,
interaction_input: None,
},
true,
/*animations_enabled*/ true,
);
let stderr = "error: first line on stderr\nerror: second line on stderr".to_string();
@@ -4260,7 +4297,7 @@ mod tests {
remote_image_urls: vec!["https://example.com/example.png".to_string()],
};
let rendered = render_lines(&cell.display_lines(80)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");
assert!(rendered.contains("[Image #1]"));
assert!(rendered.contains("describe these"));
@@ -4276,7 +4313,7 @@ mod tests {
remote_image_urls: vec!["data:image/png;base64,aGVsbG8=".to_string()],
};
let rendered = render_lines(&cell.display_lines(80)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");
assert!(rendered.contains("[Image #1]"));
assert!(rendered.contains("describe inline image"));
@@ -4294,7 +4331,7 @@ mod tests {
],
};
let rendered = render_lines(&cell.display_lines(80)).join("\n");
let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");
assert!(rendered.contains("[Image #1]"));
assert!(rendered.contains("[Image #2]"));
@@ -4332,7 +4369,7 @@ mod tests {
remote_image_urls: vec!["https://example.com/one.png".to_string()],
};
let rendered = render_lines(&cell.display_lines(80));
let rendered = render_lines(&cell.display_lines(/*width*/ 80));
let trailing_blank_count = rendered
.iter()
.rev()
@@ -4355,7 +4392,7 @@ mod tests {
remote_image_urls: vec!["https://example.com/one.png".to_string()],
};
let rendered = render_lines(&cell.display_lines(80));
let rendered = render_lines(&cell.display_lines(/*width*/ 80));
let trailing_blank_count = rendered
.iter()
.rev()
@@ -4440,7 +4477,7 @@ mod tests {
let cell = new_plan_update(update);
// Narrow width to force wrapping for both the note and steps
let lines = cell.display_lines(32);
let lines = cell.display_lines(/*width*/ 32);
let rendered = render_lines(&lines).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -4462,7 +4499,7 @@ mod tests {
};
let cell = new_plan_update(update);
let lines = cell.display_lines(40);
let lines = cell.display_lines(/*width*/ 40);
let rendered = render_lines(&lines).join("\n");
insta::assert_snapshot!(rendered);
}
@@ -4483,7 +4520,7 @@ mod tests {
};
let cell = new_plan_update(update);
let rendered = render_lines(&cell.display_lines(30));
let rendered = render_lines(&cell.display_lines(/*width*/ 30));
assert_eq!(
rendered
@@ -4510,7 +4547,7 @@ mod tests {
&test_cwd(),
);
let rendered_display = render_lines(&cell.display_lines(80));
let rendered_display = render_lines(&cell.display_lines(/*width*/ 80));
assert_eq!(rendered_display, vec!["• Detailed reasoning goes here."]);
let rendered_transcript = render_transcript(cell.as_ref());
@@ -4524,7 +4561,7 @@ mod tests {
"High level reasoning".to_string(),
summary.to_string(),
&test_cwd(),
false,
/*transcript_only*/ false,
));
let width: u16 = 24;
@@ -4581,7 +4618,7 @@ mod tests {
&test_cwd(),
);
let rendered_display = render_lines(&cell.display_lines(80));
let rendered_display = render_lines(&cell.display_lines(/*width*/ 80));
assert_eq!(rendered_display, vec!["• Detailed reasoning goes here."]);
}
@@ -4622,7 +4659,7 @@ mod tests {
&test_cwd(),
);
let rendered_display = render_lines(&cell.display_lines(80));
let rendered_display = render_lines(&cell.display_lines(/*width*/ 80));
assert_eq!(rendered_display, vec!["• We should fix the bug next."]);
let rendered_transcript = render_transcript(cell.as_ref());
@@ -4635,7 +4672,7 @@ mod tests {
"Feature flag `foo`".to_string(),
Some("Use flag `bar` instead.".to_string()),
);
let lines = cell.display_lines(80);
let lines = cell.display_lines(/*width*/ 80);
let rendered = render_lines(&lines);
assert_eq!(
rendered,
+7 -7
View File
@@ -1815,7 +1815,7 @@ mod tests {
let cwd = temp_dir.path().join("project");
let params = latest_session_lookup_params(
false,
/*is_remote*/ false,
&config,
Some(cwd.as_path()),
/*include_non_interactive*/ false,
@@ -1834,7 +1834,7 @@ mod tests {
let cwd = temp_dir.path().join("project");
let params = latest_session_lookup_params(
true,
/*is_remote*/ true,
&config,
Some(cwd.as_path()),
/*include_non_interactive*/ false,
@@ -1850,7 +1850,7 @@ mod tests {
let temp_dir = TempDir::new()?;
let config = build_config(&temp_dir).await?;
let cwd = read_session_cwd(&config, ThreadId::new(), None).await;
let cwd = read_session_cwd(&config, ThreadId::new(), /*path*/ None).await;
assert_eq!(cwd, None);
Ok(())
@@ -1862,7 +1862,7 @@ mod tests {
let temp_dir = TempDir::new()?;
let mut config = build_config(&temp_dir).await?;
config.active_project = ProjectConfig { trust_level: None };
config.set_windows_sandbox_enabled(false);
config.set_windows_sandbox_enabled(/*value*/ false);
let should_show = should_show_trust_screen(&config);
assert!(
@@ -1914,7 +1914,7 @@ mod tests {
.await
.map_err(std::io::Error::other)?;
state_runtime
.mark_backfill_complete(None)
.mark_backfill_complete(/*last_watermark*/ None)
.await
.map_err(std::io::Error::other)?;
@@ -1986,7 +1986,7 @@ mod tests {
let temp_dir = TempDir::new()?;
let mut config = build_config(&temp_dir).await?;
config.active_project = ProjectConfig { trust_level: None };
config.set_windows_sandbox_enabled(true);
config.set_windows_sandbox_enabled(/*value*/ true);
let should_show = should_show_trust_screen(&config);
if cfg!(target_os = "windows") {
@@ -2288,7 +2288,7 @@ trust_level = "untrusted"
.await
.map_err(std::io::Error::other)?;
runtime
.mark_backfill_complete(None)
.mark_backfill_complete(/*last_watermark*/ None)
.await
.map_err(std::io::Error::other)?;
+7 -7
View File
@@ -213,7 +213,7 @@ mod tests {
#[test]
fn rows_do_not_exceed_width_ascii() {
let mut rb = RowBuilder::new(10);
let mut rb = RowBuilder::new(/*target_width*/ 10);
rb.push_fragment("hello whirl this is a test");
let rows = rb.rows().to_vec();
assert_eq!(
@@ -234,7 +234,7 @@ mod tests {
#[test]
fn rows_do_not_exceed_width_emoji_cjk() {
// 😀 is width 2; 你/好 are width 2.
let mut rb = RowBuilder::new(6);
let mut rb = RowBuilder::new(/*target_width*/ 6);
rb.push_fragment("😀😀 你好");
let rows = rb.rows().to_vec();
// At width 6, we expect the first row to fit exactly two emojis and a space
@@ -252,11 +252,11 @@ mod tests {
#[test]
fn fragmentation_invariance_long_token() {
let s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 26 chars
let mut rb_all = RowBuilder::new(7);
let mut rb_all = RowBuilder::new(/*target_width*/ 7);
rb_all.push_fragment(s);
let all_rows = rb_all.rows().to_vec();
let mut rb_chunks = RowBuilder::new(7);
let mut rb_chunks = RowBuilder::new(/*target_width*/ 7);
for i in (0..s.len()).step_by(3) {
let end = (i + 3).min(s.len());
rb_chunks.push_fragment(&s[i..end]);
@@ -268,7 +268,7 @@ mod tests {
#[test]
fn newline_splits_rows() {
let mut rb = RowBuilder::new(10);
let mut rb = RowBuilder::new(/*target_width*/ 10);
rb.push_fragment("hello\nworld");
let rows = rb.display_rows();
assert!(rows.iter().any(|r| r.explicit_break));
@@ -279,10 +279,10 @@ mod tests {
#[test]
fn rewrap_on_width_change() {
let mut rb = RowBuilder::new(10);
let mut rb = RowBuilder::new(/*target_width*/ 10);
rb.push_fragment("abcdefghijK");
assert!(!rb.rows().is_empty());
rb.set_width(5);
rb.set_width(/*width*/ 5);
for r in rb.rows() {
assert!(r.width() <= 5);
}
@@ -132,8 +132,12 @@ mod tests {
fn rejects_missing_local_auth() {
let codex_home = TempDir::new().expect("tempdir");
let err = load_local_chatgpt_auth(codex_home.path(), AuthCredentialsStoreMode::File, None)
.expect_err("missing auth should fail");
let err = load_local_chatgpt_auth(
codex_home.path(),
AuthCredentialsStoreMode::File,
/*forced_chatgpt_workspace_id*/ None,
)
.expect_err("missing auth should fail");
assert_eq!(err, "no local auth available");
}
@@ -153,8 +157,12 @@ mod tests {
)
.expect("api key auth should save");
let err = load_local_chatgpt_auth(codex_home.path(), AuthCredentialsStoreMode::File, None)
.expect_err("api key auth should fail");
let err = load_local_chatgpt_auth(
codex_home.path(),
AuthCredentialsStoreMode::File,
/*forced_chatgpt_workspace_id*/ None,
)
.expect_err("api key auth should fail");
assert_eq!(err, "local auth is not a ChatGPT login");
}
+10 -5
View File
@@ -41,7 +41,7 @@ mod tests {
fn citations_render_as_plain_text() {
let src = "Before 【F:/x.rs†L1】\nAfter 【F:/x.rs†L3】\n";
let mut out = Vec::new();
append_markdown(src, None, None, &mut out);
append_markdown(src, /*width*/ None, /*cwd*/ None, &mut out);
let rendered = lines_to_strings(&out);
assert_eq!(
rendered,
@@ -57,7 +57,7 @@ mod tests {
// Basic sanity: indented code with surrounding blank lines should produce the indented line.
let src = "Before\n\n code 1\n\nAfter\n";
let mut out = Vec::new();
append_markdown(src, None, None, &mut out);
append_markdown(src, /*width*/ None, /*cwd*/ None, &mut out);
let lines = lines_to_strings(&out);
assert_eq!(lines, vec!["Before", "", " code 1", "", "After"]);
}
@@ -66,7 +66,7 @@ mod tests {
fn append_markdown_preserves_full_text_line() {
let src = "Hi! How can I help with codex-rs today? Want me to explore the repo, run tests, or work on a specific change?\n";
let mut out = Vec::new();
append_markdown(src, None, None, &mut out);
append_markdown(src, /*width*/ None, /*cwd*/ None, &mut out);
assert_eq!(
out.len(),
1,
@@ -87,7 +87,12 @@ mod tests {
#[test]
fn append_markdown_matches_tui_markdown_for_ordered_item() {
let mut out = Vec::new();
append_markdown("1. Tight item\n", None, None, &mut out);
append_markdown(
"1. Tight item\n",
/*width*/ None,
/*cwd*/ None,
&mut out,
);
let lines = lines_to_strings(&out);
assert_eq!(lines, vec!["1. Tight item".to_string()]);
}
@@ -96,7 +101,7 @@ mod tests {
fn append_markdown_keeps_ordered_list_line_unsplit_in_context() {
let src = "Loose vs. tight list items:\n1. Tight item\n";
let mut out = Vec::new();
append_markdown(src, None, None, &mut out);
append_markdown(src, /*width*/ None, /*cwd*/ None, &mut out);
let lines = lines_to_strings(&out);
@@ -12,7 +12,7 @@ use crate::markdown_render::render_markdown_text_with_width_and_cwd;
use insta::assert_snapshot;
fn render_markdown_text_for_cwd(input: &str, cwd: &Path) -> Text<'static> {
render_markdown_text_with_width_and_cwd(input, None, Some(cwd))
render_markdown_text_with_width_and_cwd(input, /*width*/ None, Some(cwd))
}
#[test]
+57 -24
View File
@@ -118,7 +118,7 @@ pub(crate) fn simulate_stream_markdown_for_tests(
deltas: &[&str],
finalize: bool,
) -> Vec<Line<'static>> {
let mut collector = MarkdownStreamCollector::new(None, &test_cwd());
let mut collector = MarkdownStreamCollector::new(/*width*/ None, &test_cwd());
let mut out = Vec::new();
for d in deltas {
collector.push_delta(d);
@@ -139,7 +139,7 @@ mod tests {
#[tokio::test]
async fn no_commit_until_newline() {
let mut c = super::MarkdownStreamCollector::new(None, &super::test_cwd());
let mut c = super::MarkdownStreamCollector::new(/*width*/ None, &super::test_cwd());
c.push_delta("Hello, world");
let out = c.commit_complete_lines();
assert!(out.is_empty(), "should not commit without newline");
@@ -150,7 +150,7 @@ mod tests {
#[tokio::test]
async fn finalize_commits_partial_line() {
let mut c = super::MarkdownStreamCollector::new(None, &super::test_cwd());
let mut c = super::MarkdownStreamCollector::new(/*width*/ None, &super::test_cwd());
c.push_delta("Line without newline");
let out = c.finalize_and_drain();
assert_eq!(out.len(), 1);
@@ -158,7 +158,7 @@ mod tests {
#[tokio::test]
async fn e2e_stream_blockquote_simple_is_green() {
let out = super::simulate_stream_markdown_for_tests(&["> Hello\n"], true);
let out = super::simulate_stream_markdown_for_tests(&["> Hello\n"], /*finalize*/ true);
assert_eq!(out.len(), 1);
let l = &out[0];
assert_eq!(
@@ -171,7 +171,10 @@ mod tests {
#[tokio::test]
async fn e2e_stream_blockquote_nested_is_green() {
let out = super::simulate_stream_markdown_for_tests(&["> Level 1\n>> Level 2\n"], true);
let out = super::simulate_stream_markdown_for_tests(
&["> Level 1\n>> Level 2\n"],
/*finalize*/ true,
);
// Filter out any blank lines that may be inserted at paragraph starts.
let non_blank: Vec<_> = out
.into_iter()
@@ -194,7 +197,10 @@ mod tests {
#[tokio::test]
async fn e2e_stream_blockquote_with_list_items_is_green() {
let out = super::simulate_stream_markdown_for_tests(&["> - item 1\n> - item 2\n"], true);
let out = super::simulate_stream_markdown_for_tests(
&["> - item 1\n> - item 2\n"],
/*finalize*/ true,
);
assert_eq!(out.len(), 2);
assert_eq!(out[0].style.fg, Some(Color::Green));
assert_eq!(out[1].style.fg, Some(Color::Green));
@@ -209,7 +215,7 @@ mod tests {
" - Fourth level (bullet)\n",
" - Fifth level to test indent consistency\n",
];
let out = super::simulate_stream_markdown_for_tests(&md, true);
let out = super::simulate_stream_markdown_for_tests(&md, /*finalize*/ true);
// Find the line that contains the third-level ordered text
let find_idx = out.iter().position(|l| {
l.spans
@@ -234,10 +240,12 @@ mod tests {
#[tokio::test]
async fn e2e_stream_blockquote_wrap_preserves_green_style() {
let long = "> This is a very long quoted line that should wrap across multiple columns to verify style preservation.";
let out = super::simulate_stream_markdown_for_tests(&[long, "\n"], true);
let out = super::simulate_stream_markdown_for_tests(&[long, "\n"], /*finalize*/ true);
// Wrap to a narrow width to force multiple output lines.
let wrapped =
crate::wrapping::word_wrap_lines(out.iter(), crate::wrapping::RtOptions::new(24));
let wrapped = crate::wrapping::word_wrap_lines(
out.iter(),
crate::wrapping::RtOptions::new(/*width*/ 24),
);
// Filter out purely blank lines
let non_blank: Vec<_> = wrapped
.into_iter()
@@ -270,7 +278,7 @@ mod tests {
async fn heading_starts_on_new_line_when_following_paragraph() {
// Stream a paragraph line, then a heading on the next line.
// Expect two distinct rendered lines: "Hello." and "Heading".
let mut c = super::MarkdownStreamCollector::new(None, &super::test_cwd());
let mut c = super::MarkdownStreamCollector::new(/*width*/ None, &super::test_cwd());
c.push_delta("Hello.\n");
let out1 = c.commit_complete_lines();
let s1: Vec<String> = out1
@@ -326,7 +334,7 @@ mod tests {
// Paragraph without trailing newline, then a chunk that starts with the newline
// and the heading text, then a final newline. The collector should first commit
// only the paragraph line, and later commit the heading as its own line.
let mut c = super::MarkdownStreamCollector::new(None, &super::test_cwd());
let mut c = super::MarkdownStreamCollector::new(/*width*/ None, &super::test_cwd());
c.push_delta("Sounds good!");
// No commit yet
assert!(c.commit_complete_lines().is_empty());
@@ -372,7 +380,12 @@ mod tests {
// Sanity check raw markdown rendering for a simple line does not produce spurious extras.
let mut rendered: Vec<ratatui::text::Line<'static>> = Vec::new();
let test_cwd = super::test_cwd();
crate::markdown::append_markdown("Hello.\n", None, Some(test_cwd.as_path()), &mut rendered);
crate::markdown::append_markdown(
"Hello.\n",
/*width*/ None,
Some(test_cwd.as_path()),
&mut rendered,
);
let rendered_strings: Vec<String> = rendered
.iter()
.map(|l| {
@@ -428,12 +441,17 @@ mod tests {
"\n",
];
let streamed = simulate_stream_markdown_for_tests(&deltas, true);
let streamed = simulate_stream_markdown_for_tests(&deltas, /*finalize*/ true);
let streamed_str = lines_to_plain_strings(&streamed);
let mut rendered_all: Vec<ratatui::text::Line<'static>> = Vec::new();
let test_cwd = super::test_cwd();
crate::markdown::append_markdown(input, None, Some(test_cwd.as_path()), &mut rendered_all);
crate::markdown::append_markdown(
input,
/*width*/ None,
Some(test_cwd.as_path()),
&mut rendered_all,
);
let rendered_all_str = lines_to_plain_strings(&rendered_all);
assert_eq!(
@@ -445,7 +463,7 @@ mod tests {
#[tokio::test]
async fn e2e_stream_deep_nested_third_level_marker_is_light_blue() {
let md = "1. First\n - Second level\n 1. Third level (ordered)\n - Fourth level (bullet)\n - Fifth level to test indent consistency\n";
let streamed = super::simulate_stream_markdown_for_tests(&[md], true);
let streamed = super::simulate_stream_markdown_for_tests(&[md], /*finalize*/ true);
let streamed_strs = lines_to_plain_strings(&streamed);
// Locate the third-level line in the streamed output; avoid relying on exact indent.
@@ -496,7 +514,7 @@ mod tests {
// An empty fenced code block followed by a heading should not render the fence,
// but should preserve a blank separator line so the heading starts on a new line.
let deltas = vec!["```bash\n```\n", "## Heading\n"]; // empty block and close in same commit
let streamed = simulate_stream_markdown_for_tests(&deltas, true);
let streamed = simulate_stream_markdown_for_tests(&deltas, /*finalize*/ true);
let texts = lines_to_plain_strings(&streamed);
assert!(
texts.iter().all(|s| !s.contains("```")),
@@ -512,7 +530,7 @@ mod tests {
#[tokio::test]
async fn paragraph_then_empty_fence_then_heading_keeps_heading_on_new_line() {
let deltas = vec!["Para.\n", "```\n```\n", "## Title\n"]; // empty fence block in one commit
let streamed = simulate_stream_markdown_for_tests(&deltas, true);
let streamed = simulate_stream_markdown_for_tests(&deltas, /*finalize*/ true);
let texts = lines_to_plain_strings(&streamed);
let para_idx = match texts.iter().position(|s| s == "Para.") {
Some(i) => i,
@@ -534,13 +552,18 @@ mod tests {
// that still reproduce the mismatch.
let deltas = vec!["- item.\n\n", "-"];
let streamed = simulate_stream_markdown_for_tests(&deltas, true);
let streamed = simulate_stream_markdown_for_tests(&deltas, /*finalize*/ true);
let streamed_strs = lines_to_plain_strings(&streamed);
let full: String = deltas.iter().copied().collect();
let mut rendered_all: Vec<ratatui::text::Line<'static>> = Vec::new();
let test_cwd = super::test_cwd();
crate::markdown::append_markdown(&full, None, Some(test_cwd.as_path()), &mut rendered_all);
crate::markdown::append_markdown(
&full,
/*width*/ None,
Some(test_cwd.as_path()),
&mut rendered_all,
);
let rendered_all_strs = lines_to_plain_strings(&rendered_all);
assert_eq!(
@@ -622,14 +645,19 @@ mod tests {
"\n\n",
];
let streamed = simulate_stream_markdown_for_tests(&deltas, true);
let streamed = simulate_stream_markdown_for_tests(&deltas, /*finalize*/ true);
let streamed_strs = lines_to_plain_strings(&streamed);
// Compute a full render for diagnostics only.
let full: String = deltas.iter().copied().collect();
let mut rendered_all: Vec<ratatui::text::Line<'static>> = Vec::new();
let test_cwd = super::test_cwd();
crate::markdown::append_markdown(&full, None, Some(test_cwd.as_path()), &mut rendered_all);
crate::markdown::append_markdown(
&full,
/*width*/ None,
Some(test_cwd.as_path()),
&mut rendered_all,
);
// Also assert exact expected plain strings for clarity.
let expected = vec![
@@ -652,12 +680,17 @@ mod tests {
// Targeted tests derived from fuzz findings. Each asserts streamed == full render.
async fn assert_streamed_equals_full(deltas: &[&str]) {
let streamed = simulate_stream_markdown_for_tests(deltas, true);
let streamed = simulate_stream_markdown_for_tests(deltas, /*finalize*/ true);
let streamed_strs = lines_to_plain_strings(&streamed);
let full: String = deltas.iter().copied().collect();
let mut rendered: Vec<ratatui::text::Line<'static>> = Vec::new();
let test_cwd = super::test_cwd();
crate::markdown::append_markdown(&full, None, Some(test_cwd.as_path()), &mut rendered);
crate::markdown::append_markdown(
&full,
/*width*/ None,
Some(test_cwd.as_path()),
&mut rendered,
);
let rendered_strs = lines_to_plain_strings(&rendered);
assert_eq!(streamed_strs, rendered_strs, "full:\n---\n{full}\n---");
}
+22 -22
View File
@@ -432,15 +432,15 @@ mod tests {
migration_copy_for_models(
"gpt-5.1-codex-mini",
"gpt-5.1-codex-max",
None,
/*model_link*/ None,
Some(
"Upgrade to gpt-5.2-codex for the latest and greatest agentic coding model."
.to_string(),
),
None,
/*migration_markdown*/ None,
"gpt-5.1-codex-max".to_string(),
Some("Codex-optimized flagship for deep and fast reasoning.".to_string()),
true,
/*can_opt_out*/ true,
),
);
@@ -455,7 +455,7 @@ mod tests {
#[test]
fn prompt_snapshot_gpt5_family() {
let backend = VT100Backend::new(65, 22);
let backend = VT100Backend::new(/*width*/ 65, /*height*/ 22);
let mut terminal = Terminal::with_options(backend).expect("terminal");
terminal.set_viewport_area(Rect::new(0, 0, 65, 22));
@@ -465,11 +465,11 @@ mod tests {
"gpt-5",
"gpt-5.1",
Some("https://www.codex.com/models/gpt-5.1".to_string()),
None,
None,
/*migration_copy*/ None,
/*migration_markdown*/ None,
"gpt-5.1".to_string(),
Some("Broad world knowledge with strong general reasoning.".to_string()),
false,
/*can_opt_out*/ false,
),
);
{
@@ -482,7 +482,7 @@ mod tests {
#[test]
fn prompt_snapshot_gpt5_codex() {
let backend = VT100Backend::new(60, 22);
let backend = VT100Backend::new(/*width*/ 60, /*height*/ 22);
let mut terminal = Terminal::with_options(backend).expect("terminal");
terminal.set_viewport_area(Rect::new(0, 0, 60, 22));
@@ -492,11 +492,11 @@ mod tests {
"gpt-5-codex",
"gpt-5.1-codex-max",
Some("https://www.codex.com/models/gpt-5.1-codex-max".to_string()),
None,
None,
/*migration_copy*/ None,
/*migration_markdown*/ None,
"gpt-5.1-codex-max".to_string(),
Some("Codex-optimized flagship for deep and fast reasoning.".to_string()),
false,
/*can_opt_out*/ false,
),
);
{
@@ -509,7 +509,7 @@ mod tests {
#[test]
fn prompt_snapshot_gpt5_codex_mini() {
let backend = VT100Backend::new(60, 22);
let backend = VT100Backend::new(/*width*/ 60, /*height*/ 22);
let mut terminal = Terminal::with_options(backend).expect("terminal");
terminal.set_viewport_area(Rect::new(0, 0, 60, 22));
@@ -519,11 +519,11 @@ mod tests {
"gpt-5-codex-mini",
"gpt-5.1-codex-mini",
Some("https://www.codex.com/models/gpt-5.1-codex-mini".to_string()),
None,
None,
/*migration_copy*/ None,
/*migration_markdown*/ None,
"gpt-5.1-codex-mini".to_string(),
Some("Optimized for codex. Cheaper, faster, but less capable.".to_string()),
false,
/*can_opt_out*/ false,
),
);
{
@@ -542,11 +542,11 @@ mod tests {
"gpt-old",
"gpt-new",
Some("https://www.codex.com/models/gpt-new".to_string()),
None,
None,
/*migration_copy*/ None,
/*migration_markdown*/ None,
"gpt-new".to_string(),
Some("Latest recommended model for better performance.".to_string()),
true,
/*can_opt_out*/ true,
),
);
@@ -571,11 +571,11 @@ mod tests {
"gpt-old",
"gpt-new",
Some("https://www.codex.com/models/gpt-new".to_string()),
None,
None,
/*migration_copy*/ None,
/*migration_markdown*/ None,
"gpt-new".to_string(),
Some("Latest recommended model for better performance.".to_string()),
true,
/*can_opt_out*/ true,
),
);
@@ -608,7 +608,7 @@ mod tests {
},
);
let backend = VT100Backend::new(40, 16);
let backend = VT100Backend::new(/*width*/ 40, /*height*/ 16);
let mut terminal = Terminal::with_options(backend).expect("terminal");
terminal.set_viewport_area(Rect::new(0, 0, 40, 16));
+8 -8
View File
@@ -688,27 +688,27 @@ mod tests {
fn agent_shortcut_matches_option_arrow_word_motion_fallbacks_only_when_allowed() {
assert!(previous_agent_shortcut_matches(
KeyEvent::new(KeyCode::Left, KeyModifiers::ALT),
false,
/*allow_word_motion_fallback*/ false,
));
assert!(next_agent_shortcut_matches(
KeyEvent::new(KeyCode::Right, KeyModifiers::ALT),
false,
/*allow_word_motion_fallback*/ false,
));
assert!(previous_agent_shortcut_matches(
KeyEvent::new(KeyCode::Char('b'), KeyModifiers::ALT),
true,
/*allow_word_motion_fallback*/ true,
));
assert!(next_agent_shortcut_matches(
KeyEvent::new(KeyCode::Char('f'), KeyModifiers::ALT),
true,
/*allow_word_motion_fallback*/ true,
));
assert!(!previous_agent_shortcut_matches(
KeyEvent::new(KeyCode::Char('b'), KeyModifiers::ALT),
false,
/*allow_word_motion_fallback*/ false,
));
assert!(!next_agent_shortcut_matches(
KeyEvent::new(KeyCode::Char('f'), KeyModifiers::ALT),
false,
/*allow_word_motion_fallback*/ false,
));
}
@@ -757,7 +757,7 @@ mod tests {
}),
);
let lines = cell.display_lines(200);
let lines = cell.display_lines(/*width*/ 200);
let title = &lines[0];
assert_eq!(title.spans[2].content.as_ref(), "Robie");
assert_eq!(title.spans[2].style.fg, Some(Color::Cyan));
@@ -789,7 +789,7 @@ mod tests {
}
fn cell_to_text(cell: &PlainHistoryCell) -> String {
cell.display_lines(200)
cell.display_lines(/*width*/ 200)
.iter()
.map(line_to_text)
.collect::<Vec<_>>()
@@ -922,7 +922,7 @@ mod tests {
loader_overrides: Default::default(),
cloud_requirements: cloud_requirements_loader_for_storage(
codex_home_path.clone(),
false,
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
"https://chatgpt.com/backend-api/".to_string(),
),
@@ -214,7 +214,8 @@ mod tests {
error: None,
};
let mut terminal = Terminal::new(VT100Backend::new(70, 14)).expect("terminal");
let mut terminal =
Terminal::new(VT100Backend::new(/*width*/ 70, /*height*/ 14)).expect("terminal");
terminal
.draw(|f| (&widget).render_ref(f.area(), f.buffer_mut()))
.expect("draw");
@@ -128,7 +128,11 @@ mod tests {
#[test]
fn welcome_renders_animation_on_first_draw() {
let widget = WelcomeWidget::new(false, FrameRequester::test_dummy(), true);
let widget = WelcomeWidget::new(
/*is_logged_in*/ false,
FrameRequester::test_dummy(),
/*animations_enabled*/ true,
);
let area = Rect::new(0, 0, MIN_ANIMATION_WIDTH, MIN_ANIMATION_HEIGHT);
let mut buf = Buffer::empty(area);
let frame_lines = widget.animation.current_frame().lines().count() as u16;
@@ -140,7 +144,11 @@ mod tests {
#[test]
fn welcome_skips_animation_below_height_breakpoint() {
let widget = WelcomeWidget::new(false, FrameRequester::test_dummy(), true);
let widget = WelcomeWidget::new(
/*is_logged_in*/ false,
FrameRequester::test_dummy(),
/*animations_enabled*/ true,
);
let area = Rect::new(0, 0, MIN_ANIMATION_WIDTH, MIN_ANIMATION_HEIGHT - 1);
let mut buf = Buffer::empty(area);
(&widget).render(area, &mut buf);
@@ -153,7 +161,11 @@ mod tests {
fn ctrl_dot_changes_animation_variant() {
let mut widget = WelcomeWidget {
is_logged_in: false,
animation: AsciiAnimation::with_variants(FrameRequester::test_dummy(), &VARIANTS, 0),
animation: AsciiAnimation::with_variants(
FrameRequester::test_dummy(),
&VARIANTS,
/*variant_idx*/ 0,
),
animations_enabled: true,
layout_area: Cell::new(None),
};
+26 -19
View File
@@ -914,7 +914,7 @@ mod tests {
lines: vec![Line::from("alpha")],
})]);
overlay.sync_live_tail(
40,
/*width*/ 40,
Some(ActiveCellTranscriptKey {
revision: 1,
is_stream_continuation: false,
@@ -942,11 +942,11 @@ mod tests {
animation_tick: None,
};
overlay.sync_live_tail(40, Some(key), |_| {
overlay.sync_live_tail(/*width*/ 40, Some(key), |_| {
calls.set(calls.get() + 1);
Some(vec![Line::from("tail")])
});
overlay.sync_live_tail(40, Some(key), |_| {
overlay.sync_live_tail(/*width*/ 40, Some(key), |_| {
calls.set(calls.get() + 1);
Some(vec![Line::from("tail2")])
});
@@ -1012,8 +1012,8 @@ mod tests {
vec!["bash".into(), "-lc".into(), "ls".into()],
vec![ParsedCommand::Unknown { cmd: "ls".into() }],
ExecCommandSource::Agent,
None,
true,
/*interaction_input*/ None,
/*animations_enabled*/ true,
);
exec_cell.complete_call(
"exec-1",
@@ -1212,30 +1212,33 @@ mod tests {
#[test]
fn pager_view_content_height_counts_renderables() {
let pv = PagerView::new(
vec![paragraph_block("a", 2), paragraph_block("b", 3)],
vec![
paragraph_block("a", /*lines*/ 2),
paragraph_block("b", /*lines*/ 3),
],
"T".to_string(),
0,
/*scroll_offset*/ 0,
);
assert_eq!(pv.content_height(80), 5);
assert_eq!(pv.content_height(/*width*/ 80), 5);
}
#[test]
fn pager_view_ensure_chunk_visible_scrolls_down_when_needed() {
let mut pv = PagerView::new(
vec![
paragraph_block("a", 1),
paragraph_block("b", 3),
paragraph_block("c", 3),
paragraph_block("a", /*lines*/ 1),
paragraph_block("b", /*lines*/ 3),
paragraph_block("c", /*lines*/ 3),
],
"T".to_string(),
0,
/*scroll_offset*/ 0,
);
let area = Rect::new(0, 0, 20, 8);
pv.scroll_offset = 0;
let content_area = pv.content_area(area);
pv.ensure_chunk_visible(2, content_area);
pv.ensure_chunk_visible(/*idx*/ 2, content_area);
let mut buf = Buffer::empty(area);
pv.render(area, &mut buf);
@@ -1259,24 +1262,28 @@ mod tests {
fn pager_view_ensure_chunk_visible_scrolls_up_when_needed() {
let mut pv = PagerView::new(
vec![
paragraph_block("a", 2),
paragraph_block("b", 3),
paragraph_block("c", 3),
paragraph_block("a", /*lines*/ 2),
paragraph_block("b", /*lines*/ 3),
paragraph_block("c", /*lines*/ 3),
],
"T".to_string(),
0,
/*scroll_offset*/ 0,
);
let area = Rect::new(0, 0, 20, 3);
pv.scroll_offset = 6;
pv.ensure_chunk_visible(0, area);
pv.ensure_chunk_visible(/*idx*/ 0, area);
assert_eq!(pv.scroll_offset, 0);
}
#[test]
fn pager_view_is_scrolled_to_bottom_accounts_for_wrapped_height() {
let mut pv = PagerView::new(vec![paragraph_block("a", 10)], "T".to_string(), 0);
let mut pv = PagerView::new(
vec![paragraph_block("a", /*lines*/ 10)],
"T".to_string(),
/*scroll_offset*/ 0,
);
let area = Rect::new(0, 0, 20, 8);
let mut buf = Buffer::empty(area);
@@ -750,7 +750,7 @@ mod tests {
}
fn unique_foreground_colors_for_theme(theme_name: &str) -> Vec<String> {
let theme = resolve_theme_by_name(theme_name, None)
let theme = resolve_theme_by_name(theme_name, /*codex_home*/ None)
.unwrap_or_else(|| panic!("expected built-in theme {theme_name} to resolve"));
let lines = highlight_to_line_spans_with_theme(
"fn main() { let answer = 42; println!(\"hello\"); }\n",
@@ -1000,13 +1000,13 @@ mod tests {
#[test]
fn ansi_palette_color_maps_ansi_white_to_gray() {
assert_eq!(ansi_palette_color(0x07), RtColor::Gray);
assert_eq!(ansi_palette_color(/*index*/ 0x07), RtColor::Gray);
}
#[test]
fn ansi_family_themes_use_terminal_palette_colors_not_rgb() {
for theme_name in ["ansi", "base16", "base16-256"] {
let theme = resolve_theme_by_name(theme_name, None)
let theme = resolve_theme_by_name(theme_name, /*codex_home*/ None)
.unwrap_or_else(|| panic!("expected built-in theme {theme_name} to resolve"));
let lines = highlight_to_line_spans_with_theme(
"fn main() { let answer = 42; println!(\"hello\"); }\n",
@@ -1212,8 +1212,8 @@ mod tests {
#[test]
fn bundled_theme_can_provide_diff_scope_backgrounds() {
let theme =
resolve_theme_by_name("github", None).expect("expected built-in GitHub theme to load");
let theme = resolve_theme_by_name("github", /*codex_home*/ None)
.expect("expected built-in GitHub theme to load");
let rgbs = diff_scope_background_rgbs_for_theme(&theme);
assert!(
rgbs.inserted.is_some() && rgbs.deleted.is_some(),
@@ -1331,13 +1331,13 @@ mod tests {
#[test]
fn validate_theme_name_none_for_bundled() {
// Bundled themes should never produce a warning.
assert!(validate_theme_name(Some("dracula"), None).is_none());
assert!(validate_theme_name(Some("dracula"), /*codex_home*/ None).is_none());
assert!(validate_theme_name(Some("nord"), Some(Path::new("/nonexistent"))).is_none());
}
#[test]
fn validate_theme_name_none_when_no_override() {
assert!(validate_theme_name(None, None).is_none());
assert!(validate_theme_name(/*name*/ None, /*codex_home*/ None).is_none());
}
#[test]
+67 -51
View File
@@ -1872,8 +1872,8 @@ mod tests {
FrameRequester::test_dummy(),
loader,
ProviderFilter::Any,
false,
None,
/*show_all*/ false,
/*filter_cwd*/ None,
SessionPickerAction::Resume,
);
let row = Row {
@@ -1903,8 +1903,8 @@ mod tests {
FrameRequester::test_dummy(),
loader,
ProviderFilter::MatchDefault(String::from("openai")),
true,
None,
/*show_all*/ true,
/*filter_cwd*/ None,
SessionPickerAction::Resume,
);
@@ -1946,7 +1946,7 @@ mod tests {
state.view_rows = Some(3);
state.selected = 1;
state.scroll_top = 0;
state.update_view_rows(3);
state.update_view_rows(/*rows*/ 3);
let metrics = calculate_column_metrics(&state.filtered_rows, state.show_all);
@@ -1981,8 +1981,8 @@ mod tests {
FrameRequester::test_dummy(),
loader,
ProviderFilter::MatchDefault(String::from("openai")),
true,
None,
/*show_all*/ true,
/*filter_cwd*/ None,
SessionPickerAction::Resume,
);
state.inline_error = Some(String::from(
@@ -2217,8 +2217,8 @@ mod tests {
FrameRequester::test_dummy(),
loader,
ProviderFilter::MatchDefault(String::from("openai")),
true,
None,
/*show_all*/ true,
/*filter_cwd*/ None,
SessionPickerAction::Resume,
);
@@ -2250,7 +2250,7 @@ mod tests {
state.view_rows = Some(2);
state.selected = 0;
state.scroll_top = 0;
state.update_view_rows(2);
state.update_view_rows(/*rows*/ 2);
state.update_thread_names().await;
@@ -2284,8 +2284,8 @@ mod tests {
FrameRequester::test_dummy(),
loader,
ProviderFilter::MatchDefault(String::from("openai")),
true,
None,
/*show_all*/ true,
/*filter_cwd*/ None,
SessionPickerAction::Resume,
);
@@ -2298,8 +2298,8 @@ mod tests {
Some(cursor_from_str(
"2025-01-02T00-00-00|00000000-0000-0000-0000-000000000000",
)),
2,
false,
/*num_scanned_files*/ 2,
/*reached_scan_cap*/ false,
));
state.ingest_page(page(
@@ -2310,8 +2310,8 @@ mod tests {
Some(cursor_from_str(
"2025-01-01T00-00-00|00000000-0000-0000-0000-000000000001",
)),
2,
false,
/*num_scanned_files*/ 2,
/*reached_scan_cap*/ false,
));
state.ingest_page(page(
@@ -2320,9 +2320,9 @@ mod tests {
"2024-12-31T23:00:00Z",
"very old",
)],
None,
1,
false,
/*next_cursor*/ None,
/*num_scanned_files*/ 1,
/*reached_scan_cap*/ false,
));
let previews: Vec<_> = state
@@ -2353,8 +2353,8 @@ mod tests {
FrameRequester::test_dummy(),
loader,
ProviderFilter::MatchDefault(String::from("openai")),
true,
None,
/*show_all*/ true,
/*filter_cwd*/ None,
SessionPickerAction::Resume,
);
state.reset_pagination();
@@ -2366,12 +2366,12 @@ mod tests {
Some(cursor_from_str(
"2025-01-03T00-00-00|00000000-0000-0000-0000-000000000000",
)),
2,
false,
/*num_scanned_files*/ 2,
/*reached_scan_cap*/ false,
));
assert!(recorded_requests.lock().unwrap().is_empty());
state.ensure_minimum_rows_for_view(10);
state.ensure_minimum_rows_for_view(/*minimum_rows*/ 10);
let guard = recorded_requests.lock().unwrap();
assert_eq!(guard.len(), 1);
assert!(guard[0].search_token.is_none());
@@ -2387,7 +2387,7 @@ mod tests {
labels: Vec::new(),
};
let created = column_visibility(30, &metrics, ThreadSortKey::CreatedAt);
let created = column_visibility(/*area_width*/ 30, &metrics, ThreadSortKey::CreatedAt);
assert_eq!(
created,
ColumnVisibility {
@@ -2398,7 +2398,7 @@ mod tests {
}
);
let updated = column_visibility(30, &metrics, ThreadSortKey::UpdatedAt);
let updated = column_visibility(/*area_width*/ 30, &metrics, ThreadSortKey::UpdatedAt);
assert_eq!(
updated,
ColumnVisibility {
@@ -2409,7 +2409,7 @@ mod tests {
}
);
let wide = column_visibility(40, &metrics, ThreadSortKey::CreatedAt);
let wide = column_visibility(/*area_width*/ 40, &metrics, ThreadSortKey::CreatedAt);
assert_eq!(
wide,
ColumnVisibility {
@@ -2434,8 +2434,8 @@ mod tests {
FrameRequester::test_dummy(),
loader,
ProviderFilter::MatchDefault(String::from("openai")),
true,
None,
/*show_all*/ true,
/*filter_cwd*/ None,
SessionPickerAction::Resume,
);
@@ -2464,8 +2464,8 @@ mod tests {
FrameRequester::test_dummy(),
loader,
ProviderFilter::MatchDefault(String::from("openai")),
true,
None,
/*show_all*/ true,
/*filter_cwd*/ None,
SessionPickerAction::Resume,
);
@@ -2478,8 +2478,11 @@ mod tests {
}
state.reset_pagination();
state.ingest_page(page(items, None, 20, false));
state.update_view_rows(5);
state.ingest_page(page(
items, /*next_cursor*/ None, /*num_scanned_files*/ 20,
/*reached_scan_cap*/ false,
));
state.update_view_rows(/*rows*/ 5);
assert_eq!(state.selected, 0);
state
@@ -2509,8 +2512,8 @@ mod tests {
FrameRequester::test_dummy(),
loader,
ProviderFilter::MatchDefault(String::from("openai")),
true,
None,
/*show_all*/ true,
/*filter_cwd*/ None,
SessionPickerAction::Resume,
);
@@ -2549,8 +2552,8 @@ mod tests {
FrameRequester::test_dummy(),
loader,
ProviderFilter::MatchDefault(String::from("openai")),
true,
None,
/*show_all*/ true,
/*filter_cwd*/ None,
SessionPickerAction::Resume,
);
let thread_id = ThreadId::new();
@@ -2618,8 +2621,8 @@ mod tests {
FrameRequester::test_dummy(),
loader,
ProviderFilter::MatchDefault(String::from("openai")),
true,
None,
/*show_all*/ true,
/*filter_cwd*/ None,
SessionPickerAction::Resume,
);
@@ -2632,8 +2635,11 @@ mod tests {
}
state.reset_pagination();
state.ingest_page(page(items, None, 10, false));
state.update_view_rows(5);
state.ingest_page(page(
items, /*next_cursor*/ None, /*num_scanned_files*/ 10,
/*reached_scan_cap*/ false,
));
state.update_view_rows(/*rows*/ 5);
state.selected = state.filtered_rows.len().saturating_sub(1);
state.ensure_selected_visible();
@@ -2663,8 +2669,8 @@ mod tests {
FrameRequester::test_dummy(),
loader,
ProviderFilter::MatchDefault(String::from("openai")),
true,
None,
/*show_all*/ true,
/*filter_cwd*/ None,
SessionPickerAction::Resume,
);
state.reset_pagination();
@@ -2677,8 +2683,8 @@ mod tests {
Some(cursor_from_str(
"2025-01-02T00-00-00|00000000-0000-0000-0000-000000000000",
)),
1,
false,
/*num_scanned_files*/ 1,
/*reached_scan_cap*/ false,
));
recorded_requests.lock().unwrap().clear();
@@ -2698,8 +2704,8 @@ mod tests {
Some(cursor_from_str(
"2025-01-03T00-00-00|00000000-0000-0000-0000-000000000001",
)),
5,
false,
/*num_scanned_files*/ 5,
/*reached_scan_cap*/ false,
)),
})
.await
@@ -2726,8 +2732,8 @@ mod tests {
Some(cursor_from_str(
"2025-01-04T00-00-00|00000000-0000-0000-0000-000000000002",
)),
7,
false,
/*num_scanned_files*/ 7,
/*reached_scan_cap*/ false,
)),
})
.await
@@ -2748,7 +2754,12 @@ mod tests {
.handle_background_event(BackgroundEvent::PageLoaded {
request_token: second_request.request_token,
search_token: second_request.search_token,
page: Ok(page(Vec::new(), None, 0, false)),
page: Ok(page(
Vec::new(),
/*next_cursor*/ None,
/*num_scanned_files*/ 0,
/*reached_scan_cap*/ false,
)),
})
.await
.unwrap();
@@ -2758,7 +2769,12 @@ mod tests {
.handle_background_event(BackgroundEvent::PageLoaded {
request_token: active_request.request_token,
search_token: active_request.search_token,
page: Ok(page(Vec::new(), None, 3, true)),
page: Ok(page(
Vec::new(),
/*next_cursor*/ None,
/*num_scanned_files*/ 3,
/*reached_scan_cap*/ true,
)),
})
.await
.unwrap();
@@ -366,7 +366,7 @@ mod tests {
let codex = RateLimitSnapshotDisplay {
limit_name: "codex".to_string(),
captured_at: now,
primary: Some(window(10.0)),
primary: Some(window(/*used_percent*/ 10.0)),
secondary: None,
credits: Some(CreditsSnapshotDisplay {
has_credits: true,
@@ -377,7 +377,7 @@ mod tests {
let other = RateLimitSnapshotDisplay {
limit_name: "codex-other".to_string(),
captured_at: now,
primary: Some(window(20.0)),
primary: Some(window(/*used_percent*/ 20.0)),
secondary: None,
credits: Some(CreditsSnapshotDisplay {
has_credits: true,
+93 -93
View File
@@ -127,12 +127,12 @@ async fn status_snapshot_includes_reasoning_details() {
primary: Some(RateLimitWindow {
used_percent: 72.5,
window_minutes: Some(300),
resets_at: Some(reset_at_from(&captured_at, 600)),
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 600)),
}),
secondary: Some(RateLimitWindow {
used_percent: 45.0,
window_minutes: Some(10080),
resets_at: Some(reset_at_from(&captured_at, 1_200)),
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 1_200)),
}),
credits: None,
plan_type: None,
@@ -149,16 +149,16 @@ async fn status_snapshot_includes_reasoning_details() {
Some(&token_info),
&usage,
&None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
/*collaboration_mode*/ None,
reasoning_effort_override,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
@@ -203,19 +203,19 @@ async fn status_permissions_non_default_workspace_write_is_custom() {
let composite = new_status_output(
&config,
account_display.as_ref(),
None,
/*token_info*/ None,
&usage,
&None,
None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
/*rate_limits*/ None,
None,
captured_at,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let rendered_lines = render_lines(&composite.display_lines(80));
let rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
let permissions_line = rendered_lines
.iter()
.find(|line| line.contains("Permissions:"))
@@ -268,16 +268,16 @@ async fn status_snapshot_includes_forked_from() {
Some(&token_info),
&usage,
&Some(session_id),
None,
/*thread_name*/ None,
Some(forked_from),
None,
/*rate_limits*/ None,
None,
captured_at,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
@@ -314,7 +314,7 @@ async fn status_snapshot_includes_monthly_limit() {
primary: Some(RateLimitWindow {
used_percent: 12.0,
window_minutes: Some(43_200),
resets_at: Some(reset_at_from(&captured_at, 86_400)),
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 86_400)),
}),
secondary: None,
credits: None,
@@ -330,16 +330,16 @@ async fn status_snapshot_includes_monthly_limit() {
Some(&token_info),
&usage,
&None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
@@ -380,16 +380,16 @@ async fn status_snapshot_shows_unlimited_credits() {
Some(&token_info),
&usage,
&None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let rendered = render_lines(&composite.display_lines(120));
let rendered = render_lines(&composite.display_lines(/*width*/ 120));
assert!(
rendered
.iter()
@@ -429,16 +429,16 @@ async fn status_snapshot_shows_positive_credits() {
Some(&token_info),
&usage,
&None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let rendered = render_lines(&composite.display_lines(120));
let rendered = render_lines(&composite.display_lines(/*width*/ 120));
assert!(
rendered
.iter()
@@ -478,16 +478,16 @@ async fn status_snapshot_hides_zero_credits() {
Some(&token_info),
&usage,
&None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let rendered = render_lines(&composite.display_lines(120));
let rendered = render_lines(&composite.display_lines(/*width*/ 120));
assert!(
rendered.iter().all(|line| !line.contains("Credits:")),
"expected no Credits line, got {rendered:?}"
@@ -525,16 +525,16 @@ async fn status_snapshot_hides_when_has_no_credits_flag() {
Some(&token_info),
&usage,
&None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let rendered = render_lines(&composite.display_lines(120));
let rendered = render_lines(&composite.display_lines(/*width*/ 120));
assert!(
rendered.iter().all(|line| !line.contains("Credits:")),
"expected no Credits line when has_credits is false, got {rendered:?}"
@@ -570,16 +570,16 @@ async fn status_card_token_usage_excludes_cached_tokens() {
Some(&token_info),
&usage,
&None,
None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
/*rate_limits*/ None,
None,
now,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let rendered = render_lines(&composite.display_lines(120));
let rendered = render_lines(&composite.display_lines(/*width*/ 120));
assert!(
rendered.iter().all(|line| !line.contains("cached")),
@@ -615,7 +615,7 @@ async fn status_snapshot_truncates_in_narrow_terminal() {
primary: Some(RateLimitWindow {
used_percent: 72.5,
window_minutes: Some(300),
resets_at: Some(reset_at_from(&captured_at, 600)),
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 600)),
}),
secondary: None,
credits: None,
@@ -632,16 +632,16 @@ async fn status_snapshot_truncates_in_narrow_terminal() {
Some(&token_info),
&usage,
&None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
/*collaboration_mode*/ None,
reasoning_effort_override,
);
let mut rendered_lines = render_lines(&composite.display_lines(70));
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 70));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
@@ -681,16 +681,16 @@ async fn status_snapshot_shows_missing_limits_message() {
Some(&token_info),
&usage,
&None,
None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
/*rate_limits*/ None,
None,
now,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
@@ -726,12 +726,12 @@ async fn status_snapshot_includes_credits_and_limits() {
primary: Some(RateLimitWindow {
used_percent: 45.0,
window_minutes: Some(300),
resets_at: Some(reset_at_from(&captured_at, 900)),
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 900)),
}),
secondary: Some(RateLimitWindow {
used_percent: 30.0,
window_minutes: Some(10_080),
resets_at: Some(reset_at_from(&captured_at, 2_700)),
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 2_700)),
}),
credits: Some(CreditsSnapshot {
has_credits: true,
@@ -750,16 +750,16 @@ async fn status_snapshot_includes_credits_and_limits() {
Some(&token_info),
&usage,
&None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
@@ -807,16 +807,16 @@ async fn status_snapshot_shows_empty_limits_message() {
Some(&token_info),
&usage,
&None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
Some(&rate_display),
None,
captured_at,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
@@ -852,12 +852,12 @@ async fn status_snapshot_shows_stale_limits_message() {
primary: Some(RateLimitWindow {
used_percent: 72.5,
window_minutes: Some(300),
resets_at: Some(reset_at_from(&captured_at, 600)),
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 600)),
}),
secondary: Some(RateLimitWindow {
used_percent: 40.0,
window_minutes: Some(10_080),
resets_at: Some(reset_at_from(&captured_at, 1_800)),
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 1_800)),
}),
credits: None,
plan_type: None,
@@ -873,16 +873,16 @@ async fn status_snapshot_shows_stale_limits_message() {
Some(&token_info),
&usage,
&None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
Some(&rate_display),
None,
now,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
@@ -918,12 +918,12 @@ async fn status_snapshot_cached_limits_hide_credits_without_flag() {
primary: Some(RateLimitWindow {
used_percent: 60.0,
window_minutes: Some(300),
resets_at: Some(reset_at_from(&captured_at, 1_200)),
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 1_200)),
}),
secondary: Some(RateLimitWindow {
used_percent: 35.0,
window_minutes: Some(10_080),
resets_at: Some(reset_at_from(&captured_at, 2_400)),
resets_at: Some(reset_at_from(&captured_at, /*seconds*/ 2_400)),
}),
credits: Some(CreditsSnapshot {
has_credits: false,
@@ -943,16 +943,16 @@ async fn status_snapshot_cached_limits_hide_credits_without_flag() {
Some(&token_info),
&usage,
&None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
Some(&rate_display),
None,
now,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let mut rendered_lines = render_lines(&composite.display_lines(80));
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
if cfg!(windows) {
for line in &mut rendered_lines {
*line = line.replace('\\', "/");
@@ -1001,16 +1001,16 @@ async fn status_context_window_uses_last_usage() {
Some(&token_info),
&total_usage,
&None,
None,
None,
None,
/*thread_name*/ None,
/*forked_from*/ None,
/*rate_limits*/ None,
None,
now,
&model_slug,
None,
None,
/*collaboration_mode*/ None,
/*reasoning_effort_override*/ None,
);
let rendered_lines = render_lines(&composite.display_lines(80));
let rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
let context_line = rendered_lines
.into_iter()
.find(|line| line.contains("Context window"))
@@ -302,14 +302,14 @@ mod tests {
#[test]
fn fmt_elapsed_compact_formats_seconds_minutes_hours() {
assert_eq!(fmt_elapsed_compact(0), "0s");
assert_eq!(fmt_elapsed_compact(1), "1s");
assert_eq!(fmt_elapsed_compact(59), "59s");
assert_eq!(fmt_elapsed_compact(60), "1m 00s");
assert_eq!(fmt_elapsed_compact(61), "1m 01s");
assert_eq!(fmt_elapsed_compact(/*elapsed_secs*/ 0), "0s");
assert_eq!(fmt_elapsed_compact(/*elapsed_secs*/ 1), "1s");
assert_eq!(fmt_elapsed_compact(/*elapsed_secs*/ 59), "59s");
assert_eq!(fmt_elapsed_compact(/*elapsed_secs*/ 60), "1m 00s");
assert_eq!(fmt_elapsed_compact(/*elapsed_secs*/ 61), "1m 01s");
assert_eq!(fmt_elapsed_compact(3 * 60 + 5), "3m 05s");
assert_eq!(fmt_elapsed_compact(59 * 60 + 59), "59m 59s");
assert_eq!(fmt_elapsed_compact(3600), "1h 00m 00s");
assert_eq!(fmt_elapsed_compact(/*elapsed_secs*/ 3600), "1h 00m 00s");
assert_eq!(fmt_elapsed_compact(3600 + 60 + 1), "1h 01m 01s");
assert_eq!(fmt_elapsed_compact(25 * 3600 + 2 * 60 + 3), "25h 02m 03s");
}
@@ -318,7 +318,11 @@ mod tests {
fn renders_with_working_header() {
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
let tx = AppEventSender::new(tx_raw);
let w = StatusIndicatorWidget::new(tx, crate::tui::FrameRequester::test_dummy(), true);
let w = StatusIndicatorWidget::new(
tx,
crate::tui::FrameRequester::test_dummy(),
/*animations_enabled*/ true,
);
// Render into a fixed-size test terminal and snapshot the backend.
let mut terminal = Terminal::new(TestBackend::new(80, 2)).expect("terminal");
@@ -332,7 +336,11 @@ mod tests {
fn renders_truncated() {
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
let tx = AppEventSender::new(tx_raw);
let w = StatusIndicatorWidget::new(tx, crate::tui::FrameRequester::test_dummy(), true);
let w = StatusIndicatorWidget::new(
tx,
crate::tui::FrameRequester::test_dummy(),
/*animations_enabled*/ true,
);
// Render into a fixed-size test terminal and snapshot the backend.
let mut terminal = Terminal::new(TestBackend::new(20, 2)).expect("terminal");
@@ -346,7 +354,11 @@ mod tests {
fn renders_wrapped_details_panama_two_lines() {
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
let tx = AppEventSender::new(tx_raw);
let mut w = StatusIndicatorWidget::new(tx, crate::tui::FrameRequester::test_dummy(), false);
let mut w = StatusIndicatorWidget::new(
tx,
crate::tui::FrameRequester::test_dummy(),
/*animations_enabled*/ false,
);
w.update_details(
Some("A man a plan a canal panama".to_string()),
StatusDetailsCapitalization::CapitalizeFirst,
@@ -371,8 +383,11 @@ mod tests {
fn timer_pauses_when_requested() {
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
let tx = AppEventSender::new(tx_raw);
let mut widget =
StatusIndicatorWidget::new(tx, crate::tui::FrameRequester::test_dummy(), true);
let mut widget = StatusIndicatorWidget::new(
tx,
crate::tui::FrameRequester::test_dummy(),
/*animations_enabled*/ true,
);
let baseline = Instant::now();
widget.last_resume_at = baseline;
@@ -393,14 +408,18 @@ mod tests {
fn details_overflow_adds_ellipsis() {
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
let tx = AppEventSender::new(tx_raw);
let mut w = StatusIndicatorWidget::new(tx, crate::tui::FrameRequester::test_dummy(), true);
let mut w = StatusIndicatorWidget::new(
tx,
crate::tui::FrameRequester::test_dummy(),
/*animations_enabled*/ true,
);
w.update_details(
Some("abcd abcd abcd abcd".to_string()),
StatusDetailsCapitalization::CapitalizeFirst,
STATUS_DETAILS_DEFAULT_MAX_LINES,
);
let lines = w.wrapped_details_lines(6);
let lines = w.wrapped_details_lines(/*width*/ 6);
assert_eq!(lines.len(), STATUS_DETAILS_DEFAULT_MAX_LINES);
let last = lines.last().expect("expected last details line");
assert!(
@@ -413,11 +432,15 @@ mod tests {
fn details_args_can_disable_capitalization_and_limit_lines() {
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
let tx = AppEventSender::new(tx_raw);
let mut w = StatusIndicatorWidget::new(tx, crate::tui::FrameRequester::test_dummy(), true);
let mut w = StatusIndicatorWidget::new(
tx,
crate::tui::FrameRequester::test_dummy(),
/*animations_enabled*/ true,
);
w.update_details(
Some("cargo test -p codex-core and then cargo test -p codex-tui".to_string()),
StatusDetailsCapitalization::Preserve,
1,
/*max_lines*/ 1,
);
assert_eq!(
@@ -425,7 +448,7 @@ mod tests {
Some("cargo test -p codex-core and then cargo test -p codex-tui")
);
let lines = w.wrapped_details_lines(24);
let lines = w.wrapped_details_lines(/*width*/ 24);
assert_eq!(lines.len(), 1);
let last = lines.last().expect("expected one details line");
assert!(
@@ -310,7 +310,7 @@ mod tests {
let mut policy = AdaptiveChunkingPolicy::default();
let now = Instant::now();
let decision = policy.decide(snapshot(1, 10), now);
let decision = policy.decide(snapshot(/*queued_lines*/ 1, /*oldest_age_ms*/ 10), now);
assert_eq!(decision.mode, ChunkingMode::Smooth);
assert_eq!(decision.entered_catch_up, false);
assert_eq!(decision.drain_plan, DrainPlan::Single);
@@ -321,7 +321,7 @@ mod tests {
let mut policy = AdaptiveChunkingPolicy::default();
let now = Instant::now();
let decision = policy.decide(snapshot(8, 10), now);
let decision = policy.decide(snapshot(/*queued_lines*/ 8, /*oldest_age_ms*/ 10), now);
assert_eq!(decision.mode, ChunkingMode::CatchUp);
assert_eq!(decision.entered_catch_up, true);
assert_eq!(decision.drain_plan, DrainPlan::Batch(8));
@@ -332,7 +332,7 @@ mod tests {
let mut policy = AdaptiveChunkingPolicy::default();
let now = Instant::now();
let decision = policy.decide(snapshot(2, 120), now);
let decision = policy.decide(snapshot(/*queued_lines*/ 2, /*oldest_age_ms*/ 120), now);
assert_eq!(decision.mode, ChunkingMode::CatchUp);
assert_eq!(decision.entered_catch_up, true);
assert_eq!(decision.drain_plan, DrainPlan::Batch(2));
@@ -342,9 +342,12 @@ mod tests {
fn severe_backlog_uses_faster_paced_batches() {
let mut policy = AdaptiveChunkingPolicy::default();
let now = Instant::now();
let _ = policy.decide(snapshot(9, 10), now);
let _ = policy.decide(snapshot(/*queued_lines*/ 9, /*oldest_age_ms*/ 10), now);
let decision = policy.decide(snapshot(64, 10), now + Duration::from_millis(5));
let decision = policy.decide(
snapshot(/*queued_lines*/ 64, /*oldest_age_ms*/ 10),
now + Duration::from_millis(5),
);
assert_eq!(decision.mode, ChunkingMode::CatchUp);
assert_eq!(decision.drain_plan, DrainPlan::Batch(64));
}
@@ -353,7 +356,7 @@ mod tests {
fn catch_up_batch_drains_current_backlog() {
let mut policy = AdaptiveChunkingPolicy::default();
let now = Instant::now();
let decision = policy.decide(snapshot(512, 400), now);
let decision = policy.decide(snapshot(/*queued_lines*/ 512, /*oldest_age_ms*/ 400), now);
assert_eq!(decision.mode, ChunkingMode::CatchUp);
assert_eq!(decision.drain_plan, DrainPlan::Batch(512));
}
@@ -363,13 +366,19 @@ mod tests {
let mut policy = AdaptiveChunkingPolicy::default();
let t0 = Instant::now();
let _ = policy.decide(snapshot(9, 10), t0);
let _ = policy.decide(snapshot(/*queued_lines*/ 9, /*oldest_age_ms*/ 10), t0);
assert_eq!(policy.mode(), ChunkingMode::CatchUp);
let pre_hold = policy.decide(snapshot(2, 40), t0 + Duration::from_millis(200));
let pre_hold = policy.decide(
snapshot(/*queued_lines*/ 2, /*oldest_age_ms*/ 40),
t0 + Duration::from_millis(200),
);
assert_eq!(pre_hold.mode, ChunkingMode::CatchUp);
let post_hold = policy.decide(snapshot(2, 40), t0 + Duration::from_millis(460));
let post_hold = policy.decide(
snapshot(/*queued_lines*/ 2, /*oldest_age_ms*/ 40),
t0 + Duration::from_millis(460),
);
assert_eq!(post_hold.mode, ChunkingMode::Smooth);
assert_eq!(post_hold.drain_plan, DrainPlan::Single);
}
@@ -378,7 +387,7 @@ mod tests {
fn drops_back_to_smooth_when_idle() {
let mut policy = AdaptiveChunkingPolicy::default();
let now = Instant::now();
let _ = policy.decide(snapshot(9, 10), now);
let _ = policy.decide(snapshot(/*queued_lines*/ 9, /*oldest_age_ms*/ 10), now);
assert_eq!(policy.mode(), ChunkingMode::CatchUp);
let decision = policy.decide(
@@ -397,7 +406,7 @@ mod tests {
let mut policy = AdaptiveChunkingPolicy::default();
let t0 = Instant::now();
let entered = policy.decide(snapshot(8, 20), t0);
let entered = policy.decide(snapshot(/*queued_lines*/ 8, /*oldest_age_ms*/ 20), t0);
assert_eq!(entered.mode, ChunkingMode::CatchUp);
let drained = policy.decide(
@@ -409,11 +418,17 @@ mod tests {
);
assert_eq!(drained.mode, ChunkingMode::Smooth);
let held = policy.decide(snapshot(8, 20), t0 + Duration::from_millis(120));
let held = policy.decide(
snapshot(/*queued_lines*/ 8, /*oldest_age_ms*/ 20),
t0 + Duration::from_millis(120),
);
assert_eq!(held.mode, ChunkingMode::Smooth);
assert_eq!(held.drain_plan, DrainPlan::Single);
let reentered = policy.decide(snapshot(8, 20), t0 + Duration::from_millis(320));
let reentered = policy.decide(
snapshot(/*queued_lines*/ 8, /*oldest_age_ms*/ 20),
t0 + Duration::from_millis(320),
);
assert_eq!(reentered.mode, ChunkingMode::CatchUp);
assert_eq!(reentered.drain_plan, DrainPlan::Batch(8));
}
@@ -423,7 +438,7 @@ mod tests {
let mut policy = AdaptiveChunkingPolicy::default();
let t0 = Instant::now();
let _ = policy.decide(snapshot(8, 20), t0);
let _ = policy.decide(snapshot(/*queued_lines*/ 8, /*oldest_age_ms*/ 20), t0);
let _ = policy.decide(
QueueSnapshot {
queued_lines: 0,
@@ -432,7 +447,10 @@ mod tests {
t0 + Duration::from_millis(20),
);
let severe = policy.decide(snapshot(64, 20), t0 + Duration::from_millis(120));
let severe = policy.decide(
snapshot(/*queued_lines*/ 64, /*oldest_age_ms*/ 20),
t0 + Duration::from_millis(120),
);
assert_eq!(severe.mode, ChunkingMode::CatchUp);
assert_eq!(severe.drain_plan, DrainPlan::Batch(64));
}
@@ -271,7 +271,7 @@ mod tests {
#[tokio::test]
async fn controller_loose_vs_tight_with_commit_ticks_matches_full() {
let mut ctrl = StreamController::new(None, &test_cwd());
let mut ctrl = StreamController::new(/*width*/ None, &test_cwd());
let mut lines = Vec::new();
// Exact deltas from the session log (section: Loose vs. tight list items)
@@ -370,7 +370,12 @@ mod tests {
let source: String = deltas.iter().copied().collect();
let mut rendered: Vec<ratatui::text::Line<'static>> = Vec::new();
let test_cwd = test_cwd();
crate::markdown::append_markdown(&source, None, Some(test_cwd.as_path()), &mut rendered);
crate::markdown::append_markdown(
&source,
/*width*/ None,
Some(test_cwd.as_path()),
&mut rendered,
);
let rendered_strs = lines_to_plain_strings(&rendered);
assert_eq!(streamed, rendered_strs);
+2 -2
View File
@@ -116,10 +116,10 @@ mod tests {
#[test]
fn drain_n_clamps_to_available_lines() {
let mut state = StreamState::new(None, &test_cwd());
let mut state = StreamState::new(/*width*/ None, &test_cwd());
state.enqueue(vec![Line::from("one")]);
let drained = state.drain_n(8);
let drained = state.drain_n(/*max_lines*/ 8);
assert_eq!(drained, vec![Line::from("one")]);
assert!(state.is_idle());
}
+16 -16
View File
@@ -362,80 +362,80 @@ mod tests {
#[test]
fn test_truncate_text() {
let text = "Hello, world!";
let truncated = truncate_text(text, 8);
let truncated = truncate_text(text, /*max_graphemes*/ 8);
assert_eq!(truncated, "Hello...");
}
#[test]
fn test_truncate_empty_string() {
let text = "";
let truncated = truncate_text(text, 5);
let truncated = truncate_text(text, /*max_graphemes*/ 5);
assert_eq!(truncated, "");
}
#[test]
fn test_truncate_max_graphemes_zero() {
let text = "Hello";
let truncated = truncate_text(text, 0);
let truncated = truncate_text(text, /*max_graphemes*/ 0);
assert_eq!(truncated, "");
}
#[test]
fn test_truncate_max_graphemes_one() {
let text = "Hello";
let truncated = truncate_text(text, 1);
let truncated = truncate_text(text, /*max_graphemes*/ 1);
assert_eq!(truncated, "H");
}
#[test]
fn test_truncate_max_graphemes_two() {
let text = "Hello";
let truncated = truncate_text(text, 2);
let truncated = truncate_text(text, /*max_graphemes*/ 2);
assert_eq!(truncated, "He");
}
#[test]
fn test_truncate_max_graphemes_three_boundary() {
let text = "Hello";
let truncated = truncate_text(text, 3);
let truncated = truncate_text(text, /*max_graphemes*/ 3);
assert_eq!(truncated, "...");
}
#[test]
fn test_truncate_text_shorter_than_limit() {
let text = "Hi";
let truncated = truncate_text(text, 10);
let truncated = truncate_text(text, /*max_graphemes*/ 10);
assert_eq!(truncated, "Hi");
}
#[test]
fn test_truncate_text_exact_length() {
let text = "Hello";
let truncated = truncate_text(text, 5);
let truncated = truncate_text(text, /*max_graphemes*/ 5);
assert_eq!(truncated, "Hello");
}
#[test]
fn test_truncate_emoji() {
let text = "👋🌍🚀✨💫";
let truncated = truncate_text(text, 3);
let truncated = truncate_text(text, /*max_graphemes*/ 3);
assert_eq!(truncated, "...");
let truncated_longer = truncate_text(text, 4);
let truncated_longer = truncate_text(text, /*max_graphemes*/ 4);
assert_eq!(truncated_longer, "👋...");
}
#[test]
fn test_truncate_unicode_combining_characters() {
let text = "é́ñ̃"; // Characters with combining marks
let truncated = truncate_text(text, 2);
let truncated = truncate_text(text, /*max_graphemes*/ 2);
assert_eq!(truncated, "é́ñ̃");
}
#[test]
fn test_truncate_very_long_text() {
let text = "a".repeat(1000);
let truncated = truncate_text(&text, 10);
let truncated = truncate_text(&text, /*max_graphemes*/ 10);
assert_eq!(truncated, "aaaaaaa...");
assert_eq!(truncated.len(), 10); // 7 'a's + 3 dots
}
@@ -461,7 +461,7 @@ mod tests {
fn test_center_truncate_doesnt_truncate_short_path() {
let sep = std::path::MAIN_SEPARATOR;
let path = format!("{sep}Users{sep}codex{sep}Public");
let truncated = center_truncate_path(&path, 40);
let truncated = center_truncate_path(&path, /*max_width*/ 40);
assert_eq!(truncated, path);
}
@@ -470,7 +470,7 @@ mod tests {
fn test_center_truncate_truncates_long_path() {
let sep = std::path::MAIN_SEPARATOR;
let path = format!("~{sep}hello{sep}the{sep}fox{sep}is{sep}very{sep}fast");
let truncated = center_truncate_path(&path, 24);
let truncated = center_truncate_path(&path, /*max_width*/ 24);
assert_eq!(
truncated,
@@ -484,7 +484,7 @@ mod tests {
let path = format!(
"C:{sep}Users{sep}codex{sep}Projects{sep}super{sep}long{sep}windows{sep}path{sep}file.txt"
);
let truncated = center_truncate_path(&path, 36);
let truncated = center_truncate_path(&path, /*max_width*/ 36);
let expected = format!("C:{sep}Users{sep}codex{sep}{sep}path{sep}file.txt");
@@ -495,7 +495,7 @@ mod tests {
fn test_center_truncate_handles_long_segment() {
let sep = std::path::MAIN_SEPARATOR;
let path = format!("~{sep}supercalifragilisticexpialidocious");
let truncated = center_truncate_path(&path, 18);
let truncated = center_truncate_path(&path, /*max_width*/ 18);
assert_eq!(truncated, format!("~{sep}…cexpialidocious"));
}
+23 -6
View File
@@ -476,7 +476,9 @@ mod tests {
#[test]
fn theme_picker_uses_half_width_with_stacked_fallback_preview() {
let params = build_theme_picker_params(None, None, None);
let params = build_theme_picker_params(
/*current_name*/ None, /*codex_home*/ None, /*terminal_width*/ None,
);
assert_eq!(params.side_content_width, SideContentWidth::Half);
assert_eq!(params.side_content_min_width, WIDE_PREVIEW_MIN_WIDTH);
assert!(params.stacked_side_content.is_some());
@@ -484,7 +486,9 @@ mod tests {
#[test]
fn theme_picker_items_include_search_values_for_preview_mapping() {
let params = build_theme_picker_params(None, None, None);
let params = build_theme_picker_params(
/*current_name*/ None, /*codex_home*/ None, /*terminal_width*/ None,
);
assert!(
params.items.iter().all(|item| item.search_value.is_some()),
"theme picker preview mapping relies on item search_value to stay aligned with final item order"
@@ -493,7 +497,11 @@ mod tests {
#[test]
fn wide_preview_renders_all_lines_with_vertical_center_and_left_inset() {
let lines = render_lines(&ThemePreviewWideRenderable, 80, 20);
let lines = render_lines(
&ThemePreviewWideRenderable,
/*width*/ 80,
/*height*/ 20,
);
let numbered_rows: Vec<usize> = lines
.iter()
.enumerate()
@@ -539,7 +547,11 @@ mod tests {
#[test]
fn narrow_preview_renders_single_add_and_single_remove_in_four_lines() {
let lines = render_lines(&ThemePreviewNarrowRenderable, 80, 6);
let lines = render_lines(
&ThemePreviewNarrowRenderable,
/*width*/ 80,
/*height*/ 6,
);
let numbered_lines: Vec<usize> = lines
.iter()
.filter_map(|line| preview_line_number(line))
@@ -606,7 +618,8 @@ mod tests {
#[test]
fn subtitle_falls_back_to_preview_instructions_without_tilde_path() {
let subtitle = theme_picker_subtitle(None, None);
let subtitle =
theme_picker_subtitle(/*codex_home*/ None, /*terminal_width*/ None);
assert_eq!(subtitle, PREVIEW_FALLBACK_SUBTITLE);
}
@@ -623,7 +636,11 @@ mod tests {
#[test]
fn unavailable_configured_theme_falls_back_to_configured_or_default_selection() {
let configured_or_default_theme = highlight::configured_theme_name();
let params = build_theme_picker_params(Some("not-a-real-theme"), None, Some(120));
let params = build_theme_picker_params(
Some("not-a-real-theme"),
/*codex_home*/ None,
Some(120),
);
let selected_idx = params
.initial_selected_idx
.expect("expected selected index for active fallback theme");
+4 -2
View File
@@ -291,7 +291,9 @@ mod tests {
let mut seen = std::collections::BTreeSet::new();
for seed in 0..32 {
let mut rng = StdRng::seed_from_u64(seed);
seen.insert(pick_paid_tooltip(&mut rng, false));
seen.insert(pick_paid_tooltip(
&mut rng, /*fast_mode_enabled*/ false,
));
}
let expected = std::collections::BTreeSet::from([paid_app_tooltip(), FAST_TOOLTIP]);
@@ -303,7 +305,7 @@ mod tests {
let mut seen = std::collections::BTreeSet::new();
for seed in 0..8 {
let mut rng = StdRng::seed_from_u64(seed);
seen.insert(pick_paid_tooltip(&mut rng, true));
seen.insert(pick_paid_tooltip(&mut rng, /*fast_mode_enabled*/ true));
}
let expected = std::collections::BTreeSet::from([paid_app_tooltip()]);
+24 -9
View File
@@ -68,32 +68,47 @@ mod tests {
#[test]
fn detects_update_action_without_env_mutation() {
assert_eq!(
detect_update_action(false, std::path::Path::new("/any/path"), false, false),
detect_update_action(
/*is_macos*/ false,
std::path::Path::new("/any/path"),
/*managed_by_npm*/ false,
/*managed_by_bun*/ false
),
None
);
assert_eq!(
detect_update_action(false, std::path::Path::new("/any/path"), true, false),
detect_update_action(
/*is_macos*/ false,
std::path::Path::new("/any/path"),
/*managed_by_npm*/ true,
/*managed_by_bun*/ false
),
Some(UpdateAction::NpmGlobalLatest)
);
assert_eq!(
detect_update_action(false, std::path::Path::new("/any/path"), false, true),
detect_update_action(
/*is_macos*/ false,
std::path::Path::new("/any/path"),
/*managed_by_npm*/ false,
/*managed_by_bun*/ true
),
Some(UpdateAction::BunGlobalLatest)
);
assert_eq!(
detect_update_action(
true,
/*is_macos*/ true,
std::path::Path::new("/opt/homebrew/bin/codex"),
false,
false
/*managed_by_npm*/ false,
/*managed_by_bun*/ false
),
Some(UpdateAction::BrewUpgrade)
);
assert_eq!(
detect_update_action(
true,
/*is_macos*/ true,
std::path::Path::new("/usr/local/bin/codex"),
false,
false
/*managed_by_npm*/ false,
/*managed_by_bun*/ false
),
Some(UpdateAction::BrewUpgrade)
);
+4 -1
View File
@@ -881,7 +881,10 @@ mod tests {
#[test]
fn convert_pcm16_downmixes_and_resamples_for_model_input() {
let input = vec![100, 300, 200, 400, 500, 700, 600, 800];
let converted = convert_pcm16(&input, 48_000, 2, 24_000, 1);
let converted = convert_pcm16(
&input, /*input_sample_rate*/ 48_000, /*input_channels*/ 2,
/*output_sample_rate*/ 24_000, /*output_channels*/ 1,
);
assert_eq!(converted, vec![200, 700]);
}
+31 -31
View File
@@ -898,7 +898,7 @@ mod tests {
#[test]
fn trivial_unstyled_no_indents_wide_width() {
let line = Line::from("hello");
let out = word_wrap_line(&line, 10);
let out = word_wrap_line(&line, /*width_or_options*/ 10);
assert_eq!(out.len(), 1);
assert_eq!(concat_line(&out[0]), "hello");
}
@@ -906,7 +906,7 @@ mod tests {
#[test]
fn simple_unstyled_wrap_narrow_width() {
let line = Line::from("hello world");
let out = word_wrap_line(&line, 5);
let out = word_wrap_line(&line, /*width_or_options*/ 5);
assert_eq!(out.len(), 2);
assert_eq!(concat_line(&out[0]), "hello");
assert_eq!(concat_line(&out[1]), "world");
@@ -915,7 +915,7 @@ mod tests {
#[test]
fn simple_styled_wrap_preserves_styles() {
let line = Line::from(vec!["hello ".red(), "world".into()]);
let out = word_wrap_line(&line, 6);
let out = word_wrap_line(&line, /*width_or_options*/ 6);
assert_eq!(out.len(), 2);
// First line should carry the red style
assert_eq!(concat_line(&out[0]), "hello");
@@ -929,7 +929,7 @@ mod tests {
#[test]
fn with_initial_and_subsequent_indents() {
let opts = RtOptions::new(8)
let opts = RtOptions::new(/*width*/ 8)
.initial_indent(Line::from("- "))
.subsequent_indent(Line::from(" "));
let line = Line::from("hello world foo");
@@ -946,7 +946,7 @@ mod tests {
#[test]
fn empty_initial_indent_subsequent_spaces() {
let opts = RtOptions::new(8)
let opts = RtOptions::new(/*width*/ 8)
.initial_indent(Line::from(""))
.subsequent_indent(Line::from(" "));
let line = Line::from("hello world foobar");
@@ -960,7 +960,7 @@ mod tests {
#[test]
fn empty_input_yields_single_empty_line() {
let line = Line::from("");
let out = word_wrap_line(&line, 10);
let out = word_wrap_line(&line, /*width_or_options*/ 10);
assert_eq!(out.len(), 1);
assert_eq!(concat_line(&out[0]), "");
}
@@ -968,7 +968,7 @@ mod tests {
#[test]
fn leading_spaces_preserved_on_first_line() {
let line = Line::from(" hello");
let out = word_wrap_line(&line, 8);
let out = word_wrap_line(&line, /*width_or_options*/ 8);
assert_eq!(out.len(), 1);
assert_eq!(concat_line(&out[0]), " hello");
}
@@ -976,7 +976,7 @@ mod tests {
#[test]
fn multiple_spaces_between_words_dont_start_next_line_with_spaces() {
let line = Line::from("hello world");
let out = word_wrap_line(&line, 8);
let out = word_wrap_line(&line, /*width_or_options*/ 8);
assert_eq!(out.len(), 2);
assert_eq!(concat_line(&out[0]), "hello");
assert_eq!(concat_line(&out[1]), "world");
@@ -984,7 +984,7 @@ mod tests {
#[test]
fn break_words_false_allows_overflow_for_long_word() {
let opts = RtOptions::new(5).break_words(false);
let opts = RtOptions::new(/*width*/ 5).break_words(/*break_words*/ false);
let line = Line::from("supercalifragilistic");
let out = word_wrap_line(&line, opts);
assert_eq!(out.len(), 1);
@@ -994,7 +994,7 @@ mod tests {
#[test]
fn hyphen_splitter_breaks_at_hyphen() {
let line = Line::from("hello-world");
let out = word_wrap_line(&line, 7);
let out = word_wrap_line(&line, /*width_or_options*/ 7);
assert_eq!(out.len(), 2);
assert_eq!(concat_line(&out[0]), "hello-");
assert_eq!(concat_line(&out[1]), "world");
@@ -1002,7 +1002,7 @@ mod tests {
#[test]
fn indent_consumes_width_leaving_one_char_space() {
let opts = RtOptions::new(4)
let opts = RtOptions::new(/*width*/ 4)
.initial_indent(Line::from(">>>>"))
.subsequent_indent(Line::from("--"));
let line = Line::from("hello");
@@ -1016,7 +1016,7 @@ mod tests {
#[test]
fn wide_unicode_wraps_by_display_width() {
let line = Line::from("😀😀😀");
let out = word_wrap_line(&line, 4);
let out = word_wrap_line(&line, /*width_or_options*/ 4);
assert_eq!(out.len(), 2);
assert_eq!(concat_line(&out[0]), "😀😀");
assert_eq!(concat_line(&out[1]), "😀");
@@ -1026,7 +1026,7 @@ mod tests {
fn styled_split_within_span_preserves_style() {
use ratatui::style::Stylize;
let line = Line::from(vec!["abcd".red()]);
let out = word_wrap_line(&line, 2);
let out = word_wrap_line(&line, /*width_or_options*/ 2);
assert_eq!(out.len(), 2);
assert_eq!(out[0].spans.len(), 1);
assert_eq!(out[1].spans.len(), 1);
@@ -1038,7 +1038,7 @@ mod tests {
#[test]
fn wrap_lines_applies_initial_indent_only_once() {
let opts = RtOptions::new(8)
let opts = RtOptions::new(/*width*/ 8)
.initial_indent(Line::from("- "))
.subsequent_indent(Line::from(" "));
@@ -1057,14 +1057,14 @@ mod tests {
#[test]
fn wrap_lines_without_indents_is_concat_of_single_wraps() {
let lines = vec![Line::from("hello"), Line::from("world!")];
let out = word_wrap_lines(lines, 10);
let out = word_wrap_lines(lines, /*width_or_options*/ 10);
let rendered: Vec<String> = out.iter().map(concat_line).collect();
assert_eq!(rendered, vec!["hello", "world!"]);
}
#[test]
fn wrap_lines_borrowed_applies_initial_indent_only_once() {
let opts = RtOptions::new(8)
let opts = RtOptions::new(/*width*/ 8)
.initial_indent(Line::from("- "))
.subsequent_indent(Line::from(" "));
@@ -1081,7 +1081,7 @@ mod tests {
#[test]
fn wrap_lines_borrowed_without_indents_is_concat_of_single_wraps() {
let lines = [Line::from("hello"), Line::from("world!")];
let out = word_wrap_lines_borrowed(lines.iter(), 10);
let out = word_wrap_lines_borrowed(lines.iter(), /*width_or_options*/ 10);
let rendered: Vec<String> = out.iter().map(concat_line).collect();
assert_eq!(rendered, vec!["hello", "world!"]);
}
@@ -1089,7 +1089,7 @@ mod tests {
#[test]
fn wrap_lines_accepts_borrowed_iterators() {
let lines = [Line::from("hello world"), Line::from("foo bar baz")];
let out = word_wrap_lines(lines, 10);
let out = word_wrap_lines(lines, /*width_or_options*/ 10);
let rendered: Vec<String> = out.iter().map(concat_line).collect();
assert_eq!(rendered, vec!["hello", "world", "foo bar", "baz"]);
}
@@ -1097,7 +1097,7 @@ mod tests {
#[test]
fn wrap_lines_accepts_str_slices() {
let lines = ["hello world", "goodnight moon"];
let out = word_wrap_lines(lines, 12);
let out = word_wrap_lines(lines, /*width_or_options*/ 12);
let rendered: Vec<String> = out.iter().map(concat_line).collect();
assert_eq!(rendered, vec!["hello world", "goodnight", "moon"]);
}
@@ -1105,9 +1105,9 @@ mod tests {
#[test]
fn line_height_counts_double_width_emoji() {
let line = "😀😀😀".into(); // each emoji ~ width 2
assert_eq!(word_wrap_line(&line, 4).len(), 2);
assert_eq!(word_wrap_line(&line, 2).len(), 3);
assert_eq!(word_wrap_line(&line, 6).len(), 1);
assert_eq!(word_wrap_line(&line, /*width_or_options*/ 4).len(), 2);
assert_eq!(word_wrap_line(&line, /*width_or_options*/ 2).len(), 3);
assert_eq!(word_wrap_line(&line, /*width_or_options*/ 6).len(), 1);
}
#[test]
@@ -1116,7 +1116,7 @@ mod tests {
let line = Line::from(sample);
let lines = [line];
// Force small width to exercise wrapping at spaces.
let wrapped = word_wrap_lines_borrowed(&lines, 40);
let wrapped = word_wrap_lines_borrowed(&lines, /*width_or_options*/ 40);
let joined: String = wrapped.iter().map(ToString::to_string).join("\n");
assert_eq!(
joined,
@@ -1134,10 +1134,10 @@ them."#
let line = Line::from(
"http://example.com/long-url-with-dashes-wider-than-terminal-window/blah-blah-blah-text/more-gibberish-text",
);
let opts = RtOptions::new(24)
let opts = RtOptions::new(/*width*/ 24)
.word_separator(textwrap::WordSeparator::AsciiSpace)
.word_splitter(textwrap::WordSplitter::NoHyphenation)
.break_words(false);
.break_words(/*break_words*/ false);
let out = word_wrap_line(&line, opts);
@@ -1229,7 +1229,7 @@ them."#
#[test]
fn adaptive_wrap_line_keeps_long_url_like_token_intact() {
let line = Line::from("example.test/a-very-long-path-with-many-segments-and-query?x=1&y=2");
let out = adaptive_wrap_line(&line, RtOptions::new(20));
let out = adaptive_wrap_line(&line, RtOptions::new(/*width*/ 20));
assert_eq!(out.len(), 1);
assert_eq!(
concat_line(&out[0]),
@@ -1240,7 +1240,7 @@ them."#
#[test]
fn adaptive_wrap_line_preserves_default_behavior_for_non_url_tokens() {
let line = Line::from("a_very_long_token_without_spaces_to_force_wrapping");
let out = adaptive_wrap_line(&line, RtOptions::new(20));
let out = adaptive_wrap_line(&line, RtOptions::new(/*width*/ 20));
assert!(
out.len() > 1,
"expected non-url token to wrap with default options"
@@ -1251,7 +1251,7 @@ them."#
fn adaptive_wrap_line_mixed_line_wraps_long_non_url_token() {
let long_non_url = "a_very_long_token_without_spaces_to_force_wrapping";
let line = Line::from(format!("see https://ex.com {long_non_url}"));
let out = adaptive_wrap_line(&line, RtOptions::new(24));
let out = adaptive_wrap_line(&line, RtOptions::new(/*width*/ 24));
assert!(
out.iter()
@@ -1269,7 +1269,7 @@ them."#
fn map_owned_wrapped_line_to_range_recovers_on_non_prefix_mismatch() {
// Match source chars first, then introduce a non-penalty mismatch.
// The function should recover and return the mapped prefix range.
let range = map_owned_wrapped_line_to_range("hello world", 0, "helloX", "");
let range = map_owned_wrapped_line_to_range("hello world", /*cursor*/ 0, "helloX", "");
assert_eq!(range, 0..5);
}
@@ -1285,7 +1285,7 @@ them."#
let text = "- item one and some more words";
// Simulate what textwrap would produce for the first continuation line
// when subsequent_indent = "- ": it prepends "- " to the source slice.
let range = map_owned_wrapped_line_to_range(text, 0, "- - item one", "- ");
let range = map_owned_wrapped_line_to_range(text, /*cursor*/ 0, "- - item one", "- ");
// The mapper should skip the synthetic "- " prefix and map "- item one"
// back to source bytes 0..10.
assert_eq!(range, 0..10);
@@ -1330,7 +1330,7 @@ them."#
panic!("expected at least one wrapped line");
};
let mapped = map_owned_wrapped_line_to_range(text, 0, line.as_ref(), "- ");
let mapped = map_owned_wrapped_line_to_range(text, /*cursor*/ 0, line.as_ref(), "- ");
let expected_len = line
.as_ref()
.strip_prefix("- ")