File: IntegrationTests\ComponentDiagnosticRazorIntegrationTest.cs
Web Access
Project: src\src\Razor\src\Compiler\Microsoft.AspNetCore.Razor.Language\test\Microsoft.AspNetCore.Razor.Language.UnitTests.csproj (Microsoft.AspNetCore.Razor.Language.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Globalization;
using Xunit;
 
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests;
 
public class ComponentDiagnosticRazorIntegrationTest : RazorIntegrationTestBase
{
    internal override RazorFileKind? FileKind => RazorFileKind.Component;
 
    internal override bool UseTwoPhaseCompilation => true;
 
    [Fact]
    public void RejectsEndTagWithNoStartTag()
    {
        // Arrange/Act
        var result = CompileToCSharp(
            "Line1\nLine2\nLine3</mytag>");
 
        // Assert
        Assert.Collection(result.RazorDiagnostics,
            item =>
            {
                Assert.Equal("RZ9981", item.Id);
                Assert.Equal("Unexpected closing tag 'mytag' with no matching start tag.", item.GetMessage(CultureInfo.CurrentCulture));
            });
    }
 
    // This used to be a sugar syntax for lambdas, but we don't support that anymore
    [Fact]
    public void OldCodeBlockAttributeSyntax_ReportsError()
    {
        // Arrange/Act
        var generated = CompileToCSharp(@"
<elem attr=@{ DidInvokeCode = true; } />
@functions {
    public bool DidInvokeCode { get; set; } = false;
}");
 
        // Assert
        var diagnostic = Assert.Single(generated.RazorDiagnostics);
        Assert.Equal("RZ9979", diagnostic.Id);
        Assert.NotNull(diagnostic.GetMessage(CultureInfo.CurrentCulture));
    }
 
    [Fact]
    public void RejectsTagHelperDirectives()
    {
        // Arrange/Act
        AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
    }
}
"));
 
        var result = CompileToCSharp(@"
@addTagHelper *, TestAssembly
@tagHelperPrefix th
 
<MyComponent />
");
 
        // Assert
        Assert.Collection(result.RazorDiagnostics,
            item =>
            {
                Assert.Equal("RZ9978", item.Id);
                Assert.Equal("""
            The directives @addTagHelper, @removeTagHelper and @tagHelperPrefix are not valid in a component document. Use '@using <namespace>' directive instead.
            """, item.GetMessage(CultureInfo.CurrentCulture));
                Assert.Equal(0, item.Span.LineIndex);
                Assert.Equal(0, item.Span.CharacterIndex);
            },
            item =>
            {
                Assert.Equal("RZ9978", item.Id);
                Assert.Equal("""
            The directives @addTagHelper, @removeTagHelper and @tagHelperPrefix are not valid in a component document. Use '@using <namespace>' directive instead.
            """, item.GetMessage(CultureInfo.CurrentCulture));
                Assert.Equal(1, item.Span.LineIndex);
                Assert.Equal(0, item.Span.CharacterIndex);
            });
    }
 
    [Fact]
    public void DirectiveAttribute_ComplexContent_ReportsError()
    {
        // Arrange & Act
        var generated = CompileToCSharp(@"
<input type=""text"" @key=""Foo @Text"" />
@functions {
    public string Text { get; set; } = ""text"";
}");
 
        // Assert
        var diagnostic = Assert.Single(generated.RazorDiagnostics);
        Assert.Equal("RZ9986", diagnostic.Id);
        Assert.Equal(
            "Component attributes do not support complex content (mixed C# and markup). Attribute: '@key', text: 'Foo @Text'",
            diagnostic.GetMessage(CultureInfo.CurrentCulture));
    }
 
    [Fact]
    public void Component_StartsWithLowerCase_ReportsError()
    {
        // Arrange & Act
        var generated = CompileToCSharp("lowerCase.razor", cshtmlContent: @"
<input type=""text"" @bind=""Text"" />
@functions {
    public string Text { get; set; } = ""text"";
}");
 
        // Assert
        var diagnostic = Assert.Single(generated.RazorDiagnostics);
        Assert.Equal("RZ10011", diagnostic.Id);
        Assert.Equal(
            "Component 'lowerCase' starts with a lowercase character. Component names cannot start with a lowercase character.",
            diagnostic.GetMessage(CultureInfo.CurrentCulture));
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void Component_NotFound_ReportsWarning(bool supportLocalizedComponentNames)
    {
        // Arrange & Act
        var generated = CompileToCSharp(@"
<PossibleComponent></PossibleComponent>
 
@functions {
    public string Text { get; set; } = ""text"";
}", supportLocalizedComponentNames: supportLocalizedComponentNames);
 
        // Assert
        var diagnostic = Assert.Single(generated.RazorDiagnostics);
        Assert.Equal("RZ10012", diagnostic.Id);
        Assert.Equal(RazorDiagnosticSeverity.Warning, diagnostic.Severity);
        Assert.Equal(
            "Found markup element with unexpected name 'PossibleComponent'. If this is intended to be a component, add a @using directive for its namespace.",
            diagnostic.GetMessage(CultureInfo.CurrentCulture));
    }
 
    [Fact]
    public void Component_NotFound_StartsWithOtherLetter_WhenLocalizedComponentNamesIsAllowed_ReportsWarning()
    {
        // Arrange & Act
        var generated = CompileToCSharp(@"
<繁体字></繁体字>
 
@functions {
    public string Text { get; set; } = ""text"";
}", supportLocalizedComponentNames: true);
 
        // Assert
        var diagnostic = Assert.Single(generated.RazorDiagnostics);
        Assert.Equal("RZ10012", diagnostic.Id);
        Assert.Equal(RazorDiagnosticSeverity.Warning, diagnostic.Severity);
        Assert.Equal(
            "Found markup element with unexpected name '繁体字'. If this is intended to be a component, add a @using directive for its namespace.",
            diagnostic.GetMessage(CultureInfo.CurrentCulture));
    }
 
    [Fact]
    public void Component_NotFound_StartsWithOtherLetter_WhenLocalizedComponentNamesIsDisallowed()
    {
        // Arrange & Act
        var generated = CompileToCSharp(@"
<繁体字></繁体字>
 
@functions {
    public string Text { get; set; } = ""text"";
}", supportLocalizedComponentNames: false);
 
        // Assert
        Assert.Empty(generated.RazorDiagnostics);
    }
 
    [Fact]
    public void Element_DoesNotStartWithLowerCase_OverrideWithBang_NoWarning()
    {
        // Arrange & Act
        var generated = CompileToCSharp(@"
<!PossibleComponent></!PossibleComponent>");
 
        // Assert
        Assert.Empty(generated.RazorDiagnostics);
    }
 
    [Fact]
    public void Component_StartAndEndTagCaseMismatch_ReportsError()
    {
        // Arrange & Act
        AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;
 
namespace Test
{
    public class MyComponent : ComponentBase
    {
    }
}
"));
        var generated = CompileToCSharp(@"
<MyComponent></mycomponent>");
 
        // Assert
        var diagnostic = Assert.Single(generated.RazorDiagnostics);
        Assert.Equal("RZ10013", diagnostic.Id);
        Assert.Equal(
            "The start tag name 'MyComponent' does not match the end tag name 'mycomponent'. Components must have matching start and end tag names (case-sensitive).",
            diagnostic.GetMessage(CultureInfo.CurrentCulture));
    }
}