use {crate::specifier::Specifier, std::cmp::Ordering};
#[test]
fn equal_specifiers_are_equal() {
let a = Specifier::new("1.2.3");
let b = Specifier::new("1.2.3");
assert_eq!(a.cmp(&b), Ordering::Equal);
assert_eq!(a, b);
}
#[test]
fn different_versions_compare_by_version() {
let older = Specifier::new("1.0.0");
let newer = Specifier::new("2.0.0");
assert_eq!(older.cmp(&newer), Ordering::Less);
assert_eq!(newer.cmp(&older), Ordering::Greater);
}
#[test]
fn semver_ordering_respects_numeric_comparison() {
let cases = vec![
("0.0.1", "0.0.2"),
("0.1.0", "0.2.0"),
("1.0.0", "2.0.0"),
("1.0.0", "1.0.1"),
("1.0.0", "1.1.0"),
("1.2.3", "1.2.4"),
("1.2.3", "1.3.0"),
("1.2.3", "2.0.0"),
];
for (older, newer) in cases {
let a = Specifier::new(older);
let b = Specifier::new(newer);
assert_eq!(a.cmp(&b), Ordering::Less, "Expected {older} < {newer}");
assert_eq!(b.cmp(&a), Ordering::Greater, "Expected {newer} > {older}");
}
}
#[test]
fn same_version_sorts_by_range_greediness() {
let specs = [
Specifier::new("<1.0.0"), Specifier::new("<=1.0.0"), Specifier::new("1.0.0"), Specifier::new("~1.0.0"), Specifier::new("^1.0.0"), Specifier::new(">=1.0.0"), Specifier::new(">1.0.0"), Specifier::new("*"), ];
for i in 0..specs.len() - 1 {
assert_eq!(
specs[i].cmp(&specs[i + 1]),
Ordering::Less,
"Expected {:?} < {:?}",
specs[i],
specs[i + 1]
);
}
}
#[test]
fn sorting_same_version_different_ranges() {
let mut specifiers = vec![
Specifier::new("0.0.0"),
Specifier::new("<0.0.0"),
Specifier::new("*"),
Specifier::new(">0.0.0"),
Specifier::new(">=0.0.0"),
Specifier::new("<=0.0.0"),
Specifier::new("^0.0.0"),
Specifier::new("~0.0.0"),
];
let expected = vec![
Specifier::new("<0.0.0"),
Specifier::new("<=0.0.0"),
Specifier::new("0.0.0"),
Specifier::new("~0.0.0"),
Specifier::new("^0.0.0"),
Specifier::new(">=0.0.0"),
Specifier::new(">0.0.0"),
Specifier::new("*"),
];
specifiers.sort();
assert_eq!(specifiers, expected);
}
#[test]
fn range_greediness_tiebreaker_for_equal_versions() {
let exact = Specifier::new("2.5.0");
let patch = Specifier::new("~2.5.0");
let minor = Specifier::new("^2.5.0");
assert_eq!(exact.cmp(&patch), Ordering::Less);
assert_eq!(patch.cmp(&minor), Ordering::Less);
assert_eq!(exact.cmp(&minor), Ordering::Less);
}
#[test]
fn sorting_mixed_versions_and_ranges() {
let mut specifiers = [
Specifier::new("2.0.0"),
Specifier::new("1.0.0"),
Specifier::new("^1.5.0"),
Specifier::new("~1.0.0"),
Specifier::new("^1.0.0"),
Specifier::new("1.5.0"),
];
specifiers.sort();
assert_eq!(specifiers[0], Specifier::new("1.0.0"));
assert_eq!(specifiers[1], Specifier::new("~1.0.0"));
assert_eq!(specifiers[2], Specifier::new("^1.0.0"));
assert_eq!(specifiers[3], Specifier::new("1.5.0"));
assert_eq!(specifiers[4], Specifier::new("^1.5.0"));
assert_eq!(specifiers[5], Specifier::new("2.0.0"));
}
#[test]
fn sorting_preserves_version_then_range_order() {
let mut specifiers = [
Specifier::new("3.0.0"),
Specifier::new("^1.0.0"),
Specifier::new("~2.0.0"),
Specifier::new("1.0.0"),
Specifier::new("2.0.0"),
Specifier::new("^2.0.0"),
];
specifiers.sort();
assert_eq!(specifiers[0], Specifier::new("1.0.0"));
assert_eq!(specifiers[1], Specifier::new("^1.0.0"));
assert_eq!(specifiers[2], Specifier::new("2.0.0"));
assert_eq!(specifiers[3], Specifier::new("~2.0.0"));
assert_eq!(specifiers[4], Specifier::new("^2.0.0"));
assert_eq!(specifiers[5], Specifier::new("3.0.0"));
}
#[test]
fn non_version_specifiers_sort_before_versioned() {
let versioned = Specifier::new("1.0.0");
let file = Specifier::new("file:../path");
let url = Specifier::new("https://example.com/package.tgz");
let tag = Specifier::new("alpha");
let unsupported = Specifier::new("}invalid{");
assert_eq!(file.cmp(&versioned), Ordering::Less);
assert_eq!(url.cmp(&versioned), Ordering::Less);
assert_eq!(tag.cmp(&versioned), Ordering::Less);
assert_eq!(unsupported.cmp(&versioned), Ordering::Less);
}
#[test]
fn non_version_specifiers_are_equal_to_each_other() {
let file = Specifier::new("file:../path");
let url = Specifier::new("https://example.com/package.tgz");
let tag = Specifier::new("alpha");
assert_eq!(file.cmp(&url), Ordering::Equal);
assert_eq!(file.cmp(&tag), Ordering::Equal);
assert_eq!(url.cmp(&tag), Ordering::Equal);
}
#[test]
fn sorting_non_version_specifiers() {
let mut specifiers = [
Specifier::new("1.0.0"),
Specifier::new("file:../path"),
Specifier::new("alpha"),
Specifier::new("workspace:^"),
Specifier::new("2.0.0"),
];
specifiers.sort();
assert_eq!(specifiers[0], Specifier::new("file:../path"));
assert_eq!(specifiers[1], Specifier::new("alpha"));
assert_eq!(specifiers[2], Specifier::new("workspace:^"));
assert_eq!(specifiers[3], Specifier::new("1.0.0"));
assert_eq!(specifiers[4], Specifier::new("2.0.0"));
}
#[test]
fn none_variant_sorts_before_versioned() {
let none = Specifier::new("");
let versioned = Specifier::new("1.0.0");
assert_eq!(none.cmp(&versioned), Ordering::Less);
assert_eq!(versioned.cmp(&none), Ordering::Greater);
}
#[test]
fn unresolved_workspace_protocol_sorts_before_versioned() {
let workspace = Specifier::new("workspace:^");
let versioned = Specifier::new("1.0.0");
assert_eq!(workspace.cmp(&versioned), Ordering::Less);
assert_eq!(versioned.cmp(&workspace), Ordering::Greater);
}
#[test]
fn workspace_protocol_with_embedded_version_sorts_normally() {
let workspace = Specifier::new("workspace:^1.5.0");
let exact = Specifier::new("1.5.0");
let minor = Specifier::new("^1.5.0");
assert_eq!(workspace.cmp(&minor), Ordering::Equal);
assert_eq!(workspace.cmp(&exact), Ordering::Greater);
}
#[test]
fn major_version_shorthand_sorts_correctly() {
let major = Specifier::new("1"); let patch = Specifier::new("1.0.0");
let high_patch = Specifier::new("1.999.999");
assert_eq!(major.cmp(&patch), Ordering::Greater);
assert_eq!(major.cmp(&high_patch), Ordering::Greater);
}
#[test]
fn minor_version_shorthand_sorts_correctly() {
let minor = Specifier::new("1.4"); let exact = Specifier::new("1.4.0");
let high_patch = Specifier::new("1.4.999");
assert_eq!(minor.cmp(&exact), Ordering::Greater);
assert_eq!(minor.cmp(&high_patch), Ordering::Greater);
}
#[test]
fn major_shorthand_with_range() {
let range_major = Specifier::new("^1"); let range_full = Specifier::new("^1.0.0");
assert_eq!(range_major.cmp(&range_full), Ordering::Greater);
}
#[test]
fn minor_shorthand_with_range() {
let range_minor = Specifier::new("~1.2"); let range_full = Specifier::new("~1.2.0");
assert_eq!(range_minor.cmp(&range_full), Ordering::Greater);
}
#[test]
fn aliases_sort_by_version_and_range() {
let alias1 = Specifier::new("npm:@jsr/std__fmt@1.0.0");
let alias2 = Specifier::new("npm:@jsr/std__fmt@2.0.0");
let alias3 = Specifier::new("npm:@jsr/std__fmt@^1.0.0");
assert_eq!(alias1.cmp(&alias2), Ordering::Less);
assert_eq!(alias1.cmp(&alias3), Ordering::Less); assert_eq!(alias3.cmp(&alias2), Ordering::Less); }
#[test]
fn aliases_without_version_sort_after_those_with_version() {
let alias_no_version = Specifier::new("npm:@jsr/std__fmt");
let alias_with_version = Specifier::new("npm:@jsr/std__fmt@1.0.0");
assert_eq!(alias_no_version.cmp(&alias_with_version), Ordering::Greater);
}
#[test]
fn sorting_aliases_with_same_versions_different_ranges() {
let mut specifiers = vec![
Specifier::new("npm:@jsr/std__fmt@0.0.0"),
Specifier::new("npm:@jsr/std__fmt@<0.0.0"),
Specifier::new("npm:@jsr/std__fmt@*"),
Specifier::new("npm:@jsr/std__fmt@>0.0.0"),
Specifier::new("npm:@jsr/std__fmt@>=0.0.0"),
Specifier::new("npm:@jsr/std__fmt@<=0.0.0"),
Specifier::new("npm:@jsr/std__fmt@^0.0.0"),
Specifier::new("npm:@jsr/std__fmt@~0.0.0"),
];
let expected = vec![
Specifier::new("npm:@jsr/std__fmt@<0.0.0"),
Specifier::new("npm:@jsr/std__fmt@<=0.0.0"),
Specifier::new("npm:@jsr/std__fmt@0.0.0"),
Specifier::new("npm:@jsr/std__fmt@~0.0.0"),
Specifier::new("npm:@jsr/std__fmt@^0.0.0"),
Specifier::new("npm:@jsr/std__fmt@>=0.0.0"),
Specifier::new("npm:@jsr/std__fmt@>0.0.0"),
Specifier::new("npm:@jsr/std__fmt@*"),
];
specifiers.sort();
assert_eq!(specifiers, expected);
}
#[test]
fn git_specifiers_with_semver_sort_normally() {
let git1 = Specifier::new("github:user/repo#1.0.0");
let git2 = Specifier::new("github:user/repo#2.0.0");
let git3 = Specifier::new("github:user/repo#semver:^1.0.0");
assert_eq!(git1.cmp(&git2), Ordering::Less);
assert_eq!(git1.cmp(&git3), Ordering::Less); }
#[test]
fn git_specifiers_without_semver_sort_before_those_with() {
let git_no_semver = Specifier::new("github:user/repo");
let git_with_semver = Specifier::new("github:user/repo#1.0.0");
assert_eq!(git_no_semver.cmp(&git_with_semver), Ordering::Less);
}
#[test]
fn prerelease_versions_sort_correctly() {
let stable = Specifier::new("1.0.0");
let alpha = Specifier::new("1.0.0-alpha");
let beta = Specifier::new("1.0.0-beta");
let rc = Specifier::new("1.0.0-rc.1");
assert_eq!(alpha.cmp(&stable), Ordering::Less);
assert_eq!(beta.cmp(&stable), Ordering::Less);
assert_eq!(rc.cmp(&stable), Ordering::Less);
assert_eq!(alpha.cmp(&beta), Ordering::Less);
assert_eq!(alpha.cmp(&rc), Ordering::Less);
assert_eq!(beta.cmp(&rc), Ordering::Less);
}
#[test]
fn multi_segment_prerelease_ordering() {
let cases = vec![
("1.0.0-alpha.1", "1.0.0-alpha.1.1"),
("1.0.0-alpha.1.1", "1.0.0-alpha.1.2"),
("1.0.0-alpha.1.2.3", "1.0.0-alpha.1.2.3.4"),
];
for (older, newer) in cases {
let a = Specifier::new(older);
let b = Specifier::new(newer);
assert_eq!(a.cmp(&b), Ordering::Less, "Expected {older} < {newer}");
}
}
#[test]
fn prerelease_with_ranges() {
let exact = Specifier::new("1.0.0-alpha");
let patch = Specifier::new("~1.0.0-alpha");
let minor = Specifier::new("^1.0.0-alpha");
assert_eq!(exact.cmp(&patch), Ordering::Less);
assert_eq!(patch.cmp(&minor), Ordering::Less);
}
#[test]
fn complex_semver_without_simple_version_sorts_before_versioned() {
let complex = Specifier::new(">=1.0.0 <2.0.0");
let simple = Specifier::new("1.5.0");
assert_eq!(complex.cmp(&simple), Ordering::Less);
}
#[test]
fn latest_with_version_sorts_by_version() {
let latest = Specifier::new("*");
let versioned = Specifier::new("1.0.0");
assert_eq!(latest.cmp(&versioned), Ordering::Greater);
}
#[test]
fn partial_ord_delegates_to_ord() {
let a = Specifier::new("1.0.0");
let b = Specifier::new("2.0.0");
assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater));
assert_eq!(a.partial_cmp(&a), Some(Ordering::Equal));
}
#[test]
fn partial_ord_always_returns_some() {
let specs = vec![
Specifier::new("1.0.0"),
Specifier::new("file:../path"),
Specifier::new("alpha"),
Specifier::new("workspace:^"),
];
for spec1 in &specs {
for spec2 in &specs {
assert!(spec1.partial_cmp(spec2).is_some());
}
}
}
#[test]
fn eq_trait_matches_partial_eq() {
let a = Specifier::new("1.2.3");
let b = Specifier::new("1.2.3");
let c = Specifier::new("1.2.4");
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn eq_reflexive() {
let a = Specifier::new("1.2.3");
assert_eq!(a, a);
}
#[test]
fn eq_symmetric() {
let a = Specifier::new("1.2.3");
let b = Specifier::new("1.2.3");
assert_eq!(a, b);
assert_eq!(b, a);
}
#[test]
fn eq_transitive() {
let a = Specifier::new("1.2.3");
let b = Specifier::new("1.2.3");
let c = Specifier::new("1.2.3");
assert_eq!(a, b);
assert_eq!(b, c);
assert_eq!(a, c);
}
#[test]
fn comprehensive_sorting() {
let mut actual = vec![
Specifier::new("1.0.0-beta.1"),
Specifier::new("^1.0.0"),
Specifier::new(">2.0.0"),
Specifier::new("1.5.0"),
Specifier::new("1.0.0"),
Specifier::new("<=1.0.0"),
Specifier::new("1.0.0-alpha"),
Specifier::new("<1.0.0"),
Specifier::new(">=1.5.0"),
Specifier::new("~1.0.0"),
Specifier::new("2.0.0"),
Specifier::new("~1.2"), Specifier::new("1.2"), Specifier::new("1"), Specifier::new("^1"), Specifier::new("npm:typescript@*"),
Specifier::new("npm:react@1.0.0"),
Specifier::new("npm:lodash@^4.17.0"),
Specifier::new("npm:jest"), Specifier::new("npm:vue@~2.6.0"),
Specifier::new("github:user/repo#1.0.0"),
Specifier::new("github:user/repo#semver:^1.5.0"),
Specifier::new("github:user/repo"), Specifier::new("file:../local"),
Specifier::new("https://example.com/package.tgz"),
Specifier::new("alpha"),
Specifier::new("beta"),
Specifier::new("workspace:^"),
Specifier::new("workspace:*"),
Specifier::new(""), Specifier::new("*"),
Specifier::new("latest"),
Specifier::new(">=1.0.0 <2.0.0"),
Specifier::new("}invalid{"),
];
actual.sort();
let expected = vec![
Specifier::new("github:user/repo"), Specifier::new("file:../local"), Specifier::new("https://example.com/package.tgz"), Specifier::new("alpha"), Specifier::new("beta"), Specifier::new("workspace:^"), Specifier::new("workspace:*"), Specifier::new(""), Specifier::new(">=1.0.0 <2.0.0"), Specifier::new("}invalid{"), Specifier::new("1.0.0-alpha"), Specifier::new("1.0.0-beta.1"), Specifier::new("<1.0.0"), Specifier::new("<=1.0.0"), Specifier::new("1.0.0"), Specifier::new("npm:react@1.0.0"), Specifier::new("github:user/repo#1.0.0"), Specifier::new("~1.0.0"), Specifier::new("^1.0.0"), Specifier::new("1.2"), Specifier::new("~1.2"), Specifier::new("1.5.0"), Specifier::new("github:user/repo#semver:^1.5.0"), Specifier::new(">=1.5.0"), Specifier::new("1"), Specifier::new("^1"), Specifier::new("2.0.0"), Specifier::new(">2.0.0"), Specifier::new("npm:vue@~2.6.0"), Specifier::new("npm:lodash@^4.17.0"), Specifier::new("npm:jest"), Specifier::new("npm:typescript@*"), Specifier::new("*"), Specifier::new("latest"), ];
assert_eq!(actual.len(), expected.len(), "Actual and expected have different lengths");
for i in 0..(expected.len() - 4) {
assert_eq!(
&actual[i], &expected[i],
"Mismatch at index {}: expected {:?}, got {:?}",
i, expected[i], actual[i]
);
}
let last_four_actual: Vec<String> = actual.iter().skip(expected.len() - 4).map(|s| s.get_raw().to_string()).collect();
let last_four_expected: Vec<String> = expected.iter().skip(expected.len() - 4).map(|s| s.get_raw().to_string()).collect();
let mut last_four_actual_sorted = last_four_actual.clone();
let mut last_four_expected_sorted = last_four_expected.clone();
last_four_actual_sorted.sort();
last_four_expected_sorted.sort();
assert_eq!(
last_four_actual_sorted, last_four_expected_sorted,
"Last 4 items should be the same set (in any order)"
);
}
#[test]
fn comparing_specifier_with_itself() {
let spec = Specifier::new("1.2.3");
assert_eq!(spec.cmp(&spec), Ordering::Equal);
}
#[test]
fn stable_sort_preserves_equal_elements_order() {
let mut specifiers = vec![
Specifier::new("file:path1"),
Specifier::new("file:path2"),
Specifier::new("alpha"),
Specifier::new("beta"),
];
let original_order = specifiers.clone();
specifiers.sort();
assert_eq!(specifiers, original_order);
}
#[test]
fn unsupported_specifiers_sort_consistently() {
let unsupported1 = Specifier::new("}invalid{");
let unsupported2 = Specifier::new("@@@");
let versioned = Specifier::new("1.0.0");
assert_eq!(unsupported1.cmp(&versioned), Ordering::Less);
assert_eq!(unsupported2.cmp(&versioned), Ordering::Less);
assert_eq!(unsupported1.cmp(&unsupported2), Ordering::Equal);
}