using AvParser.Core.Proxies;
using AvParser.Core.Settings;
using AvParser.Infrastructure.Storage;
using AvParser.UI.Services;
using AvParser.UI.Tests.Fakes;
using AvParser.UI.ViewModels;
using ReactiveUI.Primitives;
using ReactiveUI.Primitives.Concurrency;
using Serilog.Core;
namespace AvParser.UI.Tests;
public class SettingsViewModelTests
{
/// Language service that applies nothing; these tests are not about translation.
private sealed class FakeLocalizationService : ILocalizationService, IDisposable
{
private readonly ReactiveUI.Primitives.Signals.BehaviorSignal _current = new(AppLanguage.English);
public AppLanguage Current => _current.Value;
public IObservable Changes => _current;
public void Apply(AppLanguage language) => _current.OnNext(language);
public void Dispose() => _current.Dispose();
}
/// A picker that answers with whatever the test decided, without a window.
private sealed class FakeFolderPicker : IFolderPicker
{
public string? Answer { get; set; }
public string? StartedAt { get; private set; }
public Task PickAsync(string title, string? startAt = null)
{
StartedAt = startAt;
return Task.FromResult(Answer);
}
}
private static (SettingsViewModel Page, FakeSettingsService Settings, FakeFolderPicker Picker) Build(
AppSettings? settings = null,
string? mediaDirectory = null
)
{
var settingsService = new FakeSettingsService(settings);
var picker = new FakeFolderPicker();
var paths = new AppPaths(
Path.Combine(Path.GetTempPath(), "AvParserTests", Guid.NewGuid().ToString("N")),
mediaDirectory
);
var page = new SettingsViewModel(
settingsService,
new FakeThemeService(),
paths,
new LoggingLevelSwitch(),
new ProxyPool([], new FakeProxyProbe(), new ProxyOptions()),
new FakeLocalizationService(),
picker,
ImmediateSequencer.Instance
);
return (page, settingsService, picker);
}
[Fact]
public void An_unset_media_root_shows_as_empty_so_the_watermark_can_speak()
{
var (page, _, _) = Build();
page.MediaRootOverride.ShouldBeEmpty();
page.MediaDirectoryNeedsRestart.ShouldBeFalse();
}
[Fact]
public async Task Choosing_a_directory_records_it()
{
var chosen = Path.Combine(Path.GetTempPath(), "AvParserArchive");
var (page, settings, picker) = Build();
picker.Answer = chosen;
await page.BrowseMediaDirectoryCommand.Execute().ToTask(TestContext.Current.CancellationToken);
page.MediaRootOverride.ShouldBe(chosen);
settings.Current.MediaRootOverride.ShouldBe(chosen);
}
[Fact]
public async Task Cancelling_the_picker_changes_nothing()
{
var (page, _, picker) = Build();
page.MediaRootOverride = "D:/archive";
picker.Answer = null;
await page.BrowseMediaDirectoryCommand.Execute().ToTask(TestContext.Current.CancellationToken);
page.MediaRootOverride.ShouldBe("D:/archive");
}
[Fact]
public async Task Resetting_clears_the_override_so_the_default_applies_again()
{
var (page, settings, _) = Build(new AppSettings { MediaRootOverride = "D:/archive" });
page.MediaRootOverride.ShouldBe("D:/archive");
await page.ResetMediaDirectoryCommand.Execute().ToTask(TestContext.Current.CancellationToken);
page.MediaRootOverride.ShouldBeEmpty();
settings.Current.MediaRootOverride.ShouldBeNull();
}
[Fact]
public void A_root_that_differs_from_this_run_asks_for_a_restart()
{
// Paths are resolved before the container exists, so the change cannot take effect now.
// Saying so beats writing somewhere other than the box claims.
var (page, _, _) = Build(mediaDirectory: "D:/current-archive");
page.MediaRootOverride = "D:/somewhere-else";
page.MediaDirectoryNeedsRestart.ShouldBeTrue();
}
[Fact]
public void A_root_that_matches_this_run_does_not()
{
var (page, _, _) = Build(mediaDirectory: "D:/current-archive");
page.MediaRootOverride = "D:/current-archive/";
page.MediaDirectoryNeedsRestart.ShouldBeFalse();
}
[Fact]
public async Task Whitespace_is_treated_as_no_override_at_all()
{
var (page, settings, _) = Build(new AppSettings { MediaRootOverride = "D:/archive" });
page.MediaRootOverride = " ";
await Task.Delay(50, TestContext.Current.CancellationToken);
settings.Current.MediaRootOverride.ShouldBeNull();
page.MediaDirectoryNeedsRestart.ShouldBeFalse();
}
}