// 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 Microsoft.AspNetCore.Blazor.Layouts; using Microsoft.AspNetCore.Blazor.RenderTree; using Microsoft.AspNetCore.Blazor.Test.Helpers; using System.Collections.Generic; using System.Linq; using Xunit; namespace Microsoft.AspNetCore.Blazor.Test { public class LayoutTest { private TestRenderer _renderer = new TestRenderer(); private LayoutDisplay _layoutDisplayComponent = new LayoutDisplay(); private int _layoutDisplayComponentId; public LayoutTest() { _renderer = new TestRenderer(); _layoutDisplayComponent = new LayoutDisplay(); _layoutDisplayComponentId = _renderer.AssignComponentId(_layoutDisplayComponent); } [Fact] public void DisplaysComponentInsideLayout() { // Arrange/Act _layoutDisplayComponent.SetParameters(new Dictionary { { nameof(LayoutDisplay.Page), typeof(ComponentWithLayout) } }); // Assert var batch = _renderer.Batches.Single(); Assert.Collection(batch.DiffsInOrder, diff => { // First is the LayoutDisplay component, which contains a RootLayout var singleEdit = diff.Edits.Single(); Assert.Equal(RenderTreeEditType.PrependFrame, singleEdit.Type); AssertFrame.Component( batch.ReferenceFrames[singleEdit.ReferenceFrameIndex]); }, diff => { // ... then a RootLayout which contains a ComponentWithLayout // First is the LayoutDisplay component, which contains a RootLayout Assert.Collection(diff.Edits, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "RootLayout starts here"); }, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Component( batch.ReferenceFrames[edit.ReferenceFrameIndex]); }, edit => { Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Text( batch.ReferenceFrames[edit.ReferenceFrameIndex], "RootLayout ends here"); }); }, diff => { // ... then the ComponentWithLayout var singleEdit = diff.Edits.Single(); Assert.Equal(RenderTreeEditType.PrependFrame, singleEdit.Type); AssertFrame.Text( batch.ReferenceFrames[singleEdit.ReferenceFrameIndex], $"{nameof(ComponentWithLayout)} is here."); }); } [Fact] public void DisplaysComponentInsideNestedLayout() { // Arrange/Act _layoutDisplayComponent.SetParameters(new Dictionary { { nameof(LayoutDisplay.Page), typeof(ComponentWithNestedLayout) } }); // Assert var batch = _renderer.Batches.Single(); Assert.Collection(batch.DiffsInOrder, // First, a LayoutDisplay containing a RootLayout diff => AssertFrame.Component( batch.ReferenceFrames[diff.Edits[0].ReferenceFrameIndex]), // Then a RootLayout containing a NestedLayout diff => AssertFrame.Component( batch.ReferenceFrames[diff.Edits[1].ReferenceFrameIndex]), // Then a NestedLayout containing a ComponentWithNestedLayout diff => AssertFrame.Component( batch.ReferenceFrames[diff.Edits[1].ReferenceFrameIndex]), // Then the ComponentWithNestedLayout diff => AssertFrame.Text( batch.ReferenceFrames[diff.Edits[0].ReferenceFrameIndex], $"{nameof(ComponentWithNestedLayout)} is here.")); } [Fact] public void CanChangeDisplayedPageWithSameLayout() { // Arrange _layoutDisplayComponent.SetParameters(new Dictionary { { nameof(LayoutDisplay.Page), typeof(ComponentWithLayout) } }); // Act _layoutDisplayComponent.SetParameters(new Dictionary { { nameof(LayoutDisplay.Page), typeof(DifferentComponentWithLayout) } }); // Assert Assert.Equal(2, _renderer.Batches.Count); var batch = _renderer.Batches[1]; Assert.Equal(1, batch.DisposedComponentIDs.Count); // Disposed only the inner page component Assert.Collection(batch.DiffsInOrder, diff => Assert.Empty(diff.Edits), // LayoutDisplay rerendered, but with no changes diff => { // RootLayout rerendered Assert.Collection(diff.Edits, edit => { // Removed old page Assert.Equal(RenderTreeEditType.RemoveFrame, edit.Type); Assert.Equal(1, edit.SiblingIndex); }, edit => { // Inserted new one Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); Assert.Equal(1, edit.SiblingIndex); AssertFrame.Component( batch.ReferenceFrames[edit.ReferenceFrameIndex]); }); }, diff => { // New page rendered var singleEdit = diff.Edits.Single(); Assert.Equal(RenderTreeEditType.PrependFrame, singleEdit.Type); AssertFrame.Text( batch.ReferenceFrames[singleEdit.ReferenceFrameIndex], $"{nameof(DifferentComponentWithLayout)} is here."); }); } [Fact] public void CanChangeDisplayedPageWithDifferentLayout() { // Arrange _layoutDisplayComponent.SetParameters(new Dictionary { { nameof(LayoutDisplay.Page), typeof(ComponentWithLayout) } }); // Act _layoutDisplayComponent.SetParameters(new Dictionary { { nameof(LayoutDisplay.Page), typeof(ComponentWithNestedLayout) } }); // Assert Assert.Equal(2, _renderer.Batches.Count); var batch = _renderer.Batches[1]; Assert.Equal(1, batch.DisposedComponentIDs.Count); // Disposed only the inner page component Assert.Collection(batch.DiffsInOrder, diff => Assert.Empty(diff.Edits), // LayoutDisplay rerendered, but with no changes diff => { // RootLayout rerendered Assert.Collection(diff.Edits, edit => { // Removed old page Assert.Equal(RenderTreeEditType.RemoveFrame, edit.Type); Assert.Equal(1, edit.SiblingIndex); }, edit => { // Inserted new nested layout Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); Assert.Equal(1, edit.SiblingIndex); AssertFrame.Component( batch.ReferenceFrames[edit.ReferenceFrameIndex]); }); }, diff => { // New nested layout rendered var edit = diff.Edits[1]; Assert.Equal(RenderTreeEditType.PrependFrame, edit.Type); AssertFrame.Component( batch.ReferenceFrames[edit.ReferenceFrameIndex]); }, diff => { // New inner page rendered var singleEdit = diff.Edits.Single(); Assert.Equal(RenderTreeEditType.PrependFrame, singleEdit.Type); AssertFrame.Text( batch.ReferenceFrames[singleEdit.ReferenceFrameIndex], $"{nameof(ComponentWithNestedLayout)} is here."); }); } private class RootLayout : AutoRenderComponent, ILayoutComponent { public RenderFragment Body { get; set; } protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.AddContent(0, "RootLayout starts here"); builder.AddContent(1, Body); builder.AddContent(2, "RootLayout ends here"); } } [Layout(typeof(RootLayout))] private class NestedLayout : AutoRenderComponent, ILayoutComponent { public RenderFragment Body { get; set; } protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.AddContent(0, "NestedLayout starts here"); builder.AddContent(1, Body); builder.AddContent(2, "NestedLayout ends here"); } } [Layout(typeof(RootLayout))] private class ComponentWithLayout : AutoRenderComponent { protected override void BuildRenderTree(RenderTreeBuilder builder) => builder.AddContent(0, $"{nameof(ComponentWithLayout)} is here."); } [Layout(typeof(RootLayout))] private class DifferentComponentWithLayout : AutoRenderComponent { protected override void BuildRenderTree(RenderTreeBuilder builder) => builder.AddContent(0, $"{nameof(DifferentComponentWithLayout)} is here."); } [Layout(typeof(NestedLayout))] private class ComponentWithNestedLayout : AutoRenderComponent { protected override void BuildRenderTree(RenderTreeBuilder builder) => builder.AddContent(0, $"{nameof(ComponentWithNestedLayout)} is here."); } } }