Skip to content

Commit

Permalink
refactor: Merge RenderedFragment and RenderedComponent into IRendered…
Browse files Browse the repository at this point in the history
…Component with single internal RenderedComponent<TComponent> type

* refactor: Merge RenderedFragment and RenderedComponent into IRenderedComponent

* fix: use generic parameters correctly

* revert: rename BunitRootComponent to ContainerFragment

* refactor: rename RootComponent to BunitRootComponent

---------

Co-authored-by: Egil Hansen <[email protected]>
  • Loading branch information
2 people authored and bUnitBot committed Apr 19, 2024
1 parent d322526 commit 88dc406
Show file tree
Hide file tree
Showing 94 changed files with 826 additions and 912 deletions.
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ dotnet_diagnostic.S112.severity = none # S112: General exceptions should never b
dotnet_diagnostic.S1075.severity = suggestion # S1075: URIs should not be hardcoded
dotnet_diagnostic.S1186.severity = suggestion # S1186: Methods should not be empty
dotnet_diagnostic.S2292.severity = suggestion # S2292: Trivial properties should be auto-implemented
dotnet_diagnostic.S2743.severity = suggestion # S2743: A static field in a generic type is not shared among instances
dotnet_diagnostic.S4158.severity = none # BUGGY with C#9 code - doesnt understand local methods

# Razor specific rules
Expand Down
2 changes: 1 addition & 1 deletion docs/samples/tests/xunit/ReRenderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public async Task RendersViaInvokeAsyncWithLoading()
// Indirectly re-renders and returns the task returned by Calculate().
// The explicit <Task> here is important, otherwise the call to Calculate()
// will be awaited automatically.
var task = await cut.InvokeAsync<Task>(() => cut.Instance.Calculate(1, 2));
var task = await cut.InvokeAsync<CalcWithLoading, Task>(() => cut.Instance.Calculate(1, 2));
cut.MarkupMatches("<output>Loading</output>");

// Wait for the task to complete.
Expand Down
4 changes: 2 additions & 2 deletions docs/site/docs/interaction/awaiting-async-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ A test can fail if a component performs asynchronous renders. This may be due to

You need to handle this specifically in your tests because tests execute in the test framework's synchronization context and the test renderer executes renders in its own synchronization context. If you do not, you will likely experience tests that sometimes pass and sometimes fail.

bUnit comes with two methods that help to deal with this issue: the [`WaitForState()`](xref:Bunit.RenderedFragmentWaitForHelperExtensions.WaitForState(Bunit.RenderedFragment,System.Func{System.Boolean},System.Nullable{System.TimeSpan})) method covered on this page, and the [`WaitForAssertion()`](xref:Bunit.RenderedFragmentWaitForHelperExtensions.WaitForAssertion(Bunit.RenderedFragment,System.Action,System.Nullable{System.TimeSpan})) method covered on the <xref:async-assertion> page.
bUnit comes with two methods that help to deal with this issue: the [`WaitForState()`](xref:Bunit.RenderedComponentWaitForHelperExtensions.WaitForState``1(Bunit.IRenderedComponent{``0},System.Func{System.Boolean},System.Nullable{System.TimeSpan})) method covered on this page, and the [`WaitForAssertion()`](xref:Bunit.RenderedComponentWaitForHelperExtensions.WaitForAssertion``1(Bunit.IRenderedComponent{``0},System.Action,System.Nullable{System.TimeSpan})) method covered on the <xref:async-assertion> page.

Let's start by taking a look at the `WaitForState` method in more detail.

## Waiting for state using `WaitForState`

The [`WaitForState(Func<Boolean>, TimeSpan?)`](xref:Bunit.RenderedFragmentWaitForHelperExtensions.WaitForState(Bunit.RenderedFragment,System.Func{System.Boolean},System.Nullable{System.TimeSpan})) method can be used to block and wait in a test method, until the provided predicate returns true or the timeout is reached. (The default timeout is one second.)
The [`WaitForState(Func<Boolean>, TimeSpan?)`](xref:Bunit.RenderedComponentWaitForHelperExtensions.WaitForState``1(Bunit.IRenderedComponent{``0},System.Func{System.Boolean},System.Nullable{System.TimeSpan})) method can be used to block and wait in a test method, until the provided predicate returns true or the timeout is reached. (The default timeout is one second.)

