diff --git a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs index 80fc0a9b5697..9019db3ffc73 100644 --- a/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs +++ b/src/Cli/dotnet/Commands/Solution/Add/SolutionAddCommand.cs @@ -102,9 +102,17 @@ public override int Execute() relativeSolutionFolderPath = _solutionFolderPath; } - return string.IsNullOrEmpty(relativeSolutionFolderPath) - ? null - : solution.AddFolder(GetSolutionFolderPathWithForwardSlashes(relativeSolutionFolderPath)); + if (string.IsNullOrEmpty(relativeSolutionFolderPath)) + { + return null; + } + + // Check if a solution folder with this path already exists + // Solution folder paths should be unique, so use SingleOrDefault + var solutionFolderPath = GetSolutionFolderPathWithForwardSlashes(relativeSolutionFolderPath); + var existingFolder = solution.SolutionFolders.SingleOrDefault(f => f.Path == solutionFolderPath); + + return existingFolder ?? solution.AddFolder(solutionFolderPath); } private async Task AddProjectsToSolutionAsync(IEnumerable projectPaths, CancellationToken cancellationToken) diff --git a/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs b/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs index 3f78a0896e87..96fd06ebf7c1 100644 --- a/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs +++ b/test/dotnet.Tests/CommandTests/Solution/Add/GivenDotnetSlnAdd.cs @@ -409,6 +409,46 @@ public void WhenDirectoryContainsMultipleProjectsItCancelsWholeOperation(string .Should().BeVisuallyEquivalentTo(contentBefore); } + [Theory] + [InlineData("sln", ".sln")] + [InlineData("solution", ".sln")] + [InlineData("sln", ".slnx")] + [InlineData("solution", ".slnx")] + public async Task WhenMultipleProjectsFromSameDirectoryAreAddedSolutionFolderIsNotDuplicated(string solutionCommand, string solutionExtension) + { + var projectDirectory = _testAssetsManager + .CopyTestAsset("TestAppWithSlnAndCsprojFiles", identifier: $"GivenDotnetSlnAdd-{solutionCommand}{solutionExtension}") + .WithSource() + .Path; + + var firstProject = Path.Combine("Multiple", "First.csproj"); + var secondProject = Path.Combine("Multiple", "Second.csproj"); + var cmd = new DotnetCommand(Log) + .WithWorkingDirectory(projectDirectory) + .Execute(solutionCommand, $"App{solutionExtension}", "add", firstProject, secondProject); + cmd.Should().Pass(); + + ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(Path.Combine(projectDirectory, $"App{solutionExtension}")); + SolutionModel solution = await serializer.OpenAsync(Path.Combine(projectDirectory, $"App{solutionExtension}"), CancellationToken.None); + + // The solution already has App project, plus we added First and Second = 3 total + var projectsInSolution = solution.SolutionProjects.ToList(); + projectsInSolution.Count.Should().Be(3); + projectsInSolution.Should().Contain(p => p.FilePath.Contains("First.csproj")); + projectsInSolution.Should().Contain(p => p.FilePath.Contains("Second.csproj")); + + // Should only have one solution folder for "Multiple", not two + var solutionFolders = solution.SolutionFolders.ToList(); + solutionFolders.Count.Should().Be(1); + solutionFolders.Single().Path.Should().Contain("Multiple"); + + // Both new projects should be in the same solution folder + var solutionFolder = solutionFolders.Single(); + var multipleProjects = projectsInSolution.Where(p => p.FilePath.Contains("Multiple")).ToList(); + multipleProjects.Count.Should().Be(2); + multipleProjects.All(p => p.Parent?.Id == solutionFolder.Id).Should().BeTrue(); + } + [Theory] [InlineData("sln", ".sln")] [InlineData("solution", ".sln")]