strop-editor 0.3.0

strop — a modal text editor in Rust: see the cut before you make it
//! Multicursor cascade tests (0013): Q toggles at point, Space c stacks
//! a cursor on the next line, edits cascade, Esc collapses, undo takes
//! the whole cascade back as one unit. Motions move every cursor
//! (nvim 0.13 semantics).

#[cfg(test)]
mod tests {
    use crate::editor::Editor;
    use strop_core::Buffer;

    fn text(e: &Editor) -> String {
        e.buf().rope.to_string()
    }

    #[test]
    fn q_toggles_at_point_and_motions_move_all() {
        let mut e = Editor::new(Buffer::from_text("one\ntwo\nthree\n"));
        e.feed_text("Q");
        assert_eq!(e.extra_cursors, vec![0]);
        e.feed_text("j"); // every cursor moves (nvim 0.13)
        assert_eq!(e.cursor, 4);
        assert_eq!(e.extra_cursors, vec![4]);
        e.feed_text("Q"); // the extra sits on the primary → toggles off
        assert!(e.extra_cursors.is_empty());
    }

    #[test]
    fn space_c_stacks_down_a_column() {
        let mut e = Editor::new(Buffer::from_text("one\ntwo\nthree\n"));
        e.feed_text(" c"); // cursor below joins
        assert_eq!(e.extra_cursors, vec![4]);
        e.feed_text(" c");
        assert_eq!(e.extra_cursors, vec![4, 8]); // "three" starts at 8
        e.feed_text(" c"); // no fourth line
        assert_eq!(e.extra_cursors, vec![4, 8]);
    }

    #[test]
    fn delete_cascades_and_undoes_as_one_unit() {
        let mut e = Editor::new(Buffer::from_text("foo a\nfoo b\n"));
        e.feed_text(" c"); // cursors on both "foo"s
        e.feed_text("dw");
        assert_eq!(text(&e), "a\nb\n");
        e.feed_text("u"); // ONE undo for the whole cascade
        assert_eq!(text(&e), "foo a\nfoo b\n");
    }

    #[test]
    fn change_flows_into_mirrored_insert() {
        let mut e = Editor::new(Buffer::from_text("x = 1\ny = 2\n"));
        e.feed_text(" c");
        e.feed_text("cw"); // change both first words
        e.feed_text("val");
        e.feed(crate::editor::Key::Esc);
        assert_eq!(text(&e), "val = 1\nval = 2\n");
        e.feed_text("u"); // change + insert = one unit
        assert_eq!(text(&e), "x = 1\ny = 2\n");
    }

    #[test]
    fn esc_collapses_to_primary() {
        let mut e = Editor::new(Buffer::from_text("a\nb\nc\n"));
        e.feed_text(" c");
        e.feed_text(" c");
        assert_eq!(e.extra_cursors.len(), 2);
        e.feed(crate::editor::Key::Esc);
        assert!(e.extra_cursors.is_empty());
    }

    #[test]
    fn stacked_cursors_edit_once() {
        let mut e = Editor::new(Buffer::from_text("word here\n"));
        e.feed_text("Q"); // extra stacked on the primary at 0
        e.feed_text("dw"); // one delete, not two
        assert_eq!(text(&e), "here\n");
        e.feed_text("u");
        e.feed_text("i"); // insert mirrors once, not twice
        e.feed_text("Z");
        e.feed(crate::editor::Key::Esc);
        assert_eq!(text(&e), "Zword here\n");
    }

    #[test]
    fn paste_cascades_to_every_cursor() {
        let mut e = Editor::new(Buffer::from_text("aa\nbb\n"));
        e.feed_text("yiw"); // yank "aa"
        e.feed_text(" c"); // cursor joins on line 2
        e.feed_text("e"); // word end on both
        e.feed_text("p"); // paste "aa" after each cursor (vim: no space magic)
        assert_eq!(text(&e), "aaaa\nbbaa\n");
    }

    #[test]
    fn n_cascades_to_every_cursor() {
        let mut e = Editor::new(Buffer::from_text("1 foo\n2 foo\n3 foo\n"));
        e.feed_text(" c"); // extra on line 2
        e.feed_text("/foo\r");
        assert_eq!(e.cursor, 2);
        assert_eq!(e.extra_cursors, vec![8]); // foo on line 2 starts at 8
    }

    #[test]
    fn extra_cursors_render_without_panicking() {
        let mut e = Editor::new(Buffer::from_text("one\ntwo\nthree\n"));
        e.feed_text(" c");
        let frame = crate::headless::frame_string(&mut e, 40, 10);
        assert!(frame.contains("one") && frame.contains("two"));
    }
}