use std::sync::Arc;
use markdown_parser::{compute_formatted_text_delta, parse_markdown};
use serde_yaml::Value;
use string_offset::CharOffset;
use vec1::Vec1;
use galaxyui::{App, ReadModel};
use crate::content::{
buffer::{
Buffer, BufferEditAction, EditOrigin, StyledBlockBoundaryBehavior, tests::TestEmbeddedItem,
},
text::{IndentBehavior, TABLE_BLOCK_MARKDOWN_LANG},
};
use super::MarkdownStyle;
#[test]
fn test_export_normalizes_code_languages() {
let formatted = parse_markdown(
r#"
```JavaScript
console.log("Hello, World");
```
```Rust
println!("Hello, World");
```
```ocaml
print_endline "Hello, World!"
```
"#,
)
.unwrap();
let exported = Buffer::export_to_markdown(
formatted,
None,
MarkdownStyle::Export {
app_context: None,
should_not_escape_markdown_punctuation: false,
},
);
// Exporting should use external code languages.
assert_eq!(
exported,
r#"
```js
console.log("Hello, World");
```
```rust
println!("Hello, World");
```
```ocaml
print_endline "Hello, World!"
```
"#
);
}
#[test]
fn test_mermaid_markdown_round_trip() {
App::test((), |mut app| async move {
let _flag = galaxy_core::features::FeatureFlag::MarkdownMermaid.override_enabled(true);
let markdown = "```mermaid\ngraph TD\nA --> B\n```\n";
let (buffer, _selection) = Buffer::mock_from_markdown(
markdown,
None,
Box::new(|_, _| IndentBehavior::Ignore),
&mut app,
);
let internal_markdown = app.read_model(&buffer, |buffer, _| buffer.markdown());
assert_eq!(internal_markdown, markdown);
let exported_markdown = app.read_model(&buffer, |buffer, _| buffer.markdown_unescaped());
assert_eq!(exported_markdown, markdown);
});
}
#[test]
fn test_export_expands_embeds() {
// This tests styled block for the edge case of querying just the
// leading block item (0..1).
App::test((), |mut app| async move {
let (buffer, _selection) = Buffer::mock_from_markdown(
r#"
```warp-embedded-object
id: embed-123
```
```warp-embedded-object
id: embed-456
ignored: value
```"#,
Some(|mut mapping| match mapping.remove(&"id".into()) {
Some(Value::String(id)) => Some(Arc::new(TestEmbeddedItem { id })),
_ => None,
}),
Box::new(|_, _| IndentBehavior::Ignore),
&mut app,
);
let exported = app.read_model(&buffer, |buffer, _| {
buffer.to_markdown(MarkdownStyle::Export {
app_context: None,
should_not_escape_markdown_punctuation: false,
})
});
// Exporting should expand the embedded objects.
assert_eq!(
exported,
r#"
```warp-embedded-object
---
id: embed-123
export: true
```
```warp-embedded-object
---
id: embed-456
export: true
```
"#
);
});
}
#[test]
fn test_table_html_serialization() {
App::test((), |mut app| async move {
let markdown = format!(
"```{}\nheader 1\theader 2\nvalue 1\tvalue 2\n```\n",
TABLE_BLOCK_MARKDOWN_LANG
);
let (buffer, _selection) = Buffer::mock_from_markdown(
&markdown,
None,
Box::new(|_, _| IndentBehavior::Ignore),
&mut app,
);
let html = app.read_model(&buffer, |buffer, ctx| {
let range = CharOffset::from(1)..buffer.max_charoffset();
buffer.ranges_as_html(Vec1::try_from_vec(vec![range]).unwrap(), ctx)
});
assert!(html.is_some());
let html = html.unwrap();
assert!(html.contains(
"
| header 1 | header 2 |
|---|
| value 1 | value 2 |
"
));
});
}
#[test]
fn test_gfm_table_html_serialization() {
App::test((), |mut app| async move {
let _flag = galaxy_core::features::FeatureFlag::MarkdownTables.override_enabled(true);
let markdown = "\
| header 1 | header 2 |\n\
| --- | --- |\n\
| value 1 | value 2 |\n";
let (buffer, _selection) = Buffer::mock_from_markdown(
markdown,
None,
Box::new(|_, _| IndentBehavior::Ignore),
&mut app,
);
let html = app.read_model(&buffer, |buffer, ctx| {
let range = CharOffset::from(1)..buffer.max_charoffset();
buffer.ranges_as_html(Vec1::try_from_vec(vec![range]).unwrap(), ctx)
});
assert!(html.is_some());
let html = html.unwrap();
assert!(html.contains(
"| header 1 | header 2 |
|---|
| value 1 | value 2 |
"
));
});
}
#[test]
fn test_apply_formatted_text_delta_append() {
App::test((), |mut app| async move {
let old_markdown = "hello world\n";
let (buffer, selection) = Buffer::mock_from_markdown(
old_markdown,
None,
Box::new(|_, _| IndentBehavior::Ignore),
&mut app,
);
// Buffer::mock_from_markdown removes the trailing newline, so add it back.
buffer.update(&mut app, |buffer, ctx| {
let end_offset = buffer.max_charoffset();
let edits =
Vec1::try_from_vec(vec![("\n".to_string(), end_offset..end_offset)]).unwrap();
buffer.update_content(
BufferEditAction::InsertAtCharOffsetRanges { edits: &edits },
EditOrigin::SystemEdit,
selection.clone(),
ctx,
);
});
let (exported, old_formatted) = app.read_model(&buffer, |buffer, _| {
let old_formatted = buffer.range_to_formatted_text(
CharOffset::from(1)..buffer.max_charoffset(),
StyledBlockBoundaryBehavior::Exclusive,
);
(buffer.markdown_unescaped(), old_formatted)
});
assert_eq!(exported, "hello world\n");
let new_markdown = "hello world\n#";
let new_formatted = parse_markdown(new_markdown).unwrap();
let delta = compute_formatted_text_delta(old_formatted, new_formatted.clone());
// Should just be appending a new line
assert_eq!(delta.common_prefix_lines, 1);
// There's a trailing linebreak being replaced
assert_eq!(delta.old_suffix_formatted_text_lines, 1);
assert_eq!(delta.new_suffix.len(), 1);
buffer.update(&mut app, |buffer, ctx| {
buffer.apply_formatted_text_delta(&delta, selection.clone(), ctx);
});
let (exported, formatted_in_buffer) = app.read_model(&buffer, |buffer, _| {
let new_formatted = buffer.range_to_formatted_text(
CharOffset::from(1)..buffer.max_charoffset(),
StyledBlockBoundaryBehavior::Exclusive,
);
(buffer.markdown_unescaped(), new_formatted)
});
assert_eq!(exported, new_markdown);
assert_eq!(new_formatted, formatted_in_buffer);
let new_markdown_2 = "hello world\n# This is a heading";
let new_formatted_2 = parse_markdown(new_markdown_2).unwrap();
let delta_2 = compute_formatted_text_delta(new_formatted, new_formatted_2.clone());
// Should be replacing the # line while keeping the hello world line
assert_eq!(delta_2.common_prefix_lines, 1);
assert_eq!(delta_2.old_suffix_formatted_text_lines, 1);
assert_eq!(delta_2.new_suffix.len(), 1);
buffer.update(&mut app, |buffer, ctx| {
buffer.apply_formatted_text_delta(&delta_2, selection.clone(), ctx);
});
let (exported, formatted_in_buffer) = app.read_model(&buffer, |buffer, _| {
let new_formatted = buffer.range_to_formatted_text(
CharOffset::from(1)..buffer.max_charoffset(),
StyledBlockBoundaryBehavior::Exclusive,
);
(buffer.markdown_unescaped(), new_formatted)
});
// We add a trailing newline
assert_eq!(exported.trim_end(), new_markdown_2);
assert_eq!(new_formatted_2, formatted_in_buffer);
});
}
#[test]
fn test_image_html_serialization() {
App::test((), |mut app| async move {
let markdown = "\n";
let (buffer, _selection) = Buffer::mock_from_markdown(
markdown,
None,
Box::new(|_, _| IndentBehavior::Ignore),
&mut app,
);
let html = app.read_model(&buffer, |buffer, ctx| {
let range = CharOffset::from(1)..buffer.max_charoffset();
buffer.ranges_as_html(Vec1::try_from_vec(vec![range]).unwrap(), ctx)
});
// Image should be serialized as
assert!(html.is_some());
let html = html.unwrap();
assert!(html.contains("
"));
assert!(html.contains("Header"));
assert!(html.contains("![]()