Skip to content

Commit 90e3eae

Browse files
authored
Merge pull request #1819 from bUnit-dev/release/v2.7
2 parents 2395161 + 4a1f28b commit 90e3eae

File tree

11 files changed

+155
-22
lines changed

11 files changed

+155
-22
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ jobs:
7878
dotnet pack src/bunit.generators/ -c release --output ${{ env.NUGET_DIRECTORY }} -p:ContinuousIntegrationBuild=true -p:publicrelease=true
7979
8080
# Publish the NuGet package as an artifact, so they can be used in the following jobs
81-
- uses: actions/upload-artifact@v6
81+
- uses: actions/upload-artifact@v7
8282
with:
8383
name: ${{ env.NUGET_PACKAGES_ARTIFACT }}
8484
if-no-files-found: error
@@ -93,7 +93,7 @@ jobs:
9393
- name: Setup .NET
9494
uses: actions/setup-dotnet@v5
9595

96-
- uses: actions/download-artifact@v7
96+
- uses: actions/download-artifact@v8
9797
with:
9898
name: ${{ env.NUGET_PACKAGES_ARTIFACT }}
9999
path: ${{ env.NUGET_DIRECTORY }}
@@ -141,7 +141,7 @@ jobs:
141141

142142
- name: 📛 Upload hang- and crash-dumps on test failure
143143
if: success() || failure()
144-
uses: actions/upload-artifact@v6
144+
uses: actions/upload-artifact@v7
145145
with:
146146
if-no-files-found: ignore
147147
name: test-dumps
@@ -163,7 +163,7 @@ jobs:
163163
dotnet-version: |
164164
10.0.x
165165
166-
- uses: actions/download-artifact@v7
166+
- uses: actions/download-artifact@v8
167167
with:
168168
name: ${{ env.NUGET_PACKAGES_ARTIFACT }}
169169
path: ${{ env.NUGET_DIRECTORY }}
@@ -278,7 +278,7 @@ jobs:
278278
permissions:
279279
id-token: write
280280
steps:
281-
- uses: actions/download-artifact@v7
281+
- uses: actions/download-artifact@v8
282282
with:
283283
name: ${{ env.NUGET_PACKAGES_ARTIFACT }}
284284
path: ${{ env.NUGET_DIRECTORY }}

.github/workflows/docs-deploy.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737

3838
- name: ⚙️ Import GPG key
3939
id: import_gpg
40-
uses: crazy-max/ghaction-import-gpg@v6
40+
uses: crazy-max/ghaction-import-gpg@v7
4141
with:
4242
gpg_private_key: ${{ secrets.BUNIT_BOT_GPG_PRIVATE_KEY }}
4343
passphrase: ${{ secrets.BUNIT_BOT_GPG_KEY_PASSPHRASE }}
@@ -98,7 +98,7 @@ jobs:
9898
9999
- name: 🛠️ Deploy to GitHub Pages
100100
if: success()
101-
uses: crazy-max/ghaction-github-pages@v4
101+
uses: crazy-max/ghaction-github-pages@v5
102102
with:
103103
build_dir: docs/site/_site
104104
fqdn: bunit.dev

.github/workflows/prepare-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
4848
- name: ⚙️ Import GPG key
4949
id: import_gpg
50-
uses: crazy-max/ghaction-import-gpg@v6
50+
uses: crazy-max/ghaction-import-gpg@v7
5151
with:
5252
gpg_private_key: ${{ secrets.BUNIT_BOT_GPG_PRIVATE_KEY }}
5353
passphrase: ${{ secrets.BUNIT_BOT_GPG_KEY_PASSPHRASE }}

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141

4242
- name: ⚙️ Import GPG key
4343
id: import_gpg
44-
uses: crazy-max/ghaction-import-gpg@v6
44+
uses: crazy-max/ghaction-import-gpg@v7
4545
with:
4646
gpg_private_key: ${{ secrets.BUNIT_BOT_GPG_PRIVATE_KEY }}
4747
passphrase: ${{ secrets.BUNIT_BOT_GPG_KEY_PASSPHRASE }}

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ All notable changes to **bUnit** will be documented in this file. The project ad
66

77
## [Unreleased]
88

