using System.Runtime.CompilerServices; using Avalonia.Controls; using Avalonia.Headless.XUnit; using Avalonia.Threading; using Avalonia.VisualTree; using AvParser.Core.Collecting; using AvParser.Core.Parsing; using AvParser.Core.Proxies; using AvParser.Core.Settings; using AvParser.UI.ViewModels; using AvParser.UI.Views; using Microsoft.Extensions.Logging.Abstractions; using ReactiveUI.Primitives.Concurrency; namespace AvParser.UI.HeadlessTests; public class CollectViewTests { private sealed class StubSource(string id, string name, bool network) : IMediaSource { public string Id => id; public string DisplayName => name; public string Description => "A source"; public bool RequiresNetwork => network; public bool CanParse(MediaQuery input) => true; public async IAsyncEnumerable> ParseAsync( MediaQuery input, IProgress? progress, [EnumeratorCancellation] CancellationToken cancellationToken ) { await Task.Yield(); yield break; } } private sealed class IdleRunner : ICollectRunner { public async IAsyncEnumerable> RunAsync( IMediaSource source, MediaQuery query, CollectOptions options, IProgress? progress, [EnumeratorCancellation] CancellationToken cancellationToken ) { await Task.Yield(); yield break; } } private sealed class EmptyServiceProvider : IServiceProvider { public object? GetService(Type serviceType) => null; } private static (CollectView View, CollectViewModel ViewModel, Window Window) ShowPage(bool networkSource) { IMediaSource[] sources = networkSource ? [new StubSource("url-list", "URL list", false), new StubSource("own-service", "Own service", true)] : [new StubSource("url-list", "URL list", false)]; var viewModel = new CollectViewModel( new MediaSourceCatalog(sources, networkSource ? "own-service" : "url-list"), new FakeSettingsService(new AppSettings { LastSourceId = networkSource ? "own-service" : "url-list" }), new ProxyPool([], new FakeProxyProbe(), new ProxyOptions()), new IdleRunner(), new FakeMediaStore(), new FakeThumbnailCache(), new EmptyServiceProvider(), NullLogger.Instance, ImmediateSequencer.Instance ); var view = new CollectView { DataContext = viewModel }; var window = new Window { Width = 1400, Height = 900, Content = view, }; window.Show(); Dispatcher.UIThread.RunJobs(); return (view, viewModel, window); } private static Border Banner(CollectView view) => view.FindControl("ProxyGateBanner").ShouldNotBeNull(); [AvaloniaFact] public void The_page_renders() { var (view, _, _) = ShowPage(networkSource: false); view.GetVisualDescendants().OfType().ShouldNotBeEmpty(); } [AvaloniaFact] public void No_banner_is_shown_for_a_source_that_needs_no_network() { var (view, viewModel, _) = ShowPage(networkSource: false); viewModel.IsBlockedWithoutProxy.ShouldBeFalse(); Banner(view).IsVisible.ShouldBeFalse(); } [AvaloniaFact] public void A_blocked_network_source_puts_the_banner_on_screen() { // Rendered rather than asserted on the view model: an IsVisible binding that never fires // leaves the page silently unhelpful, which is exactly the failure this guards. var (view, viewModel, _) = ShowPage(networkSource: true); Dispatcher.UIThread.RunJobs(); viewModel.IsBlockedWithoutProxy.ShouldBeTrue(); Banner(view).IsEffectivelyVisible.ShouldBeTrue(); } [AvaloniaFact] public void The_banner_offers_a_way_to_the_proxies_page() { var (view, viewModel, _) = ShowPage(networkSource: true); Dispatcher.UIThread.RunJobs(); var button = Banner(view).GetVisualDescendants().OfType