feat(text): persist independent text layer settings
Add additive defaults and round-trip tests for per-layer text content and formatting. Model the static text source after OBS text-source conventions: https://obsproject.com/kb/text-sources
This commit is contained in:
@@ -15,6 +15,97 @@ namespace ytLive.Tests;
|
||||
/// </summary>
|
||||
public class LayoutStorePersistenceTests
|
||||
{
|
||||
[Fact]
|
||||
public void Text_Layers_RoundTrip_Independently_And_Legacy_Rows_Get_Defaults()
|
||||
{
|
||||
var path = Path.Combine(Path.GetTempPath(), $"ytLlive-layout-{Guid.NewGuid():N}.db");
|
||||
try
|
||||
{
|
||||
var scene = new Scene { Name = "Starting" };
|
||||
scene.Elements.Add(new Source
|
||||
{
|
||||
Name = "Headline",
|
||||
Type = SourceType.TextOverlay,
|
||||
TextContent = "First line\nSecond line",
|
||||
TextFontFamily = "Arial",
|
||||
TextFontSize = 72,
|
||||
TextColor = "#12AB34",
|
||||
TextBold = true,
|
||||
TextAlignment = TextLayerAlignment.Center,
|
||||
});
|
||||
scene.Elements.Add(new Source
|
||||
{
|
||||
Name = "Caption",
|
||||
Type = SourceType.TextOverlay,
|
||||
TextContent = "Independent caption",
|
||||
TextFontFamily = "Segoe UI",
|
||||
TextFontSize = 24,
|
||||
TextColor = "#80445566",
|
||||
TextItalic = true,
|
||||
TextUnderline = true,
|
||||
TextAlignment = TextLayerAlignment.Right,
|
||||
});
|
||||
|
||||
using (var store = new LayoutStore(path))
|
||||
store.Save(new[] { scene }, null, null);
|
||||
|
||||
using (var reopened = new LayoutStore(path))
|
||||
{
|
||||
var sources = Assert.Single(reopened.Load()).Elements.OfType<Source>()
|
||||
.Where(source => source.Type == SourceType.TextOverlay).ToArray();
|
||||
Assert.Equal(2, sources.Length);
|
||||
Assert.Equal("First line\nSecond line", sources[0].TextContent);
|
||||
Assert.Equal("Arial", sources[0].TextFontFamily);
|
||||
Assert.Equal(72, sources[0].TextFontSize);
|
||||
Assert.Equal("#12AB34", sources[0].TextColor);
|
||||
Assert.True(sources[0].TextBold);
|
||||
Assert.False(sources[0].TextItalic);
|
||||
Assert.False(sources[0].TextUnderline);
|
||||
Assert.Equal(TextLayerAlignment.Center, sources[0].TextAlignment);
|
||||
Assert.Equal("Independent caption", sources[1].TextContent);
|
||||
Assert.Equal(24, sources[1].TextFontSize);
|
||||
Assert.Equal("#80445566", sources[1].TextColor);
|
||||
Assert.False(sources[1].TextBold);
|
||||
Assert.True(sources[1].TextItalic);
|
||||
Assert.True(sources[1].TextUnderline);
|
||||
Assert.Equal(TextLayerAlignment.Right, sources[1].TextAlignment);
|
||||
}
|
||||
|
||||
SqliteConnection.ClearAllPools();
|
||||
using (var raw = new SqliteConnection($"Data Source={path}"))
|
||||
{
|
||||
raw.Open();
|
||||
using var cmd = raw.CreateCommand();
|
||||
cmd.CommandText = "ALTER TABLE Source DROP COLUMN TextContent; ALTER TABLE Source DROP COLUMN TextFontFamily; ALTER TABLE Source DROP COLUMN TextFontSize; ALTER TABLE Source DROP COLUMN TextColor; ALTER TABLE Source DROP COLUMN TextBold; ALTER TABLE Source DROP COLUMN TextItalic; ALTER TABLE Source DROP COLUMN TextUnderline; ALTER TABLE Source DROP COLUMN TextAlignment;";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
SqliteConnection.ClearAllPools();
|
||||
|
||||
using (var legacy = new LayoutStore(path))
|
||||
{
|
||||
var sources = Assert.Single(legacy.Load()).Elements.OfType<Source>()
|
||||
.Where(source => source.Type == SourceType.TextOverlay).ToArray();
|
||||
Assert.Equal(2, sources.Length);
|
||||
foreach (var source in sources)
|
||||
{
|
||||
Assert.Equal("Text", source.TextContent);
|
||||
Assert.Equal("Segoe UI", source.TextFontFamily);
|
||||
Assert.Equal(48, source.TextFontSize);
|
||||
Assert.Equal("#FFFFFF", source.TextColor);
|
||||
Assert.False(source.TextBold);
|
||||
Assert.False(source.TextItalic);
|
||||
Assert.False(source.TextUnderline);
|
||||
Assert.Equal(TextLayerAlignment.Left, source.TextAlignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
SqliteConnection.ClearAllPools();
|
||||
try { File.Delete(path); } catch { /* best-effort cleanup */ }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>TASK 47: the alert announcement display method must survive a
|
||||
/// restart. This one is easy to get quietly wrong — the Source row is read back
|
||||
/// by POSITIONAL index, so a column inserted anywhere but the end silently
|
||||
|
||||
Reference in New Issue
Block a user