releases/tests/Paca.Tests/Browser/BrowserMenuAndDesignTests.cs

260 lines
12 KiB
C#

using System.IO;
using Paca.Server;
using Paca.Tests.Infrastructure;
using Xunit;
namespace Paca.Tests.Browser;
/// <summary>
/// 브라우저 UX 고도화 및 글래스모피즘 디자인 검증:
/// - 햄버거 메인 메뉴(MainMenuBtn 및 MainMenuPopup 다층 서브메뉴) 구성
/// - 다크/라이트/고대비 테마의 GlassBorder 토큰 무결성
/// - Shared.xaml 제로 헤어라인 고스트/아이콘 버튼 및 글래스모픽 메뉴 스타일
/// - WebCompanion 글래스모피즘 CSS 토큰 및 반응형 레이아웃
/// </summary>
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=\"다운로드 &amp; 라이브러리\"", content);
Assert.Contains("Header=\"페이지 도구\"", content);
Assert.Contains("Header=\"미디어 &amp; 원격 기능\"", content);
Assert.Contains("Header=\"개발자 도구\"", content);
Assert.Contains("Header=\"도움말 &amp; 정보\"", 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);
}
}