## What Brings AppKit into conformance with [KofTwentyTwo/standards](https://github.com/KofTwentyTwo/standards). Reviewable commit by commit: | Commit | Change | | --- | --- | | build | Quality gate from `configs/dotnet` (analyzers incl. Meziantou, warnings as errors), `packages.lock.json` per project, locked-mode restore | | style | Kingsrook layout and LF line endings, produced mechanically by `dotnet format` (formatting only) | | docs | SPDX license header on every C# file | | refactor | Real code changes: analyzer findings fixed (one type per file, regex timeouts, async void, …) and header comments on every type and member | | ci | Old workflows replaced by callers of the shared standards workflows (PR gates, security, CodeQL, .NET CI, NuGet release with SBOM + attestation + trusted publishing), Scorecard | | docs | README, CONTRIBUTING (GitHub Flow, DCO, signing), SECURITY, CODEOWNERS, templates, Dependabot, threat model, ADRs, RELEASING | Also includes `dev`'s CI fix, so it supersedes #1; the `dev` branch goes away under GitHub Flow. ## Why AppKit is the first Product repository to adopt the standards. Static conformance before: 16 FAIL; after: 0 FAIL, 0 WARN (the remaining UNKNOWN rows are GitHub settings and rulesets, applied separately). ## How tested Locally with `CI=true` (lock files enforced): locked restore x64 and ARM64; zero-warning x64 build; 115 unit tests with 100% line coverage of `KofTwentyTwo.AppKit` and `KofTwentyTwo.AppKit.Updates`; 12 FlaUI UI tests (WinUI + WPF samples); `dotnet format --verify-no-changes --severity warn` clean; pack produces 4 packages + symbols; actionlint, zizmor (pedantic), gitleaks clean. The UI tests caught a regression introduced while fixing MA0147 (the WPF splash stopped dismissing because no UI context existed yet); fixed by starting the countdown through the dispatcher in both packages. ## Notes - `ci.yml` and `release.yml` are pinned to KofTwentyTwo/standards#3's head commit; they are repinned after that PR merges. - This pull request's new check names become required checks once the repository's rulesets are updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: James Maes <james@kof22.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
3.8 KiB
C#
116 lines
3.8 KiB
C#
/*
|
|
* Copyright (c) 2026 James Maes (KofTwentyTwo)
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
using FlaUI.Core.AutomationElements;
|
|
|
|
|
|
namespace KofTwentyTwo.AppKit.UiTests;
|
|
|
|
/// <summary>
|
|
/// End-to-end smoke tests: every shell piece AppKit provides, driven through the real
|
|
/// WinUI and WPF sample apps. UI tests share one desktop, so they never run in parallel.
|
|
/// </summary>
|
|
[Collection("Desktop")]
|
|
public class SampleAppTests
|
|
{
|
|
/// <summary>Splash: shows then dismisses itself.</summary>
|
|
[Theory]
|
|
[InlineData(SampleKind.WinUI)]
|
|
[InlineData(SampleKind.Wpf)]
|
|
public void Splash_ShowsThenDismissesItself(SampleKind kind)
|
|
{
|
|
using var session = new AppSession(kind, showSplash: true);
|
|
session.WaitForElement("AppKitSplash");
|
|
session.WaitForElementGone("AppKitSplash", TimeSpan.FromSeconds(15));
|
|
}
|
|
|
|
|
|
|
|
/// <summary>About: shows the build version.</summary>
|
|
[Theory]
|
|
[InlineData(SampleKind.WinUI)]
|
|
[InlineData(SampleKind.Wpf)]
|
|
public void About_ShowsTheBuildVersion(SampleKind kind)
|
|
{
|
|
using var session = new AppSession(kind);
|
|
session.InvokeMenuItem("Help", "AboutMenuItem");
|
|
AutomationElement version = session.WaitForElement("AboutVersionText");
|
|
Assert.StartsWith("Version 0.", version.Name, StringComparison.Ordinal);
|
|
session.ClickButton("Close");
|
|
session.WaitForElementGone("AboutVersionText");
|
|
}
|
|
|
|
|
|
|
|
/// <summary>UnhandledException: is logged and the app survives.</summary>
|
|
[Theory]
|
|
[InlineData(SampleKind.WinUI)]
|
|
[InlineData(SampleKind.Wpf)]
|
|
public void UnhandledException_IsLoggedAndTheAppSurvives(SampleKind kind)
|
|
{
|
|
using var session = new AppSession(kind);
|
|
session.MouseClickElement("ThrowButton");
|
|
session.WaitFor(
|
|
() => session.ReadLogs().Contains("Deliberately unhandled sample exception", StringComparison.Ordinal) ? session.MainWindow : null,
|
|
"the crash net's log entry");
|
|
Assert.False(session.App.HasExited);
|
|
Assert.Contains("Unhandled UI exception", session.ReadLogs(), StringComparison.Ordinal);
|
|
}
|
|
|
|
|
|
|
|
/// <summary>ActivityLog: opens a window showing the log.</summary>
|
|
[Theory]
|
|
[InlineData(SampleKind.WinUI)]
|
|
[InlineData(SampleKind.Wpf)]
|
|
public void ActivityLog_OpensAWindowShowingTheLog(SampleKind kind)
|
|
{
|
|
using var session = new AppSession(kind);
|
|
session.InvokeMenuItem("Help", "ActivityLogMenuItem");
|
|
session.WaitFor(
|
|
() => session.TopLevelWindows().FirstOrDefault(w => string.Equals(w.Name, "AppKit Sample — Activity log", StringComparison.Ordinal)),
|
|
"the activity log window");
|
|
}
|
|
|
|
|
|
|
|
/// <summary>ThemeChoice: is saved.</summary>
|
|
[Theory]
|
|
[InlineData(SampleKind.WinUI)]
|
|
[InlineData(SampleKind.Wpf)]
|
|
public void ThemeChoice_IsSaved(SampleKind kind)
|
|
{
|
|
using var session = new AppSession(kind);
|
|
session.InvokeMenuItem("View", "ThemeDark");
|
|
session.WaitFor(
|
|
() => session.ReadSettings().Contains("\"Theme\": \"Dark\"", StringComparison.Ordinal) ? session.MainWindow : null,
|
|
"settings.json to record the dark theme");
|
|
}
|
|
|
|
|
|
|
|
/// <summary>CheckForUpdates: in a loose build says updates need an install.</summary>
|
|
[Theory]
|
|
[InlineData(SampleKind.WinUI)]
|
|
[InlineData(SampleKind.Wpf)]
|
|
public void CheckForUpdates_InALooseBuild_SaysUpdatesNeedAnInstall(SampleKind kind)
|
|
{
|
|
using var session = new AppSession(kind);
|
|
session.InvokeMenuItem("Help", "CheckForUpdatesMenuItem");
|
|
session.WaitFor(
|
|
() => session.FindInApp(cf => cf.ByName("Updates are only available in installed builds.")),
|
|
"the not-installed message");
|
|
session.ClickButton("OK");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>UI tests share one desktop, so the collection never runs in parallel.</summary>
|
|
[CollectionDefinition("Desktop", DisableParallelization = true)]
|
|
public class DesktopCollectionDefinition
|
|
{
|
|
}
|