pmat 3.30.1

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP)
Documentation
// Included from progress.rs — unit tests and property tests
// NO use imports or #! inner attributes allowed (shares parent module scope)

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
    use super::*;

    /// Test that progress tracker methods work
    #[test]
    fn test_progress_tracker_methods() {
        let progress = ProgressTracker::new(true);

        let spinner = progress.create_spinner("Test");
        assert!(!spinner.is_hidden());

        let file_progress = progress.create_file_progress(100, "Files");
        assert!(!file_progress.is_hidden());

        let bytes_progress = progress.create_bytes_progress(1000, "Bytes");
        assert!(!bytes_progress.is_hidden());
    }

    #[test]
    fn test_hidden_progress() {
        let progress = ProgressTracker::new(false);

        let spinner = progress.create_spinner("Test");
        assert!(spinner.is_hidden());

        let file_progress = progress.create_file_progress(100, "Files");
        assert!(file_progress.is_hidden());
    }

    #[test]
    fn test_progress_bar_operations() {
        let pb = SimpleProgressBar::new(100);
        pb.set_message("Testing");
        assert_eq!(pb.message(), "Testing");

        pb.set_position(50);
        assert_eq!(pb.position(), 50);

        pb.inc(10);
        assert_eq!(pb.position(), 60);

        pb.set_length(200);
        assert_eq!(pb.length(), Some(200));
    }

    #[test]
    fn test_progress_bar_lifecycle_methods() {
        let pb = SimpleProgressBar::new(100);

        // Test lifecycle methods (all no-ops but need coverage)
        pb.finish();
        pb.finish_and_clear();
        pb.abandon();
        pb.finish_with_message("Done");
        pb.abandon_with_message("Cancelled");
    }

    #[test]
    fn test_progress_bar_spinner_and_style() {
        let spinner = SimpleProgressBar::new_spinner();
        assert_eq!(spinner.length(), None); // Spinner has no length

        spinner.enable_steady_tick(std::time::Duration::from_millis(100));
        spinner.tick();

        let style = SimpleProgressStyle::default_spinner();
        spinner.set_style(style);
    }

    #[test]
    fn test_progress_bar_output_methods() {
        let pb = SimpleProgressBar::new(100);
        pb.println("Test output");

        // Test suspend
        let result = pb.suspend(|| 42);
        assert_eq!(result, 42);
    }

    #[test]
    fn test_hidden_progress_bar_no_output() {
        let pb = SimpleProgressBar::hidden();
        assert!(pb.is_hidden());

        // These should not print anything when hidden
        pb.finish_with_message("Should not print");
        pb.abandon_with_message("Should not print");
        pb.println("Should not print");
    }

    #[test]
    fn test_progress_bar_clone() {
        let pb = SimpleProgressBar::new(100);
        pb.set_message("Original");
        pb.set_position(50);

        let cloned = pb.clone();
        assert_eq!(cloned.message(), "Original");
        assert_eq!(cloned.position(), 50);
        assert_eq!(cloned.is_hidden(), pb.is_hidden());
    }

    #[test]
    fn test_progress_style_methods() {
        let style = SimpleProgressStyle::default_bar();
        let styled = style.tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏");
        let styled = styled.tick_strings(&["", "", ""]);
        let styled = styled.progress_chars("=>-");
        let styled = styled.template("{msg}").unwrap();
        // All return Self, just verify no panic
        let _ = styled;
    }

    #[test]
    fn test_multi_progress() {
        let mp = SimpleMultiProgress::new();
        let pb = SimpleProgressBar::new(100);
        let added = mp.add(pb);
        assert!(!added.is_hidden());

        assert!(mp.clear().is_ok());
    }

    #[test]
    fn test_multi_progress_default() {
        let mp = SimpleMultiProgress;
        let _ = mp.add(SimpleProgressBar::hidden());
    }

    #[test]
    fn test_tracker_additional_methods() {
        let tracker = ProgressTracker::new(true);

        // Test sub_progress
        let sub = tracker.create_sub_progress("Sub task", 50);
        assert!(!sub.is_hidden());

        // Test log_skipped_file
        tracker.log_skipped_file(std::path::Path::new("test.rs"), "test reason");

        // Test clear
        tracker.clear();
    }

    #[test]
    fn test_tracker_disabled_sub_progress() {
        let tracker = ProgressTracker::new(false);
        let sub = tracker.create_sub_progress("Sub task", 50);
        assert!(sub.is_hidden());
    }

    #[test]
    fn test_file_classification_reporter() {
        use crate::services::file_classifier::SkipReason;

        let tracker = ProgressTracker::new(true);
        let reporter = FileClassificationReporter::new(tracker);

        // Report different skip reasons
        reporter.report_skipped(std::path::Path::new("big.js"), SkipReason::LargeFile);
        reporter.report_skipped(std::path::Path::new("min.js"), SkipReason::MinifiedContent);
        reporter.report_skipped(
            std::path::Path::new("vendor/lib.js"),
            SkipReason::VendorDirectory,
        );
        reporter.report_skipped(std::path::Path::new("long.txt"), SkipReason::LineTooLong);
        reporter.report_skipped(std::path::Path::new("other.bin"), SkipReason::BinaryContent);

        let (count, large_files) = reporter.get_summary();
        assert_eq!(count, 5);
        assert_eq!(large_files.len(), 1); // Only LargeFile goes to large_files list
    }

    #[test]
    fn test_file_classification_reporter_disabled() {
        let tracker = ProgressTracker::new(false);
        let reporter = FileClassificationReporter::new(tracker);

        reporter.report_skipped(
            std::path::Path::new("test.rs"),
            crate::services::file_classifier::SkipReason::LargeFile,
        );

        let (count, _) = reporter.get_summary();
        assert_eq!(count, 1);
    }
}

/// `pmat context --quiet` was byte-identical to `pmat context`, and `pmat
/// score --quiet` to `pmat score`.
///
/// Neither handler printed the surviving chatter itself: it came out of this
/// module. `ProgressTracker::create_spinner` announced "⏳ Analyzing
/// project..." and `SimpleProgressBar::finish_with_message` printed "Analyses
/// complete" / "Analysis complete!" (context) and "✅ Analysis complete"
/// (score, via the rust-project-score orchestrator) with an unguarded stderr
/// macro. Every consumer of a progress bar inherited that, which is why fixing
/// it per-handler would have been the wrong shape: the emitters now route
/// through `status_eprintln!`, so the existing `hidden` rule and the `--quiet`
/// rule are both honoured in the one place that prints.
#[cfg(test)]
mod progress_quiet_chatter_tests {
    use crate::cli::handlers::bottleneck_handler::quiet_chatter_tests::unguarded_stderr_lines;

    #[test]
    fn progress_emitters_obey_quiet() {
        for (what, source) in [
            ("progress bar", include_str!("progress_bar.rs")),
            ("progress tracker", include_str!("progress_tracking.rs")),
        ] {
            let leaking = unguarded_stderr_lines(source);
            assert!(
                leaking.is_empty(),
                "{what}: a progress bar prints nothing but chatter, so every \
                 line must be suppressible; unguarded: {leaking:?}"
            );
        }
    }
}