Files
LlamaCasty/ytLive.Tests/ThemeResourceTests.cs
T
gramps c1212644b0 fix(socials): live preview of the bar appearance + the crash on dragging a slider
Two bugs from the creator's first look at the new controls.

1. CRASH — '{DependencyProperty.UnsetValue}' is not a valid value for property 'Fill'.
   The token conversion deleted `YtSliderKnobHoverBrush` while two slider triggers still
   referenced it. WPF does not fail that at build time: it substitutes UnsetValue and throws at
   the next layout, inside `Ellipse.OnRender` — so the first slider touched (the new opacity one)
   killed the app. The brush is back, as a glossier version of the rest state.
   Guard: `ThemeResourceTests` scans every app XAML and proves each {StaticResource} key is
   defined (theme/App resources or the same file). Proven by removing the key again — the test
   names it.

2. PREVIEW DID NOTHING — the preview bar is its own XAML rendering (not the rasterized strip the
   compositor overlays), and it was hard-wired white-on-transparent, so the drawer's controls had
   nothing to move. Now:
   - `SocialBarTextBrush` / `SocialBarBackgroundBrush` come from the same `SocialBarStyle` the
     renderer bakes, so preview and output cannot disagree.
   - the preview draws the plate (same colour + opacity) and the drop shadow.
   - the drawer applies changes to the live config as you edit (`ApplyAppearancePreview`), and
     Cancel restores the pre-edit snapshot — so the preview tracks the controls without
     persisting anything until Save.

46 tests green (theme, socials, design tokens, layout store, real-MainWindow meter test).
2026-10-30 08:57:09 -07:00

62 lines
2.9 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Xunit;
namespace ytLive.Tests;
/// <summary>A missing <c>{StaticResource}</c> key does not fail the build — WPF substitutes
/// <c>DependencyProperty.UnsetValue</c> and throws at the next layout pass. That is exactly how
/// the app crashed on 2026-10-30: the token conversion deleted <c>YtSliderKnobHoverBrush</c>
/// while two slider triggers still asked for it, so the first slider the creator touched
/// (the new socials opacity slider) killed the app inside <c>Ellipse.OnRender</c>.
///
/// <para>This guard scans every XAML file in the app and proves each referenced key exists —
/// either in the theme/App resources or in the same file — so the class cannot come back.</para></summary>
public class ThemeResourceTests
{
[Fact]
public void Every_StaticResource_reference_resolves_to_a_defined_key()
{
var root = System.IO.Path.GetDirectoryName(SourceAssert.RepoPath("App.xaml"))!;
var globalKeys = new HashSet<string>(StringComparer.Ordinal);
foreach (var file in new[] { System.IO.Path.Combine(root, "App.xaml") }
.Concat(Directory.GetFiles(System.IO.Path.Combine(root, "Themes"), "*.xaml")))
globalKeys.UnionWith(KeysIn(File.ReadAllText(file)));
var missing = new List<string>();
foreach (var path in XamlFiles())
{
var text = File.ReadAllText(path);
var localKeys = KeysIn(text);
foreach (Match match in Regex.Matches(text, @"\{StaticResource ([^}]+)\}"))
{
var key = match.Groups[1].Value.Trim();
if (key.StartsWith("{", StringComparison.Ordinal)) continue; // nested markup extension
if (globalKeys.Contains(key) || localKeys.Contains(key)) continue;
missing.Add($"{System.IO.Path.GetFileName(path)}: {key}");
}
}
Assert.True(missing.Count == 0,
"StaticResource keys with no definition (WPF throws UnsetValue at layout):\n "
+ string.Join("\n ", missing.Distinct()));
}
private static HashSet<string> KeysIn(string text)
=> Regex.Matches(text, @"x:Key=""([^""]+)""").Select(m => m.Groups[1].Value)
.ToHashSet(StringComparer.Ordinal);
private static IEnumerable<string> XamlFiles()
{
var root = System.IO.Path.GetDirectoryName(SourceAssert.RepoPath("App.xaml"))!;
return Directory.EnumerateFiles(root, "*.xaml", SearchOption.AllDirectories)
.Where(p => !p.Contains($"{System.IO.Path.DirectorySeparatorChar}obj{System.IO.Path.DirectorySeparatorChar}", StringComparison.Ordinal)
&& !p.Contains($"{System.IO.Path.DirectorySeparatorChar}bin{System.IO.Path.DirectorySeparatorChar}", StringComparison.Ordinal)
&& !p.Contains("ytLive.Tests", StringComparison.Ordinal));
}
}