Social bar (Branch B): global resource with per-scene presence — Models/Socials.cs (19 services, icons, canonical URLs), Services/SocialValidator.cs (HTTP lookup validation seam), LayoutStore schema v7 (Socials + SocialEntry tables + Scene.HasSocialBar), MainViewModel (socials collection, AddSocial/RemoveSocial/ToggleSceneSocialBar commands, freemium cap YT+1, premium seam), MainWindow.xaml (footer Social button + [+/-] toggle on meter line, preview bar rendering top/bottom + LCR), 4 integration tests (roundtrip, empty-not-persisted, canonical URLs, icons), 85 tests passing, 0 warnings
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Xunit;
|
||||
using ytLive.Models;
|
||||
using ytLive.Services;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
public class SocialBarTests
|
||||
{
|
||||
private static string TempDbPath()
|
||||
{
|
||||
var path = Path.Combine(Path.GetTempPath(), $"ytLlive-social-test-{System.Guid.NewGuid():N}.db");
|
||||
SqliteConnection.ClearAllPools();
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SocialsConfig_RoundTrip_PersistsEntriesAndBarSettings()
|
||||
{
|
||||
var path = TempDbPath();
|
||||
try
|
||||
{
|
||||
var socials = new SocialsConfig
|
||||
{
|
||||
BarPosition = SocialBarPosition.Top,
|
||||
BarJustify = SocialBarJustify.Left,
|
||||
};
|
||||
socials.Entries.Add(new SocialEntry
|
||||
{
|
||||
Service = SocialService.YouTube,
|
||||
Handle = "mychannel",
|
||||
ProfileUrl = "https://www.youtube.com/@mychannel",
|
||||
});
|
||||
socials.Entries.Add(new SocialEntry
|
||||
{
|
||||
Service = SocialService.Twitch,
|
||||
Handle = "streamer123",
|
||||
ProfileUrl = "https://www.twitch.tv/streamer123",
|
||||
});
|
||||
|
||||
using (var store = new LayoutStore(path))
|
||||
{
|
||||
var scene = new Scene { Name = "Live" };
|
||||
scene.HasSocialBar = true;
|
||||
store.Save(new[] { scene }, null, socials);
|
||||
}
|
||||
|
||||
using (var store = new LayoutStore(path))
|
||||
{
|
||||
var scenes = store.Load();
|
||||
Assert.NotNull(store.Socials);
|
||||
Assert.Equal(2, store.Socials!.Entries.Count);
|
||||
Assert.Equal(SocialService.YouTube, store.Socials.Entries[0].Service);
|
||||
Assert.Equal("mychannel", store.Socials.Entries[0].Handle);
|
||||
Assert.Equal("https://www.youtube.com/@mychannel", store.Socials.Entries[0].ProfileUrl);
|
||||
Assert.Equal(SocialService.Twitch, store.Socials.Entries[1].Service);
|
||||
Assert.Equal(SocialBarPosition.Top, store.Socials.BarPosition);
|
||||
Assert.Equal(SocialBarJustify.Left, store.Socials.BarJustify);
|
||||
Assert.True(scenes[0].HasSocialBar);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
SqliteConnection.ClearAllPools();
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SocialsConfig_EmptyEntries_NotPersisted()
|
||||
{
|
||||
var path = TempDBPath();
|
||||
try
|
||||
{
|
||||
var socials = new SocialsConfig();
|
||||
using (var store = new LayoutStore(path))
|
||||
{
|
||||
store.Save(new[] { new Scene { Name = "Live" } }, null, socials);
|
||||
}
|
||||
|
||||
using (var store = new LayoutStore(path))
|
||||
{
|
||||
Assert.Null(store.Socials);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
SqliteConnection.ClearAllPools();
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SocialServiceIcons_CanonicalUrlFor_AllServices()
|
||||
{
|
||||
Assert.Equal("https://www.youtube.com/@test", SocialServiceIcons.CanonicalUrlFor(SocialService.YouTube, "test"));
|
||||
Assert.Equal("https://www.youtube.com/@test", SocialServiceIcons.CanonicalUrlFor(SocialService.YouTube, "@test"));
|
||||
Assert.Equal("https://x.com/user", SocialServiceIcons.CanonicalUrlFor(SocialService.X, "user"));
|
||||
Assert.Equal("https://github.com/dev", SocialServiceIcons.CanonicalUrlFor(SocialService.GitHub, "dev"));
|
||||
Assert.Equal("https://example.com", SocialServiceIcons.CanonicalUrlFor(SocialService.Website, "example.com"));
|
||||
Assert.Equal("https://example.com", SocialServiceIcons.CanonicalUrlFor(SocialService.Website, "https://example.com"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SocialServiceIcons_InitialsAndColor_AllServices()
|
||||
{
|
||||
Assert.Equal("YT", SocialServiceIcons.InitialsFor(SocialService.YouTube));
|
||||
Assert.Equal("#FF0000", SocialServiceIcons.ColorFor(SocialService.YouTube));
|
||||
Assert.Equal("TW", SocialServiceIcons.InitialsFor(SocialService.Twitch));
|
||||
Assert.NotEmpty(SocialServiceIcons.InitialsFor(SocialService.Website));
|
||||
Assert.NotEmpty(SocialServiceIcons.ColorFor(SocialService.Website));
|
||||
}
|
||||
|
||||
private static string TempDBPath() => TempDbPath();
|
||||
}
|
||||
Reference in New Issue
Block a user