|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Components;
public class ComponentMarkupBlockPassTest
{
public ComponentMarkupBlockPassTest()
{
ProjectEngine = RazorProjectEngine.Create(
RazorConfiguration.Default,
RazorProjectFileSystem.Create(Environment.CurrentDirectory),
b =>
{
if (b.Features.OfType<ComponentMarkupBlockPass>().Any())
{
b.Features.Remove(b.Features.OfType<ComponentMarkupBlockPass>().Single());
}
});
Engine = ProjectEngine.Engine;
Pass = new ComponentMarkupBlockPass(RazorLanguageVersion.Latest)
{
Engine = Engine
};
}
private RazorProjectEngine ProjectEngine { get; }
private RazorEngine Engine { get; }
private ComponentMarkupBlockPass Pass { get; }
[Fact]
public void Execute_RewritesHtml_Basic()
{
// Arrange
var document = CreateDocument(@"
<html>
<head cool=""beans"">
Hello, World!
</head>
</html>");
var expected = NormalizeContent(@"
<html><head cool=""beans"">
Hello, World!
</head></html>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_WithComment()
{
// Arrange
var document = CreateDocument(@"Start<!-- -->End");
var expected = NormalizeContent(@"StartEnd");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact, WorkItem("https://github.com/dotnet/aspnetcore/issues/6480")]
public void Execute_RewritesHtml_HtmlAttributePrefix()
{
// Arrange
var document = CreateDocument(@"<div class=""one two"">Hi</div>");
var expected = NormalizeContent(@"<div class=""one two"">Hi</div>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_MergesSiblings()
{
// Arrange
var document = CreateDocument(@"
<html>
@(""Hi"")<div></div>
<div></div>
<div>@(""Hi"")</div>
</html>");
var expected = NormalizeContent(@"
<div></div>
<div></div>
");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_MergesSiblings_LeftEdge()
{
// Arrange
var document = CreateDocument(@"
<html><div></div>
<div></div>
<div>@(""Hi"")</div>
</html>");
var expected = NormalizeContent(@"
<div></div>
<div></div>
");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_CSharpInAttributes()
{
// Arrange
var document = CreateDocument(@"
<html>
<head cool=""beans"" csharp=""@yes"" mixed=""hi @there"">
<div>foo</div>
</head>
</html>");
var expected = NormalizeContent("<div>foo</div>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_CSharpInBody()
{
// Arrange
var document = CreateDocument(@"
<html>
<head cool=""beans"">
<div>@foo</div>
<div>rewriteme</div>
<div>@bar</div>
</head>
</html>");
var expected = NormalizeContent("<div>rewriteme</div>\n ");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_EncodesHtmlEntities()
{
// Arrange
var document = CreateDocument(@"
<div>
<span>Hi</span>
</div>");
var expected = NormalizeContent(@"
<div>
<span>Hi</span>
</div>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_EmptyNonvoid()
{
// Arrange
var document = CreateDocument(@"<a href=""...""></a>");
var expected = NormalizeContent(@"<a href=""...""></a>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_RewritesHtml_Void()
{
// Arrange
var document = CreateDocument(@"<link rel=""..."" href=""...""/>");
var expected = NormalizeContent(@"<link rel=""..."" href=""..."">");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
[Fact]
public void Execute_CannotRewriteHtml_CSharpInCode()
{
// Arrange
var document = CreateDocument(@"
<html>
@if (some_bool)
{
<head cool=""beans"">
@hello
</head>
}
</html>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
Assert.Empty(documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>());
}
[Fact]
public void Execute_CannotRewriteHtml_Script()
{
// Arrange
var document = CreateDocument(@"
<html>
@if (some_bool)
{
<head cool=""beans"">
<script>...</script>
</head>
}
</html>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal("""<head cool="beans"><script>...</script></head>""", block.Content);
}
[Fact]
public void Execute_CannotRewriteHtml_SelectOption()
{
// Arrange
var document = CreateDocument(@"
<html>
@if (some_bool)
{
<head cool=""beans"">
<select>
<option value='1'>One</option>
<option selected value='2'>Two</option>
<option value='3'>Three</option>
</select>
</head>
}
</html>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
Assert.Empty(documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>());
}
[Fact]
public void Execute_CanRewriteHtml_OptionWithNoSelectAncestor()
{
// Arrange
var document = CreateDocument(@"
<html>
@if (some_bool)
{
<head cool=""beans"">
<option value='1'>One</option>
<option selected value='2'>Two</option>
</head>
}
</html>");
var expected = NormalizeContent(@"
<head cool=""beans""><option value='1'>One</option>
<option selected value='2'>Two</option></head>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
// The unclosed tag will have errors, so we won't rewrite it or its parent.
[Fact]
public void Execute_CannotRewriteHtml_Errors()
{
// Arrange
var document = CreateDocument(@"
<html>
<a href=""..."">
</html>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
Assert.Empty(documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>());
}
// We want duplicate attributes to result in an error and prevent rewriting.
//
// This is because Blazor de-duplicates attributes differently from browsers, so we don't
// want to allow any markup blocks to exist with duplicate attributes or else they will have
// the browser's behavior.
[Fact]
public void Execute_CannotRewriteHtml_DuplicateAttribute()
{
// Arrange
var document = CreateDocument(@"
<html>
<a href=""test1"" href=""test2""></a>
</html>");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
Assert.Empty(documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>());
var diagnostic = Assert.Single(documentNode.GetAllDiagnostics());
Assert.Same(ComponentDiagnosticFactory.DuplicateMarkupAttribute.Id, diagnostic.Id);
}
[Fact]
public void Execute_RewritesHtml_MismatchedClosingTag()
{
// Arrange
var document = CreateDocument(@"
<html>
<div>
<div>rewriteme</div>
</span>
</html>");
var expected = NormalizeContent("<div>rewriteme</div>\n ");
var documentNode = Lower(document);
// Act
Pass.Execute(document, documentNode);
// Assert
var block = documentNode.FindDescendantNodes<MarkupBlockIntermediateNode>().Single();
Assert.Equal(expected, block.Content, ignoreLineEndingDifferences: true);
}
private string NormalizeContent(string content)
{
// Test inputs frequently have leading space for readability.
content = content.TrimStart();
// Normalize newlines since we are testing lengths of things.
content = content.Replace("\r", "");
content = content.Replace("\n", "\r\n");
return content;
}
private RazorCodeDocument CreateDocument(string content)
{
// Normalize newlines since we are testing lengths of things.
content = content.Replace("\r", "");
content = content.Replace("\n", "\r\n");
var source = RazorSourceDocument.Create(content, "test.cshtml");
return ProjectEngine.CreateCodeDocument(source, RazorFileKind.Component);
}
private DocumentIntermediateNode Lower(RazorCodeDocument codeDocument)
{
foreach (var phase in Engine.Phases)
{
if (phase is IRazorCSharpLoweringPhase)
{
break;
}
codeDocument = phase.Execute(codeDocument);
}
var document = codeDocument.GetRequiredDocumentNode();
Engine.GetFeatures<ComponentDocumentClassifierPass>().Single().Execute(codeDocument, document);
Engine.GetFeatures<ComponentMarkupDiagnosticPass>().Single().Execute(codeDocument, document);
return document;
}
}
|