> [!NOTE]
> The `WaitForState()` method will try the predicate passed to it when the `WaitForState()` method is called, and every time the component under test renders.
Expand Down
2 changes: 1 addition & 1 deletion docs/site/docs/interaction/dispose-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The following example of this:
[!code-csharp[DisposeComponentsTest.cs](../../../samples/tests/xunit/DisposeComponentsTest.cs#L13-L22)]

> [!WARNING]
> For `IAsyncDisposable` (since .net5) relying on [`WaitForState()`](xref:Bunit.RenderedFragmentWaitForHelperExtensions.WaitForState(Bunit.RenderedFragment,System.Func{System.Boolean},System.Nullable{System.TimeSpan})) or [`WaitForAssertion()`](xref:Bunit.RenderedFragmentWaitForHelperExtensions.WaitForAssertion(Bunit.RenderedFragment,System.Action,System.Nullable{System.TimeSpan})) will not work as a disposed component will not trigger a new render cycle.
> For `IAsyncDisposable` (since .net5) relying on [`WaitForState()`](xref:Bunit.RenderedComponentWaitForHelperExtensions.WaitForState``1(Bunit.IRenderedComponent{``0},System.Func{System.Boolean},System.Nullable{System.TimeSpan})) or [`WaitForAssertion()`](xref:Bunit.RenderedComponentWaitForHelperExtensions.WaitForAssertion``1(Bunit.IRenderedComponent{``0},System.Action,System.Nullable{System.TimeSpan})) will not work as a disposed component will not trigger a new render cycle.
## Disposing components asynchronously
If a component implements `IAsyncDisposable`, `DisposeComponentsAsync` can be awaited to wait for all asynchronous `DisposeAsync` methods. Sometimes interacting with JavaScript in Blazor WebAssembly requires disposing or resetting state in `DisposeAsync`.
Expand Down
4 changes: 2 additions & 2 deletions docs/site/docs/interaction/trigger-event-handlers.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ bUnit comes with event dispatch helper methods that makes it possible to invoke
- [Progress events](xref:Bunit.ProgressEventDispatchExtensions)
- [Touch event](xref:Bunit.TouchEventDispatchExtensions)

To use these, first find the element in the component under test where the event handler is bound. This is usually done with the [`Find(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.Find(Bunit.RenderedFragment,System.String)) method. Next, invoke the event dispatch helper method of choice.
To use these, first find the element in the component under test where the event handler is bound. This is usually done with the [`Find(string cssSelector)`](xref:Bunit.RenderedComponentExtensions.Find``1(Bunit.IRenderedComponent{``0},System.String)) method. Next, invoke the event dispatch helper method of choice.

The following section demonstrates how to do this...

Expand All @@ -49,7 +49,7 @@ To trigger the `@onclick` `ClickHandler` event handler method in the `<ClickMe>`

This is what happens in the test:

1. In the arrange step of the test, the `<ClickMe>` component is rendered and the `<button>` element is found using the [`Find(string cssSelector)`](xref:Bunit.RenderedFragmentExtensions.Find(Bunit.RenderedFragment,System.String)) method.
1. In the arrange step of the test, the `<ClickMe>` component is rendered and the `<button>` element is found using the [`Find(string cssSelector)`](xref:Bunit.RenderedComponentExtensions.Find``1(Bunit.IRenderedComponent{``0},System.String)) method.
2. The act step of the test is the `<button>`'s click event handler. In this case, the `ClickHandler` event handler method is invoked in three different ways:
- The first and second invocations use the same [`Click`](xref:Bunit.MouseEventDispatchExtensions.Click(AngleSharp.Dom.IElement,System.Int64,System.Double,System.Double,System.Double,System.Double,System.Double,System.Double,System.Double,System.Double,System.Int64,System.Int64,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.String)) method. It has a number of optional arguments, some of which are passed in the second invocation. If any arguments are provided, they are added to an instance of the `MouseEventArgs` type, which is passed to the event handler if it has it as an argument.
- The last invocation uses the [`Click`](xref:Bunit.MouseEventDispatchExtensions.Click(AngleSharp.Dom.IElement,Microsoft.AspNetCore.Components.Web.MouseEventArgs)) method. This takes an instance of the `MouseEventArgs` type, which is passed to the event handler if it has it as an argument.
Expand Down
22 changes: 11 additions & 11 deletions docs/site/docs/interaction/trigger-renders.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,37 @@ title: Triggering a render life cycle on a component

# Triggering a render life cycle on a component

To trigger a re-render of a component under test, a reference to it through a <xref:Bunit.RenderedComponent`1> type is needed. When using the <xref:Bunit.BunitContext>'s `Render<TComponent>()` method, this is the type returned.
To trigger a re-render of a component under test, a reference to it through a <xref:Bunit.IRenderedComponent`1> type is needed. When using the <xref:Bunit.BunitContext>'s `Render<TComponent>()` method, this is the type returned.

In `.razor` based tests, using the <xref:Bunit.BunitContext>'s <xref:Bunit.BunitContext.Render(Microsoft.AspNetCore.Components.RenderFragment)> method also returns an <xref:Bunit.RenderedComponent`1> (as opposed to the <xref:Bunit.BunitContext.Render(Microsoft.AspNetCore.Components.RenderFragment)> method which returns the more simple <xref:Bunit.RenderedFragment>).
In `.razor` based tests, using the <xref:Bunit.BunitContext>'s <xref:Bunit.BunitContext.Render(Microsoft.AspNetCore.Components.RenderFragment)> method also returns an <xref:Bunit.IRenderedComponent`1> (as opposed to the <xref:Bunit.BunitContext.Render(Microsoft.AspNetCore.Components.RenderFragment)> method which returns the more simple <xref:Bunit.IRenderedComponent`1>).

If you have a <xref:Bunit.RenderedFragment> or a <xref:Bunit.RenderedComponent`1> in a test, but need a child component's <xref:Bunit.RenderedComponent`1>, then use the `FindComponent<TComponent>()` or the `FindComponents<TComponent>()` methods, which traverse down the render tree and finds rendered components.
If you have a <xref:Bunit.IRenderedComponent`1> in a test, but need a child component's <xref:Bunit.IRenderedComponent`1>, then use the `FindComponent<TComponent>()` or the `FindComponents<TComponent>()` methods, which traverse down the render tree and finds rendered components.

With a <xref:Bunit.RenderedComponent`1>, it is possible to cause the component to render again directly through the [`Render()`](xref:Bunit.BunitContext.Render``1(Microsoft.AspNetCore.Components.RenderFragment)) method or one of the [`Render()`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.RenderedComponent{``0},System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})) methods, or indirectly through the [`InvokeAsync()`](xref:Bunit.RenderedFragment.Bunit.RenderedFragmentInvokeAsyncExtensions.InvokeAsync(System.Action)) method.
With a <xref:Bunit.IRenderedComponent`1>, it is possible to cause the component to render again directly through the [`Render()`](xref:Bunit.BunitContext.Render``1(Microsoft.AspNetCore.Components.RenderFragment)) method or one of the [`Render()`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.IRenderedComponent{``0},System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})) methods, or indirectly through the [`InvokeAsync()`](xref:Bunit.RenderedComponentInvokeAsyncExtensions.InvokeAsync``1(Bunit.IRenderedComponent{``0},System.Action)) method.

Let's look at how to use each of these methods to cause a re-render.

## Render

The [`Render()`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.RenderedComponent{``0},System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})) method tells the renderer to re-render the component, i.e. go through its life-cycle methods (except for `OnInitialized()` and `OnInitializedAsync()` methods). To use it, do the following:
The [`Render()`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.IRenderedComponent{``0},System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})) method tells the renderer to re-render the component, i.e. go through its life-cycle methods (except for `OnInitialized()` and `OnInitializedAsync()` methods). To use it, do the following:

