gpui-box-kit 0.1.1

GPUI Box Kit design-system components and interaction primitives
Documentation
use std::cell::RefCell;
use std::rc::Rc;

use gpui::{IntoElement, TestAppContext};
use gpui_kit::prelude::*;
use gpui_kit_semantics::Role;
use gpui_kit_testkit::harness::Harness;

fn row(index: usize) -> TreeGridRow {
    let mut row = TreeGridRow::new(format!("node-{index:04}"), if index == 0 { 1 } else { 2 })
        .text(format!("Node {index}"))
        .cell(
            "name",
            Cell::new(format!("Node {index}"))
                .text(format!("Node {index}"))
                .published(true),
        )
        .cell("value", format!("Value {index}"));
    if index == 0 {
        row = row.branch(true);
    } else {
        row = row.parent("node-0000");
    }
    row
}

fn tree_grid(cx: &mut TestAppContext, disabled: bool) -> (Harness, Rc<RefCell<Vec<String>>>) {
    let calls = Rc::new(RefCell::new(Vec::new()));
    let sink = calls.clone();
    let harness = Harness::new(cx, gpui_kit::install, move |_, _| {
        let select = sink.clone();
        let expand = sink.clone();
        TreeGrid::new("data.tree-grid", 1000, |index, _, _| row(index))
            .columns([
                GridColumn::new("name", "Name").flex(1.0),
                GridColumn::new("value", "Value").fixed(120.0),
            ])
            .selected("node-0001")
            .visible_rows(6)
            .disabled(disabled)
            .on_select(move |id, _, _| select.borrow_mut().push(format!("select:{id}")))
            .on_expand(move |id, open, _, _| {
                expand.borrow_mut().push(format!("expand:{id}:{open}"))
            })
            .into_any_element()
    });
    (harness, calls)
}

#[gpui::test]
fn publishes_bounded_treegrid_hierarchy(cx: &mut TestAppContext) {
    let (mut harness, _) = tree_grid(cx, false);
    let root = harness
        .node("data.tree-grid")
        .expect("treegrid root should publish");
    assert_eq!(root.role, Role::TreeGrid);
    assert_eq!(root.value.as_deref(), Some("1000"));
    let rows = harness
        .snapshot()
        .children_of("data.tree-grid")
        .into_iter()
        .filter(|n| n.role == Role::Row)
        .count();
    assert!((1..24).contains(&rows));
    let row = harness
        .node("data.tree-grid.node-0001")
        .expect("visible row should publish");
    assert_eq!(row.level, Some(2));
    assert_eq!(
        harness
            .node("data.tree-grid.node-0001.name")
            .expect("visible name cell should publish")
            .role,
        Role::GridCell
    );
    assert_eq!(
        harness
            .node("data.tree-grid.node-0001.value")
            .expect("visible value cell should publish")
            .role,
        Role::GridCell
    );
    assert!(harness.node("data.tree-grid.node-0900").is_none());
}

#[gpui::test]
fn logical_keys_emit_caller_owned_intents_and_disabled_rows_do_not(cx: &mut TestAppContext) {
    let (mut harness, calls) = tree_grid(cx, false);
    harness.click("data.tree-grid.node-0001");
    harness.keystrokes("left");
    assert!(calls.borrow().iter().any(|call| call == "select:node-0000"));
    harness.keystrokes("right");
    assert!(calls.borrow().iter().any(|call| call == "select:node-0001"));

    let (mut disabled, disabled_calls) = tree_grid(cx, true);
    disabled.click("data.tree-grid.node-0001");
    disabled.keystrokes("left right down");
    assert!(disabled_calls.borrow().is_empty());
}

#[gpui::test]
fn logical_start_column_and_disclosure_follow_reading_direction(cx: &mut TestAppContext) {
    let (mut harness, _) = tree_grid(cx, false);
    let ltr_name = harness
        .bounds("data.tree-grid.node-0000.name")
        .expect("LTR name cell");
    let ltr_value = harness
        .bounds("data.tree-grid.node-0000.value")
        .expect("LTR value cell");
    let ltr_disclosure = harness
        .bounds("data.tree-grid.node-0000.expand")
        .expect("LTR disclosure");
    assert!(ltr_name.left() < ltr_value.left());
    assert!(ltr_disclosure.left() < ltr_name.center().x);

    harness.update(|_, cx| set_layout_direction(LayoutDirection::RightToLeft, cx));
    let rtl_name = harness
        .bounds("data.tree-grid.node-0000.name")
        .expect("RTL name cell");
    let rtl_value = harness
        .bounds("data.tree-grid.node-0000.value")
        .expect("RTL value cell");
    let rtl_disclosure = harness
        .bounds("data.tree-grid.node-0000.expand")
        .expect("RTL disclosure");
    assert!(rtl_name.left() > rtl_value.left());
    assert!(rtl_disclosure.left() > rtl_name.center().x);
}