Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
@@ -0,0 +1,498 @@
use super::SlashCommandEntryState;
use crate::report_if_error;
use crate::search::slash_command_menu::static_commands::commands;
use crate::settings::AISettings;
use crate::terminal::input::tests::{add_window_with_bootstrapped_terminal, initialize_app};
use settings::Setting as _;
use warpui::{App, SingletonEntity as _};
#[test]
fn test_parse_slash_command_handles_argument_rules() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(
&mut app, None, /* history_file_commands */
None,
)
.await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
let slash_command_data_source =
input.read(&app, |input, _| input.slash_command_data_source.clone());
slash_command_data_source.read(&app, |data_source, _| {
let no_argument_command_name = data_source
.active_commands()
.find_map(|(_, command)| command.argument.is_none().then_some(command.name))
.expect("expected at least one slash command without an argument");
let optional_argument_command_name = data_source
.active_commands()
.find_map(|(_, command)| {
(command.argument.is_some()
&& data_source.parse_slash_command(command.name).is_some())
.then_some(command.name)
})
.expect("expected at least one slash command that accepts optional arguments");
let with_extra_text = format!("{no_argument_command_name} trailing");
assert!(
data_source.parse_slash_command(&with_extra_text).is_none(),
"commands without arguments should reject non-whitespace suffixes"
);
let with_whitespace = format!("{no_argument_command_name} ");
let detected_with_whitespace = data_source
.parse_slash_command(&with_whitespace)
.expect("command with whitespace-only suffix should still be parsed");
assert_eq!(
detected_with_whitespace.command.name,
no_argument_command_name
);
let detected_without_argument = data_source
.parse_slash_command(optional_argument_command_name)
.expect("optional-argument command should parse without an argument");
assert_eq!(detected_without_argument.argument, None);
let with_argument = format!("{optional_argument_command_name} prompt");
let detected_with_argument = data_source
.parse_slash_command(&with_argument)
.expect("optional-argument command should parse with an argument");
assert_eq!(detected_with_argument.argument.as_deref(), Some("prompt"));
});
});
}
#[test]
fn test_parse_rename_tab_slash_command_arguments() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(
&mut app, None, /* history_file_commands */
None,
)
.await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
let slash_command_data_source =
input.read(&app, |input, _| input.slash_command_data_source.clone());
slash_command_data_source.read(&app, |data_source, _| {
assert!(
data_source
.parse_slash_command(commands::RENAME_TAB.name)
.is_none(),
"expected /rename-tab without an argument not to parse"
);
let detected_with_argument = data_source
.parse_slash_command("/rename-tab Backend")
.expect("expected /rename-tab to parse with an argument");
assert_eq!(detected_with_argument.argument.as_deref(), Some("Backend"));
let detected_with_multi_word_argument = data_source
.parse_slash_command("/rename-tab Backend API")
.expect("expected /rename-tab to preserve the rest of the line as one argument");
assert_eq!(
detected_with_multi_word_argument.argument.as_deref(),
Some("Backend API")
);
let detected_with_empty_argument = data_source
.parse_slash_command("/rename-tab ")
.expect("expected /rename-tab to parse once the required argument is started");
assert_eq!(detected_with_empty_argument.argument.as_deref(), Some(""));
});
});
}
#[test]
fn test_non_ai_commands_remain_active_when_ai_is_disabled() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(
&mut app, None, /* history_file_commands */
None,
)
.await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
let slash_command_data_source =
input.read(&app, |input, _| input.slash_command_data_source.clone());
// Disable AI globally.
AISettings::handle(&app).update(&mut app, |settings, ctx| {
report_if_error!(settings.is_any_ai_enabled.set_value(false, ctx));
});
slash_command_data_source.read(&app, |data_source, _| {
let active_command_names: Vec<&str> = data_source
.active_commands()
.map(|(_, command)| command.name)
.collect();
// Commands that don't require AI should still be active.
// `/rename-tab` is a good canary because it has no session-context requirements
// other than ALWAYS.
assert!(
active_command_names.contains(&commands::RENAME_TAB.name),
"/rename-tab should remain active when AI is off, got: {active_command_names:?}"
);
// Commands that require AI should be filtered out.
assert!(
!active_command_names.contains(&commands::AGENT.name),
"/agent should NOT be active when AI is off, got: {active_command_names:?}"
);
assert!(
!active_command_names.contains(&commands::PLAN.name),
"/plan should NOT be active when AI is off, got: {active_command_names:?}"
);
});
});
}
#[test]
fn test_disabled_until_empty_buffer_ignores_non_slash_edits() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(
&mut app, None, /* history_file_commands */
None,
)
.await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
input.update(&mut app, |input, ctx| {
input.set_input_mode_natural_language_detection(ctx);
input.user_insert("echo hello", ctx);
});
input.update(&mut app, |input, ctx| {
input.slash_command_model.update(ctx, |model, ctx| {
model.disable(ctx);
});
});
input.read(&app, |input, ctx| {
assert!(matches!(
input.slash_command_model.as_ref(ctx).state(),
SlashCommandEntryState::DisabledUntilEmptyBuffer
));
});
input.update(&mut app, |input, ctx| {
input.user_insert(" world", ctx);
});
input.read(&app, |input, ctx| {
assert!(matches!(
input.slash_command_model.as_ref(ctx).state(),
SlashCommandEntryState::DisabledUntilEmptyBuffer
));
});
});
}
#[test]
fn test_disabled_until_empty_buffer_reevaluates_when_slash_is_added_to_start() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(
&mut app, None, /* history_file_commands */
None,
)
.await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
let slash_command_data_source =
input.read(&app, |input, _| input.slash_command_data_source.clone());
let detected_command_name = slash_command_data_source.read(&app, |data_source, _| {
data_source
.active_commands()
.find_map(|(_, command)| {
data_source
.parse_slash_command(command.name)
.is_some()
.then(|| command.name.to_owned())
})
.expect("expected at least one active slash command")
});
input.update(&mut app, |input, ctx| {
input.set_input_mode_natural_language_detection(ctx);
input.user_insert("echo hello", ctx);
});
input.update(&mut app, |input, ctx| {
input.slash_command_model.update(ctx, |model, ctx| {
model.disable(ctx);
});
});
input.update(&mut app, |input, ctx| {
input.user_replace_editor_text(&detected_command_name, ctx);
});
input.read(&app, |input, ctx| {
assert!(
matches!(
input.slash_command_model.as_ref(ctx).state(),
SlashCommandEntryState::SlashCommand(_)
),
"adding '/' at the start of the buffer should re-enable slash command parsing"
);
});
});
}
#[test]
fn test_second_slash_in_command_token_sets_state_to_none() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(
&mut app, None, /* history_file_commands */
None,
)
.await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
input.update(&mut app, |input, ctx| {
input.set_input_mode_natural_language_detection(ctx);
input.user_insert("/foo/bar", ctx);
});
input.read(&app, |input, ctx| {
assert!(matches!(
input.slash_command_model.as_ref(ctx).state(),
SlashCommandEntryState::None
));
});
});
}
#[test]
fn test_detect_command_returns_none_for_plain_text() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(&mut app, None, None).await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
input.read(&app, |input, ctx| {
let result = input
.slash_command_model
.as_ref(ctx)
.detect_command("fix the tests", ctx);
assert!(
matches!(result, SlashCommandEntryState::None),
"plain text should not be detected as a command"
);
});
});
}
#[test]
fn test_detect_command_finds_known_slash_command() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(&mut app, None, None).await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
input.read(&app, |input, ctx| {
let slash_command_data_source = &input.slash_command_data_source;
let command_name = slash_command_data_source
.as_ref(ctx)
.active_commands()
.find_map(|(_, command)| {
slash_command_data_source
.as_ref(ctx)
.parse_slash_command(command.name)
.is_some()
.then(|| command.name.to_owned())
})
.expect("expected at least one active slash command");
let result = input
.slash_command_model
.as_ref(ctx)
.detect_command(&command_name, ctx);
assert!(
matches!(result, SlashCommandEntryState::SlashCommand(_)),
"known command should be detected: {command_name}"
);
});
});
}
#[test]
fn test_detect_command_returns_none_for_unknown_slash() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(&mut app, None, None).await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
input.read(&app, |input, ctx| {
let result = input
.slash_command_model
.as_ref(ctx)
.detect_command("/nonexistent-command-xyz", ctx);
assert!(
matches!(result, SlashCommandEntryState::None),
"unknown /command should return None"
);
});
});
}
#[test]
fn test_detect_command_matches_buffer_driven_detection() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(&mut app, None, None).await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
let command_name = input.read(&app, |input, ctx| {
let ds = &input.slash_command_data_source;
ds.as_ref(ctx)
.active_commands()
.find_map(|(_, command)| {
ds.as_ref(ctx)
.parse_slash_command(command.name)
.is_some()
.then(|| command.name.to_owned())
})
.expect("expected at least one active slash command")
});
// Type the command into the buffer so the buffer-driven model processes it.
input.update(&mut app, |input, ctx| {
input.set_input_mode_natural_language_detection(ctx);
input.user_insert(&command_name, ctx);
});
// Compare the buffer-driven state with the stateless detect_command result.
input.read(&app, |input, ctx| {
let buffer_state = input.slash_command_model.as_ref(ctx).state().clone();
let detect_state = input
.slash_command_model
.as_ref(ctx)
.detect_command(&command_name, ctx);
let buffer_is_slash = matches!(buffer_state, SlashCommandEntryState::SlashCommand(_));
let detect_is_slash = matches!(detect_state, SlashCommandEntryState::SlashCommand(_));
assert_eq!(
buffer_is_slash, detect_is_slash,
"detect_command and buffer-driven detection should agree for '{command_name}'"
);
});
});
}
#[test]
fn test_detect_command_parses_queue_with_argument() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(&mut app, None, None).await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
input.read(&app, |input, ctx| {
let result = input
.slash_command_model
.as_ref(ctx)
.detect_command("/queue fix the tests", ctx);
if let SlashCommandEntryState::SlashCommand(detected) = result {
assert_eq!(detected.command.name, "/queue");
assert_eq!(detected.argument.as_deref(), Some("fix the tests"));
} else {
// /queue may not be registered if the feature flag is off in tests.
// That's fine — this test validates argument extraction when it is.
}
});
});
}
#[test]
fn test_detect_command_returns_queue_with_no_argument() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(&mut app, None, None).await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
input.read(&app, |input, ctx| {
let result = input
.slash_command_model
.as_ref(ctx)
.detect_command("/queue", ctx);
// /queue requires an argument, so bare "/queue" should not be detected.
// (StaticCommand with required argument rejects input without a space-delimited arg.)
assert!(
!matches!(result, SlashCommandEntryState::SlashCommand(_)),
"/queue with no argument should not be detected as a complete slash command"
);
});
});
}
#[test]
fn test_submit_queued_prompt_routes_plain_text_to_conversation() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(&mut app, None, None).await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
// Set up AI input mode so the input can interact with the AI controller.
input.update(&mut app, |input, ctx| {
input.set_input_mode_natural_language_detection(ctx);
});
// submit_queued_prompt with plain text should not panic or crash.
// It routes through detect_command (returning None) and falls through
// to send_user_query_in_new_conversation.
input.update(&mut app, |input, ctx| {
input.submit_queued_prompt("fix the tests".to_string(), ctx);
});
});
}
#[test]
fn test_submit_queued_prompt_detects_slash_command() {
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(&mut app, None, None).await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
input.update(&mut app, |input, ctx| {
input.set_input_mode_natural_language_detection(ctx);
});
// Verify that submit_queued_prompt correctly detects slash commands.
// Find a command that exists and has an optional argument.
let command_with_arg = input.read(&app, |input, ctx| {
let ds = &input.slash_command_data_source;
ds.as_ref(ctx).active_commands().find_map(|(_, command)| {
(command.argument.as_ref().is_some_and(|a| a.is_optional)
&& ds.as_ref(ctx).parse_slash_command(command.name).is_some())
.then(|| command.name.to_owned())
})
});
if let Some(command_text) = command_with_arg {
// submit_queued_prompt should detect the slash command and route through
// execute_slash_command. This should not panic.
input.update(&mut app, |input, ctx| {
input.submit_queued_prompt(command_text, ctx);
});
}
});
}