using AvParser.Core.Proxies; using AvParser.Core.Settings; using AvParser.Infrastructure.Proxies; using AvParser.UI.Services; using AvParser.UI.ViewModels; using ReactiveUI.Primitives.Signals; namespace AvParser.UI.HeadlessTests; /// /// Test doubles for the headless tests. /// /// /// Intentionally duplicated from AvParser.UI.Tests rather than shared through a fourth /// project: these are a few lines of trivial stand-ins, and a shared test-kit assembly would have /// to opt out of the tests/ conventions (self-executing xUnit exe) to build at all. /// internal sealed class FakePage(string titleKey, string iconKey = "IconHome") : PageViewModel { public override string TitleKey { get; } = titleKey; public override string IconKey { get; } = iconKey; } /// internal sealed class FakeThemeService(AppTheme initial = AppTheme.System) : IThemeService, IDisposable { private readonly BehaviorSignal _current = new(initial); public AppTheme Current => _current.Value; public IObservable Changes => _current; public void Apply(AppTheme theme) { if (theme != _current.Value) { _current.OnNext(theme); } } public void Dispose() => _current.Dispose(); } /// In-memory settings, so tests never touch the developer's real profile. internal sealed class FakeSettingsService(AppSettings? initial = null) : ISettingsService, IDisposable { private readonly BehaviorSignal _current = new(initial ?? new AppSettings()); public AppSettings Current => _current.Value; public IObservable Changes => _current; public void Update(Func mutate) => _current.OnNext(mutate(_current.Value)); public Task FlushAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; public void Dispose() => _current.Dispose(); } /// An editable proxy list held in memory. internal sealed class FakeMutableProxySource : IMutableProxySource { public string Id => "fake-custom"; public string DisplayName => "Fake custom list"; public ProxySourceKind Kind => ProxySourceKind.Custom; public List Endpoints { get; } = []; public Task> GetProxiesAsync(CancellationToken cancellationToken = default) => Task.FromResult>(Endpoints.ToArray()); public Task AddAsync(IEnumerable endpoints, CancellationToken cancellationToken = default) { Endpoints.AddRange(endpoints); return Task.FromResult(Endpoints.Count); } public Task RemoveAsync(ProxyEndpoint endpoint, CancellationToken cancellationToken = default) => Task.FromResult(Endpoints.RemoveAll(e => e.Key == endpoint.Key) > 0); public Task ClearAsync(CancellationToken cancellationToken = default) { Endpoints.Clear(); return Task.CompletedTask; } } /// A probe that says everything is dead. The view never calls it in these tests. internal sealed class FakeProxyProbe : IProxyProbe { public Task ProbeAsync( ProxyEndpoint endpoint, ProxyOptions options, CancellationToken cancellationToken = default ) => Task.FromResult(ProxyProbeResult.Failure("not probed in tests")); } /// A remembered-state store that keeps everything in memory. internal sealed class FakeProxyStateStore : IProxyStateStore { public Dictionary State { get; } = new(StringComparer.Ordinal); public Task> LoadAsync( CancellationToken cancellationToken = default ) => Task.FromResult>(State); public Task SaveAsync(IEnumerable entries, CancellationToken cancellationToken = default) => Task.CompletedTask; } /// /// A loader that does nothing. /// /// /// The real one probes on startup, which would race these view tests: the load is fired from the /// page constructor and would mark every fake proxy dead partway through an assertion. /// internal sealed class FakeProxyPoolLoader : IProxyPoolLoader { public bool IsLoaded => true; public bool IsToppingUp => false; public Task EnsureLoadedAsync() => Task.FromResult(new ProxyPoolLoadResult(0, 0, 0)); public Task SaveStateAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; }