// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using Microsoft.AspNetCore.Blazor.RenderTree; using Microsoft.AspNetCore.Blazor.Test.Helpers; using Xunit; namespace Microsoft.AspNetCore.Blazor.Build.Test { // Integration tests for the end-to-end of successful Razor compilation of component definitions // Includes running the component code to verify the output. public class RenderingRazorIntegrationTest : RazorIntegrationTestBase { [Fact] public void SupportsPlainText() { // Arrange/Act var component = CompileToComponent("Some plain text"); var frames = GetRenderTree(component); // Assert Assert.Collection(frames, frame => AssertFrame.Text(frame, "Some plain text", 0)); } [Fact] public void SupportsCSharpExpressions() { // Arrange/Act var component = CompileToComponent(@" @(""Hello"") @((object)null) @(123) @(new object()) "); // Assert var frames = GetRenderTree(component); Assert.Collection(frames, frame => AssertFrame.Text(frame, "Hello", 0), frame => AssertFrame.Whitespace(frame, 1), frame => AssertFrame.Whitespace(frame, 2), // @((object)null) frame => AssertFrame.Whitespace(frame, 3), frame => AssertFrame.Text(frame, "123", 4), frame => AssertFrame.Whitespace(frame, 5), frame => AssertFrame.Text(frame, new object().ToString(), 6)); } [Fact] public void SupportsCSharpFunctionsBlock() { // Arrange/Act var component = CompileToComponent(@" @foreach(var item in items) { @item } @functions { string[] items = new[] { ""First"", ""Second"", ""Third"" }; } "); // Assert var frames = GetRenderTree(component); Assert.Collection(frames, frame => AssertFrame.Text(frame, "First", 0), frame => AssertFrame.Text(frame, "Second", 0), frame => AssertFrame.Text(frame, "Third", 0)); } [Fact] public void SupportsElementsWithDynamicContent() { // Arrange/Act var component = CompileToComponent("Hello @(\"there\")"); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Element(frame, "myelem", 3, 0), frame => AssertFrame.Text(frame, "Hello ", 1), frame => AssertFrame.Text(frame, "there", 2)); } [Fact(Skip = "Temporarily disable compiling markup frames in 0.5.1")] public void SupportsElementsAsStaticBlock() { // Arrange/Act var component = CompileToComponent("Hello"); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Markup(frame, "Hello", 0)); } [Fact] public void CreatesSeparateMarkupFrameForEachTopLevelStaticElement() { // The JavaScript-side rendering code does not rely on this behavior. It supports // inserting markup frames with arbitrary markup (e.g., multiple top-level elements // or none). This test exists only as an observation of the current behavior rather // than a promise that we never want to change it. // Arrange/Act var component = CompileToComponent( "@(\"Hi\") a b "); // Assert var frames = GetRenderTree(component); Assert.Collection( frames, frame => AssertFrame.Element(frame, "root", 5, 0), frame => AssertFrame.Text(frame, "Hi", 1), frame => AssertFrame.Text(frame, " ", 2), frame => AssertFrame.Markup(frame, "a ", 3), frame => AssertFrame.Markup(frame, "b ", 4)); } [Fact] public void RendersMarkupStringAsMarkupFrame() { // Arrange/Act var component = CompileToComponent( "@{ var someMarkup = new MarkupString(\"
Hello
\"); }" + "

@someMarkup

"); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Element(frame, "p", 2, 0), frame => AssertFrame.Markup(frame, "
Hello
", 1)); } [Fact] public void SupportsSelfClosingElementsWithDynamicContent() { // Arrange/Act var component = CompileToComponent("Some text so elem isn't at position 0 "); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Text(frame, "Some text so elem isn't at position 0 ", 0), frame => AssertFrame.Element(frame, "myelem", 2, 1), frame => AssertFrame.Attribute(frame, "myattr", "val", 2)); } [Fact(Skip = "Temporarily disable compiling markup frames in 0.5.1")] public void SupportsSelfClosingElementsAsStaticBlock() { // Arrange/Act var component = CompileToComponent("Some text so elem isn't at position 0 "); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Text(frame, "Some text so elem isn't at position 0 ", 0), frame => AssertFrame.Markup(frame, "", 1)); } [Fact(Skip = "Temporarily disable compiling markup frames in 0.5.1")] public void SupportsVoidHtmlElements() { // Arrange/Act var component = CompileToComponent("Some text so elem isn't at position 0 "); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Text(frame, "Some text so elem isn't at position 0 ", 0), frame => AssertFrame.Markup(frame, "", 1)); } [Fact] public void SupportsComments() { // Arrange/Act var component = CompileToComponent("StartEnd"); var frames = GetRenderTree(component); // Assert Assert.Collection( frames, frame => AssertFrame.Markup(frame, "StartEnd", 0)); } [Fact] public void SupportsAttributesWithLiteralValues() { // Arrange/Act var component = CompileToComponent("@(\"Hello\")"); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Element(frame, "elem", 4, 0), frame => AssertFrame.Attribute(frame, "attrib-one", "Value 1", 1), frame => AssertFrame.Attribute(frame, "a2", "v2", 2), frame => AssertFrame.Text(frame, "Hello", 3)); } [Fact] public void SupportsAttributesWithStringExpressionValues() { // Arrange/Act var component = CompileToComponent( "@{ var myValue = \"My string\"; }" + ""); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Element(frame, "elem", 2, 0), frame => AssertFrame.Attribute(frame, "attr", "My string", 1)); } [Fact] public void SupportsAttributesWithNonStringExpressionValues() { // Arrange/Act var component = CompileToComponent( "@{ var myValue = 123; }" + ""); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Element(frame, "elem", 2, 0), frame => AssertFrame.Attribute(frame, "attr", "123", 1)); } [Fact] public void SupportsAttributesWithInterpolatedStringExpressionValues() { // Arrange/Act var component = CompileToComponent( "@{ var myValue = \"world\"; var myNum=123; }" + ""); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Element(frame, "elem", 2, 0), frame => AssertFrame.Attribute(frame, "attr", "Hello, WORLD with number 246!", 1)); } // This test exercises the case where two IntermediateTokens are part of the same expression. // In these case they are split by a comment. [Fact] public void SupportsAttributesWithInterpolatedStringExpressionValues_SplitByComment() { // Arrange/Act var component = CompileToComponent( "@{ var myValue = \"world\"; var myNum=123; }" + ""); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Element(frame, "elem", 2, 0), frame => AssertFrame.Attribute(frame, "attr", "Hello, WORLD with number 246!", 1)); } [Fact] public void SupportsAttributesWithInterpolatedTernaryExpressionValues() { // Arrange/Act var component = CompileToComponent( "@{ var myValue = \"world\"; }" + ""); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Element(frame, "elem", 2, 0), frame => AssertFrame.Attribute(frame, "attr", "Hello, world!", 1)); } [Fact] public void SupportsHyphenedAttributesWithCSharpExpressionValues() { // Arrange/Act var component = CompileToComponent( "@{ var myValue = \"My string\"; }" + ""); // Assert Assert.Collection(GetRenderTree(component), frame => AssertFrame.Element(frame, "elem", 2, 0), frame => AssertFrame.Attribute(frame, "abc-def", "My string", 1)); } [Fact] public void SupportsDataDashAttributes() { // Arrange/Act var component = CompileToComponent(@" @{ var myValue = ""Expression value""; } "); // Assert Assert.Collection( GetRenderTree(component), frame => AssertFrame.Element(frame, "elem", 3, 0), frame => AssertFrame.Attribute(frame, "data-abc", "Literal value", 1), frame => AssertFrame.Attribute(frame, "data-def", "Expression value", 2)); } [Fact] public void SupportsAttributesWithEventHandlerValues() { // Arrange/Act var component = CompileToComponent( @" @functions { public bool HandlerWasCalled { get; set; } = false; void MyHandleEvent(Microsoft.AspNetCore.Blazor.UIEventArgs eventArgs) { HandlerWasCalled = true; } }"); var handlerWasCalledProperty = component.GetType().GetProperty("HandlerWasCalled"); // Assert Assert.False((bool)handlerWasCalledProperty.GetValue(component)); Assert.Collection(GetRenderTree(component), frame => AssertFrame.Element(frame, "elem", 2, 0), frame => { Assert.Equal(RenderTreeFrameType.Attribute, frame.FrameType); Assert.Equal(1, frame.Sequence); Assert.NotNull(frame.AttributeValue); ((Action)frame.AttributeValue)(null); Assert.True((bool)handlerWasCalledProperty.GetValue(component)); }); } [Fact] public void SupportsUsingStatements() { // Arrange/Act var component = CompileToComponent( @"@using System.Collections.Generic @(typeof(List).FullName)"); var frames = GetRenderTree(component); // Assert Assert.Collection(frames, frame => AssertFrame.Text(frame, typeof(List).FullName, 0)); } [Fact] public void SupportsTwoWayBindingForTextboxes() { // Arrange/Act var component = CompileToComponent( @" @functions { public string MyValue { get; set; } = ""Initial value""; }"); var myValueProperty = component.GetType().GetProperty("MyValue"); // Assert var frames = GetRenderTree(component); Assert.Collection(frames, frame => AssertFrame.Element(frame, "input", 3, 0), frame => AssertFrame.Attribute(frame, "value", "Initial value", 1), frame => { AssertFrame.Attribute(frame, "onchange", 2); // Trigger the change event to show it updates the property ((Action)frame.AttributeValue)(new UIChangeEventArgs { Value = "Modified value" }); Assert.Equal("Modified value", myValueProperty.GetValue(component)); }); } [Fact] public void SupportsTwoWayBindingForTextareas() { // Arrange/Act var component = CompileToComponent( @" @functions { public string MyValue { get; set; } = ""Initial value""; }"); var myValueProperty = component.GetType().GetProperty("MyValue"); // Assert var frames = GetRenderTree(component); Assert.Collection(frames, frame => AssertFrame.Element(frame, "textarea", 3, 0), frame => AssertFrame.Attribute(frame, "value", "Initial value", 1), frame => { AssertFrame.Attribute(frame, "onchange", 2); // Trigger the change event to show it updates the property ((Action)frame.AttributeValue)(new UIChangeEventArgs { Value = "Modified value" }); Assert.Equal("Modified value", myValueProperty.GetValue(component)); }); } [Fact] public void SupportsTwoWayBindingForDateValues() { // Arrange/Act var component = CompileToComponent( @" @functions { public DateTime MyDate { get; set; } = new DateTime(2018, 3, 4, 1, 2, 3); }"); var myDateProperty = component.GetType().GetProperty("MyDate"); // Assert var frames = GetRenderTree(component); Assert.Collection(frames, frame => AssertFrame.Element(frame, "input", 3, 0), frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4, 1, 2, 3).ToString(), 1), frame => { AssertFrame.Attribute(frame, "onchange", 2); // Trigger the change event to show it updates the property var newDateValue = new DateTime(2018, 3, 5, 4, 5, 6); ((Action)frame.AttributeValue)(new UIChangeEventArgs { Value = newDateValue.ToString() }); Assert.Equal(newDateValue, myDateProperty.GetValue(component)); }); } [Fact] public void SupportsTwoWayBindingForDateValuesWithFormatString() { // Arrange/Act var testDateFormat = "ddd yyyy-MM-dd"; var component = CompileToComponent( $@" @functions {{ public DateTime MyDate {{ get; set; }} = new DateTime(2018, 3, 4); }}"); var myDateProperty = component.GetType().GetProperty("MyDate"); // Assert var frames = GetRenderTree(component); Assert.Collection(frames, frame => AssertFrame.Element(frame, "input", 3, 0), frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4).ToString(testDateFormat), 1), frame => { AssertFrame.Attribute(frame, "onchange", 2); // Trigger the change event to show it updates the property ((Action)frame.AttributeValue)(new UIChangeEventArgs { Value = new DateTime(2018, 3, 5).ToString(testDateFormat) }); Assert.Equal(new DateTime(2018, 3, 5), myDateProperty.GetValue(component)); }); } [Fact] // In this case, onclick is just a normal HTML attribute public void SupportsEventHandlerWithString() { // Arrange var component = CompileToComponent(@"