indicatif 0.17.8

A progress bar and cli reporting library for Rust
Documentation
#![cfg(feature = "in_memory")]

use std::time::Duration;

use indicatif::{
    InMemoryTerm, MultiProgress, MultiProgressAlignment, ProgressBar, ProgressDrawTarget,
    ProgressFinish, ProgressStyle, TermLike,
};
use pretty_assertions::assert_eq;

#[test]
fn basic_progress_bar() {
    let in_mem = InMemoryTerm::new(10, 80);
    let pb = ProgressBar::with_draw_target(
        Some(10),
        ProgressDrawTarget::term_like(Box::new(in_mem.clone())),
    );

    assert_eq!(in_mem.contents(), String::new());

    pb.tick();
    assert_eq!(
        in_mem.contents(),
        "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"
    );

    pb.inc(1);
    assert_eq!(
        in_mem.contents(),
        "███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/10"
    );

    pb.finish();
    assert_eq!(
        in_mem.contents(),
        "██████████████████████████████████████████████████████████████████████████ 10/10"
    );
}

#[test]
fn progress_bar_builder_method_order() {
    let in_mem = InMemoryTerm::new(10, 80);
    // Test that `with_style` doesn't overwrite the message or prefix
    let pb = ProgressBar::with_draw_target(
        Some(10),
        ProgressDrawTarget::term_like(Box::new(in_mem.clone())),
    )
    .with_message("crate")
    .with_prefix("Downloading")
    .with_style(
        ProgressStyle::with_template("{prefix:>12.cyan.bold} {msg}: {wide_bar} {pos}/{len}")
            .unwrap(),
    );

    assert_eq!(in_mem.contents(), String::new());

    pb.tick();
    assert_eq!(
        in_mem.contents(),
        " Downloading crate: ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"
    );
}

#[test]
fn progress_bar_percent_with_no_length() {
    let in_mem = InMemoryTerm::new(10, 80);
    let pb = ProgressBar::with_draw_target(
        None,
        ProgressDrawTarget::term_like(Box::new(in_mem.clone())),
    )
    .with_style(ProgressStyle::with_template("{wide_bar} {percent}%").unwrap());

    assert_eq!(in_mem.contents(), String::new());

    pb.tick();

    assert_eq!(
        in_mem.contents(),
        "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0%"
    );

    pb.set_length(10);

    pb.inc(1);
    assert_eq!(
        in_mem.contents(),
        "███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 10%"
    );

    pb.finish();
    assert_eq!(
        in_mem.contents(),
        "███████████████████████████████████████████████████████████████████████████ 100%"
    );
}

#[test]
fn progress_bar_percent_precise_with_no_length() {
    let in_mem = InMemoryTerm::new(10, 80);
    let pb = ProgressBar::with_draw_target(
        None,
        ProgressDrawTarget::term_like(Box::new(in_mem.clone())),
    )
    .with_style(ProgressStyle::with_template("{wide_bar} {percent_precise}%").unwrap());

    assert_eq!(in_mem.contents(), String::new());

    pb.tick();

    assert_eq!(
        in_mem.contents(),
        "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0.000%"
    );

    pb.set_length(10);

    pb.inc(1);
    assert_eq!(
        in_mem.contents(),
        "███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 10.000%"
    );

    pb.finish();
    assert_eq!(
        in_mem.contents(),
        "███████████████████████████████████████████████████████████████████████ 100.000%"
    );
}

#[test]
fn multi_progress_single_bar_and_leave() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb1 = mp.add(ProgressBar::new(10).with_finish(ProgressFinish::AndLeave));

    assert_eq!(in_mem.contents(), String::new());

    pb1.tick();
    assert_eq!(
        in_mem.contents(),
        r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"#
    );

    drop(pb1);
    assert_eq!(
        in_mem.contents(),
        r#"██████████████████████████████████████████████████████████████████████████ 10/10"#
    );
}