9+
### Fixed
10+
11+
- Implemented `InvokeConstructorAsync` on `BunitJSRuntime` and `BunitJSObjectReference` for .NET 10+, which previously threw `NotImplementedException`. Reported by [@Floopy-Doo](https://github.com/Floopy-Doo) in #1818. Fixed by [@linkdotnet](https://github.com/linkdotnet).
12+
913
## [2.6.2] - 2026-02-27
1014

1115
### Added

Directory.Packages.props

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,18 @@
7272
</ItemGroup>
7373

7474
<ItemGroup Condition="'$(TargetFramework)' == 'net11.0'">
75-
<PackageVersion Include="Microsoft.Extensions.Logging" Version="11.0.0-preview.1.26104.118"/>
76-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="11.0.0-preview.1.26104.118"/>
77-
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="11.0.0-preview.1.26104.118"/>
78-
79-
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="11.0.0-preview.1.26104.118"/>
80-
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="11.0.0-preview.1.26104.118"/>
81-
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="11.0.0-preview.1.26104.118"/>
82-
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="11.0.0-preview.1.26104.118"/>
83-
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="11.0.0-preview.1.26104.118"/>
84-
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="11.0.0-preview.1.26104.118"/>
85-
86-
<PackageVersion Include="System.Text.Json" Version="11.0.0-preview.1.26104.118"/>
75+
<PackageVersion Include="Microsoft.Extensions.Logging" Version="11.0.0-preview.2.26159.112"/>
76+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="11.0.0-preview.2.26159.112"/>
77+
<PackageVersion Include="Microsoft.AspNetCore.Components" Version="11.0.0-preview.2.26159.112"/>
78+
79+
<PackageVersion Include="Microsoft.AspNetCore.Components.Authorization" Version="11.0.0-preview.2.26159.112"/>
80+
<PackageVersion Include="Microsoft.Extensions.Localization.Abstractions" Version="11.0.0-preview.2.26159.112"/>
81+
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="11.0.0-preview.2.26159.112"/>
82+
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="11.0.0-preview.2.26159.112"/>
83+
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="11.0.0-preview.2.26159.112"/>
84+
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="11.0.0-preview.2.26159.112"/>
85+
86+
<PackageVersion Include="System.Text.Json" Version="11.0.0-preview.2.26159.112"/>
8787
</ItemGroup>
8888

8989
<ItemGroup Label="Test Dependencies">

src/bunit/JSInterop/Implementation/BunitJSObjectReference.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToke
2525

2626
#if NET10_0_OR_GREATER
2727
/// <inheritdoc/>
28+
public ValueTask<IJSObjectReference> InvokeConstructorAsync(string identifier, object?[]? args)
29+
=> JSInterop.HandleInvokeConstructorAsync(identifier, args);
30+
31+
/// <inheritdoc/>
32+
public ValueTask<IJSObjectReference> InvokeConstructorAsync(string identifier, CancellationToken cancellationToken, object?[]? args)
33+
=> JSInterop.HandleInvokeConstructorAsync(identifier, cancellationToken, args);
2834

2935
/// <inheritdoc/>
3036
public ValueTask<TValue> GetValueAsync<TValue>(string identifier) => throw new NotImplementedException();
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#if NET10_0_OR_GREATER
2+
using Bunit.JSInterop.Implementation;
3+
4+
namespace Bunit.JSInterop;
5+
6+
/// <summary>
7+
/// bUnit's implementation of the <c>InvokeConstructorAsync</c> methods on <see cref="IJSRuntime"/>.
8+
/// </summary>
9+
internal sealed partial class BunitJSRuntime
10+
{
11+
/// <inheritdoc/>
12+
ValueTask<IJSObjectReference> IJSRuntime.InvokeConstructorAsync(string identifier, object?[]? args)
13+
=> JSInterop.HandleInvokeConstructorAsync(identifier, args);
14+
15+
/// <inheritdoc/>
16+
ValueTask<IJSObjectReference> IJSRuntime.InvokeConstructorAsync(string identifier, CancellationToken cancellationToken, object?[]? args)
17+
=> JSInterop.HandleInvokeConstructorAsync(identifier, cancellationToken, args);
18+
}
19+
#endif

src/bunit/JSInterop/Implementation/JSRuntimeExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,21 @@ internal static TResult HandleInvokeUnmarshalled<T0, T1, T2, TResult>(this Bunit
8585
.GetResult();
8686
}
8787

88+
#if NET10_0_OR_GREATER
89+
internal static ValueTask<IJSObjectReference> HandleInvokeConstructorAsync(this BunitJSInterop jSInterop, string identifier, object?[]? args)
90+
{
91+
var invocation = new JSRuntimeInvocation(identifier, null, args, typeof(IJSObjectReference), "InvokeConstructorAsync");
92+
return jSInterop.HandleInvocation<IJSObjectReference>(invocation);
93+
}
94+
95+
[SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Matching Blazor's JSRuntime design.")]
96+
internal static ValueTask<IJSObjectReference> HandleInvokeConstructorAsync(this BunitJSInterop jSInterop, string identifier, CancellationToken cancellationToken, object?[]? args)
97+
{
98+
var invocation = new JSRuntimeInvocation(identifier, cancellationToken, args, typeof(IJSObjectReference), "InvokeConstructorAsync");
99+
return jSInterop.HandleInvocation<IJSObjectReference>(invocation);
100+
}
101+
#endif
102+
88103
private static string GetInvokeAsyncMethodName<TValue>()
89104
=> typeof(TValue) == typeof(Microsoft.JSInterop.Infrastructure.IJSVoidResult)
90105
? "InvokeVoidAsync"

tests/bunit.tests/JSInterop/BunitJSInteropTest.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,4 +702,93 @@ public async Task Test309()
702702
var exception = await Should.ThrowAsync<JSRuntimeInvocationNotSetException>(invocationTask.AsTask());
703703
exception.Invocation.Identifier.ShouldBe(identifier);
704704
}
705+
706+
#if NET10_0_OR_GREATER
707+
[Fact(DisplayName = "InvokeConstructorAsync returns IJSObjectReference in loose mode without setup")]
708+
public async Task Test400()
709+
{
710+
var sut = CreateSut(JSRuntimeMode.Loose);
711+
712+
var result = await sut.JSRuntime.InvokeConstructorAsync("SomeClass");
713+
714+
result.ShouldNotBeNull();
715+
result.ShouldBeAssignableTo<IJSObjectReference>();
716+
}
717+
718+
[Fact(DisplayName = "InvokeConstructorAsync throws in strict mode when no handler is set up")]
719+
public void Test401()
720+
{
721+
var sut = CreateSut(JSRuntimeMode.Strict);
722+
723+
Should.Throw<JSRuntimeUnhandledInvocationException>(
724+
async () => await sut.JSRuntime.InvokeConstructorAsync("SomeClass"));
725+
}
726+
727+
[Theory(DisplayName = "InvokeConstructorAsync records invocation with correct method name and arguments"), AutoData]
728+
public void Test402(string identifier)
729+
{
730+
var args = new object[] { "arg1", 42 };
731+
var sut = CreateSut(JSRuntimeMode.Loose);
732+
733+
sut.JSRuntime.InvokeConstructorAsync(identifier, args);
734+
735+
var invocation = sut.Invocations[identifier].ShouldHaveSingleItem();
736+
invocation.Identifier.ShouldBe(identifier);
737+
invocation.Arguments.ShouldBe(args);
738+
invocation.InvocationMethodName.ShouldBe("InvokeConstructorAsync");
739+
invocation.ResultType.ShouldBe(typeof(IJSObjectReference));
740+
}
741+
742+
[Theory(DisplayName = "InvokeConstructorAsync with CancellationToken records invocation correctly"), AutoData]
743+
public void Test403(string identifier)
744+
{
745+
var args = new object[] { "arg1" };
746+
using var cts = new CancellationTokenSource();
747+
var sut = CreateSut(JSRuntimeMode.Loose);
748+
749+
sut.JSRuntime.InvokeConstructorAsync(identifier, cts.Token, args);
750+
751+
var invocation = sut.Invocations[identifier].ShouldHaveSingleItem();
752+
invocation.Identifier.ShouldBe(identifier);
753+
invocation.Arguments.ShouldBe(args);
754+
invocation.CancellationToken.ShouldBe(cts.Token);
755+
invocation.InvocationMethodName.ShouldBe("InvokeConstructorAsync");
756+
}
757+
758+
[Fact(DisplayName = "InvokeConstructorAsync with SetupModule handler returns configured object reference")]
759+
public async Task Test404()
760+
{
761+
var sut = CreateSut(JSRuntimeMode.Strict);
762+
sut.SetupModule(inv => inv.Identifier == "SomeClass" && inv.InvocationMethodName == "InvokeConstructorAsync");
763+
764+
var result = await sut.JSRuntime.InvokeConstructorAsync("SomeClass");
765+
766+
result.ShouldNotBeNull();
767+
result.ShouldBeAssignableTo<IJSObjectReference>();
768+
}
769+
770+
[Fact(DisplayName = "InvokeConstructorAsync on IJSObjectReference from module import works in loose mode")]
771+
public async Task Test405()
772+
{
773+
var sut = CreateSut(JSRuntimeMode.Loose);
774+
775+
var module = await sut.JSRuntime.InvokeAsync<IJSObjectReference>("import", "./myModule.js");
776+
var result = await module.InvokeConstructorAsync("JsClass", "arg1", "arg2");
777+
778+
result.ShouldNotBeNull();
779+
result.ShouldBeAssignableTo<IJSObjectReference>();
780+
}
781+
782+
[Fact(DisplayName = "InvokeConstructorAsync on IJSObjectReference records invocation")]
783+
public async Task Test406()
784+
{
785+
var sut = CreateSut(JSRuntimeMode.Loose);
786+
787+
var module = await sut.JSRuntime.InvokeAsync<IJSObjectReference>("import", "./myModule.js");
788+
await module.InvokeConstructorAsync("JsClass", "arg1");
789+
790+
sut.Invocations["JsClass"].ShouldHaveSingleItem()
791+
.InvocationMethodName.ShouldBe("InvokeConstructorAsync");
792+
}
793+
#endif
705794
}

0 commit comments

Comments
 (0)