rnk 0.17.2

A React-like declarative terminal UI framework for Rust, inspired by Ink
Documentation
//! Test: Verify virtual screen buffer and diff algorithm
//!
//! This test validates that tink correctly implements:
//! 1. Virtual screen buffer (previous_lines storage)
//! 2. Diff algorithm (only updates changed lines)
//! 3. Proper output preservation on exit

fn terminal_source() -> String {
    std::fs::read_to_string(format!(
        "{}/src/renderer/terminal.rs",
        env!("CARGO_MANIFEST_DIR")
    ))
    .expect("failed to read terminal.rs")
}

fn exit_inline_section(source: &str) -> &str {
    let start = source
        .find("pub fn exit_inline")
        .expect("exit_inline() should exist");
    let end = source[start..]
        .find("pub fn switch_to_alt_screen")
        .map(|idx| start + idx)
        .expect("switch_to_alt_screen() should exist after exit_inline()");
    &source[start..end]
}

/// Test that virtual screen buffer stores previous frame
#[test]
fn test_virtual_screen_buffer_exists() {
    let source = terminal_source();
    assert!(source.contains("previous_lines: Vec<String>"));
}

/// Test that diff algorithm compares old and new lines
#[test]
fn test_diff_algorithm_implementation() {
    let source = terminal_source();
    assert!(source.contains("let old_line = self.previous_lines.get(i)"));
    assert!(source.contains("if old_line != Some(*new_line)"));
}

/// Test that exit_inline preserves output
#[test]
fn test_exit_inline_preserves_output() {
    let source = terminal_source();
    let section = exit_inline_section(&source);
    assert!(section.contains("DisableMouseCapture"));
    assert!(section.contains("writeln!(stdout)?"));
    assert!(!section.contains("LeaveAlternateScreen"));
}

/// Test incremental rendering logic
#[test]
fn test_incremental_rendering() {
    // Simulate the incremental rendering logic

    let previous_lines = ["Line 1", "Line 2", "Line 3"];
    let new_lines = ["Line 1", "Line 2 CHANGED", "Line 3"];

    let mut updates_needed = Vec::new();

    for (i, new_line) in new_lines.iter().enumerate() {
        let old_line = previous_lines.get(i);

        if old_line != Some(new_line) {
            updates_needed.push(i);
        }
    }

    // Only line 1 (index 1) should need updating
    assert_eq!(updates_needed, vec![1]);
    println!("✅ PASSED: Incremental rendering only updates changed lines");
}

/// Test handling of size changes
#[test]
fn test_size_change_handling() {
    // Test when new content is shorter than previous

    let previous_lines = ["Line 1", "Line 2", "Line 3", "Line 4"];
    let new_lines = ["Line 1", "Line 2"];

    let max_lines = previous_lines.len().max(new_lines.len());
    let mut clears_needed = 0;

    for i in 0..max_lines {
        if i >= new_lines.len() && i < previous_lines.len() {
            // Need to clear this line
            clears_needed += 1;
        }
    }

    // Should clear 2 lines (lines 2 and 3)
    assert_eq!(clears_needed, 2);
    println!("✅ PASSED: Correctly handles when new content is shorter");
}

/// Test that previous_lines is updated after render
#[test]
fn test_previous_lines_update() {
    // Verify the previous_lines buffer is updated after each render
    // This is done at terminal.rs:454, 502, 423

    let new_lines = ["Line 1", "Line 2"];
    let previous_lines: Vec<String> = new_lines.iter().map(|s| s.to_string()).collect();

    assert_eq!(previous_lines.len(), 2);
    assert_eq!(previous_lines[0], "Line 1");
    assert_eq!(previous_lines[1], "Line 2");

    println!("✅ PASSED: previous_lines buffer is correctly updated");
}

/// Test cursor position management
#[test]
fn test_cursor_position_management() {
    // Verify cursor positioning logic for inline mode

    let prev_count = 5;
    let new_count = 3;

    // When content shrinks, cursor needs to move up
    let lines_to_move_up = if new_count < prev_count {
        prev_count - new_count
    } else {
        0
    };

    assert_eq!(lines_to_move_up, 2);
    println!("✅ PASSED: Cursor positioning correctly handles content size changes");
}

/// Test that escape sequences are correctly avoided
#[test]
fn test_no_alternate_screen_in_inline_mode() {
    let source = terminal_source();
    let section = exit_inline_section(&source);
    assert!(section.contains("disable_raw_mode()"));
    assert!(!section.contains("EnterAlternateScreen"));
    assert!(!section.contains("LeaveAlternateScreen"));
}