From c1bab618849072abedbe976d71f451a3720326c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:01:47 +0000 Subject: [PATCH 1/9] Initial plan From 934baa8e2e91890aee49101b9b97841cd727efdf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:10:33 +0000 Subject: [PATCH 2/9] Fix TFM ordering to use NuGetFrameworkSorter instead of lexicographic sorting Co-authored-by: joelverhagen <94054+joelverhagen@users.noreply.github.com> --- .../ViewModels/DependencySetsViewModel.cs | 45 +++++++++++++++---- .../DependencySetsViewModelFacts.cs | 23 ++++++++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs b/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs index e3555da9d0..b1fa177795 100644 --- a/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs +++ b/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs @@ -24,21 +24,50 @@ public DependencySetsViewModel(IEnumerable packageDependencie OnlyHasAllFrameworks = dependencySets.Count() == 1 && dependencySets.First().Key == null; + // 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)>(); + 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) + { + friendlyName = "All Frameworks"; + framework = null; + } + else { - DependencySets.Add(targetFramework, - dependencySet.OrderBy(x => x.Id).Select(d => d.Id == null ? null : new DependencyViewModel(d.Id, d.VersionSpec))); + framework = NuGetFramework.Parse(tfmString); + friendlyName = framework.ToFriendlyName(); } + + var dependencies = dependencySet.OrderBy(x => x.Id).Select(d => d.Id == null ? null : new DependencyViewModel(d.Id, d.VersionSpec)); + frameworkGroups.Add((tfmString, framework, friendlyName, dependencies)); } - // Order the top level frameworks by their resulting friendly name - DependencySets = DependencySets.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); + // Sort by framework using NuGetFrameworkSorter, with null frameworks (All Frameworks) first + var sortedGroups = frameworkGroups.OrderBy(g => g.framework, + Comparer.Create((a, b) => + { + // Put "All Frameworks" (null) first + if (a == null && b == null) return 0; + if (a == null) return -1; + if (b == null) return 1; + // Use NuGetFrameworkSorter for actual frameworks + return NuGetFrameworkSorter.Instance.Compare(a, b); + })); + + // Build the final dictionary with friendly names + foreach (var group in sortedGroups) + { + if (!DependencySets.ContainsKey(group.friendlyName)) + { + DependencySets.Add(group.friendlyName, group.dependencies); + } + } } catch (Exception e) { diff --git a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs index ef470421e6..6a9f6a60c1 100644 --- a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs +++ b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs @@ -91,6 +91,29 @@ public void GivenAListOfDependenciesPackageIdsWillBeOrdered() Assert.Equal("cde", dependencyViewModels[2].Id); Assert.Equal("def", 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.Keys.ToList(); + + Assert.Equal(".NET 8.0", dependencySetsList[0]); + Assert.Equal(".NET 9.0", dependencySetsList[1]); + Assert.Equal(".NET 10.0", dependencySetsList[2]); + } } } } From 434a6438f6c6a0c3573e406079f15d3686dad6e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:38:22 +0000 Subject: [PATCH 3/9] Address PR feedback: use case-insensitive Id sorting and simplify framework comparer Co-authored-by: joelverhagen <94054+joelverhagen@users.noreply.github.com> --- .../ViewModels/DependencySetsViewModel.cs | 26 +++++++++++-------- .../DependencySetsViewModelFacts.cs | 26 +++++++++++++++++++ 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs b/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs index b1fa177795..57178fcca6 100644 --- a/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs +++ b/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs @@ -44,21 +44,12 @@ public DependencySetsViewModel(IEnumerable packageDependencie friendlyName = framework.ToFriendlyName(); } - var dependencies = dependencySet.OrderBy(x => x.Id).Select(d => d.Id == null ? null : new DependencyViewModel(d.Id, d.VersionSpec)); + 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, - Comparer.Create((a, b) => - { - // Put "All Frameworks" (null) first - if (a == null && b == null) return 0; - if (a == null) return -1; - if (b == null) return 1; - // Use NuGetFrameworkSorter for actual frameworks - return NuGetFrameworkSorter.Instance.Compare(a, b); - })); + var sortedGroups = frameworkGroups.OrderBy(g => g.framework, new NullableFrameworkComparer()); // Build the final dictionary with friendly names foreach (var group in sortedGroups) @@ -101,5 +92,18 @@ public DependencyViewModel(string id, string versionSpec) public string VersionSpec { get; private set; } public string PackageUrl { get; private set; } } + + private class NullableFrameworkComparer : IComparer + { + 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 6a9f6a60c1..0f7879b32b 100644 --- a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs +++ b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs @@ -92,6 +92,32 @@ public void GivenAListOfDependenciesPackageIdsWillBeOrdered() 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() { From 70ce314a82e957da929650432ae500e6682150a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:44:55 +0000 Subject: [PATCH 4/9] Fix test expectations for net8.0/9.0/10.0 friendly names Co-authored-by: joelverhagen <94054+joelverhagen@users.noreply.github.com> --- .../ViewModels/DependencySetsViewModelFacts.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs index 0f7879b32b..8dd61117a7 100644 --- a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs +++ b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs @@ -136,9 +136,9 @@ public void GivenAListOfDependenciesNet10WillBeOrderedAfterNet9() var dependencySetsList = viewModel.DependencySets.Keys.ToList(); - Assert.Equal(".NET 8.0", dependencySetsList[0]); - Assert.Equal(".NET 9.0", dependencySetsList[1]); - Assert.Equal(".NET 10.0", dependencySetsList[2]); + Assert.Equal("net8.0", dependencySetsList[0]); + Assert.Equal("net9.0", dependencySetsList[1]); + Assert.Equal("net10.0", dependencySetsList[2]); } } } From 5c08fdad1cbcb9f26be2390db0fa841d5c763c85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:52:24 +0000 Subject: [PATCH 5/9] Add static Instance property to NullableFrameworkComparer for caching Co-authored-by: joelverhagen <94054+joelverhagen@users.noreply.github.com> --- src/NuGetGallery/ViewModels/DependencySetsViewModel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs b/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs index 57178fcca6..0f5315978f 100644 --- a/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs +++ b/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs @@ -49,7 +49,7 @@ public DependencySetsViewModel(IEnumerable packageDependencie } // Sort by framework using NuGetFrameworkSorter, with null frameworks (All Frameworks) first - var sortedGroups = frameworkGroups.OrderBy(g => g.framework, new NullableFrameworkComparer()); + var sortedGroups = frameworkGroups.OrderBy(g => g.framework, NullableFrameworkComparer.Instance); // Build the final dictionary with friendly names foreach (var group in sortedGroups) @@ -95,6 +95,8 @@ public DependencyViewModel(string id, string versionSpec) private class NullableFrameworkComparer : IComparer { + public static readonly NullableFrameworkComparer Instance = new NullableFrameworkComparer(); + public int Compare(NuGetFramework x, NuGetFramework y) { // Put "All Frameworks" (null) first From 299e3cef82a29199d8f3e36776617117f1f93b2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:59:53 +0000 Subject: [PATCH 6/9] Fix code style: move array initializer opening brace to next line Co-authored-by: joelverhagen <94054+joelverhagen@users.noreply.github.com> --- .../ViewModels/DependencySetsViewModelFacts.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs index 8dd61117a7..55d100027d 100644 --- a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs +++ b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs @@ -96,7 +96,8 @@ public void GivenAListOfDependenciesPackageIdsWillBeOrdered() public void GivenAListOfDependenciesPackageIdsWillBeOrderedCaseInsensitively() { // Arrange - var dependencies = new[] { + var dependencies = new[] + { new PackageDependency { TargetFramework = null, Id = "Zebra" }, new PackageDependency { TargetFramework = null, Id = "apple" }, new PackageDependency { TargetFramework = null, Id = "Banana" }, @@ -122,7 +123,8 @@ public void GivenAListOfDependenciesPackageIdsWillBeOrderedCaseInsensitively() public void GivenAListOfDependenciesNet10WillBeOrderedAfterNet9() { // Arrange - var dependencies = new[] { + var dependencies = new[] + { new PackageDependency { TargetFramework = "net10.0" }, new PackageDependency { TargetFramework = "net8.0" }, new PackageDependency { TargetFramework = "net9.0" } From 7a1b3c019d98fd9717e5bb230e08addd6c3aab23 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:58:50 +0000 Subject: [PATCH 7/9] Use IReadOnlyList to explicitly preserve TFM ordering instead of relying on Dictionary implementation Co-authored-by: joelverhagen <94054+joelverhagen@users.noreply.github.com> --- .../ViewModels/DependencySetsViewModel.cs | 18 ++++++++---------- .../ViewModels/DependencySetsViewModelFacts.cs | 6 +++--- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs b/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs index 0f5315978f..7d5588d73a 100644 --- a/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs +++ b/src/NuGetGallery/ViewModels/DependencySetsViewModel.cs @@ -18,15 +18,13 @@ 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); OnlyHasAllFrameworks = dependencySets.Count() == 1 && dependencySets.First().Key == null; - // 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)>(); - foreach (var dependencySet in dependencySets) { string tfmString = dependencySet.Key; @@ -51,14 +49,14 @@ public DependencySetsViewModel(IEnumerable packageDependencie // Sort by framework using NuGetFrameworkSorter, with null frameworks (All Frameworks) first var sortedGroups = frameworkGroups.OrderBy(g => g.framework, NullableFrameworkComparer.Instance); - // Build the final dictionary with friendly names + // Build an ordered list to preserve the sorting + var orderedDependencySets = new List>>(); foreach (var group in sortedGroups) { - if (!DependencySets.ContainsKey(group.friendlyName)) - { - DependencySets.Add(group.friendlyName, group.dependencies); - } + orderedDependencySets.Add(new KeyValuePair>(group.friendlyName, group.dependencies)); } + + DependencySets = orderedDependencySets; } catch (Exception e) { @@ -68,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 diff --git a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs index 55d100027d..97e8a36439 100644 --- a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs +++ b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs @@ -26,10 +26,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); From d85037bc2257a4f572059ce6647d3a7168dd7be1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 16:01:32 +0000 Subject: [PATCH 8/9] Fix test compilation errors - use Select to get keys from IReadOnlyList Co-authored-by: joelverhagen <94054+joelverhagen@users.noreply.github.com> --- .../ViewModels/DependencySetsViewModelFacts.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs index 97e8a36439..cb67757429 100644 --- a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs +++ b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs @@ -55,7 +55,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]); @@ -136,7 +136,7 @@ public void GivenAListOfDependenciesNet10WillBeOrderedAfterNet9() // Assert Assert.Equal(3, viewModel.DependencySets.Count); - var dependencySetsList = viewModel.DependencySets.Keys.ToList(); + var dependencySetsList = viewModel.DependencySets.Select(kvp => kvp.Key).ToList(); Assert.Equal("net8.0", dependencySetsList[0]); Assert.Equal("net9.0", dependencySetsList[1]); From d86e79d2996ad52b7ab461260035d512d3dde11c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Dec 2025 16:20:41 +0000 Subject: [PATCH 9/9] Add missing System.Collections.Generic using statement for IReadOnlyList Co-authored-by: joelverhagen <94054+joelverhagen@users.noreply.github.com> --- .../ViewModels/DependencySetsViewModelFacts.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs b/tests/NuGetGallery.Facts/ViewModels/DependencySetsViewModelFacts.cs index cb67757429..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;