Files
LlamaCasty/ytLive.Tests/TextLayerEditorPreviewTests.cs
T
gramps 5c4acf141b feat(text): composite shared text rasters in output
Raster static text on UI-thread changes and reuse the same transparent BGRA frame in preview and all compositor paths. Cache text frame identity in the frame-pump signature and invalidate static bakes on edits. Follow OBS text-source conventions: https://obsproject.com/kb/text-sources
2026-11-04 15:01:47 -08:00

104 lines
3.7 KiB
C#

using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.Data.Sqlite;
using Xunit;
using ytLive.Controls;
using ytLive.Models;
using ytLive.ViewModels;
namespace ytLive.Tests;
[Collection("RealApp")]
public sealed class TextLayerEditorPreviewTests
{
private readonly RealAppHost _app;
public TextLayerEditorPreviewTests(RealAppHost app) => _app = app;
[Fact]
public void Editing_One_Text_Layer_Updates_Only_That_Preview()
{
_app.Run(Run);
}
private static void Run()
{
var dbPath = Path.Combine(Path.GetTempPath(), $"ytLlive-text-editor-{Guid.NewGuid():N}.db");
MainViewModel.LayoutPathOverride = dbPath;
MainWindow? window = null;
try
{
window = new MainWindow();
var vm = (MainViewModel)window.DataContext;
vm.EditingActive = true;
vm.StagedScene = vm.Scenes.Single(scene => SceneCatalog.Is(scene.Name, SceneCatalog.Live));
vm.AddSourceCommand.Execute(SourceType.TextOverlay);
vm.AddSourceCommand.Execute(SourceType.TextOverlay);
var textLayers = vm.StagedScene!.Elements.OfType<Source>()
.Where(source => source.Type == SourceType.TextOverlay).ToArray();
Assert.Equal(2, textLayers.Length);
var first = textLayers[0];
var second = textLayers[1];
second.TextContent = "Leave this caption alone";
vm.SelectedElement = first;
window.Show();
window.UpdateLayout();
var leftPanel = (LeftPanel)window.FindName("LeftPanel")!;
var editor = (TextBox)leftPanel.FindName("TextLayerEditor")!;
Assert.Equal("Text", editor.Text);
var firstBitmap = first.VideoImageSource;
var secondBitmap = second.VideoImageSource;
Assert.NotNull(firstBitmap);
Assert.NotNull(secondBitmap);
Assert.True(first.IsDraggable);
Assert.Equal(900, first.Width);
Assert.Equal(140, first.Height);
editor.Text = "Updated headline\nSecond line";
editor.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
window.UpdateLayout();
Assert.Equal("Updated headline\nSecond line", first.TextContent);
Assert.Equal("Leave this caption alone", second.TextContent);
Assert.NotSame(firstBitmap, first.VideoImageSource);
Assert.Same(secondBitmap, second.VideoImageSource);
var preview = (PreviewPane)window.FindName("PreviewPane")!;
var firstPreview = FindTextImage(preview, first);
var secondPreview = FindTextImage(preview, second);
Assert.NotNull(firstPreview);
Assert.NotNull(secondPreview);
Assert.Same(first.VideoImageSource, firstPreview!.Source);
Assert.Same(second.VideoImageSource, secondPreview!.Source);
Assert.True(firstPreview.IsVisible);
Assert.True(secondPreview.IsVisible);
}
finally
{
window?.Close();
MainViewModel.LayoutPathOverride = null;
SqliteConnection.ClearAllPools();
try { File.Delete(dbPath); } catch { /* best-effort cleanup */ }
}
}
private static Image? FindTextImage(DependencyObject parent, Source source)
{
if (parent is Image image && ReferenceEquals(image.DataContext, source))
return image;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var result = FindTextImage(VisualTreeHelper.GetChild(parent, i), source);
if (result != null) return result;
}
return null;
}
}