declutter-diff 0.1.3

Review AI-generated diffs with comments, tests, imports, logging and formatting toggled in and out
Documentation
use declutter::project::LayerMode;
use declutter::render::plain;
use declutter::review::{ChangeStatus, FileChange, FileReview, Layers, Summary};
use declutter::test_files::{is_test_file, is_test_path};
use declutter::tui::App;
use ratatui::crossterm::event::{KeyCode, KeyEvent};

fn review(path: &str, old: &str, new: &str) -> FileReview {
    FileReview::new(FileChange {
        path: path.to_string(),
        old_path: None,
        status: ChangeStatus::Modified,
        old: Some(old.to_string()),
        new: Some(new.to_string()),
        binary: false,
    })
}

fn files() -> Vec<FileReview> {
    vec![
        review(
            "Sources/Cart/Cart.swift",
            "let total = 1\n",
            "let total = 2\n",
        ),
        review(
            "Tests/CartTests/CartTests.swift",
            "let expected = 1\n",
            "let expected = 2\n",
        ),
        review("web/src/cart.test.ts", "expect(1);\n", "expect(2);\n"),
    ]
}

fn layers(tests: LayerMode) -> Layers {
    Layers {
        tests,
        ..Layers::default()
    }
}

#[test]
fn test_paths_are_recognised_across_languages() {
    let tests = [
        "Tests/CartTests/CartTests.swift",
        "App/AppTests/LoginTests.swift",
        "App/AppTests/Helpers/Fixtures.swift",
        "web/src/cart.test.ts",
        "web/src/Cart.spec.tsx",
        "web/src/__tests__/cart.ts",
        "web/src/__mocks__/api.js",
        "pkg/test_cart.py",
        "pkg/cart_test.py",
        "pkg/conftest.py",
        "tests/fixtures/cart.json",
        "Tests/CartTests/__Snapshots__/CartTests/render.1.png",
    ];
    let code = [
        "Sources/Cart/Cart.swift",
        "Sources/Cart/Latest.swift",
        "Sources/Cart/Contest.swift",
        "Sources/Testing/Harness.swift",
        "web/src/attestation.ts",
        "pkg/testimonials.py",
        "Test.swift",
        "Sources/Cart/CartSpec.swift",
        "src/main/kotlin/PropertySpec.kt",
    ];

    for path in tests {
        assert!(is_test_path(path), "{path} should be a test path");
    }
    for path in code {
        assert!(!is_test_path(path), "{path} should not be a test path");
    }
}

#[test]
fn files_outside_test_directories_count_as_tests_when_they_import_a_test_framework() {
    assert!(is_test_file(
        "Sources/Harness.swift",
        "import Foundation\n@testable import Cart\n"
    ));
    assert!(is_test_file("Sources/Harness.swift", "import Testing\n"));
    assert!(is_test_file(
        "Sources/CartSpec.swift",
        "import Quick\nimport Nimble\n"
    ));
    assert!(is_test_file("pkg/checks.py", "import pytest\n"));
    assert!(is_test_file(
        "web/src/checks.ts",
        "import { describe } from 'vitest';\n"
    ));
    assert!(!is_test_file(
        "Sources/Cart.swift",
        "import Foundation\n// import XCTest\n"
    ));
    assert!(!is_test_file(
        "web/src/cart.ts",
        "import { total } from './vitest-free';\n"
    ));
}

#[test]
fn hiding_tests_leaves_test_files_out_and_says_how_many() {
    let files = files();
    let layers = layers(LayerMode::Hidden);

    let summary = Summary::new(&files, layers);
    assert_eq!((summary.files, summary.filtered_files), (1, 2));
    assert_eq!(
        summary.status_line(layers),
        "comments: hidden · tests: hidden · showing 1 of 1 hunks · 0 comment-only hunks hidden · 2 test files hidden"
    );

    let text = plain(&files, layers);
    assert!(text.contains("M Sources/Cart/Cart.swift [Swift]"), "{text}");
    assert!(!text.contains("CartTests"), "{text}");
}

#[test]
fn tests_only_lists_just_the_test_files() {
    let files = files();
    let layers = layers(LayerMode::Only);

    let text = plain(&files, layers);
    assert!(
        text.contains("M Tests/CartTests/CartTests.swift [Swift, test]"),
        "{text}"
    );
    assert!(
        text.contains("M web/src/cart.test.ts [TypeScript, test]"),
        "{text}"
    );
    assert!(!text.contains("Sources/Cart/Cart.swift"), "{text}");
    assert!(
        text.trim_end().ends_with("1 non-test file hidden"),
        "{text}"
    );
}

#[test]
fn pressing_t_keeps_the_cursor_on_the_same_file_when_it_stays_listed() {
    let mut app = App::new(files(), Layers::default());
    app.handle_key(KeyEvent::from(KeyCode::Down));
    app.handle_key(KeyEvent::from(KeyCode::Down));
    assert_eq!(
        app.current().map(|f| f.path.as_str()),
        Some("web/src/cart.test.ts")
    );

    app.handle_key(KeyEvent::from(KeyCode::Char('t')));
    assert_eq!(app.layers.tests, LayerMode::Hidden);
    assert_eq!(
        app.current().map(|f| f.path.as_str()),
        Some("Sources/Cart/Cart.swift")
    );

    app.handle_key(KeyEvent::from(KeyCode::Char('T')));
    assert_eq!(app.layers.tests, LayerMode::Only);
    assert_eq!(
        app.current().map(|f| f.path.as_str()),
        Some("Tests/CartTests/CartTests.swift")
    );
    app.handle_key(KeyEvent::from(KeyCode::Down));
    app.handle_key(KeyEvent::from(KeyCode::Char('t')));
    assert_eq!(
        app.layers.tests,
        LayerMode::Shown,
        "t from only goes back to shown"
    );
    assert_eq!(
        app.current().map(|f| f.path.as_str()),
        Some("web/src/cart.test.ts")
    );
}