This commit is contained in:
parent
3da9c7cff7
commit
8ba7c7b457
|
|
@ -69,6 +69,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
CurrentRenderTree.Clear();
|
CurrentRenderTree.Clear();
|
||||||
renderFragment(CurrentRenderTree);
|
renderFragment(CurrentRenderTree);
|
||||||
|
|
||||||
|
CurrentRenderTree.AssertTreeIsValid(Component);
|
||||||
|
|
||||||
var diff = RenderTreeDiffBuilder.ComputeDiff(
|
var diff = RenderTreeDiffBuilder.ComputeDiff(
|
||||||
_renderer,
|
_renderer,
|
||||||
batchBuilder,
|
batchBuilder,
|
||||||
|
|
|
||||||
|
|
@ -685,6 +685,17 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
public ArrayRange<RenderTreeFrame> GetFrames() =>
|
public ArrayRange<RenderTreeFrame> GetFrames() =>
|
||||||
_entries.ToRange();
|
_entries.ToRange();
|
||||||
|
|
||||||
|
internal void AssertTreeIsValid(IComponent component)
|
||||||
|
{
|
||||||
|
if (_openElementIndices.Count > 0)
|
||||||
|
{
|
||||||
|
// It's never valid to leave an element/component/region unclosed. Doing so
|
||||||
|
// could cause undefined behavior in diffing.
|
||||||
|
ref var invalidFrame = ref _entries.Buffer[_openElementIndices.Peek()];
|
||||||
|
throw new InvalidOperationException($"Render output is invalid for component of type '{component.GetType().FullName}'. A frame of type '{invalidFrame.FrameType}' was left unclosed. Do not use try/catch inside rendering logic, because partial output cannot be undone.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Internal for testing
|
// Internal for testing
|
||||||
internal void ProcessDuplicateAttributes(int first)
|
internal void ProcessDuplicateAttributes(int first)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4001,6 +4001,22 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
requestedType => Assert.Equal(typeof(TestComponent), requestedType));
|
requestedType => Assert.Equal(typeof(TestComponent), requestedType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ThrowsIfComponentProducesInvalidRenderTree()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
var component = new TestComponent(builder =>
|
||||||
|
{
|
||||||
|
builder.OpenElement(0, "myElem");
|
||||||
|
});
|
||||||
|
var rootComponentId = renderer.AssignRootComponentId(component);
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => renderer.RenderRootComponentAsync(rootComponentId));
|
||||||
|
Assert.StartsWith($"Render output is invalid for component of type '{typeof(TestComponent).FullName}'. A frame of type 'Element' was left unclosed.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestComponentActivator<TResult> : IComponentActivator where TResult : IComponent, new()
|
private class TestComponentActivator<TResult> : IComponentActivator where TResult : IComponent, new()
|
||||||
{
|
{
|
||||||
public List<Type> RequestedComponentTypes { get; } = new List<Type>();
|
public List<Type> RequestedComponentTypes { get; } = new List<Type>();
|
||||||
|
|
|
||||||
|
|
@ -1831,6 +1831,69 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
f => AssertFrame.Attribute(f, "3", "see ya"));
|
f => AssertFrame.Attribute(f, "3", "see ya"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AcceptsClosedFramesAsValid()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder();
|
||||||
|
var component = new TestComponent();
|
||||||
|
builder.OpenElement(0, "myElem");
|
||||||
|
builder.OpenRegion(1);
|
||||||
|
builder.OpenComponent<OtherComponent>(2);
|
||||||
|
builder.CloseComponent();
|
||||||
|
builder.CloseRegion();
|
||||||
|
builder.CloseElement();
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
// Lack of exception is success
|
||||||
|
builder.AssertTreeIsValid(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReportsUnclosedElementAsInvalid()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder();
|
||||||
|
var component = new TestComponent();
|
||||||
|
builder.OpenElement(0, "outerElem");
|
||||||
|
builder.OpenElement(1, "innerElem");
|
||||||
|
builder.CloseElement();
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => builder.AssertTreeIsValid(component));
|
||||||
|
Assert.StartsWith($"Render output is invalid for component of type '{typeof(TestComponent).FullName}'. A frame of type 'Element' was left unclosed.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReportsUnclosedComponentAsInvalid()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder();
|
||||||
|
var component = new TestComponent();
|
||||||
|
builder.OpenComponent<OtherComponent>(0);
|
||||||
|
builder.OpenComponent<OtherComponent>(1);
|
||||||
|
builder.CloseComponent();
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => builder.AssertTreeIsValid(component));
|
||||||
|
Assert.StartsWith($"Render output is invalid for component of type '{typeof(TestComponent).FullName}'. A frame of type 'Component' was left unclosed.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ReportsUnclosedRegionAsInvalid()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder();
|
||||||
|
var component = new TestComponent();
|
||||||
|
builder.OpenRegion(0);
|
||||||
|
builder.OpenRegion(1);
|
||||||
|
builder.CloseRegion();
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => builder.AssertTreeIsValid(component));
|
||||||
|
Assert.StartsWith($"Render output is invalid for component of type '{typeof(TestComponent).FullName}'. A frame of type 'Region' was left unclosed.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestComponent : IComponent
|
private class TestComponent : IComponent
|
||||||
{
|
{
|
||||||
public void Attach(RenderHandle renderHandle) { }
|
public void Attach(RenderHandle renderHandle) { }
|
||||||
|
|
@ -1839,6 +1902,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
=> throw new NotImplementedException();
|
=> throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class OtherComponent : TestComponent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private class TestRenderer : Renderer
|
private class TestRenderer : Renderer
|
||||||
{
|
{
|
||||||
public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance)
|
public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue