using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; using VideoDownloader.Tests.Infrastructure; namespace VideoDownloader.Tests.E2E; /// /// 아이콘 tofu 실결함(사용자 보고: "아이콘은 절대 안고쳐지네") 재현 시도 — /// 테마 사전을 앱과 같은 방식으로 로드했을 때 FluentIcon/IconButton 경유 글리프가 /// 명시적 Segoe Fluent Icons 렌더와 픽셀 단위로 같은지 검증한다. /// 같지 않고 본문 폰트 렌더(tofu)와 같다면 리소스 해석이 깨진 것이다. /// [Collection("E2E")] public class IconGlyphRenderTests { private static byte[] Render(FrameworkElement el) { el.Measure(new Size(48, 48)); el.Arrange(new Rect(0, 0, 48, 48)); el.UpdateLayout(); var rtb = new RenderTargetBitmap(48, 48, 96, 96, PixelFormats.Pbgra32); rtb.Render(el); var px = new byte[48 * 48 * 4]; rtb.CopyPixels(px, 48 * 4, 0); return px; } private static int Lit(byte[] px) { var lit = 0; for (var i = 3; i < px.Length; i += 4) if (px[i] > 60) lit++; return lit; } [Fact] public async Task FluentIcon_스타일_글리프는_명시적_아이콘폰트_렌더와_동일하다() { using var sta = StaDispatcher.Start(); WpfTestApp.Ensure(sta.Dispatcher); var (family, litStyled, litExplicit, litBody, sameAsExplicit, sameAsBody) = await sta.Dispatcher.InvokeAsync(() => { TextBlock Make() => new() { Text = "", FontSize = 14, Foreground = Brushes.White, Background = Brushes.Black, Width = 48, Height = 48, }; var styled = Make(); styled.Style = (Style)Application.Current.FindResource("FluentIcon"); var explicitIcon = Make(); explicitIcon.FontFamily = new FontFamily("Segoe Fluent Icons"); var body = Make(); body.FontFamily = (FontFamily)Application.Current.FindResource("UiFont"); var pStyled = Render(styled); var pExplicit = Render(explicitIcon); var pBody = Render(body); return (styled.FontFamily.Source, Lit(pStyled), Lit(pExplicit), Lit(pBody), pStyled.SequenceEqual(pExplicit), pStyled.SequenceEqual(pBody)); }); Assert.True(litExplicit > 0, "기준(명시적 Segoe Fluent Icons) 렌더 자체가 비어 있음 — 폰트 미설치?"); Assert.True(sameAsExplicit && !sameAsBody, $"FluentIcon 경유 글리프가 아이콘 폰트로 렌더되지 않음 — 해석된 폰트: '{family}', " + $"픽셀(스타일/명시/본문): {litStyled}/{litExplicit}/{litBody}, 본문과 동일: {sameAsBody}"); } [Fact] public async Task 앱_시작_시퀀스를_그대로_태워도_FluentIcon_글리프가_렌더된다() { // 실제 App.OnStartup 과 동일한 순서: ApplyTheme(절대 pack URI 재로드) → 강조색 → 폰트 → 모션 var dataDir = Path.Combine(Path.GetTempPath(), $"vd_icon_{Guid.NewGuid():N}"); Environment.SetEnvironmentVariable("VD_DATA_DIR", dataDir); try { using var sta = StaDispatcher.Start(); WpfTestApp.Ensure(sta.Dispatcher); var (family, same, litStyled, litExplicit) = await sta.Dispatcher.InvokeAsync(() => { VideoDownloader.App.App.ApplyTheme("dark"); VideoDownloader.App.App.ApplyAccent(null); VideoDownloader.App.App.ApplyFontSettings(); VideoDownloader.App.App.ApplyReduceMotion(false); var styled = new TextBlock { Text = "\uE76B", FontSize = 14, Foreground = Brushes.White, Background = Brushes.Black, Width = 48, Height = 48, Style = (Style)Application.Current.FindResource("FluentIcon"), }; var explicitIcon = new TextBlock { Text = "\uE76B", FontSize = 14, Foreground = Brushes.White, Background = Brushes.Black, Width = 48, Height = 48, FontFamily = new FontFamily("Segoe Fluent Icons"), }; var pStyled = Render(styled); var pExplicit = Render(explicitIcon); return (styled.FontFamily.Source, pStyled.SequenceEqual(pExplicit), Lit(pStyled), Lit(pExplicit)); }); Assert.True(same, $"앱 시작 시퀀스 후 FluentIcon 글리프 깨짐 — 해석된 폰트: '{family}', 픽셀(스타일/명시): {litStyled}/{litExplicit}"); } finally { Environment.SetEnvironmentVariable("VD_DATA_DIR", null); try { Directory.Delete(dataDir, true); } catch { } } } }