bernardo-tui 0.2.7

A keyboard-only, distraction-free TUI widget library
Documentation
use std::sync::{Arc, Mutex};

use crate::fs::filesystem_front::FilesystemFront;
use crate::fs::mock_fs::MockFS;
use crate::fs::path::SPath;
use crate::io::input_event::InputEvent;
use crate::io::keys::Keycode;
use crate::spath;
use crate::widget::widget::Widget;
use crate::widgets::spath_tree_view_node::FileTreeNode;
use crate::widgets::tree_view::tree_view::TreeViewWidget;

#[test]
fn test_set_path() {
    let mockfs = MockFS::new("/tmp")
        .with_file("folder1/folder2/file1.txt", "some text")
        .with_file("folder1/folder3/moulder.txt", "truth is out there")
        .to_fsf();

    let mut widget = TreeViewWidget::<SPath, FileTreeNode>::new(FileTreeNode::new(spath!(mockfs, "folder1").unwrap()));

    assert_eq!(
        widget.is_expanded(&spath!(mockfs, "folder1", "folder2", "file1.txt").unwrap()),
        false
    );

    assert_eq!(
        widget.expand_path(&spath!(mockfs, "folder1", "folder2", "file1.txt").unwrap()),
        true
    );
    assert_eq!(widget.is_expanded(&spath!(mockfs, "folder1").unwrap()), true);
    assert_eq!(widget.is_expanded(&spath!(mockfs, "folder1", "folder2").unwrap()), true);
    assert_eq!(widget.is_expanded(&spath!(mockfs, "folder1", "folder3").unwrap()), false);
}

// Regression test: pressing Enter on a non-leaf node used to panic when the streaming
// promise had fewer cached items than self.highlighted (e.g. highlighted=51, cache=50).
// Fix: HitEnter now toggles the expanded set first, fires the callback while the old
// promise is still intact (get_highlighted() valid), then resets the promise.
#[test]
fn test_flip_expand_callback_receives_correct_item() {
    let mockfs = MockFS::new("/tmp")
        .with_file("root/subdir/inner.txt", "x")
        .with_file("root/file_a.txt", "a")
        .with_file("root/file_b.txt", "b")
        .to_fsf();

    let root = spath!(mockfs, "root").unwrap();
    let subdir = spath!(mockfs, "root", "subdir").unwrap();

    let captured: Arc<Mutex<Option<SPath>>> = Arc::new(Mutex::new(None));
    let captured2 = captured.clone();

    let mut widget = TreeViewWidget::<SPath, FileTreeNode>::new(FileTreeNode::new(root)).with_on_flip_expand(Box::new(move |widget| {
        let (_, item) = widget.get_highlighted();
        *captured2.lock().unwrap() = Some(item.spath().clone());
        None
    }));

    widget.expand_root();

    // Drain the streaming promise so items are visible.
    widget.prelayout();
    std::thread::sleep(std::time::Duration::from_millis(100));
    widget.prelayout();

    // Navigate to the subdir node.
    assert!(widget.set_selected(&subdir), "subdir must be visible after expand_root");

    // Press Enter — this triggers flip_expand on the subdir directory.
    // Before the fix this panicked: "got N items, highlighted is N+1"
    // because the callback called get_highlighted() on the post-reset promise.
    widget.act_on(InputEvent::KeyInput(Keycode::Enter.to_key()));

    assert_eq!(
        *captured.lock().unwrap(),
        Some(subdir),
        "callback must receive the item that was highlighted when Enter was pressed"
    );
}