using System; using System.IO; using Xunit; using ytLive.ViewModels; namespace ytLive.Tests; /// /// Zoom box follow mode (creator ruling 2026-11-01): the Settings pill toggles between **Anchor** /// — the capture stays on the clicked point, which is the default and the 2026-10-08 model — and /// **Follow**, where dragging the box re-anchors the capture to the box centre so the magnifier /// shows whatever it hovers over. /// // Shares the static MainViewModel.LayoutPathOverride with the sibling class; without a // shared collection xUnit runs them in parallel and the two view models fight over one temp DB. [Collection("MainViewModelLayoutPath")] public class ZoomFollowModeTests { private static MainViewModel NewVm(out string dbPath) { dbPath = Path.Combine(Path.GetTempPath(), $"llamacasty-zoom-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = dbPath; return new MainViewModel(); } [Fact] public void Anchor_is_the_default_and_dragging_leaves_the_capture_alone() { var vm = NewVm(out var dbPath); try { Assert.False(vm.ZoomFollowsBox); vm.ActivateZoom(1000, 500); var captureX = vm.ZoomState.CaptureX; vm.StartZoomMove(1000, 500); vm.UpdateZoomMove(1200, 700); Assert.Equal(captureX, vm.ZoomState.CaptureX); } finally { MainViewModel.LayoutPathOverride = null; try { File.Delete(dbPath); } catch (IOException) { } } } [Fact] public void Follow_moves_the_capture_with_the_box() { var vm = NewVm(out var dbPath); try { vm.ActivateZoom(1000, 500); var anchoredCaptureX = vm.ZoomState.CaptureX; vm.ZoomFollowsBox = true; vm.StartZoomMove(1000, 500); vm.UpdateZoomMove(1200, 700); Assert.NotEqual(anchoredCaptureX, vm.ZoomState.CaptureX); // The capture is half the box, centred on the box: box centre minus a quarter of it. var expected = Math.Clamp( vm.ZoomState.X + vm.ZoomState.Width / 2 - vm.ZoomState.Width / 4, 0, 1920 - vm.ZoomState.Width / 2); Assert.Equal(expected, vm.ZoomState.CaptureX, 3); } finally { MainViewModel.LayoutPathOverride = null; try { File.Delete(dbPath); } catch (IOException) { } } } [Fact] public void The_choice_persists_in_settings() { // Persistence is the store's job, tested against the store directly: two live view models // in one process fight over the SQLite file and the frame pump, which is a property of the // fixture, not of the setting. var dbPath = Path.Combine(Path.GetTempPath(), $"llamacasty-zoom-{Guid.NewGuid():N}.db"); try { using (var store = new ytLive.Services.LayoutStore(dbPath)) { Assert.False(store.LoadZoomFollowBox()); store.SaveZoomFollowBox(true); } using (var reopened = new ytLive.Services.LayoutStore(dbPath)) { Assert.True(reopened.LoadZoomFollowBox()); reopened.SaveZoomFollowBox(false); } using (var again = new ytLive.Services.LayoutStore(dbPath)) Assert.False(again.LoadZoomFollowBox()); } finally { try { File.Delete(dbPath); } catch (IOException) { } } } }