From 00103479e9b55c86a978aad22110237ff8eeb3ec Mon Sep 17 00:00:00 2001 From: Jesus Fernandez Date: Fri, 5 Jul 2024 18:56:14 +0300 Subject: [PATCH] Module: HTML (#4) --- .../ConvertDataTableToHtmlTableAction.cs | 64 +++++ .../ConvertListToHtmlListAction.cs | 56 ++++ modules/Modules.HTML.Actions/Debugger.cs | 16 ++ modules/Modules.HTML.Actions/ErrorCodes.cs | 10 + .../Modules.HTML.Actions.csproj | 35 +++ .../Properties/Resources.Designer.cs | 242 ++++++++++++++++++ .../Properties/Resources.resx | 185 +++++++++++++ .../SetRegistryValueAction.cs | 12 + src/PowerAutomate.Desktop.sln | 9 +- .../RegistryVisitor.cs | 3 + .../ActionSelectorTests.cs | 36 +++ tests/Modules.Actions.Tests/ActionTests.cs | 139 ++++++++++ .../AssemblyExtensions.cs | 22 ++ .../Modules.Actions.Tests/ModuleEnumerator.cs | 23 ++ tests/Modules.Actions.Tests/ModuleTests.cs | 29 +++ .../Modules.Actions.Tests.csproj} | 1 + .../ActionSelectorTests.cs | 35 --- .../ActionTests.cs | 132 ---------- .../ModuleTests.cs | 28 -- 19 files changed, 881 insertions(+), 196 deletions(-) create mode 100644 modules/Modules.HTML.Actions/ConvertDataTableToHtmlTableAction.cs create mode 100644 modules/Modules.HTML.Actions/ConvertListToHtmlListAction.cs create mode 100644 modules/Modules.HTML.Actions/Debugger.cs create mode 100644 modules/Modules.HTML.Actions/ErrorCodes.cs create mode 100644 modules/Modules.HTML.Actions/Modules.HTML.Actions.csproj create mode 100644 modules/Modules.HTML.Actions/Properties/Resources.Designer.cs create mode 100644 modules/Modules.HTML.Actions/Properties/Resources.resx create mode 100644 tests/Modules.Actions.Tests/ActionSelectorTests.cs create mode 100644 tests/Modules.Actions.Tests/ActionTests.cs create mode 100644 tests/Modules.Actions.Tests/AssemblyExtensions.cs create mode 100644 tests/Modules.Actions.Tests/ModuleEnumerator.cs create mode 100644 tests/Modules.Actions.Tests/ModuleTests.cs rename tests/{Modules.Windows.Registry.Actions.Tests/Modules.Windows.Registry.Actions.Tests.csproj => Modules.Actions.Tests/Modules.Actions.Tests.csproj} (83%) delete mode 100644 tests/Modules.Windows.Registry.Actions.Tests/ActionSelectorTests.cs delete mode 100644 tests/Modules.Windows.Registry.Actions.Tests/ActionTests.cs delete mode 100644 tests/Modules.Windows.Registry.Actions.Tests/ModuleTests.cs diff --git a/modules/Modules.HTML.Actions/ConvertDataTableToHtmlTableAction.cs b/modules/Modules.HTML.Actions/ConvertDataTableToHtmlTableAction.cs new file mode 100644 index 0000000..6edd884 --- /dev/null +++ b/modules/Modules.HTML.Actions/ConvertDataTableToHtmlTableAction.cs @@ -0,0 +1,64 @@ +// -------------------------------------------------------------- +// Copyright (c) Jesus Fernandez. All Rights Reserved. +// -------------------------------------------------------------- + +using System; +using System.Data; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK; +using Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK.Attributes; + +namespace PowerAutomate.Desktop.Modules.HTML.Actions; + +[Action(Id = "ConvertDataTableToHtmlTable")] +[Throws(ErrorCodes.Unknown)] +[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "MemberCanBePrivate.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "UnusedType.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +public class ConvertDataTableToHtmlTableAction : ActionBase +{ + [InputArgument] + public DataTable DataTable { get; set; } = null!; + + [OutputArgument] + public string HtmlTable { get; set; } = null!; + + public override void Execute(ActionContext context) + { + Debugger.Launch(); + + try + { + var html = new StringBuilder(); + html.Append(""); + html.Append(""); + foreach (DataColumn column in DataTable.Columns) + { + html.Append(""); + } + + html.Append(""); + + foreach (DataRow row in DataTable.Rows) + { + html.Append(""); + foreach (var item in row.ItemArray) + { + html.Append(""); + } + + html.Append(""); + } + + html.Append("
").Append(column.ColumnName).Append("
").Append(item).Append("
"); + HtmlTable = html.ToString(); + } + catch (Exception ex) + { + throw new ActionException(ErrorCodes.Unknown, ex.Message, ex); + } + } +} \ No newline at end of file diff --git a/modules/Modules.HTML.Actions/ConvertListToHtmlListAction.cs b/modules/Modules.HTML.Actions/ConvertListToHtmlListAction.cs new file mode 100644 index 0000000..76db374 --- /dev/null +++ b/modules/Modules.HTML.Actions/ConvertListToHtmlListAction.cs @@ -0,0 +1,56 @@ +// -------------------------------------------------------------- +// Copyright (c) Jesus Fernandez. All Rights Reserved. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK; +using Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK.Attributes; + +namespace PowerAutomate.Desktop.Modules.HTML.Actions; + +[Action(Id = "ConvertListToHtmlList")] +[Throws(ErrorCodes.Unknown)] +[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "MemberCanBePrivate.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "UnusedType.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +public class ConvertListToHtmlListAction : ActionBase +{ + [OutputArgument(Order = 1)] + public string HtmlList { get; set; } = null!; + + [InputArgument(Order = 0)] + [DefaultValue(false)] + public bool IsOrdered { get; set; } + + [InputArgument(Order = 1)] + public List List { get; set; } = null!; + + public override void Execute(ActionContext context) + { + Debugger.Launch(); + + try + { + var rootTag = IsOrdered ? "ol" : "ul"; + var html = new StringBuilder(); + html.Append($"<{rootTag}>"); + foreach (var item in List) + { + html.Append("
  • ").Append(item).Append("
  • "); + } + + html.Append($""); + HtmlList = html.ToString(); + } + catch (Exception ex) + { + throw new ActionException(ErrorCodes.Unknown, ex.Message, ex); + } + } +} \ No newline at end of file diff --git a/modules/Modules.HTML.Actions/Debugger.cs b/modules/Modules.HTML.Actions/Debugger.cs new file mode 100644 index 0000000..10767e7 --- /dev/null +++ b/modules/Modules.HTML.Actions/Debugger.cs @@ -0,0 +1,16 @@ +// -------------------------------------------------------------- +// Copyright (c) Jesus Fernandez. All Rights Reserved. +// -------------------------------------------------------------- + +using System.Diagnostics; + +namespace PowerAutomate.Desktop.Modules.HTML.Actions; + +internal static class Debugger +{ + [Conditional("DEBUG")] + public static void Launch() + { + System.Diagnostics.Debugger.Launch(); + } +} \ No newline at end of file diff --git a/modules/Modules.HTML.Actions/ErrorCodes.cs b/modules/Modules.HTML.Actions/ErrorCodes.cs new file mode 100644 index 0000000..6288725 --- /dev/null +++ b/modules/Modules.HTML.Actions/ErrorCodes.cs @@ -0,0 +1,10 @@ +// -------------------------------------------------------------- +// Copyright (c) Jesus Fernandez. All Rights Reserved. +// -------------------------------------------------------------- + +namespace PowerAutomate.Desktop.Modules.HTML.Actions; + +internal static class ErrorCodes +{ + public const string Unknown = "UnknownError"; +} \ No newline at end of file diff --git a/modules/Modules.HTML.Actions/Modules.HTML.Actions.csproj b/modules/Modules.HTML.Actions/Modules.HTML.Actions.csproj new file mode 100644 index 0000000..c3b2919 --- /dev/null +++ b/modules/Modules.HTML.Actions/Modules.HTML.Actions.csproj @@ -0,0 +1,35 @@ + + + + HTML + net472 + latest + enable + true + + + + + + + + + True + True + Resources.resx + + + True + True + Resources.resx + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/modules/Modules.HTML.Actions/Properties/Resources.Designer.cs b/modules/Modules.HTML.Actions/Properties/Resources.Designer.cs new file mode 100644 index 0000000..467f707 --- /dev/null +++ b/modules/Modules.HTML.Actions/Properties/Resources.Designer.cs @@ -0,0 +1,242 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PowerAutomate.Desktop.Modules.HTML.Actions.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PowerAutomate.Desktop.Modules.HTML.Actions.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The data table.. + /// + public static string ConvertDataTableToHtmlTable_DataTable_Description { + get { + return ResourceManager.GetString("ConvertDataTableToHtmlTable_DataTable_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Data Table. + /// + public static string ConvertDataTableToHtmlTable_DataTable_FriendlyName { + get { + return ResourceManager.GetString("ConvertDataTableToHtmlTable_DataTable_FriendlyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Converts a data table to an HTML table.. + /// + public static string ConvertDataTableToHtmlTable_Description { + get { + return ResourceManager.GetString("ConvertDataTableToHtmlTable_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Convert data table to HTML table. + /// + public static string ConvertDataTableToHtmlTable_FriendlyName { + get { + return ResourceManager.GetString("ConvertDataTableToHtmlTable_FriendlyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The HTML table.. + /// + public static string ConvertDataTableToHtmlTable_HtmlTable_Description { + get { + return ResourceManager.GetString("ConvertDataTableToHtmlTable_HtmlTable_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to HTML Table. + /// + public static string ConvertDataTableToHtmlTable_HtmlTable_FriendlyName { + get { + return ResourceManager.GetString("ConvertDataTableToHtmlTable_HtmlTable_FriendlyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Convert <DATATABLE> to an HTML table and store it into <HTMLTABLE>.. + /// + public static string ConvertDataTableToHtmlTable_Summary { + get { + return ResourceManager.GetString("ConvertDataTableToHtmlTable_Summary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Converts a list to an HTML list.. + /// + public static string ConvertListToHtmlList_Description { + get { + return ResourceManager.GetString("ConvertListToHtmlList_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Convert list to HTML list. + /// + public static string ConvertListToHtmlList_FriendlyName { + get { + return ResourceManager.GetString("ConvertListToHtmlList_FriendlyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The HTML list.. + /// + public static string ConvertListToHtmlList_HtmlList_Description { + get { + return ResourceManager.GetString("ConvertListToHtmlList_HtmlList_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to HTML List. + /// + public static string ConvertListToHtmlList_HtmlList_FriendlyName { + get { + return ResourceManager.GetString("ConvertListToHtmlList_HtmlList_FriendlyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Whether the resulting HTML list should be ordered.. + /// + public static string ConvertListToHtmlList_IsOrdered_Description { + get { + return ResourceManager.GetString("ConvertListToHtmlList_IsOrdered_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ordered List. + /// + public static string ConvertListToHtmlList_IsOrdered_FriendlyName { + get { + return ResourceManager.GetString("ConvertListToHtmlList_IsOrdered_FriendlyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The list.. + /// + public static string ConvertListToHtmlList_List_Description { + get { + return ResourceManager.GetString("ConvertListToHtmlList_List_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to List. + /// + public static string ConvertListToHtmlList_List_FriendlyName { + get { + return ResourceManager.GetString("ConvertListToHtmlList_List_FriendlyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Convert <LIST> to an HTML list and store it into <HTMLLIST>.. + /// + public static string ConvertListToHtmlList_Summary { + get { + return ResourceManager.GetString("ConvertListToHtmlList_Summary", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Indicates that there was a problem when executing the operation.. + /// + public static string Error_UnknownError_Description { + get { + return ResourceManager.GetString("Error_UnknownError_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Operation failed. + /// + public static string Error_UnknownError_FriendlyName { + get { + return ResourceManager.GetString("Error_UnknownError_FriendlyName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Perform operations related to HyperText Markup Language (HTML).. + /// + public static string HTML_Description { + get { + return ResourceManager.GetString("HTML_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to HTML. + /// + public static string HTML_FriendlyName { + get { + return ResourceManager.GetString("HTML_FriendlyName", resourceCulture); + } + } + } +} diff --git a/modules/Modules.HTML.Actions/Properties/Resources.resx b/modules/Modules.HTML.Actions/Properties/Resources.resx new file mode 100644 index 0000000..c07b73f --- /dev/null +++ b/modules/Modules.HTML.Actions/Properties/Resources.resx @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + HTML + + + Perform operations related to HyperText Markup Language (HTML). + + + Convert data table to HTML table + + + Converts a data table to an HTML table. + + + Convert <DATATABLE> to an HTML table and store it into <HTMLTABLE>. + + + Convert list to HTML list + + + Converts a list to an HTML list. + + + Convert <LIST> to an HTML list and store it into <HTMLLIST>. + + + Data Table + + + The data table. + + + HTML Table + + + The HTML table. + + + List + + + The list. + + + HTML List + + + The HTML list. + + + Ordered List + + + Whether the resulting HTML list should be ordered. + + + Operation failed + + + Indicates that there was a problem when executing the operation. + + \ No newline at end of file diff --git a/modules/Modules.Windows.Registry.Actions/SetRegistryValueAction.cs b/modules/Modules.Windows.Registry.Actions/SetRegistryValueAction.cs index 2dd2ab3..5f26f73 100644 --- a/modules/Modules.Windows.Registry.Actions/SetRegistryValueAction.cs +++ b/modules/Modules.Windows.Registry.Actions/SetRegistryValueAction.cs @@ -92,6 +92,8 @@ public override void Execute(ActionContext context) } } +[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "UnusedType.Global", Justification = "PowerAutomate.Desktop.Module.Action")] public class SetStringRegistryValueActionSelector : ActionSelector { public SetStringRegistryValueActionSelector() @@ -108,6 +110,8 @@ public SetStringRegistryValueActionSelector() } } +[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "UnusedType.Global", Justification = "PowerAutomate.Desktop.Module.Action")] public class SetExpandStringRegistryValueActionSelector : ActionSelector { public SetExpandStringRegistryValueActionSelector() @@ -124,6 +128,8 @@ public SetExpandStringRegistryValueActionSelector() } } +[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "UnusedType.Global", Justification = "PowerAutomate.Desktop.Module.Action")] public class SetMultiStringRegistryValueActionSelector : ActionSelector { public SetMultiStringRegistryValueActionSelector() @@ -140,6 +146,8 @@ public SetMultiStringRegistryValueActionSelector() } } +[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "UnusedType.Global", Justification = "PowerAutomate.Desktop.Module.Action")] public class SetBinaryRegistryValueActionSelector : ActionSelector { public SetBinaryRegistryValueActionSelector() @@ -156,6 +164,8 @@ public SetBinaryRegistryValueActionSelector() } } +[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "UnusedType.Global", Justification = "PowerAutomate.Desktop.Module.Action")] public class SetInt32RegistryValueActionSelector : ActionSelector { public SetInt32RegistryValueActionSelector() @@ -172,6 +182,8 @@ public SetInt32RegistryValueActionSelector() } } +[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "PowerAutomate.Desktop.Module.Action")] +[SuppressMessage("ReSharper", "UnusedType.Global", Justification = "PowerAutomate.Desktop.Module.Action")] public class SetInt64RegistryValueActionSelector : ActionSelector { public SetInt64RegistryValueActionSelector() diff --git a/src/PowerAutomate.Desktop.sln b/src/PowerAutomate.Desktop.sln index 4b84578..26a9498 100644 --- a/src/PowerAutomate.Desktop.sln +++ b/src/PowerAutomate.Desktop.sln @@ -16,7 +16,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{688BD8 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{79D5DFFD-323B-4DC4-950D-13DA2C1F31E0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modules.Windows.Registry.Actions.Tests", "..\tests\Modules.Windows.Registry.Actions.Tests\Modules.Windows.Registry.Actions.Tests.csproj", "{D622D84B-9853-4E6E-AE09-1988FBFE6392}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modules.Actions.Tests", "..\tests\Modules.Actions.Tests\Modules.Actions.Tests.csproj", "{D622D84B-9853-4E6E-AE09-1988FBFE6392}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modules.HTML.Actions", "..\modules\Modules.HTML.Actions\Modules.HTML.Actions.csproj", "{435C2B05-E667-4AAD-9BE5-3863345B98A4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -40,6 +42,10 @@ Global {D622D84B-9853-4E6E-AE09-1988FBFE6392}.Debug|Any CPU.Build.0 = Debug|Any CPU {D622D84B-9853-4E6E-AE09-1988FBFE6392}.Release|Any CPU.ActiveCfg = Release|Any CPU {D622D84B-9853-4E6E-AE09-1988FBFE6392}.Release|Any CPU.Build.0 = Release|Any CPU + {435C2B05-E667-4AAD-9BE5-3863345B98A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {435C2B05-E667-4AAD-9BE5-3863345B98A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {435C2B05-E667-4AAD-9BE5-3863345B98A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {435C2B05-E667-4AAD-9BE5-3863345B98A4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {60B2AD52-230C-4B79-894D-64642E88D9C7} = {AAC42F13-B42E-462A-B605-40BFEC07FD26} @@ -49,5 +55,6 @@ Global {AAC42F13-B42E-462A-B605-40BFEC07FD26} = {688BD8E9-5FCD-4EA8-82AF-DBC986681712} {79D5DFFD-323B-4DC4-950D-13DA2C1F31E0} = {3B78C634-DFD6-43DA-A30E-33AC42224BD4} {D622D84B-9853-4E6E-AE09-1988FBFE6392} = {79D5DFFD-323B-4DC4-950D-13DA2C1F31E0} + {435C2B05-E667-4AAD-9BE5-3863345B98A4} = {3B78C634-DFD6-43DA-A30E-33AC42224BD4} EndGlobalSection EndGlobal diff --git a/src/Windows.Registry.Abstractions/RegistryVisitor.cs b/src/Windows.Registry.Abstractions/RegistryVisitor.cs index 97e7cd7..7eea6a3 100644 --- a/src/Windows.Registry.Abstractions/RegistryVisitor.cs +++ b/src/Windows.Registry.Abstractions/RegistryVisitor.cs @@ -3,9 +3,12 @@ // -------------------------------------------------------------- using System; +using System.Diagnostics.CodeAnalysis; namespace PowerAutomate.Desktop.Windows.Registry.Abstractions; +[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "PowerAutomate.Desktop")] +[SuppressMessage("ReSharper", "UnusedType.Global", Justification = "PowerAutomate.Desktop")] public abstract class RegistryVisitor : IRegistryVisitor { public virtual bool VisitEnterRegistry(IRegistry value) diff --git a/tests/Modules.Actions.Tests/ActionSelectorTests.cs b/tests/Modules.Actions.Tests/ActionSelectorTests.cs new file mode 100644 index 0000000..da8a63c --- /dev/null +++ b/tests/Modules.Actions.Tests/ActionSelectorTests.cs @@ -0,0 +1,36 @@ +// -------------------------------------------------------------- +// Copyright (c) Jesus Fernandez. All Rights Reserved. +// -------------------------------------------------------------- + +using System; +using System.Linq; +using Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK.ActionSelectors; +using NUnit.Framework; + +namespace PowerAutomate.Desktop.Modules.Actions.Tests; + +[TestFixture] +public class ActionSelectorTests +{ + [Test] + public void Action_All_HasLocalizableResources() + { + var assemblies = ModuleEnumerator.GetAllAssemblies(); + foreach (var assembly in assemblies) + { + var resourceManager = assembly.GetResourceManager(); + var actionSelectorBaseType = typeof(ActionSelectorBase); + var actionSelectorTypes = assembly.ExportedTypes + .Where(type => actionSelectorBaseType.IsAssignableFrom(type)) + .Where(type => type is { IsGenericType: false, IsAbstract: false, IsPublic: true }) + .ToList(); + + foreach (var actionSelectorType in actionSelectorTypes) + { + var instance = (ActionSelectorBase)Activator.CreateInstance(actionSelectorType); + var summaryResource = resourceManager.GetString($"{instance.ActionName}_Summary"); + Assert.That(summaryResource, Is.Not.Null.Or.Empty, $"Action selector '{instance.ActionName}' doesn't have a summary resource"); + } + } + } +} \ No newline at end of file diff --git a/tests/Modules.Actions.Tests/ActionTests.cs b/tests/Modules.Actions.Tests/ActionTests.cs new file mode 100644 index 0000000..d791191 --- /dev/null +++ b/tests/Modules.Actions.Tests/ActionTests.cs @@ -0,0 +1,139 @@ +// -------------------------------------------------------------- +// Copyright (c) Jesus Fernandez. All Rights Reserved. +// -------------------------------------------------------------- + +using System; +using System.Linq; +using System.Reflection; +using Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK.Attributes; +using NUnit.Framework; + +namespace PowerAutomate.Desktop.Modules.Actions.Tests; + +[TestFixture] +public class ActionTests +{ + [Test] + public void Action_All_Arguments_All_Enums_HasLocalizableResources() + { + var assemblies = ModuleEnumerator.GetAllAssemblies(); + foreach (var assembly in assemblies) + { + var resourceManager = assembly.GetResourceManager(); + var enumArgumentsByActions = assembly.ExportedTypes + .Select(type => (ActionType: type, ActionAttribute: type.GetCustomAttribute())) + .Where(pair => pair.ActionAttribute is not null) + .Select(pair => ( + pair.ActionType, + pair.ActionAttribute, + Arguments: pair.ActionType + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(property => (Property: property, InputArgumentAttribute: property.GetCustomAttribute(), OutputArgumentAttribute: property.GetCustomAttribute())) + .Where(property => property.InputArgumentAttribute is not null || property.OutputArgumentAttribute is not null) + .Where(property => property.Property.PropertyType.IsEnum) + .ToList()) + ) + .Where(pair => pair.Arguments.Any()) + .ToList(); + var enumValuesByPropertyTypes = enumArgumentsByActions.SelectMany(s => s.Arguments.Select(argument => argument.Property.PropertyType)) + .Distinct() + .Select(propertyType => (PropertyType: propertyType, Values: Enum.GetValues(propertyType) + .OfType() + .ToList())) + .ToList(); + foreach (var enumValuesByPropertyType in enumValuesByPropertyTypes) + { + foreach (var friendlyNameResource in enumValuesByPropertyType.Values.Select(value => resourceManager.GetString($"{enumValuesByPropertyType.PropertyType.Name}_{value}_FriendlyName"))) + { + Assert.That(friendlyNameResource, Is.Not.Null.Or.Empty, $"Enum argument '{enumValuesByPropertyType.PropertyType.Name}' doesn't have a friendly name resource"); + } + } + } + } + + [Test] + public void Action_All_Arguments_All_HasLocalizableResources() + { + var assemblies = ModuleEnumerator.GetAllAssemblies(); + foreach (var assembly in assemblies) + { + var resourceManager = assembly.GetResourceManager(); + var argumentsByActions = assembly.ExportedTypes + .Select(type => (ActionType: type, ActionAttribute: type.GetCustomAttribute())) + .Where(pair => pair.ActionAttribute is not null) + .Select(pair => ( + pair.ActionType, + pair.ActionAttribute, + Arguments: pair.ActionType + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(property => (Property: property, InputArgumentAttribute: property.GetCustomAttribute(), OutputArgumentAttribute: property.GetCustomAttribute())) + .Where(property => property.InputArgumentAttribute is not null || property.OutputArgumentAttribute is not null) + .ToList()) + ) + .ToList(); + + foreach (var argumentsByAction in argumentsByActions) + { + foreach (var argument in argumentsByAction.Arguments) + { + var friendlyNameResource = resourceManager.GetString($"{argumentsByAction.ActionAttribute.Id}_{argument.Property.Name}_FriendlyName"); + var descriptionResource = resourceManager.GetString($"{argumentsByAction.ActionAttribute.Id}_{argument.Property.Name}_Description"); + + Assert.That(friendlyNameResource, Is.Not.Null.Or.Empty, $"Argument '{argument.Property.Name}' in action '{argumentsByAction.ActionAttribute.Id}' doesn't have a friendly name resource"); + Assert.That(descriptionResource, Is.Not.Null.Or.Empty, $"Argument '{argument.Property.Name}' in action '{argumentsByAction.ActionAttribute.Id}' doesn't have a description resource"); + } + } + } + } + + [Test] + public void Action_All_Errors_All_HasLocalizableResources() + { + var assemblies = ModuleEnumerator.GetAllAssemblies(); + foreach (var assembly in assemblies) + { + var resourceManager = assembly.GetResourceManager(); + var errors = assembly.ExportedTypes + .Select(type => (ActionType: type, ActionAttribute: type.GetCustomAttribute())) + .Where(pair => pair.ActionAttribute is not null) + .SelectMany(pair => pair.ActionType.GetCustomAttributes()) + .Select(attribute => attribute.Name) + .Distinct() + .ToList(); + + foreach (var error in errors) + { + var friendlyNameResource = resourceManager.GetString($"Error_{error}_FriendlyName"); + var descriptionResource = resourceManager.GetString($"Error_{error}_Description"); + + Assert.That(friendlyNameResource, Is.Not.Null.Or.Empty, $"Error '{error}' doesn't have a friendly name resource"); + Assert.That(descriptionResource, Is.Not.Null.Or.Empty, $"Error '{error}' doesn't have a friendly name resource"); + } + } + } + + [Test] + public void Action_All_HasLocalizableResources() + { + var assemblies = ModuleEnumerator.GetAllAssemblies(); + foreach (var assembly in assemblies) + { + var resourceManager = assembly.GetResourceManager(); + var actions = assembly.ExportedTypes + .Select(type => (ActionType: type, ActionAttribute: type.GetCustomAttribute())) + .Where(pair => pair.ActionAttribute is not null) + .ToList(); + + foreach (var action in actions) + { + var friendlyNameResource = resourceManager.GetString($"{action.ActionAttribute.Id}_FriendlyName"); + var descriptionResource = resourceManager.GetString($"{action.ActionAttribute.Id}_Description"); + var summaryResource = resourceManager.GetString($"{action.ActionAttribute.Id}_Summary"); + + Assert.That(friendlyNameResource, Is.Not.Null.Or.Empty, $"Action '{action.ActionAttribute.Id}' doesn't have a friendly name resource"); + Assert.That(descriptionResource, Is.Not.Null.Or.Empty, $"Action '{action.ActionAttribute.Id}' doesn't have a description resource"); + Assert.That(summaryResource, Is.Not.Null.Or.Empty, $"Action '{action.ActionAttribute.Id}' doesn't have a summary resource"); + } + } + } +} \ No newline at end of file diff --git a/tests/Modules.Actions.Tests/AssemblyExtensions.cs b/tests/Modules.Actions.Tests/AssemblyExtensions.cs new file mode 100644 index 0000000..05218d2 --- /dev/null +++ b/tests/Modules.Actions.Tests/AssemblyExtensions.cs @@ -0,0 +1,22 @@ +// -------------------------------------------------------------- +// Copyright (c) Jesus Fernandez. All Rights Reserved. +// -------------------------------------------------------------- + +using System; +using System.Reflection; +using System.Resources; + +namespace PowerAutomate.Desktop.Modules.Actions.Tests; + +internal static class AssemblyExtensions +{ + public static ResourceManager GetResourceManager(this Assembly value) + { + var assemblyName = value.GetName(); + var resourcesType = Type.GetType($"{assemblyName.Name}.Properties.Resources", _ => value, (_, name, throwOnError) => value.GetType(name, throwOnError), true)!; + var resourcesInstance = Activator.CreateInstance(resourcesType, true); + var resourceManagerPropertyInfo = resourcesType.GetProperty("ResourceManager", BindingFlags.Public | BindingFlags.Static)!; + var resourceManager = (ResourceManager)resourceManagerPropertyInfo.GetValue(resourcesInstance); + return resourceManager; + } +} \ No newline at end of file diff --git a/tests/Modules.Actions.Tests/ModuleEnumerator.cs b/tests/Modules.Actions.Tests/ModuleEnumerator.cs new file mode 100644 index 0000000..c2f4bac --- /dev/null +++ b/tests/Modules.Actions.Tests/ModuleEnumerator.cs @@ -0,0 +1,23 @@ +// -------------------------------------------------------------- +// Copyright (c) Jesus Fernandez. All Rights Reserved. +// -------------------------------------------------------------- + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace PowerAutomate.Desktop.Modules.Actions.Tests; + +internal static class ModuleEnumerator +{ + public static IEnumerable GetAllAssemblies() + { + var type = typeof(ModuleEnumerator); + var assembly = type.Assembly; + var assemblyLocation = assembly.Location; + var assemblyDirectory = Path.GetDirectoryName(assemblyLocation)!; + var moduleFileNames = Directory.GetFiles(assemblyDirectory, "*.Actions.dll", SearchOption.TopDirectoryOnly); + return moduleFileNames.Select(Assembly.LoadFile).ToList(); + } +} \ No newline at end of file diff --git a/tests/Modules.Actions.Tests/ModuleTests.cs b/tests/Modules.Actions.Tests/ModuleTests.cs new file mode 100644 index 0000000..822ddc2 --- /dev/null +++ b/tests/Modules.Actions.Tests/ModuleTests.cs @@ -0,0 +1,29 @@ +// -------------------------------------------------------------- +// Copyright (c) Jesus Fernandez. All Rights Reserved. +// -------------------------------------------------------------- + +using System.Reflection; +using NUnit.Framework; + +namespace PowerAutomate.Desktop.Modules.Actions.Tests; + +[TestFixture] +public class ModuleTests +{ + [Test] + public void Module_HasLocalizableResources() + { + var assemblies = ModuleEnumerator.GetAllAssemblies(); + foreach (var assembly in assemblies) + { + var resourceManager = assembly.GetResourceManager(); + var assemblyTitle = assembly.GetCustomAttribute(); + + var friendlyNameResource = resourceManager.GetString($"{assemblyTitle.Title}_FriendlyName"); + var descriptionResource = resourceManager.GetString($"{assemblyTitle.Title}_Description"); + + Assert.That(friendlyNameResource, Is.Not.Null.Or.Empty, $"Module '{assemblyTitle.Title}' doesn't have a friendly name resource"); + Assert.That(descriptionResource, Is.Not.Null.Or.Empty, $"Module '{assemblyTitle.Title}' doesn't have a description resource"); + } + } +} \ No newline at end of file diff --git a/tests/Modules.Windows.Registry.Actions.Tests/Modules.Windows.Registry.Actions.Tests.csproj b/tests/Modules.Actions.Tests/Modules.Actions.Tests.csproj similarity index 83% rename from tests/Modules.Windows.Registry.Actions.Tests/Modules.Windows.Registry.Actions.Tests.csproj rename to tests/Modules.Actions.Tests/Modules.Actions.Tests.csproj index 5c909a9..f8ca6d6 100644 --- a/tests/Modules.Windows.Registry.Actions.Tests/Modules.Windows.Registry.Actions.Tests.csproj +++ b/tests/Modules.Actions.Tests/Modules.Actions.Tests.csproj @@ -12,6 +12,7 @@ + diff --git a/tests/Modules.Windows.Registry.Actions.Tests/ActionSelectorTests.cs b/tests/Modules.Windows.Registry.Actions.Tests/ActionSelectorTests.cs deleted file mode 100644 index 3de1c8b..0000000 --- a/tests/Modules.Windows.Registry.Actions.Tests/ActionSelectorTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -// -------------------------------------------------------------- -// Copyright (c) Jesus Fernandez. All Rights Reserved. -// -------------------------------------------------------------- - -using System; -using System.Linq; -using Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK.ActionSelectors; -using NUnit.Framework; -using PowerAutomate.Desktop.Modules.Windows.Registry.Actions.Properties; - -namespace PowerAutomate.Desktop.Modules.Windows.Registry.Actions.Tests; - -[TestFixture] -public class ActionSelectorTests -{ - [Test] - public void Action_All_HasLocalizableResources() - { - var resourceManager = Resources.ResourceManager; - var resourcesType = typeof(Resources); - var assembly = resourcesType.Assembly; - var actionSelectorBaseType = typeof(ActionSelectorBase); - var actionSelectorTypes = assembly.ExportedTypes - .Where(type => actionSelectorBaseType.IsAssignableFrom(type)) - .Where(type => type is { IsGenericType: false, IsAbstract: false, IsPublic: true }) - .ToList(); - - foreach (var actionSelectorType in actionSelectorTypes) - { - var instance = (ActionSelectorBase)Activator.CreateInstance(actionSelectorType); - var summaryResource = resourceManager.GetString($"{instance.ActionName}_Summary"); - Assert.That(summaryResource, Is.Not.Null.Or.Empty, $"Action selector '{instance.ActionName}' doesn't have a summary resource"); - } - } -} \ No newline at end of file diff --git a/tests/Modules.Windows.Registry.Actions.Tests/ActionTests.cs b/tests/Modules.Windows.Registry.Actions.Tests/ActionTests.cs deleted file mode 100644 index b89c8ce..0000000 --- a/tests/Modules.Windows.Registry.Actions.Tests/ActionTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -// -------------------------------------------------------------- -// Copyright (c) Jesus Fernandez. All Rights Reserved. -// -------------------------------------------------------------- - -using System; -using System.Linq; -using System.Reflection; -using Microsoft.PowerPlatform.PowerAutomate.Desktop.Actions.SDK.Attributes; -using NUnit.Framework; -using PowerAutomate.Desktop.Modules.Windows.Registry.Actions.Properties; - -namespace PowerAutomate.Desktop.Modules.Windows.Registry.Actions.Tests; - -[TestFixture] -public class ActionTests -{ - [Test] - public void Action_All_Arguments_All_Enums_HasLocalizableResources() - { - var resourceManager = Resources.ResourceManager; - var resourcesType = typeof(Resources); - var assembly = resourcesType.Assembly; - var enumArgumentsByActions = assembly.ExportedTypes - .Select(type => (ActionType: type, ActionAttribute: type.GetCustomAttribute())) - .Where(pair => pair.ActionAttribute is not null) - .Select(pair => ( - pair.ActionType, - pair.ActionAttribute, - Arguments: pair.ActionType - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(property => (Property: property, InputArgumentAttribute: property.GetCustomAttribute(), OutputArgumentAttribute: property.GetCustomAttribute())) - .Where(property => property.InputArgumentAttribute is not null || property.OutputArgumentAttribute is not null) - .Where(property => property.Property.PropertyType.IsEnum) - .ToList()) - ) - .Where(pair => pair.Arguments.Any()) - .ToList(); - var enumValuesByPropertyTypes = enumArgumentsByActions.SelectMany(s => s.Arguments.Select(argument => argument.Property.PropertyType)) - .Distinct() - .Select(propertyType => (PropertyType: propertyType, Values: Enum.GetValues(propertyType) - .OfType() - .ToList())) - .ToList(); - foreach (var enumValuesByPropertyType in enumValuesByPropertyTypes) - { - foreach (var friendlyNameResource in enumValuesByPropertyType.Values.Select(value => resourceManager.GetString($"{enumValuesByPropertyType.PropertyType.Name}_{value}_FriendlyName"))) - { - Assert.That(friendlyNameResource, Is.Not.Null.Or.Empty, $"Enum argument '{enumValuesByPropertyType.PropertyType.Name}' doesn't have a friendly name resource"); - } - } - } - - [Test] - public void Action_All_Arguments_All_HasLocalizableResources() - { - var resourceManager = Resources.ResourceManager; - var resourcesType = typeof(Resources); - var assembly = resourcesType.Assembly; - var argumentsByActions = assembly.ExportedTypes - .Select(type => (ActionType: type, ActionAttribute: type.GetCustomAttribute())) - .Where(pair => pair.ActionAttribute is not null) - .Select(pair => ( - pair.ActionType, - pair.ActionAttribute, - Arguments: pair.ActionType - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(property => (Property: property, InputArgumentAttribute: property.GetCustomAttribute(), OutputArgumentAttribute: property.GetCustomAttribute())) - .Where(property => property.InputArgumentAttribute is not null || property.OutputArgumentAttribute is not null) - .ToList()) - ) - .ToList(); - - foreach (var argumentsByAction in argumentsByActions) - { - foreach (var argument in argumentsByAction.Arguments) - { - var friendlyNameResource = resourceManager.GetString($"{argumentsByAction.ActionAttribute.Id}_{argument.Property.Name}_FriendlyName"); - var descriptionResource = resourceManager.GetString($"{argumentsByAction.ActionAttribute.Id}_{argument.Property.Name}_Description"); - - Assert.That(friendlyNameResource, Is.Not.Null.Or.Empty, $"Argument '{argument.Property.Name}' in action '{argumentsByAction.ActionAttribute.Id}' doesn't have a friendly name resource"); - Assert.That(descriptionResource, Is.Not.Null.Or.Empty, $"Argument '{argument.Property.Name}' in action '{argumentsByAction.ActionAttribute.Id}' doesn't have a description resource"); - } - } - } - - [Test] - public void Action_All_Errors_All_HasLocalizableResources() - { - var resourceManager = Resources.ResourceManager; - var resourcesType = typeof(Resources); - var assembly = resourcesType.Assembly; - var errors = assembly.ExportedTypes - .Select(type => (ActionType: type, ActionAttribute: type.GetCustomAttribute())) - .Where(pair => pair.ActionAttribute is not null) - .SelectMany(pair => pair.ActionType.GetCustomAttributes()) - .Select(attribute => attribute.Name) - .Distinct() - .ToList(); - - foreach (var error in errors) - { - var friendlyNameResource = resourceManager.GetString($"Error_{error}_FriendlyName"); - var descriptionResource = resourceManager.GetString($"Error_{error}_Description"); - - Assert.That(friendlyNameResource, Is.Not.Null.Or.Empty, $"Error '{error}' doesn't have a friendly name resource"); - Assert.That(descriptionResource, Is.Not.Null.Or.Empty, $"Error '{error}' doesn't have a friendly name resource"); - } - } - - [Test] - public void Action_All_HasLocalizableResources() - { - var resourceManager = Resources.ResourceManager; - var resourcesType = typeof(Resources); - var assembly = resourcesType.Assembly; - var actions = assembly.ExportedTypes - .Select(type => (ActionType: type, ActionAttribute: type.GetCustomAttribute())) - .Where(pair => pair.ActionAttribute is not null) - .ToList(); - - foreach (var action in actions) - { - var friendlyNameResource = resourceManager.GetString($"{action.ActionAttribute.Id}_FriendlyName"); - var descriptionResource = resourceManager.GetString($"{action.ActionAttribute.Id}_Description"); - var summaryResource = resourceManager.GetString($"{action.ActionAttribute.Id}_Summary"); - - Assert.That(friendlyNameResource, Is.Not.Null.Or.Empty, $"Action '{action.ActionAttribute.Id}' doesn't have a friendly name resource"); - Assert.That(descriptionResource, Is.Not.Null.Or.Empty, $"Action '{action.ActionAttribute.Id}' doesn't have a description resource"); - Assert.That(summaryResource, Is.Not.Null.Or.Empty, $"Action '{action.ActionAttribute.Id}' doesn't have a summary resource"); - } - } -} \ No newline at end of file diff --git a/tests/Modules.Windows.Registry.Actions.Tests/ModuleTests.cs b/tests/Modules.Windows.Registry.Actions.Tests/ModuleTests.cs deleted file mode 100644 index 8576deb..0000000 --- a/tests/Modules.Windows.Registry.Actions.Tests/ModuleTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -// -------------------------------------------------------------- -// Copyright (c) Jesus Fernandez. All Rights Reserved. -// -------------------------------------------------------------- - -using System.Reflection; -using NUnit.Framework; -using PowerAutomate.Desktop.Modules.Windows.Registry.Actions.Properties; - -namespace PowerAutomate.Desktop.Modules.Windows.Registry.Actions.Tests; - -[TestFixture] -public class ModuleTests -{ - [Test] - public void Module_HasLocalizableResources() - { - var resourceManager = Resources.ResourceManager; - var resourcesType = typeof(Resources); - var assembly = resourcesType.Assembly; - var assemblyTitle = assembly.GetCustomAttribute(); - - var friendlyNameResource = resourceManager.GetString($"{assemblyTitle.Title}_FriendlyName"); - var descriptionResource = resourceManager.GetString($"{assemblyTitle.Title}_Description"); - - Assert.That(friendlyNameResource, Is.Not.Null.Or.Empty, $"Module '{assemblyTitle.Title}' doesn't have a friendly name resource"); - Assert.That(descriptionResource, Is.Not.Null.Or.Empty, $"Module '{assemblyTitle.Title}' doesn't have a description resource"); - } -} \ No newline at end of file