From 8d17540add856d785c10b5a9f28dab411df736b2 Mon Sep 17 00:00:00 2001 From: Gleb Khmyznikov Date: Wed, 5 Nov 2025 21:36:27 -0800 Subject: [PATCH] Add more reusable code --- .../UITestAutomation/SettingsConfigHelper.cs | 101 ++++++++++++++--- .../peek/Peek.UITests/PeekFilePreviewTests.cs | 104 ++++++++---------- 2 files changed, 133 insertions(+), 72 deletions(-) diff --git a/src/common/UITestAutomation/SettingsConfigHelper.cs b/src/common/UITestAutomation/SettingsConfigHelper.cs index 1aea4f37e1..833ec4f19d 100644 --- a/src/common/UITestAutomation/SettingsConfigHelper.cs +++ b/src/common/UITestAutomation/SettingsConfigHelper.cs @@ -21,6 +21,7 @@ namespace Microsoft.PowerToys.UITest public class SettingsConfigHelper { private static readonly JsonSerializerOptions IndentedJsonOptions = new() { WriteIndented = true }; + private static readonly SettingsUtils SettingsUtils = new SettingsUtils(); /// /// Configures global PowerToys settings to enable only specified modules and disable all others. @@ -36,13 +37,10 @@ namespace Microsoft.PowerToys.UITest try { - var settingsUtils = new SettingsUtils(); - - // Get settings or create default if they don't exist GeneralSettings settings; try { - settings = settingsUtils.GetSettingsOrDefault(); + settings = SettingsUtils.GetSettingsOrDefault(); } catch (Exception ex) { @@ -50,32 +48,24 @@ namespace Microsoft.PowerToys.UITest settings = new GeneralSettings(); } - // Convert settings to JSON string string settingsJson = settings.ToJsonString(); - - // Deserialize to JsonDocument to manipulate the Enabled modules using (JsonDocument doc = JsonDocument.Parse(settingsJson)) { var options = new JsonSerializerOptions { WriteIndented = true }; var root = doc.RootElement.Clone(); - // Get the enabled modules object if (root.TryGetProperty("enabled", out var enabledElement)) { - // Create a dictionary of module properties with their enable states var enabledModules = new Dictionary(); - // Iterate through all properties in the enabled object foreach (var property in enabledElement.EnumerateObject()) { string moduleName = property.Name; - // Check if this module should be enabled bool shouldEnable = Array.Exists(modulesToEnable, m => string.Equals(m, moduleName, StringComparison.Ordinal)); enabledModules[moduleName] = shouldEnable; } - // Rebuild the settings with updated enabled modules var settingsDict = JsonSerializer.Deserialize>(settingsJson); if (settingsDict != null) { @@ -85,8 +75,7 @@ namespace Microsoft.PowerToys.UITest } } - // Save the modified settings - settingsUtils.SaveSettings(settingsJson); + SettingsUtils.SaveSettings(settingsJson); string enabledList = modulesToEnable.Length > 0 ? string.Join(", ", modulesToEnable) : "none"; Debug.WriteLine($"Successfully updated global settings"); @@ -98,5 +87,89 @@ namespace Microsoft.PowerToys.UITest throw new InvalidOperationException($"Failed to configure global module settings: {ex.Message}", ex); } } + + /// + /// Updates a module's settings file. If the file doesn't exist, creates it with default content. + /// If the file exists, reads it and applies the provided update function to modify the settings. + /// + /// The name of the module (e.g., "Peek", "FancyZones"). + /// The default JSON content to use if the settings file doesn't exist. + /// + /// A callback function that modifies the settings dictionary. The function receives the deserialized settings + /// and should modify it in-place. The function should accept a Dictionary<string, object> and not return a value. + /// Example: (settings) => { ((Dictionary<string, object>)settings["properties"])["SomeSetting"] = newValue; } + /// + /// Thrown when moduleName or updateSettingsAction is null. + /// Thrown when settings file operations fail. + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is test code and will not be trimmed")] + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "This is test code and will not be AOT compiled")] + public static void UpdateModuleSettings( + string moduleName, + string defaultSettingsContent, + Action> updateSettingsAction) + { + ArgumentNullException.ThrowIfNull(moduleName); + ArgumentNullException.ThrowIfNull(updateSettingsAction); + + try + { + // Build the path to the module settings file + string powerToysSettingsDirectory = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "Microsoft", + "PowerToys"); + + string moduleDirectory = Path.Combine(powerToysSettingsDirectory, moduleName); + string settingsPath = Path.Combine(moduleDirectory, "settings.json"); + + // Ensure directory exists + Directory.CreateDirectory(moduleDirectory); + + // Read existing settings or use default + string existingJson = string.Empty; + if (File.Exists(settingsPath)) + { + existingJson = File.ReadAllText(settingsPath); + } + + Dictionary? settings; + + // If file doesn't exist or is empty, create from defaults + if (string.IsNullOrWhiteSpace(existingJson)) + { + if (string.IsNullOrWhiteSpace(defaultSettingsContent)) + { + throw new ArgumentException("Default settings content must be provided when file doesn't exist.", nameof(defaultSettingsContent)); + } + + settings = JsonSerializer.Deserialize>(defaultSettingsContent) + ?? throw new InvalidOperationException($"Failed to deserialize default settings for {moduleName}"); + + Debug.WriteLine($"Created default settings for {moduleName} at {settingsPath}"); + } + else + { + // Parse existing settings + settings = JsonSerializer.Deserialize>(existingJson) + ?? throw new InvalidOperationException($"Failed to deserialize existing settings for {moduleName}"); + + Debug.WriteLine($"Loaded existing settings for {moduleName} from {settingsPath}"); + } + + // Apply the update action to modify settings + updateSettingsAction(settings); + + // Serialize and save the updated settings using SettingsUtils + string updatedJson = JsonSerializer.Serialize(settings, IndentedJsonOptions); + SettingsUtils.SaveSettings(updatedJson, moduleName); + + Debug.WriteLine($"Successfully updated settings for {moduleName}"); + } + catch (Exception ex) + { + Debug.WriteLine($"ERROR in UpdateModuleSettings for {moduleName}: {ex.Message}"); + throw new InvalidOperationException($"Failed to update settings for {moduleName}: {ex.Message}", ex); + } + } } } diff --git a/src/modules/peek/Peek.UITests/PeekFilePreviewTests.cs b/src/modules/peek/Peek.UITests/PeekFilePreviewTests.cs index df04bd686c..fd57c444ca 100644 --- a/src/modules/peek/Peek.UITests/PeekFilePreviewTests.cs +++ b/src/modules/peek/Peek.UITests/PeekFilePreviewTests.cs @@ -47,30 +47,8 @@ public class PeekFilePreviewTests : UITestBase { try { - // Common base path for PowerToys settings - string powerToysSettingsDirectory = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "Microsoft", - "PowerToys"); - - // Fix Peek module settings - string peekDirectory = Path.Combine(powerToysSettingsDirectory, "Peek"); - string peekSettingsPath = Path.Combine(peekDirectory, "settings.json"); - - // Ensure directory exists - Directory.CreateDirectory(peekDirectory); - - // Check if file exists and is not empty - string existingPeekJson = string.Empty; - if (File.Exists(peekSettingsPath)) - { - existingPeekJson = File.ReadAllText(peekSettingsPath); - } - - // If file doesn't exist or is empty, create default settings - if (string.IsNullOrWhiteSpace(existingPeekJson)) - { - string peekSettingsContent = @"{ + // Default Peek settings + string peekSettingsContent = @"{ ""name"": ""Peek"", ""version"": ""1.0"", ""properties"": { @@ -96,50 +74,60 @@ public class PeekFilePreviewTests : UITestBase } } }"; - File.WriteAllText(peekSettingsPath, peekSettingsContent); - Debug.WriteLine($"Created default Peek settings file at {peekSettingsPath}"); - } - else - { - // Parse and update existing settings - using var peekDoc = JsonDocument.Parse(existingPeekJson); - var peekSettings = JsonSerializer.Deserialize>(existingPeekJson) - ?? throw new InvalidOperationException("Failed to deserialize Peek settings"); - // Get properties section - var propertiesElement = (JsonElement)peekSettings["properties"]; - var properties = JsonSerializer.Deserialize>(propertiesElement.GetRawText()) - ?? throw new InvalidOperationException("Failed to deserialize properties"); - - // Update only the required properties: ActivationShortcut and EnableSpaceToActivate - properties["ActivationShortcut"] = new Dictionary + // Update Peek module settings + SettingsConfigHelper.UpdateModuleSettings( + "Peek", + peekSettingsContent, + (settings) => { - { "win", false }, - { "ctrl", true }, - { "alt", false }, - { "shift", false }, - { "code", 32 }, - { "key", "Space" }, - }; + // Get or ensure properties section exists + Dictionary properties; - properties["EnableSpaceToActivate"] = new Dictionary - { - { "value", false }, - }; + if (settings.TryGetValue("properties", out var propertiesObj)) + { + if (propertiesObj is Dictionary dict) + { + properties = dict; + } + else if (propertiesObj is JsonElement jsonElem) + { + properties = JsonSerializer.Deserialize>(jsonElem.GetRawText()) + ?? throw new InvalidOperationException("Failed to deserialize properties"); + } + else + { + properties = new Dictionary(); + } + } + else + { + properties = new Dictionary(); + } - peekSettings["properties"] = properties; + // Update the required properties + properties["ActivationShortcut"] = new Dictionary + { + { "win", false }, + { "ctrl", true }, + { "alt", false }, + { "shift", false }, + { "code", 32 }, + { "key", "Space" }, + }; - // Serialize and save Peek settings - string peekSettingsJson = JsonSerializer.Serialize(peekSettings, IndentedJsonOptions); - File.WriteAllText(peekSettingsPath, peekSettingsJson); + properties["EnableSpaceToActivate"] = new Dictionary + { + { "value", false }, + }; - Debug.WriteLine($"Successfully updated Ctrl+Space shortcut in settings file at {peekSettingsPath}"); - } + settings["properties"] = properties; + }); // Disable all modules except Peek in global settings SettingsConfigHelper.ConfigureGlobalModuleSettings("Peek"); - Debug.WriteLine("Successfully updated global settings - disabled all modules except Peek"); + Debug.WriteLine("Successfully updated all settings - Peek shortcut configured and all modules except Peek disabled"); } catch (Exception ex) {