feat(CommitType): implement CommitType and tests

This commit is contained in:
2026-02-05 20:34:57 +01:00
parent 644854f088
commit d60486a0be
7 changed files with 283 additions and 2 deletions

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
pub mod types;

277
src/commit/types.rs Normal file
View File

@@ -0,0 +1,277 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CommitType {
Feat,
Fix,
Docs,
Style,
Refactor,
Perf,
Test,
Build,
Ci,
Chore,
Revert,
}
impl CommitType {
pub fn all() -> &'static [Self] {
&[
Self::Feat,
Self::Fix,
Self::Docs,
Self::Style,
Self::Refactor,
Self::Perf,
Self::Test,
Self::Build,
Self::Ci,
Self::Chore,
Self::Revert,
]
}
pub const fn description(&self) -> &'static str {
match self {
Self::Feat => "A new feature",
Self::Fix => "A bug fix",
Self::Docs => "Documentation only changes",
Self::Style => "Changes that do not affect the meaning of the code",
Self::Refactor => "A code change that neither fixes a bug nor adds a feature",
Self::Perf => "A code change that improves performance",
Self::Test => "Adding missing tests or correcting existing tests",
Self::Build => "Changes that affect the build system or external dependencies",
Self::Ci => "Changes to CI configuration files and scripts",
Self::Chore => "Other changes that don't modify src or test files",
Self::Revert => "Reverts a previous commit",
}
}
pub const fn as_str(&self) -> &'static str {
match self {
Self::Feat => "feat",
Self::Fix => "fix",
Self::Docs => "docs",
Self::Style => "style",
Self::Refactor => "refactor",
Self::Perf => "perf",
Self::Test => "test",
Self::Build => "build",
Self::Ci => "ci",
Self::Chore => "chore",
Self::Revert => "revert",
}
}
}
impl std::fmt::Display for CommitType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Test that all 11 commit types exist and can be constructed
#[test]
fn all_eleven_variants_exist() {
// Exhaustive pattern matching ensures all variants exist at compile time
let variants = [
CommitType::Feat,
CommitType::Fix,
CommitType::Docs,
CommitType::Style,
CommitType::Refactor,
CommitType::Perf,
CommitType::Test,
CommitType::Build,
CommitType::Ci,
CommitType::Chore,
CommitType::Revert,
];
assert_eq!(variants.len(), 11);
}
/// Test that as_str() returns the correct lowercase string for each variant
#[test]
fn as_str_returns_lowercase_string() {
assert_eq!(CommitType::Feat.as_str(), "feat");
assert_eq!(CommitType::Fix.as_str(), "fix");
assert_eq!(CommitType::Docs.as_str(), "docs");
assert_eq!(CommitType::Style.as_str(), "style");
assert_eq!(CommitType::Refactor.as_str(), "refactor");
assert_eq!(CommitType::Perf.as_str(), "perf");
assert_eq!(CommitType::Test.as_str(), "test");
assert_eq!(CommitType::Build.as_str(), "build");
assert_eq!(CommitType::Ci.as_str(), "ci");
assert_eq!(CommitType::Chore.as_str(), "chore");
assert_eq!(CommitType::Revert.as_str(), "revert");
}
/// Test that as_str() output is always lowercase (property-based check)
#[test]
fn as_str_is_always_lowercase() {
for commit_type in CommitType::all() {
let s = commit_type.as_str();
assert_eq!(
s,
s.to_lowercase(),
"as_str() should return lowercase for {:?}",
commit_type
);
}
}
/// Test that description() returns a non-empty string for each variant
#[test]
fn description_returns_non_empty_string() {
for commit_type in CommitType::all() {
let desc = commit_type.description();
assert!(
!desc.is_empty(),
"description() should not be empty for {:?}",
commit_type
);
}
}
/// Test that description() returns the expected descriptions per spec
#[test]
fn description_returns_expected_values() {
assert_eq!(CommitType::Feat.description(), "A new feature");
assert_eq!(CommitType::Fix.description(), "A bug fix");
assert_eq!(CommitType::Docs.description(), "Documentation only changes");
assert_eq!(
CommitType::Style.description(),
"Changes that do not affect the meaning of the code"
);
assert_eq!(
CommitType::Refactor.description(),
"A code change that neither fixes a bug nor adds a feature"
);
assert_eq!(
CommitType::Perf.description(),
"A code change that improves performance"
);
assert_eq!(
CommitType::Test.description(),
"Adding missing tests or correcting existing tests"
);
assert_eq!(
CommitType::Build.description(),
"Changes that affect the build system or external dependencies"
);
assert_eq!(
CommitType::Ci.description(),
"Changes to CI configuration files and scripts"
);
assert_eq!(
CommitType::Chore.description(),
"Other changes that don't modify src or test files"
);
assert_eq!(
CommitType::Revert.description(),
"Reverts a previous commit"
);
}
/// Test that all() returns exactly 11 types
#[test]
fn all_returns_eleven_types() {
assert_eq!(CommitType::all().len(), 11);
}
/// Test that all() returns types in the expected order (feat first, revert last)
#[test]
fn all_returns_types_in_expected_order() {
let all = CommitType::all();
assert_eq!(all[0], CommitType::Feat);
assert_eq!(all[1], CommitType::Fix);
assert_eq!(all[2], CommitType::Docs);
assert_eq!(all[3], CommitType::Style);
assert_eq!(all[4], CommitType::Refactor);
assert_eq!(all[5], CommitType::Perf);
assert_eq!(all[6], CommitType::Test);
assert_eq!(all[7], CommitType::Build);
assert_eq!(all[8], CommitType::Ci);
assert_eq!(all[9], CommitType::Chore);
assert_eq!(all[10], CommitType::Revert);
}
/// Test that all() contains all unique variants (no duplicates)
#[test]
fn all_contains_unique_variants() {
let all = CommitType::all();
for (i, variant) in all.iter().enumerate() {
for (j, other) in all.iter().enumerate() {
if i != j {
assert_ne!(variant, other, "all() should not contain duplicates");
}
}
}
}
/// Test that Display implementation delegates to as_str()
#[test]
fn display_delegates_to_as_str() {
for commit_type in CommitType::all() {
let display_output = format!("{}", commit_type);
let as_str_output = commit_type.as_str();
assert_eq!(
display_output, as_str_output,
"Display should delegate to as_str() for {:?}",
commit_type
);
}
}
/// Test Display for specific variants
#[test]
fn display_shows_lowercase_type() {
assert_eq!(format!("{}", CommitType::Feat), "feat");
assert_eq!(format!("{}", CommitType::Fix), "fix");
assert_eq!(format!("{}", CommitType::Docs), "docs");
assert_eq!(format!("{}", CommitType::Style), "style");
assert_eq!(format!("{}", CommitType::Refactor), "refactor");
assert_eq!(format!("{}", CommitType::Perf), "perf");
assert_eq!(format!("{}", CommitType::Test), "test");
assert_eq!(format!("{}", CommitType::Build), "build");
assert_eq!(format!("{}", CommitType::Ci), "ci");
assert_eq!(format!("{}", CommitType::Chore), "chore");
assert_eq!(format!("{}", CommitType::Revert), "revert");
}
/// Test that CommitType implements Copy (can be used after move)
#[test]
fn commit_type_is_copy() {
let original = CommitType::Feat;
let copied = original; // Copy, not move
assert_eq!(original, copied); // original still usable
}
/// Test that CommitType implements PartialEq correctly
#[test]
fn commit_type_equality() {
assert_eq!(CommitType::Feat, CommitType::Feat);
assert_ne!(CommitType::Feat, CommitType::Fix);
}
/// Test that CommitType can be used as HashMap key (Hash + Eq)
#[test]
fn commit_type_can_be_hash_key() {
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert(CommitType::Feat, "feature");
map.insert(CommitType::Fix, "bugfix");
assert_eq!(map.get(&CommitType::Feat), Some(&"feature"));
assert_eq!(map.get(&CommitType::Fix), Some(&"bugfix"));
}
/// Test that Debug is implemented
#[test]
fn commit_type_has_debug() {
let debug_output = format!("{:?}", CommitType::Feat);
assert!(debug_output.contains("Feat"));
}
}

View File

@@ -16,5 +16,5 @@ pub enum Error {
#[error("Operation cancelled by user")]
Cancelled,
#[error("Non-interactive terminal detected")]
NonInteractive
NonInteractive,
}

View File

@@ -0,0 +1 @@

View File

@@ -1,5 +1,5 @@
mod cli;
mod commit;
mod error;
mod jj;
mod prompts;
mod error;

View File

@@ -0,0 +1 @@