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).
62 lines
2.9 KiB
C#
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));
|
|
}
|
|
}
|