File: CodeActions\SyncNamespace\CSharpSyncNamespaceTestsBase.cs
Web Access
Project: src\src\EditorFeatures\CSharpTest\Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.EditorFeatures.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.CodeRefactorings.SyncNamespace;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.UnitTests;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeActions.SyncNamespace;
 
public abstract class CSharpSyncNamespaceTestsBase : AbstractCodeActionTest
{
    protected override ParseOptions GetScriptOptions() => Options.Script;
 
    protected internal override string GetLanguage() => LanguageNames.CSharp;
 
    protected override CodeRefactoringProvider CreateCodeRefactoringProvider(EditorTestWorkspace workspace, TestParameters parameters)
        => new CSharpSyncNamespaceCodeRefactoringProvider();
 
    protected static string ProjectRootPath
        => PathUtilities.IsUnixLikePlatform
        ? @"/ProjectA/"
        : @"C:\ProjectA\";
 
    protected static string ProjectFilePath
        => PathUtilities.CombineAbsoluteAndRelativePaths(ProjectRootPath, "ProjectA.csproj");
 
    protected static (string folder, string filePath) CreateDocumentFilePath(string[] folder, string fileName = "DocumentA.cs")
    {
        if (folder == null || folder.Length == 0)
        {
            return (string.Empty, PathUtilities.CombineAbsoluteAndRelativePaths(ProjectRootPath, fileName));
        }
        else
        {
            var folderPath = CreateFolderPath(folder);
            var relativePath = PathUtilities.CombinePossiblyRelativeAndRelativePaths(folderPath, fileName);
            return (folderPath, PathUtilities.CombineAbsoluteAndRelativePaths(ProjectRootPath, relativePath));
        }
    }
 
    protected static string CreateFolderPath(params string[] folders)
        => string.Join(PathUtilities.DirectorySeparatorStr, folders);
 
    protected async Task TestMoveFileToMatchNamespace(string initialMarkup, List<string[]> expectedFolders = null)
    {
        var testOptions = new TestParameters();
        using (var workspace = (EditorTestWorkspace)CreateWorkspaceFromOptions(initialMarkup, testOptions))
        {
            if (expectedFolders?.Count > 0)
            {
                var expectedFolderPaths = expectedFolders.Select(f => string.Join(PathUtilities.DirectorySeparatorStr, f));
 
                var oldDocument = workspace.Documents[0];
                var oldDocumentId = oldDocument.Id;
                var expectedText = workspace.Documents[0].GetTextBuffer().CurrentSnapshot.GetText();
 
                // a new document with the same text as old document is added.
                var allResults = await TestOperationAsync(testOptions, workspace, expectedText);
 
                var actualFolderPaths = new HashSet<string>();
                foreach (var result in allResults)
                {
                    // the original source document does not exist in the new solution.
                    var oldSolution = result.Item1;
                    var newSolution = result.Item2;
 
                    Assert.Null(newSolution.GetDocument(oldDocumentId));
 
                    var newDocument = GetDocumentToVerify(expectedChangedDocumentId: null, oldSolution, newSolution);
                    actualFolderPaths.Add(string.Join(PathUtilities.DirectorySeparatorStr, newDocument.Folders));
                }
 
                Assert.True(expectedFolderPaths.Count() == actualFolderPaths.Count, "Number of available \"Move file\" actions are not equal.");
                foreach (var expected in expectedFolderPaths)
                {
                    Assert.True(actualFolderPaths.Contains(expected));
                }
            }
            else
            {
                var (actions, _) = await GetCodeActionsAsync(workspace, testOptions);
                if (actions.Length > 0)
                {
                    var renameFileAction = actions.Any(action => action is not CodeAction.SolutionChangeAction);
                    Assert.False(renameFileAction, "Move File to match namespace code action was not expected, but shows up.");
                }
            }
        }
 
        async Task<List<Tuple<Solution, Solution>>> TestOperationAsync(
            TestParameters parameters,
            EditorTestWorkspace workspace,
            string expectedCode)
        {
            var results = new List<Tuple<Solution, Solution>>();
 
            var (actions, _) = await GetCodeActionsAsync(workspace, parameters);
            var moveFileActions = actions.Where(a => a is not CodeAction.SolutionChangeAction);
 
            foreach (var action in moveFileActions)
            {
                var operations = await action.GetOperationsAsync(CancellationToken.None);
 
                results.Add(
                    await TestOperationsAsync(workspace,
                    expectedText: expectedCode,
                    operations: operations,
                    conflictSpans: ImmutableArray<TextSpan>.Empty,
                    renameSpans: ImmutableArray<TextSpan>.Empty,
                    warningSpans: ImmutableArray<TextSpan>.Empty,
                    navigationSpans: ImmutableArray<TextSpan>.Empty,
                    expectedChangedDocumentId: null));
            }
 
            return results;
        }
    }
 