#[test]
fn multi_progress_single_bar_and_clear() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb1 = mp.add(ProgressBar::new(10));

    assert_eq!(in_mem.contents(), String::new());

    pb1.tick();
    assert_eq!(
        in_mem.contents(),
        r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"#
    );

    drop(pb1);
    assert_eq!(in_mem.contents(), "");
}
#[test]
fn multi_progress_two_bars() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb1 = mp.add(ProgressBar::new(10).with_finish(ProgressFinish::AndLeave));
    let pb2 = mp.add(ProgressBar::new(5));

    assert_eq!(in_mem.contents(), String::new());

    pb1.tick();
    assert_eq!(
        in_mem.contents(),
        r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"#
    );

    pb2.tick();

    assert_eq!(
        in_mem.contents(),
        r#"
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5"#
            .trim_start()
    );

    drop(pb1);
    assert_eq!(
        in_mem.contents(),
        r#"
██████████████████████████████████████████████████████████████████████████ 10/10
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5"#
            .trim_start()
    );

    drop(pb2);

    assert_eq!(
        in_mem.contents(),
        r#"██████████████████████████████████████████████████████████████████████████ 10/10"#
    );
}

#[test]
fn multi_progress() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb1 = mp.add(ProgressBar::new(10).with_finish(ProgressFinish::AndLeave));
    let pb2 = mp.add(ProgressBar::new(5));
    let pb3 = mp.add(ProgressBar::new(100));

    assert_eq!(in_mem.contents(), String::new());

    pb1.tick();
    assert_eq!(
        in_mem.contents(),
        r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"#
    );

    pb2.tick();

    assert_eq!(
        in_mem.contents(),
        r#"
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5"#
            .trim_start()
    );

    pb3.tick();
    assert_eq!(
        in_mem.contents(),
        r#"
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100"#
            .trim_start()
    );

    drop(pb1);
    assert_eq!(
        in_mem.contents(),
        r#"
██████████████████████████████████████████████████████████████████████████ 10/10
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100"#
            .trim_start()
    );

    drop(pb2);
    assert_eq!(
        in_mem.contents(),
        r#"
██████████████████████████████████████████████████████████████████████████ 10/10
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100"#
            .trim_start()
    );

    drop(pb3);

    assert_eq!(
        in_mem.contents(),
        r#"██████████████████████████████████████████████████████████████████████████ 10/10"#
    );
}

#[test]
fn multi_progress_println() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb1 = mp.add(ProgressBar::new(10));
    let pb2 = mp.add(ProgressBar::new(5));
    let pb3 = mp.add(ProgressBar::new(100));

    assert_eq!(in_mem.contents(), "");

    pb1.inc(2);
    mp.println("message printed :)").unwrap();

    assert_eq!(
        in_mem.contents(),
        r#"
message printed :)
███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10
            "#
        .trim()
    );

    mp.println("another great message!").unwrap();
    assert_eq!(
        in_mem.contents(),
        r#"
message printed :)
another great message!
███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10
            "#
        .trim()
    );

    pb2.inc(1);
    pb3.tick();
    mp.println("one last message").unwrap();

    assert_eq!(
        in_mem.contents(),
        r#"
message printed :)
another great message!
one last message
███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10
███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/5
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100
        "#
        .trim()
    );

    drop(pb1);
    drop(pb2);
    drop(pb3);

    assert_eq!(
        in_mem.contents(),
        r#"
message printed :)
another great message!
one last message"#
            .trim()
    );
}

#[test]
fn multi_progress_suspend() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb1 = mp.add(ProgressBar::new(10));
    let pb2 = mp.add(ProgressBar::new(10));

    assert_eq!(in_mem.contents(), "");

    pb1.inc(2);
    mp.println("message printed :)").unwrap();

    assert_eq!(
        in_mem.contents(),
        r#"
message printed :)
███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10
            "#
        .trim()
    );

    mp.suspend(|| {
        in_mem.write_line("This is write_line output!").unwrap();
        in_mem.write_line("And so is this").unwrap();
        in_mem.move_cursor_down(1).unwrap();
    });

    assert_eq!(
        in_mem.contents(),
        r#"
message printed :)
This is write_line output!
And so is this

███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10
            "#
        .trim()
    );

    pb2.inc(1);
    mp.println("Another line printed").unwrap();

    assert_eq!(
        in_mem.contents(),
        r#"
message printed :)
This is write_line output!
And so is this

