tact 0.6.2

Terminal interface for Nanocodex
use super::{
    floating::Floating,
    node::{Component, ComponentUpdate, RenderRequest},
};
use crate::tui::theme::Theme;
use crossterm::event::{Event, KeyCode, KeyEventKind};
use ratatui::{
    Frame,
    layout::Rect,
    style::Style,
    text::Line,
    widgets::{Paragraph, Wrap},
};

const KEY_BINDINGS: [&str; 2] = ["enter/y download", "esc/n cancel"];

pub(super) enum ReviewConfirmationEvent {
    Terminal(Event),
}

#[derive(Debug, Eq, PartialEq)]
pub(super) enum ReviewConfirmationEffect {
    Confirm,
    Dismiss,
}

pub(super) struct ReviewDownloadConfirmation;

impl Component for ReviewDownloadConfirmation {
    type Event = ReviewConfirmationEvent;
    type Effect = ReviewConfirmationEffect;

    fn update(&mut self, event: Self::Event) -> ComponentUpdate<Self::Effect> {
        let ReviewConfirmationEvent::Terminal(Event::Key(key)) = event else {
            return ComponentUpdate::none();
        };
        if !matches!(key.kind, KeyEventKind::Press | KeyEventKind::Repeat) {
            return ComponentUpdate::none();
        }
        match key.code {
            KeyCode::Enter | KeyCode::Char('y' | 'Y') => ComponentUpdate {
                effects: vec![ReviewConfirmationEffect::Confirm],
                render: RenderRequest::Immediate,
            },
            KeyCode::Esc | KeyCode::Char('n' | 'N') => ComponentUpdate {
                effects: vec![ReviewConfirmationEffect::Dismiss],
                render: RenderRequest::Immediate,
            },
            _ => ComponentUpdate::none(),
        }
    }

    fn render(&mut self, frame: &mut Frame<'_>, area: Rect, theme: &Theme) {
        let layout = Floating::new("Install review interface", 64, 9, &KEY_BINDINGS)
            .render(frame, area, theme);
        let lines = vec![
            Line::from("The browser review interface is not installed."),
            Line::from(""),
            Line::styled(
                "Download the matching, checksummed bundle from this Tact release?",
                Style::default().fg(theme.muted()),
            ),
        ];
        frame.render_widget(
            Paragraph::new(lines).wrap(Wrap { trim: false }),
            layout.body,
        );
    }
}

#[cfg(test)]
mod tests {
    use super::{Component, ReviewDownloadConfirmation};
    use crate::tui::theme::Theme;
    use ratatui::{Terminal, backend::TestBackend};

    #[test]
    fn download_message_fits_inside_the_popup() {
        let mut terminal = Terminal::new(TestBackend::new(64, 9)).unwrap();
        terminal
            .draw(|frame| {
                ReviewDownloadConfirmation.render(frame, frame.area(), &Theme::default());
            })
            .unwrap();

        let buffer = terminal.backend().buffer();
        let text = (0..buffer.area.height)
            .map(|y| (1..63).map(|x| buffer[(x, y)].symbol()).collect::<String>())
            .collect::<Vec<_>>()
            .join(" ");
        let text = text.split_whitespace().collect::<Vec<_>>().join(" ");
        assert!(text.contains("Download the matching, checksummed bundle from this Tact release?"));
        for y in 1..8 {
            assert_eq!(buffer[(63, y)].symbol(), "");
        }
    }
}