clankerdiff-core 0.1.4

Renderer-independent diff and code review domain model
Documentation
#![cfg(feature = "test-support")]

use clankerdiff_core::{
    DiffPresentation, DiffSide, MAX_HUNK_SEQUENCE_LINES, PresentationOptions, RowKind,
    SourceDocument, testing::DocumentBuilder,
};
use std::{borrow::Cow, error::Error};

#[test]
fn cell_context_prefers_complete_source_and_side_specific_paths() {
    let old = "/* open\nold\nclosed */\n";
    let new = "/* open\nnew\nclosed */\n";
    let presentation = DiffPresentation::new(
        DocumentBuilder::new()
            .renamed("old.rs", "new.rs", old, new)
            .build(),
        PresentationOptions::default(),
    );
    let mut seen = 0;
    for row in (0..presentation.row_count()).filter_map(|index| presentation.row(index)) {
        for cell in row.cells() {
            let Some(location) = cell.source_line else {
                continue;
            };
            let context = presentation.cell_context(row, cell);
            let (path, source) = match location.side {
                DiffSide::Old => ("old.rs", old),
                DiffSide::New => ("new.rs", new),
            };
            assert_eq!(context.path, path);
            assert_eq!(context.target_line, location.line_number - 1);
            assert_eq!(context.text(), source);
            assert!(matches!(context.text(), Cow::Borrowed(_)));
            assert_eq!(
                context.text().lines().nth(context.target_line),
                Some(cell.text.as_ref())
            );
            seen += 1;
        }
    }
    assert!(seen > 0);
}

#[test]
fn patch_only_cells_share_their_bounded_hunk_context() -> Result<(), Box<dyn Error>> {
    let presentation = DiffPresentation::new(
        DocumentBuilder::new().generated("src/lib.rs", 8).build(),
        PresentationOptions::default(),
    );
    let mut previous = None;
    for row in (0..presentation.row_count()).filter_map(|index| presentation.row(index)) {
        if row.kind != RowKind::Code {
            continue;
        }
        let cell = row.primary_cell().ok_or("missing code cell")?;
        let context = presentation.cell_context(row, cell);
        assert_eq!(context.path, "src/lib.rs");
        assert_eq!(context.text().lines().count(), 8);
        assert!(matches!(context.text(), Cow::Owned(_)));
        assert_eq!(
            context.text().lines().nth(context.target_line),
            Some(cell.text.as_ref())
        );
        if let Some(id) = previous {
            assert_eq!(context.id, id);
        }
        previous = Some(context.id);
    }
    assert!(previous.is_some());
    Ok(())
}

#[test]
fn context_identity_distinguishes_exact_source_line_endings() -> Result<(), Box<dyn Error>> {
    let mut ids = Vec::new();
    for text in ["let x = 1;", "let x = 1;\n", "let x = 1;\r\n"] {
        let source = SourceDocument::new(text)?;
        let presentation = DiffPresentation::new(
            DocumentBuilder::new()
                .changed("src/lib.rs", "", text)
                .build(),
            PresentationOptions::default(),
        );
        let row = (0..presentation.row_count())
            .filter_map(|index| presentation.row(index))
            .find(|row| row.kind == RowKind::Code)
            .ok_or("missing code row")?;
        let cell = row.primary_cell().ok_or("missing code cell")?;
        let context = presentation.cell_context(row, cell);
        assert_eq!(context.text(), text);
        assert_eq!(context.id, source.content_id());
        assert!(!ids.contains(&context.id));
        ids.push(context.id);
    }
    Ok(())
}

#[test]
fn hunk_and_cell_contexts_have_distinct_identities() -> Result<(), Box<dyn Error>> {
    let presentation = DiffPresentation::new(
        DocumentBuilder::new().generated("src/lib.rs", 1).build(),
        PresentationOptions::default(),
    );
    let row = (0..presentation.row_count())
        .filter_map(|index| presentation.row(index))
        .find(|row| row.kind == RowKind::Code)
        .ok_or("missing code row")?;
    let cell = row.primary_cell().ok_or("missing code cell")?;
    let hunk = presentation.cell_context(row, cell);
    let mut synthetic = cell.clone();
    synthetic.patch_source = None;
    synthetic.source_line = None;
    let fallback = presentation.cell_context(row, &synthetic);
    assert_eq!(hunk.text(), format!("{}\n", fallback.text()));
    assert_ne!(hunk.id, fallback.id);
    assert_eq!(hunk.id, presentation.cell_context(row, cell).id);
    Ok(())
}

#[test]
fn oversized_hunks_and_synthetic_cells_use_only_the_cell_text() {
    let presentation = DiffPresentation::new(
        DocumentBuilder::new()
            .generated("src/lib.rs", MAX_HUNK_SEQUENCE_LINES + 1)
            .build(),
        PresentationOptions::default(),
    );
    for row in (0..presentation.row_count()).filter_map(|index| presentation.row(index)) {
        for cell in row.cells() {
            let context = presentation.cell_context(row, cell);
            assert_eq!(context.target_line, 0);
            assert_eq!(context.text(), cell.text.as_ref());
            assert!(matches!(context.text(), Cow::Borrowed(_)));
        }
    }
}