Skip to content

Commit 162a583

Browse files
committed
Docs: Added trigger renders page
1 parent 6080436 commit 162a583

File tree

5 files changed

+216
-81
lines changed

5 files changed

+216
-81
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<output>@result</output>
2+
3+
@code
4+
{
5+
int result = 0;
6+
7+
public void Calculate(int x, int y)
8+
{
9+
result = x + y;
10+
StateHasChanged();
11+
}
12+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using Xunit;
2+
using Bunit;
3+
using System.Collections.Generic;
4+
using Microsoft.AspNetCore.Components;
5+
using Microsoft.AspNetCore.Components.Web;
6+
7+
using static Bunit.ComponentParameterFactory;
8+
9+
namespace Bunit.Docs.Samples
10+
{
11+
public class ReRenderTest
12+
{
13+
[Fact]
14+
public void RenderAgainUsingRender()
15+
{
16+
// Arrange - renders the Heading component
17+
using var ctx = new TestContext();
18+
var cut = ctx.RenderComponent<Heading>();
19+
Assert.Equal(1, cut.RenderCount);
20+
21+
// Re-render without new parameters
22+
cut.Render();
23+
24+
Assert.Equal(2, cut.RenderCount);
25+
}
26+
27+
[Fact]
28+
public void RenderAgainUsingSetParametersAndRender()
29+
{
30+
// Arrange - renders the Heading component
31+
using var ctx = new TestContext();
32+
var cut = ctx.RenderComponent<Item>(parameters => parameters
33+
.Add(p => p.Value, "Foo")
34+
);
35+
cut.MarkupMatches("<span>Foo</span>");
36+
37+
// Re-render with new parameters
38+
cut.SetParametersAndRender(parameters => parameters
39+
.Add(p => p.Value, "Bar")
40+
);
41+
42+
cut.MarkupMatches("<span>Bar</span>");
43+
}
44+
45+
[Fact]
46+
public void RendersViaInvokeAsync()
47+
{
48+
// Arrange - renders the Heading component
49+
using var ctx = new TestContext();
50+
var cut = ctx.RenderComponent<ImparativeCalc>();
51+
52+
// Indirectly re-renders through the call to StateHasChanged
53+
// in the Calculate(x, y) method.
54+
cut.InvokeAsync(() => cut.Instance.Calculate(1, 2));
55+
56+
cut.MarkupMatches("<output>3</output>");
57+
}
58+
}
59+
}
Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,59 @@
11
---
22
uid: trigger-renders
3-
title: Triggering a Render Life-Cycle on a Component
3+
title: Triggering a Render Life Cycle on a Component
44
---
55

6-
# Triggering a Render Life-Cycle on a Component
6+
# Triggering a Render Life Cycle on a Component
77

8-
Describe how to trigger an explicit render life-cycle through the IRenderedComponent Render, SetParametersAndRender, and InvokeAsync methods.
8+
When a component under test is rendered, an instance of the <xref:Bunit.IRenderedComponent`1> type is returned. Through that, it is possible to cause the component under test to render again directly through the <xref:Bunit.IRenderedComponentBase`1.Render> method or one of the [`SetParametersAndRender(...)`](xref:Bunit.IRenderedComponentBase`1.SetParametersAndRender(Bunit.Rendering.ComponentParameter[])) methods or indirectly through the <xref:Bunit.IRenderedFragmentBase.InvokeAsync(System.Action)> method.
9+
10+
> [!WARNING]
11+
> The `Render()` and `SetParametersAndRender()` methods are not available in the <xref:Bunit.IRenderedFragment> type that is returned when calling the _non_-generic version of `GetComponentUnderTest()` in `<Fixture>`-based Razor tests. Call the generic version of `GetComponentUnderTest<TComponent>()` to get a <xref:Bunit.IRenderedComponent`1>.
12+
13+
> [!NOTE]
14+
> These methods are available and work the same in both C# and Razor-based tests. The examples below are from C# based tests only.
15+
16+
Let's look at how to use each of these methods to cause a re-render.
17+
18+
## Render
19+
20+
The <xref:Bunit.IRenderedComponentBase`1.Render> 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:
21+
22+
[!code-csharp[](../../../samples/tests/xunit/ReRenderTest.cs?start=17&end=24&highlight=6)]
23+
24+
The highlighted line shows the call to <xref:Bunit.IRenderedComponentBase`1.Render>.
25+
26+
> [!TIP]
27+
> The number of renders a component has been through can be inspected and verified using the <xref:Bunit.IRenderedFragmentBase.RenderCount> property.
28+
29+
## SetParametersAndRender
30+
31+
The [`SetParametersAndRender(...)`](xref:Bunit.IRenderedComponentBase`1.SetParametersAndRender(Bunit.Rendering.ComponentParameter[])) 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 to the `SetParametersAsync()` method, _but only the new parameters_. To use it, do the following:
32+
33+
[!code-csharp[](../../../samples/tests/xunit/ReRenderTest.cs?start=31&end=42&highlight=8-10)]
34+
35+
The highlighted line shows the call to <xref:Bunit.IRenderedComponentBase`1.SetParametersAndRender(System.Action{Bunit.ComponentParameterBuilder{`0}})>, which is also available as <xref:Bunit.IRenderedComponentBase`1.SetParametersAndRender(Bunit.Rendering.ComponentParameter[])> if you prefer that method of passing parameters.
36+
37+
> [!NOTE]
38+
> Passing parameters to components through the [`SetParametersAndRender(...)`](xref:Bunit.IRenderedComponentBase`1.SetParametersAndRender(Bunit.Rendering.ComponentParameter[])) methods are identical to doing it with the [`RenderComponent<TComponent>(...)`](xref:Bunit.IRenderedComponentBase`1.SetParametersAndRender(Bunit.Rendering.ComponentParameter[])) methods, described in detail on the <xref:passing-parameters-to-components> page.
39+
40+
## InvokeAsync
41+
42+
Invoking methods on a component under test, which causes a render, e.g. by calling `StateHasChanged`, can result in the following error:
43+
44+
> The current thread is not associated with the Dispatcher. Use InvokeAsync() to switch execution to the Dispatcher when triggering rendering or component state.
45+
46+
If you receive this error, you need to invoke your method inside an `Action` delegate passed to the <xref:Bunit.IRenderedFragmentBase.InvokeAsync(System.Action)> method.
47+
48+
Consider the `<ImparativeCalc>` component listed below:
49+
50+
[!code-html[ImparativeCalc.razor](../../../samples/components/ImparativeCalc.razor)]
51+
52+
To invoke the `Calculate()` method on the component instance, do the following:
53+
54+
[!code-csharp[](../../../samples/tests/xunit/ReRenderTest.cs?start=49&end=56&highlight=6)]
55+
56+
The highlighted line shows the call to <xref:Bunit.IRenderedFragmentBase.InvokeAsync(System.Action)>, which is passed an `Action` delegate, that calls the `Calculate` method.
57+
58+
> [!TIP]
59+
> The instance of a component under test is available through the <xref:Bunit.IRenderedComponentBase`1.Instance> property.

docs/site/docs/toc.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
## [Asynchronous Assertion of Changes](xref:async-assertion)
2323

2424
# [Test Doubles](xref:test-doubles)
25-
## [Mocking Authorization](xref:mocking-auth)
25+
## [Faking Authorization](xref:faking-auth)
2626
## [Mocking HttpClient](xref:mocking-httpclient)
2727
## [Mocking IJsRuntime](xref:mocking-ijsruntime)
2828
## [Mocking Localization](xref:mocking-localizer)

docs/site/templates/bunit/styles/main.css

Lines changed: 90 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,136 @@
11
.navbar-brand {
2-
line-height: 50px;
3-
margin-right: 15px !important;
2+
line-height: 50px;
3+
margin-right: 15px !important;
44
}
55

6-
.navbar-brand > img {
7-
display: inline-block;
8-
margin-top: -8px;
9-
margin-right: 5px;
10-
}
6+
.navbar-brand>img {
7+
display: inline-block;
8+
margin-top: -8px;
9+
margin-right: 5px;
10+
}
1111

12-
.navbar-brand > span {
13-
font-weight: 600;
14-
font-size: 16px;
15-
color: #fff;
16-
}
12+
.navbar-brand>span {
13+
font-weight: 600;
14+
font-size: 16px;
15+
color: #fff;
16+
}
1717

1818
.sidetoc {
19-
top: 142px;
19+
top: 142px;
2020
}
2121

2222
#toc {
23-
margin: 0;
23+
margin: 0;
2424
}
2525

26-
#toc .nav > li > .expand-stub {
27-
display: none;
28-
}
26+
#toc .nav>li>.expand-stub {
27+
display: none;
28+
}
2929

3030
.nav.level2 {
31-
display: block !important;
32-
margin-left: 0;
31+
display: block !important;
32+
margin-left: 0;
3333
}
3434

3535
.body-content {
36-
font-size: 1.4rem;
36+
font-size: 1.4rem;
3737
}
3838

3939
.article {
40-
margin-top: 80px;
40+
margin-top: 80px;
4141
}
4242

4343
.sideaffix {
44-
margin-top: 0;
44+
margin-top: 0;
4545
}
4646

4747
.tabGroup {
48-
margin-bottom: 1rem;
48+
margin-bottom: 1rem;
4949
}
5050

51-
.tabGroup section[role="tabpanel"] {
52-
border: none;
53-
padding: 0px;
54-
}
51+
.tabGroup section[role="tabpanel"] {
52+
border: none;
53+
padding: 0px;
54+
}
5555

56-
.tabGroup section[role="tabpanel"] > pre {
57-
margin-left: 0;
58-
margin-right: 0;
59-
}
56+
.tabGroup section[role="tabpanel"]>pre {
57+
margin-left: 0;
58+
margin-right: 0;
59+
}
6060

61-
.tabGroup section[role="tabpanel"] > pre:last-child {
62-
margin-bottom: 0;
63-
}
61+
.tabGroup section[role="tabpanel"]>pre:last-child {
62+
margin-bottom: 0;
63+
}
6464

65-
.tabGroup .alert {
66-
margin-bottom: 0;
67-
}
65+
.tabGroup .alert {
66+
margin-bottom: 0;
67+
}
6868

6969
pre {
70-
border-radius: 4px;
71-
border: none;
70+
border-radius: 4px;
71+
border: none;
7272
}
7373

74-
pre code {
75-
white-space: pre;
76-
}
74+
pre code {
75+
white-space: pre;
76+
}
7777

7878
code[name] {
79-
display: block;
80-
position: relative;
81-
}
82-
83-
code[name]:before {
84-
content: attr(name);
85-
position: absolute;
86-
top: 0;
87-
right: 0;
88-
font-style: italic;
89-
text-align: right;
90-
color: #c5c5c5;
91-
background: rgba(255,255,255, 0.7);
92-
border-radius: 4px;
93-
padding: 0 6px;
94-
}
79+
display: block;
80+
position: relative;
81+
}
82+
83+
code[name]:before {
84+
content: attr(name);
85+
position: absolute;
86+
top: 0;
87+
right: 0;
88+
font-style: italic;
89+
text-align: right;
90+
color: #c5c5c5;
91+
background: rgba(255, 255, 255, 0.7);
92+
border-radius: 4px;
93+
padding: 0 6px;
94+
}
9595

9696
@media (max-width: 900px) {
97-
code[name] {
98-
padding-top: 1.5em;
99-
}
97+
code[name] {
98+
padding-top: 1.5em;
99+
}
100100
}
101101

102-
.alert-info code,
103-
.alert-info a.xref {
104-
background-color: inherit;
105-
color: #131ed2;
102+
.alert-info code, .alert-info a.xref {
103+
background-color: inherit;
104+
color: #131ed2;
106105
}
107106

108107
a.xref[href*="/api/"] {
109-
font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
110-
padding: 2px 4px;
111-
font-size: 90%;
112-
color: #c7254e;
113-
background-color: #f9f2f4;
114-
border-radius: 4px;
115-
}
116-
117-
a.xref[href*="/api/"]:hover {
118-
background-color: inherit;
119-
text-decoration: underline;
120-
}
108+
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
109+
padding: 2px 4px;
110+
font-size: 90%;
111+
color: #337ab7;
112+
background-color: #f1f2f3;
113+
border-radius: 4px;
114+
}
115+
116+
a.xref[href*="/api/"]:hover {
117+
background-color: inherit;
118+
text-decoration: underline;
119+
}
120+
121+
a code {
122+
text-decoration: none;
123+
}
124+
125+
a code:hover {
126+
background-color: inherit;
127+
text-decoration: underline;
128+
}
129+
130+
h1 a h2 a, h3 a, h4 a, h5 a, h6 a {
131+
color: rgb(51, 51, 51);
132+
background-color: transparent;
133+
}
121134

122135
footer small {
123136
font-size: 0.9rem;

0 commit comments

Comments
 (0)