public async Task SupportsTwoWayBindingForTextareas() { // Arrange/Act var component = CompileToComponent( @"<textarea bind=""MyValue"" ></textarea> @functions { 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 async Task RespondsToValidationStateChangeNotifications() { // Arrange var model = new TestModel(); var rootComponent = new TestInputHostComponent <string, TestInputComponent <string> > { EditContext = new EditContext(model), ValueExpression = () => model.StringProperty }; var fieldIdentifier = FieldIdentifier.Create(() => model.StringProperty); var renderer = new TestRenderer(); var rootComponentId = renderer.AssignRootComponentId(rootComponent); await renderer.RenderRootComponentAsync(rootComponentId); // Initally, it rendered one batch and is valid var batch1 = renderer.Batches.Single(); var componentFrame1 = batch1.GetComponentFrames <TestInputComponent <string> >().Single(); var inputComponentId = componentFrame1.ComponentId; var component = (TestInputComponent <string>)componentFrame1.Component; Assert.Equal("valid", component.CssClass); Assert.Null(component.AdditionalAttributes); // Act: update the field state in the EditContext and notify var messageStore = new ValidationMessageStore(rootComponent.EditContext); messageStore.Add(fieldIdentifier, "Some message"); await renderer.Dispatcher.InvokeAsync(rootComponent.EditContext.NotifyValidationStateChanged); // Assert: The input component rendered itself again and now has the new class var batch2 = renderer.Batches.Skip(1).Single(); Assert.Equal(inputComponentId, batch2.DiffsByComponentId.Keys.Single()); Assert.Equal("invalid", component.CssClass); Assert.NotNull(component.AdditionalAttributes); Assert.True(component.AdditionalAttributes.ContainsKey("aria-invalid")); }
public void RespondsToNotificationsFromAuthenticationStateProvider() { // Arrange: Service var services = new ServiceCollection(); var authStateProvider = new TestAuthenticationStateProvider() { CurrentAuthStateTask = Task.FromResult(CreateAuthenticationState(null)) }; services.AddSingleton <AuthenticationStateProvider>(authStateProvider); // Arrange: Renderer and component, initially rendered var renderer = new TestRenderer(services.BuildServiceProvider()); var component = new UseCascadingAuthenticationStateComponent(); renderer.AssignRootComponentId(component); component.TriggerRender(); var receiveAuthStateId = renderer.Batches.Single() .GetComponentFrames <ReceiveAuthStateComponent>().Single().ComponentId; // Act 2: AuthenticationStateProvider issues notification authStateProvider.TriggerAuthenticationStateChanged( Task.FromResult(CreateAuthenticationState("Bert"))); // Assert 2: Re-renders content Assert.Equal(2, renderer.Batches.Count); var batch = renderer.Batches.Last(); var receiveAuthStateDiff = batch.DiffsByComponentId[receiveAuthStateId].Single(); Assert.Collection(receiveAuthStateDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "Authenticated: True; Name: Bert; Pending: False; Renders: 2"); }); }
public void DoesNotNotifyDescendantsIfCascadingParameterValuesAreImmutableAndUnchanged() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent(builder => { builder.OpenComponent <CascadingValue <string> >(0); builder.AddAttribute(1, "Value", "Unchanging value"); builder.AddAttribute(2, RenderTreeBuilder.ChildContent, new RenderFragment(childBuilder => { childBuilder.OpenComponent <CascadingParameterConsumerComponent <string> >(0); childBuilder.AddAttribute(1, "RegularParameter", "Goodbye"); childBuilder.CloseComponent(); })); builder.CloseComponent(); }); // Act 1: Initial render var componentId = renderer.AssignRootComponentId(component); component.TriggerRender(); var firstBatch = renderer.Batches.Single(); var nestedComponent = FindComponent <CascadingParameterConsumerComponent <string> >(firstBatch, out _); Assert.Equal(3, firstBatch.DiffsByComponentId.Count); // Root + CascadingValue + nested Assert.Equal(1, nestedComponent.NumRenders); // Act/Assert: Re-render the CascadingValue; observe nested component wasn't re-rendered component.TriggerRender(); // Assert: We did not re-render CascadingParameterConsumerComponent Assert.Equal(2, renderer.Batches.Count); var secondBatch = renderer.Batches[1]; Assert.Equal(2, secondBatch.DiffsByComponentId.Count); // Root + CascadingValue, but not nested one Assert.Equal(1, nestedComponent.NumRenders); }
public void RunsOnParametersSetAsyncWhenRendered() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent(); int onParametersSetAsyncRuns = 0; component.RunsBaseOnParametersSetAsync = false; component.OnParametersSetAsyncLogic = c => { onParametersSetAsyncRuns++; return(Task.CompletedTask); }; // Act var componentId = renderer.AssignRootComponentId(component); renderer.RenderRootComponent(componentId); // Assert Assert.Equal(1, onParametersSetAsyncRuns); Assert.Single(renderer.Batches); }
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 CanAccessToChildComponentInstance() { // Arrange var instance = new DynamicComponent(); var renderer = new TestRenderer(); var childParameters = new Dictionary <string, object> { { nameof(TestComponent.IntProp), 123 }, }; var parameters = ParameterView.FromDictionary(new Dictionary <string, object> { { nameof(DynamicComponent.Type), typeof(TestComponent) }, { nameof(DynamicComponent.Parameters), childParameters }, }); // Act renderer.RenderRootComponent( renderer.AssignRootComponentId(instance), parameters); // Assert Assert.IsType <TestComponent>(instance.Instance); Assert.Equal(123, ((TestComponent)instance.Instance).IntProp); }
public void RunsOnInitAsyncAlsoOnBaseClassWhenRendered() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent(); var onInitAsyncRuns = 0; component.RunsBaseOnInitAsync = true; component.OnInitAsyncLogic = c => { onInitAsyncRuns++; return(Task.CompletedTask); }; // Act var componentId = renderer.AssignRootComponentId(component); renderer.RenderRootComponent(componentId); // Assert Assert.Equal(1, onInitAsyncRuns); Assert.Single(renderer.Batches); }
public void CanRenderComponentByTypeWithParameters() { // Arrange var instance = new DynamicComponent(); var renderer = new TestRenderer(); var childParameters = new Dictionary <string, object> { { nameof(TestComponent.IntProp), 123 }, { nameof(TestComponent.ChildContent), (RenderFragment)(builder => { builder.AddContent(0, "This is some child content"); }) }, }; var parameters = ParameterView.FromDictionary(new Dictionary <string, object> { { nameof(DynamicComponent.Type), typeof(TestComponent) }, { nameof(DynamicComponent.Parameters), childParameters }, }); // Act renderer.RenderRootComponent( renderer.AssignRootComponentId(instance), parameters); // Assert var batch = renderer.Batches.Single(); // It renders a reference to the child component with its parameters AssertFrame.Component <TestComponent>(batch.ReferenceFrames[0], 4, 0); AssertFrame.Attribute(batch.ReferenceFrames[1], nameof(TestComponent.IntProp), 123, 1); AssertFrame.Attribute(batch.ReferenceFrames[2], nameof(TestComponent.ChildContent), 1); // The child component itself is rendered AssertFrame.Text(batch.ReferenceFrames[4], "Hello from TestComponent with IntProp=123", 0); AssertFrame.Text(batch.ReferenceFrames[5], "This is some child content", 0); }
public void CanReRenderTopLevelComponents() { // Arrange var renderer = new TestRenderer(); var component = new MessageComponent { Message = "Initial message" }; var componentId = renderer.AssignComponentId(component); // Act/Assert: first render renderer.RenderNewBatch(componentId); var firstDiff = renderer.Batches.Single().DiffsByComponentId[componentId].Single(); Assert.Collection(firstDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); Assert.Equal(0, edit.ReferenceFrameIndex); }); Assert.Collection(firstDiff.ReferenceFrames, frame => AssertFrame.Text(frame, "Initial message")); // Act/Assert: second render component.Message = "Modified message"; renderer.RenderNewBatch(componentId); var secondDiff = renderer.Batches.Skip(1).Single().DiffsByComponentId[componentId].Single(); Assert.Collection(firstDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); Assert.Equal(0, edit.ReferenceFrameIndex); }); Assert.Collection(firstDiff.ReferenceFrames, frame => AssertFrame.Text(frame, "Modified message")); }
public void PassesCascadingParametersToNestedComponents() { // Arrange var renderer = new TestRenderer(); var component = new TestComponent(builder => { builder.OpenComponent <CascadingValue <string> >(0); builder.AddAttribute(1, "Value", "Hello"); builder.AddAttribute(2, "ChildContent", new RenderFragment(childBuilder => { childBuilder.OpenComponent <CascadingParameterConsumerComponent <string> >(0); childBuilder.AddAttribute(1, "RegularParameter", "Goodbye"); childBuilder.CloseComponent(); })); builder.CloseComponent(); }); // Act/Assert var componentId = renderer.AssignRootComponentId(component); component.TriggerRender(); var batch = renderer.Batches.Single(); var nestedComponent = FindComponent <CascadingParameterConsumerComponent <string> >(batch, out var nestedComponentId); var nestedComponentDiff = batch.DiffsByComponentId[nestedComponentId].Single(); // The nested component was rendered with the correct parameters Assert.Collection(nestedComponentDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "CascadingParameter=Hello; RegularParameter=Goodbye"); }); Assert.Equal(1, nestedComponent.NumRenders); }
public LayoutTest() { _renderer = new TestRenderer(); _layoutDisplayComponent = new LayoutDisplay(); _layoutDisplayComponentId = _renderer.AssignRootComponentId(_layoutDisplayComponent); }
public DependencyInjectionTest() { _serviceProvider = new TestServiceProvider(); _renderer = new TestRenderer(_serviceProvider); }
public void DoesNotNotifyDescendantsOfUpdatedCascadingParameterValuesWhenFixed() { // Arrange var providedValue = "Initial value"; var shouldIncludeChild = true; var renderer = new TestRenderer(); var component = new TestComponent(builder => { builder.OpenComponent <CascadingValue <string> >(0); builder.AddAttribute(1, "Value", providedValue); builder.AddAttribute(2, "IsFixed", true); builder.AddAttribute(3, "ChildContent", new RenderFragment(childBuilder => { if (shouldIncludeChild) { childBuilder.OpenComponent <CascadingParameterConsumerComponent <string> >(0); childBuilder.AddAttribute(1, "RegularParameter", "Goodbye"); childBuilder.CloseComponent(); } })); builder.CloseComponent(); }); // Act 1: Initial render; capture nested component ID var componentId = renderer.AssignRootComponentId(component); component.TriggerRender(); var firstBatch = renderer.Batches.Single(); var nestedComponent = FindComponent <CascadingParameterConsumerComponent <string> >(firstBatch, out var nestedComponentId); Assert.Equal(1, nestedComponent.NumRenders); // Assert: Initial value is supplied to descendant var nestedComponentDiff = firstBatch.DiffsByComponentId[nestedComponentId].Single(); Assert.Collection(nestedComponentDiff.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( firstBatch.ReferenceFrames[edit.ReferenceFrameIndex], "CascadingParameter=Initial value; RegularParameter=Goodbye"); }); // Act 2: Re-render CascadingValue with new value providedValue = "Updated value"; component.TriggerRender(); // Assert: We did not re-render the descendant Assert.Equal(2, renderer.Batches.Count); var secondBatch = renderer.Batches[1]; Assert.Equal(2, secondBatch.DiffsByComponentId.Count); // Root + CascadingValue, but not nested one Assert.Equal(1, nestedComponent.NumSetParametersCalls); Assert.Equal(1, nestedComponent.NumRenders); // Act 3: Dispose shouldIncludeChild = false; component.TriggerRender(); // Assert: Absence of an exception here implies we didn't cause a problem by // trying to remove a non-existent subscription }
public void StopsNotifyingDescendantsIfTheyAreRemoved() { // Arrange var providedValue = "Initial value"; var displayNestedComponent = true; var renderer = new TestRenderer(); var component = new TestComponent(builder => { // At the outer level, have an unrelated fixed cascading value to show we can deal with combining both types builder.OpenComponent <CascadingValue <int> >(0); builder.AddAttribute(1, "Value", 123); builder.AddAttribute(2, "IsFixed", true); builder.AddAttribute(3, "ChildContent", new RenderFragment(builder2 => { // Then also have a non-fixed cascading value so we can show that unsubscription works builder2.OpenComponent <CascadingValue <string> >(0); builder2.AddAttribute(1, "Value", providedValue); builder2.AddAttribute(2, "ChildContent", new RenderFragment(builder3 => { if (displayNestedComponent) { builder3.OpenComponent <SecondCascadingParameterConsumerComponent <string, int> >(0); builder3.AddAttribute(1, "RegularParameter", "Goodbye"); builder3.CloseComponent(); } })); builder2.CloseComponent(); })); builder.CloseComponent(); }); // Act 1: Initial render; capture nested component ID var componentId = renderer.AssignRootComponentId(component); component.TriggerRender(); var firstBatch = renderer.Batches.Single(); var nestedComponent = FindComponent <CascadingParameterConsumerComponent <string> >(firstBatch, out var nestedComponentId); Assert.Equal(1, nestedComponent.NumSetParametersCalls); Assert.Equal(1, nestedComponent.NumRenders); // Act/Assert 2: Re-render the CascadingValue; observe nested component wasn't re-rendered providedValue = "Updated value"; displayNestedComponent = false; // Remove the nested component component.TriggerRender(); // Assert: We did not render the nested component now it's been removed Assert.Equal(2, renderer.Batches.Count); var secondBatch = renderer.Batches[1]; Assert.Equal(1, nestedComponent.NumRenders); Assert.Equal(3, secondBatch.DiffsByComponentId.Count); // Root + CascadingValue + CascadingValue, but not nested component // We *did* send updated params during the first render where it was removed, // because the params are sent before the disposal logic runs. We could avoid // this by moving the notifications into the OnAfterRender phase, but then we'd // often render descendants twice (once because they are descendants and some // direct parameter might have changed, then once because a cascading parameter // changed). We can't have it both ways, so optimize for the case when the // nested component *hasn't* just been removed. Assert.Equal(2, nestedComponent.NumSetParametersCalls); // Act 3: However, after disposal, the subscription is removed, so we won't send // updated params on subsequent CascadingValue renders. providedValue = "Updated value 2"; component.TriggerRender(); Assert.Equal(2, nestedComponent.NumSetParametersCalls); }
protected RenderTreeFrame[] GetRenderTree(IComponent component) { var renderer = new TestRenderer(); return(GetRenderTree(renderer, component)); }
public LayoutViewTest() { _renderer = new TestRenderer(); _layoutViewComponent = new LayoutView(); _layoutViewComponentId = _renderer.AssignRootComponentId(_layoutViewComponent); }
public PageDisplayTest() { _renderer = new TestRenderer(); _pageDisplayComponent = new PageDisplay(); _pageDisplayComponentId = _renderer.AssignRootComponentId(_pageDisplayComponent); }
public void ComponentCanTriggerRenderWhenExistingBatchIsInProgress() { // Arrange var renderer = new TestRenderer(); TestComponent parent = null; var parentRenderCount = 0; parent = new TestComponent(builder => { builder.OpenComponent <ReRendersParentComponent>(0); builder.AddAttribute(1, nameof(ReRendersParentComponent.Parent), parent); builder.CloseComponent(); builder.AddContent(2, $"Parent render count: {++parentRenderCount}"); }); var parentComponentId = renderer.AssignComponentId(parent); // Act parent.TriggerRender(); // Assert var batch = renderer.Batches.Single(); Assert.Equal(4, batch.DiffsInOrder.Count); // First is the parent component's initial render var diff1 = batch.DiffsInOrder[0]; Assert.Equal(parentComponentId, diff1.ComponentId); Assert.Collection(diff1.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Component <ReRendersParentComponent>( batch.ReferenceFrames[edit.ReferenceFrameIndex]); }, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "Parent render count: 1"); }); // Second is the child component's single render var diff2 = batch.DiffsInOrder[1]; Assert.NotEqual(parentComponentId, diff2.ComponentId); var diff2edit = diff2.Edits.Single(); Assert.Equal(RenderTreeEditType.PrependFrame, diff2edit.Type); AssertFrame.Text(batch.ReferenceFrames[diff2edit.ReferenceFrameIndex], "Child is here"); // Third is the parent's triggered render var diff3 = batch.DiffsInOrder[2]; Assert.Equal(parentComponentId, diff3.ComponentId); var diff3edit = diff3.Edits.Single(); Assert.Equal(RenderTreeEditType.UpdateText, diff3edit.Type); AssertFrame.Text(batch.ReferenceFrames[diff3edit.ReferenceFrameIndex], "Parent render count: 2"); // Fourth is child's rerender due to parent rendering var diff4 = batch.DiffsInOrder[3]; Assert.NotEqual(parentComponentId, diff4.ComponentId); Assert.Empty(diff4.Edits); }
public void AllRendersTriggeredSynchronouslyDuringEventHandlerAreHandledAsSingleBatch() { // Arrange: A root component with a child whose event handler explicitly queues // a re-render of both the root component and the child var renderer = new TestRenderer(); var eventCount = 0; TestComponent rootComponent = null; EventComponent childComponent = null; rootComponent = new TestComponent(builder => { builder.AddContent(0, "Child event count: " + eventCount); builder.OpenComponent <EventComponent>(1); builder.AddAttribute(2, nameof(EventComponent.OnTest), args => { eventCount++; rootComponent.TriggerRender(); childComponent.TriggerRender(); }); builder.CloseComponent(); }); var rootComponentId = renderer.AssignComponentId(rootComponent); rootComponent.TriggerRender(); var origBatchReferenceFrames = renderer.Batches.Single().ReferenceFrames; var childComponentFrame = origBatchReferenceFrames .Single(f => f.Component is EventComponent); var childComponentId = childComponentFrame.ComponentId; childComponent = (EventComponent)childComponentFrame.Component; var origEventHandlerId = origBatchReferenceFrames .Where(f => f.FrameType == RenderTreeFrameType.Attribute) .Last(f => f.AttributeEventHandlerId != 0) .AttributeEventHandlerId; Assert.Single(renderer.Batches); // Act renderer.DispatchEvent(childComponentId, origEventHandlerId, args: null); // Assert Assert.Equal(2, renderer.Batches.Count); var batch = renderer.Batches.Last(); Assert.Collection(batch.DiffsInOrder, diff => { // First we triggered the root component to re-render Assert.Equal(rootComponentId, diff.ComponentId); Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "Child event count: 1"); }); }, diff => { // Then the root re-render will have triggered an update to the child Assert.Equal(childComponentId, diff.ComponentId); Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "Render count: 2"); }); }, diff => { // Finally we explicitly requested a re-render of the child Assert.Equal(childComponentId, diff.ComponentId); Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.UpdateText, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "Render count: 3"); }); }); }
public RouteViewTest() { _renderer = new TestRenderer(); _routeViewComponent = new RouteView(); _routeViewComponentId = _renderer.AssignRootComponentId(_routeViewComponent); }
public ContainerComponent(TestRenderer renderer) { this.renderer = renderer; componentId = renderer.AttachTestRootComponent(this); }