1use changeset_core::{BumpType, PrereleaseSpec, ZeroVersionBehavior};
2use semver::{Prerelease, Version};
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum VersionError {
7 #[error("invalid prerelease identifier: {identifier}")]
8 InvalidPrerelease { identifier: String },
9 #[error("cannot graduate from prerelease version '{version}'; release stable 0.x first")]
10 CannotGraduateFromPrerelease { version: String },
11 #[error("can only graduate 0.x versions to 1.0.0; version is {version}")]
12 CanOnlyGraduateZeroVersions { version: String },
13}
14
15#[must_use]
16pub fn max_bump_type(bumps: &[BumpType]) -> Option<BumpType> {
17 bumps.iter().max().copied()
18}
19
20pub fn bump_version(version: &Version, bump_type: BumpType) -> Version {
21 let mut new_version = version.clone();
22
23 match bump_type {
24 BumpType::None => return new_version,
25 BumpType::Major => {
26 new_version.major += 1;
27 new_version.minor = 0;
28 new_version.patch = 0;
29 }
30 BumpType::Minor => {
31 new_version.minor += 1;
32 new_version.patch = 0;
33 }
34 BumpType::Patch => {
35 new_version.patch += 1;
36 }
37 }
38
39 new_version.pre = Prerelease::EMPTY;
40 new_version
41}
42
43fn parse_prerelease(pre: &Prerelease) -> Option<(String, u64)> {
44 let pre_str = pre.as_str();
45 if pre_str.is_empty() {
46 return None;
47 }
48
49 let parts: Vec<&str> = pre_str.split('.').collect();
50 if parts.len() < 2 {
51 return Some((pre_str.to_string(), 1));
52 }
53
54 let last = parts.last()?;
55 if let Ok(num) = last.parse::<u64>() {
56 let tag = parts[..parts.len() - 1].join(".");
57 Some((tag, num))
58 } else {
59 Some((pre_str.to_string(), 1))
60 }
61}
62
63pub fn calculate_new_version(
70 current: &Version,
71 bump_type: Option<BumpType>,
72 prerelease: Option<&PrereleaseSpec>,
73) -> Result<Version, VersionError> {
74 let mut new_version = current.clone();
75
76 match prerelease {
77 Some(spec) => {
78 let tag = spec.identifier();
79
80 if current.pre.is_empty() {
81 let bump = bump_type
82 .filter(|b| !b.is_noop())
83 .unwrap_or(BumpType::Patch);
84 new_version = bump_version(current, bump);
85 new_version.pre = make_prerelease(tag, 1)?;
86 } else if let Some((current_tag, current_num)) = parse_prerelease(¤t.pre) {
87 if current_tag == tag {
88 new_version.pre = make_prerelease(tag, current_num + 1)?;
89 } else {
90 new_version.pre = make_prerelease(tag, 1)?;
91 }
92 } else {
93 new_version.pre = make_prerelease(tag, 1)?;
94 }
95 }
96 None => {
97 if !current.pre.is_empty() {
98 new_version.pre = Prerelease::EMPTY;
99 } else if let Some(bump) = bump_type {
100 new_version = bump_version(current, bump);
101 }
102 }
103 }
104
105 Ok(new_version)
106}
107
108fn make_prerelease(tag: &str, num: u64) -> Result<Prerelease, VersionError> {
109 let identifier = format!("{tag}.{num}");
110 Prerelease::new(&identifier).map_err(|_| VersionError::InvalidPrerelease { identifier })
111}
112
113#[must_use]
114pub fn is_prerelease(version: &Version) -> bool {
115 !version.pre.is_empty()
116}
117
118#[must_use]
119pub fn extract_prerelease_tag(version: &Version) -> Option<String> {
120 parse_prerelease(&version.pre).map(|(tag, _)| tag)
121}
122
123#[must_use]
124pub fn is_zero_version(version: &Version) -> bool {
125 version.major == 0
126}
127
128pub fn calculate_new_version_with_zero_behavior(
150 current: &Version,
151 bump_type: Option<BumpType>,
152 prerelease: Option<&PrereleaseSpec>,
153 zero_behavior: ZeroVersionBehavior,
154 graduate: bool,
155) -> Result<Version, VersionError> {
156 if graduate {
157 return calculate_graduation(current, prerelease);
158 }
159
160 if current.major >= 1 {
161 return calculate_new_version(current, bump_type, prerelease);
162 }
163
164 let effective_bump = match zero_behavior {
165 ZeroVersionBehavior::AutoPromoteOnMajor => {
166 if bump_type == Some(BumpType::Major) {
167 return apply_prerelease_to_version(Version::new(1, 0, 0), prerelease);
168 }
169 bump_type
170 }
171 _ => bump_type.map(|bt| match bt {
172 BumpType::None => BumpType::None,
173 BumpType::Major => BumpType::Minor,
174 BumpType::Minor | BumpType::Patch => BumpType::Patch,
175 }),
176 };
177
178 calculate_new_version(current, effective_bump, prerelease)
179}
180
181fn calculate_graduation(
182 current: &Version,
183 prerelease: Option<&PrereleaseSpec>,
184) -> Result<Version, VersionError> {
185 if is_prerelease(current) {
186 return Err(VersionError::CannotGraduateFromPrerelease {
187 version: current.to_string(),
188 });
189 }
190
191 if current.major >= 1 {
192 return Err(VersionError::CanOnlyGraduateZeroVersions {
193 version: current.to_string(),
194 });
195 }
196
197 apply_prerelease_to_version(Version::new(1, 0, 0), prerelease)
198}
199
200fn apply_prerelease_to_version(
201 base: Version,
202 prerelease: Option<&PrereleaseSpec>,
203) -> Result<Version, VersionError> {
204 match prerelease {
205 Some(spec) => {
206 let mut version = base;
207 version.pre = make_prerelease(spec.identifier(), 1)?;
208 Ok(version)
209 }
210 None => Ok(base),
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn bump_patch() {
220 let version = Version::parse("1.2.3").unwrap();
221 let bumped = bump_version(&version, BumpType::Patch);
222 assert_eq!(bumped, Version::parse("1.2.4").unwrap());
223 }
224
225 #[test]
226 fn bump_minor() {
227 let version = Version::parse("1.2.3").unwrap();
228 let bumped = bump_version(&version, BumpType::Minor);
229 assert_eq!(bumped, Version::parse("1.3.0").unwrap());
230 }
231
232 #[test]
233 fn bump_major() {
234 let version = Version::parse("1.2.3").unwrap();
235 let bumped = bump_version(&version, BumpType::Major);
236 assert_eq!(bumped, Version::parse("2.0.0").unwrap());
237 }
238
239 #[test]
240 fn bump_version_strips_prerelease() {
241 let version = Version::parse("1.2.3-alpha.1").unwrap();
242 let bumped = bump_version(&version, BumpType::Patch);
243 assert_eq!(bumped, Version::parse("1.2.4").unwrap());
244 }
245
246 #[test]
247 fn bump_none_returns_version_unchanged() {
248 let version = Version::parse("1.2.3").unwrap();
249 let bumped = bump_version(&version, BumpType::None);
250 assert_eq!(bumped, Version::parse("1.2.3").unwrap());
251 }
252
253 #[test]
254 fn bump_none_preserves_prerelease() {
255 let version = Version::parse("1.2.3-alpha.1").unwrap();
256 let bumped = bump_version(&version, BumpType::None);
257 assert_eq!(bumped, Version::parse("1.2.3-alpha.1").unwrap());
258 }
259
260 mod max_bump_type_tests {
261 use super::*;
262
263 #[test]
264 fn returns_none_for_empty_slice() {
265 assert_eq!(max_bump_type(&[]), None);
266 }
267
268 #[test]
269 fn returns_single_element() {
270 assert_eq!(max_bump_type(&[BumpType::Patch]), Some(BumpType::Patch));
271 assert_eq!(max_bump_type(&[BumpType::Minor]), Some(BumpType::Minor));
272 assert_eq!(max_bump_type(&[BumpType::Major]), Some(BumpType::Major));
273 }
274
275 #[test]
276 fn returns_minor_for_patch_and_minor() {
277 assert_eq!(
278 max_bump_type(&[BumpType::Patch, BumpType::Minor]),
279 Some(BumpType::Minor)
280 );
281 }
282
283 #[test]
284 fn returns_major_for_minor_and_major() {
285 assert_eq!(
286 max_bump_type(&[BumpType::Minor, BumpType::Major]),
287 Some(BumpType::Major)
288 );
289 }
290
291 #[test]
292 fn returns_major_for_all_three() {
293 assert_eq!(
294 max_bump_type(&[BumpType::Patch, BumpType::Minor, BumpType::Major]),
295 Some(BumpType::Major)
296 );
297 }
298
299 #[test]
300 fn handles_duplicates() {
301 assert_eq!(
302 max_bump_type(&[BumpType::Patch, BumpType::Patch, BumpType::Minor]),
303 Some(BumpType::Minor)
304 );
305 }
306
307 #[test]
308 fn order_does_not_matter() {
309 assert_eq!(
310 max_bump_type(&[BumpType::Major, BumpType::Patch, BumpType::Minor]),
311 Some(BumpType::Major)
312 );
313 }
314
315 #[test]
316 fn none_with_patch_returns_patch() {
317 assert_eq!(
318 max_bump_type(&[BumpType::None, BumpType::Patch]),
319 Some(BumpType::Patch)
320 );
321 }
322
323 #[test]
324 fn all_none_returns_none() {
325 assert_eq!(
326 max_bump_type(&[BumpType::None, BumpType::None]),
327 Some(BumpType::None)
328 );
329 }
330
331 #[test]
332 fn single_none() {
333 assert_eq!(max_bump_type(&[BumpType::None]), Some(BumpType::None));
334 }
335 }
336
337 mod parse_prerelease_tests {
338 use super::*;
339
340 #[test]
341 fn empty_prerelease_returns_none() {
342 let pre = Prerelease::EMPTY;
343 assert!(parse_prerelease(&pre).is_none());
344 }
345
346 #[test]
347 fn parses_standard_format() {
348 let pre = Prerelease::new("alpha.1").unwrap();
349 let (tag, num) = parse_prerelease(&pre).unwrap();
350 assert_eq!(tag, "alpha");
351 assert_eq!(num, 1);
352 }
353
354 #[test]
355 fn parses_higher_numbers() {
356 let pre = Prerelease::new("rc.42").unwrap();
357 let (tag, num) = parse_prerelease(&pre).unwrap();
358 assert_eq!(tag, "rc");
359 assert_eq!(num, 42);
360 }
361
362 #[test]
363 fn handles_tag_without_number() {
364 let pre = Prerelease::new("beta").unwrap();
365 let (tag, num) = parse_prerelease(&pre).unwrap();
366 assert_eq!(tag, "beta");
367 assert_eq!(num, 1);
368 }
369
370 #[test]
371 fn handles_complex_tag_with_dots() {
372 let pre = Prerelease::new("pre.release.3").unwrap();
373 let (tag, num) = parse_prerelease(&pre).unwrap();
374 assert_eq!(tag, "pre.release");
375 assert_eq!(num, 3);
376 }
377 }
378
379 mod calculate_new_version_tests {
380 use super::*;
381
382 #[test]
383 fn stable_to_alpha_with_patch() {
384 let version = Version::parse("1.0.0").unwrap();
385 let result = calculate_new_version(
386 &version,
387 Some(BumpType::Patch),
388 Some(&PrereleaseSpec::Alpha),
389 )
390 .unwrap();
391 assert_eq!(result, Version::parse("1.0.1-alpha.1").unwrap());
392 }
393
394 #[test]
395 fn stable_to_alpha_with_minor() {
396 let version = Version::parse("1.0.0").unwrap();
397 let result = calculate_new_version(
398 &version,
399 Some(BumpType::Minor),
400 Some(&PrereleaseSpec::Alpha),
401 )
402 .unwrap();
403 assert_eq!(result, Version::parse("1.1.0-alpha.1").unwrap());
404 }
405
406 #[test]
407 fn stable_to_alpha_with_major() {
408 let version = Version::parse("1.0.0").unwrap();
409 let result = calculate_new_version(
410 &version,
411 Some(BumpType::Major),
412 Some(&PrereleaseSpec::Alpha),
413 )
414 .unwrap();
415 assert_eq!(result, Version::parse("2.0.0-alpha.1").unwrap());
416 }
417
418 #[test]
419 fn alpha_increment_same_tag() {
420 let version = Version::parse("1.0.1-alpha.1").unwrap();
421 let result =
422 calculate_new_version(&version, None, Some(&PrereleaseSpec::Alpha)).unwrap();
423 assert_eq!(result, Version::parse("1.0.1-alpha.2").unwrap());
424 }
425
426 #[test]
427 fn alpha_to_beta_transition() {
428 let version = Version::parse("1.0.1-alpha.3").unwrap();
429 let result =
430 calculate_new_version(&version, None, Some(&PrereleaseSpec::Beta)).unwrap();
431 assert_eq!(result, Version::parse("1.0.1-beta.1").unwrap());
432 }
433
434 #[test]
435 fn beta_to_rc_transition() {
436 let version = Version::parse("1.0.1-beta.2").unwrap();
437 let result = calculate_new_version(&version, None, Some(&PrereleaseSpec::Rc)).unwrap();
438 assert_eq!(result, Version::parse("1.0.1-rc.1").unwrap());
439 }
440
441 #[test]
442 fn rc_graduate_to_stable() {
443 let version = Version::parse("1.0.1-rc.1").unwrap();
444 let result = calculate_new_version(&version, None, None).unwrap();
445 assert_eq!(result, Version::parse("1.0.1").unwrap());
446 }
447
448 #[test]
449 fn alpha_graduate_to_stable() {
450 let version = Version::parse("1.0.1-alpha.5").unwrap();
451 let result = calculate_new_version(&version, None, None).unwrap();
452 assert_eq!(result, Version::parse("1.0.1").unwrap());
453 }
454
455 #[test]
456 fn custom_prerelease_tag() {
457 let version = Version::parse("1.0.0").unwrap();
458 let spec = PrereleaseSpec::Custom("dev".to_string());
459 let result =
460 calculate_new_version(&version, Some(BumpType::Patch), Some(&spec)).unwrap();
461 assert_eq!(result, Version::parse("1.0.1-dev.1").unwrap());
462 }
463
464 #[test]
465 fn stable_bump_without_prerelease() {
466 let version = Version::parse("1.0.0").unwrap();
467 let result = calculate_new_version(&version, Some(BumpType::Minor), None).unwrap();
468 assert_eq!(result, Version::parse("1.1.0").unwrap());
469 }
470
471 #[test]
472 fn stable_no_change_without_bump_or_prerelease() {
473 let version = Version::parse("1.0.0").unwrap();
474 let result = calculate_new_version(&version, None, None).unwrap();
475 assert_eq!(result, Version::parse("1.0.0").unwrap());
476 }
477
478 #[test]
479 fn prerelease_defaults_to_patch_when_no_bump_specified() {
480 let version = Version::parse("1.0.0").unwrap();
481 let result =
482 calculate_new_version(&version, None, Some(&PrereleaseSpec::Alpha)).unwrap();
483 assert_eq!(result, Version::parse("1.0.1-alpha.1").unwrap());
484 }
485
486 #[test]
487 fn none_bump_with_prerelease_defaults_to_patch() {
488 let version = Version::parse("1.0.0").unwrap();
489 let result =
490 calculate_new_version(&version, Some(BumpType::None), Some(&PrereleaseSpec::Alpha))
491 .unwrap();
492 assert_eq!(result, Version::parse("1.0.1-alpha.1").unwrap());
493 }
494
495 #[test]
496 fn none_bump_with_prerelease_on_zero_version_defaults_to_patch() {
497 let version = Version::parse("0.5.0").unwrap();
498 let result =
499 calculate_new_version(&version, Some(BumpType::None), Some(&PrereleaseSpec::Beta))
500 .unwrap();
501 assert_eq!(result, Version::parse("0.5.1-beta.1").unwrap());
502 }
503 }
504
505 mod is_prerelease_tests {
506 use super::*;
507
508 #[test]
509 fn stable_version_is_not_prerelease() {
510 let version = Version::parse("1.0.0").unwrap();
511 assert!(!is_prerelease(&version));
512 }
513
514 #[test]
515 fn alpha_version_is_prerelease() {
516 let version = Version::parse("1.0.0-alpha.1").unwrap();
517 assert!(is_prerelease(&version));
518 }
519
520 #[test]
521 fn rc_version_is_prerelease() {
522 let version = Version::parse("1.0.0-rc.1").unwrap();
523 assert!(is_prerelease(&version));
524 }
525 }
526
527 mod extract_prerelease_tag_tests {
528 use super::*;
529
530 #[test]
531 fn stable_version_returns_none() {
532 let version = Version::parse("1.0.0").unwrap();
533 assert!(extract_prerelease_tag(&version).is_none());
534 }
535
536 #[test]
537 fn extracts_alpha_tag() {
538 let version = Version::parse("1.0.0-alpha.1").unwrap();
539 assert_eq!(extract_prerelease_tag(&version), Some("alpha".to_string()));
540 }
541
542 #[test]
543 fn extracts_rc_tag() {
544 let version = Version::parse("1.0.0-rc.3").unwrap();
545 assert_eq!(extract_prerelease_tag(&version), Some("rc".to_string()));
546 }
547
548 #[test]
549 fn extracts_custom_tag() {
550 let version = Version::parse("1.0.0-nightly.5").unwrap();
551 assert_eq!(
552 extract_prerelease_tag(&version),
553 Some("nightly".to_string())
554 );
555 }
556 }
557
558 mod is_zero_version_tests {
559 use super::*;
560
561 #[test]
562 fn zero_major_is_zero_version() {
563 let version = Version::parse("0.1.0").unwrap();
564 assert!(is_zero_version(&version));
565 }
566
567 #[test]
568 fn zero_minor_patch_is_zero_version() {
569 let version = Version::parse("0.0.1").unwrap();
570 assert!(is_zero_version(&version));
571 }
572
573 #[test]
574 fn one_major_is_not_zero_version() {
575 let version = Version::parse("1.0.0").unwrap();
576 assert!(!is_zero_version(&version));
577 }
578
579 #[test]
580 fn two_major_is_not_zero_version() {
581 let version = Version::parse("2.3.4").unwrap();
582 assert!(!is_zero_version(&version));
583 }
584
585 #[test]
586 fn zero_prerelease_is_zero_version() {
587 let version = Version::parse("0.1.0-alpha.1").unwrap();
588 assert!(is_zero_version(&version));
589 }
590 }
591
592 mod calculate_new_version_with_zero_behavior_tests {
593 use super::*;
594
595 mod effective_minor_behavior {
596 use super::*;
597
598 #[test]
599 fn major_becomes_minor() {
600 let version = Version::parse("0.1.2").unwrap();
601 let result = calculate_new_version_with_zero_behavior(
602 &version,
603 Some(BumpType::Major),
604 None,
605 ZeroVersionBehavior::EffectiveMinor,
606 false,
607 )
608 .unwrap();
609 assert_eq!(result, Version::parse("0.2.0").unwrap());
610 }
611
612 #[test]
613 fn minor_becomes_patch() {
614 let version = Version::parse("0.1.2").unwrap();
615 let result = calculate_new_version_with_zero_behavior(
616 &version,
617 Some(BumpType::Minor),
618 None,
619 ZeroVersionBehavior::EffectiveMinor,
620 false,
621 )
622 .unwrap();
623 assert_eq!(result, Version::parse("0.1.3").unwrap());
624 }
625
626 #[test]
627 fn patch_stays_patch() {
628 let version = Version::parse("0.1.2").unwrap();
629 let result = calculate_new_version_with_zero_behavior(
630 &version,
631 Some(BumpType::Patch),
632 None,
633 ZeroVersionBehavior::EffectiveMinor,
634 false,
635 )
636 .unwrap();
637 assert_eq!(result, Version::parse("0.1.3").unwrap());
638 }
639
640 #[test]
641 fn major_with_prerelease() {
642 let version = Version::parse("0.1.2").unwrap();
643 let result = calculate_new_version_with_zero_behavior(
644 &version,
645 Some(BumpType::Major),
646 Some(&PrereleaseSpec::Alpha),
647 ZeroVersionBehavior::EffectiveMinor,
648 false,
649 )
650 .unwrap();
651 assert_eq!(result, Version::parse("0.2.0-alpha.1").unwrap());
652 }
653
654 #[test]
655 fn double_zero_version() {
656 let version = Version::parse("0.0.5").unwrap();
657 let result = calculate_new_version_with_zero_behavior(
658 &version,
659 Some(BumpType::Major),
660 None,
661 ZeroVersionBehavior::EffectiveMinor,
662 false,
663 )
664 .unwrap();
665 assert_eq!(result, Version::parse("0.1.0").unwrap());
666 }
667
668 #[test]
669 fn none_stays_none() {
670 let version = Version::parse("0.1.2").unwrap();
671 let result = calculate_new_version_with_zero_behavior(
672 &version,
673 Some(BumpType::None),
674 None,
675 ZeroVersionBehavior::EffectiveMinor,
676 false,
677 )
678 .unwrap();
679 assert_eq!(result, Version::parse("0.1.2").unwrap());
680 }
681 }
682
683 mod auto_promote_behavior {
684 use super::*;
685
686 #[test]
687 fn major_becomes_1_0_0() {
688 let version = Version::parse("0.1.2").unwrap();
689 let result = calculate_new_version_with_zero_behavior(
690 &version,
691 Some(BumpType::Major),
692 None,
693 ZeroVersionBehavior::AutoPromoteOnMajor,
694 false,
695 )
696 .unwrap();
697 assert_eq!(result, Version::parse("1.0.0").unwrap());
698 }
699
700 #[test]
701 fn minor_stays_minor() {
702 let version = Version::parse("0.1.2").unwrap();
703 let result = calculate_new_version_with_zero_behavior(
704 &version,
705 Some(BumpType::Minor),
706 None,
707 ZeroVersionBehavior::AutoPromoteOnMajor,
708 false,
709 )
710 .unwrap();
711 assert_eq!(result, Version::parse("0.2.0").unwrap());
712 }
713
714 #[test]
715 fn patch_stays_patch() {
716 let version = Version::parse("0.1.2").unwrap();
717 let result = calculate_new_version_with_zero_behavior(
718 &version,
719 Some(BumpType::Patch),
720 None,
721 ZeroVersionBehavior::AutoPromoteOnMajor,
722 false,
723 )
724 .unwrap();
725 assert_eq!(result, Version::parse("0.1.3").unwrap());
726 }
727
728 #[test]
729 fn major_with_prerelease() {
730 let version = Version::parse("0.1.2").unwrap();
731 let result = calculate_new_version_with_zero_behavior(
732 &version,
733 Some(BumpType::Major),
734 Some(&PrereleaseSpec::Alpha),
735 ZeroVersionBehavior::AutoPromoteOnMajor,
736 false,
737 )
738 .unwrap();
739 assert_eq!(result, Version::parse("1.0.0-alpha.1").unwrap());
740 }
741 }
742
743 mod none_bump_behavior {
744 use super::*;
745
746 #[test]
747 fn none_leaves_version_unchanged() {
748 let version = Version::parse("0.1.2").unwrap();
749 let result = calculate_new_version_with_zero_behavior(
750 &version,
751 Some(BumpType::None),
752 None,
753 ZeroVersionBehavior::AutoPromoteOnMajor,
754 false,
755 )
756 .unwrap();
757 assert_eq!(result, Version::parse("0.1.2").unwrap());
758 }
759 }
760
761 mod stable_versions_unaffected {
762 use super::*;
763
764 #[test]
765 fn effective_minor_major_bump() {
766 let version = Version::parse("1.2.3").unwrap();
767 let result = calculate_new_version_with_zero_behavior(
768 &version,
769 Some(BumpType::Major),
770 None,
771 ZeroVersionBehavior::EffectiveMinor,
772 false,
773 )
774 .unwrap();
775 assert_eq!(result, Version::parse("2.0.0").unwrap());
776 }
777
778 #[test]
779 fn auto_promote_major_bump() {
780 let version = Version::parse("1.2.3").unwrap();
781 let result = calculate_new_version_with_zero_behavior(
782 &version,
783 Some(BumpType::Major),
784 None,
785 ZeroVersionBehavior::AutoPromoteOnMajor,
786 false,
787 )
788 .unwrap();
789 assert_eq!(result, Version::parse("2.0.0").unwrap());
790 }
791 }
792
793 mod graduation {
794 use super::*;
795
796 #[test]
797 fn promotes_zero_to_1_0_0() {
798 let version = Version::parse("0.5.3").unwrap();
799 let result = calculate_new_version_with_zero_behavior(
800 &version,
801 None,
802 None,
803 ZeroVersionBehavior::EffectiveMinor,
804 true,
805 )
806 .unwrap();
807 assert_eq!(result, Version::parse("1.0.0").unwrap());
808 }
809
810 #[test]
811 fn with_prerelease() {
812 let version = Version::parse("0.5.3").unwrap();
813 let result = calculate_new_version_with_zero_behavior(
814 &version,
815 None,
816 Some(&PrereleaseSpec::Alpha),
817 ZeroVersionBehavior::EffectiveMinor,
818 true,
819 )
820 .unwrap();
821 assert_eq!(result, Version::parse("1.0.0-alpha.1").unwrap());
822 }
823
824 #[test]
825 fn errors_on_prerelease_version() {
826 let version = Version::parse("0.5.3-alpha.1").unwrap();
827 let result = calculate_new_version_with_zero_behavior(
828 &version,
829 None,
830 None,
831 ZeroVersionBehavior::EffectiveMinor,
832 true,
833 );
834 assert!(matches!(
835 result,
836 Err(VersionError::CannotGraduateFromPrerelease { .. })
837 ));
838 }
839
840 #[test]
841 fn errors_on_stable_version() {
842 let version = Version::parse("1.2.3").unwrap();
843 let result = calculate_new_version_with_zero_behavior(
844 &version,
845 None,
846 None,
847 ZeroVersionBehavior::EffectiveMinor,
848 true,
849 );
850 assert!(matches!(
851 result,
852 Err(VersionError::CanOnlyGraduateZeroVersions { .. })
853 ));
854 }
855
856 #[test]
857 fn bump_type_ignored_when_graduating() {
858 let version = Version::parse("0.5.3").unwrap();
859 let result = calculate_new_version_with_zero_behavior(
860 &version,
861 Some(BumpType::Patch),
862 None,
863 ZeroVersionBehavior::EffectiveMinor,
864 true,
865 )
866 .unwrap();
867 assert_eq!(result, Version::parse("1.0.0").unwrap());
868 }
869
870 #[test]
871 fn behavior_ignored_when_graduating() {
872 let version = Version::parse("0.5.3").unwrap();
873 let result = calculate_new_version_with_zero_behavior(
874 &version,
875 None,
876 None,
877 ZeroVersionBehavior::AutoPromoteOnMajor,
878 true,
879 )
880 .unwrap();
881 assert_eq!(result, Version::parse("1.0.0").unwrap());
882 }
883 }
884 }
885}