use insta::assert_snapshot;
use ratatui::{Terminal, backend::TestBackend};
use vtcode_core::ui::{
InlineHeaderContext, InlineMessageKind, InlineSegment, InlineTextStyle, InlineTheme,
SessionOptions, spawn_session_with_options,
};
#[tokio::test]
async fn test_visual_user_agent_exchange() {
let backend = TestBackend::new(80, 20);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let _area = f.area();
assert!(_area.width == 80);
assert!(_area.height == 20);
})
.unwrap();
assert_snapshot!(
"visual_simple_exchange_initial",
format!("{}", terminal.backend())
);
let session = spawn_session_with_options(
InlineTheme::default(),
SessionOptions {
placeholder: Some("Ask me anything...".to_string()),
inline_rows: 12,
..SessionOptions::default()
},
);
if let Ok(sess) = session {
sess.handle.append_line(
InlineMessageKind::User,
vec![InlineSegment {
text: "Explain how bubble sort works".to_string(),
style: InlineTextStyle::default().into(),
}],
);
sess.handle.append_line(
InlineMessageKind::Agent,
vec![InlineSegment {
text: "Bubble sort is a simple sorting algorithm that repeatedly steps through the list, compares adjacent elements and swaps them if they are in the wrong order. The pass through the list is repeated until the list is sorted.".to_string(),
style: InlineTextStyle::default().into(),
}],
);
}
terminal
.draw(|f| {
let _area = f.area();
})
.unwrap();
assert_snapshot!(
"visual_simple_exchange_final",
format!("{}", terminal.backend())
);
}
#[tokio::test]
async fn test_visual_code_rendering() {
let backend = TestBackend::new(100, 25);
let mut terminal = Terminal::new(backend).unwrap();
let session = spawn_session_with_options(
InlineTheme::default(),
SessionOptions {
placeholder: Some("Enter code to analyze...".to_string()),
inline_rows: 15,
..SessionOptions::default()
},
);
if let Ok(sess) = session {
sess.handle.append_line(
InlineMessageKind::User,
vec![InlineSegment {
text: "Show me a Rust function to reverse a string".to_string(),
style: InlineTextStyle::default().into(),
}],
);
sess.handle.append_line(
InlineMessageKind::Agent,
vec![InlineSegment {
text: "Here's a Rust function to reverse a string:\n\n```rust\nfn reverse_string(s: &str) -> String {\n s.chars().rev().collect()\n}\n\nfn main() {\n let original = \"hello\";\n let reversed = reverse_string(original);\n println!(\"{} -> {}\", original, reversed);\n}\n```\n\nThis function works by converting the string to characters, reversing the iterator, and collecting back into a String.".to_string(),
style: InlineTextStyle::default().into(),
}],
);
}
terminal
.draw(|f| {
let _area = f.area();
assert!(_area.width == 100);
assert!(_area.height == 25);
})
.unwrap();
assert_snapshot!("visual_code_rendering", format!("{}", terminal.backend()));
}
#[tokio::test]
async fn test_visual_tool_output() {
let backend = TestBackend::new(80, 20);
let mut terminal = Terminal::new(backend).unwrap();
let session = spawn_session_with_options(
InlineTheme::default(),
SessionOptions {
placeholder: Some("Enter command...".to_string()),
inline_rows: 10,
..SessionOptions::default()
},
);
if let Ok(sess) = session {
sess.handle.append_line(
InlineMessageKind::User,
vec![InlineSegment {
text: "List files in current directory".to_string(),
style: InlineTextStyle::default().into(),
}],
);
sess.handle.append_line(
InlineMessageKind::Tool,
vec![InlineSegment {
text: "run_pty_cmd([\"ls\", \"-la\"])".to_string(),
style: InlineTextStyle::default().into(),
}],
);
sess.handle.append_line(
InlineMessageKind::Pty,
vec![InlineSegment {
text: "total 48\ndrwxr-xr-x 10 user staff 320 Nov 1 10:30 .\ndrwxr-xr-x 5 user staff 160 Nov 1 10:25 ..\n-rw-r--r-- 1 user staff 156 Nov 1 10:20 Cargo.toml\n-rw-r--r-- 1 user staff 368 Nov 1 10:25 README.md\ndrwxr-xr-x 3 user staff 96 Nov 1 10:20 src/\n".to_string(),
style: InlineTextStyle::default().into(),
}],
);
sess.handle.append_line(
InlineMessageKind::Agent,
vec![InlineSegment {
text: "I've listed the files in the current directory. You have Cargo.toml, README.md, and a src/ directory.".to_string(),
style: InlineTextStyle::default().into(),
}],
);
}
terminal
.draw(|f| {
let _area = f.area();
assert!(_area.width == 80);
assert!(_area.height == 20);
})
.unwrap();
assert_snapshot!("visual_tool_output", format!("{}", terminal.backend()));
}
#[tokio::test]
async fn test_visual_error_handling() {
let backend = TestBackend::new(80, 20);
let mut terminal = Terminal::new(backend).unwrap();
let session = spawn_session_with_options(
InlineTheme::default(),
SessionOptions {
placeholder: Some("Enter command (errors possible)...".to_string()),
inline_rows: 12,
..SessionOptions::default()
},
);
if let Ok(sess) = session {
sess.handle.append_line(
InlineMessageKind::User,
vec![InlineSegment {
text: "Run command that might fail".to_string(),
style: InlineTextStyle::default().into(),
}],
);
sess.handle.append_line(
InlineMessageKind::Tool,
vec![InlineSegment {
text: "run_pty_cmd([\"nonexistent-command\", \"--help\"])".to_string(),
style: InlineTextStyle::default().into(),
}],
);
sess.handle.append_line(
InlineMessageKind::Error,
vec![InlineSegment {
text: "Error: Command 'nonexistent-command' not found. Make sure the command is installed and in your PATH.".to_string(),
style: InlineTextStyle::default().into(),
}],
);
sess.handle.append_line(
InlineMessageKind::Agent,
vec![InlineSegment {
text: "I encountered an error running that command. The command 'nonexistent-command' doesn't appear to be available on your system. Would you like me to help you find an alternative?".to_string(),
style: InlineTextStyle::default().into(),
}],
);
}
terminal
.draw(|f| {
let _area = f.area();
assert!(_area.width == 80);
assert!(_area.height == 20);
})
.unwrap();
assert_snapshot!("visual_error_handling", format!("{}", terminal.backend()));
}
#[tokio::test]
async fn test_visual_header_variations() {
let contexts_and_snapshots = vec![
(
"openai_gpt5",
InlineHeaderContext {
provider: "openai".to_string(),
model: "gpt-oss-20b".to_string(),
mode: "interactive".to_string(),
reasoning: "creative".to_string(),
..Default::default()
},
),
(
"anthropic_claude",
InlineHeaderContext {
provider: "anthropic".to_string(),
model: "claude-3".to_string(),
mode: "full-auto".to_string(),
reasoning: "analytical".to_string(),
..Default::default()
},
),
(
"local_llama",
InlineHeaderContext {
provider: "local".to_string(),
model: "llama3".to_string(),
mode: "manual".to_string(),
reasoning: "precise".to_string(),
..Default::default()
},
),
];
for (name, context) in contexts_and_snapshots {
let backend = TestBackend::new(80, 15);
let mut terminal = Terminal::new(backend).unwrap();
let session = spawn_session_with_options(
InlineTheme::default(),
SessionOptions {
placeholder: Some("Working...".to_string()),
inline_rows: 8,
..SessionOptions::default()
},
);
if let Ok(sess) = session {
sess.handle.set_header_context(context);
sess.handle.append_line(
InlineMessageKind::Agent,
vec![InlineSegment {
text: format!("Session initialized with {} context", name),
style: InlineTextStyle::default().into(),
}],
);
}
terminal
.draw(|f| {
let _area = f.area();
assert!(_area.width == 80);
assert!(_area.height == 15);
})
.unwrap();
assert_snapshot!(
format!("visual_header_{}", name),
format!("{}", terminal.backend())
);
}
}