Another line printed
███████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10
███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/10
            "#
        .trim()
    );

    drop(pb1);
    drop(pb2);

    assert_eq!(
        in_mem.contents(),
        r#"
message printed :)
This is write_line output!
And so is this

Another line printed"#
            .trim()
    );
}

#[test]
fn ticker_drop() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let mut spinner: Option<ProgressBar> = None;

    for i in 0..5 {
        let new_spinner = mp.add(
            ProgressBar::new_spinner()
                .with_finish(ProgressFinish::AndLeave)
                .with_message(format!("doing stuff {i}")),
        );
        new_spinner.enable_steady_tick(Duration::from_millis(100));
        spinner.replace(new_spinner);
    }

    drop(spinner);
    assert_eq!(
        in_mem.contents(),
        "  doing stuff 0\n  doing stuff 1\n  doing stuff 2\n  doing stuff 3\n  doing stuff 4"
    );
}

#[test]
fn manually_inc_ticker() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let spinner = mp.add(ProgressBar::new_spinner().with_message("msg"));

    assert_eq!(in_mem.contents(), "");

    spinner.inc(1);
    assert_eq!(in_mem.contents(), "⠁ msg");

    spinner.inc(1);
    assert_eq!(in_mem.contents(), "⠉ msg");

    // set_message / set_prefix shouldn't increase tick
    spinner.set_message("new message");
    spinner.set_prefix("prefix");
    assert_eq!(in_mem.contents(), "⠉ new message");
}

#[test]
fn multi_progress_prune_zombies() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb0 = mp
        .add(ProgressBar::new(10))
        .with_finish(ProgressFinish::AndLeave);
    let pb1 = mp.add(ProgressBar::new(15));
    pb0.tick();
    assert_eq!(
        in_mem.contents(),
        "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"
    );

    pb0.inc(1);
    assert_eq!(
        in_mem.contents(),
        "███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/10"
    );

    drop(pb0);

    // Clear the screen
    mp.clear().unwrap();

    // Write a line that we expect to remain. This helps ensure the adjustment to last_line_count is
    // working as expected, and `MultiState` isn't erasing lines when it shouldn't.
    in_mem.write_line("don't erase me plz").unwrap();

    // pb0 is dead, so only pb1 should be drawn from now on
    pb1.tick();
    assert_eq!(
        in_mem.contents(),
        "don't erase me plz\n░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/15"
    );
}

#[test]
fn multi_progress_prune_zombies_2() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb1 = mp.add(ProgressBar::new(10).with_finish(ProgressFinish::AndLeave));
    let pb2 = mp.add(ProgressBar::new(5));
    let pb3 = mp
        .add(ProgressBar::new(100))
        .with_finish(ProgressFinish::Abandon);
    let pb4 = mp
        .add(ProgressBar::new(500))
        .with_finish(ProgressFinish::AndLeave);
    let pb5 = mp.add(ProgressBar::new(7));

    assert_eq!(in_mem.contents(), String::new());

    pb1.tick();
    assert_eq!(
        in_mem.contents(),
        r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"#
    );

    pb2.tick();

    assert_eq!(
        in_mem.contents(),
        r#"
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5"#
            .trim_start()
    );

    pb3.tick();
    assert_eq!(
        in_mem.contents(),
        r#"
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/5
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100"#
            .trim_start()
    );

    drop(pb1);
    drop(pb2);
    drop(pb3);

    assert_eq!(
        in_mem.contents(),
        r#"
██████████████████████████████████████████████████████████████████████████ 10/10
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100"#
            .trim_start()
    );

    mp.clear().unwrap();

    assert_eq!(in_mem.contents(), "");

    // A sacrificial line we expect shouldn't be touched
    in_mem.write_line("don't erase plz").unwrap();

    mp.println("Test friend :)").unwrap();
    assert_eq!(
        in_mem.contents(),
        r#"
don't erase plz
Test friend :)"#
            .trim_start()
    );

    pb4.tick();
    assert_eq!(
        in_mem.contents(),
        r#"
don't erase plz
Test friend :)
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/500"#
            .trim_start()
    );

    drop(pb4);
    assert_eq!(
        in_mem.contents(),
        r#"
don't erase plz
Test friend :)
████████████████████████████████████████████████████████████████████████ 500/500"#
            .trim_start()
    );

    mp.clear().unwrap();
    assert_eq!(in_mem.contents(), "don't erase plz\nTest friend :)");

    pb5.tick();
    assert_eq!(
        in_mem.contents(),
        r#"
don't erase plz
Test friend :)
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/7"#
            .trim_start()
    );

    mp.println("not your friend, buddy").unwrap();
    assert_eq!(
        in_mem.contents(),
        r#"
don't erase plz
Test friend :)
not your friend, buddy
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/7"#
            .trim_start()
    );

    pb5.inc(1);
    assert_eq!(
        in_mem.contents(),
        r#"
don't erase plz
Test friend :)
not your friend, buddy
██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/7"#
            .trim_start()
    );

    mp.clear().unwrap();
    in_mem.write_line("don't erase me either").unwrap();

    pb5.inc(1);
    assert_eq!(
        in_mem.contents(),
        r#"
don't erase plz
Test friend :)
not your friend, buddy
don't erase me either
█████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/7"#
            .trim_start()
    );

    drop(pb5);

    assert_eq!(
        in_mem.contents(),
        r#"
don't erase plz
Test friend :)
not your friend, buddy
don't erase me either"#
            .trim_start()
    );
}

