Skip to content

Commit

Permalink
XUnit V3 (#2486)
Browse files Browse the repository at this point in the history
  • Loading branch information
VladislavAntonyuk authored Feb 3, 2025
1 parent 3a373e0 commit fa4004d
Show file tree
Hide file tree
Showing 49 changed files with 425 additions and 302 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ env:
LATEST_NET_VERSION: '9.0.x'
PathToCommunityToolkitAnalyzersBenchmarkCsproj: 'src/CommunityToolkit.Maui.Analyzers.Benchmarks/CommunityToolkit.Maui.Analyzers.Benchmarks.csproj'

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
run_benchmarks:
name: Run Benchmarks
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-15]

Expand Down
30 changes: 21 additions & 9 deletions .github/workflows/dotnet-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ env:
PathToCommunityToolkitMediaElementCsproj: 'src/CommunityToolkit.Maui.MediaElement/CommunityToolkit.Maui.MediaElement.csproj'
PathToCommunityToolkitMapsCsproj: 'src/CommunityToolkit.Maui.Maps/CommunityToolkit.Maui.Maps.csproj'
PathToCommunityToolkitSampleCsproj: 'samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.csproj'
PathToCommunityToolkitUnitTestCsproj: 'src/CommunityToolkit.Maui.UnitTests/CommunityToolkit.Maui.UnitTests.csproj'
PathToCommunityToolkitUnitTestCsproj: 'src/CommunityToolkit.Maui.UnitTests'
PathToCommunityToolkitAnalyzersCsproj: 'src/CommunityToolkit.Maui.Analyzers/CommunityToolkit.Maui.Analyzers.csproj'
PathToCommunityToolkitCameraAnalyzersCsproj: 'src/CommunityToolkit.Maui.Camera.Analyzers/CommunityToolkit.Maui.Camera.Analyzers.csproj'
PathToCommunityToolkitMediaElementAnalyzersCsproj: 'src/CommunityToolkit.Maui.MediaElement.Analyzers/CommunityToolkit.Maui.MediaElement.Analyzers.csproj'
Expand All @@ -41,16 +41,21 @@ env:
PathToCommunityToolkitAnalyzersCodeFixCsproj: 'src/CommunityToolkit.Maui.Analyzers.CodeFixes/CommunityToolkit.Maui.Analyzers.CodeFixes.csproj'
PathToCommunityToolkitCameraAnalyzersCodeFixCsproj: 'src/CommunityToolkit.Maui.Camera.Analyzers.CodeFixes/CommunityToolkit.Maui.Camera.Analyzers.CodeFixes.csproj'
PathToCommunityToolkitMediaElementAnalyzersCodeFixCsproj: 'src/CommunityToolkit.Maui.MediaElement.Analyzers.CodeFixes/CommunityToolkit.Maui.MediaElement.Analyzers.CodeFixes.csproj'
PathToCommunityToolkitAnalyzersUnitTestCsproj: 'src/CommunityToolkit.Maui.Analyzers.UnitTests/CommunityToolkit.Maui.Analyzers.UnitTests.csproj'
PathToCommunityToolkitAnalyzersUnitTestCsproj: 'src/CommunityToolkit.Maui.Analyzers.UnitTests'
PathToCommunityToolkitAnalyzersBenchmarkCsproj: 'src/CommunityToolkit.Maui.Analyzers.Benchmarks/CommunityToolkit.Maui.Analyzers.Benchmarks.csproj'
CommunityToolkitSampleApp_Xcode_Version: '16.2'
CommunityToolkitLibrary_Xcode_Version: '16.2'

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
build_sample:
name: Build Sample App using Latest .NET SDK
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-15]
steps:
Expand Down Expand Up @@ -90,9 +95,8 @@ jobs:
build_library:
name: Build Library
runs-on: ${{ matrix.os }}
env:
VSTEST_TESTHOST_SHUTDOWN_TIMEOUT: 1100 # Fixes "The active test run was aborted. Reason: Test host process crashed"
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-15]
steps:
Expand Down Expand Up @@ -189,16 +193,24 @@ jobs:
- name: 'Build CommunityToolkit.Maui'
run: dotnet build ${{ env.PathToLibrarySolution }} -c Release -p:PackageVersion=${{ env.NugetPackageVersion }} -p:Version=${{ env.NugetPackageVersion }}

