public void UpdatesOnlyMatchingAttributeValue() { // Arrange var valuePropName = "testprop"; var renderer = new TestRenderer(); var builder = new RenderTreeBuilder(); builder.OpenElement(0, "elem"); builder.AddAttribute(1, "eventname", (Action)(() => { })); builder.SetUpdatesAttributeName(valuePropName); builder.AddAttribute(2, valuePropName, "unchanged 1"); builder.CloseElement(); builder.OpenElement(3, "elem"); builder.AddAttribute(4, "eventname", (Action)(() => { })); builder.SetUpdatesAttributeName(valuePropName); builder.AddAttribute(5, "unrelated prop before", "unchanged 2"); builder.AddAttribute(6, valuePropName, "initial value"); builder.AddAttribute(7, "unrelated prop after", "unchanged 3"); builder.CloseElement(); var frames = builder.GetFrames(); frames.Array[1] = frames.Array[1].WithAttributeEventHandlerId(123); // An unrelated event frames.Array[4] = frames.Array[4].WithAttributeEventHandlerId(456); // Act RenderTreeUpdater.UpdateToMatchClientState(builder, 456, "new value"); // Assert Assert.Collection(frames.AsEnumerable(), frame => AssertFrame.Element(frame, "elem", 3, 0), frame => AssertFrame.Attribute(frame, "eventname", v => Assert.IsType <Action>(v), 1), frame => AssertFrame.Attribute(frame, valuePropName, "unchanged 1", 2), frame => AssertFrame.Element(frame, "elem", 5, 3), frame => AssertFrame.Attribute(frame, "eventname", v => Assert.IsType <Action>(v), 4), frame => AssertFrame.Attribute(frame, "unrelated prop before", "unchanged 2", 5), frame => AssertFrame.Attribute(frame, valuePropName, "new value", 6), frame => AssertFrame.Attribute(frame, "unrelated prop after", "unchanged 3", 7)); }
public async Task SupportsTwoWayBindingForBoolValues() { // Arrange/Act var component = CompileToComponent( @"<input @bind=""MyValue"" /> @code { public bool MyValue { get; set; } = true; }"); var myValueProperty = component.GetType().GetProperty("MyValue"); var renderer = new TestRenderer(); // Assert EventCallback setter = default; var frames = GetRenderTree(renderer, component); Assert.Collection(frames, frame => AssertFrame.Element(frame, "input", 3, 0), frame => AssertFrame.Attribute(frame, "value", true, 1), frame => { AssertFrame.Attribute(frame, "onchange", 2); setter = Assert.IsType <EventCallback>(frame.AttributeValue); }); // Trigger the change event to show it updates the property // // This should always complete synchronously. var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs() { Value = false, })); Assert.Equal(TaskStatus.RanToCompletion, task.Status); await task; Assert.False((bool)myValueProperty.GetValue(component)); }
public void CanNestElements() { // Arrange var builder = new RenderTreeBuilder(new TestRenderer()); // Act builder.AddContent(0, "standalone text 1"); // 0: standalone text 1 builder.OpenElement(0, "root"); // 1: <root> builder.AddContent(0, "root text 1"); // 2: root text 1 builder.AddContent(0, "root text 2"); // 3: root text 2 builder.OpenElement(0, "child"); // 4: <child> builder.AddContent(0, "child text"); // 5: child text builder.OpenElement(0, "grandchild"); // 6: <grandchild> builder.AddContent(0, "grandchild text 1"); // 7: grandchild text 1 builder.AddContent(0, "grandchild text 2"); // 8: grandchild text 2 builder.CloseElement(); // </grandchild> builder.CloseElement(); // </child> builder.AddContent(0, "root text 3"); // 9: root text 3 builder.OpenElement(0, "child 2"); // 10: <child 2> builder.CloseElement(); // </child 2> builder.CloseElement(); // </root> builder.AddContent(0, "standalone text 2"); // 11: standalone text 2 // Assert Assert.Collection(builder.GetFrames().AsEnumerable(), frame => AssertFrame.Text(frame, "standalone text 1"), frame => AssertFrame.Element(frame, "root", 10), frame => AssertFrame.Text(frame, "root text 1"), frame => AssertFrame.Text(frame, "root text 2"), frame => AssertFrame.Element(frame, "child", 5), frame => AssertFrame.Text(frame, "child text"), frame => AssertFrame.Element(frame, "grandchild", 3), frame => AssertFrame.Text(frame, "grandchild text 1"), frame => AssertFrame.Text(frame, "grandchild text 2"), frame => AssertFrame.Text(frame, "root text 3"), frame => AssertFrame.Element(frame, "child 2", 1), frame => AssertFrame.Text(frame, "standalone text 2")); }
public async Task SupportsTwoWayBindingForEnumValues() { // Arrange/Act var myEnumType = FullTypeName <MyEnum>(); var component = CompileToComponent( $@"<input @bind=""MyValue"" /> @code {{ public {myEnumType} MyValue {{ get; set; }} = {myEnumType}.{nameof(MyEnum.FirstValue)}; }}"); var myValueProperty = component.GetType().GetProperty("MyValue"); var renderer = new TestRenderer(); // Assert EventCallback setter = default; var frames = GetRenderTree(renderer, component); Assert.Collection(frames, frame => AssertFrame.Element(frame, "input", 3, 0), frame => AssertFrame.Attribute(frame, "value", MyEnum.FirstValue.ToString(), 1), frame => { AssertFrame.Attribute(frame, "onchange", 2); setter = Assert.IsType <EventCallback>(frame.AttributeValue); }); // Trigger the change event to show it updates the property // // This should always complete synchronously. var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = MyEnum.SecondValue.ToString(), })); Assert.Equal(TaskStatus.RanToCompletion, task.Status); await task; Assert.Equal(MyEnum.SecondValue, (MyEnum)myValueProperty.GetValue(component)); }
public async Task SupportsTwoWayBindingForDateValuesWithFormatString() { // Arrange/Act var testDateFormat = "ddd yyyy-MM-dd"; var component = CompileToComponent( $@"<input @bind=""@MyDate"" @bind:format=""{testDateFormat}"" /> @code {{ public DateTime MyDate {{ get; set; }} = new DateTime(2018, 3, 4); }}"); var myDateProperty = component.GetType().GetProperty("MyDate"); var renderer = new TestRenderer(); // Assert EventCallback setter = default; var frames = GetRenderTree(renderer, 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); setter = Assert.IsType <EventCallback>(frame.AttributeValue); }); // Trigger the change event to show it updates the property // // This should always complete synchronously. var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = new DateTime(2018, 3, 5).ToString(testDateFormat), })); Assert.Equal(TaskStatus.RanToCompletion, task.Status); await task; Assert.Equal(new DateTime(2018, 3, 5), myDateProperty.GetValue(component)); }
[Fact] // Additional coverage of OrphanTagHelperLoweringPass public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_WithCSharpAttribute() { // Arrange var component = CompileToComponent(@" @addTagHelper *, TestAssembly <input type=""@(""text"")"" bind-value-onchange=""@ParentValue"" visible /> @functions { public int ParentValue { get; set; } = 42; }"); // Act var frames = GetRenderTree(component); // Assert Assert.Collection( frames, frame => AssertFrame.Element(frame, "input", 5, 0), frame => AssertFrame.Attribute(frame, "visible", 1), // This gets reordered in the node writer frame => AssertFrame.Attribute(frame, "type", "text", 2), frame => AssertFrame.Attribute(frame, "value", "42", 3), frame => AssertFrame.Attribute(frame, "onchange", typeof(UIEventHandler), 4), frame => AssertFrame.Whitespace(frame, 5)); }
public void Render_AttributeChildContent_IgnoresWhitespaceBody() { // Arrange AdditionalSyntaxTrees.Add(RenderChildContentComponent); var component = CompileToComponent(@" @addTagHelper *, TestAssembly @{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; } <RenderChildContent ChildContent=""@template(""HI"")""> </RenderChildContent>"); // Act var frames = GetRenderTree(component); // Assert Assert.Collection( frames, frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2), frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 3), frame => AssertFrame.Element(frame, "div", 2, 0), frame => AssertFrame.Text(frame, "hi", 1)); }
public void Render_BuiltIn_BindToInputText_WithFormatFromProperty_WritesAttributes() { // Arrange var component = CompileToComponent(@" @using Microsoft.AspNetCore.Components.Web <input type=""text"" @bind=""@CurrentDate"" @bind:format=""@Format""/> @code { public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public string Format { get; set; } = ""MM/dd/yyyy""; }"); // Act var frames = GetRenderTree(component); // Assert Assert.Collection( frames, frame => AssertFrame.Element(frame, "input", 4, 0), frame => AssertFrame.Attribute(frame, "type", "text", 1), frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2), frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3)); }
public void Render_BuiltIn_BindToInputText_WithFormatFromProperty_WritesAttributes() { // Arrange var component = CompileToComponent(@" @addTagHelper *, TestAssembly <input type=""text"" bind=""@CurrentDate"" format-value=""@Format""/> @functions { public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); public string Format { get; set; } = ""MM/dd/yyyy""; }"); // Act var frames = GetRenderTree(component); // Assert Assert.Collection( frames, frame => AssertFrame.Element(frame, "input", 4, 0), frame => AssertFrame.Attribute(frame, "type", "text", 1), frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2), frame => AssertFrame.Attribute(frame, "onchange", typeof(Action <UIEventArgs>), 3)); }
public async Task SupportsTwoWayBindingForTextareas() { // Arrange/Act var component = CompileToComponent( @"<textarea @bind=""MyValue"" ></textarea> @code { public string MyValue { get; set; } = ""Initial value""; }"); var myValueProperty = component.GetType().GetProperty("MyValue"); var renderer = new TestRenderer(); // Assert EventCallback setter = default; var frames = GetRenderTree(renderer, 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); setter = Assert.IsType <EventCallback>(frame.AttributeValue); }); // Trigger the change event to show it updates the property // // This should always complete synchronously. var task = renderer.InvokeAsync(() => setter.InvokeAsync(new UIChangeEventArgs { Value = "Modified value", })); Assert.Equal(TaskStatus.RanToCompletion, task.Status); await task; Assert.Equal("Modified value", myValueProperty.GetValue(component)); }
public void RazorTemplate_Generic_CanBeUsedFromMethod() { // Arrange var component = CompileToComponent(@" @(Repeat((context) => @<div>@context.ToLower()</div>, ""Hello, World!"", 3)) @functions { RenderFragment Repeat<T>(RenderFragment<T> template, T value, int count) { return (b) => { for (var i = 0; i < count; i++) { b.AddContent(i, template, value); } }; } }"); // Act var frames = GetRenderTree(component); // Assert // // The sequence numbers start at 1 here because there is an AddContent(0, Repeat(....) call // that precedes the definition of the lambda. Sequence numbers for the lambda are allocated // from the same logical sequence as the surrounding code. Assert.Collection( frames, frame => AssertFrame.Element(frame, "div", 2, 1), frame => AssertFrame.Text(frame, "hello, world!", 2), frame => AssertFrame.Element(frame, "div", 2, 1), frame => AssertFrame.Text(frame, "hello, world!", 2), frame => AssertFrame.Element(frame, "div", 2, 1), frame => AssertFrame.Text(frame, "hello, world!", 2)); }
public void RazorTemplate_Generic_CanBeUsedFromRazorCode() { // Arrange var component = CompileToComponent(@" @{ RenderFragment<string> template = (context) => @<div>@context.ToLower()</div>; } @for (var i = 0; i < 3; i++) { @template(""Hello, World!""); } "); // Act var frames = GetRenderTree(component); // Assert Assert.Collection( frames, frame => AssertFrame.Element(frame, "div", 2, 0), frame => AssertFrame.Text(frame, "hello, world!", 1), frame => AssertFrame.Element(frame, "div", 2, 0), frame => AssertFrame.Text(frame, "hello, world!", 1), frame => AssertFrame.Element(frame, "div", 2, 0), frame => AssertFrame.Text(frame, "hello, world!", 1)); }
[Fact] // See https://github.com/aspnet/Blazor/issues/703 public void Workaround_703() { // Arrange var component = CompileToComponent(@" <input bind-value-onchange=""@ParentValue"" type=""text"" visible /> @functions { public int ParentValue { get; set; } = 42; }"); // Act var frames = GetRenderTree(component); // Assert // // The workaround for 703 is that the value attribute MUST be after the type // attribute. Assert.Collection( frames, frame => AssertFrame.Element(frame, "input", 5, 0), frame => AssertFrame.Attribute(frame, "type", "text", 1), frame => AssertFrame.Attribute(frame, "visible", 2), frame => AssertFrame.Attribute(frame, "value", "42", 3), frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4)); }
public void Render_BodyChildContent_Generic() { // Arrange AdditionalSyntaxTrees.Add(RenderChildContentStringComponent); var component = CompileToComponent(@" <RenderChildContentString Value=""HI""> <div>@context.ToLowerInvariant()</div> </RenderChildContentString>"); // Act var frames = GetRenderTree(component); // Assert Assert.Collection( frames, frame => AssertFrame.Component(frame, "Test.RenderChildContentString", 3, 0), frame => AssertFrame.Attribute(frame, "Value", "HI", 1), frame => AssertFrame.Attribute(frame, "ChildContent", 2), frame => AssertFrame.MarkupWhitespace(frame, 3), frame => AssertFrame.Element(frame, "div", 2, 4), frame => AssertFrame.Text(frame, "hi", 5), frame => AssertFrame.MarkupWhitespace(frame, 6)); }
public void CanAddMultipleReferenceCapturesToSameElement() { // There won't be any way of doing this from Razor because there's no known use // case for it. However it's harder to *not* support it than to support it, and // there's no known reason to prevent it, so here's test coverage to show it // just works. // Arrange var builder = new RenderTreeBuilder(new TestRenderer()); Action <ElementRef> referenceCaptureAction1 = elementRef => { }; Action <ElementRef> referenceCaptureAction2 = elementRef => { }; // Act builder.OpenElement(0, "myelement"); builder.AddElementReferenceCapture(0, referenceCaptureAction1); builder.AddElementReferenceCapture(0, referenceCaptureAction2); builder.CloseElement(); // Assert Assert.Collection(builder.GetFrames(), frame => AssertFrame.Element(frame, "myelement", 3), frame => AssertFrame.ElementReferenceCapture(frame, referenceCaptureAction1), frame => AssertFrame.ElementReferenceCapture(frame, referenceCaptureAction2)); }
public void CanIncludeChildrenInComponents() { // Arrange/Act var testComponentTypeName = FullTypeName<TestComponent>(); var component = CompileToComponent($"<c:{testComponentTypeName} MyAttr=@(\"abc\")>" + $"Some text" + $"<some-child a='1'>Nested text</some-child>" + $"</c:{testComponentTypeName}>"); var frames = GetRenderTree(component); // Assert: component frames are correct Assert.Collection(frames, frame => AssertFrame.Component<TestComponent>(frame, 3, 0), frame => AssertFrame.Attribute(frame, "MyAttr", "abc", 1), frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 2)); // Assert: Captured ChildContent frames are correct var childFrames = GetFrames((RenderFragment)frames[2].AttributeValue); Assert.Collection(childFrames, frame => AssertFrame.Text(frame, "Some text", 3), frame => AssertFrame.Element(frame, "some-child", 3, 4), frame => AssertFrame.Attribute(frame, "a", "1", 5), frame => AssertFrame.Text(frame, "Nested text", 6)); }
public void OmitsAttributeIfNotFoundButValueIsOmissible() { // Arrange var valuePropName = "testprop"; var renderer = new TestRenderer(); var builder = new RenderTreeBuilder(); builder.OpenElement(0, "elem"); builder.AddAttribute(1, "eventname", (Action)(() => { })); builder.SetUpdatesAttributeName(valuePropName); builder.CloseElement(); var frames = builder.GetFrames(); frames.Array[1] = frames.Array[1].WithAttributeEventHandlerId(123); // Act RenderTreeUpdater.UpdateToMatchClientState(builder, 123, false); frames = builder.GetFrames(); // Assert Assert.Collection(frames.AsEnumerable(), frame => AssertFrame.Element(frame, "elem", 2, 0), frame => AssertFrame.Attribute(frame, "eventname", v => Assert.IsType <Action>(v), 1)); }
public void CanAddKeyToElement() { // Arrange var builder = new RenderTreeBuilder(new TestRenderer()); var keyValue = new object(); // Act builder.OpenElement(0, "elem"); builder.AddAttribute(1, "attribute before", "before value"); builder.SetKey(keyValue); builder.AddAttribute(2, "attribute after", "after value"); builder.CloseElement(); // Assert Assert.Collection( builder.GetFrames().AsEnumerable(), frame => { AssertFrame.Element(frame, "elem", 3, 0); Assert.Same(keyValue, frame.ElementKey); }, frame => AssertFrame.Attribute(frame, "attribute before", "before value", 1), frame => AssertFrame.Attribute(frame, "attribute after", "after value", 2)); }
public void BuildRenderTree_ForPropertyWithDisplayAttributeAndStringLocalizer_Should_CreateCorrectFramesUsingStringLocalizer() { // Arrange var stringLocalizerMock = new Mock <IStringLocalizer>(); stringLocalizerMock.Setup(l => l[It.IsAny <string>()]).Returns(new LocalizedString("name", "value")); _sut.StringLocalizerMock = stringLocalizerMock.Object; _sut.For = () => _model.First; // Act var renderTreeBuilder = new RenderTreeBuilder(); _sut.BuildRenderTreeMock(renderTreeBuilder); // Assert var frames = renderTreeBuilder.GetFrames().Array; AssertFrame.Element(frames[0], "label", 2); AssertFrame.Text(frames[1], "value"); // Verify stringLocalizerMock.Verify(localizer => localizer["Person_First"], Times.Once); }
public void Render_HtmlBlock_Integration() { // Arrange AdditionalSyntaxTrees.Add(Parse(@" using Microsoft.AspNetCore.Components; namespace Test { public class MyComponent : ComponentBase { [Parameter] public RenderFragment ChildContent { get; set; } } } ")); var component = CompileToComponent(@" <html> <head><meta><meta></head> <body> <MyComponent> <div><span></span><span></span></div> <div>@(""hi"")</div> <div><span></span><span></span></div> <div></div> <div>@(""hi"")</div> <div></div> </MyComponent> </body> </html>"); // Act var frames = GetRenderTree(component); // Assert: component frames are correct Assert.Collection( frames, frame => AssertFrame.Element(frame, "html", 9, 0), frame => AssertFrame.MarkupWhitespace(frame, 1), frame => AssertFrame.Markup(frame, "<head><meta><meta></head>\n ", 2), frame => AssertFrame.Element(frame, "body", 5, 3), frame => AssertFrame.MarkupWhitespace(frame, 4), frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 5), frame => AssertFrame.Attribute(frame, "ChildContent", 6), frame => AssertFrame.MarkupWhitespace(frame, 16), frame => AssertFrame.MarkupWhitespace(frame, 17)); // Assert: Captured ChildContent frames are correct var childFrames = GetFrames((RenderFragment)frames[6].AttributeValue); Assert.Collection( childFrames.AsEnumerable(), frame => AssertFrame.MarkupWhitespace(frame, 7), frame => AssertFrame.Markup(frame, "<div><span></span><span></span></div>\n ", 8), frame => AssertFrame.Element(frame, "div", 2, 9), frame => AssertFrame.Text(frame, "hi", 10), frame => AssertFrame.MarkupWhitespace(frame, 11), frame => AssertFrame.Markup(frame, "<div><span></span><span></span></div>\n <div></div>\n ", 12), frame => AssertFrame.Element(frame, "div", 2, 13), frame => AssertFrame.Text(frame, "hi", 14), frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 15)); }