-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Throw if a component produces an invalid render tree. Fixes #24579 #24650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1831,6 +1831,69 @@ public void ProcessDuplicateAttributes_CanRemoveOverwrittenAttributes() | |
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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have validation for when the close statements balance out but are in different order? For example
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a good point to raise, but I think we're OK with this one. In practice all of the Considering how perf-critical is this low-level part of rendering, we wouldn't want penalize people using |
||
|
||
// 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 | ||
{ | ||
public void Attach(RenderHandle renderHandle) { } | ||
|
@@ -1839,6 +1902,10 @@ public Task SetParametersAsync(ParameterView parameters) | |
=> throw new NotImplementedException(); | ||
} | ||
|
||
private class OtherComponent : TestComponent | ||
{ | ||
} | ||
|
||
private class TestRenderer : Renderer | ||
{ | ||
public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it always guaranteed that the index is going to be in range (assuming there are no bugs in the RenderTreeBuilder code) I believe that's the case, but given we are dealing with "failure modes" here I ask just in case to be more cautious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but even if not, the code here is certain to throw (either from the
throw
statement on L695, or an index-out-of-range on L694), which is all we need.