using System.IO; using System.Threading.Tasks; using Microsoft.Data.Sqlite; using Xunit; using ytLive.Services.Audio; using ytLive.ViewModels; namespace ytLive.Tests; /// 2026-10-09 bug: pressing TRAX started the music but the status light stayed off — /// and the toggle couldn't pause, because the playing state was never refreshed after Play. /// TRAX is a toggle: the light is green while playing, red with a terse scrolling message on /// failure, gray when idle. [Collection("RealApp")] public sealed class TraxToggleTests { private sealed class FakePlayer : ITraxPlayer { public bool Starts = true; public event Action? PlaybackEnded; public string? TrackName { get; private set; } public bool IsPlaying { get; private set; } public float LocalGain { get; set; } = 1f; public void Load(string path) => TrackName = Path.GetFileNameWithoutExtension(path); public void Play() => IsPlaying = Starts; public void Pause() => IsPlaying = false; public void Dispose() { } public void Fail() { IsPlaying = false; PlaybackEnded?.Invoke(new IOException("device lost")); } } private readonly RealAppHost _app; public TraxToggleTests(RealAppHost app) => _app = app; [Fact] public async Task Trax_Toggle_Lights_Green_While_Playing_And_Red_With_A_Terse_Error() { await _app.RunAsync(async () => { var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-test-{Guid.NewGuid():N}.db"); var track = Path.Combine(Path.GetTempPath(), $"trax-{Guid.NewGuid():N}.mp3"); File.WriteAllBytes(track, new byte[16]); var player = new FakePlayer(); MainViewModel.LayoutPathOverride = tempDb; MainViewModel.TraxPlayerOverride = () => player; var window = new MainWindow(); var vm = (MainViewModel)window.DataContext; try { await vm.SetTraxTrackAsync(track, play: false); Assert.False(vm.IsMusicPlaying); var idle = vm.TraxDotBrush.Color; vm.TraxLeftClick(); // press: plays → light on Assert.True(vm.IsMusicPlaying); var playing = vm.TraxDotBrush.Color; Assert.NotEqual(idle, playing); Assert.Null(vm.TraxError); vm.TraxLeftClick(); // press again: pauses → light off Assert.False(vm.IsMusicPlaying); Assert.Equal(idle, vm.TraxDotBrush.Color); player.Starts = false; // press: playback can't start vm.TraxLeftClick(); Assert.False(vm.IsMusicPlaying); Assert.NotNull(vm.TraxError); Assert.True(vm.TraxError!.Length <= 32, $"error not terse: '{vm.TraxError}'"); var red = vm.TraxDotBrush.Color; Assert.NotEqual(idle, red); Assert.NotEqual(playing, red); player.Starts = true; // recovery clears the error vm.TraxLeftClick(); Assert.True(vm.IsMusicPlaying); Assert.Null(vm.TraxError); Assert.Equal(playing, vm.TraxDotBrush.Color); player.Fail(); // device lost mid-play Assert.False(vm.IsMusicPlaying); Assert.Equal(red, vm.TraxDotBrush.Color); } finally { window.Close(); MainViewModel.LayoutPathOverride = null; MainViewModel.TraxPlayerOverride = null; SqliteConnection.ClearAllPools(); try { File.Delete(tempDb); File.Delete(track); } catch { /* best-effort cleanup */ } } }); } }