Files
Ely-GPUI-Components/src/documents/document/tests/tools.rs
T
ZacharyZhang-NYandClaude Opus 5.5 2594b3fd0f
CI / platforms (windows-latest) (push) Failing after 10s
CI / site (push) Successful in 23s
CI / crate (push) Failing after 11m56s
CI / platforms (ubuntu-24.04) (push) Canceled after 0s
Documents: a press in a table opens its cell alone
Visual mode opened a pressed table whole, as Markdown source. A press
in a table now opens only that cell, in place, as its source in a
one-line field; the rest of the table stays drawn. Tab and Shift-Tab
walk the cells, Up and Down move a row or leave the table, Enter and
Escape write back in one undo step, and a bare pipe typed in a cell is
escaped on write-back. The table drawing moves to render/table.rs and
presses on a cell's padding name the cell.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 00:50:20 -04:00

436 lines
16 KiB
Rust

use gpui::{Modifiers, TestAppContext};
use super::{MarkdownMode, open, opened, press, text};
#[gpui::test]
fn enter_in_source_goes_on_with_the_list(cx: &mut TestAppContext) {
let (document, cx) = open("- a", MarkdownMode::Source, cx);
document.update(cx, |document, cx| document.set_caret(0, 3, cx));
let editor = document.read_with(cx, |document, _| document.editor().clone());
let focus = editor.read_with(cx, gpui::Focusable::focus_handle);
cx.update(|window, cx| window.focus(&focus, cx));
cx.simulate_keystrokes("enter");
cx.simulate_input("b");
assert_eq!(text(&document, cx), "- a\n- b");
cx.simulate_keystrokes("enter enter");
assert_eq!(
text(&document, cx),
"- a\n- b\n",
"an empty item ends the list"
);
}
#[gpui::test]
fn enter_in_a_block_goes_on_with_the_list(cx: &mut TestAppContext) {
let (document, cx) = open("1. a\n", MarkdownMode::Visual, cx);
press(0, Modifiers::none(), cx);
cx.simulate_keystrokes("enter");
cx.simulate_input("b");
assert_eq!(opened(&document, cx), "1. a\n2. b");
}
#[gpui::test]
fn tab_shifts_a_block_line_in_and_out(cx: &mut TestAppContext) {
let (document, cx) = open("- a\n- b\n", MarkdownMode::Visual, cx);
press(0, Modifiers::none(), cx);
cx.simulate_keystrokes("tab");
assert_eq!(opened(&document, cx), "- a\n - b");
cx.simulate_keystrokes("shift-tab");
assert_eq!(opened(&document, cx), "- a\n- b");
}
#[gpui::test]
fn a_table_edit_is_one_undo_step_at_the_caret(cx: &mut TestAppContext) {
let (document, cx) = open("| A |\n| - |\n| 1 |\n", MarkdownMode::Source, cx);
document.update(cx, |document, cx| document.set_caret(2, 2, cx));
let edited = document.update(cx, |document, cx| {
document.edit_table(crate::documents::TableEdit::RowBelow, cx)
});
assert_eq!(edited, Ok(()));
cx.run_until_parked();
assert_eq!(
text(&document, cx),
"| A |\n| ---- |\n| 1 |\n| |\n"
);
let editor = document.read_with(cx, |document, _| document.editor().clone());
editor.update(cx, |editor, cx| editor.undo(cx));
assert_eq!(text(&document, cx), "| A |\n| - |\n| 1 |\n");
}
#[gpui::test]
fn a_new_table_goes_after_the_caret_line(cx: &mut TestAppContext) {
let (document, cx) = open("Intro\nNext\n", MarkdownMode::Visual, cx);
document.update(cx, |document, cx| {
document.insert_table(cx).expect("inserted")
});
assert_eq!(
text(&document, cx),
"Intro\n\n| | | |\n| ---- | ---- | ---- |\n| | | |\n| | | |\n\nNext\n"
);
}
#[gpui::test]
fn a_new_table_after_end_keeps_crlf(cx: &mut TestAppContext) {
let (document, cx) = open("Intro\r\nNext\r\n", MarkdownMode::Source, cx);
let editor = document.read_with(cx, |document, _| document.editor().clone());
let focus = editor.read_with(cx, gpui::Focusable::focus_handle);
cx.update(|window, cx| window.focus(&focus, cx));
cx.simulate_keystrokes("end");
let caret = editor.read_with(cx, |editor, _| editor.primary().range().start);
assert_eq!(caret, 6, "End lands between CR and LF");
document.update(cx, |document, cx| {
document.insert_table(cx).expect("inserted")
});
assert_eq!(
text(&document, cx),
"Intro\r\n\r\n| | | |\r\n| ---- | ---- | ---- |\r\n| | | |\r\n| | | |\r\n\r\nNext\r\n"
);
}
#[gpui::test]
fn a_match_in_visual_opens_its_block_selected(cx: &mut TestAppContext) {
let (document, cx) = open("# A\n\nfind me here\n", MarkdownMode::Visual, cx);
let at = "# A\n\nfind me here\n".find("me").expect("the word");
document.update_in(cx, |document, window, cx| {
document.show_match(at..at + 2, window, cx)
});
let selected = document.read_with(cx, |document, cx| {
let input = document
.open
.as_ref()
.expect("a block opens")
.input
.read(cx);
input.text()[input.selection()].to_string()
});
assert_eq!(selected, "me");
}
#[gpui::test]
fn every_replacement_is_one_undo_step(cx: &mut TestAppContext) {
let (document, cx) = open("a b a", MarkdownMode::Visual, cx);
document.update(cx, |document, cx| {
document.replace(vec![(0..1, "x".into()), (4..5, "x".into())], cx)
});
assert_eq!(text(&document, cx), "x b x");
let editor = document.read_with(cx, |document, _| document.editor().clone());
editor.update(cx, |editor, cx| editor.undo(cx));
assert_eq!(text(&document, cx), "a b a");
}
const FENCED: &str = "```text\n- a\n```\n";
#[gpui::test]
fn enter_in_source_code_is_a_plain_line_break(cx: &mut TestAppContext) {
let (document, cx) = open(FENCED, MarkdownMode::Source, cx);
document.update(cx, |document, cx| document.set_caret(1, 3, cx));
let editor = document.read_with(cx, |document, _| document.editor().clone());
let focus = editor.read_with(cx, gpui::Focusable::focus_handle);
cx.update(|window, cx| window.focus(&focus, cx));
cx.simulate_keystrokes("enter");
assert_eq!(text(&document, cx), "```text\n- a\n\n```\n");
}
#[gpui::test]
fn enter_in_an_open_code_block_is_a_plain_line_break(cx: &mut TestAppContext) {
let (document, cx) = open(FENCED, MarkdownMode::Visual, cx);
press(0, Modifiers::none(), cx);
document.update(cx, |document, cx| {
let input = document.open.as_ref().expect("open").input.clone();
input.update(cx, |input, cx| input.select(11..11, cx));
});
cx.simulate_keystrokes("enter");
assert_eq!(opened(&document, cx), "```text\n- a\n\n```");
}
#[gpui::test]
fn a_match_shown_after_a_replacement_opens_selected(cx: &mut TestAppContext) {
let (document, cx) = open("a b a\n", MarkdownMode::Visual, cx);
document.update_in(cx, |document, window, cx| {
document.replace(vec![(0..1, "x".into())], cx);
document.show_match(4..5, window, cx);
});
cx.run_until_parked();
cx.simulate_input("z");
assert_eq!(text(&document, cx), "x b z\n");
}
#[gpui::test]
fn preview_takes_no_edits(cx: &mut TestAppContext) {
let (document, cx) = open("| A |\n| - |\n| 1 |\n", MarkdownMode::Preview, cx);
let refused = document.update(cx, |document, cx| {
(
document.replace(vec![(2..3, "B".into())], cx),
document.edit_table(crate::documents::TableEdit::RowBelow, cx),
document.insert_table(cx),
)
});
let preview = Err(crate::documents::EditRefusal::Preview);
assert_eq!(refused, (false, preview, preview));
assert_eq!(text(&document, cx), "| A |\n| - |\n| 1 |\n");
}
#[gpui::test]
fn a_quoted_list_goes_on_in_source_and_in_a_block(cx: &mut TestAppContext) {
let (document, cx) = open("> - a\n", MarkdownMode::Visual, cx);
press(0, Modifiers::none(), cx);
cx.simulate_keystrokes("enter");
assert_eq!(opened(&document, cx), "> - a\n> - ");
cx.simulate_keystrokes("escape");
document.update_in(cx, |document, window, cx| {
document.set_mode(MarkdownMode::Source, window, cx);
document.set_text("> - a", cx);
document.set_caret(0, 5, cx);
});
cx.run_until_parked();
cx.simulate_keystrokes("enter");
assert_eq!(text(&document, cx), "> - a\n> - ");
}
/// A text long enough to be read off the window.
fn long() -> String {
"para\n\n".repeat(super::super::AT_ONCE / 6 + 1)
}
#[gpui::test]
fn a_waiting_match_lapses_when_the_text_changes(cx: &mut TestAppContext) {
let (document, cx) = open("x\n", MarkdownMode::Visual, cx);
document.update_in(cx, |document, window, cx| {
document.set_text(long(), cx);
document.show_match(0..1, window, cx);
});
document.update(cx, |document, cx| {
document.set_text(format!("y{}", long()), cx)
});
super::frames(cx);
let opened = document.read_with(cx, |document, _| document.open.is_some());
assert!(!opened, "the old match does not land on the new text");
}
#[gpui::test]
fn a_newer_match_takes_the_place_of_a_waiting_one(cx: &mut TestAppContext) {
let (document, cx) = open("x\n", MarkdownMode::Visual, cx);
document.update_in(cx, |document, window, cx| {
document.set_text(long(), cx);
document.show_match(0..1, window, cx);
});
document.update(cx, |document, cx| document.set_text("a b a\n", cx));
cx.run_until_parked();
document.update_in(cx, |document, window, cx| {
document.show_match(4..5, window, cx)
});
super::frames(cx);
cx.simulate_input("z");
assert_eq!(text(&document, cx), "a b z\n");
}
#[gpui::test]
fn an_empty_task_ends_the_list(cx: &mut TestAppContext) {
let (document, cx) = open("- [x] done", MarkdownMode::Source, cx);
document.update(cx, |document, cx| document.set_caret(0, 10, cx));
let editor = document.read_with(cx, |document, _| document.editor().clone());
let focus = editor.read_with(cx, gpui::Focusable::focus_handle);
cx.update(|window, cx| window.focus(&focus, cx));
cx.simulate_keystrokes("enter");
cx.run_until_parked();
cx.simulate_keystrokes("enter");
assert_eq!(text(&document, cx), "- [x] done\n");
}
#[gpui::test]
fn shift_tab_takes_a_tab_indent_out(cx: &mut TestAppContext) {
let (document, cx) = open("- a\n\t- b\n", MarkdownMode::Visual, cx);
press(0, Modifiers::none(), cx);
cx.simulate_keystrokes("shift-tab");
assert_eq!(opened(&document, cx), "- a\n- b");
}
#[gpui::test]
fn shift_tab_takes_spaces_then_a_tab_out(cx: &mut TestAppContext) {
let (document, cx) = open("- a\n \t- b\n", MarkdownMode::Visual, cx);
press(0, Modifiers::none(), cx);
cx.simulate_keystrokes("shift-tab");
assert_eq!(opened(&document, cx), "- a\n- b");
}
#[gpui::test]
fn a_table_on_an_indented_blank_line_is_a_table(cx: &mut TestAppContext) {
let (document, cx) = open(
"
",
MarkdownMode::Source,
cx,
);
document.update(cx, |document, cx| {
document.set_caret(0, 4, cx);
document.insert_table(cx).expect("inserted");
});
let column = document.update(cx, |document, cx| {
document.edit_table(crate::documents::TableEdit::ColumnRight, cx)
});
assert_eq!(column, Ok(()), "{}", text(&document, cx));
cx.run_until_parked();
assert!(text(&document, cx).starts_with("| "));
}
const SMALL: &str = "| A | B |\n| - | - |\n| 1 | 2 |\n";
#[gpui::test]
fn a_table_move_reads_apart_as_one_undo_step(cx: &mut TestAppContext) {
let (document, cx) = open(SMALL, MarkdownMode::Source, cx);
document.update(cx, |document, cx| document.set_caret(2, 2, cx));
let moved = document.update(cx, |document, cx| {
document.edit_table(crate::documents::TableEdit::MoveRowUp, cx)
});
assert_eq!(moved, Ok(()));
cx.run_until_parked();
assert_eq!(
text(&document, cx),
"| 1 | 2 |\n| ---- | ---- |\n| A | B |\n"
);
let editor = document.read_with(cx, |document, _| document.editor().clone());
editor.update(cx, |editor, cx| editor.undo(cx));
assert_eq!(text(&document, cx), SMALL);
}
#[gpui::test]
fn a_table_edit_at_an_edge_refuses_by_event(cx: &mut TestAppContext) {
let (document, cx) = open(SMALL, MarkdownMode::Source, cx);
let heard = super::format::refusals(&document, cx);
document.update(cx, |document, cx| document.set_caret(2, 2, cx));
document.update(cx, |document, cx| {
document
.edit_table(crate::documents::TableEdit::MoveRowDown, cx)
.expect("taken")
});
cx.run_until_parked();
let edge = crate::documents::EditRefusal::Cells(crate::documents::TableRefusal::Edge);
assert_eq!(*heard.borrow(), [edge]);
assert_eq!(text(&document, cx), SMALL);
}
#[gpui::test]
fn tidy_in_cr_text_keeps_what_comes_before(cx: &mut TestAppContext) {
let source = "Before\r\r| A | B |\r| - | - |\r| 1 | 2 |\r\rAfter\r";
let (document, cx) = open(source, MarkdownMode::Source, cx);
let at = source.find('1').expect("a cell");
let editor = document.read_with(cx, |document, _| document.editor().clone());
editor.update(cx, |editor, cx| editor.select(std::iter::once(at..at), cx));
document.update(cx, |document, cx| {
document
.edit_table(crate::documents::TableEdit::Tidy, cx)
.expect("taken")
});
cx.run_until_parked();
assert_eq!(
text(&document, cx),
"Before\r\r| A | B |\r| ---- | ---- |\r| 1 | 2 |\r\rAfter\r"
);
}
#[gpui::test]
fn a_new_table_takes_the_texts_line_ending(cx: &mut TestAppContext) {
let (document, cx) = open("Intro\rNext\r", MarkdownMode::Visual, cx);
document.update(cx, |document, cx| {
document.insert_table(cx).expect("inserted")
});
assert_eq!(
text(&document, cx),
"Intro\r\r| | | |\r| ---- | ---- | ---- |\r| | | |\r| | | |\r\rNext\r"
);
}
#[gpui::test]
fn tidy_in_an_open_cell_writes_it_and_tidies(cx: &mut TestAppContext) {
let (document, cx) = open(SMALL, MarkdownMode::Visual, cx);
press(0, Modifiers::none(), cx);
assert_eq!(opened(&document, cx), "2");
document.update(cx, |document, cx| {
document
.edit_table(crate::documents::TableEdit::Tidy, cx)
.expect("taken")
});
cx.run_until_parked();
assert_eq!(
text(&document, cx),
"| A | B |\n| ---- | ---- |\n| 1 | 2 |\n"
);
}
const CRLF: &str = "Intro\r\nNext\r\n";
/// A code block in CRLF text after `keys`.
fn code_block_after(keys: &str, cx: &mut TestAppContext) -> String {
let (document, cx) = open(CRLF, MarkdownMode::Source, cx);
let editor = document.read_with(cx, |document, _| document.editor().clone());
let focus = editor.read_with(cx, gpui::Focusable::focus_handle);
cx.update(|window, cx| window.focus(&focus, cx));
cx.simulate_keystrokes(keys);
document.update(cx, |document, cx| {
document
.block(crate::documents::BlockCommand::CodeBlock, cx)
.expect("taken")
});
cx.run_until_parked();
text(&document, cx)
}
const FENCE_AFTER_INTRO: &str = "Intro\r\n\r\n```\r\n\r\n```\r\n\r\nNext\r\n";
#[gpui::test]
fn a_block_after_end_in_crlf_text_reads_the_line(cx: &mut TestAppContext) {
assert_eq!(code_block_after("end", cx), FENCE_AFTER_INTRO);
}
#[gpui::test]
fn a_selection_from_inside_a_crlf_reads_the_line(cx: &mut TestAppContext) {
assert_eq!(code_block_after("end shift-right", cx), FENCE_AFTER_INTRO);
}
#[gpui::test]
fn delete_table_takes_the_table_or_refuses_outside(cx: &mut TestAppContext) {
let source = "before\n\n| A | B |\n| - | - |\n| 1 | 2 |\n\nafter\n";
let (document, cx) = open(source, MarkdownMode::Source, cx);
let heard = super::format::refusals(&document, cx);
let delete = |cx: &mut gpui::VisualTestContext| {
document.update(cx, |document, cx| {
document
.edit_table(crate::documents::TableEdit::Delete, cx)
.expect("taken")
});
cx.run_until_parked();
};
document.update(cx, |document, cx| document.set_caret(4, 2, cx));
delete(cx);
assert_eq!(text(&document, cx), "before\n\nafter\n");
let editor = document.read_with(cx, |document, _| document.editor().clone());
editor.update(cx, |editor, cx| editor.undo(cx));
assert_eq!(text(&document, cx), source);
document.update(cx, |document, cx| document.set_caret(0, 2, cx));
delete(cx);
let outside = crate::documents::EditRefusal::Cells(crate::documents::TableRefusal::Outside);
assert_eq!(*heard.borrow(), [outside]);
assert_eq!(text(&document, cx), source);
}
#[gpui::test]
fn undo_follows_a_table_edit_that_closes_the_block(cx: &mut TestAppContext) {
let source = "before\n\n| A |\n| - |\n| 1 |\n\nafter\n";
let (document, cx) = open(source, MarkdownMode::Visual, cx);
press(1, Modifiers::none(), cx);
document.update(cx, |document, cx| {
document
.edit_table(crate::documents::TableEdit::Delete, cx)
.expect("taken")
});
cx.run_until_parked();
assert_eq!(text(&document, cx), "before\n\nafter\n");
let held = cx.update(|window, cx| {
let handle = gpui::Focusable::focus_handle(document.read(cx), cx);
handle.contains_focused(window, cx)
});
assert!(held, "focus stays in the document");
cx.simulate_keystrokes("secondary-z");
assert_eq!(text(&document, cx), source);
}