diff --git a/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs b/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs index e3555da9d0..7d5588d73a 100644 --- a/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs +++ b/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs @@ -18,7 +18,8 @@ public DependencySetsViewModel(IEnumerable packageDependencie { try { - DependencySets = new Dictionary>(); + // Create a list to hold TFM string, parsed framework, and dependencies for proper sorting + var frameworkGroups = new List<(string tfmString, NuGetFramework framework, string friendlyName, IEnumerable dependencies)>(); var dependencySets = packageDependencies.GroupBy(d => d.TargetFramework); @@ -26,19 +27,36 @@ public DependencySetsViewModel(IEnumerable packageDependencie foreach (var dependencySet in dependencySets) { - var targetFramework = dependencySet.Key == null - ? "All Frameworks" - : NuGetFramework.Parse(dependencySet.Key).ToFriendlyName(); + string tfmString = dependencySet.Key; + string friendlyName; + NuGetFramework framework; - if (!DependencySets.ContainsKey(targetFramework)) + if (tfmString == null) { - DependencySets.Add(targetFramework, - dependencySet.OrderBy(x => x.Id).Select(d => d.Id == null ? null : new DependencyViewModel(d.Id, d.VersionSpec))); + friendlyName = "All Frameworks"; + framework = null; } + else + { + framework = NuGetFramework.Parse(tfmString); + friendlyName = framework.ToFriendlyName(); + } + + var dependencies = dependencySet.OrderBy(x => x.Id, StringComparer.OrdinalIgnoreCase).Select(d => d.Id == null ? null : new DependencyViewModel(d.Id, d.VersionSpec)); + frameworkGroups.Add((tfmString, framework, friendlyName, dependencies)); + } + + // Sort by framework using NuGetFrameworkSorter, with null frameworks (All Frameworks) first + var sortedGroups = frameworkGroups.OrderBy(g => g.framework, NullableFrameworkComparer.Instance); + + // Build an ordered list to preserve the sorting + var orderedDependencySets = new List>>(); + foreach (var group in sortedGroups) + { + orderedDependencySets.Add(new KeyValuePair>(group.friendlyName, group.dependencies)); } - // Order the top level frameworks by their resulting friendly name - DependencySets = DependencySets.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); + DependencySets = orderedDependencySets; } catch (Exception e) { @@ -48,7 +66,7 @@ public DependencySetsViewModel(IEnumerable packageDependencie } } - public IDictionary> DependencySets { get; private set; } + public IReadOnlyList>> DependencySets { get; private set; } public bool OnlyHasAllFrameworks { get; private set; } public class DependencyViewModel @@ -72,5 +90,20 @@ public DependencyViewModel(string id, string versionSpec) public string VersionSpec { get; private set; } public string PackageUrl { get; private set; } } + + private class NullableFrameworkComparer : IComparer + { + public static readonly NullableFrameworkComparer Instance = new NullableFrameworkComparer(); + + public int Compare(NuGetFramework x, NuGetFramework y) + { + // Put "All Frameworks" (null) first + if (x == null && y == null) return 0; + if (x == null) return -1; + if (y == null) return 1; + // Use NuGetFrameworkSorter for actual frameworks + return NuGetFrameworkSorter.Instance.Compare(x, y); + } + } } } diff --git a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs index ef470421e6..a494ca08aa 100644 --- a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs +++ b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Linq; using NuGet.Services.Entities; using Xunit; @@ -26,10 +27,10 @@ public void GivenAListOfDependenciesItShouldGroupByTargetFrameworkName() // Assert Assert.Equal(3, vm.DependencySets.Count); - Assert.Null(vm.DependencySets["All Frameworks"].Single()); - Assert.Null(vm.DependencySets["Portable Class Library (.NETFramework 4.5, Windows 8.0)"].Single()); + Assert.Null(vm.DependencySets.Single(kvp => kvp.Key == "All Frameworks").Value.Single()); + Assert.Null(vm.DependencySets.Single(kvp => kvp.Key == "Portable Class Library (.NETFramework 4.5, Windows 8.0)").Value.Single()); - var actual = vm.DependencySets["Portable Class Library (.NETFramework 4.0, Silverlight 4.0, Windows 8.0, WindowsPhone 7.1)"].ToArray(); + var actual = vm.DependencySets.Single(kvp => kvp.Key == "Portable Class Library (.NETFramework 4.0, Silverlight 4.0, Windows 8.0, WindowsPhone 7.1)").Value.ToArray(); Assert.Single(actual); Assert.Equal("Microsoft.Net.Http", actual[0].Id); Assert.Equal("(>= 2.1.0 && < 3.0.0)", actual[0].VersionSpec); @@ -55,7 +56,7 @@ public void GivenAListOfDependenciesTargetFrameworksWillBeOrdered() // Assert Assert.Equal(7, viewModel.DependencySets.Count); - var dependencySetsList = viewModel.DependencySets.Keys.ToList(); + var dependencySetsList = viewModel.DependencySets.Select(kvp => kvp.Key).ToList(); Assert.Equal(".NETFramework 4.5", dependencySetsList[0]); Assert.Equal(".NETFramework 4.6.2", dependencySetsList[1]); @@ -91,6 +92,57 @@ public void GivenAListOfDependenciesPackageIdsWillBeOrdered() Assert.Equal("cde", dependencyViewModels[2].Id); Assert.Equal("def", dependencyViewModels[3].Id); } + + [Fact] + public void GivenAListOfDependenciesPackageIdsWillBeOrderedCaseInsensitively() + { + // Arrange + var dependencies = new[] + { + new PackageDependency { TargetFramework = null, Id = "Zebra" }, + new PackageDependency { TargetFramework = null, Id = "apple" }, + new PackageDependency { TargetFramework = null, Id = "Banana" }, + new PackageDependency { TargetFramework = null, Id = "cherry" } + }; + + // Act + var viewModel = new DependencySetsViewModel(dependencies); + + // Assert + Assert.Single(viewModel.DependencySets); + Assert.Equal(4, viewModel.DependencySets.First().Value.Count()); + + var dependencyViewModels = viewModel.DependencySets.First().Value.ToList(); + + Assert.Equal("apple", dependencyViewModels[0].Id); + Assert.Equal("Banana", dependencyViewModels[1].Id); + Assert.Equal("cherry", dependencyViewModels[2].Id); + Assert.Equal("Zebra", dependencyViewModels[3].Id); + } + + [Fact] + public void GivenAListOfDependenciesNet10WillBeOrderedAfterNet9() + { + // Arrange + var dependencies = new[] + { + new PackageDependency { TargetFramework = "net10.0" }, + new PackageDependency { TargetFramework = "net8.0" }, + new PackageDependency { TargetFramework = "net9.0" } + }; + + // Act + var viewModel = new DependencySetsViewModel(dependencies); + + // Assert + Assert.Equal(3, viewModel.DependencySets.Count); + + var dependencySetsList = viewModel.DependencySets.Select(kvp => kvp.Key).ToList(); + + Assert.Equal("net8.0", dependencySetsList[0]); + Assert.Equal("net9.0", dependencySetsList[1]); + Assert.Equal("net10.0", dependencySetsList[2]); + } } } }