#[test]
fn basic_tab_expansion() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let mut spinner = mp.add(ProgressBar::new_spinner().with_message("Test\t:)"));
    spinner.tick();

    // 8 is the default number of spaces
    assert_eq!(in_mem.contents(), "⠁ Test        :)");

    spinner.set_tab_width(4);
    assert_eq!(in_mem.contents(), "⠁ Test    :)");
}

#[test]
fn tab_expansion_in_template() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let mut spinner = mp.add(
        ProgressBar::new_spinner()
            .with_message("Test\t:)")
            .with_prefix("Pre\tfix!")
            .with_style(ProgressStyle::with_template("{spinner}{prefix}\t{msg}").unwrap()),
    );

    spinner.tick();
    assert_eq!(in_mem.contents(), "⠁Pre        fix!        Test        :)");

    spinner.set_tab_width(4);
    assert_eq!(in_mem.contents(), "⠁Pre    fix!    Test    :)");

    spinner.set_tab_width(2);
    assert_eq!(in_mem.contents(), "⠁Pre  fix!  Test  :)");
}

#[test]
fn progress_style_tab_width_unification() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    // Style will have default of 8 spaces for tabs
    let style = ProgressStyle::with_template("{msg}\t{msg}").unwrap();

    let spinner = mp.add(
        ProgressBar::new_spinner()
            .with_message("OK")
            .with_tab_width(4),
    );

    // Setting the spinner's style to |style| should override the style's tab width with that of bar
    spinner.set_style(style);
    spinner.tick();
    assert_eq!(in_mem.contents(), "OK    OK");
}

#[test]
fn multi_progress_clear_println() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    mp.println("Test of println").unwrap();
    // Should have no effect
    mp.clear().unwrap();
    assert_eq!(in_mem.contents(), "Test of println");
}

#[test]
fn multi_progress_clear_zombies_no_ticks() {
    _multi_progress_clear_zombies(0);
}

#[test]
fn multi_progress_clear_zombies_one_tick() {
    _multi_progress_clear_zombies(1);
}

#[test]
fn multi_progress_clear_zombies_two_ticks() {
    _multi_progress_clear_zombies(2);
}

// In the old (broken) implementation, zombie handling sometimes worked differently depending on
// how many draws were between certain operations. Let's make sure that doesn't happen again.
fn _multi_progress_clear_zombies(ticks: usize) {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));
    let style = ProgressStyle::with_template("{msg}").unwrap();

    let pb1 = mp.add(
        ProgressBar::new_spinner()
            .with_style(style.clone())
            .with_message("pb1"),
    );
    pb1.tick();

    let pb2 = mp.add(
        ProgressBar::new_spinner()
            .with_style(style)
            .with_message("pb2"),
    );

    pb2.tick();
    assert_eq!(in_mem.contents(), "pb1\npb2");

    pb1.finish_with_message("pb1 done");
    drop(pb1);
    assert_eq!(in_mem.contents(), "pb1 done\npb2");

    for _ in 0..ticks {
        pb2.tick();
    }

    mp.clear().unwrap();
    assert_eq!(in_mem.contents(), "");
}

