@@ -1429,15 +1429,15 @@ mod highest_or_lowest {
14291429 overridden: None ,
14301430 } ,
14311431 ExpectedInstance {
1432- state: InstanceState :: valid( SatisfiesHighestOrLowestSemver ) ,
1432+ state: InstanceState :: valid( IsHighestOrLowestSemver ) ,
14331433 dependency_name: "@typescript/native-preview" ,
14341434 id: "@typescript/native-preview in /devDependencies of pkg-a" ,
14351435 actual: "7.0.0-dev.20260117.1" ,
14361436 expected: Some ( "7.0.0-dev.20260117.1" ) ,
14371437 overridden: None ,
14381438 } ,
14391439 ExpectedInstance {
1440- state: InstanceState :: valid( SatisfiesHighestOrLowestSemver ) ,
1440+ state: InstanceState :: valid( IsHighestOrLowestSemver ) ,
14411441 dependency_name: "lodash" ,
14421442 id: "lodash in /devDependencies of pkg-a" ,
14431443 actual: "4.17.21" ,
@@ -1470,6 +1470,187 @@ mod highest_or_lowest {
14701470 } ,
14711471 ] ) ;
14721472 }
1473+
1474+ /// https://github.com/JamieMason/syncpack/issues/314#issuecomment-3848268945
1475+ /// When versions differ AND a semver group exists, the fix target for
1476+ /// DiffersToHighestOrLowestSemver should apply the semver group's preferred
1477+ /// range — not inherit the range from the highest specifier.
1478+ #[ test]
1479+ fn differs_to_highest_semver_should_apply_semver_group_range ( ) {
1480+ let ctx = TestBuilder :: new ( )
1481+ . with_packages ( vec ! [
1482+ json!( {
1483+ "name" : "pkg-a" ,
1484+ "devDependencies" : {
1485+ "react" : "^18.0.0"
1486+ }
1487+ } ) ,
1488+ json!( {
1489+ "name" : "pkg-b" ,
1490+ "devDependencies" : {
1491+ "react" : "17.0.0"
1492+ }
1493+ } ) ,
1494+ json!( {
1495+ "name" : "pkg-c" ,
1496+ "devDependencies" : {
1497+ "react" : "17.0.0"
1498+ }
1499+ } ) ,
1500+ ] )
1501+ . with_semver_group ( json ! ( {
1502+ "label" : "pin all ranges" ,
1503+ "range" : "" ,
1504+ "packages" : [ "**" ] ,
1505+ "dependencies" : [ "**" ]
1506+ } ) )
1507+ . build_and_visit_packages ( ) ;
1508+
1509+ expect ( & ctx) . to_have_instances ( vec ! [
1510+ ExpectedInstance {
1511+ state: InstanceState :: suspect( InvalidLocalVersion ) ,
1512+ dependency_name: "pkg-a" ,
1513+ id: "pkg-a in /version of pkg-a" ,
1514+ actual: "" ,
1515+ expected: Some ( "" ) ,
1516+ overridden: None ,
1517+ } ,
1518+ // pkg-a has the highest version but wrong range (^ instead of pinned)
1519+ ExpectedInstance {
1520+ state: InstanceState :: fixable( SemverRangeMismatch ) ,
1521+ dependency_name: "react" ,
1522+ id: "react in /devDependencies of pkg-a" ,
1523+ actual: "^18.0.0" ,
1524+ expected: Some ( "18.0.0" ) ,
1525+ overridden: None ,
1526+ } ,
1527+ ExpectedInstance {
1528+ state: InstanceState :: suspect( InvalidLocalVersion ) ,
1529+ dependency_name: "pkg-b" ,
1530+ id: "pkg-b in /version of pkg-b" ,
1531+ actual: "" ,
1532+ expected: Some ( "" ) ,
1533+ overridden: None ,
1534+ } ,
1535+ // pkg-b has a lower version — fix should update version AND apply pinned range
1536+ // BUG: currently suggests ^18.0.0 (inherits ^ from highest)
1537+ ExpectedInstance {
1538+ state: InstanceState :: fixable( DiffersToHighestOrLowestSemver ) ,
1539+ dependency_name: "react" ,
1540+ id: "react in /devDependencies of pkg-b" ,
1541+ actual: "17.0.0" ,
1542+ expected: Some ( "18.0.0" ) ,
1543+ overridden: None ,
1544+ } ,
1545+ ExpectedInstance {
1546+ state: InstanceState :: suspect( InvalidLocalVersion ) ,
1547+ dependency_name: "pkg-c" ,
1548+ id: "pkg-c in /version of pkg-c" ,
1549+ actual: "" ,
1550+ expected: Some ( "" ) ,
1551+ overridden: None ,
1552+ } ,
1553+ // pkg-c same as pkg-b
1554+ ExpectedInstance {
1555+ state: InstanceState :: fixable( DiffersToHighestOrLowestSemver ) ,
1556+ dependency_name: "react" ,
1557+ id: "react in /devDependencies of pkg-c" ,
1558+ actual: "17.0.0" ,
1559+ expected: Some ( "18.0.0" ) ,
1560+ overridden: None ,
1561+ } ,
1562+ ] ) ;
1563+ }
1564+
1565+ /// Exploratory: when a semver group makes one instance greedier (e.g. exact
1566+ /// → ^), that adjusted specifier becomes the highest via the range tiebreaker.
1567+ /// Other instances (not in a semver group) should follow the new highest.
1568+ #[ test]
1569+ fn semver_group_greedier_range_becomes_highest ( ) {
1570+ let ctx = TestBuilder :: new ( )
1571+ . with_packages ( vec ! [
1572+ json!( {
1573+ "name" : "pkg-a" ,
1574+ "dependencies" : {
1575+ "react" : "1.0.0"
1576+ }
1577+ } ) ,
1578+ json!( {
1579+ "name" : "pkg-b" ,
1580+ "dependencies" : {
1581+ "react" : "~1.0.0"
1582+ }
1583+ } ) ,
1584+ json!( {
1585+ "name" : "pkg-c" ,
1586+ "dependencies" : {
1587+ "react" : "~1.0.0"
1588+ }
1589+ } ) ,
1590+ ] )
1591+ . with_semver_group ( json ! ( {
1592+ "label" : "use caret for pkg-a" ,
1593+ "range" : "^" ,
1594+ "packages" : [ "pkg-a" ]
1595+ } ) )
1596+ . build_and_visit_packages ( ) ;
1597+
1598+ expect ( & ctx) . to_have_instances ( vec ! [
1599+ ExpectedInstance {
1600+ state: InstanceState :: suspect( InvalidLocalVersion ) ,
1601+ dependency_name: "pkg-a" ,
1602+ id: "pkg-a in /version of pkg-a" ,
1603+ actual: "" ,
1604+ expected: Some ( "" ) ,
1605+ overridden: None ,
1606+ } ,
1607+ // pkg-a has exact 1.0.0 but semver group wants ^ → adjusted to ^1.0.0
1608+ // ^1.0.0 is greedier than ~1.0.0, making it the highest
1609+ // pkg-a's actual range (exact) doesn't match its preferred range (^)
1610+ ExpectedInstance {
1611+ state: InstanceState :: fixable( SemverRangeMismatch ) ,
1612+ dependency_name: "react" ,
1613+ id: "react in /dependencies of pkg-a" ,
1614+ actual: "1.0.0" ,
1615+ expected: Some ( "^1.0.0" ) ,
1616+ overridden: None ,
1617+ } ,
1618+ ExpectedInstance {
1619+ state: InstanceState :: suspect( InvalidLocalVersion ) ,
1620+ dependency_name: "pkg-b" ,
1621+ id: "pkg-b in /version of pkg-b" ,
1622+ actual: "" ,
1623+ expected: Some ( "" ) ,
1624+ overridden: None ,
1625+ } ,
1626+ // pkg-b has ~1.0.0, no semver group — follows adjusted highest ^1.0.0
1627+ ExpectedInstance {
1628+ state: InstanceState :: fixable( DiffersToHighestOrLowestSemver ) ,
1629+ dependency_name: "react" ,
1630+ id: "react in /dependencies of pkg-b" ,
1631+ actual: "~1.0.0" ,
1632+ expected: Some ( "^1.0.0" ) ,
1633+ overridden: None ,
1634+ } ,
1635+ ExpectedInstance {
1636+ state: InstanceState :: suspect( InvalidLocalVersion ) ,
1637+ dependency_name: "pkg-c" ,
1638+ id: "pkg-c in /version of pkg-c" ,
1639+ actual: "" ,
1640+ expected: Some ( "" ) ,
1641+ overridden: None ,
1642+ } ,
1643+ // pkg-c same as pkg-b
1644+ ExpectedInstance {
1645+ state: InstanceState :: fixable( DiffersToHighestOrLowestSemver ) ,
1646+ dependency_name: "react" ,
1647+ id: "react in /dependencies of pkg-c" ,
1648+ actual: "~1.0.0" ,
1649+ expected: Some ( "^1.0.0" ) ,
1650+ overridden: None ,
1651+ } ,
1652+ ] ) ;
1653+ }
14731654}
14741655
14751656mod non_semver {
0 commit comments