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
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
Loading