using System; using System.Windows; using Microsoft.Data.Sqlite; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Threading; using Xunit; using ytLive.Controls; using ytLive.Models; using ytLive.Services.Entitlement; using ytLive.ViewModels; namespace ytLive.Tests; /// /// The About overlay is the in-app creator hub: it opens from the real window, /// and its licensing sub-panel surfaces the full THIRD-PARTY-NOTICES.txt /// in-app (scrolling text, never the OS viewer / Notepad). Drives the real VM /// through the real window — same real-App + temp-DB pattern as the round-clip /// and source-naming tests. /// [Collection("RealApp")] public sealed class AboutHubTests { private readonly RealAppHost _app; public AboutHubTests(RealAppHost app) { _app = app; } [Fact] public void About_Opens_InApp_Licensing_Loads_Shipped_Notices() { _app.Run(Run); } private void Run() { var tempDb = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"ytLlive-about-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = tempDb; var window = new MainWindow(); var vm = (MainViewModel)window.DataContext; try { // About hub opens (the wordmark / gear menu entry point). vm.OpenAboutCommand.Execute(null); Assert.True(vm.IsAboutOpen); Assert.False(vm.IsLicensesOpen); // The hub links resolve to real destinations. Assert.Equal("https://youtube.com/@llamachileshop", MainViewModel.ChannelUrl); Assert.Equal("https://mastodon.llamachile.tube/@gramps", MainViewModel.MastodonUrl); Assert.Equal("https://buymeacoffee.com/llamachiley", MainViewModel.CoffeeUrl); Assert.Equal("https://llamacasty.com", MainViewModel.PremiumUrl); // The licensing panel loads the full shipped notices text in-app. vm.OpenLicensesCommand.Execute(null); Assert.True(vm.IsLicensesOpen); Assert.False(string.IsNullOrWhiteSpace(vm.LicensesText)); Assert.Contains("Third-Party Notices", vm.LicensesText, StringComparison.OrdinalIgnoreCase); Assert.Contains("LGPL", vm.LicensesText, StringComparison.OrdinalIgnoreCase); // Back returns to the hub view (code-behind click handler path is // equivalent; here we assert the VM contract the handler uses). vm.IsLicensesOpen = false; Assert.True(vm.IsAboutOpen); } finally { window.Close(); MainViewModel.LayoutPathOverride = null; SqliteConnection.ClearAllPools(); try { System.IO.File.Delete(tempDb); } catch { /* best-effort cleanup */ } } } [Fact] public void Free_user_gets_1080p30_and_cannot_select_or_change_to_60fps() { _app.Run(() => { var tempDb = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"llamacasty-upgrade-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = tempDb; MainWindow? window = null; try { window = new MainWindow(); var vm = (MainViewModel)window.DataContext; vm.EntitlementOverride = new FakeEntitlement(false, "Steam could not verify ownership. Open Steam, sign in, then restart LlamaCasty."); window.Show(); window.UpdateLayout(); var freeTier = vm.QualityOptions.Single(option => option.Display == "1080p30"); var lockedTier = vm.QualityOptions.First(option => option.Fps == 60); Assert.Same(freeTier, vm.SelectedQuality); var errorPanel = (Border)window.FindName("EntitlementErrorPanel"); var errorText = (TextBlock)window.FindName("EntitlementErrorText"); Assert.Equal(Visibility.Visible, errorPanel.Visibility); Assert.Contains("Open Steam, sign in", errorText.Text); var picker = (ComboBox)window.FindName("QualityPicker"); Assert.True(vm.CanChangeQuality); Assert.True(picker.IsEnabled); picker.IsDropDownOpen = true; picker.UpdateLayout(); Dispatcher.CurrentDispatcher.Invoke(() => { }, DispatcherPriority.Loaded); var lockedItem = (ComboBoxItem)picker.ItemContainerGenerator.ContainerFromItem(lockedTier)!; Assert.False(lockedItem.IsEnabled); var struckText = FindVisualChild(lockedItem); Assert.NotNull(struckText); Assert.Contains(struckText!.TextDecorations, decoration => decoration.Location == TextDecorationLocation.Strikethrough); picker.SelectedItem = lockedTier; picker.UpdateLayout(); Assert.Same(freeTier, vm.SelectedQuality); Assert.False(vm.IsAboutOpen); picker.IsDropDownOpen = false; vm.IsRecording = true; window.UpdateLayout(); Assert.False(vm.CanChangeQuality); Assert.False(picker.IsEnabled); vm.SelectedQuality = vm.QualityOptions.Single(option => option.Display == "720p30"); Assert.Same(freeTier, vm.SelectedQuality); vm.IsRecording = false; vm.StreamStatus = StreamStatus.Streaming; window.UpdateLayout(); Assert.False(vm.CanChangeQuality); Assert.False(picker.IsEnabled); vm.SelectedQuality = vm.QualityOptions.Single(option => option.Display == "720p30"); Assert.Same(freeTier, vm.SelectedQuality); vm.StreamStatus = StreamStatus.Offline; Assert.True(vm.CanChangeQuality); Assert.True(picker.IsEnabled); } finally { window?.Close(); MainViewModel.LayoutPathOverride = null; SqliteConnection.ClearAllPools(); try { System.IO.File.Delete(tempDb); } catch { /* best-effort cleanup */ } } }); } private static T? FindVisualChild(DependencyObject parent) where T : DependencyObject { for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); if (child is T match) return match; var nested = FindVisualChild(child); if (nested != null) return nested; } return null; } }