    protected async Task TestChangeNamespaceAsync(
        string initialMarkUp,
        string expectedSourceOriginal,
        string expectedSourceReference = null)
    {
        var testOptions = new TestParameters();
        using (var workspace = CreateWorkspaceFromOptions(initialMarkUp, testOptions))
        {
            if (workspace.Projects.Count == 2)
            {
                var project = workspace.Documents.Single(doc => !doc.SelectedSpans.IsEmpty()).Project;
                var dependentProject = workspace.Projects.Single(proj => proj.Id != project.Id);
                var references = dependentProject.ProjectReferences.ToList();
                references.Add(new ProjectReference(project.Id));
                dependentProject.ProjectReferences = references;
                workspace.OnProjectReferenceAdded(dependentProject.Id, new ProjectReference(project.Id));
            }
 
            if (expectedSourceOriginal != null)
            {
                var originalDocument = workspace.Documents.Single(doc => !doc.SelectedSpans.IsEmpty());
                var originalDocumentId = originalDocument.Id;
 
                var refDocument = workspace.Documents.Where(doc => doc.Id != originalDocumentId).SingleOrDefault();
                var refDocumentId = refDocument?.Id;
 
                var oldAndNewSolution = await TestOperationAsync(testOptions, workspace);
                var oldSolution = oldAndNewSolution.Item1;
                var newSolution = oldAndNewSolution.Item2;
 
                var changedDocumentIds = SolutionUtilities.GetChangedDocuments(oldSolution, newSolution);
 
                Assert.True(changedDocumentIds.Contains(originalDocumentId), "original document was not changed.");
 
                var modifiedOriginalDocument = newSolution.GetDocument(originalDocumentId);
                var modifiedOringinalRoot = await modifiedOriginalDocument.GetSyntaxRootAsync();
 
                // One node/token will contain the warning we attached for change namespace action.
                Assert.Single(modifiedOringinalRoot.DescendantNodesAndTokensAndSelf(), n =>
                    {
                        IEnumerable<SyntaxAnnotation> annotations;
                        if (n.IsNode)
                        {
                            annotations = n.AsNode().GetAnnotations(WarningAnnotation.Kind);
                        }
                        else
                        {
                            annotations = n.AsToken().GetAnnotations(WarningAnnotation.Kind);
                        }
 
                        return annotations.Any(annotation =>
                            WarningAnnotation.GetDescription(annotation) == FeaturesResources.Warning_colon_changing_namespace_may_produce_invalid_code_and_change_code_meaning);
                    });
 
                var actualText = (await modifiedOriginalDocument.GetTextAsync()).ToString();
                AssertEx.EqualOrDiff(expectedSourceOriginal, actualText);
 
                if (expectedSourceReference == null)
                {
                    // there shouldn't be any textual change
                    if (changedDocumentIds.Contains(refDocumentId))
                    {
                        var oldRefText = (await oldSolution.GetDocument(refDocumentId).GetTextAsync()).ToString();
                        var newRefText = (await newSolution.GetDocument(refDocumentId).GetTextAsync()).ToString();
                        Assert.Equal(oldRefText, newRefText);
                    }
                }
                else
                {
                    Assert.True(changedDocumentIds.Contains(refDocumentId));
                    var actualRefText = (await newSolution.GetDocument(refDocumentId).GetTextAsync()).ToString();
                    Assert.Equal(expectedSourceReference, actualRefText);
                }
            }
            else
            {
                var (actions, _) = await GetCodeActionsAsync(workspace, testOptions);
                if (actions.Length > 0)
                {
                    var hasChangeNamespaceAction = actions.Any(action => action is CodeAction.SolutionChangeAction);
                    Assert.False(hasChangeNamespaceAction, "Change namespace to match folder action was not expected, but shows up.");
                }
            }
        }
 
        async Task<Tuple<Solution, Solution>> TestOperationAsync(TestParameters parameters, EditorTestWorkspace workspace)
        {
            var (actions, _) = await GetCodeActionsAsync(workspace, parameters);
            var changeNamespaceAction = actions.Single(a => a is CodeAction.SolutionChangeAction);
            var operations = await changeNamespaceAction.GetOperationsAsync(CancellationToken.None);
 
            return await ApplyOperationsAndGetSolutionAsync(workspace, operations);
        }
    }
}