using System.Reactive.Linq; using System.Reactive.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using PLib.Application.Library; using PLib.Desktop.Services; using PLib.Desktop.Settings; using PLib.Desktop.ViewModels; using Shouldly; namespace PLib.Tests.Settings; public sealed class SettingsViewModelTests { private readonly IAppSettingsStore _store = Substitute.For(); [Fact] public async Task Opening_and_saving_without_touching_anything_changes_nothing() { // Both of these are displayed in a lossier unit than they are stored in: 65 536 bytes // shows as 0,06 MB. Converting the untouched field back used to yield 62 915, which // silently rewrote the setting and forced a full rescan on every visit to the panel. var options = new LibraryOptions { Folders = [@"C:\videos"], MinimumFileSizeInBytes = 65_536, ThumbnailPositionRatio = 0.15, }; var (viewModel, outcome) = await SaveAsync(options); outcome.RescanRequired.ShouldBeFalse(); Written.MinimumFileSizeInBytes.ShouldBe(65_536); Written.ThumbnailPositionRatio.ShouldBe(0.15); viewModel.HasFolders.ShouldBeTrue(); } [Fact] public async Task Editing_the_minimum_size_does_write_the_new_value() { var (_, outcome) = await SaveAsync( new LibraryOptions { Folders = [@"C:\videos"], MinimumFileSizeInBytes = 65_536 }, viewModel => viewModel.MinimumFileSizeMegabytes = 4); Written.MinimumFileSizeInBytes.ShouldBe(4 * 1024 * 1024); outcome.RescanRequired.ShouldBeTrue(); } [Fact] public async Task Removing_a_folder_is_carried_into_the_saved_settings() { var (_, outcome) = await SaveAsync( new LibraryOptions { Folders = [@"C:\a", @"C:\b"] }, viewModel => viewModel.Folders[0].RemoveCommand.Execute().Subscribe()); Written.Folders.ShouldBe([@"C:\b"]); outcome.RescanRequired.ShouldBeTrue(); } [Fact] public async Task Saving_the_panel_keeps_the_settings_it_does_not_edit() { // The panel writes the whole file, so anything outside its own screens — the // playback volume, for one — has to be carried through untouched. await SaveAsync( new LibraryOptions { Folders = [@"C: ideos"] }, viewModel => viewModel.ThumbnailWidth = 720, new PlaybackOptions { Volume = 0.42, IsMuted = true }); Written.ThumbnailWidth.ShouldBe(720); Written.Volume.ShouldBe(0.42); Written.IsMuted.ShouldBeTrue(); } [Fact] public async Task Cancelling_writes_nothing() { var viewModel = Create(new LibraryOptions { Folders = [@"C:\videos"] }); var closed = viewModel.Closed.FirstAsync().ToTask(); viewModel.MinimumFileSizeMegabytes = 999; await viewModel.CancelCommand.Execute().FirstAsync().ToTask(TestContext.Current.CancellationToken); (await closed).Saved.ShouldBeFalse(); _store.ReceivedCalls().ShouldNotContain(call => call.GetMethodInfo().Name == nameof(IAppSettingsStore.SaveAsync)); } private AppSettings Written => (AppSettings)_store.ReceivedCalls() .Single(call => call.GetMethodInfo().Name == nameof(IAppSettingsStore.SaveAsync)) .GetArguments()[0]!; private async Task<(SettingsViewModel ViewModel, SettingsDialogOutcome Outcome)> SaveAsync( LibraryOptions options, Action? edit = null, PlaybackOptions? playback = null) { var viewModel = Create(options, playback); var closed = viewModel.Closed.FirstAsync().ToTask(); edit?.Invoke(viewModel); await viewModel.SaveCommand.Execute().FirstAsync().ToTask(TestContext.Current.CancellationToken); return (viewModel, await closed); } private SettingsViewModel Create(LibraryOptions options, PlaybackOptions? playback = null) { _store.Current.Returns( AppSettings.From(options, new AppearanceOptions(), playback ?? new PlaybackOptions())); return new SettingsViewModel( Substitute.For(), _store, Substitute.For(), Substitute.For(), NullLogger.Instance); } }