use super::*;
use crate::base::Size;
use crate::theme::default_theme;
use crate::widgets::test_util::{draw_into, row};
const DOC: &str = "# Title\n\nBody with `code` inline.\n\n- first\n- second\n\n> wisdom\n\n```\nfn main() {}\n```\n";
fn cell_of(row: &str, needle: &str) -> i32 {
let byte = row.find(needle).unwrap();
row[..byte].chars().count() as i32
}
#[test]
fn heading_list_quote_and_fence_chrome() {
let t = default_theme().tokens;
let c = draw_into(MarkdownView::new(DOC).element(&t), Size::new(28, 14));
let title_y = 0;
assert!(row(&c, title_y).starts_with("Title"));
assert_eq!(c.cell(Point::new(0, title_y)).unwrap().1, t.accent);
assert!(row(&c, title_y + 1).starts_with('─'));
assert_eq!(c.cell(Point::new(0, title_y + 1)).unwrap().1, t.border);
let body_y = (0..14).find(|y| row(&c, *y).contains("code")).unwrap();
let cx = cell_of(&row(&c, body_y), "code");
assert_eq!(c.cell(Point::new(cx, body_y)).unwrap().2, t.surface_raised);
let li_y = (0..14).find(|y| row(&c, *y).contains("• first")).unwrap();
let mx = cell_of(&row(&c, li_y), "•");
assert_eq!(c.cell(Point::new(mx, li_y)).unwrap().1, t.accent_alt);
let q_y = (0..14).find(|y| row(&c, *y).contains("wisdom")).unwrap();
assert_eq!(c.cell(Point::new(0, q_y)).unwrap().0, '▎');
let wx = cell_of(&row(&c, q_y), "wisdom");
assert_eq!(c.cell(Point::new(wx, q_y)).unwrap().1, t.text_muted);
let f_y = (0..14).find(|y| row(&c, *y).contains("fn main")).unwrap();
let fx = cell_of(&row(&c, f_y), "fn");
let (_, fg, bg) = c.cell(Point::new(fx, f_y)).unwrap();
assert_eq!(fg, t.syntax_keyword);
assert_eq!(bg, t.surface_raised);
}
#[test]
fn outline_rows_and_scroll_share_the_fold() {
let t = default_theme().tokens;
assert_eq!(
MarkdownView::outline(DOC, &t),
vec![(1, "Title".to_string())]
);
let total = MarkdownView::rows(DOC, &t, 28);
assert!(total >= 10, "typeset rows: {total}");
let c = draw_into(
MarkdownView::new(DOC).scroll_offset(1).element(&t),
Size::new(28, 6),
);
assert!(!row(&c, 0).contains("Title"));
}
#[test]
fn diff_fences_tint_added_removed_and_plain_fences_stay_clike() {
let t = default_theme().tokens;
let doc = "```diff\n-old line\n+new line\n```\n\n```\nfn main() {}\n```\n";
let c = draw_into(MarkdownView::new(doc).element(&t), Size::new(28, 10));
let minus_y = (0..10).find(|y| row(&c, *y).contains("-old")).unwrap();
let mx = cell_of(&row(&c, minus_y), "-old");
let (_, fg, bg) = c.cell(Point::new(mx, minus_y)).unwrap();
assert_eq!(fg, t.error);
assert_eq!(bg, t.surface_raised);
let plus_y = (0..10).find(|y| row(&c, *y).contains("+new")).unwrap();
let px = cell_of(&row(&c, plus_y), "+new");
assert_eq!(c.cell(Point::new(px, plus_y)).unwrap().1, t.ok);
let fn_y = (0..10).find(|y| row(&c, *y).contains("fn main")).unwrap();
let fx = cell_of(&row(&c, fn_y), "fn");
assert_eq!(c.cell(Point::new(fx, fn_y)).unwrap().1, t.syntax_keyword);
}
#[test]
fn json_and_yaml_fences_tint_through_the_data_mapping() {
let t = default_theme().tokens;
let doc = "```json\n{\"name\": \"Ada\", \"ok\": true}\n```\n\n```yaml\nregion: eu-west-1 # main\n```\n";
let c = draw_into(MarkdownView::new(doc).element(&t), Size::new(40, 8));
let j_y = (0..8).find(|y| row(&c, *y).contains("name")).unwrap();
let jrow = row(&c, j_y);
let kx = cell_of(&jrow, "\"name\"");
let (_, fg, bg) = c.cell(Point::new(kx, j_y)).unwrap();
assert_eq!(fg, t.syntax_func, "json key ink");
assert_eq!(bg, t.surface_raised, "fence ground survives");
let vx = cell_of(&jrow, "\"Ada\"");
assert_eq!(c.cell(Point::new(vx, j_y)).unwrap().1, t.syntax_string);
let lx = cell_of(&jrow, "true");
assert_eq!(c.cell(Point::new(lx, j_y)).unwrap().1, t.syntax_keyword);
let y_y = (0..8).find(|y| row(&c, *y).contains("region")).unwrap();
let yrow = row(&c, y_y);
let yx = cell_of(&yrow, "region");
assert_eq!(
c.cell(Point::new(yx, y_y)).unwrap().1,
t.syntax_func,
"yaml key ink"
);
let cx = cell_of(&yrow, "# main");
assert_eq!(c.cell(Point::new(cx, y_y)).unwrap().1, t.syntax_comment);
let sx = cell_of(&yrow, "eu-west-1");
assert_eq!(c.cell(Point::new(sx, y_y)).unwrap().1, t.text);
}
#[test]
fn h3_and_deeper_carry_a_faint_level_prefix() {
let t = default_theme().tokens;
let doc = "## Two\n\n### Three\n\n#### Four";
let c = draw_into(MarkdownView::new(doc).element(&t), Size::new(24, 8));
let two_y = (0..8).find(|y| row(&c, *y).contains("Two")).unwrap();
assert!(row(&c, two_y).starts_with("Two"), "H2 has no prefix");
let three_y = (0..8).find(|y| row(&c, *y).contains("Three")).unwrap();
assert!(
row(&c, three_y).starts_with("### Three"),
"{:?}",
row(&c, three_y)
);
assert_eq!(
c.cell(Point::new(0, three_y)).unwrap().1,
t.text_faint,
"prefix rides the faint ink"
);
let tx = cell_of(&row(&c, three_y), "Three");
assert_eq!(
c.cell(Point::new(tx, three_y)).unwrap().1,
t.text,
"heading text keeps body ink + bold"
);
let four_y = (0..8).find(|y| row(&c, *y).contains("Four")).unwrap();
assert!(row(&c, four_y).starts_with("#### Four"));
}
#[test]
fn wrapping_and_tiny_rects_never_panic() {
let t = default_theme().tokens;
let long = "paragraph with quite a few words that must wrap around";
let c = draw_into(MarkdownView::new(long).element(&t), Size::new(12, 8));
assert!(row(&c, 0).trim_end().len() <= 12);
for size in [Size::new(0, 0), Size::new(2, 1), Size::new(5, 2)] {
let _ = draw_into(MarkdownView::new(DOC).element(&t), size);
}
}
struct BoxFence;
impl crate::widgets::FenceBlock for BoxFence {
fn measure(&self, lang: &str, _source: &str, _width: i32) -> Option<i32> {
(lang == "box").then_some(3)
}
fn draw_row(
&self,
_lang: &str,
source: &str,
row: i32,
at: crate::base::Point,
width: i32,
t: &crate::theme::TokenSet,
canvas: &mut dyn crate::ui::StyledCanvas,
) {
let w = width.max(2) as usize;
let text = match row {
0 => format!("┌{}┐", "─".repeat(w - 2)),
1 => {
let body: String = source
.lines()
.next()
.unwrap_or("")
.chars()
.take(w - 2)
.collect();
format!("│{body:<0$}│", w - 2)
}
_ => format!("└{}┘", "─".repeat(w - 2)),
};
canvas.print(at, &text, t.text, Rgba::TRANSPARENT);
}
}
#[test]
fn a_claimed_fence_renders_in_place_of_its_code() {
let src = "# Title\n\n```box\ndiagram here\n```\n\nafter";
let canvas = draw_into(
MarkdownView::new(src)
.fence_block(std::rc::Rc::new(BoxFence))
.element(&default_theme().tokens),
Size::new(24, 12),
);
let screen: Vec<String> = (0..12).map(|y| canvas.row_text(y)).collect();
let joined = screen.join("\n");
assert!(joined.contains("┌"), "the block drew its frame:\n{joined}");
assert!(
joined.contains("diagram here"),
"the block drew its content:\n{joined}"
);
assert!(
!joined.contains("```"),
"the fence markers never reach the screen:\n{joined}"
);
assert!(joined.contains("after"), "{joined}");
assert!(joined.contains("Title"), "{joined}");
}
#[test]
fn a_declined_fence_still_renders_as_code() {
let src = "```rust\nlet x = 1;\n```";
let canvas = draw_into(
MarkdownView::new(src)
.fence_block(std::rc::Rc::new(BoxFence))
.element(&default_theme().tokens),
Size::new(30, 6),
);
let joined: String = (0..6)
.map(|y| canvas.row_text(y))
.collect::<Vec<_>>()
.join("\n");
assert!(
joined.contains("let x = 1;"),
"code is untouched:\n{joined}"
);
assert!(!joined.contains("┌"), "the claimant declined:\n{joined}");
}
#[test]
fn claimed_rows_participate_in_scrolling_and_measurement() {
let src = "```box\nd\n```\n\ntail";
let t = crate::theme::default_theme().tokens;
let plain = MarkdownView::rows(src, &t, 24);
let with_block = super::doc::layout_doc_with(src, &t, 24, Some(std::rc::Rc::new(BoxFence)))
.rows
.len();
assert_ne!(plain, with_block, "the block changes the row count");
let canvas = draw_into(
MarkdownView::new(src)
.fence_block(std::rc::Rc::new(BoxFence))
.scroll_offset(1)
.element(&default_theme().tokens),
Size::new(24, 3),
);
assert!(
canvas.row_text(0).contains("│"),
"scrolled INTO the block: {:?}",
canvas.row_text(0)
);
}