feat: implement breaking change input
This commit is contained in:
@@ -5,8 +5,10 @@
|
||||
//! [`CommitWorkflow`](super::CommitWorkflow) to use real interactive prompts
|
||||
//! in production while accepting mock implementations in tests.
|
||||
|
||||
use inquire::{Confirm, Text};
|
||||
|
||||
use crate::{
|
||||
commit::types::{CommitType, Description, Scope},
|
||||
commit::types::{BreakingChange, CommitType, Description, Scope},
|
||||
error::Error,
|
||||
};
|
||||
|
||||
@@ -24,6 +26,9 @@ pub trait Prompter: Send + Sync {
|
||||
/// Prompt the user to input a required description
|
||||
fn input_description(&self) -> Result<Description, Error>;
|
||||
|
||||
/// Prompt the user for breaking change
|
||||
fn input_breaking_change(&self) -> Result<BreakingChange, Error>;
|
||||
|
||||
/// Prompt the user to confirm applying the commit message
|
||||
fn confirm_apply(&self, message: &str) -> Result<bool, Error>;
|
||||
|
||||
@@ -34,6 +39,18 @@ pub trait Prompter: Send + Sync {
|
||||
fn emit_message(&self, msg: &str);
|
||||
}
|
||||
|
||||
fn format_message_box(message: &str) -> String {
|
||||
let preview_width = 72 + 2; // max width + space padding
|
||||
let mut lines: Vec<String> = Vec::new();
|
||||
lines.push(format!("┌{}┐", "─".repeat(preview_width)));
|
||||
for line in message.split("\n") {
|
||||
let padding = 72_usize.saturating_sub(line.chars().count());
|
||||
lines.push(format!("│ {line}{:padding$} │", ""));
|
||||
}
|
||||
lines.push(format!("└{}┘", "─".repeat(preview_width)));
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
/// Production implementation of [`Prompter`] using the `inquire` crate
|
||||
#[derive(Debug)]
|
||||
pub struct RealPrompts;
|
||||
@@ -137,18 +154,30 @@ impl Prompter for RealPrompts {
|
||||
}
|
||||
}
|
||||
|
||||
fn input_breaking_change(&self) -> Result<BreakingChange, Error> {
|
||||
if !Confirm::new("Does this revision include a breaking change?")
|
||||
.with_default(false)
|
||||
.prompt()
|
||||
.map_err(|_| Error::Cancelled)?
|
||||
{
|
||||
return Ok(BreakingChange::No);
|
||||
}
|
||||
let answer = Text::new("Enter the description of the breaking change:")
|
||||
.with_help_message("Enter an empty message to skip creating a message footer")
|
||||
.prompt()
|
||||
.map_err(|_| Error::Cancelled)?;
|
||||
let trimmed = answer.trim();
|
||||
Ok(trimmed.into())
|
||||
}
|
||||
|
||||
fn confirm_apply(&self, message: &str) -> Result<bool, Error> {
|
||||
use inquire::Confirm;
|
||||
|
||||
// Show preview
|
||||
println!();
|
||||
println!("📝 Commit Message Preview:");
|
||||
println!("┌────────────────────────────────────────────────────────────────────────┐");
|
||||
// Pad with spaces to fill the box
|
||||
let padding = 72_usize.saturating_sub(message.chars().count()) - 1;
|
||||
println!("│ {message}{:padding$}│", "");
|
||||
println!("└────────────────────────────────────────────────────────────────────────┘");
|
||||
println!();
|
||||
println!(
|
||||
"\n📝 Commit Message Preview:\n{}\n",
|
||||
format_message_box(message)
|
||||
);
|
||||
|
||||
// Get confirmation
|
||||
Confirm::new("Apply this commit message?")
|
||||
@@ -174,4 +203,71 @@ mod tests {
|
||||
fn _accepts_prompter(_p: impl Prompter) {}
|
||||
_accepts_prompter(real);
|
||||
}
|
||||
|
||||
/// Top border uses exactly preview_width (74) dashes; bottom likewise
|
||||
#[test]
|
||||
fn format_message_box_borders() {
|
||||
let result = format_message_box("hello");
|
||||
let lines: Vec<&str> = result.split('\n').collect();
|
||||
let dashes = "─".repeat(74);
|
||||
assert_eq!(lines[0], format!("┌{dashes}┐"));
|
||||
assert_eq!(lines[lines.len() - 1], format!("└{dashes}┘"));
|
||||
}
|
||||
|
||||
/// A single-line message produces exactly 3 rows: top, content, bottom
|
||||
#[test]
|
||||
fn format_message_box_single_line_row_count() {
|
||||
let result = format_message_box("feat: add login");
|
||||
assert_eq!(result.split('\n').count(), 3);
|
||||
}
|
||||
|
||||
/// A message with one `\n` produces 4 rows: top, two content, bottom
|
||||
#[test]
|
||||
fn format_message_box_multi_line_row_count() {
|
||||
let result = format_message_box("feat: add login\nsecond line");
|
||||
assert_eq!(result.split('\n').count(), 4);
|
||||
}
|
||||
|
||||
/// A breaking-change message (`\n\n`) produces an empty content row for the blank line
|
||||
#[test]
|
||||
fn format_message_box_blank_separator_line() {
|
||||
let msg = "feat!: drop old API\n\nBREAKING CHANGE: removed";
|
||||
let result = format_message_box(msg);
|
||||
assert_eq!(result.split('\n').count(), 5); // top + 3 content + bottom
|
||||
}
|
||||
|
||||
/// All output rows have identical char counts (the box is rectangular)
|
||||
#[test]
|
||||
fn format_message_box_all_rows_same_width() {
|
||||
let msg = "feat(auth): add login\n\nBREAKING CHANGE: old API removed";
|
||||
let result = format_message_box(msg);
|
||||
let widths: Vec<usize> = result.split('\n').map(|l| l.chars().count()).collect();
|
||||
let expected = widths[0];
|
||||
assert!(
|
||||
widths.iter().all(|&w| w == expected),
|
||||
"rows have differing widths: {:?}",
|
||||
widths
|
||||
);
|
||||
}
|
||||
|
||||
/// An empty message produces a single fully-padded content row
|
||||
#[test]
|
||||
fn format_message_box_empty_message() {
|
||||
let result = format_message_box("");
|
||||
let lines: Vec<&str> = result.split('\n').collect();
|
||||
assert_eq!(lines.len(), 3);
|
||||
// "│ " + 72 spaces + " │" = 76 chars
|
||||
let expected = format!("│ {:72} │", "");
|
||||
assert_eq!(lines[1], expected);
|
||||
}
|
||||
|
||||
/// A line of exactly 72 characters leaves no right-hand padding
|
||||
#[test]
|
||||
fn format_message_box_line_exactly_72_chars() {
|
||||
let line_72 = "a".repeat(72);
|
||||
let result = format_message_box(&line_72);
|
||||
let lines: Vec<&str> = result.split('\n').collect();
|
||||
let expected = format!("│ {line_72} │");
|
||||
assert_eq!(lines[1], expected);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user