using System; using System.IO; using System.Text.RegularExpressions; using System.Windows.Media; using Paca.Tests.Infrastructure; using Xunit; namespace Paca.Tests.E2E; /// /// AGENTS.md 25대 헌법 Phase 2 심화 게이트 테스트: /// - Gate 201: 하위 대화상자 인터랙티브 컨트롤 UIA Name 전수 보증 (헌법 제20조) /// - Gate 202: 고대비 테마(WHCM) 상태 브러시 WCAG AAA 7.0:1 보증 (헌법 제21조) /// - Gate 203: TabHost 순백(#FFFFFF) 발광 및 하드코딩 색상 차단 (헌법 제13조) /// - Gate 206: 최대화 시 피츠의 법칙 툴바 상단 패딩 0px 전환 (헌법 제16조) /// - Gate 207: SettingsWindow 보더리스 최대화 WM_GETMINMAXINFO 훅 보증 /// public class DesignSystemCommandmentsPhase2Tests { private static readonly string AppDir = Path.Combine(SourceTree.Root, "src", "Paca.App"); // ========================================================================= // Gate 201: 하위 대화상자 인터랙티브 컨트롤 UIA Name 전수 보증 // ========================================================================= [Theory] [InlineData("ClearBrowsingDataDialog.xaml", new[] { "TimeRangeCombo", "ClearHistoryCheck", "ClearCookiesCheck", "ClearCacheCheck", "ClearDownloadsCheck", "ClearBtn", "CancelBtn" })] [InlineData("CrashReportDialog.xaml", new[] { "DetailBox", "CopyDetailBtn", "CloseBtn" })] [InlineData("QrCodeDialog.xaml", new[] { "UrlDisplayBox", "CopyUrlBtn", "DoneBtn" })] [InlineData("BrowserTaskManagerWindow.xaml", new[] { "RefreshBtn", "TaskList", "EndTaskBtn", "CloseBtn" })] public void Gate201_SecondaryDialogs_Interactive_Controls_UiaName_Audit(string xamlFile, string[] controlNames) { var path = Path.Combine(AppDir, xamlFile); Assert.True(File.Exists(path), $"{xamlFile} must exist"); var xaml = File.ReadAllText(path); foreach (var name in controlNames) { // 각 컨트롤 태그 선언부 주변에 AutomationProperties.Name 이 선언되어 있는지 검증 var pattern = $@"(?:<[A-Za-z]+[^>]*x:Name=""{name}""[^>]*>|<[A-Za-z]+[^>]*Name=""{name}""[^>]*>)"; var match = Regex.Match(xaml, pattern, RegexOptions.Singleline); Assert.True(match.Success, $"컨트롤 '{name}' 태그를 {xamlFile} 에서 찾을 수 있어야 합니다."); Assert.True(match.Value.Contains("AutomationProperties.Name="), $"{xamlFile} 의 컨트롤 '{name}' 에 AutomationProperties.Name 이 필수 지정되어야 합니다."); } } // ========================================================================= // Gate 202: 고대비 테마(WHCM) 상태 브러시 WCAG AAA 7.0:1 보증 // ========================================================================= [Fact] public void Gate202_HighContrast_ErrorBrush_WCAG_AAA_Contrast_Audit() { var hcXaml = File.ReadAllText(Path.Combine(AppDir, "Themes", "HighContrast.xaml")); // ErrorBrush 의 색상 코드 추출 var match = Regex.Match(hcXaml, @" RGB var r = Convert.ToByte(hex[..2], 16) / 255.0; var g = Convert.ToByte(hex.Substring(2, 2), 16) / 255.0; var b = Convert.ToByte(hex.Substring(4, 2), 16) / 255.0; // sRGB 상대 휘도 계산 static double Lum(double c) => c <= 0.03928 ? c / 12.92 : Math.Pow((c + 0.055) / 1.055, 2.4); var lError = 0.2126 * Lum(r) + 0.7152 * Lum(g) + 0.0722 * Lum(b); // CardBgHover (#2D2D2D) 휘도 계산 var bgVal = 0x2D / 255.0; var lBg = 0.2126 * Lum(bgVal) + 0.7152 * Lum(bgVal) + 0.0722 * Lum(bgVal); var contrast = (lError + 0.05) / (lBg + 0.05); Assert.True(contrast >= 7.0, $"HighContrast ErrorBrush 의 명도 대비({contrast:F2}:1)는 WCAG AAA 7.0:1 이상이어야 합니다."); } // ========================================================================= // Gate 203: TabHost 순백(#FFFFFF) 발광 및 하드코딩 색상 차단 // ========================================================================= [Fact] public void Gate203_TabHost_NoWhiteBackground_Hardcode_Audit() { var mwXaml = File.ReadAllText(Path.Combine(AppDir, "MainWindow.xaml")); // TabHost 주변에 Background="#FFFFFF" 하드코딩이 없어야 함 var match = Regex.Match(mwXaml, @"]*x:Name=""TabHost""[^>]*>"); Assert.True(match.Success, "MainWindow.xaml 에 TabHost 가 존재해야 합니다."); Assert.DoesNotContain("Background=\"#FFFFFF\"", match.Value); Assert.DoesNotContain("Background=\"White\"", match.Value); } // ========================================================================= // Gate 206: 최대화 시 피츠의 법칙 툴바 상단 패딩 0px 전환 보증 // ========================================================================= [Fact] public void Gate206_FittsLaw_Toolbar_Padding_Zero_On_Maximized_Audit() { var mwCs = File.ReadAllText(Path.Combine(AppDir, "MainWindow.xaml.cs")); // WindowStateChanged 핸들러에서 Maximized 시 ToolbarRow.Padding 상단을 0으로 전환하는 로직 보증 Assert.Contains("WindowState.Maximized", mwCs); Assert.Contains("ToolbarRow", mwCs); } // ========================================================================= // Gate 207: SettingsWindow 보더리스 최대화 WM_GETMINMAXINFO 훅 보증 // ========================================================================= [Fact] public void Gate207_SettingsWindow_Has_WmGetMinMaxInfo_Hook_Audit() { var settingsCs = File.ReadAllText(Path.Combine(AppDir, "SettingsWindow.xaml.cs")); // 작업표시줄 가림 방지 및 모니터 작업영역 클램핑 윈도우 메시지 훅 검증 Assert.Contains("WM_GETMINMAXINFO", settingsCs); } }