facett-core 0.1.2

facett — visual kernel: render a node/edge Scene into egui (wgpu fast path to come)
Documentation
//! Theme-level tests: the WCAG contrast gate (all presets, light+dark), serde
//! round-trip (theme + remapped key), and `apply` taking effect + publishing the
//! legacy palette (the COH-1 bridge). The 3-preset effects-off diff (§25) lands
//! fully in M3; M1 asserts the gate + apply + serde here.

use super::*;

/// Every preset light+dark, for gate sweeps.
fn all_presets() -> Vec<Theme> {
    vec![
        Theme::windows_light(),
        Theme::windows_dark(),
        Theme::macos_light(),
        Theme::macos_dark(),
        Theme::device(),
    ]
}

#[test]
fn wcag_gate_passes_for_every_preset_light_and_dark() {
    for t in all_presets() {
        let p = &t.palette;
        let body = p.body_text_contrast();
        assert!(body >= 4.5, "{}: body text contrast {body:.2} < 4.5", t.name);
        let dim = p.dim_text_contrast();
        assert!(dim >= 3.0, "{}: dim text contrast {dim:.2} < 3.0", t.name);
        let ui = p.ui_boundary_contrast();
        assert!(ui >= 3.0, "{}: UI boundary contrast {ui:.2} < 3.0", t.name);
        let status = p.status_contrast();
        assert!(status >= 4.5, "{}: status on-colour contrast {status:.2} < 4.5", t.name);
    }
}

#[test]
fn every_preset_fully_populates_and_is_distinct() {
    let names = Theme::preset_names();
    let mut sorted = names.clone();
    sorted.sort();
    sorted.dedup();
    assert_eq!(sorted.len(), names.len(), "preset names must be unique");
    // by_name round-trips (fuzzy too).
    for n in &names {
        assert_eq!(Theme::by_name(n).map(|t| t.name), Some(n.clone()));
    }
    assert_eq!(Theme::by_name("Windows Dark").map(|t| t.name), Some("windows-dark".into()));
}

#[test]
fn theme_serde_round_trips_with_a_remapped_key() {
    let mut t = Theme::macos_light();
    t.keymap.set(Action::Find, egui::KeyboardShortcut::new(egui::Modifiers::COMMAND | egui::Modifiers::SHIFT, egui::Key::F));
    let json = serde_json::to_string_pretty(&t).unwrap();
    let back: Theme = serde_json::from_str(&json).unwrap();
    assert_eq!(t, back, "theme must serde round-trip exactly");
    assert_eq!(
        back.keymap.shortcut(Action::Find),
        Some(egui::KeyboardShortcut::new(egui::Modifiers::COMMAND | egui::Modifiers::SHIFT, egui::Key::F))
    );
}

#[test]
fn apply_installs_style_and_publishes_legacy_palette() {
    let theme = Theme::windows_dark();
    let ctx = egui::Context::default();
    theme.apply(&ctx);

    // The egui style picked up our spacing + scroll.
    let style = ctx.style();
    assert_eq!(style.spacing.item_spacing, theme.metrics.item_spacing_vec());
    assert_eq!(style.spacing.scroll.bar_width, theme.scroll.bar_width);
    assert!(style.visuals.override_text_color.is_none(), "§27: no global override_text_color");

    // The legacy palette is published so existing components follow (COH-1).
    let mut got = "";
    let _ = ctx.run(egui::RawInput::default(), |ctx| {
        egui::CentralPanel::default().show(ctx, |ui| {
            got = crate::theme(ui).name;
        });
    });
    assert_eq!(got, "windows-dark");
}

#[test]
fn from_os_falls_back_to_windows_on_linux() {
    use egui::os::OperatingSystem as Os;
    assert_eq!(Theme::from_os(Os::Mac).name, "macos-dark");
    assert_eq!(Theme::from_os(Os::Windows).name, "windows-dark");
    assert_eq!(Theme::from_os(Os::Nix).name, "windows-dark", "Linux → documented default");
}

#[test]
fn device_has_effects_off() {
    let d = Theme::device();
    assert_eq!(d.effects, EffectsPolicy::None);
    assert!(!d.effects.allows_transparency());
    assert_eq!(d.surface, SurfaceSpec::Opaque);
    assert_eq!(d.motion.duration, 0.0, "no decorative motion on Device");
}

#[test]
fn win_mac_parity_same_semantic_surface_different_chrome() {
    // M2 parity: Windows and macOS presets describe the *same semantic surface*
    // (both pass the gate, both bind every action, both fully populate) but differ
    // in the chrome the work order calls for (scroll style, line-nav chord, radii).
    let w = Theme::windows_dark();
    let m = Theme::macos_dark();

    // Same semantic completeness.
    assert!(w.palette.body_text_contrast() >= 4.5 && m.palette.body_text_contrast() >= 4.5);
    for a in Action::ALL {
        assert!(w.keymap.shortcut(*a).is_some() && m.keymap.shortcut(*a).is_some());
    }

    // Different chrome (the deliberate per-OS divergence).
    assert!(!w.scroll.floating && m.scroll.floating, "Windows solid vs macOS floating scrollbars");
    assert_ne!(w.keymap.shortcut(Action::LineStart), m.keymap.shortcut(Action::LineStart), "line nav differs");
    assert!(m.metrics.corner_radius >= w.metrics.corner_radius, "macOS radii softer");
    // Copy is the SAME chord on both (COMMAND auto-maps ⌘/Ctrl).
    assert_eq!(w.keymap.shortcut(Action::Copy), m.keymap.shortcut(Action::Copy));
}

#[test]
fn legacy_palette_bridge_carries_semantic_roles() {
    let t = Theme::windows_dark();
    let legacy = t.to_legacy_palette();
    assert_eq!(legacy.bg, t.palette.surface.to_color32());
    assert_eq!(legacy.accent, t.palette.accent.to_color32());
    assert_eq!(legacy.text, t.palette.on_surface.to_color32());
    assert_eq!(legacy.glow, t.palette.glow.to_color32());
}