using System; using System.IO; using System.Linq; using Microsoft.Data.Sqlite; using Xunit; using ytLive.Models; using ytLive.ViewModels; namespace ytLive.Tests; /// /// TASK 27 integration: the (+) menu's YouTube Chat row gates on one chat layer /// per layout (same rule as the webcam's app-wide identity), and ChatBox layers /// created before the "YouTube Chat" default (legacy name "Chat") heal to the /// picklist's label on load — creator renames stay untouched. /// Real-App + temp-DB pattern (see WebcamMenuGateTests). /// [Collection("RealApp")] public sealed class ChatLayerGateTests { [Fact] public void LegacyChatName_Heals_And_CanAddYouTubeChat_Gates_On_The_Existing_Layer() { _app.Run(Run); } private readonly RealAppHost _app; public ChatLayerGateTests(RealAppHost app) => _app = app; private void Run() { var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-chatgate-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = tempDb; var window = default(MainWindow); try { using (var schema = new ytLive.Services.LayoutStore(tempDb)) { } SqliteConnection.ClearAllPools(); SeedDatabaseWithLegacyChatLayer(tempDb); window = new MainWindow(); var vm = (MainViewModel)window.DataContext; var starting = vm.Scenes.Single(s => SceneCatalog.Is(s.Name, SceneCatalog.Starting)); var live = vm.Scenes.Single(s => SceneCatalog.Is(s.Name, SceneCatalog.Live)); var chatLayer = Assert.Single(starting.Elements.OfType() .Where(s => s.Type == SourceType.ChatBox).ToList()); Assert.Equal("YouTube Chat", chatLayer.Name); vm.StagedScene = live; Assert.False(vm.CanAddYouTubeChat, "chat layer exists in another scene — Add YouTube Chat must be greyed out"); vm.StagedScene = starting; Assert.False(vm.CanAddYouTubeChat, "staged scene itself has the chat layer"); vm.SaveLayoutNow(); using (var connection = new SqliteConnection($"Data Source={tempDb}")) { connection.Open(); using var names = connection.CreateCommand(); names.CommandText = "SELECT Name FROM Source WHERE Type = 'ChatBox';"; Assert.Equal("YouTube Chat", (string)names.ExecuteScalar()!); } vm.RemoveSourceCommand.Execute(chatLayer); Assert.True(vm.CanAddYouTubeChat, "the only chat layer was removed — the (+) item re-enables"); } finally { window?.Close(); MainViewModel.LayoutPathOverride = null; SqliteConnection.ClearAllPools(); try { File.Delete(tempDb); } catch { /* best-effort cleanup */ } } } /// Five canonical scenes + a ChatBox layer still carrying the /// pre-rename default name "Chat" in Starting. private static void SeedDatabaseWithLegacyChatLayer(string path) { using var connection = new SqliteConnection($"Data Source={path}"); connection.Open(); var scenes = new (string Id, string Name)[] { ("scene-starting", "Starting"), ("scene-live", "Live"), ("scene-brb", "BRB"), ("scene-chat", "Chat"), ("scene-ending", "Ending"), }; foreach (var (id, name) in scenes) { using var cmd = connection.CreateCommand(); cmd.CommandText = "INSERT INTO Scene (Id, Name, HasBackground, SortOrder) VALUES ($id, $name, 1, $order);"; cmd.Parameters.AddWithValue("$id", id); cmd.Parameters.AddWithValue("$name", name); cmd.Parameters.AddWithValue("$order", Array.IndexOf(scenes, (id, name))); cmd.ExecuteNonQuery(); } using (var background = connection.CreateCommand()) { background.CommandText = @"INSERT INTO Source (Id, SceneId, Type, Name, IsEnabled, X, Y, Width, Height, IsBackground, SortOrder) VALUES ('bg-1', 'scene-starting', 'Background', 'Background', 1, 0, 0, 1920, 1080, 1, 0);"; background.ExecuteNonQuery(); } using (var chat = connection.CreateCommand()) { chat.CommandText = @"INSERT INTO Source (Id, SceneId, Type, Name, IsEnabled, X, Y, Width, Height, IsBackground, SortOrder) VALUES ('chat-1', 'scene-starting', 'ChatBox', 'Chat', 1, 1400, 20, 500, 648, 0, 1);"; chat.ExecuteNonQuery(); } } }