Files
gpui-native-kit/crates/editor/tests/diff_view.rs
T
2026-11-06 08:01:23 +07:00

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"
);
}