Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
use super::parse_markdown_into_text_and_code_sections;
|
||||
use crate::ai::agent::{AIAgentTextSection, AgentOutputImageLayout, AgentOutputTableRendering};
|
||||
use crate::features::FeatureFlag;
|
||||
|
||||
#[test]
|
||||
fn extracts_gfm_pipe_table_into_table_section() {
|
||||
let _flag = FeatureFlag::BlocklistMarkdownTableRendering.override_enabled(true);
|
||||
let input = "Intro\n\n| A | B |\n| --- | --- |\n| 1 | 2 |\n\nOutro";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 3);
|
||||
|
||||
match §ions[0] {
|
||||
AIAgentTextSection::PlainText { text } => {
|
||||
assert!(text.text().contains("Intro"));
|
||||
}
|
||||
_ => panic!("expected first section to be PlainText"),
|
||||
}
|
||||
|
||||
match §ions[1] {
|
||||
AIAgentTextSection::Table { table } => {
|
||||
assert_eq!(table.markdown_source, "| A | B |\n| --- | --- |\n| 1 | 2 |");
|
||||
match &table.rendering {
|
||||
AgentOutputTableRendering::Legacy { .. } => {
|
||||
panic!("expected structured table rendering")
|
||||
}
|
||||
AgentOutputTableRendering::Structured { table } => {
|
||||
assert_eq!(table.headers.len(), 2);
|
||||
assert_eq!(table.rows.len(), 1);
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
table.rendered_lines(),
|
||||
vec!["A\tB".to_string(), "1\t2".to_string()]
|
||||
);
|
||||
}
|
||||
_ => panic!("expected second section to be Table"),
|
||||
}
|
||||
|
||||
match §ions[2] {
|
||||
AIAgentTextSection::PlainText { text } => {
|
||||
assert!(text.text().contains("Outro"));
|
||||
}
|
||||
_ => panic!("expected third section to be PlainText"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_extract_pipe_text_without_separator_row() {
|
||||
let _flag = FeatureFlag::BlocklistMarkdownTableRendering.override_enabled(true);
|
||||
let input = "a | b\nc | d";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 1);
|
||||
assert!(matches!(sections[0], AIAgentTextSection::PlainText { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_can_be_followed_immediately_by_text() {
|
||||
let _flag = FeatureFlag::BlocklistMarkdownTableRendering.override_enabled(true);
|
||||
let input = "| A | B |\n|---|---|\n| 1 | 2 |\nAfter";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 2);
|
||||
assert!(matches!(sections[0], AIAgentTextSection::Table { .. }));
|
||||
match §ions[1] {
|
||||
AIAgentTextSection::PlainText { text } => {
|
||||
assert!(text.text().contains("After"));
|
||||
}
|
||||
_ => panic!("expected second section to be PlainText"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_gfm_pipe_table_into_legacy_table_section_when_flag_disabled() {
|
||||
let _flag = FeatureFlag::BlocklistMarkdownTableRendering.override_enabled(false);
|
||||
let input = "Intro\n\n| A | B |\n|---|---|\n| 1 | 2 |\n\nOutro";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 3);
|
||||
|
||||
match §ions[1] {
|
||||
AIAgentTextSection::Table { table } => {
|
||||
assert_eq!(
|
||||
table.markdown_source,
|
||||
"| A | B |\n| --- | --- |\n| 1 | 2 |"
|
||||
);
|
||||
assert_eq!(
|
||||
table.rendered_lines(),
|
||||
vec![
|
||||
"| A | B |".to_string(),
|
||||
"| --- | --- |".to_string(),
|
||||
"| 1 | 2 |".to_string()
|
||||
]
|
||||
);
|
||||
assert!(matches!(
|
||||
&table.rendering,
|
||||
AgentOutputTableRendering::Legacy { .. }
|
||||
));
|
||||
}
|
||||
_ => panic!("expected second section to be Table"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_markdown_image_into_image_section() {
|
||||
let input = "Intro\n\n\n\nOutro";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 3);
|
||||
assert!(matches!(sections[0], AIAgentTextSection::PlainText { .. }));
|
||||
match §ions[1] {
|
||||
AIAgentTextSection::Image { image } => {
|
||||
assert_eq!(image.alt_text, "Diagram");
|
||||
assert_eq!(image.source, "./diagram.png");
|
||||
assert_eq!(image.markdown_source, "");
|
||||
assert_eq!(image.layout, AgentOutputImageLayout::Block);
|
||||
}
|
||||
_ => panic!("expected second section to be Image"),
|
||||
}
|
||||
assert!(matches!(sections[2], AIAgentTextSection::PlainText { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_multiple_markdown_images_in_order() {
|
||||
let input = "\n\n";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 2);
|
||||
match §ions[0] {
|
||||
AIAgentTextSection::Image { image } => {
|
||||
assert_eq!(image.markdown_source, "");
|
||||
assert_eq!(image.layout, AgentOutputImageLayout::Block);
|
||||
}
|
||||
_ => panic!("expected first section to be Image"),
|
||||
}
|
||||
match §ions[1] {
|
||||
AIAgentTextSection::Image { image } => {
|
||||
assert_eq!(image.markdown_source, "");
|
||||
assert_eq!(image.layout, AgentOutputImageLayout::Block);
|
||||
}
|
||||
_ => panic!("expected second section to be Image"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_same_line_markdown_images_into_inline_image_sections() {
|
||||
let input = " \n";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 2);
|
||||
for (section, expected_markdown) in sections.iter().zip(["", ""])
|
||||
{
|
||||
match section {
|
||||
AIAgentTextSection::Image { image } => {
|
||||
assert_eq!(image.markdown_source, expected_markdown);
|
||||
assert_eq!(image.layout, AgentOutputImageLayout::Inline);
|
||||
}
|
||||
_ => panic!("expected inline image section"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_extract_inline_image_run_from_mixed_text_line() {
|
||||
let input = "Intro  \n";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 1);
|
||||
assert!(matches!(sections[0], AIAgentTextSection::PlainText { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_block_image_with_commonmark_title() {
|
||||
let input = "\n";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 1);
|
||||
match §ions[0] {
|
||||
AIAgentTextSection::Image { image } => {
|
||||
assert_eq!(image.alt_text, "Rex");
|
||||
assert_eq!(image.source, "./rex.png");
|
||||
assert_eq!(image.title.as_deref(), Some("My dog Rex"));
|
||||
// Right-click copy uses `markdown_source`, so it must round-trip
|
||||
// the authored title (product invariant 9).
|
||||
assert_eq!(image.markdown_source, "");
|
||||
}
|
||||
_ => panic!("expected image section"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_inline_image_run_with_partial_title() {
|
||||
// The inline run contains two images where only the second carries a title.
|
||||
let input = " \n";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 2);
|
||||
match §ions[0] {
|
||||
AIAgentTextSection::Image { image } => {
|
||||
assert_eq!(image.title, None);
|
||||
assert_eq!(image.markdown_source, "");
|
||||
assert_eq!(image.layout, AgentOutputImageLayout::Inline);
|
||||
}
|
||||
_ => panic!("expected first inline image"),
|
||||
}
|
||||
match §ions[1] {
|
||||
AIAgentTextSection::Image { image } => {
|
||||
assert_eq!(image.title.as_deref(), Some("caption"));
|
||||
assert_eq!(image.markdown_source, "");
|
||||
assert_eq!(image.layout, AgentOutputImageLayout::Inline);
|
||||
}
|
||||
_ => panic!("expected second inline image"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_image_with_empty_title_normalizes_to_none() {
|
||||
let input = "\n";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 1);
|
||||
match §ions[0] {
|
||||
AIAgentTextSection::Image { image } => {
|
||||
assert_eq!(image.title, None);
|
||||
// Empty titles normalize away, so `markdown_source` is the
|
||||
// canonical untitled form, not the original source text.
|
||||
assert_eq!(image.markdown_source, "");
|
||||
}
|
||||
_ => panic!("expected image section"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_image_with_unclosed_title_falls_back_to_plain_text() {
|
||||
let input = "\n";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 1);
|
||||
// Unclosed titles cause the whole image to render as plain text.
|
||||
assert!(matches!(sections[0], AIAgentTextSection::PlainText { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_mermaid_code_block_into_mermaid_section() {
|
||||
let input = "```mermaid\ngraph TD\nA[Start] --> B[Finish]\n```";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 1);
|
||||
match §ions[0] {
|
||||
AIAgentTextSection::MermaidDiagram { diagram } => {
|
||||
assert_eq!(diagram.source, "graph TD\nA[Start] --> B[Finish]");
|
||||
assert_eq!(
|
||||
diagram.markdown_source,
|
||||
"```mermaid\ngraph TD\nA[Start] --> B[Finish]\n```"
|
||||
);
|
||||
}
|
||||
_ => panic!("expected mermaid diagram section"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_multiple_mermaid_code_blocks_in_order() {
|
||||
let input = "```mermaid\ngraph TD\nA --> B\n```\n\n```mermaid\ngraph TD\nB --> C\n```";
|
||||
let sections = parse_markdown_into_text_and_code_sections(input);
|
||||
|
||||
assert_eq!(sections.len(), 2);
|
||||
match §ions[0] {
|
||||
AIAgentTextSection::MermaidDiagram { diagram } => {
|
||||
assert_eq!(diagram.source, "graph TD\nA --> B");
|
||||
}
|
||||
_ => panic!("expected first section to be MermaidDiagram"),
|
||||
}
|
||||
match §ions[1] {
|
||||
AIAgentTextSection::MermaidDiagram { diagram } => {
|
||||
assert_eq!(diagram.source, "graph TD\nB --> C");
|
||||
}
|
||||
_ => panic!("expected second section to be MermaidDiagram"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user