// This test reproduces examples/multi.rs in a simpler form
#[test]
fn multi_zombie_handling() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));
    let style = ProgressStyle::with_template("{msg}").unwrap();

    let pb1 = mp.add(
        ProgressBar::new_spinner()
            .with_style(style.clone())
            .with_message("pb1"),
    );
    pb1.tick();
    let pb2 = mp.add(
        ProgressBar::new_spinner()
            .with_style(style.clone())
            .with_message("pb2"),
    );
    pb2.tick();
    let pb3 = mp.add(
        ProgressBar::new_spinner()
            .with_style(style)
            .with_message("pb3"),
    );
    pb3.tick();

    mp.println("pb1 done!").unwrap();
    pb1.finish_with_message("done");
    assert_eq!(in_mem.contents(), "pb1 done!\ndone\npb2\npb3");
    drop(pb1);

    assert_eq!(in_mem.contents(), "pb1 done!\ndone\npb2\npb3");

    pb2.tick();
    assert_eq!(in_mem.contents(), "pb1 done!\ndone\npb2\npb3");
    pb3.tick();
    assert_eq!(in_mem.contents(), "pb1 done!\ndone\npb2\npb3");

    mp.println("pb3 done!").unwrap();
    assert_eq!(in_mem.contents(), "pb1 done!\npb3 done!\npb2\npb3");

    pb3.finish_with_message("done");
    drop(pb3);

    pb2.tick();

    mp.println("pb2 done!").unwrap();
    pb2.finish_with_message("done");
    drop(pb2);

    assert_eq!(
        in_mem.contents(),
        "pb1 done!\npb3 done!\npb2 done!\ndone\ndone"
    );

    mp.clear().unwrap();

    assert_eq!(in_mem.contents(), "pb1 done!\npb3 done!\npb2 done!");
}

#[test]
fn multi_progress_multiline_msg() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb1 = mp.add(ProgressBar::new_spinner().with_message("test1"));
    let pb2 = mp.add(ProgressBar::new_spinner().with_message("test2"));

    assert_eq!(in_mem.contents(), "");

    pb1.inc(1);
    pb2.inc(1);

    assert_eq!(
        in_mem.contents(),
        r#"
⠁ test1
⠁ test2
            "#
        .trim()
    );

    pb1.set_message("test1\n  test1 line2\n  test1 line3");

    assert_eq!(
        in_mem.contents(),
        r#"
⠁ test1
  test1 line2
  test1 line3
⠁ test2
            "#
        .trim()
    );

    pb1.inc(1);
    pb2.inc(1);

    assert_eq!(
        in_mem.contents(),
        r#"
⠉ test1
  test1 line2
  test1 line3
⠉ test2
            "#
        .trim()
    );

    pb2.set_message("test2\n  test2 line2");

    assert_eq!(
        in_mem.contents(),
        r#"
⠉ test1
  test1 line2
  test1 line3
⠉ test2
  test2 line2
            "#
        .trim()
    );

    pb1.set_message("single line again");

    assert_eq!(
        in_mem.contents(),
        r#"
⠉ single line again
⠉ test2
  test2 line2
            "#
        .trim()
    );

    pb1.finish_with_message("test1 done!");
    pb2.finish_with_message("test2 done!");

    assert_eq!(
        in_mem.contents(),
        r#"  test1 done!
  test2 done!"#
    );
}

#[test]
fn multi_progress_bottom_alignment() {
    let in_mem = InMemoryTerm::new(10, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));
    mp.set_alignment(MultiProgressAlignment::Bottom);

    let pb1 = mp.add(ProgressBar::new_spinner().with_message("test1"));
    let pb2 = mp.add(ProgressBar::new_spinner().with_message("test2"));

    pb1.tick();
    pb2.tick();
    pb1.finish_and_clear();

    assert_eq!(in_mem.contents(), "\n⠁ test2");

    pb2.finish_and_clear();
    // `InMemoryTerm::contents` normally gets rid of trailing newlines, so write some text to ensure
    // the newlines are seen.
    in_mem.write_line("anchor").unwrap();
    assert_eq!(in_mem.contents(), "\n\nanchor");
}

