using System.IO; using Paca.Server; using Paca.Tests.Infrastructure; using Xunit; namespace Paca.Tests.Browser; /// /// 브라우저 UX 고도화 및 글래스모피즘 디자인 검증: /// - 햄버거 메인 메뉴(MainMenuBtn 및 MainMenuPopup 다층 서브메뉴) 구성 /// - 다크/라이트/고대비 테마의 GlassBorder 토큰 무결성 /// - Shared.xaml 제로 헤어라인 고스트/아이콘 버튼 및 글래스모픽 메뉴 스타일 /// - WebCompanion 글래스모피즘 CSS 토큰 및 반응형 레이아웃 /// public class BrowserMenuAndDesignTests { private static string AppPath(params string[] parts) => SourceTree.AppPath(Path.Combine(parts)); [Fact] public void 메인윈도우_툴바_메인메뉴_버튼과_팝업이_정의되어_있다() { var xamlPath = AppPath("MainWindow.xaml"); Assert.True(File.Exists(xamlPath), "MainWindow.xaml 이 존재해야 합니다."); var content = File.ReadAllText(xamlPath); // 햄버거/3-dots 메인 메뉴 버튼 Assert.Contains("Name=\"MainMenuBtn\"", content); Assert.Contains("AutomationProperties.Name=\"메인 메뉴\"", content); // 다층 서브메뉴가 있는 MainMenuPopup 및 VerticalMenu Assert.Contains("Name=\"MainMenuPopup\"", content); Assert.Contains("Style=\"{StaticResource VerticalMenu}\"", content); Assert.Contains("MenuItem.Click=\"MainMenu_MenuItemClick\"", content); Assert.Contains("Header=\"새 탭\"", content); Assert.Contains("Header=\"새 창\"", content); Assert.Contains("Header=\"새 InPrivate 창\"", content); Assert.Contains("Header=\"방문 기록\"", content); Assert.Contains("Header=\"즐겨찾기\"", content); Assert.Contains("Header=\"다운로드 & 라이브러리\"", content); Assert.Contains("Header=\"페이지 도구\"", content); Assert.Contains("Header=\"미디어 & 원격 기능\"", content); Assert.Contains("Header=\"개발자 도구\"", content); Assert.Contains("Header=\"도움말 & 정보\"", content); Assert.Contains("Name=\"MenuZoomText\"", content); } [Theory] [InlineData("Dark.xaml")] [InlineData("Light.xaml")] [InlineData("HighContrast.xaml")] public void 모든_테마에_GlassBorder_브러시_토큰이_정의되어_있다(string themeFile) { var themePath = AppPath("Themes", themeFile); Assert.True(File.Exists(themePath), $"{themeFile} 이 존재해야 합니다."); var content = File.ReadAllText(themePath); Assert.Contains("x:Key=\"GlassBorder\"", content); } [Fact] public void Shared_사전에_GhostButton과_MenuItem_글래스모피즘_스타일이_정의되어_있다() { var sharedPath = AppPath("Themes", "Shared.xaml"); Assert.True(File.Exists(sharedPath), "Themes/Shared.xaml 이 존재해야 합니다."); var content = File.ReadAllText(sharedPath); // 버튼 스타일 Assert.Contains("x:Key=\"GhostButton\"", content); Assert.Contains("x:Key=\"IconButton\"", content); Assert.Contains("x:Key=\"CardActionButton\"", content); // 메뉴 스타일 Assert.Contains("TargetType=\"MenuItem\"", content); Assert.Contains("TargetType=\"ContextMenu\"", content); Assert.Contains("TargetType=\"Menu\"", content); Assert.Contains("x:Key=\"VerticalMenu\"", content); Assert.Contains("TargetType=\"Separator\"", content); } [Fact] public void WebCompanion_글래스모피즘_CSS_스타일이_적용되어_있다() { var html = WebCompanion.Html; Assert.Contains("backdrop-filter: blur(", html); Assert.Contains("--glass-border", html); Assert.Contains("--card-bg", html); Assert.Contains("PACA", html); Assert.Contains("원격 다운로드", html); } [Fact] public void Shared_사전에_탭가독성_Foreground와_미니콤보박스_스타일이_정의되어_있다() { var sharedPath = AppPath("Themes", "Shared.xaml"); Assert.True(File.Exists(sharedPath), "Themes/Shared.xaml 이 존재해야 합니다."); var content = File.ReadAllText(sharedPath); // 탭 제목 Foreground 설정 (다크/라이트 테마 가독성 보장) Assert.Contains("x:Key=\"TabTitleStyle\"", content); Assert.Contains("Value=\"{DynamicResource TextSecondary}\"", content); // 탭 닫기 및 미니 인라인 콤보박스 스타일 Assert.Contains("x:Key=\"TabCloseButton\"", content); Assert.Contains("x:Key=\"MiniDarkComboBox\"", content); } [Fact] public void 메인윈도우_작업공간_캡슐과_활성탭_대비가_정의되어_있다() { var xamlPath = AppPath("MainWindow.xaml"); Assert.True(File.Exists(xamlPath), "MainWindow.xaml 이 존재해야 합니다."); var content = File.ReadAllText(xamlPath); // 불필요한 라벨 제거 및 미니멀 작업공간 드롭다운/인디케이터 확인 Assert.DoesNotContain("작업공간:", content); Assert.Contains("x:Name=\"WorkspaceIndicator\"", content); Assert.Contains("x:Name=\"WorkspaceSwitcher\"", content); Assert.Contains("Style=\"{StaticResource MiniDarkComboBox}\"", content); // 활성 탭 텍스트 명암비(TextPrimary 및 SemiBold) Assert.Contains("x:Name=\"TabTitleText\"", content); Assert.Contains("Value=\"{DynamicResource TextPrimary}\"", content); Assert.Contains("Value=\"SemiBold\"", content); } [Fact] public void 메뉴와_팝업_플라이아웃에_Fade_애니메이션이_정의되어_있다() { var sharedPath = AppPath("Themes", "Shared.xaml"); Assert.True(File.Exists(sharedPath), "Themes/Shared.xaml 이 존재해야 합니다."); var sharedContent = File.ReadAllText(sharedPath); // FlyoutBorder 스타일 및 페이드 애니메이션 스토리보드 Assert.Contains("x:Key=\"FlyoutBorder\"", sharedContent); Assert.Contains("Storyboard.TargetProperty=\"Opacity\"", sharedContent); Assert.Contains("DoubleAnimation", sharedContent); // MainWindow.xaml 팝업들의 Fade 애니메이션 및 FlyoutBorder 적용 var xamlPath = AppPath("MainWindow.xaml"); Assert.True(File.Exists(xamlPath), "MainWindow.xaml 이 존재해야 합니다."); var mainContent = File.ReadAllText(xamlPath); Assert.Contains("PopupAnimation=\"Fade\"", mainContent); Assert.Contains("Style=\"{StaticResource FlyoutBorder}\"", mainContent); } [Fact] public void TabViewService_초기탐색은_about_blank에서도_HomeUrl을_탐색한다() { var servicePath = AppPath("Services", "TabViewService.cs"); Assert.True(File.Exists(servicePath), "TabViewService.cs 가 존재해야 합니다."); var content = File.ReadAllText(servicePath); // 초기 URL 탐색 시 about:blank 가 감지되어도 tab.Url 로 Navigate 호출을 수행해야 함 Assert.Contains("string.Equals(cv.Source, \"about:blank\", StringComparison.OrdinalIgnoreCase)", content); } [Fact] public void WebCompanion_모바일_100dvh_및_세련된_글래스모피즘이_적용되어_있다() { var html = WebCompanion.Html; Assert.Contains("100dvh", html); // 모바일 브라우저 뷰포트 점핑 방지 Assert.Contains("--glass-inner-shadow", html); Assert.Contains("--accent-gradient", html); Assert.Contains("Pretendard", html); } [Fact] public void WebCompanion_ConnectCode_원격_게이트웨이_및_이어보기_동기화가_포함되어_있다() { var html = WebCompanion.Html; // ConnectCode 및 3G/4G/5G 원격 안내와 이어보기 위치 API(/api/position) 스크립트 검증 Assert.Contains("cc=", html); Assert.Contains("/api/position", html); } [Fact] public void WebCompanion_Token_QueryParam_및_LocalStorage_영속화_스크립트가_포함되어_있다() { var html = WebCompanion.Html; Assert.Contains("localStorage", html); Assert.Contains("paca_t", html); } [Fact] public void PairingWindow_다크_테마_글래스모피즘_리소스가_적용되어_있다() { var pairingPath = AppPath("Browser", "PairingWindow.cs"); Assert.True(File.Exists(pairingPath), "PairingWindow.cs 가 존재해야 합니다."); var content = File.ReadAllText(pairingPath); Assert.Contains("WindowBgGradient", content); Assert.Contains("CardBg", content); Assert.Contains("WindowChrome", content); Assert.Contains("PrimaryButton", content); Assert.DoesNotContain("Background = System.Windows.Media.Brushes.White;", content); } [Fact] public void PairingWindow_LAN_및_5G_원격_게이트웨이_UI가_구비되어_있다() { var pairingPath = AppPath("Browser", "PairingWindow.cs"); Assert.True(File.Exists(pairingPath), "PairingWindow.cs 가 존재해야 합니다."); var content = File.ReadAllText(pairingPath); // LAN 원클릭 자동 탐색 및 3G/4G/5G 원격 게이트웨이 Connect Code UI 요소 검증 Assert.Contains("LAN 자동 탐색", content); Assert.Contains("3G / 4G / 5G", content); Assert.Contains("ConnectCode", content); } [Fact] public void PlayerWindow_테마_토큰_및_글래스_HUD가_정의되어_있다() { var playerPath = AppPath("Browser", "PlayerWindow.cs"); Assert.True(File.Exists(playerPath), "PlayerWindow.cs 가 존재해야 합니다."); var content = File.ReadAllText(playerPath); Assert.Contains("PanelBg", content); Assert.Contains("BorderSubtle", content); Assert.Contains("CardActionButton", content); Assert.Contains("MiniDarkComboBox", content); Assert.Contains("backdrop-filter:blur(24px)", content); } [Fact] public void WebView2_웹페이지_우클릭_컨텍스트메뉴는_네이티브_메뉴로_표시되고_다운로드항목이_주입된다() { var tabViewPath = AppPath("Services", "TabViewService.cs"); var tabViewContent = File.ReadAllText(tabViewPath); // ce.Handled = true; 로 네이티브 메뉴를 전면 차단하지 않아야 함 Assert.DoesNotContain("ce.Handled = true;\r\n ContextMenuBuild?.Invoke", tabViewContent); Assert.DoesNotContain("ce.Handled = true;\n ContextMenuBuild?.Invoke", tabViewContent); var mainPath = AppPath("MainWindow.xaml.cs"); var mainContent = File.ReadAllText(mainPath); // WPF ContextMenu 팝업 우회 대신 WebView2 CreateContextMenuItem 네이티브 주입 사용 Assert.Contains("CreateContextMenuItem", mainContent); Assert.Contains("CustomItemSelected", mainContent); } [Fact] public void TabViewService_소셜로그인_OAuth_팝업_WindowCloseRequested_이벤트를_배선한다() { var tabViewPath = AppPath("Services", "TabViewService.cs"); var tabViewContent = File.ReadAllText(tabViewPath); // 구글 로그인 등 OAuth 팝업이 window.close() 호출 시 탭이 자동 닫히도록 배선 필수 Assert.Contains("WindowCloseRequested", tabViewContent); Assert.Contains("TabCloseRequested", tabViewContent); var mainPath = AppPath("MainWindow.xaml.cs"); var mainContent = File.ReadAllText(mainPath); Assert.Contains("TabCloseRequested += tab =>", mainContent); } }