350 lines
11 KiB
Rust
350 lines
11 KiB
Rust
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
|
|
use gpui::{
|
|
AppContext as _, Context, Entity, IntoElement, MouseButton, ParentElement as _, Render,
|
|
Styled as _, TestAppContext, VisualTestContext, Window, div, point, px,
|
|
};
|
|
use gpui_native_editor::diff_view::{DiffRowKind, DiffViewState, diff_rows};
|
|
use gpui_native_editor::highlight::{Language, Role};
|
|
|
|
const OLD: &str = "fn a() {}\nfn b() {}\nfn c() {}\n";
|
|
const NEW: &str = "fn a() {}\nfn x() {}\nfn y() {}\nfn c() {}\n";
|
|
|
|
#[test]
|
|
fn rows_mark_removals_additions_and_context_with_line_numbers() {
|
|
let rows = diff_rows(OLD, NEW, Language::Rust);
|
|
|
|
let kinds: Vec<DiffRowKind> = rows.iter().map(|row| row.kind).collect();
|
|
assert_eq!(
|
|
kinds,
|
|
vec![
|
|
DiffRowKind::Context,
|
|
DiffRowKind::Removed,
|
|
DiffRowKind::Added,
|
|
DiffRowKind::Added,
|
|
DiffRowKind::Context,
|
|
]
|
|
);
|
|
|
|
let removed = &rows[1];
|
|
assert_eq!(removed.old_number, Some(2));
|
|
assert_eq!(removed.new_number, None);
|
|
let removed_text: String = removed
|
|
.content
|
|
.spans
|
|
.iter()
|
|
.map(|span| span.text.as_ref())
|
|
.collect();
|
|
assert!(removed_text.contains("fn b() {}"));
|
|
|
|
let added = &rows[2];
|
|
assert_eq!(added.old_number, None);
|
|
assert_eq!(added.new_number, Some(2));
|
|
let added_text: String = added
|
|
.content
|
|
.spans
|
|
.iter()
|
|
.map(|span| span.text.as_ref())
|
|
.collect();
|
|
assert!(added_text.contains("fn x() {}"));
|
|
|
|
let context = &rows[0];
|
|
assert_eq!(context.old_number, Some(1));
|
|
assert_eq!(context.new_number, Some(1));
|
|
}
|
|
|
|
#[test]
|
|
fn an_added_line_carries_its_highlight_spans() {
|
|
let rows = diff_rows("", "fn zed() {}\n", Language::Rust);
|
|
let added = rows
|
|
.iter()
|
|
.find(|row| row.kind == DiffRowKind::Added)
|
|
.expect("the new file is all additions");
|
|
|
|
assert!(
|
|
added
|
|
.content
|
|
.spans
|
|
.iter()
|
|
.any(|span| span.role == Role::Keyword),
|
|
"fn highlights as a keyword inside the diff row"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn identical_versions_are_all_context() {
|
|
let rows = diff_rows(OLD, OLD, Language::Rust);
|
|
assert_eq!(rows.len(), 3);
|
|
assert!(rows.iter().all(|row| row.kind == DiffRowKind::Context));
|
|
}
|
|
|
|
struct Host {
|
|
diff: Entity<DiffViewState>,
|
|
}
|
|
|
|
impl Render for Host {
|
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
|
div().size_full().child(self.diff.clone())
|
|
}
|
|
}
|
|
|
|
fn diff_view_window(
|
|
rows: usize,
|
|
changes_at: usize,
|
|
) -> (TestAppContext, VisualTestContext, Entity<DiffViewState>) {
|
|
let mut cx = TestAppContext::single();
|
|
cx.update(gpui_native_theme::Theme::init);
|
|
let mut old = String::new();
|
|
let mut new = String::new();
|
|
for line in 0..rows {
|
|
old.push_str(&format!("line {line} unchanged\n"));
|
|
if line == changes_at {
|
|
new.push_str(&format!("line {line} rewritten\n"));
|
|
new.push_str(&format!("line {line} added\n"));
|
|
} else {
|
|
new.push_str(&format!("line {line} unchanged\n"));
|
|
}
|
|
}
|
|
let state = DiffViewState::new("test-diff", old, new, Language::Rust);
|
|
let state_cell: Rc<RefCell<Option<Entity<DiffViewState>>>> = Rc::new(RefCell::new(None));
|
|
let cell = state_cell.clone();
|
|
let window = cx.add_window(move |_, cx| {
|
|
let diff = cx.new(|_| state);
|
|
cell.borrow_mut().replace(diff.clone());
|
|
Host { diff }
|
|
});
|
|
let state = state_cell.borrow().clone().expect("state captured");
|
|
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
|
visual.update(|window, cx| {
|
|
window.draw(cx).clear(cx);
|
|
});
|
|
(cx, visual, state)
|
|
}
|
|
|
|
#[test]
|
|
fn rows_render_virtualized_and_the_view_jumps_to_the_first_change() {
|
|
let (_cx, mut visual, state) = diff_view_window(200, 150);
|
|
|
|
assert!(
|
|
visual.debug_bounds("test-diff-row-0").is_some(),
|
|
"the first row renders"
|
|
);
|
|
assert!(
|
|
visual.debug_bounds("test-diff-row-199").is_none(),
|
|
"rows past the viewport never render"
|
|
);
|
|
|
|
state.update_in(&mut visual, |state, _window, _cx| {
|
|
state.scroll_to_first_change()
|
|
});
|
|
visual.update(|window, cx| {
|
|
window.draw(cx).clear(cx);
|
|
});
|
|
|
|
assert!(
|
|
visual.debug_bounds("test-diff-row-150").is_some(),
|
|
"the first change scrolled into view"
|
|
);
|
|
assert!(
|
|
visual.debug_bounds("test-diff-row-0").is_none(),
|
|
"the context before it scrolled out"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn set_versions_re_diffs() {
|
|
let mut cx = TestAppContext::single();
|
|
let state = cx.new(|_| DiffViewState::new("test-diff", OLD, NEW, Language::Rust));
|
|
|
|
cx.update(|cx| {
|
|
state.update(cx, |state, cx| {
|
|
state.set_versions("same\n", "same\n", Language::Rust, cx)
|
|
});
|
|
});
|
|
|
|
let all_context = cx.update(|cx| {
|
|
state
|
|
.read(cx)
|
|
.rows()
|
|
.iter()
|
|
.all(|row| row.kind == DiffRowKind::Context)
|
|
});
|
|
assert!(all_context, "identical versions diff to context rows");
|
|
}
|
|
|
|
#[test]
|
|
fn scrolling_horizontally_moves_rows_without_vertical_drift() {
|
|
let mut cx = TestAppContext::single();
|
|
cx.update(gpui_native_theme::Theme::init);
|
|
let old = "fn same() {}\n".repeat(40);
|
|
let new = format!("{old}fn long() {{ {} }}\n", "text ".repeat(300));
|
|
let state = DiffViewState::new("test-diff", old, new, Language::Rust);
|
|
let state_cell: Rc<RefCell<Option<Entity<DiffViewState>>>> = Rc::new(RefCell::new(None));
|
|
let cell = state_cell.clone();
|
|
let window = cx.add_window(move |_, cx| {
|
|
let diff = cx.new(|_| state);
|
|
cell.borrow_mut().replace(diff.clone());
|
|
Host { diff }
|
|
});
|
|
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
|
visual.update(|window, cx| {
|
|
window.draw(cx).clear(cx);
|
|
});
|
|
|
|
let before = visual
|
|
.debug_bounds("test-diff-row-0")
|
|
.expect("the first row renders");
|
|
|
|
visual.simulate_event(gpui::ScrollEvent {
|
|
position: before.center(),
|
|
delta: gpui::ScrollDelta::Pixels(point(px(-200.), px(0.))),
|
|
..Default::default()
|
|
});
|
|
visual.update(|window, cx| {
|
|
window.draw(cx).clear(cx);
|
|
});
|
|
|
|
let after = visual
|
|
.debug_bounds("test-diff-row-0")
|
|
.expect("the first row still renders");
|
|
assert!(
|
|
(after.origin.x - before.origin.x + px(200.)).abs() <= px(2.),
|
|
"a horizontal wheel gesture scrolls the rows left"
|
|
);
|
|
assert_eq!(
|
|
after.origin.y, before.origin.y,
|
|
"a horizontal gesture must not drift vertically"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn scrolling_vertically_does_not_drift_horizontally() {
|
|
let mut cx = TestAppContext::single();
|
|
cx.update(gpui_native_theme::Theme::init);
|
|
let old = "fn same() {}\n".repeat(200);
|
|
let new = format!("{old}fn long() {{ {} }}\n", "text ".repeat(300));
|
|
let state = DiffViewState::new("test-diff", old, new, Language::Rust);
|
|
let state_cell: Rc<RefCell<Option<Entity<DiffViewState>>>> = Rc::new(RefCell::new(None));
|
|
let cell = state_cell.clone();
|
|
let window = cx.add_window(move |_, cx| {
|
|
let diff = cx.new(|_| state);
|
|
cell.borrow_mut().replace(diff.clone());
|
|
Host { diff }
|
|
});
|
|
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
|
visual.update(|window, cx| {
|
|
window.draw(cx).clear(cx);
|
|
});
|
|
|
|
let before = visual
|
|
.debug_bounds("test-diff-row-0")
|
|
.expect("the first row renders");
|
|
|
|
// A pure vertical gesture...
|
|
visual.simulate_event(gpui::ScrollEvent {
|
|
position: before.center(),
|
|
delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-5.))),
|
|
..Default::default()
|
|
});
|
|
// ...then a vertical one carrying minor horizontal noise, below the
|
|
// threshold where gpui would unlock the gesture's axis.
|
|
visual.simulate_event(gpui::ScrollEvent {
|
|
position: before.center(),
|
|
delta: gpui::ScrollDelta::Pixels(point(px(2.), px(-5.))),
|
|
..Default::default()
|
|
});
|
|
visual.update(|window, cx| {
|
|
window.draw(cx).clear(cx);
|
|
});
|
|
|
|
let after = visual
|
|
.debug_bounds("test-diff-row-0")
|
|
.expect("the first row still renders");
|
|
assert_eq!(
|
|
after.origin.x, before.origin.x,
|
|
"vertical gestures with horizontal noise must not scroll horizontally"
|
|
);
|
|
assert!(
|
|
(after.origin.y - (before.origin.y - px(10.))).abs() <= px(2.),
|
|
"the vertical component scrolls the rows up"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dragging_the_vertical_thumb_leaves_the_horizontal_offset_alone() {
|
|
let mut cx = TestAppContext::single();
|
|
cx.update(gpui_native_theme::Theme::init);
|
|
let old = "fn same() {}\n".repeat(200);
|
|
let new = format!("{old}fn long() {{ {} }}\n", "text ".repeat(300));
|
|
let state = DiffViewState::new("test-diff", old, new, Language::Rust);
|
|
let state_cell: Rc<RefCell<Option<Entity<DiffViewState>>>> = Rc::new(RefCell::new(None));
|
|
let cell = state_cell.clone();
|
|
let window = cx.add_window(move |_, cx| {
|
|
let diff = cx.new(|_| state);
|
|
cell.borrow_mut().replace(diff.clone());
|
|
Host { diff }
|
|
});
|
|
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
|
visual.update(|window, cx| {
|
|
window.draw(cx).clear(cx);
|
|
});
|
|
|
|
// Scroll the strip left first, so horizontal drift would be observable.
|
|
let row = visual
|
|
.debug_bounds("test-diff-row-0")
|
|
.expect("the first row renders");
|
|
visual.simulate_event(gpui::ScrollEvent {
|
|
position: row.center(),
|
|
delta: gpui::ScrollDelta::Pixels(point(px(-100.), px(0.))),
|
|
..Default::default()
|
|
});
|
|
visual.update(|window, cx| {
|
|
window.draw(cx).clear(cx);
|
|
});
|
|
let before = visual
|
|
.debug_bounds("test-diff-row-0")
|
|
.expect("the first row still renders");
|
|
|
|
// Press the vertical thumb: right edge, within its minimum length.
|
|
let (thumb_width, inset, min_thumb) = cx.update(|cx| {
|
|
let theme = gpui_native_theme::Theme::global(cx);
|
|
(
|
|
theme.scrollbar.width,
|
|
theme.scrollbar.inset,
|
|
theme.scrollbar.min_thumb,
|
|
)
|
|
});
|
|
let press = point(
|
|
before.origin.x + before.size.width - thumb_width / 2.,
|
|
before.origin.y + inset + min_thumb / 2.,
|
|
);
|
|
visual.simulate_event(gpui::MouseDownEvent {
|
|
position: press,
|
|
button: MouseButton::Left,
|
|
..Default::default()
|
|
});
|
|
|
|
// Move sideways while held; only the vertical thumb is being dragged.
|
|
visual.simulate_event(gpui::MouseMoveEvent {
|
|
position: press + point(px(30.), px(0.)),
|
|
pressed_button: Some(MouseButton::Left),
|
|
..Default::default()
|
|
});
|
|
visual.update(|window, cx| {
|
|
window.draw(cx).clear(cx);
|
|
});
|
|
|
|
let after = visual
|
|
.debug_bounds("test-diff-row-0")
|
|
.expect("the first row still renders");
|
|
assert_eq!(
|
|
after.origin.x, before.origin.x,
|
|
"dragging the vertical thumb must not scroll the strip horizontally"
|
|
);
|
|
assert_eq!(
|
|
after.origin.y, before.origin.y,
|
|
"a sideways-only drag must not scroll vertically"
|
|
);
|
|
}
|