#[test]
fn progress_bar_terminal_wrap() {
    use std::cmp::min;
    let in_mem = InMemoryTerm::new(10, 20);

    let mut downloaded = 0;
    let total_size = 231231231;

    let pb = ProgressBar::with_draw_target(
        None,
        ProgressDrawTarget::term_like(Box::new(in_mem.clone())),
    );
    pb.set_style(ProgressStyle::default_bar()
        .template("{msg:>12.cyan.bold} {spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes}").unwrap()
        .progress_chars("#>-"));

    pb.set_message("Downloading");
    assert_eq!(
        in_mem.contents(),
        r#" Downloading ⠁ [00:0
0:00] [-------------
--------------------
-------] 0 B/0 B"#
    );

    let new = min(downloaded + 223211, total_size);
    downloaded = new;
    pb.set_position(new);
    assert_eq!(
        in_mem.contents(),
        r#" Downloading ⠁ [00:0
0:00] [-------------
--------------------
-------] 217.98 KiB/
217.98 KiB"#
    );

    let new = min(downloaded + 223211, total_size);
    pb.set_position(new);
    assert_eq!(
        in_mem.contents(),
        r#" Downloading ⠉ [00:0
0:00] [-------------
--------------------
-------] 435.96 KiB/
435.96 KiB"#
    );

    pb.set_style(
        ProgressStyle::default_bar()
            .template("{msg:>12.green.bold} downloading {total_bytes:.green} in {elapsed:.green}")
            .unwrap(),
    );
    pb.finish_with_message("Finished");
    assert_eq!(
        in_mem.contents(),
        r#"    Finished downloa
ding 435.96 KiB in 0
s"#
    );

    println!("{:?}", in_mem.contents())
}

#[test]
fn spinner_terminal_cleared_log_line_with_ansi_codes() {
    let in_mem = InMemoryTerm::new(10, 100);

    let pb = ProgressBar::with_draw_target(
        Some(10),
        ProgressDrawTarget::term_like(Box::new(in_mem.clone())),
    );
    pb.set_style(ProgressStyle::default_spinner());
    assert_eq!(in_mem.contents(), String::new());

    pb.finish_and_clear();
    // Visually empty, but consists of an ANSII code
    pb.println("\u{1b}[1m");

    pb.println("text\u{1b}[0m");
    assert_eq!(in_mem.contents(), "\ntext");
}

#[test]
fn multi_progress_println_terminal_wrap() {
    let in_mem = InMemoryTerm::new(10, 48);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb1 = mp.add(ProgressBar::new(10));
    let pb2 = mp.add(ProgressBar::new(5));
    let pb3 = mp.add(ProgressBar::new(100));

    assert_eq!(in_mem.contents(), "");

    pb1.inc(2);
    mp.println("message printed that is longer than terminal width :)")
        .unwrap();
    assert_eq!(
        in_mem.contents(),
        r#"message printed that is longer than terminal wid
th :)
████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10"#
    );

    mp.println("another great message!").unwrap();
    assert_eq!(
        in_mem.contents(),
        r#"message printed that is longer than terminal wid
th :)
another great message!
████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10"#
    );

    pb2.inc(1);
    pb3.tick();
    mp.println("one last message but this one is also longer than terminal width")
        .unwrap();

    assert_eq!(
        in_mem.contents(),
        r#"message printed that is longer than terminal wid
th :)
another great message!
one last message but this one is also longer tha
n terminal width
████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2/10
████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/5
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/100"#
            .trim()
    );

    drop(pb1);
    drop(pb2);
    drop(pb3);

    assert_eq!(
        in_mem.contents(),
        r#"message printed that is longer than terminal wid
th :)
another great message!
one last message but this one is also longer tha
n terminal width"#
            .trim()
    );
}

#[test]
fn basic_progress_bar_newline() {
    let in_mem = InMemoryTerm::new(10, 80);
    let pb = ProgressBar::with_draw_target(
        Some(10),
        ProgressDrawTarget::term_like(Box::new(in_mem.clone())),
    );

    assert_eq!(in_mem.contents(), String::new());

    pb.println("\nhello");
    pb.tick();
    assert_eq!(
        in_mem.contents(),
        r#"
hello
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"#
    );

    pb.inc(1);
    pb.println("");
    assert_eq!(
        in_mem.contents(),
        r#"
hello

███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1/10"#
    );

    pb.finish();
    assert_eq!(
        in_mem.contents(),
        "
hello

██████████████████████████████████████████████████████████████████████████ 10/10"
    );
}

