diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Gallery/ExtensionGalleryItemViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Gallery/ExtensionGalleryItemViewModel.cs index 4f643fc61ce3..c5ba9c9d8cf8 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Gallery/ExtensionGalleryItemViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Gallery/ExtensionGalleryItemViewModel.cs @@ -105,6 +105,13 @@ public ExtensionGalleryItemViewModel( public string? Homepage => _entry.Homepage; + // Validated, browser-openable homepage uri. Null when the entry has no + // homepage or it is not a web uri. NavigateUri bindings must use this + // (a Uri) rather than the raw Homepage string: x:Bind evaluates bindings + // regardless of element visibility, and converting a null/invalid string + // to Uri throws and crashes the page. + public Uri? HomepageUri => _homepageHttpUri; + public Uri IconUri { get; } public ImageSource IconSource diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionGalleryItemPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionGalleryItemPage.xaml index 3ed9a4f6b405..cc9585cd4fee 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionGalleryItemPage.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionGalleryItemPage.xaml @@ -232,7 +232,7 @@ Grid.Row="3" Padding="0" AutomationProperties.AutomationId="CmdPal_GalleryItemPage_ViewRepository" - NavigateUri="{x:Bind ViewModel.Homepage, Mode=OneWay}" + NavigateUri="{x:Bind ViewModel.HomepageUri, Mode=OneWay}" ToolTipService.ToolTip="{x:Bind ViewModel.Homepage, Mode=OneWay}" Visibility="{x:Bind help:BindTransformers.BoolToVisibility(ViewModel.HasHomepage), Mode=OneWay, FallbackValue=Collapsed}"> diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/ExtensionGalleryItemViewModelTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/ExtensionGalleryItemViewModelTests.cs index fc7469b32094..563f2ead1cb3 100644 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/ExtensionGalleryItemViewModelTests.cs +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests/ExtensionGalleryItemViewModelTests.cs @@ -119,6 +119,7 @@ public void Constructor_IgnoresNonWebGalleryLinks() var viewModel = CreateViewModel(entry); Assert.IsFalse(viewModel.HasHomepage); + Assert.IsNull(viewModel.HomepageUri); Assert.IsFalse(viewModel.HasAuthorUrl); Assert.IsFalse(viewModel.HasUrlSource); Assert.IsFalse(viewModel.HasActionableSourceDetails); @@ -131,6 +132,32 @@ public void Constructor_IgnoresNonWebGalleryLinks() Assert.IsFalse(viewModel.OpenInstallUrlCommand.CanExecute(null)); } + [TestMethod] + public void Constructor_SetsHomepageUri_WhenHomepageIsWebUri() + { + var entry = CreateEntry(iconUrl: null); + entry.Homepage = "https://example.com/extension"; + + var viewModel = CreateViewModel(entry); + + Assert.IsTrue(viewModel.HasHomepage); + Assert.AreEqual(new Uri("https://example.com/extension"), viewModel.HomepageUri); + Assert.IsTrue(viewModel.OpenHomepageCommand.CanExecute(null)); + } + + [TestMethod] + public void Constructor_LeavesHomepageUriNull_WhenHomepageIsMissing() + { + var entry = CreateEntry(iconUrl: null); + entry.Homepage = null; + + var viewModel = CreateViewModel(entry); + + Assert.IsFalse(viewModel.HasHomepage); + Assert.IsNull(viewModel.HomepageUri); + Assert.IsFalse(viewModel.OpenHomepageCommand.CanExecute(null)); + } + [TestMethod] public void Constructor_EnablesCopyCommand_WhenWinGetIdIsAvailable() {