Files
LlamaCasty/ytLive.Tests/AudioSyncSettingsTests.cs
T
gramps cc75212997 fix(audio): a sync offset is saved the moment it changes
Both offsets rode the debounced layout save, so a value set seconds before the app closed was lost
without a word. That is what happened to the -130 ms offset on 2026-11-12: two clips were measured
against a setting that was not in force, the numbers disagreed by ~65 ms, and the disagreement was
blamed on the audio path.

Setters now write the row immediately (two tiny rows), keeping ScheduleSave for the rest of the
layout. AudioSyncSettingsTests gains the regression: set both offsets on a view model and read them
back through a fresh store with no debounce flush.

MyMistakes records the general shape — "a setting that didn't stick" is almost always ScheduleSave —
with the check list and the two related rules: offsets never ship with a non-zero default, and
scripts/avsync.py only resolves ~50 ms, so below that the creator's judgement wins.

677 pass, 0 warnings. Scope justification: Assets/starting-background.jpg is outside this unit — the
creator's replacement art, still awaiting their visual check.
2026-11-12 14:21:48 -08:00

76 lines
2.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.IO;
using Microsoft.Data.Sqlite;
using Xunit;
using ytLive.Services;
using ytLive.ViewModels;
namespace ytLive.Tests;
/// <summary>
/// Sync offsets ship at zero. A correction belongs to one camera on one desk, so a fresh profile must
/// not arrive with audio already shifted — a non-zero default would move every creator's voice to fix
/// one machine and would hide whether the fault is theirs or ours.
/// </summary>
// The view model below touches the static LayoutPathOverride, so it shares the layout collection.
[Collection("MainViewModelLayoutPath")]
public sealed class AudioSyncSettingsTests : IDisposable
{
private readonly string _db = Path.Combine(Path.GetTempPath(), $"ytLlive-syncsettings-{Guid.NewGuid():N}.db");
public void Dispose()
{
SqliteConnection.ClearAllPools();
try { File.Delete(_db); } catch (IOException) { /* best effort */ }
}
[Fact]
public void Both_offsets_start_at_zero_and_only_then_take_a_value()
{
using (var store = new LayoutStore(_db))
{
Assert.Equal(0, store.LoadMicSyncOffsetMs()); // opt-in, not pre-applied
Assert.Equal(0, store.LoadAudioSyncOffsetMs());
// A mic delay cannot be negative — advancing is the global offset's job.
store.SaveMicSyncOffsetMs(-120);
Assert.Equal(0, store.LoadMicSyncOffsetMs());
store.SaveMicSyncOffsetMs(600); // beyond the slider's range
Assert.Equal(500, store.LoadMicSyncOffsetMs());
store.SaveMicSyncOffsetMs(240);
store.SaveAudioSyncOffsetMs(-130);
}
using var reopened = new LayoutStore(_db);
Assert.Equal(240, reopened.LoadMicSyncOffsetMs());
Assert.Equal(-130, reopened.LoadAudioSyncOffsetMs());
}
/// <summary>
/// A slider move is persisted as it happens. It used to ride the debounced layout save, so a value
/// set seconds before closing the app was lost — which cost a record-and-measure cycle on 2026-11-12
/// when a −130 ms offset vanished between two clips.
/// </summary>
[Fact]
public void A_slider_move_survives_without_any_debounce_flush()
{
MainViewModel.LayoutPathOverride = _db;
try
{
var vm = new MainViewModel();
vm.AudioSyncOffsetMs = -130;
vm.MicSyncOffsetMs = 240;
using var store = new LayoutStore(_db);
Assert.Equal(-130, store.LoadAudioSyncOffsetMs());
Assert.Equal(240, store.LoadMicSyncOffsetMs());
}
finally
{
MainViewModel.LayoutPathOverride = null;
}
}
}