File: Completion\MarkupTransitionCompletionItemProviderTest.cs
Web Access
Project: src\src\Razor\src\Razor\test\Microsoft.CodeAnalysis.Razor.Workspaces.UnitTests\Microsoft.CodeAnalysis.Razor.Workspaces.UnitTests.csproj (Microsoft.CodeAnalysis.Razor.Workspaces.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Microsoft.AspNetCore.Razor.Test.Common;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.CodeAnalysis.Razor.Completion;
 
public class MarkupTransitionCompletionItemProviderTest(ITestOutputHelper testOutput) : ToolingTestBase(testOutput)
{
    private readonly MarkupTransitionCompletionItemProvider _provider = new();
 
    [Fact]
    public void GetCompletionItems_ReturnsEmptyCompletionItemInUnopenedMarkupContext()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("<div>$$");
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Empty(completionItems);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsEmptyCompletionItemInSimpleMarkupContext()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("<div><$$");
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Empty(completionItems);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsEmptyCompletionItemInNestedMarkupContext()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("<div><span><p></p><p><$$ </p></span></div>");
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Empty(completionItems);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsMarkupTransitionCompletionItemInCodeBlockStartingTag()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("@{<$$");
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Collection(completionItems, AssertRazorCompletionItem);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsMarkupTransitionCompletionItemInCodeBlockPartialCompletion()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("@{<te$$");
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Collection(completionItems, AssertRazorCompletionItem);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsMarkupTransitionCompletionItemInIfConditional()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("@if (true) {<$$ }");
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Collection(completionItems, AssertRazorCompletionItem);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsMarkupTransitionCompletionItemInFunctionDirective()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("@functions {public string GetHello(){<$$ return \"pi\";}}", FunctionsDirective.Directive);
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Collection(completionItems, AssertRazorCompletionItem);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsEmptyCompletionItemInExpression()
    {
        // Arrange
        var testCode = @"@{
    SomeFunctionAcceptingMethod(() =>
    {
        string foo = ""bar"";
    });
}
 
@SomeFunctionAcceptingMethod(() =>
{
    <$$
})";
        var razorCompletionContext = CreateRazorCompletionContext(testCode);
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Empty(completionItems);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsEmptyCompletionItemInSingleLineTransitions()
    {
        // Arrange
        var testCode = @"@{
    @* @: Here's some Markup $$| <-- You shouldn't get a <text> tag completion here. *@
    @: Here's some markup <
}";
        var razorCompletionContext = CreateRazorCompletionContext(testCode);
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Empty(completionItems);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsMarkupTransitionCompletionItemInNestedCSharpBlock()
    {
        // Arrange
        var testCode = @"<div>
@if (true)
{
  <$$ @* Should get text completion here *@
}
</div>";
        var razorCompletionContext = CreateRazorCompletionContext(testCode);
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Collection(completionItems, AssertRazorCompletionItem);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsEmptyCompletionItemInNestedMarkupBlock()
    {
        // Arrange
        var testCode = @"@if (true)
{
<div>
  <$$ @* Shouldn't get text completion here *@
</div>
}";
        var razorCompletionContext = CreateRazorCompletionContext(testCode);
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Empty(completionItems);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsMarkupTransitionCompletionItemWithUnrelatedClosingAngleBracket()
    {
        // Arrange
        var testCode = @"@functions {
    public void SomeOtherMethod()
    {
        <$$
    }
 
    private bool _collapseNavMenu => true;
}";
        var razorCompletionContext = CreateRazorCompletionContext(testCode, FunctionsDirective.Directive);
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Collection(completionItems, AssertRazorCompletionItem);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsMarkupTransitionCompletionItemWithUnrelatedClosingTag()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("@{<$$></>");
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Collection(completionItems, AssertRazorCompletionItem);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsEmptyCompletionItemWhenOwnerIsComplexExpression()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("@DateTime.Now<$$");
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Empty(completionItems);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsEmptyCompletionItemWhenOwnerIsExplicitExpression()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("@(something)<$$");
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Empty(completionItems);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsEmptyCompletionItemWithSpaceAfterStartTag()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("@{< $$");
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Empty(completionItems);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsEmptyCompletionItemWithSpaceAfterStartTagAndAttribute()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("@{< te$$=\"\"");
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Empty(completionItems);
    }
 
    [Fact]
    public void GetCompletionItems_ReturnsEmptyCompletionItemWhenInsideAttributeArea()
    {
        // Arrange
        var razorCompletionContext = CreateRazorCompletionContext("<p <$$ >");
 
        // Act
        var completionItems = _provider.GetCompletionItems(razorCompletionContext);
 
        // Assert
        Assert.Empty(completionItems);
    }
 
    private static void AssertRazorCompletionItem(RazorCompletionItem item)
    {
        Assert.Equal(SyntaxConstants.TextTagName, item.DisplayText);
        Assert.Equal(SyntaxConstants.TextTagName, item.InsertText);
        var completionDescription = Assert.IsType<MarkupTransitionCompletionDescription>(item.DescriptionInfo);
        Assert.Equal(CodeAnalysisResources.MarkupTransition_Description, completionDescription.Description);
    }
 
    private static RazorCompletionContext CreateRazorCompletionContext(TestCode text, params DirectiveDescriptor[] directives)
    {
        var syntaxTree = CreateSyntaxTree(text, directives);
        var absoluteIndex = text.Position;
        var sourceDocument = RazorSourceDocument.Create("", RazorSourceDocumentProperties.Default);
        var codeDocument = RazorCodeDocument.Create(sourceDocument);
 
        var tagHelperDocumentContext = TagHelperDocumentContext.GetOrCreate(tagHelpers: []);
 
        var owner = syntaxTree.Root.FindInnermostNode(absoluteIndex, includeWhitespace: true, walkMarkersBack: true);
        owner = AbstractRazorCompletionFactsService.AdjustSyntaxNodeForWordBoundary(owner, absoluteIndex);
        return new RazorCompletionContext(codeDocument, absoluteIndex, owner, syntaxTree, tagHelperDocumentContext);
    }
 
    private static RazorSyntaxTree CreateSyntaxTree(TestCode text, params DirectiveDescriptor[] directives)
    {
        return CreateSyntaxTree(text, RazorFileKind.Legacy, directives);
    }
 
    private static RazorSyntaxTree CreateSyntaxTree(TestCode text, RazorFileKind fileKind, params DirectiveDescriptor[] directives)
    {
        var sourceDocument = TestRazorSourceDocument.Create(text.Text);
 
        var builder = new RazorParserOptions.Builder(RazorLanguageVersion.Latest, fileKind)
        {
            Directives = [.. directives]
        };
 
        var options = builder.ToOptions();
 
        var syntaxTree = RazorSyntaxTree.Parse(sourceDocument, options);
        return syntaxTree;
    }
}