- name: Run All Unit Tests
run: dotnet test -c Release ${{ env.PathToLibrarySolution }} --settings ".runsettings" --collect "XPlat code coverage" --logger trx --results-directory ${{ runner.temp }} --logger GitHubActions
- name: Run CommunityToolkit Analyzers UnitTests
run: |
cd ${{ env.PathToCommunityToolkitAnalyzersUnitTestCsproj }}
dotnet run -c Release --results-directory "${{ runner.temp }}" --coverage --coverage-output "${{ runner.temp }}/ut-analyzers.cobertura.xml" --coverage-output-format cobertura --report-xunit
- name: Run CommunityToolkit UnitTests
run: |
cd ${{ env.PathToCommunityToolkitUnitTestCsproj }}
dotnet run -c Release --results-directory "${{ runner.temp }}" --coverage --coverage-output "${{ runner.temp }}/ut.cobertura.xml" --coverage-output-format cobertura --report-xunit
- name: Publish Test Results
if: runner.os == 'Windows'
if: runner.os == 'Windows' && (${{ success() || failure() }})
uses: actions/upload-artifact@v4
with:
name: Test Results
name: Test Results ${{ github.run_number }} ${{ runner.os }}
path: |
${{ runner.temp }}/**/*.trx
${{ runner.temp }}/*.xunit
${{ runner.temp }}/*cobertura.xml
- name: Pack CommunityToolkit.Maui.Core NuGet
run: dotnet pack -c Release ${{ env.PathToCommunityToolkitCoreCsproj }} -p:PackageVersion=${{ env.NugetPackageVersion }}
Expand Down
File renamed without changes.
2 changes: 2 additions & 0 deletions samples/CommunityToolkit.Maui.Sample.sln
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3ED2C978-9DDB-48FE-8C5A-521B254F18A3}"
ProjectSection(SolutionItems) = preProject
..\.editorconfig = ..\.editorconfig
..\.github\workflows\benchmarks.yml = ..\.github\workflows\benchmarks.yml
..\Directory.Build.props = ..\Directory.Build.props
..\Directory.Build.targets = ..\Directory.Build.targets
..\.github\workflows\dotnet-build.yml = ..\.github\workflows\dotnet-build.yml
..\global.json = ..\global.json
EndProjectSection
EndProject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,35 @@

<PropertyGroup>
<TargetFramework>$(NetVersion)</TargetFramework>
<IsPackable>false</IsPackable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GF</CompilerGeneratedFilesOutputPath>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>

<OutputType>Exe</OutputType>
<RootNamespace>CommunityToolkit.Maui.Analyzers.UnitTests</RootNamespace>

<TestingPlatformDotnetTestSupport>false</TestingPlatformDotnetTestSupport>
<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="8.0.1" />
<PackageReference Include="FluentAssertions.Analyzers" Version="0.34.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="1.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1" PrivateAssets="All" />
<PackageReference Include="coverlet.collector" Version="6.0.4" PrivateAssets="All" />
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" Version="1.1.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="1.1.2" />
<PackageReference Include="xunit.v3" Version="1.0.1" />
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" Version="17.13.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiPackageVersion)" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.4.1" PrivateAssets="All" />
</ItemGroup>

<ItemGroup>
<!--Fix vulnerabilities-->
<PackageReference Include="System.Formats.Asn1" Version="9.0.1" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json"
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal static Page GetCurrentPage(this Page currentPage)

internal record struct ParentWindow
{
static Page CurrentPage => GetCurrentPage(Application.Current?.Windows[0].Page ?? throw new InvalidOperationException($"{nameof(Page)} cannot be null."));
static Page CurrentPage => GetCurrentPage(Application.Current?.Windows[^1].Page ?? throw new InvalidOperationException($"{nameof(Page)} cannot be null."));
/// <summary>
/// Checks if the parent window is null.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ namespace {{textStyleClassMetadata.Namespace}};
public static Task<bool> TextColorTo{{textStyleClassMetadata.GenericArguments}}(this global::{{textStyleClassMetadata.Namespace}}.{{textStyleClassMetadata.ClassName}}{{textStyleClassMetadata.GenericArguments}} element, {{mauiColorFullName}} color, uint rate = 16u, uint length = 250u, Easing? easing = null, CancellationToken token = default)
{{textStyleClassMetadata.GenericConstraints}}
{
token.ThrowIfCancellationRequested();
ArgumentNullException.ThrowIfNull(element);
ArgumentNullException.ThrowIfNull(color);
Expand Down
22 changes: 11 additions & 11 deletions src/CommunityToolkit.Maui.UnitTests/Alerts/SnackbarTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public async Task SnackbarShow_CancellationTokenExpires()
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(1));

// Ensure CancellationToken expires
await Task.Delay(100, CancellationToken.None);
await Task.Delay(100, TestContext.Current.CancellationToken);

await Assert.ThrowsAsync<OperationCanceledException>(() => snackbar.Show(cts.Token));
}
Expand All @@ -61,7 +61,7 @@ public async Task SnackbarDismiss_CancellationTokenExpires()
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(1));

// Ensure CancellationToken expires
await Task.Delay(100, CancellationToken.None);
await Task.Delay(100, TestContext.Current.CancellationToken);

await Assert.ThrowsAsync<OperationCanceledException>(() => snackbar.Dismiss(cts.Token));
}
Expand All @@ -81,14 +81,14 @@ await Assert.ThrowsAsync<OperationCanceledException>(() =>
[Fact(Timeout = (int)TestDuration.Short)]
public async Task SnackbarShow_IsShownTrue()
{
await snackbar.Show(CancellationToken.None);
await snackbar.Show(TestContext.Current.CancellationToken);
Assert.True(Snackbar.IsShown);
}

[Fact(Timeout = (int)TestDuration.Short)]
public async Task SnackbarDismissed_IsShownFalse()
{
await snackbar.Dismiss(CancellationToken.None);
await snackbar.Dismiss(TestContext.Current.CancellationToken);
Assert.False(Snackbar.IsShown);
}

Expand All @@ -100,7 +100,7 @@ public async Task SnackbarShow_ShownEventRaised()
{
receivedEvents.Add(e);
};
await snackbar.Show(CancellationToken.None);
await snackbar.Show(TestContext.Current.CancellationToken);
Assert.Single(receivedEvents);
}

Expand All @@ -112,7 +112,7 @@ public async Task SnackbarDismiss_DismissedEventRaised()
{
receivedEvents.Add(e);
};
await snackbar.Dismiss(CancellationToken.None);
await snackbar.Dismiss(TestContext.Current.CancellationToken);
Assert.Single(receivedEvents);
}

Expand All @@ -125,7 +125,7 @@ public async Task VisualElement_DisplaySnackbar_ShownEventReceived()
receivedEvents.Add(e);
};
var button = new Button();
await button.DisplaySnackbar("message", token: CancellationToken.None);
await button.DisplaySnackbar("message", token: TestContext.Current.CancellationToken);
Assert.Single(receivedEvents);
}

Expand Down Expand Up @@ -210,13 +210,13 @@ public async Task SnackbarDismiss_CancellationTokenNotCancelled_NotReceiveExcept
[Fact(Timeout = (int)TestDuration.Short)]
public async Task SnackbarShow_CancellationTokenNone_NotReceiveException()
{
await snackbar.Invoking(x => x.Show(CancellationToken.None)).Should().NotThrowAsync<OperationCanceledException>();
await snackbar.Invoking(x => x.Show(TestContext.Current.CancellationToken)).Should().NotThrowAsync<OperationCanceledException>();
}

[Fact(Timeout = (int)TestDuration.Short)]
public async Task SnackbarDismiss_CancellationTokenNone_NotReceiveException()
{
await snackbar.Invoking(x => x.Dismiss(CancellationToken.None)).Should().NotThrowAsync<OperationCanceledException>();
await snackbar.Invoking(x => x.Dismiss(TestContext.Current.CancellationToken)).Should().NotThrowAsync<OperationCanceledException>();
}

[Fact]
Expand All @@ -233,8 +233,8 @@ public async Task SnackbarNullValuesThrowArgumentNullException()
});
Assert.Throws<ArgumentNullException>(() => Snackbar.Make(null));
Assert.Throws<ArgumentNullException>(() => Snackbar.Make(string.Empty, actionButtonText: null));
await Assert.ThrowsAsync<ArgumentNullException>(() => new Button().DisplaySnackbar(null, token: CancellationToken.None));
await Assert.ThrowsAsync<ArgumentNullException>(() => new Button().DisplaySnackbar(string.Empty, actionButtonText: null, token: CancellationToken.None));
await Assert.ThrowsAsync<ArgumentNullException>(() => new Button().DisplaySnackbar(null, token: TestContext.Current.CancellationToken));
await Assert.ThrowsAsync<ArgumentNullException>(() => new Button().DisplaySnackbar(string.Empty, actionButtonText: null, token: TestContext.Current.CancellationToken));
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
}
}
8 changes: 4 additions & 4 deletions src/CommunityToolkit.Maui.UnitTests/Alerts/ToastTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public async Task ToastShow_CancellationTokenExpires()
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(1));

// Ensure CancellationToken expires
await Task.Delay(100, CancellationToken.None);
await Task.Delay(100, TestContext.Current.CancellationToken);

await Assert.ThrowsAsync<OperationCanceledException>(() => toast.Show(cts.Token));
}
Expand All @@ -44,7 +44,7 @@ public async Task ToastDismiss_CancellationTokenExpires()
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(1));

// Ensure CancellationToken expires
await Task.Delay(100, CancellationToken.None);
await Task.Delay(100, TestContext.Current.CancellationToken);

await Assert.ThrowsAsync<OperationCanceledException>(() => toast.Dismiss(cts.Token));
}
Expand Down Expand Up @@ -116,13 +116,13 @@ public async Task ToastDismiss_CancellationTokenNotCancelled_NotReceiveException
[Fact(Timeout = (int)TestDuration.Short)]
public async Task ToastShow_CancellationTokenNone_NotReceiveException()
{
await toast.Invoking(x => x.Show(CancellationToken.None)).Should().NotThrowAsync<OperationCanceledException>();
await toast.Invoking(x => x.Show(TestContext.Current.CancellationToken)).Should().NotThrowAsync<OperationCanceledException>();
}

[Fact(Timeout = (int)TestDuration.Short)]
public async Task ToastDismiss_CancellationTokenNone_NotReceiveException()
{
await toast.Invoking(x => x.Dismiss(CancellationToken.None)).Should().NotThrowAsync<OperationCanceledException>();
await toast.Invoking(x => x.Dismiss(TestContext.Current.CancellationToken)).Should().NotThrowAsync<OperationCanceledException>();
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public async Task AnimateShouldThrowWithNullView()
FadeAnimation animation = new();

#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
await Assert.ThrowsAsync<ArgumentNullException>(() => animation.Animate(null, CancellationToken.None));
await Assert.ThrowsAsync<ArgumentNullException>(() => animation.Animate(null, TestContext.Current.CancellationToken));
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
}

Expand All @@ -29,10 +29,10 @@ public async Task CancellationTokenCanceled()
};
label.EnableAnimations();

await Assert.ThrowsAsync<TaskCanceledException>(() =>
await Assert.ThrowsAsync<OperationCanceledException>(async () =>
{
cts.Cancel();
return animation.Animate(label, cts.Token);
await cts.CancelAsync();
await animation.Animate(label, cts.Token);
});
}

Expand All @@ -48,7 +48,8 @@ public async Task CancellationTokenExpired()
};
label.EnableAnimations();

await Assert.ThrowsAsync<TaskCanceledException>(() => animation.Animate(label, cts.Token));
await Task.Delay(10, TestContext.Current.CancellationToken);
await Assert.ThrowsAsync<OperationCanceledException>(() => animation.Animate(label, cts.Token));
}

[Fact(Timeout = (int)TestDuration.Medium)]
Expand All @@ -62,7 +63,7 @@ public async Task AnimateShouldReturnToOriginalOpacity()
};
label.EnableAnimations();

await animation.Animate(label, CancellationToken.None);
await animation.Animate(label, TestContext.Current.CancellationToken);

label.Opacity.Should().Be(0.9);
}
Expand Down
17 changes: 7 additions & 10 deletions src/CommunityToolkit.Maui.UnitTests/BaseHandlerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ namespace CommunityToolkit.Maui.UnitTests;

public abstract class BaseHandlerTest : BaseTest
{
protected BaseHandlerTest(IReadOnlyList<Type>? servicesToRegister = null)
protected BaseHandlerTest()
{
InitializeServicesAndSetMockApplication(servicesToRegister ?? [], out var serviceProvider);
InitializeServicesAndSetMockApplication(out var serviceProvider);
ServiceProvider = serviceProvider;
}

Expand Down Expand Up @@ -41,7 +41,7 @@ protected static TViewHandler CreateViewHandler<TViewHandler>(IView view, bool d
return mockViewHandler;
}

static void InitializeServicesAndSetMockApplication(in IReadOnlyList<Type> transientServicesToRegister, out IServiceProvider serviceProvider)
static void InitializeServicesAndSetMockApplication(out IServiceProvider serviceProvider)
{
var appBuilder = MauiApp.CreateBuilder()
.UseMauiCommunityToolkit()
Expand All @@ -60,24 +60,21 @@ static void InitializeServicesAndSetMockApplication(in IReadOnlyList<Type> trans

PopupService.ClearViewModelToViewMappings();
PopupService.AddTransientPopup(mockPopup, mockPageViewModel, appBuilder.Services);
var page = new ContentPage();
#endregion

foreach (var service in transientServicesToRegister)
{
appBuilder.Services.AddTransient(service);
}

var mauiApp = appBuilder.Build();

var application = (MockApplication)mauiApp.Services.GetRequiredService<IApplication>();
application.AddWindow(new Window());
application.AddWindow(new Window() { Page = page });
serviceProvider = mauiApp.Services;

IPlatformApplication.Current = application;

application.Handler = new ApplicationHandlerStub();
application.Handler.SetMauiContext(new HandlersContextStub(mauiApp.Services));
application.Handler.SetMauiContext(new HandlersContextStub(serviceProvider));

CreateElementHandler<MockPopupHandler>(mockPopup);
CreateViewHandler<MockPageHandler>(page);
}
}
Loading

0 comments on commit fa4004d

Please sign in to comment.