using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Shouldly; using Xunit; namespace AtomUI.Generator.Tests.LinkedRegistration; public sealed class PackageEntryIntegrationTests { [Theory] [InlineData( "src/AtomUI.Desktop.Controls/ThemeManagerBuilderExtensions.cs", "ThemeManagerBuilderExtensions", "UseAllDesktopControls|UseDesktopControls")] [InlineData( "src/AtomUI.Desktop.Controls.DataGrid/ThemeManagerBuilderExtensions.cs", "DataGridThemeManagerBuilderExtensions", "UseDesktopDataGrid")] [InlineData( "src/AtomUI.Desktop.Controls.ColorPicker/ThemeManagerBuilderExtensions.cs", "ColorPickerThemeManagerBuilderExtensions", "UseDesktopColorPicker")] [InlineData( "src/AtomUI.Desktop.Controls.Extras/ThemeManagerBuilderExtensions.cs", "ExtrasThemeManagerBuilderExtensions", "UseDesktopExtras")] [InlineData( "src/AtomUI.Toolkits.GalleryBase/ThemeManagerBuilderExtensions.cs", "ThemeManagerBuilderExtensions", "UseGalleryBase")] public void Registration_packages_declare_entries_on_the_real_methods( string sourcePath, string typeName, string expectedMethodNames) { var root = CSharpSyntaxTree.ParseText( File.ReadAllText(GetRepoFile(sourcePath)), cancellationToken: TestContext.Current.CancellationToken).GetRoot( TestContext.Current.CancellationToken); var type = root.DescendantNodes() .OfType() .Single(declaration => declaration.Identifier.ValueText == typeName); var actualMethodNames = type.Members .OfType() .Where(HasRegistrationEntryAttribute) .Select(static method => method.Identifier.ValueText) .OrderBy(static name => name, StringComparer.Ordinal) .ToArray(); actualMethodNames.ShouldBe( expectedMethodNames.Split('|').OrderBy(static name => name, StringComparer.Ordinal)); } [Fact] public void Common_is_not_declared_as_a_linked_registration_package() { var projectSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Controls/AtomUI.Controls.csproj")); var legacyEntryProperty = "AtomUIRegistration" + "Entries"; projectSource.ShouldNotContain("AtomUIRegistrationPackageId"); projectSource.ShouldNotContain(legacyEntryProperty); var root = CSharpSyntaxTree.ParseText( File.ReadAllText(GetRepoFile( "src/AtomUI.Controls/ThemeManagerBuildExtensions.cs")), cancellationToken: TestContext.Current.CancellationToken).GetRoot( TestContext.Current.CancellationToken); var method = root.DescendantNodes() .OfType() .Single(declaration => declaration.Identifier.ValueText == "UseCommonControls"); HasRegistrationEntryAttribute(method).ShouldBeFalse(); } [Fact] public void Only_Desktop_Controls_Opts_Into_Directory_Registration_Granularity() { var desktopProject = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/AtomUI.Desktop.Controls.csproj")); desktopProject.ShouldContain( "Directory"); foreach (var projectPath in new[] { "src/AtomUI.Desktop.Controls.DataGrid/AtomUI.Desktop.Controls.DataGrid.csproj", "src/AtomUI.Desktop.Controls.ColorPicker/AtomUI.Desktop.Controls.ColorPicker.csproj", "src/AtomUI.Desktop.Controls.Extras/AtomUI.Desktop.Controls.Extras.csproj", "src/AtomUI.Toolkits.GalleryBase/AtomUI.Toolkits.GalleryBase.csproj" }) { File.ReadAllText(GetRepoFile(projectPath)) .ShouldNotContain("AtomUIRegistrationGranularity"); } } [Fact] public void Desktop_Directory_Registration_Uses_Stable_Button_Family_Source_Boundaries() { var desktopProject = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/AtomUI.Desktop.Controls.csproj")); desktopProject.ShouldNotContain("AtomUIRegistrationUnit"); File.Exists(GetRepoFile("src/AtomUI.Desktop.Controls/Buttons/Button.cs")).ShouldBeTrue(); File.Exists(GetRepoFile( "src/AtomUI.Desktop.Controls/DropdownButton/DropdownButton.cs")).ShouldBeTrue(); File.Exists(GetRepoFile( "src/AtomUI.Desktop.Controls/SplitButton/SplitButton.cs")).ShouldBeTrue(); File.Exists(GetRepoFile( "src/AtomUI.Desktop.Controls/Buttons/Themes/ButtonTheme.axaml")).ShouldBeTrue(); File.Exists(GetRepoFile( "src/AtomUI.Desktop.Controls/Buttons/Themes/DropdownButtonTheme.axaml")).ShouldBeTrue(); File.Exists(GetRepoFile( "src/AtomUI.Desktop.Controls/Buttons/Themes/SplitButtonTheme.axaml")).ShouldBeTrue(); } [Theory] [InlineData("src/AtomUI.Desktop.Controls.DataGrid/AtomUI.Desktop.Controls.DataGrid.csproj")] [InlineData("src/AtomUI.Desktop.Controls.ColorPicker/AtomUI.Desktop.Controls.ColorPicker.csproj")] public void Package_Granularity_Projects_Do_Not_Declare_Unit_Ownership_Patches( string projectPath) { var projectSource = File.ReadAllText(GetRepoFile(projectPath)); projectSource.ShouldNotContain("AtomUIRegistrationUnit"); } [Theory] [InlineData( "src/AtomUI.Controls/ThemeManagerBuildExtensions.cs", "ThemeManagerBuilderExtensions", "AtomUI.Controls.Common", "src/AtomUI.Controls/CommonControlThemesProvider.cs|" + "src/AtomUI.Controls/BrowserCommonControlThemesProvider.cs")] [InlineData( "src/AtomUI.Desktop.Controls/ThemeManagerBuilderExtensions.cs", "ThemeManagerBuilderExtensions", "AtomUI.Desktop.Controls", "src/AtomUI.Desktop.Controls/DesktopControlThemesProvider.cs|" + "src/AtomUI.Desktop.Controls/BrowserDesktopControlThemesProvider.cs")] [InlineData( "src/AtomUI.Desktop.Controls.DataGrid/ThemeManagerBuilderExtensions.cs", "DataGridThemeManagerBuilderExtensions", "AtomUI.Desktop.Controls.DataGrid", "src/AtomUI.Desktop.Controls.DataGrid/AtomUIDataGridThemesProvider.cs")] [InlineData( "src/AtomUI.Desktop.Controls.ColorPicker/ThemeManagerBuilderExtensions.cs", "ColorPickerThemeManagerBuilderExtensions", "AtomUI.Desktop.Controls.ColorPicker", "src/AtomUI.Desktop.Controls.ColorPicker/AtomUIColorPickerThemesProvider.cs")] [InlineData( "src/AtomUI.Desktop.Controls.Extras/ThemeManagerBuilderExtensions.cs", "ExtrasThemeManagerBuilderExtensions", "AtomUI.Desktop.Controls.Extras", "src/AtomUI.Desktop.Controls.Extras/AtomUIExtrasThemesProvider.cs")] public void Runtime_package_ids_are_declared_once_and_reused_by_theme_providers( string entrySourcePath, string entryTypeName, string packageId, string providerSourcePaths) { var entrySource = File.ReadAllText(GetRepoFile(entrySourcePath)); entrySource.ShouldContain($"internal const string PackageId = \"{packageId}\";"); entrySource.Split($"\"{packageId}\"", StringSplitOptions.None).Length.ShouldBe(2); foreach (var providerSourcePath in providerSourcePaths.Split('|')) { var providerSource = File.ReadAllText(GetRepoFile(providerSourcePath)); providerSource.ShouldContain($"Id = {entryTypeName}.PackageId;"); providerSource.ShouldNotContain($"\"{packageId}\""); } } [Fact] public void Common_dependency_remains_on_the_full_registration_path() { var source = File.ReadAllText(GetRepoFile( "src/AtomUI.Controls/ThemeManagerBuildExtensions.cs")); var body = GetMethodBody(source, "UseCommonControls"); body.ShouldContain("GeneratedControlPackageRegistration.Register"); body.ShouldContain("GeneratedLanguageModuleRegistration.Register"); body.ShouldNotContain("AotTrimRegistration"); body.ShouldNotContain("AotTrimRegistrationPlanRegistry"); } [Fact] public void Desktop_generated_entry_preserves_package_core_order() { var source = File.ReadAllText(GetRepoFile( "src/AtomUI.Desktop.Controls/ThemeManagerBuilderExtensions.cs")); source.ShouldContain("using AtomUI.Generated.AtomUIDesktopControls;"); source.ShouldNotContain("using AtomUI.Generated.AtomUIControls;"); var entryBody = GetMethodBody(source, "UseDesktopControls"); AssertOrder( entryBody, "PrepareDesktopPackageCore(builder)", "if (AotTrimRegistration.IsEnabled)", "RegisterGeneratedDesktopPackage(builder)", "RegisterFullDesktopPackage(builder)", "CompleteDesktopPackageCore(builder)"); entryBody.ShouldNotContain("useGeneratedRegistration"); var prepareBody = GetMethodBody(source, "PrepareDesktopPackageCore"); AssertOrder( prepareBody, "builder.UseCommonControls()", "DialogInputCaptureTracker.Initialize()"); var completeBody = GetMethodBody(source, "CompleteDesktopPackageCore"); AssertOrder( completeBody, "GeneratedLanguageModuleRegistration.Register(builder.Localization)", "builder.Theme.AddInitializer(InitializeDesktopRuntime)"); source.ShouldContain("public static IAtomUIBuilder UseAllDesktopControls"); var fullBody = GetMethodBody(source, "UseAllDesktopControls"); AssertOrder( fullBody, "PrepareDesktopPackageCore(builder)", "RegisterFullDesktopPackage(builder)", "CompleteDesktopPackageCore(builder)"); fullBody.ShouldNotContain("AotTrimRegistration"); fullBody.ShouldNotContain("RegisterGeneratedDesktopPackage"); source.ShouldContain("AotTrimRegistrationPlanRegistry.ApplyPackage("); source.ShouldContain("\"AtomUI.Desktop.Controls\""); source.ShouldContain("DesktopControlRegistrationSelector.IsBrowserControlSupported"); source.ShouldNotContain("DesktopControlThemeAssetSelector"); source.ShouldNotContain("selectAssets:"); } [Theory] [InlineData( "src/AtomUI.Desktop.Controls.DataGrid/ThemeManagerBuilderExtensions.cs", "UseDesktopDataGrid", "AtomUI.Desktop.Controls.DataGrid", "AtomUI.Generated.AtomUIDesktopControlsDataGrid", true)] [InlineData( "src/AtomUI.Desktop.Controls.ColorPicker/ThemeManagerBuilderExtensions.cs", "UseDesktopColorPicker", "AtomUI.Desktop.Controls.ColorPicker", "AtomUI.Generated.AtomUIDesktopControlsColorPicker", true)] [InlineData( "src/AtomUI.Desktop.Controls.Extras/ThemeManagerBuilderExtensions.cs", "UseDesktopExtras", "AtomUI.Desktop.Controls.Extras", "AtomUI.Generated.AtomUIDesktopControlsExtras", false)] public void Optional_package_entries_branch_only_the_control_package_registration( string sourcePath, string methodName, string packageId, string generatedNamespace, bool hasLanguageModule) { var source = File.ReadAllText(GetRepoFile(sourcePath)); source.ShouldContain($"using {generatedNamespace};"); var body = GetMethodBody(source, methodName); body.ShouldContain("AotTrimRegistration.IsEnabled"); body.ShouldContain("AotTrimRegistrationPlanRegistry.ApplyPackage("); body.ShouldContain("GeneratedControlPackageRegistration.Register("); body.ShouldContain("PackageId"); body.ShouldNotContain($"\"{packageId}\""); if (hasLanguageModule) { body.IndexOf("AotTrimRegistrationPlanRegistry.ApplyPackage(", StringComparison.Ordinal) .ShouldBeLessThan(body.IndexOf( "GeneratedLanguageModuleRegistration.Register(builder.Localization)", StringComparison.Ordinal)); } else { body.ShouldNotContain("GeneratedLanguageModuleRegistration"); } } [Fact] public void GalleryBase_keeps_configuration_and_language_outside_the_registration_branch() { var source = File.ReadAllText(GetRepoFile( "src/AtomUI.Toolkits.GalleryBase/ThemeManagerBuilderExtensions.cs")); source.ShouldContain("using AtomUI.Generated.AtomUIToolkitsGalleryBase;"); source.ShouldContain( "internal const string PackageId = \"AtomUI.Toolkits.GalleryBase\";"); var body = GetMethodBody(source, "UseGalleryBase"); AssertOrder( body, "GalleryBaseConfigurationProvider.SetCurrent", "AotTrimRegistrationPlanRegistry.ApplyPackage(", "GeneratedLanguageModuleRegistration.Register(builder.Localization)"); body.ShouldContain("AotTrimRegistration.IsEnabled"); body.ShouldContain("GeneratedControlPackageRegistration.Register("); body.ShouldContain("PackageId"); body.ShouldNotContain("\"AtomUI.Toolkits.GalleryBase\""); var providerSource = File.ReadAllText(GetRepoFile( "src/AtomUI.Toolkits.GalleryBase/Controls/GalleryControlThemesProvider.cs")); providerSource.ShouldContain("Id = \"AtomUI.Toolkits.GalleryBase.Controls\";"); } private static string GetMethodBody(string source, string methodName) { var root = CSharpSyntaxTree.ParseText(source).GetRoot(); var method = root.DescendantNodes() .OfType() .Single(declaration => declaration.Identifier.ValueText == methodName); return method.Body.ShouldNotBeNull().ToFullString(); } private static bool HasRegistrationEntryAttribute(MethodDeclarationSyntax method) { return method.AttributeLists .SelectMany(static list => list.Attributes) .Any(static attribute => { var name = attribute.Name.ToString(); return name.EndsWith( "ControlPackageRegistrationEntry", StringComparison.Ordinal) || name.EndsWith( "ControlPackageRegistrationEntryAttribute", StringComparison.Ordinal); }); } private static void AssertOrder(string source, params string[] values) { var previous = -1; foreach (var value in values) { var current = source.IndexOf(value, StringComparison.Ordinal); current.ShouldBeGreaterThan(previous, $"'{value}' must follow the preceding Package Core step"); previous = current; } } private static string GetRepoFile(string relativePath) { var directory = new DirectoryInfo(AppContext.BaseDirectory); while (directory is not null) { var candidate = Path.Combine(directory.FullName, relativePath); if (File.Exists(candidate)) { return candidate; } directory = directory.Parent; } throw new FileNotFoundException(relativePath); } }