lv-tui 0.4.0

A reactive TUI framework for Rust
Documentation
/// Integration tests for CSS pseudo-classes (RFC 55).
use lv_tui::prelude::*;
use lv_tui::style_parser::StyleSheet;

// ── Parsing pseudo-classes ─────────────────────────────────────

#[test]
fn parse_focus_pseudo_class() {
    let sheet = StyleSheet::parse("Button:focus { fg: blue; }").unwrap();
    let rules = sheet.rules();
    assert_eq!(rules.len(), 1);
    let sel = &rules[0].selector;
    assert_eq!(sel.pseudo_classes.len(), 1);
}

#[test]
fn parse_hover_pseudo_class() {
    let sheet = StyleSheet::parse("Button:hover { bg: gray; }").unwrap();
    let rules = sheet.rules();
    assert_eq!(rules[0].selector.pseudo_classes.len(), 1);
}

#[test]
fn parse_multiple_pseudo_classes() {
    let sheet = StyleSheet::parse("Button:focus:hover { fg: white; }").unwrap();
    let sel = &sheet.rules()[0].selector;
    assert_eq!(sel.pseudo_classes.len(), 2);
}

#[test]
fn parse_pseudo_class_with_type_selector() {
    let sheet = StyleSheet::parse("Input:focus:disabled { fg: gray; }").unwrap();
    assert_eq!(sheet.rules().len(), 1);
}

// ── Resolve with WidgetState ────────────────────────────────────

#[test]
fn resolve_focus_override() {
    let sheet = StyleSheet::parse(
        "Button { fg: red; } Button:focus { fg: blue; }"
    ).unwrap();

    let state_unfocused = WidgetState::default();
    let state_focused = WidgetState { focused: true, ..Default::default() };

    let style_unfocused = sheet.resolve("Button", None, None, &state_unfocused);
    let style_focused = sheet.resolve("Button", None, None, &state_focused);

    // Focused style should have fg: blue, not red
    assert_eq!(style_focused.fg, Some(Color::Blue));
    // Unfocused should have fg: red
    assert_eq!(style_unfocused.fg, Some(Color::Red));
}

#[test]
fn resolve_pseudo_class_has_higher_priority() {
    let sheet = StyleSheet::parse(
        "Widget { bg: black; } Widget:focus { bg: white; }"
    ).unwrap();

    let state = WidgetState { focused: true, ..Default::default() };
    let style = sheet.resolve("Widget", None, None, &state);
    // Focused rule should win
    assert_eq!(style.bg, Some(Color::White));
}

#[test]
fn resolve_hover_style() {
    let sheet = StyleSheet::parse(
        "Item { bg: black; } Item:hover { bg: gray; }"
    ).unwrap();

    let state = WidgetState { hovered: true, ..Default::default() };
    let style = sheet.resolve("Item", None, None, &state);
    assert_eq!(style.bg, Some(lv_tui::style::Color::Gray));
}

// ── Backward compatibility ─────────────────────────────────────

#[test]
fn resolve_without_pseudo_classes_still_works() {
    let sheet = StyleSheet::parse(
        "Label { fg: green; bold: true; }"
    ).unwrap();

    let style = sheet.resolve("Label", None, None, &WidgetState::default());
    assert_eq!(style.fg, Some(Color::Green));
    assert!(style.bold);
}

#[test]
fn resolve_with_no_matching_pseudo_class_skips_rule() {
    let sheet = StyleSheet::parse(
        "Input { fg: white; } Input:focus { fg: cyan; }"
    ).unwrap();

    // Without focus, :focus rule should not apply
    let style = sheet.resolve("Input", None, None, &WidgetState::default());
    assert_eq!(style.fg, Some(Color::White));
}

// ── WidgetState defaults ────────────────────────────────────────

#[test]
fn widget_state_default_is_all_false() {
    let state = WidgetState::default();
    assert!(!state.focused);
    assert!(!state.hovered);
    assert!(!state.disabled);
    assert!(!state.focus_within);
}

// ── Disabled pseudo-class ──────────────────────────────────────

#[test]
fn resolve_disabled_style() {
    let sheet = StyleSheet::parse(
        "Input { fg: white; } Input:disabled { fg: gray; }"
    ).unwrap();

    let state = WidgetState { disabled: true, ..Default::default() };
    let style = sheet.resolve("Input", None, None, &state);
    assert_eq!(style.fg, Some(lv_tui::style::Color::Gray));
}

// ── Class + pseudo-class combined ──────────────────────────────

#[test]
fn resolve_class_with_pseudo_class() {
    // Class selector + hover — class selectors work with pseudo-classes
    let sheet = StyleSheet::parse(
        "Widget { fg: white; } Widget:hover { fg: cyan; }"
    ).unwrap();

    let state = WidgetState { hovered: true, ..Default::default() };
    let style = sheet.resolve("Widget", None, None, &state);
    assert_eq!(style.fg, Some(lv_tui::style::Color::Cyan));
}