Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"rollForward": false
},
"docfx": {
"version": "2.77.0",
"version": "2.78.2",
"commands": [
"docfx"
],
Expand Down
4 changes: 2 additions & 2 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
{
"label": "Serve Docs (Without Build)",
"type": "shell",
"command": "docfx metadata docs/site/docfx.json && docfx docs/site/docfx.json --serve"
"command": "dotnet docfx metadata docs/site/docfx.json && dotnet docfx docs/site/docfx.json --serve"
},
{
"label": "Serve Docs (With Build for API Documentation)",
"type": "shell",
"command": "dotnet build -c Release && docfx metadata docs/site/docfx.json && docfx docs/site/docfx.json --serve"
"command": "dotnet build -c Release && dotnet docfx metadata docs/site/docfx.json && docfx docs/site/docfx.json --serve"
},
{
"label": "Run all tests (Release Mode)",
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ All notable changes to **bUnit** will be documented in this file. The project ad

## [Unreleased]

### Added
- Added support for `RendererInfo` and `AssignedRenderMode` (`.net9.0`).

## [1.36.0] - 2024-11-12

### Added
Expand Down
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>CA1014,NU5104,NETSDK1138,SYSLIB0051</NoWarn>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>

<!-- Used by code coverage -->
<DebugType>full</DebugType>
Expand Down
46 changes: 23 additions & 23 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<GlobalPackageReference Include="AsyncFixer" Version="1.6.0" PrivateAssets="All" IncludeAssets="Runtime;Build;Native;contentFiles;Analyzers"/>
<GlobalPackageReference Include="SonarAnalyzer.CSharp" Version="9.32.0.97167" PrivateAssets="All" IncludeAssets="Runtime;Build;Native;contentFiles;Analyzers"/>
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
<PackageVersion Include="Meziantou.Polyfill" Version="1.0.40" />
<PackageVersion Include="Meziantou.Polyfill" Version="1.0.42" />
</ItemGroup>

<ItemGroup Label="Shared">
Expand All @@ -28,7 +28,7 @@

<ItemGroup Label="System.Text.Json Vulnerability">
<!-- Due to a CVE in System.Text.Json we explicitly reference the latest version of System.Text.Json -->
<PackageVersion Include="System.Text.Json" Version="8.0.5"/>
<PackageVersion Include="System.Text.Json" Version="9.0.0"/>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
Expand Down Expand Up @@ -58,16 +58,16 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0"/>
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.1"/>
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.4"/>
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="6.0.33"/>

<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.33"/>
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="6.0.33"/>
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.2"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="6.0.33"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.33"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.33"/>
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="6.0.36"/>

<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.36"/>
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="6.0.36"/>
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="6.0.3"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="6.0.36"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.36"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.36"/>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
Expand All @@ -86,14 +86,14 @@
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.1"/>
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2"/>
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="8.0.10"/>
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="8.0.11"/>

<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.10"/>
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.10"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.11"/>
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.11"/>
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="8.0.10"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.10"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.10"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="8.0.11"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.11"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.11"/>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
Expand All @@ -103,7 +103,7 @@

<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.0"/>
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="9.0.0"/>
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0-rc.2.24473.5"/>
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="9.0.0"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0"/>
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0"/>
Expand All @@ -122,7 +122,7 @@
<PackageVersion Include="Serilog.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Shouldly" Version="4.2.1"/>
<PackageVersion Include="Verify.SourceGenerators" Version="2.5.0"/>
<PackageVersion Include="Verify.Xunit" Version="28.1.0"/>
<PackageVersion Include="Verify.Xunit" Version="28.4.0"/>
<PackageVersion Include="Xunit.Combinatorial" Version="1.6.24"/>
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.2" />
Expand All @@ -140,10 +140,10 @@

<ItemGroup Label="Source Code Generators">
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0"/>
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.11.0"/>
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.11.0"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.11.0"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0"/>
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.12.0"/>
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.12.0"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.2" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions docs/site/docs/interaction/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ This section covers the various ways to interact with a component under test, e.
- **<xref:trigger-renders>:** This covers how to manually trigger a render cycle for a component under test.
- **<xref:awaiting-async-state>:** This covers how to await one or more asynchronous changes to the state of a component under test before continuing the test.
- **<xref:dispose-components>:** This covers how to dispose components and their children.
- **<xref:render-modes>:** This covers the different render modes and their interaction with bUnit.
157 changes: 157 additions & 0 deletions docs/site/docs/interaction/render-modes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
---
uid: render-modes
title: Render modes and RendererInfo
---

# Support for render modes and `RendererInfo`
This article explains how to emulate different render modes and `RendererInfo` in bUnit tests.

Render modes in Blazor Web Apps determine the hosting model and interactivity of components. A render mode can be applied to a component using the `@rendermode` directive. The [`RendererInfo`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.rendererinfo?view=aspnetcore-9.0) allows the application to determine the interactivity and location of the component. For more details, see the [Blazor render modes](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-9.0) documentation.

## Setting the render mode for a component under test
Setting the render mode can be done via the <xref:Bunit.ComponentParameterCollectionBuilder`1.SetAssignedRenderMode(Microsoft.AspNetCore.Components.IComponentRenderMode)> method when writing in a C# file. In a razor file use the `@rendermode` directive. Both take an [`IComponentRenderMode`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.icomponentrendermode?view=aspnetcore-9.0) object as a parameter. Normally this is one of the following types:
* [`InteractiveAutoRenderMode`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.web.interactiveautorendermode?view=aspnetcore-9.0)
* [`InteractiveServerRendeMode`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.web.interactiveserverrendermode?view=aspnetcore-9.0)
* [`InteractiveWebAssemblyRenderMode`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.web.interactivewebassemblyrendermode?view=aspnetcore-9.0)

For ease of use the [`RenderMode`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.web.rendermode?view=aspnetcore-9.0) class defines all three of them.

For example `MovieComponent.razor`:
```razor
@if (AssignedRenderMode is null)
{
// The render mode is Static Server
<form action="/movies">
<input type="text" name="titleFilter" />
<input type="submit" value="Search" />
</form>
}
else
{
// The render mode is Interactive Server, WebAssembly, or Auto
<input @bind="titleFilter" />
<button @onclick="FilterMovies">Search</button>
}
```

The following example shows how to test the above component to check both render modes:

# [C# test code](#tab/csharp)

```csharp
[Fact]
public void InteractiveServer()
{
// Act
var cut = RenderComponent<MovieComponent>(ps => ps
.SetAssignedRenderMode(RenderMode.InteractiveServer));

// Assert
cut.MarkupMatches("""
<input diff:ignoreAttributes />
<button>Search</button>
""");
}

[Fact]
public void StaticRendering()
{
// Act
var cut = RenderComponent<MovieComponent>();
// This is the same behavior as:
// var cut = RenderComponent<MovieComponent>(ps => ps
// .SetAssignedRenderMode(null));

// Assert
cut.MarkupMatches("""
<form action="/movies">
<input type="text" name="titleFilter" />
<input type="submit" value="Search" />
</form>
""");
}
```

# [Razor test code](#tab/razor)

```razor
@inherits TestContext
@code {
[Fact]
public void InteractiveServer()
{
// Act
var cut = Render(@<MovieComponent @rendermode="RenderMode.InteractiveServer" />);

// Assert
cut.MarkupMatches(@<text>
<input diff:ignoreAttributes />
<button>Search</button>
</text>);
}

[Fact]
public void StaticRendering()
{
// Act
var cut = Render(@<MovieComponent />);

// Assert
cut.MarkupMatches(@<form action="/movies">
<input type="text" name="titleFilter" />
<input type="submit" value="Search" />
</form>);
}
}
```

***

## Setting the `RendererInfo` during testing
To control the `ComponentBase.RendererInfo` property during testing, use the <xref:Bunit.TestContextBase.SetRendererInfo(Microsoft.AspNetCore.Components.RendererInfo)> method on the `TestContext` class. The `SetRendererInfo` method takes an nullable `RendererInfo` object as a parameter. Passing `null` will set the `ComponentBase.RendererInfo` to `null`.

A component (`AssistentComponent.razor`) might check if interactivity is given to enable a button:

```razor
@if (RendererInfo.IsInteractive)
{
<p>Hey I am your assistant</p>
}
else
{
<p>Loading...</p>
}
```

In the test, you can set the `RendererInfo` to enable or disable the button:

```csharp
[Fact]
public void SimulatingPreRenderingOnBlazorServer()
{
// Arrange
SetRendererInfo(new RendererInfo(rendererName: "Static", isInteractive: false));

// Act
var cut = RenderComponent<AssistentComponent>();

// Assert
cut.MarkupMatches("<p>Loading...</p>");
}

[Fact]
public void SimulatingInteractiveServerRendering()
{
// Arrange
SetRendererInfo(new RendererInfo(rendererMode: "Server", isInteractive: true));

// Act
var cut = RenderComponent<AssistentComponent>();

// Assert
cut.MarkupMatches("<p>Hey I am your assistant</p>");
}
```

> [!NOTE]
> If a component under test uses the `ComponentBase.RendererInfo` property and the `SetRendererInfo` on `TestContext` hasn't been passed in a `RendererInfo` object, the renderer will throw an exception.
1 change: 1 addition & 0 deletions docs/site/docs/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
## [Trigger renders](xref:trigger-renders)
## [Awaiting an async state change](xref:awaiting-async-state)
## [Disposing components](xref:dispose-components)
## [Render modes and RendererInfo](xref:render-modes)

# [Verifying output](xref:verification)
## [Verify markup](xref:verify-markup)
Expand Down
11 changes: 11 additions & 0 deletions src/bunit.core/ComponentParameterCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ public class ComponentParameterCollection : ICollection<ComponentParameter>, IRe
/// <inheritdoc />
public bool IsReadOnly { get; }

#if NET9_0_OR_GREATER
/// <summary>
/// Gets or sets the <see cref="IComponentRenderMode"/> that will be specified in
/// the render tree for component the parameters are being passed to.
/// </summary>
public IComponentRenderMode? RenderMode { get; set; }
#endif

/// <summary>
/// Adds a <paramref name="item"/> to the collection.
/// </summary>
Expand Down Expand Up @@ -104,6 +112,9 @@ void AddComponent(RenderTreeBuilder builder)
{
builder.OpenComponent<TComponent>(0);
AddAttributes(builder);
#if NET9_0_OR_GREATER
builder.AddComponentRenderMode(RenderMode);
#endif
builder.CloseComponent();
}

Expand Down
Loading
Loading