[!code-csharp[](../../../samples/tests/xunit/ReRenderTest.cs?start=15&end=21&highlight=5)]

The highlighted line shows the call to [`Render()`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.RenderedComponent{``0},System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})).
The highlighted line shows the call to [`Render()`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.IRenderedComponent{``0},System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})).

> [!TIP]
> The number of renders a component has been through can be inspected and verified using the <xref:Bunit.RenderedFragment.RenderCount> property.
> The number of renders a component has been through can be inspected and verified using the <xref:Bunit.IRenderedComponent`1.RenderCount> property.
## Render

The [`Render(...)`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.RenderedComponent{``0},System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})) methods tells the renderer to re-render the component with new parameters, i.e. go through its life-cycle methods (except for `OnInitialized()` and `OnInitializedAsync()` methods), passing the new parameters &mdash; _but only the new parameters_ &mdash; to the `SetParametersAsync()` method. To use it, do the following:
The [`Render(...)`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.IRenderedComponent{``0},System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})) methods tells the renderer to re-render the component with new parameters, i.e. go through its life-cycle methods (except for `OnInitialized()` and `OnInitializedAsync()` methods), passing the new parameters &mdash; _but only the new parameters_ &mdash; to the `SetParametersAsync()` method. To use it, do the following:

[!code-csharp[](../../../samples/tests/xunit/ReRenderTest.cs?start=28&end=38&highlight=7-9)]

The highlighted line shows the call to [`Render()`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.RenderedComponent{``0},System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})), which is also available as a version that takes the zero or more component parameters, e.g. created through the component parameter factory helper methods, if you prefer that method of passing parameters.
The highlighted line shows the call to [`Render()`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.IRenderedComponent{``0},System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})), which is also available as a version that takes the zero or more component parameters, e.g. created through the component parameter factory helper methods, if you prefer that method of passing parameters.

> [!NOTE]
> Passing parameters to components through the [`Render(...)`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.RenderedComponent{``0},System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})) methods is identical to doing it with the `Render<TComponent>(...)` methods, described in detail on the <xref:passing-parameters-to-components> page.
> Passing parameters to components through the [`Render(...)`](xref:Bunit.RenderedComponentRenderExtensions.Render``1(Bunit.IRenderedComponent{``0},System.Action{Bunit.ComponentParameterCollectionBuilder{``0}})) methods is identical to doing it with the `Render<TComponent>(...)` methods, described in detail on the <xref:passing-parameters-to-components> page.
## InvokeAsync

Expand All @@ -56,7 +56,7 @@ To invoke the `Calculate()` method on the component instance, do the following:
The highlighted line shows the call to `InvokeAsync()`, which is passed an `Action` delegate that calls the `Calculate` method.

> [!TIP]
> The instance of a component under test is available through the <xref:Bunit.RenderedComponent`1.Instance> property.
> The instance of a component under test is available through the <xref:Bunit.IRenderedComponent`1.Instance> property.
### Advanced use cases

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ The solution is to inherit from bUnits `BunitContext` instead, i.e.:

## Limitations of rendering a `RenderFragment` inside a test

When rendering a `RenderFragment` using the <xref:Bunit.BunitContext.Render(Microsoft.AspNetCore.Components.RenderFragment)> method, the created <xref:Bunit.RenderedFragment> is static. This means that it will not re-render even if events are triggered.
When rendering a `RenderFragment` using the <xref:Bunit.BunitContext.Render(Microsoft.AspNetCore.Components.RenderFragment)> method, the created <xref:Bunit.IRenderedComponent`1> is static. This means that it will not re-render even if events are triggered.
```razor
@inherits BunitContext
Expand Down
4 changes: 2 additions & 2 deletions docs/site/docs/verification/async-assertion.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ A test can fail if a component performs asynchronous renders. This may be due to

You need to handle this specifically in your tests because tests execute in the test framework's synchronization context and the test renderer executes renders in its own synchronization context. If you do not, you will likely experience tests that sometimes pass, and sometimes fail.

bUnit comes with two methods that help to deal with this issue: the [`WaitForAssertion()`](xref:Bunit.RenderedFragmentWaitForHelperExtensions.WaitForAssertion(Bunit.RenderedFragment,System.Action,System.Nullable{System.TimeSpan})) method covered on this page, and the [`WaitForState()`](xref:Bunit.RenderedFragmentWaitForHelperExtensions.WaitForState(Bunit.RenderedFragment,System.Func{System.Boolean},System.Nullable{System.TimeSpan})) method covered on the <xref:awaiting-async-state> page.
bUnit comes with two methods that help to deal with this issue: the [`WaitForAssertion()`](xref:Bunit.RenderedComponentWaitForHelperExtensions.WaitForAssertion``1(Bunit.IRenderedComponent{``0},System.Action,System.Nullable{System.TimeSpan})) method covered on this page, and the [`WaitForState()`](xref:Bunit.RenderedComponentWaitForHelperExtensions.WaitForState``1(Bunit.IRenderedComponent{``0},System.Func{System.Boolean},System.Nullable{System.TimeSpan})) method covered on the <xref:awaiting-async-state> page.

Let's start by taking a look at the ` WaitForAssertion` method in more detail.

## Waiting for assertion to pass using `WaitForAssertion`

The [`WaitForAssertion(Action, TimeSpan?)`](xref:Bunit.RenderedFragmentWaitForHelperExtensions.WaitForAssertion(Bunit.RenderedFragment,System.Action,System.Nullable{System.TimeSpan})) method can be used to block and wait in a test method until the provided assert action does not throw an exception, or until the timeout is reached (the default timeout is one second).
The [`WaitForAssertion(Action, TimeSpan?)`](xref:Bunit.RenderedComponentWaitForHelperExtensions.WaitForAssertion``1(Bunit.IRenderedComponent{``0},System.Action,System.Nullable{System.TimeSpan})) method can be used to block and wait in a test method until the provided assert action does not throw an exception, or until the timeout is reached (the default timeout is one second).

> [!NOTE]
> The `WaitForAssertion()` method will try the assert action passed to it when the `WaitForAssertion()` method is called and every time the component under test renders.
Expand Down
Loading

0 comments on commit 88dc406

Please sign in to comment.