aether-wisp 0.7.2

A terminal UI for AI coding agents via the Agent Client Protocol (ACP)
Documentation
#![cfg(feature = "testing")]

use clankerdiff_ratatui::theme::{ReviewTheme, Rgba, SelectionState as ThemeSelectionState, ThemeError};
use clankerdiff_ratatui::{RatatuiUiTheme, composite_color, layered_style};
use ratatui::{
    buffer::Buffer,
    layout::Rect,
    style::{Color, Style},
    text::Line,
    widgets::StatefulWidget,
};
use wisp::{
    theme::Theme,
    view::{list_view::ListView, selection::SelectionState},
};

#[test]
fn sage_retains_semantic_status_and_text_roles() -> Result<(), ThemeError> {
    let theme = Theme::from_review(ReviewTheme::sage()?);
    assert_eq!(theme.info, Color::Rgb(130, 177, 204));
    assert_eq!(theme.warning, Color::Rgb(216, 181, 106));
    assert_eq!(theme.muted, Color::Rgb(92, 112, 104));
    assert_ne!(theme.text_secondary, theme.muted);
    Ok(())
}

#[test]
fn every_builtin_maps_semantic_roles_directly() -> Result<(), ThemeError> {
    for descriptor in ReviewTheme::catalog() {
        let review = ReviewTheme::builtin(&descriptor.id)?;
        let ui = review.ui;
        let theme = Theme::from_review(review);
        for (actual, expected) in [
            (theme.text_primary, ui.text),
            (theme.text_secondary, ui.text_secondary),
            (theme.muted, ui.text_muted),
            (theme.info, ui.info),
            (theme.warning, ui.warning),
            (theme.success, ui.positive),
            (theme.error, ui.destructive),
            (theme.surface, ui.surface),
        ] {
            assert_eq!(actual, composite_color(expected, ui.canvas), "{}", descriptor.id);
        }
    }
    Ok(())
}

#[test]
fn lists_use_shared_selection_instead_of_ordinary_surfaces() -> Result<(), ThemeError> {
    for id in ["sage", "dracula", "github-light"] {
        let theme = Theme::from_review(ReviewTheme::builtin(id)?);
        let native = RatatuiUiTheme::from(&theme.review().ui);
        for focused in [false, true] {
            let area = Rect::new(0, 0, 30, 2);
            let mut buffer = Buffer::empty(area);
            buffer.set_style(area, theme.surface_style());
            let rows = vec![
                Line::styled("selected", Style::new().fg(theme.info)),
                Line::styled("ordinary", Style::new().fg(theme.warning)),
            ];
            let list = ListView::new(rows, &theme);
            let list = if focused { list.pane("empty") } else { list };
            list.render(area, &mut buffer, &mut SelectionState::new(2));
            let selected = native.selection_style(if focused {
                ThemeSelectionState::Focused
            } else {
                ThemeSelectionState::Selected
            });
            assert_eq!(Some(buffer[(0, 0)].fg), selected.fg);
            assert_eq!(Some(buffer[(0, 0)].bg), selected.bg);
            assert_ne!(buffer[(0, 0)].bg, theme.surface);
            assert_eq!(buffer[(0, 1)].bg, theme.surface);
            assert_eq!(buffer[(0, 1)].fg, theme.warning);
        }
    }
    Ok(())
}

#[test]
fn ordinary_surfaces_composite_text_against_the_surface() {
    let mut review = ReviewTheme::default();
    review.ui.text = Rgba::new(200, 220, 240, 128);
    review.ui.surface = Rgba::new(50, 100, 150, 128);
    let expected = layered_style(review.ui.text, review.ui.surface, review.ui.canvas);
    assert_eq!(Theme::from_review(review).surface_style(), expected);
}