TASK 4 ship step 5.5: social bar bug fixes + bar on the live output — direction-snap drag (SocialBarSnap.Decide + ClearValue, local Canvas.Top was overriding the binding), fediverse nodeinfo subdomain probing + load-time heal (settable FediverseSoftware), SocialBarRenderer strip blitted above the flash via a re-read FramePump socialBar seam — 155 tests passing, 0 warnings

This commit is contained in:
2026-08-13 09:29:58 -07:00
parent e72ba71165
commit ac60a26d82
16 changed files with 666 additions and 52 deletions
+99
View File
@@ -1,8 +1,11 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Sqlite;
using Xunit;
using ytLive.Models;
using ytLive.Services;
using ytLive.ViewModels;
namespace ytLive.Tests;
@@ -16,6 +19,102 @@ public class SocialBarTests
return path;
}
/// <summary>Resolves every fediverse domain as Mastodon — the heal test only
/// cares that a missing stored software name gets filled in and persisted.</summary>
private sealed class MastodonValidator : ISocialValidator
{
public Task<string?> ResolveFediverseSoftwareAsync(string domain, CancellationToken ct)
=> Task.FromResult<string?>("mastodon");
public Task<SocialLookupResult> LookupAsync(SocialService service, string handleOrUrl, CancellationToken ct)
=> throw new System.NotSupportedException();
}
[Fact]
public void SocialBarSnap_Decide_DirectionAndDeadzone()
{
Assert.Equal(SocialBarPosition.Top, SocialBarSnap.Decide(-10));
Assert.Equal(SocialBarPosition.Top, SocialBarSnap.Decide(-6));
Assert.Equal(SocialBarPosition.Bottom, SocialBarSnap.Decide(10));
Assert.Equal(SocialBarPosition.Bottom, SocialBarSnap.Decide(6));
Assert.Null(SocialBarSnap.Decide(5));
Assert.Null(SocialBarSnap.Decide(0));
Assert.Null(SocialBarSnap.Decide(-5));
}
[Fact]
public void SocialBarSnap_Decide_HonorsCustomDeadzone()
{
Assert.Null(SocialBarSnap.Decide(10, 20));
Assert.Equal(SocialBarPosition.Top, SocialBarSnap.Decide(-21, 20));
}
[Fact]
public void SocialEntry_FediverseSoftware_SettableUpdatesLogo()
{
var entry = new SocialEntry
{
Service = SocialService.Fediverse,
Handle = "@gramps@llamachile.tube",
ProfileUrl = "https://llamachile.tube/@gramps",
};
var honeycomb = entry.LogoData;
entry.FediverseSoftware = "mastodon";
Assert.Equal(SocialServiceIcons.LogoDataForFediverse("mastodon"), entry.LogoData);
Assert.NotEqual(honeycomb, entry.LogoData);
}
/// <summary>The single integration test for this branch: a fediverse entry
/// persisted with a NULL software name is loaded, healed via the real validator
/// seam, and the resolved name is persisted back — surviving a second load.</summary>
[Fact]
public async Task Socials_HealMissingFediverseSoftware_RoundTripsThroughDb()
{
var path = TempDbPath();
try
{
var socials = new SocialsConfig();
socials.Entries.Add(new SocialEntry
{
Service = SocialService.Fediverse,
Handle = "@gramps@llamachile.tube",
ProfileUrl = "https://llamachile.tube/@gramps",
});
using (var store = new LayoutStore(path))
store.Save(new[] { new Scene { Name = "Live" } }, null, socials);
SocialsConfig? loaded;
using (var store = new LayoutStore(path))
{
store.Load();
loaded = store.Socials;
Assert.NotNull(loaded);
Assert.Null(loaded!.Entries[0].FediverseSoftware);
}
var healed = await MainViewModel.HealFediverseSoftwareAsync(loaded!, new MastodonValidator());
Assert.Single(healed);
Assert.Equal("mastodon", healed["@gramps@llamachile.tube"]);
loaded.Entries[0].FediverseSoftware = healed["@gramps@llamachile.tube"];
using (var store = new LayoutStore(path))
store.Save(new[] { new Scene { Name = "Live" } }, null, loaded);
using (var store = new LayoutStore(path))
{
store.Load();
var again = store.Socials;
Assert.NotNull(again);
Assert.Equal("mastodon", again!.Entries[0].FediverseSoftware);
}
}
finally
{
SqliteConnection.ClearAllPools();
if (File.Exists(path)) File.Delete(path);
}
}
[Fact]
public void SocialsConfig_RoundTrip_PersistsEntriesAndBarSettings()
{