clankerdiff-ratatui 0.1.6

Embeddable Ratatui diff review widget
Documentation
#![cfg(feature = "test-support")]

use clankerdiff_markdown::MarkdownStream;
use clankerdiff_ratatui::{
    MarkdownLayoutOptions, MarkdownPresentation, testing::MarkdownStreamFixture,
};
use std::{error::Error, sync::Arc};

#[test]
fn source_lines_preserve_identity_and_graphemes() -> Result<(), Box<dyn Error>> {
    let source = "# e\u{301}\r\n\r\n```rust\r\nlet x = 1;\r\n```\r\n";
    let mut fixture =
        MarkdownStreamFixture::from_source(source).with_options(MarkdownLayoutOptions {
            presentation: MarkdownPresentation::SourceLines,
            wrap: false,
            ..MarkdownLayoutOptions::default()
        });
    let layout = fixture.layout();
    assert_eq!(layout.row_count(), 6);
    for index in 0..6 {
        assert_eq!(
            layout.rows_for_source_line(index + 1),
            Some(index..index + 1)
        );
        assert_eq!(
            layout
                .row(index)
                .ok_or("missing row")?
                .source
                .as_ref()
                .ok_or("missing mapping")?
                .lines
                .start,
            index + 1
        );
    }
    assert_eq!(
        layout.row(0).ok_or("missing row")?.line.to_string(),
        "# e\u{301}"
    );
    fixture.options.width = 3;
    fixture.options.wrap = true;
    let wrapped = fixture.layout();
    assert!(wrapped.rows().iter().all(|row| row.line.width() <= 3));
    assert!(
        wrapped
            .rows_for_source_line(1)
            .ok_or("missing wrapped range")?
            .len()
            > 1
    );
    assert!(
        wrapped
            .rows()
            .iter()
            .any(|row| row.line.to_string().contains("e\u{301}"))
    );
    Ok(())
}

#[test]
fn chunk_slices_share_rows_and_materialization() -> Result<(), Box<dyn Error>> {
    let mut fixture =
        MarkdownStreamFixture::from_source("# One\n\nA paragraph\n\n## Two\n\nMore text");
    let layout = fixture.layout();
    for start in 0..=layout.row_count() {
        for end in start..=layout.row_count() {
            let slice = layout.rows().slice(start..end);
            assert_eq!(slice.len(), end - start);
            assert_eq!(slice.iter().count(), end - start);
            for (index, row) in slice.iter().enumerate() {
                assert!(Arc::ptr_eq(
                    row,
                    layout.row(start + index).ok_or("missing row")?
                ));
                assert!(Arc::ptr_eq(
                    row,
                    slice.get(index).ok_or("missing slice row")?
                ));
            }
            assert!(slice.get(slice.len()).is_none());
            assert!(
                slice.iter().rev().eq(layout
                    .rows()
                    .iter()
                    .skip(start)
                    .take(end - start)
                    .collect::<Vec<_>>()
                    .into_iter()
                    .rev())
            );
        }
    }
    assert!(Arc::ptr_eq(
        &layout.materialize(),
        &layout.clone().materialize()
    ));
    let unchanged = fixture.layout();
    assert!(Arc::ptr_eq(
        layout.row(0).ok_or("missing row")?,
        unchanged.row(0).ok_or("missing row")?
    ));
    Ok(())
}

#[test]
fn streaming_updates_reconstruct_every_snapshot() {
    for source in [
        "# Hello\n\n**bold** and e\u{301}\n",
        "[link][later]\n\n[later]: /target\n",
        "- one\n\n- two\n",
        "```rust\n/* open\n\nclose */\n```\n",
        "```rust\n\n\nlet value = 1;\n```\n",
    ] {
        let mut fixture = MarkdownStreamFixture::default();
        for character in source.chars() {
            fixture.stream.push(&character.to_string());
            fixture.assert_equivalent();
        }
        fixture.stream.finish();
        fixture.assert_equivalent();
        fixture.options.width = 7;
        fixture.assert_equivalent();
        fixture.options.presentation = MarkdownPresentation::SourceLines;
        fixture.assert_equivalent();
        fixture.stream = MarkdownStream::default();
        fixture.stream.push("short");
        fixture.assert_equivalent();
        fixture.state.reset();
        fixture.assert_equivalent();
    }
}

#[test]
fn stale_updates_reset_and_unchanged_updates_are_empty() {
    let mut fixture = MarkdownStreamFixture::from_source("first");
    fixture.apply_update();
    let unchanged = fixture.state.update_since(fixture.revision);
    assert!(!unchanged.reset);
    assert!(unchanged.replacement.is_empty());
    assert_eq!(unchanged.total_rows(), fixture.host.len());
    fixture.stream.push("\n\nsecond");
    fixture.apply_update();
    fixture.stream.push("\n\nthird");
    fixture.apply_update();
    let stale = fixture.state.update_since(0);
    assert!(stale.reset);
    assert_eq!(stale.first_changed_row, 0);
    assert!(stale.replacement.iter().eq(fixture.host.iter()));
}

#[test]
fn finishing_and_resuming_without_source_changes_do_not_repeat_work() {
    let mut fixture = MarkdownStreamFixture::from_source("```rust\nfn main() {}\n```");
    fixture.apply_update();
    fixture.state.take_stats();
    let revision = fixture.revision;
    fixture.stream.finish();
    let update = fixture.apply_update();
    assert_eq!(update.revision, revision);
    assert!(update.replacement.is_empty());
    let work = fixture.state.take_stats();
    assert_eq!(work.parsed_bytes, 0);
    assert_eq!(work.parsed_documents, 0);
    assert_eq!(work.highlighted_bytes, 0);
    assert_eq!(work.rows_generated, 0);
    fixture.stream.push("");
    fixture.apply_update();
    let work = fixture.state.take_stats();
    assert_eq!(work.parsed_documents, 0);
    assert_eq!(work.rows_generated, 0);
    fixture.stream.push("\n\nNew paragraph");
    fixture.assert_equivalent();
    assert!(fixture.state.take_stats().parsed_documents > 0);
}

#[test]
fn tab_expansion_survives_tiny_wrapped_widths() {
    let mut fixture =
        MarkdownStreamFixture::from_source("a\tb").with_options(MarkdownLayoutOptions {
            width: 2,
            tab_width: 4,
            presentation: MarkdownPresentation::SourceLines,
            ..MarkdownLayoutOptions::default()
        });
    let layout = fixture.layout();
    assert!(layout.rows().iter().all(|row| row.line.width() <= 2));
    assert_eq!(
        layout
            .rows()
            .iter()
            .map(|row| row.line.to_string())
            .collect::<String>(),
        "a   b"
    );
}