Compare commits

...

4 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
3ce7f4f781 Address code review feedback
- Remove redundant SubscribeToPasteAIProviders call from InitializePasteAIProviderState since AttachConfigurationHandlers already handles all subscriptions
- Remove unnecessary GetDispatcherQueue override from test class as AdvancedPasteViewModel doesn't use a virtual method pattern for dispatcher

Co-authored-by: moooyo <42196638+moooyo@users.noreply.github.com>
2026-01-29 12:19:36 +00:00
copilot-swe-agent[bot]
e6818707c3 Add unit tests for AdvancedPasteViewModel initialization
Add tests to verify that AdvancedPasteViewModel initializes correctly when settings file has null PasteAIConfiguration or AdditionalActions properties. This validates the fix for the settings page crash.

Co-authored-by: moooyo <42196638+moooyo@users.noreply.github.com>
2026-01-29 12:17:15 +00:00
copilot-swe-agent[bot]
79deb26343 Fix Advanced Paste settings crash by reordering initialization
Move InitializePasteAIProviderState() before AttachConfigurationHandlers() to ensure PasteAIConfiguration is initialized before event handlers are attached. This prevents null reference crashes when loading settings files that lack the PasteAIConfiguration property.

Co-authored-by: moooyo <42196638+moooyo@users.noreply.github.com>
2026-01-29 12:16:28 +00:00
copilot-swe-agent[bot]
8f84611c14 Initial plan 2026-01-29 12:12:54 +00:00
3 changed files with 126 additions and 4 deletions

View File

@@ -0,0 +1,122 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace ViewModelTests
{
[TestClass]
public class AdvancedPaste
{
private Mock<SettingsUtils> mockAdvancedPasteSettingsUtils;
[TestInitialize]
public void SetUpStubSettingUtils()
{
mockAdvancedPasteSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils<AdvancedPasteSettings>();
}
private sealed class TestAdvancedPasteViewModel : AdvancedPasteViewModel
{
public TestAdvancedPasteViewModel(
SettingsUtils settingsUtils,
ISettingsRepository<GeneralSettings> generalSettingsRepository,
ISettingsRepository<AdvancedPasteSettings> advancedPasteSettingsRepository,
Func<string, int> ipcMSGCallBackFunc)
: base(settingsUtils, generalSettingsRepository, advancedPasteSettingsRepository, ipcMSGCallBackFunc)
{
}
}
[TestMethod]
public void ViewModelInitialization_WithEmptySettings_ShouldNotCrash()
{
// Arrange
var mockGeneralSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils<GeneralSettings>();
var generalSettingsRepository = new Mock<ISettingsRepository<GeneralSettings>>();
generalSettingsRepository.Setup(x => x.SettingsConfig).Returns(new GeneralSettings());
var advancedPasteSettings = new AdvancedPasteSettings();
var advancedPasteSettingsRepository = new Mock<ISettingsRepository<AdvancedPasteSettings>>();
advancedPasteSettingsRepository.Setup(x => x.SettingsConfig).Returns(advancedPasteSettings);
Func<string, int> sendMockIPCConfigMSG = msg => 0;
// Act - Creating the ViewModel should not throw
var viewModel = new TestAdvancedPasteViewModel(
mockAdvancedPasteSettingsUtils.Object,
generalSettingsRepository.Object,
advancedPasteSettingsRepository.Object,
sendMockIPCConfigMSG);
// Assert
Assert.IsNotNull(viewModel);
Assert.IsNotNull(viewModel.PasteAIConfiguration, "PasteAIConfiguration should be initialized");
}
[TestMethod]
public void ViewModelInitialization_WithNullPasteAIConfiguration_ShouldInitialize()
{
// Arrange
var mockGeneralSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils<GeneralSettings>();
var generalSettingsRepository = new Mock<ISettingsRepository<GeneralSettings>>();
generalSettingsRepository.Setup(x => x.SettingsConfig).Returns(new GeneralSettings());
var advancedPasteSettings = new AdvancedPasteSettings();
// Simulate old settings file where PasteAIConfiguration might be null
advancedPasteSettings.Properties.PasteAIConfiguration = null;
var advancedPasteSettingsRepository = new Mock<ISettingsRepository<AdvancedPasteSettings>>();
advancedPasteSettingsRepository.Setup(x => x.SettingsConfig).Returns(advancedPasteSettings);
Func<string, int> sendMockIPCConfigMSG = msg => 0;
// Act - Creating the ViewModel should not throw even with null PasteAIConfiguration
var viewModel = new TestAdvancedPasteViewModel(
mockAdvancedPasteSettingsUtils.Object,
generalSettingsRepository.Object,
advancedPasteSettingsRepository.Object,
sendMockIPCConfigMSG);
// Assert
Assert.IsNotNull(viewModel);
Assert.IsNotNull(viewModel.PasteAIConfiguration, "PasteAIConfiguration should be initialized even when starting as null");
Assert.IsNotNull(viewModel.PasteAIConfiguration.Providers, "Providers collection should be initialized");
}
[TestMethod]
public void ViewModelInitialization_WithNullAdditionalActions_ShouldInitialize()
{
// Arrange
var mockGeneralSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils<GeneralSettings>();
var generalSettingsRepository = new Mock<ISettingsRepository<GeneralSettings>>();
generalSettingsRepository.Setup(x => x.SettingsConfig).Returns(new GeneralSettings());
var advancedPasteSettings = new AdvancedPasteSettings();
// Simulate old settings file where AdditionalActions might be null
advancedPasteSettings.Properties.AdditionalActions = null;
var advancedPasteSettingsRepository = new Mock<ISettingsRepository<AdvancedPasteSettings>>();
advancedPasteSettingsRepository.Setup(x => x.SettingsConfig).Returns(advancedPasteSettings);
Func<string, int> sendMockIPCConfigMSG = msg => 0;
// Act - Creating the ViewModel should not throw
var viewModel = new TestAdvancedPasteViewModel(
mockAdvancedPasteSettingsUtils.Object,
generalSettingsRepository.Object,
advancedPasteSettingsRepository.Object,
sendMockIPCConfigMSG);
// Assert
Assert.IsNotNull(viewModel);
}
}
}

View File

@@ -95,6 +95,10 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_advancedPasteSettings.Properties.AdditionalActions ??= new AdvancedPasteAdditionalActions();
_advancedPasteSettings.Properties.CustomActions ??= new AdvancedPasteCustomActions();
// Ensure PasteAIConfiguration is initialized before attaching handlers
// This prevents crashes when settings file doesn't have PasteAIConfiguration property
InitializePasteAIProviderState();
AttachConfigurationHandlers();
// set the callback functions value to handle outgoing IPC message.
@@ -105,8 +109,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
SetupSettingsFileWatcher();
InitializePasteAIProviderState();
InitializeEnabledValue();
MigrateLegacyAIEnablement();
@@ -1428,8 +1430,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
pasteConfig.Providers ??= new ObservableCollection<PasteAIProviderDefinition>();
SubscribeToPasteAIProviders(pasteConfig);
}
private static string RetrieveCredentialValue(string credentialResource, string credentialUserName)

0
tools/build/build.cmd Normal file → Executable file
View File