feat: Add btw alias for side slash command (#23592)

This commit is contained in:
anp-oai
2026-05-20 15:49:35 +00:00
committed by GitHub
parent e9f59e30d9
commit f198ca115b
7 changed files with 279 additions and 16 deletions
+113
View File
@@ -173,6 +173,59 @@ async fn slash_side_is_rejected_during_review_mode() {
);
}
#[tokio::test]
async fn slash_btw_is_rejected_during_review_mode() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.review.is_review_mode = true;
chat.dispatch_command(SlashCommand::Btw);
let event = rx
.try_recv()
.expect("expected review-mode btw conversation error");
match event {
AppEvent::InsertHistoryCell(cell) => {
let rendered = lines_to_single_string(&cell.display_lines(/*width*/ 80));
assert!(
rendered.contains("'/btw' is unavailable while code review is running."),
"expected review-mode btw conversation error, got {rendered:?}"
);
}
other => panic!("expected InsertHistoryCell error, got {other:?}"),
}
assert!(rx.try_recv().is_err(), "expected no follow-up events");
assert!(
op_rx.try_recv().is_err(),
"expected no side conversation op"
);
}
#[tokio::test]
async fn slash_btw_is_rejected_before_the_session_starts() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.dispatch_command(SlashCommand::Btw);
let event = rx
.try_recv()
.expect("expected pre-session btw conversation error");
match event {
AppEvent::InsertHistoryCell(cell) => {
let rendered = lines_to_single_string(&cell.display_lines(/*width*/ 80));
assert!(
rendered.contains("'/btw' is unavailable before the session starts."),
"expected pre-session btw conversation error, got {rendered:?}"
);
}
other => panic!("expected InsertHistoryCell error, got {other:?}"),
}
assert!(rx.try_recv().is_err(), "expected no follow-up events");
assert!(
op_rx.try_recv().is_err(),
"expected no side conversation op"
);
}
#[tokio::test]
async fn submit_user_message_as_plain_user_turn_does_not_run_shell_commands() {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
@@ -219,6 +272,31 @@ async fn slash_side_without_args_starts_empty_side_conversation() {
assert!(chat.input_queue.queued_user_messages.is_empty());
}
#[tokio::test]
async fn slash_btw_without_args_starts_empty_side_conversation() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
let parent_thread_id = ThreadId::new();
chat.thread_id = Some(parent_thread_id);
chat.on_task_started();
chat.bottom_pane
.set_composer_text("/btw".to_string(), Vec::new(), Vec::new());
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
assert_matches!(
rx.try_recv(),
Ok(AppEvent::StartSide {
parent_thread_id: emitted_parent_thread_id,
user_message: None,
}) if emitted_parent_thread_id == parent_thread_id
);
assert!(
op_rx.try_recv().is_err(),
"bare /btw should not submit an op on the parent thread"
);
assert!(chat.input_queue.queued_user_messages.is_empty());
}
#[tokio::test]
async fn slash_side_requests_forked_side_question_while_task_running() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
@@ -268,6 +346,41 @@ async fn slash_side_requests_forked_side_question_while_task_running() {
);
}
#[tokio::test]
async fn slash_btw_requests_forked_side_question_while_task_running() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
let parent_thread_id = ThreadId::new();
chat.thread_id = Some(parent_thread_id);
chat.on_task_started();
chat.bottom_pane.set_composer_text(
"/btw explore the codebase".to_string(),
Vec::new(),
Vec::new(),
);
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
assert_matches!(
rx.try_recv(),
Ok(AppEvent::StartSide {
parent_thread_id: emitted_parent_thread_id,
user_message: Some(user_message),
}) if emitted_parent_thread_id == parent_thread_id
&& user_message
== UserMessage {
text: "explore the codebase".to_string(),
local_images: Vec::new(),
remote_image_urls: Vec::new(),
text_elements: Vec::new(),
mention_bindings: Vec::new(),
}
);
assert!(
op_rx.try_recv().is_err(),
"expected no op on the parent thread"
);
}
#[tokio::test]
async fn side_context_label_preserves_status_line_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;