sqlly-datatable 2.0.2

Configurable virtualized data grid component for the GPUI toolkit.
Documentation
#![allow(clippy::expect_used)]

use gpui::{point, px, size, Bounds, TestAppContext};
use sqlly_datatable::pivot::PivotHitResult;
use sqlly_datatable::{
    CellValue, Column, ColumnKind, GridData, GridTab, PivotConfig, PivotSidebarPosition,
    SqllyDataTable,
};

#[gpui::test]
fn locked_pivot_stays_visible_but_rejects_activation(cx: &mut TestAppContext) {
    let (view, cx) = cx.add_window_view(|_window, cx| {
        let data = GridData::new(vec![], vec![]).expect("empty grid");
        SqllyDataTable::builder(data)
            .pivot(PivotConfig::default())
            .build(cx)
    });

    view.update(cx, |table, cx| {
        assert!(table.pivot_state().is_some(), "Pivot tab remains installed");
        table.set_pivot_locked(true, Some("Loading".to_string()));
        table.set_active_tab(GridTab::Pivot, cx);
        assert!(table.pivot_locked());
        assert_eq!(table.active_tab(), GridTab::Grid);

        table.set_pivot_locked(false, None);
        table.set_active_tab(GridTab::Pivot, cx);
        assert!(!table.pivot_locked());
        assert_eq!(table.active_tab(), GridTab::Pivot);
    });
}

#[gpui::test]
fn pivot_sidebar_layout_is_configurable(cx: &mut TestAppContext) {
    let (view, cx) = cx.add_window_view(|_window, cx| {
        let data = GridData::new(vec![], vec![]).expect("empty grid");
        SqllyDataTable::builder(data)
            .pivot(PivotConfig::default())
            .pivot_sidebar_position(PivotSidebarPosition::Right)
            .pivot_sidebar_collapsed(true)
            .pivot_sidebar_width(320.0)
            .build(cx)
    });

    view.update(cx, |table, cx| {
        assert_eq!(table.pivot_sidebar_position(), PivotSidebarPosition::Right);
        assert!(table.pivot_sidebar_collapsed());
        assert_eq!(table.pivot_sidebar_width(), 320.0);

        table.set_pivot_sidebar_position(PivotSidebarPosition::Left);
        table.set_pivot_sidebar_collapsed(false);
        table.set_pivot_sidebar_width(0.0);

        assert_eq!(table.pivot_sidebar_width(), 180.0);

        table.set_pivot_sidebar_width(10_000.0);

        assert_eq!(table.pivot_sidebar_position(), PivotSidebarPosition::Left);
        assert!(!table.pivot_sidebar_collapsed());
        assert_eq!(table.pivot_sidebar_width(), 480.0);
        table.set_active_tab(GridTab::Pivot, cx);
    });
    cx.run_until_parked();
}

#[gpui::test]
fn pivot_dimensions_are_configurable_readable_and_resizable(cx: &mut TestAppContext) {
    let (view, cx) = cx.add_window_view(|_window, cx| {
        let data = GridData::new(
            vec![
                Column::new("region", ColumnKind::Text, 100.0),
                Column::new("year", ColumnKind::Integer, 80.0),
                Column::new("sales", ColumnKind::Integer, 100.0),
            ],
            vec![
                vec![
                    CellValue::Text("East".into()),
                    CellValue::Integer(2025),
                    CellValue::Integer(10),
                ],
                vec![
                    CellValue::Text("West".into()),
                    CellValue::Integer(2025),
                    CellValue::Integer(20),
                ],
            ],
        )
        .expect("rectangular pivot data");
        let config = PivotConfig {
            row_fields: vec![0],
            column_fields: vec![1],
            value_field: Some(2),
            ..PivotConfig::default()
        };
        SqllyDataTable::builder(data)
            .pivot(config)
            .pivot_row_height(32.0)
            .pivot_column_width(180.0)
            .build(cx)
    });

    view.update(cx, |table, cx| {
        let pivot = table.pivot_state().expect("pivot state");
        pivot.update(cx, |state, _cx| {
            assert_eq!(state.row_height(), 32.0);
            assert_eq!(state.column_width(), 180.0);

            state.set_row_height(1.0);
            state.set_column_width(1.0);
            assert_eq!(state.row_height(), 18.0);
            assert_eq!(state.column_width(), 40.0);

            state.set_row_height(32.0);
            state.set_column_width(180.0);
            state.bounds = Bounds {
                origin: point(px(0.0), px(0.0)),
                size: size(px(800.0), px(600.0)),
            };

            let column_edge = point(px(state.row_header_width + 180.0), px(10.0));
            assert_eq!(
                state.hit_test(column_edge),
                PivotHitResult::ColBorder { col: 0 }
            );
            state.handle_mouse_down(column_edge, false);
            state.handle_mouse_move(point(px(f32::from(column_edge.x) + 20.0), px(10.0)), true);
            state.handle_mouse_up();
            assert_eq!(state.column_width(), 200.0);

            let row_edge = point(px(10.0), px(state.header_height() + 32.0));
            assert_eq!(
                state.hit_test(row_edge),
                PivotHitResult::RowBorder { row: 0 }
            );
            state.handle_mouse_down(row_edge, false);
            state.handle_mouse_move(point(px(10.0), px(f32::from(row_edge.y) + 8.0)), true);
            state.handle_mouse_up();
            assert_eq!(state.row_height(), 40.0);
        });
    });
}