#![allow(clippy::expect_used)]
use gpui::{px, Point, TestAppContext};
use sqlly_datatable::{
CellValue, Column, ColumnKind, GridConfig, GridData, Selection, SqllyDataTable,
};
fn columns() -> Vec<Column> {
vec![
Column {
name: "id".into(),
kind: ColumnKind::Integer,
width: 100.0,
},
Column {
name: "name".into(),
kind: ColumnKind::Text,
width: 200.0,
},
]
}
fn row(i: i64) -> Vec<CellValue> {
vec![CellValue::Integer(i), CellValue::Text(format!("row{i}"))]
}
fn rows(range: std::ops::Range<i64>) -> Vec<Vec<CellValue>> {
range.map(row).collect()
}
const TOTAL: usize = 1_000_000;
#[gpui::test]
fn window_presents_virtual_total_with_resident_slice(cx: &mut TestAppContext) {
let (view, cx) = cx.add_window_view(|_window, cx| {
SqllyDataTable::builder(GridData::new(columns(), vec![]).expect("rectangular"))
.config(GridConfig::default())
.build(cx)
});
cx.run_until_parked();
view.update(cx, |v, cx| {
v.state.update(cx, |s, _cx| {
s.set_row_window(TOTAL, 1000, rows(1000..1200))
.expect("rectangular window");
});
});
view.read_with(cx, |v, cx| {
let s = v.state.read(cx);
assert_eq!(s.display_row_count(), TOTAL, "scroll space is the total");
assert_eq!(s.data.rows.len(), 200, "only the window is resident");
assert_eq!(s.resident_row_for_display(1005), Some(5));
assert_eq!(
s.data.rows[5][1],
CellValue::Text("row1005".into()),
"mapping lands on the right data"
);
assert_eq!(s.resident_row_for_display(0), None);
assert_eq!(s.resident_row_for_display(500_000), None);
assert_eq!(s.resident_row_for_display(TOTAL), None);
});
}
#[gpui::test]
fn window_replacement_repositions_the_slice(cx: &mut TestAppContext) {
let (view, cx) = cx.add_window_view(|_window, cx| {
SqllyDataTable::builder(GridData::new(columns(), vec![]).expect("rectangular"))
.config(GridConfig::default())
.build(cx)
});
cx.run_until_parked();
view.update(cx, |v, cx| {
v.state.update(cx, |s, _cx| {
s.set_row_window(TOTAL, 0, rows(0..100)).expect("window 1");
s.set_row_window(TOTAL, 5000, rows(5000..5100))
.expect("window 2");
});
});
view.read_with(cx, |v, cx| {
let s = v.state.read(cx);
assert_eq!(s.resident_row_for_display(0), None, "old window evicted");
assert_eq!(s.resident_row_for_display(5050), Some(50));
assert_eq!(s.data.rows[50][0], CellValue::Integer(5050));
});
}
#[gpui::test]
fn sort_and_filter_are_disabled_while_windowed(cx: &mut TestAppContext) {
let (view, cx) = cx.add_window_view(|_window, cx| {
SqllyDataTable::builder(GridData::new(columns(), vec![]).expect("rectangular"))
.config(GridConfig::default())
.build(cx)
});
cx.run_until_parked();
view.update(cx, |v, cx| {
v.state.update(cx, |s, _cx| {
s.set_row_window(TOTAL, 0, rows(0..100)).expect("window");
s.toggle_sort(0);
assert_eq!(s.sort, None, "toggle_sort must be a no-op when windowed");
s.recompute();
assert_eq!(
s.display_indices.as_slice(),
(0..100).collect::<Vec<_>>().as_slice(),
"recompute keeps the identity order over the resident window"
);
});
});
}
#[gpui::test]
fn selection_and_copy_clamp_to_the_resident_window(cx: &mut TestAppContext) {
let (view, cx) = cx.add_window_view(|_window, cx| {
SqllyDataTable::builder(GridData::new(columns(), vec![]).expect("rectangular"))
.config(GridConfig::default())
.build(cx)
});
cx.run_until_parked();
view.update(cx, |v, cx| {
v.state.update(cx, |s, cx| {
s.set_row_window(TOTAL, 100, rows(100..200))
.expect("window");
s.selection = Selection::CellRange(50, 0, 250, 1);
s.copy_selection(false, cx);
});
});
cx.run_until_parked();
let text = cx
.update(|_, cx| cx.read_from_clipboard().and_then(|i| i.text()))
.expect("clipboard has text");
let lines: Vec<&str> = text.lines().collect();
assert_eq!(lines.len(), 100, "one line per resident row, got {lines:?}");
assert!(
lines[0].ends_with("\trow100"),
"first copied line must be resident row 100, got {:?}",
lines[0]
);
assert!(
lines[99].ends_with("\trow199"),
"last copied line must be resident row 199, got {:?}",
lines[99]
);
}
#[gpui::test]
fn select_all_spans_the_virtual_total(cx: &mut TestAppContext) {
let (view, cx) = cx.add_window_view(|_window, cx| {
SqllyDataTable::builder(GridData::new(columns(), vec![]).expect("rectangular"))
.config(GridConfig::default())
.build(cx)
});
cx.run_until_parked();
view.update(cx, |v, cx| {
v.state.update(cx, |s, _cx| {
s.set_row_window(TOTAL, 0, rows(0..100)).expect("window");
s.select_all();
assert_eq!(
s.selection,
Selection::CellRange(0, 0, TOTAL - 1, 1),
"select-all covers the virtual set"
);
});
});
}
#[gpui::test]
fn scrolled_viewport_reports_virtual_visible_range_and_paints(cx: &mut TestAppContext) {
let (view, cx) = cx.add_window_view(|_window, cx| {
SqllyDataTable::builder(GridData::new(columns(), vec![]).expect("rectangular"))
.config(GridConfig::default())
.build(cx)
});
cx.run_until_parked();
view.update(cx, |v, cx| {
v.state.update(cx, |s, cx| {
s.set_row_window(TOTAL, 0, rows(0..100)).expect("window");
let row_h = s.row_height;
s.scroll_handle.set_offset(Point {
x: px(0.0),
y: px(500_000.0 * row_h),
});
cx.notify();
});
cx.notify();
});
cx.run_until_parked();
view.read_with(cx, |v, cx| {
let s = v.state.read(cx);
let (first, last) = s.visible_row_range();
assert!(
(499_000..=501_000).contains(&first),
"visible range must be in virtual space near the scroll target, got {first}"
);
assert!(
last > first,
"range must be non-empty, got ({first},{last})"
);
});
}
#[gpui::test]
fn append_rows_extends_the_virtual_total_when_windowed(cx: &mut TestAppContext) {
let (view, cx) = cx.add_window_view(|_window, cx| {
SqllyDataTable::builder(GridData::new(columns(), vec![]).expect("rectangular"))
.config(GridConfig::default())
.build(cx)
});
cx.run_until_parked();
view.update(cx, |v, cx| {
v.state.update(cx, |s, _cx| {
s.set_row_window(1000, 900, rows(900..1000))
.expect("window");
s.append_rows(rows(1000..1010)).expect("append");
assert_eq!(s.display_row_count(), 1010, "total grows with appends");
assert_eq!(s.resident_row_for_display(1005), Some(105));
});
});
}