|
|
|
|
@@ -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"));
|
|
|
|
|
}
|
|
|
|
|
}
|