rnk 0.19.3

A React-like declarative terminal UI framework for Rust, inspired by Ink and Bubbletea
Documentation
use rnk::hooks::use_local_storage_with_dir;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

fn unique_temp_dir(test_name: &str) -> PathBuf {
    let suffix = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("system clock should be after unix epoch")
        .as_nanos();
    let dir = std::env::temp_dir().join(format!("rnk-{test_name}-{suffix}"));
    fs::create_dir_all(&dir).expect("failed to create temp test dir");
    dir
}

fn cleanup(path: &Path) {
    let _ = fs::remove_dir_all(path);
}

#[test]
fn local_storage_parent_directory_key_does_not_read_outside_storage_dir() {
    let root = unique_temp_dir("parent-read");
    let storage_dir = root.join("storage");
    fs::create_dir_all(&storage_dir).expect("failed to create storage dir");
    fs::write(root.join("outside"), "secret").expect("failed to create outside file");

    let storage = use_local_storage_with_dir("../outside", "default", &storage_dir);
    let value = storage.get();

    cleanup(&root);
    assert_eq!(value, "default");
}

#[test]
fn local_storage_parent_directory_key_does_not_write_outside_storage_dir() {
    let root = unique_temp_dir("parent-write");
    let storage_dir = root.join("storage");
    let outside = root.join("outside");

    let storage = use_local_storage_with_dir("../outside", "default", &storage_dir);
    storage.set("secret");
    let escaped = fs::read_to_string(&outside).ok();

    cleanup(&root);
    assert!(escaped.is_none());
}

#[test]
fn local_storage_absolute_key_does_not_write_outside_storage_dir() {
    let root = unique_temp_dir("absolute-write");
    let storage_dir = root.join("storage");
    let outside = root.join("outside");

    let storage = use_local_storage_with_dir(outside.to_string_lossy(), "default", &storage_dir);
    storage.set("secret");
    let escaped = fs::read_to_string(&outside).ok();

    cleanup(&root);
    assert!(escaped.is_none());
}

#[test]
fn local_storage_separator_key_is_stored_as_single_safe_file() {
    let root = unique_temp_dir("separator-write");
    let storage_dir = root.join("storage");

    let storage = use_local_storage_with_dir("nested/key", "default", &storage_dir);
    storage.set("secret");
    let nested_dir_exists = storage_dir.join("nested").exists();
    let file_count = fs::read_dir(&storage_dir)
        .expect("storage dir should exist")
        .count();

    cleanup(&root);
    assert!(!nested_dir_exists);
    assert_eq!(file_count, 1);
}

#[test]
fn local_storage_safe_key_preserves_plain_file_name() {
    let root = unique_temp_dir("safe-key");
    let storage_dir = root.join("storage");

    let storage = use_local_storage_with_dir("theme_1", "default", &storage_dir);
    storage.set("dark");
    let plain_file_value = fs::read_to_string(storage_dir.join("theme_1")).ok();

    cleanup(&root);
    assert_eq!(plain_file_value.as_deref(), Some("dark"));
}

#[test]
fn local_storage_remove_does_not_delete_outside_storage_dir() {
    let root = unique_temp_dir("parent-remove");
    let storage_dir = root.join("storage");
    fs::create_dir_all(&storage_dir).expect("failed to create storage dir");
    let outside = root.join("outside");
    fs::write(&outside, "secret").expect("failed to create outside file");

    let storage = use_local_storage_with_dir("../outside", "default", &storage_dir);
    storage.remove();
    let outside_value = fs::read_to_string(&outside).ok();

    cleanup(&root);
    assert_eq!(outside_value.as_deref(), Some("secret"));
}