Skip to content

Commit 013da0a

Browse files
committed
test: more conventional commit perser tests
1 parent 650a278 commit 013da0a

1 file changed

Lines changed: 147 additions & 11 deletions

File tree

crates/vite_shared/src/conventional_commit.rs

Lines changed: 147 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ pub fn parse_conventional_commit(subject: &str, body: &str) -> Option<Convention
3737
let (kind, scope) = match kind_with_scope.split_once('(') {
3838
Some((kind, rest)) => {
3939
let scope = rest.strip_suffix(')')?.trim();
40-
(kind.trim(), (!scope.is_empty()).then_some(scope))
40+
if scope.is_empty() {
41+
return None;
42+
}
43+
(kind.trim(), Some(scope))
4144
}
4245
None => (kind_with_scope.trim(), None),
4346
};
@@ -59,22 +62,155 @@ pub fn parse_conventional_commit(subject: &str, body: &str) -> Option<Convention
5962
mod tests {
6063
use super::*;
6164

65+
fn assert_commit<'a>(
66+
subject: &'a str,
67+
body: &'a str,
68+
kind: &'a str,
69+
scope: Option<&'a str>,
70+
description: &'a str,
71+
breaking: bool,
72+
) {
73+
let commit = parse_conventional_commit(subject, body).expect("commit should parse");
74+
assert_eq!(commit.kind, kind);
75+
assert_eq!(commit.scope, scope);
76+
assert_eq!(commit.description, description);
77+
assert_eq!(commit.breaking, breaking);
78+
}
79+
6280
#[test]
6381
fn parses_scope_and_breaking_marker() {
64-
let commit =
65-
parse_conventional_commit("feat(cli)!: add release", "").expect("commit should parse");
66-
assert_eq!(commit.kind, "feat");
67-
assert_eq!(commit.scope, Some("cli"));
68-
assert_eq!(commit.description, "add release");
69-
assert!(commit.breaking);
82+
assert_commit("feat(cli)!: add release", "", "feat", Some("cli"), "add release", true);
83+
}
84+
85+
#[test]
86+
fn parses_commits_without_scope() {
87+
assert_commit("fix: handle cache miss", "", "fix", None, "handle cache miss", false);
88+
}
89+
90+
#[test]
91+
fn trims_subject_parts() {
92+
assert_commit(
93+
" feat(parser): add support for colons: here ",
94+
"",
95+
"feat",
96+
Some("parser"),
97+
"add support for colons: here",
98+
false,
99+
);
100+
}
101+
102+
#[test]
103+
fn parses_breaking_header_without_scope() {
104+
assert_commit("refactor!: split module", "", "refactor", None, "split module", true);
105+
}
106+
107+
#[test]
108+
fn parses_scopes_with_symbols_used_in_package_names() {
109+
assert_commit(
110+
"build(pkg-utils/core): ship binary",
111+
"",
112+
"build",
113+
Some("pkg-utils/core"),
114+
"ship binary",
115+
false,
116+
);
70117
}
71118

72119
#[test]
73120
fn parses_breaking_change_footer() {
74-
let commit = parse_conventional_commit("chore: cleanup", "BREAKING CHANGE: changed API")
75-
.expect("commit should parse");
76-
assert_eq!(commit.kind, "chore");
77-
assert!(commit.breaking);
121+
assert_commit(
122+
"chore: cleanup",
123+
"BREAKING CHANGE: changed API",
124+
"chore",
125+
None,
126+
"cleanup",
127+
true,
128+
);
129+
}
130+
131+
#[test]
132+
fn parses_breaking_change_hyphenated_footer() {
133+
assert_commit(
134+
"feat: ship release",
135+
"BREAKING-CHANGE: config file layout changed",
136+
"feat",
137+
None,
138+
"ship release",
139+
true,
140+
);
141+
}
142+
143+
#[test]
144+
fn detects_breaking_footer_after_blank_line_and_indentation() {
145+
assert_commit(
146+
"feat(ui): refresh",
147+
"\n BREAKING CHANGE: theme tokens moved",
148+
"feat",
149+
Some("ui"),
150+
"refresh",
151+
true,
152+
);
153+
}
154+
155+
#[test]
156+
fn does_not_mark_non_breaking_body_text_as_breaking() {
157+
assert_commit(
158+
"docs: explain migration",
159+
"This mentions BREAKING CHANGE but not as a footer.\nAlso BREAKING CHANGE without colon",
160+
"docs",
161+
None,
162+
"explain migration",
163+
false,
164+
);
165+
}
166+
167+
#[test]
168+
fn breaking_header_wins_even_without_footer() {
169+
assert_commit("feat!: ship api v2", "some body", "feat", None, "ship api v2", true);
170+
}
171+
172+
#[test]
173+
fn returns_none_for_empty_scope() {
174+
assert!(parse_conventional_commit("feat(): release", "").is_none());
175+
assert!(parse_conventional_commit("feat( ): release", "").is_none());
176+
}
177+
178+
#[test]
179+
fn returns_none_for_missing_separator_or_description() {
180+
assert!(parse_conventional_commit("release prep", "").is_none());
181+
assert!(parse_conventional_commit("feat", "").is_none());
182+
assert!(parse_conventional_commit("feat:", "").is_none());
183+
assert!(parse_conventional_commit("feat: ", "").is_none());
184+
}
185+
186+
#[test]
187+
fn returns_none_for_missing_type() {
188+
assert!(parse_conventional_commit(": description", "").is_none());
189+
assert!(parse_conventional_commit(" : description", "").is_none());
190+
}
191+
192+
#[test]
193+
fn returns_none_for_malformed_scope_syntax() {
194+
assert!(parse_conventional_commit("feat(parser: release", "").is_none());
195+
assert!(parse_conventional_commit("feat)parser(: release", "").is_none());
196+
assert!(parse_conventional_commit("feat(parser) extra: release", "").is_none());
197+
}
198+
199+
#[test]
200+
fn only_uses_first_colon_as_header_separator() {
201+
assert_commit(
202+
"feat: add support: parser mode",
203+
"",
204+
"feat",
205+
None,
206+
"add support: parser mode",
207+
false,
208+
);
209+
}
210+
211+
#[test]
212+
fn preserves_case_of_kind_and_scope() {
213+
assert_commit("Feat(API): allow preview", "", "Feat", Some("API"), "allow preview", false);
78214
}
79215

80216
#[test]

0 commit comments

Comments
 (0)