abstracttui 0.2.24

A reactive, compositor-grade terminal UI engine: fine-grained signals, layered rendering with damage tracking, images (kitty/iTerm2/sixel/mosaic), software-rasterized 3D (GLB), themes and animation.
Documentation
//! MarkdownView draw-path tests: typeset chrome (headings, lists,
//! quotes, fences), scroll-offset/fold agreement, fence lexer routing
//! (diff + the wave-13 json/yaml data slice), and tiny-rect totality.
//! Split out of `markdown.rs` for the file-size discipline.

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));
    // Level-1 heading in accent + underline rule beneath.
    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);

    // Inline code chip ground.
    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);

    // List marker ink.
    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);

    // Blockquote bar + muted prose.
    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);

    // Code fence: raised ground + keyword ink.
    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}");
    // Scrolling by one hides the title row.
    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));
    // Diff fence: removed line in error ink, added in ok, on the
    // fence's raised ground.
    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);
    // The unlabeled fence still renders the C-like keyword ink.
    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);
}

/// Wave 13: ```json and ```yaml fences tint through the data mapping —
/// keys in `syntax_func`, string values in `syntax_string`, literals
/// in `syntax_keyword`, comments in `syntax_comment` — on the fence's
/// raised ground, through the REAL MarkdownView draw path.
#[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);
    // The bare scalar value stays body ink (prose is prose).
    let sx = cell_of(&yrow, "eu-west-1");
    assert_eq!(c.cell(Point::new(sx, y_y)).unwrap().1, t.text);
}

/// Wave 13 (mdpad port): H3+ headings carry a faint hash prefix so
/// depth stays readable where the heading inks stop differentiating
/// (L3..L6 all render body+BOLD). L1/L2 stay clean.
#[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);
    }
}