#[test]
fn multi_progress_many_bars() {
    let in_mem = InMemoryTerm::new(4, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb1 = mp.add(ProgressBar::new(10).with_finish(ProgressFinish::AndLeave));
    let mut spinners = vec![];
    for i in 0..7 {
        let spinner = ProgressBar::new_spinner().with_message(i.to_string());
        mp.add(spinner.clone());
        spinners.push(spinner);
    }

    assert_eq!(in_mem.contents(), String::new());

    pb1.tick();
    assert_eq!(
        in_mem.contents(),
        r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"#
    );
    assert_eq!(
        in_mem.moves_since_last_check(),
        r#"Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
Flush
"#
    );

    for spinner in &spinners {
        spinner.tick()
    }

    assert_eq!(
        in_mem.contents(),
        r#"
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10
⠁ 0
⠁ 1
⠁ 2"#
            .trim_start()
    );
    assert_eq!(
        in_mem.moves_since_last_check(),
        r#"Clear
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("                                                                             ")
Flush
Up(1)
Clear
Down(1)
Clear
Up(1)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("                                                                             ")
Flush
Up(2)
Clear
Down(1)
Clear
Down(1)
Clear
Up(2)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("                                                                             ")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
Flush
"#
    );

    drop(pb1);
    assert_eq!(
        in_mem.contents(),
        r#"
██████████████████████████████████████████████████████████████████████████ 10/10
⠁ 0
⠁ 1
⠁ 2"#
            .trim_start()
    );
    assert_eq!(
        in_mem.moves_since_last_check(),
        r#"Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("██████████████████████████████████████████████████████████████████████████ 10/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
Flush
"#
    );

    drop(spinners);

    assert_eq!(in_mem.contents(), r#""#);
    assert_eq!(
        in_mem.moves_since_last_check(),
        r#"Up(2)
Clear
Down(1)
Clear
Down(1)
Clear
Up(2)
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
NewLine
Str("⠁ 3")
Str("")
NewLine
Str("⠁ 4")
Str("")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("⠁ 2")
Str("")
NewLine
Str("⠁ 3")
Str("")
NewLine
Str("⠁ 4")
Str("")
NewLine
Str("⠁ 5")
Str("")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("⠁ 3")
Str("")
NewLine
Str("⠁ 4")
Str("")
NewLine
Str("⠁ 5")
Str("")
NewLine
Str("⠁ 6")
Str("                                                                             ")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("⠁ 4")
Str("")
NewLine
Str("⠁ 5")
Str("")
NewLine
Str("⠁ 6")
Str("                                                                             ")
Flush
Up(2)
Clear
Down(1)
Clear
Down(1)
Clear
Up(2)
Str("⠁ 5")
Str("")
NewLine
Str("⠁ 6")
Str("                                                                             ")
Flush
Up(1)
Clear
Down(1)
Clear
Up(1)
Str("⠁ 6")
Str("                                                                             ")
Flush
Clear
Str("")
Flush
"#
    );
}

#[test]
fn multi_progress_many_spinners() {
    let in_mem = InMemoryTerm::new(4, 80);
    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb1 = mp.add(ProgressBar::new(10).with_finish(ProgressFinish::AndLeave));
    let mut spinners = vec![];
    for i in 0..7 {
        let spinner = ProgressBar::new_spinner().with_message(i.to_string());
        mp.add(spinner.clone());
        spinners.push(spinner);
    }

    assert_eq!(in_mem.contents(), String::new());

    pb1.tick();
    assert_eq!(
        in_mem.contents(),
        r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"#
    );
    assert_eq!(
        in_mem.moves_since_last_check(),
        r#"Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
Flush
"#
    );

    for spinner in &spinners {
        spinner.tick()
    }

    assert_eq!(
        in_mem.contents(),
        r#"
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10
⠁ 0
⠁ 1
⠁ 2"#
            .trim_start()
    );

    assert_eq!(
        in_mem.moves_since_last_check(),
        r#"Clear
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("                                                                             ")
Flush
Up(1)
Clear
Down(1)
Clear
Up(1)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("                                                                             ")
Flush
Up(2)
Clear
Down(1)
Clear
Down(1)
Clear
Up(2)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("                                                                             ")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
Flush
"#
    );

    spinners.remove(3);

    assert_eq!(
        in_mem.contents(),
        r#"
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10
⠁ 0
⠁ 1
⠁ 2"#
            .trim_start()
    );

    assert_eq!(
        in_mem.moves_since_last_check(),
        r#"Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
Flush
"#
    );

    spinners.remove(4);

    assert_eq!(
        in_mem.contents(),
        r#"
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10
⠁ 0
⠁ 1
⠁ 2"#
            .trim_start()
    );
    assert_eq!(
        in_mem.moves_since_last_check(),
        r#"Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 0")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
Flush
"#
    );

    drop(spinners);

    assert_eq!(
        in_mem.contents(),
        r#"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10"#
    );
    assert_eq!(
        in_mem.moves_since_last_check(),
        r#"Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 1")
Str("")
NewLine
Str("⠁ 2")
Str("")
NewLine
Str("⠁ 4")
Str("")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 2")
Str("")
NewLine
Str("⠁ 4")
Str("")
NewLine
Str("⠁ 6")
Str("                                                                             ")
Flush
Up(3)
Clear
Down(1)
Clear
Down(1)
Clear
Down(1)
Clear
Up(3)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 4")
Str("")
NewLine
Str("⠁ 6")
Str("                                                                             ")
Flush
Up(2)
Clear
Down(1)
Clear
Down(1)
Clear
Up(2)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
NewLine
Str("⠁ 6")
Str("                                                                             ")
Flush
Up(1)
Clear
Down(1)
Clear
Up(1)
Str("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0/10")
Str("")
Flush
"#
    );
}

#[test]
fn orphan_lines() {
    let in_mem = InMemoryTerm::new(10, 80);

    let pb = ProgressBar::with_draw_target(
        Some(10),
        ProgressDrawTarget::term_like(Box::new(in_mem.clone())),
    );
    assert_eq!(in_mem.contents(), String::new());

    for i in 0..=10 {
        if i != 0 {
            pb.inc(1);
        }

        let n = 5 + i;

        pb.println("\n".repeat(n));
    }

    pb.finish();
}

#[test]
fn orphan_lines_message_above_progress_bar() {
    let in_mem = InMemoryTerm::new(10, 80);

    let pb = ProgressBar::with_draw_target(
        Some(10),
        ProgressDrawTarget::term_like(Box::new(in_mem.clone())),
    );

    orphan_lines_message_above_progress_bar_test(&pb, &in_mem);
}

#[test]
fn orphan_lines_message_above_multi_progress_bar() {
    let in_mem = InMemoryTerm::new(10, 80);

    let mp =
        MultiProgress::with_draw_target(ProgressDrawTarget::term_like(Box::new(in_mem.clone())));

    let pb = mp.add(ProgressBar::new(10));

    orphan_lines_message_above_progress_bar_test(&pb, &in_mem);
}

fn orphan_lines_message_above_progress_bar_test(pb: &ProgressBar, in_mem: &InMemoryTerm) {
    assert_eq!(in_mem.contents(), String::new());

    for i in 0..=10 {
        if i != 0 {
            pb.inc(1);
        }

        let n = 5 + i;

        // Test with messages of differing numbers of lines. The messages have the form:
        // n - 1 newlines followed by n * 11 dashes (`-`). The value of n ranges from 5
        // (less than the terminal height) to 15 (greater than the terminal height). The
        // number 11 is intentionally not a factor of the terminal width (80), but large
        // enough that the strings of dashes eventually wrap.
        pb.println(format!("{}{}", "\n".repeat(n - 1), "-".repeat(n * 11)));

        // Check that the line above the progress bar is a string of dashes of length
        // n * 11 mod the terminal width.
        assert_eq!(
            format!("{}", "-".repeat(n * 11 % 80)),
            in_mem.contents().lines().rev().nth(1).unwrap(),
        );
    }

    pb.finish();
}