use ratatui::{
buffer::Buffer,
layout::{Alignment, Constraint, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Clear, Paragraph, Widget},
};
use super::{PopupSizing, SizeHint};
use crate::tui::components::{ButtonRow, button_presets};
use crate::tui::theme::{self, BlockVariant};
pub struct ConfirmQuitPopup {
pub selected_yes: bool,
}
impl ConfirmQuitPopup {
#[must_use]
pub fn new() -> Self {
Self {
selected_yes: false,
}
}
#[must_use]
pub fn select_yes(mut self) -> Self {
self.selected_yes = true;
self
}
#[must_use]
pub fn select_no(mut self) -> Self {
self.selected_yes = false;
self
}
}
impl Default for ConfirmQuitPopup {
fn default() -> Self {
Self::new()
}
}
impl PopupSizing for ConfirmQuitPopup {
fn size_hint(&self) -> SizeHint {
SizeHint::percent(0, 0)
.with_min_width(40)
.with_min_height(6)
}
}
impl Widget for ConfirmQuitPopup {
fn render(self, area: Rect, buf: &mut Buffer) {
Clear.render(area, buf);
let block = theme::block("Quit tazuna?", BlockVariant::Warning);
let inner = block.inner(area);
block.render(area, buf);
let chunks = Layout::vertical([
Constraint::Length(1),
Constraint::Length(1),
Constraint::Length(1),
Constraint::Length(1),
])
.split(inner);
Paragraph::new("All sessions will be terminated.")
.alignment(Alignment::Center)
.render(chunks[1], buf);
let selected = usize::from(!self.selected_yes);
ButtonRow::new(&[button_presets::YES, button_presets::NO])
.selected(selected)
.centered()
.render(chunks[3], buf);
}
}
pub struct ConfirmPermissionsPopup<'a> {
branch: &'a str,
pub selected_yes: bool,
}
impl<'a> ConfirmPermissionsPopup<'a> {
#[must_use]
pub fn new(branch: &'a str) -> Self {
Self {
branch,
selected_yes: false,
}
}
#[must_use]
pub fn select_yes(mut self) -> Self {
self.selected_yes = true;
self
}
#[must_use]
pub fn select_no(mut self) -> Self {
self.selected_yes = false;
self
}
}
impl PopupSizing for ConfirmPermissionsPopup<'_> {
fn size_hint(&self) -> SizeHint {
SizeHint::percent(0, 0)
.with_min_width(50)
.with_min_height(10)
}
}
impl Widget for ConfirmPermissionsPopup<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
Clear.render(area, buf);
let block = theme::block(" âš Dangerous Permissions Mode ", BlockVariant::Warning);
let inner = block.inner(area);
block.render(area, buf);
let chunks = Layout::vertical([
Constraint::Length(1), Constraint::Length(5), Constraint::Length(1), Constraint::Length(1), ])
.split(inner);
let content_text = vec![
Line::from(Span::styled(
"This will start Claude Code with:",
Style::default().fg(Color::White),
)),
Line::from(Span::styled(
"--dangerously-skip-permissions",
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
)),
Line::from(""),
Line::from(vec![
Span::styled("Branch: ", Style::default().fg(Color::Gray)),
Span::styled(self.branch, Style::default().fg(Color::Yellow)),
]),
Line::from(Span::styled(
"Claude can run any command without asking.",
Style::default().fg(Color::Gray),
)),
];
Paragraph::new(content_text)
.alignment(Alignment::Center)
.render(chunks[1], buf);
let selected = usize::from(!self.selected_yes);
ButtonRow::new(&[button_presets::YES_UNDERSTAND, button_presets::NO_CANCEL])
.selected(selected)
.centered()
.render(chunks[3], buf);
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use crate::tui::test_utils::buffer_to_text;
#[test]
fn test_confirm_quit_popup_default() {
let popup = ConfirmQuitPopup::new();
assert!(!popup.selected_yes);
}
#[test]
fn test_confirm_quit_popup_renders() {
let popup = ConfirmQuitPopup::new();
let area = Rect::new(0, 0, 40, 6);
let mut buf = Buffer::empty(area);
popup.render(area, &mut buf);
let output = buffer_to_text(&buf);
assert!(output.contains("Quit"));
assert!(output.contains("[Y]es"));
assert!(output.contains("[N]o"));
}
#[test]
fn confirm_quit_popup_select_yes() {
let popup = ConfirmQuitPopup::new().select_yes();
assert!(popup.selected_yes);
}
#[test]
fn confirm_quit_popup_select_no() {
let popup = ConfirmQuitPopup::new().select_yes().select_no();
assert!(!popup.selected_yes);
}
#[test]
fn confirm_quit_popup_default_impl() {
let popup = ConfirmQuitPopup::default();
assert!(!popup.selected_yes);
}
#[test]
fn confirm_quit_popup_renders_yes_selected() {
let popup = ConfirmQuitPopup::new().select_yes();
let area = Rect::new(0, 0, 40, 6);
let mut buf = Buffer::empty(area);
popup.render(area, &mut buf);
let output = buffer_to_text(&buf);
assert!(output.contains("[Y]es"));
assert!(output.contains("[N]o"));
}
#[test]
fn confirm_permissions_popup_new() {
let popup = ConfirmPermissionsPopup::new("feat/issue-42");
assert!(!popup.selected_yes);
}
#[test]
fn confirm_permissions_popup_select_yes() {
let popup = ConfirmPermissionsPopup::new("feat/issue-42").select_yes();
assert!(popup.selected_yes);
}
#[test]
fn confirm_permissions_popup_select_no() {
let popup = ConfirmPermissionsPopup::new("feat/issue-42")
.select_yes()
.select_no();
assert!(!popup.selected_yes);
}
#[test]
fn confirm_permissions_popup_renders() {
let popup = ConfirmPermissionsPopup::new("feat/issue-42");
let area = Rect::new(0, 0, 60, 12);
let mut buf = Buffer::empty(area);
popup.render(area, &mut buf);
let output = buffer_to_text(&buf);
assert!(output.contains("Dangerous Permissions"));
assert!(output.contains("--dangerously-skip-permissions"));
assert!(output.contains("feat/issue-42"));
assert!(output.contains("[Y]es"));
assert!(output.contains("[N]o"));
}
#[test]
fn confirm_permissions_popup_renders_yes_selected() {
let popup = ConfirmPermissionsPopup::new("test-branch").select_yes();
let area = Rect::new(0, 0, 60, 12);
let mut buf = Buffer::empty(area);
popup.render(area, &mut buf);
let output = buffer_to_text(&buf);
assert!(output.contains("test-branch"));
}
mod snapshots {
use super::*;
use crate::tui::test_utils::render_to_snapshot;
use insta::assert_snapshot;
#[test]
fn confirm_quit_no_selected() {
let popup = ConfirmQuitPopup::new();
assert_snapshot!(render_to_snapshot(popup, 40, 6));
}
#[test]
fn confirm_quit_yes_selected() {
let popup = ConfirmQuitPopup::new().select_yes();
assert_snapshot!(render_to_snapshot(popup, 40, 6));
}
}
}