forked from reya/gpui-native-kit
191 lines
6.4 KiB
Rust
191 lines
6.4 KiB
Rust
use gpui::{
|
||
Context, InteractiveElement as _, IntoElement, Modifiers, ParentElement as _, Render,
|
||
ScrollDelta, ScrollEvent, ScrollHandle, SharedString, Styled as _, TestAppContext,
|
||
VisualTestContext, Window, div, point, px,
|
||
};
|
||
use gpui_native_core::scroll_area::ScrollArea;
|
||
use gpui_native_theme::Theme;
|
||
|
||
struct Harness {
|
||
scroll: ScrollHandle,
|
||
rows: usize,
|
||
}
|
||
|
||
impl Render for Harness {
|
||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||
div().size_full().child(
|
||
ScrollArea::new("test-scroll")
|
||
.handle(self.scroll.clone())
|
||
.h(px(200.))
|
||
.w(px(300.))
|
||
.child(div().flex().flex_col().children((0..self.rows).map(|row| {
|
||
div()
|
||
.h(px(20.))
|
||
.child(SharedString::from(format!("row {row}")))
|
||
}))),
|
||
)
|
||
}
|
||
}
|
||
|
||
/// A 300×200 scroll area over `rows` 20px rows, with the handle the test
|
||
/// can inspect directly.
|
||
fn scroll_area_window(rows: usize) -> (TestAppContext, VisualTestContext, ScrollHandle) {
|
||
let mut cx = TestAppContext::single();
|
||
cx.update(Theme::init);
|
||
let handle = ScrollHandle::new();
|
||
let handle_for_view = handle.clone();
|
||
let window = cx.add_window(move |_, _| Harness {
|
||
scroll: handle_for_view,
|
||
rows,
|
||
});
|
||
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
||
visual.update(|window, cx| {
|
||
window.draw(cx).clear(cx);
|
||
});
|
||
(cx, visual, handle)
|
||
}
|
||
|
||
#[test]
|
||
fn wheel_scrolling_moves_the_offset_and_stays_inside_the_content() {
|
||
let (_cx, mut visual, handle) = scroll_area_window(30);
|
||
|
||
// 30 rows × 20px = 600px of content in a 200px viewport.
|
||
visual.simulate_event(ScrollEvent {
|
||
position: point(px(100.), px(100.)),
|
||
delta: ScrollDelta::Pixels(point(px(0.), px(-60.))),
|
||
modifiers: Modifiers::default(),
|
||
..Default::default()
|
||
});
|
||
|
||
let offset = handle.offset().y;
|
||
assert_eq!(offset, px(-60.));
|
||
|
||
// Wheeling far past the end clamps to the content.
|
||
for _ in 0..10 {
|
||
visual.simulate_event(ScrollEvent {
|
||
position: point(px(100.), px(100.)),
|
||
delta: ScrollDelta::Pixels(point(px(0.), px(-60.))),
|
||
modifiers: Modifiers::default(),
|
||
..Default::default()
|
||
});
|
||
}
|
||
// max_offset = 600 - 200 = 400.
|
||
assert_eq!(handle.offset().y, px(-400.));
|
||
}
|
||
|
||
#[test]
|
||
fn the_content_starts_unscrolled_and_horizontal_axes_scroll_x() {
|
||
let (_cx, mut visual, handle) = scroll_area_window(30);
|
||
assert_eq!(handle.offset(), gpui::point(px(0.), px(0.)));
|
||
|
||
// A vertical-only area ignores horizontal wheel deltas.
|
||
visual.simulate_event(ScrollEvent {
|
||
position: point(px(100.), px(100.)),
|
||
delta: ScrollDelta::Pixels(point(px(-60.), px(0.))),
|
||
modifiers: Modifiers::default(),
|
||
..Default::default()
|
||
});
|
||
assert_eq!(handle.offset().x, px(0.));
|
||
}
|
||
|
||
/// The gallery's exact shell: a window-filling root, a window-filling scroll
|
||
/// area over padded flex-column content that towers past the viewport.
|
||
struct GalleryShell {
|
||
scroll: ScrollHandle,
|
||
}
|
||
|
||
impl Render for GalleryShell {
|
||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||
div().size_full().child(
|
||
ScrollArea::new("gallery-scroll")
|
||
.handle(self.scroll.clone())
|
||
.size_full()
|
||
.child(
|
||
div()
|
||
.p_6()
|
||
.flex()
|
||
.flex_col()
|
||
.gap_5()
|
||
.children((0..12).map(|section| {
|
||
div()
|
||
.h(px(220.))
|
||
.child(SharedString::from(format!("section {section}")))
|
||
})),
|
||
),
|
||
)
|
||
}
|
||
}
|
||
|
||
/// A window-filling scroll area with `p_4`, over one full-width row.
|
||
struct PaddedShell {
|
||
scroll: ScrollHandle,
|
||
}
|
||
|
||
impl Render for PaddedShell {
|
||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||
div()
|
||
.size_full()
|
||
.debug_selector(|| "padded-root".to_string())
|
||
.child(
|
||
ScrollArea::new("padded-scroll")
|
||
.handle(self.scroll.clone())
|
||
.size_full()
|
||
.p_4()
|
||
.child(
|
||
div()
|
||
.debug_selector(|| "padded-row".to_string())
|
||
.h(px(20.))
|
||
.w_full(),
|
||
),
|
||
)
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn padding_insets_the_content_exactly_once() {
|
||
let mut cx = TestAppContext::single();
|
||
cx.update(Theme::init);
|
||
let handle = ScrollHandle::new();
|
||
let handle_for_view = handle.clone();
|
||
let window = cx.add_window(move |_, _| PaddedShell {
|
||
scroll: handle_for_view,
|
||
});
|
||
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
||
visual.update(|window, cx| {
|
||
window.draw(cx).clear(cx);
|
||
});
|
||
|
||
let root = visual.debug_bounds("padded-root").expect("root rendered");
|
||
let row = visual.debug_bounds("padded-row").expect("row rendered");
|
||
// p_4 = 16px: the padding reaches only the wrapper, so the content is
|
||
// inset once, not once per wrapper and scroll pane.
|
||
assert_eq!(row.origin.x - root.origin.x, px(16.));
|
||
assert_eq!(root.size.width - row.size.width, px(32.));
|
||
}
|
||
|
||
#[test]
|
||
fn a_gallery_style_window_fill_scroll_area_scrolls_tall_flex_content() {
|
||
let mut cx = TestAppContext::single();
|
||
cx.update(Theme::init);
|
||
let handle = ScrollHandle::new();
|
||
let handle_for_view = handle.clone();
|
||
let window = cx.add_window(move |_, _| GalleryShell {
|
||
scroll: handle_for_view,
|
||
});
|
||
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
||
visual.update(|window, cx| {
|
||
window.draw(cx).clear(cx);
|
||
});
|
||
|
||
// 12 sections × 220px + padding towers past the window; a wheel event
|
||
// over the middle of the viewport must scroll the root area.
|
||
visual.simulate_event(ScrollEvent {
|
||
position: point(px(300.), px(300.)),
|
||
delta: ScrollDelta::Pixels(point(px(0.), px(-60.))),
|
||
modifiers: Modifiers::default(),
|
||
..Default::default()
|
||
});
|
||
assert_eq!(handle.offset().y, px(-60.));
|
||
assert!(handle.max_offset().y > px(1000.));
|
||
}
|