In the Socials drawer, under the show/hide switch: text colour, an optional background plate with its own colour and opacity, and a text drop shadow. The bar had been hard-wired white-on-transparent since it shipped. - `SocialsConfig` gains five additive properties (BarTextColor, BarBackgroundEnabled, BarBackgroundColor, BarBackgroundOpacity, BarTextShadow). - Persistence: five new Socials columns, added by the per-column idempotent migration pattern (a half-migrated DB heals instead of failing), written in Save and read back in Load with defaults for pre-existing rows. - `SocialBarRenderer.Render(entries, SocialBarStyle?)` — the look is a separate value type so the renderer stays a pure "entries + look" function and the preview can never read a half-applied config mid-edit. A malformed hex falls back to white/black instead of throwing inside a render tick. - The plate hugs the entries (rounded 20px, padding) so it reads as a caption bar, not a band across the frame. Off by default, because transparent is what creators have today. - The colour pickers reuse `BorderColorPalette` — one catalogue of the app's colours, per the TASK 60/63 rule. Guard: SocialBarAppearanceTests paints the real strip and inspects pixels — default is white with no plate, a red text colour reaches the glyphs, a 55% blue plate yields half-transparent blue pixels, and the shadow adds dark pixels that were not there before.
79 lines
3.5 KiB
C#
79 lines
3.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Windows.Media;
|
|
using Xunit;
|
|
using ytLive.Models;
|
|
using ytLive.Services;
|
|
using ytLive.Services.Compositor;
|
|
|
|
namespace ytLive.Tests;
|
|
|
|
/// <summary>The creator (2026-10-30): the social bar was hard-wired white-on-transparent. It now
|
|
/// takes a text colour, an optional background plate with its own colour and opacity, and a text
|
|
/// drop shadow — set in the Socials drawer.
|
|
///
|
|
/// <para>These knobs are painting rules, so the test paints the real strip and inspects its
|
|
/// pixels. Anything less would pass while the bar rendered unchanged.</para></summary>
|
|
public class SocialBarAppearanceTests
|
|
{
|
|
private static readonly List<SocialEntry> Entries = new()
|
|
{
|
|
new SocialEntry { Service = SocialService.YouTube, Handle = "creator", ProfileUrl = "https://youtube.com/@creator" },
|
|
};
|
|
|
|
[Fact]
|
|
public void The_bar_paints_the_chosen_text_colour_plate_and_shadow()
|
|
{
|
|
Exception? failure = null;
|
|
var results = new List<string>();
|
|
|
|
var thread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
// 1. Default look: white glyphs, no plate, no shadow.
|
|
var plain = Render(SocialBarStyle.Default);
|
|
Assert.NotNull(plain);
|
|
Assert.True(Count(plain!, (r, g, b, a) => r > 200 && g > 200 && b > 200 && a > 200) > 0,
|
|
"white glyphs must be painted by default");
|
|
Assert.Equal(0, Count(plain!, (r, g, b, a) => b > 150 && r < 80 && g < 80));
|
|
|
|
// 2. Text colour: red glyphs, nothing white.
|
|
var red = Render(new SocialBarStyle("#FF0000", false, "#000000", 0.55, false));
|
|
Assert.True(Count(red!, (r, g, b, a) => r > 180 && g < 80 && b < 80 && a > 200) > 0,
|
|
"the text colour must reach the glyph pixels");
|
|
Assert.Equal(0, Count(red!, (r, g, b, a) => r > 200 && g > 200 && b > 200 && a > 200));
|
|
|
|
// 3. Plate: blue at 55% — half-transparent blue pixels must appear.
|
|
var plated = Render(new SocialBarStyle("#FFFFFF", true, "#0000FF", 0.55, false));
|
|
Assert.True(Count(plated!, (r, g, b, a) => b > 150 && r < 60 && g < 60 && a is > 100 and < 170) > 200,
|
|
"the plate must fill with its colour at the chosen opacity");
|
|
|
|
// 4. Shadow: dark semi-transparent pixels appear beside the glyphs.
|
|
var shadowed = Render(new SocialBarStyle("#FFFFFF", false, "#000000", 0.55, true));
|
|
Assert.True(Count(shadowed!, (r, g, b, a) => r < 60 && g < 60 && b < 60 && a is > 0 and < 200)
|
|
> Count(plain!, (r, g, b, a) => r < 60 && g < 60 && b < 60 && a is > 0 and < 200),
|
|
"the drop shadow must add dark pixels behind the glyphs");
|
|
}
|
|
catch (Exception ex) { failure = ex; }
|
|
});
|
|
thread.SetApartmentState(ApartmentState.STA);
|
|
thread.Start();
|
|
thread.Join();
|
|
|
|
Assert.Null(failure);
|
|
}
|
|
|
|
private static VideoFrame? Render(SocialBarStyle style) => SocialBarRenderer.Render(Entries, style);
|
|
|
|
private static int Count(VideoFrame frame, Func<int, int, int, int, bool> predicate)
|
|
{
|
|
var hits = 0;
|
|
for (var i = 0; i < frame.BgraPixels.Length; i += 4)
|
|
if (predicate(frame.BgraPixels[i + 2], frame.BgraPixels[i + 1], frame.BgraPixels[i], frame.BgraPixels[i + 3]))
|
|
hits++;
|
|
return hits;
|
|
}
|
|
}
|