rho-coding-agent 1.21.0

A lightweight agent harness inspired by Pi
Documentation
use super::*;
use crate::tui::{markdown::markdown_lines, theme::Theme};
use pretty_assertions::assert_eq;
use ratatui::text::Line;

fn line_text(line: &Line<'_>) -> String {
    line.spans
        .iter()
        .map(|span| span.content.as_ref())
        .collect()
}

#[test]
fn parses_all_atx_heading_levels() {
    let cases = [
        ("# one", HeadingLevel::H1, "one"),
        ("## two", HeadingLevel::H2, "two"),
        ("### three", HeadingLevel::H3, "three"),
        ("#### four", HeadingLevel::H4, "four"),
        ("##### five", HeadingLevel::H5, "five"),
        ("###### six", HeadingLevel::H6, "six"),
    ];

    for (source, level, content) in cases {
        assert_eq!(
            parse_atx_heading(source),
            Some(AtxHeading { level, content })
        );
    }
}

#[test]
fn accepts_common_atx_spacing_and_closing_hashes() {
    let cases = [
        ("   ## heading", HeadingLevel::H2, "heading"),
        ("#\theading", HeadingLevel::H1, "heading"),
        ("### heading ###", HeadingLevel::H3, "heading"),
        ("### heading ###   ", HeadingLevel::H3, "heading"),
        ("### ###", HeadingLevel::H3, ""),
        ("######", HeadingLevel::H6, ""),
        ("## heading###", HeadingLevel::H2, "heading###"),
    ];

    for (source, level, content) in cases {
        assert_eq!(
            parse_atx_heading(source),
            Some(AtxHeading { level, content })
        );
    }
}

#[test]
fn rejects_lines_that_are_not_atx_headings() {
    for source in [
        "#hashtag",
        "####### heading",
        "    # indented",
        "text # heading",
        "\t# heading",
    ] {
        assert_eq!(parse_atx_heading(source), None, "source: {source:?}");
    }
}

#[test]
fn classifies_streaming_heading_prefixes_without_committing_early() {
    for source in ["", " ", "   ", "#", "   ###"] {
        assert_eq!(
            heading_stream_state(source),
            HeadingStreamState::Potential,
            "source: {source:?}"
        );
    }
    for source in ["# ", "## heading", "   ####\theading"] {
        assert_eq!(
            heading_stream_state(source),
            HeadingStreamState::Heading,
            "source: {source:?}"
        );
    }
    for source in ["#hashtag", "####### ", "    # heading", "ordinary"] {
        assert_eq!(
            heading_stream_state(source),
            HeadingStreamState::NotHeading,
            "source: {source:?}"
        );
    }
}

#[test]
fn preserves_heading_style_across_unicode_wrapping() {
    let content = "你🙂".repeat(20);
    let mut in_code_block = false;
    let lines = markdown_lines(&format!("### {content}"), 7, &mut in_code_block);

    assert_eq!(lines.iter().map(line_text).collect::<String>(), content);
    assert!(lines
        .iter()
        .flat_map(|line| &line.spans)
        .all(|span| { span.style == Theme::markdown_heading(HeadingLevel::H3) }));
}

#[test]
fn leaves_heading_like_text_literal_when_invalid_or_inside_code() {
    let mut in_code_block = false;
    let lines = markdown_lines(
        "#hashtag\n####### nope\n    # indented\n```md\n# literal\n```",
        80,
        &mut in_code_block,
    );
    let text = lines.iter().map(line_text).collect::<Vec<_>>();

    assert_eq!(&text[..3], ["#hashtag", "####### nope", "    # indented"]);
    assert!(text.iter().any(|line| line.contains("# literal")));
}