releases/tests/Paca.Tests/E2E/DesignSystemCommandmentsGateTests.cs

155 lines
8.6 KiB
C#

using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using Paca.App;
using Paca.Tests.Infrastructure;
using Xunit;
namespace Paca.Tests.E2E;
/// <summary>
/// AGENTS.md 25대 디자인/인터랙션/접근성 헌법 중 미구현되었던 6대 게이트를 전수 기계적으로 검증하는 종합 테스트 게이트.
/// - Gate 7: Flexbox / Grid 레이아웃 왜곡 및 0px 찌그러짐 방지
/// - Gate 18: WCAG 2.2 SC 2.5.7 드래그 조작 대안 (단일 클릭 및 단축키 대안)
/// - Gate 19: WCAG 2.2 SC 3.3.7 중복 입력 방지 (클립보드 자동 인식 / 히스토리 / 자동채움)
/// - Gate 23: WCAG 2.2 SC 2.4.11/2.4.12 포커스 비가림 (Focus Not Obscured)
/// - Gate 24: 모달 포커스 트랩 및 Esc 안전 복원 (Focus Restoration)
/// - Gate 25: 게슈탈트 근접성 및 최적 행폭 (CPL 50~75) 독서 인체공학
/// </summary>
public class DesignSystemCommandmentsGateTests
{
private static readonly string AppDir = Path.Combine(SourceTree.Root, "src", "Paca.App");
// =========================================================================
// Gate 7: Flexbox / Grid 레이아웃 왜곡 및 0px 찌그러짐 방지
// =========================================================================
[Fact]
public void Gate07_Grid_Layout_Distortion_Prevention_Audit()
{
// 메인 윈도우 및 주요 분할 화면의 ColumnDefinition 에 최소 폭(MinWidth)이 설정되어
// 창 크기가 축소되더라도 메인 뷰포트나 사이드 패널이 0px로 압축 붕괴되지 않는지 검증
var mwXaml = File.ReadAllText(Path.Combine(AppDir, "MainWindow.xaml"));
Assert.True(File.Exists(Path.Combine(AppDir, "MainWindow.xaml")), "MainWindow.xaml must exist");
// 메인 뷰포트 컬럼과 우측 패널 컬럼에 MinWidth 가 필수 선언되어 있어야 함
Assert.Contains("x:Name=\"MainGrid\"", mwXaml);
Assert.Contains("MinWidth=\"360\"", mwXaml);
Assert.Contains("MinWidth=\"56\"", mwXaml);
// CredentialVaultWindow 또한 3단 분할 레이아웃이 안전한 컬럼 규격을 가져야 함
var vaultXaml = File.ReadAllText(Path.Combine(AppDir, "CredentialVaultWindow.xaml"));
Assert.Contains("Width=\"320\"", vaultXaml);
}
// =========================================================================
// Gate 18: WCAG 2.2 SC 2.5.7 드래그 대안 (Single Pointer / Keyboard Alternative)
// =========================================================================
[Fact]
public void Gate18_WCAG22_SC257_DraggingMovements_SinglePointerAlternative_Audit()
{
// 탭 이동, 다운로드 큐 순서 변경 등의 드래그 조작에 대해
// 마우스 우클릭 컨텍스트 메뉴 또는 단축키를 통한 비-드래그 조작 대안이 제공되어야 함
var mwXaml = File.ReadAllText(Path.Combine(AppDir, "MainWindow.xaml"));
// 탭 닫기 및 탭 조작에 대한 단축키와 클릭 대안
Assert.Contains("TabClose_Click", mwXaml);
Assert.Contains("ToolTip=\"탭 닫기 (Ctrl+W)\"", mwXaml);
Assert.Contains("x:Key=\"ToolbarButtonContextMenu\"", mwXaml);
Assert.Contains("x:Key=\"BookmarkItemContextMenu\"", mwXaml);
}
// =========================================================================
// Gate 19: WCAG 2.2 SC 3.3.7 중복 입력 방지 (Redundant Entry Prevention)
// =========================================================================
[Fact]
public void Gate19_WCAG22_SC337_RedundantEntry_Prevention_Audit()
{
// 사용자가 이미 입력했던 정보(검색어, URL, 자격증명)를 다시 입력하지 않도록
// 클리어 버튼, 히스토리 팝업, 자격증명 자동 채움, 클립보드 자동 인식 지원 검증
var mwXaml = File.ReadAllText(Path.Combine(AppDir, "MainWindow.xaml"));
// 1. 다운로드 검색창 클리어 버튼 제공
Assert.Contains("x:Name=\"DownloadSearchBox\"", mwXaml);
Assert.Contains("x:Name=\"DownloadSearchClearBtn\"", mwXaml);
// 2. 주소창 히스토리/제안 팝업 및 자동 완성 연동
Assert.True(mwXaml.Contains("Name=\"AddressBox\"") || mwXaml.Contains("x:Name=\"AddressBox\""),
"AddressBox must be declared with Name or x:Name");
Assert.True(mwXaml.Contains("Name=\"HistoryPopup\"") || mwXaml.Contains("x:Name=\"HistoryPopup\""),
"HistoryPopup must be declared with Name or x:Name");
// 3. 클립보드 자동 감지 타이머/핸들러 연동
var mwUsecasesCs = File.ReadAllText(Path.Combine(AppDir, "MainWindow.Usecases.cs"));
Assert.Contains("_clipboardTimer", mwUsecasesCs);
}
// =========================================================================
// Gate 23: WCAG 2.2 SC 2.4.11/2.4.12 포커스 비가림 (Focus Not Obscured)
// =========================================================================
[Fact]
public void Gate23_WCAG22_SC2411_FocusNotObscured_ScrollViewer_Audit()
{
// 스크롤 가능한 뷰포트 내에서 포커스된 컨트롤이 고정 헤더나 툴바에 의해 가려지지 않도록
// ScrollViewer 또는 항목 컨테이너에 적절한 여백/패딩 버퍼가 제공되는지 검증
var mwXaml = File.ReadAllText(Path.Combine(AppDir, "MainWindow.xaml"));
// 다운로드 목록 스크롤 뷰어가 고정 헤더 아래에서 여백을 유지하고 있는지 검증
Assert.Contains("VerticalScrollBarVisibility=\"Auto\"", mwXaml);
Assert.Contains("MaxHeight=\"360\"", mwXaml);
// 설정 창 내부 스크롤 뷰어도 포커스가 잘리지 않도록 ScrollViewer 패딩 확보
var settingsXaml = File.ReadAllText(Path.Combine(AppDir, "SettingsWindow.xaml"));
Assert.Contains("<ScrollViewer", settingsXaml);
}
// =========================================================================
// Gate 24: 모달 포커스 트랩 및 Esc 안전 복원 (Focus Trap & Restoration)
// =========================================================================
[Fact]
public void Gate24_Modal_FocusTrap_And_EscapeDismiss_Audit()
{
// 모든 주요 모달 창은 키보드 포커스가 외부로 탈출하지 않도록 TabNavigation="Cycle"을 선언하고,
// Esc 키를 눌렀을 때 안전하게 닫히도록 IsCancel="True" 버튼을 구비해야 함
var modalFiles = new[]
{
"SettingsWindow.xaml",
"CredentialVaultWindow.xaml",
"BookmarkManagerWindow.xaml",
"ExtensionManagerWindow.xaml",
"LogViewerWindow.xaml",
"CrashReportDialog.xaml"
};
foreach (var modalFile in modalFiles)
{
var path = Path.Combine(AppDir, modalFile);
Assert.True(File.Exists(path), $"{modalFile} 파일이 존재해야 합니다.");
var content = File.ReadAllText(path);
Assert.Contains("KeyboardNavigation.TabNavigation=\"Cycle\"", content);
var hasCancel = content.Contains("IsCancel=\"True\"") || content.Contains("Key=\"Escape\"");
Assert.True(hasCancel, $"{modalFile} 에 Esc 라이트 디스미스(IsCancel=True 또는 Key=Escape)가 선언되어야 합니다.");
}
}
// =========================================================================
// Gate 25: 게슈탈트 근접성 및 최적 행폭 (CPL 50~75) 독서 인체공학
// =========================================================================
[Fact]
public void Gate25_Gestalt_OptimalMeasure_CPL_ReaderMode_Audit()
{
// 텍스트 블록의 한 줄 글자 수(Characters Per Line: 50~75자)가 시각적 피로를 유발하지 않도록
// 리더 모드 및 문서/로그 뷰어의 본문 컨테이너 MaxWidth 가 적절한 범위(680px~840px)로 제한되어 있는지 검증
var readerModeCs = File.ReadAllText(Path.Combine(AppDir, "Browser", "Features", "ReaderMode.cs"));
// ReaderMode HTML CSS 에 max-width 가 선언되어 텍스트가 화면 전체로 무한히 늘어지지 않아야 함
var match = Regex.Match(readerModeCs, @"max-width:\s*(\d+)px");
Assert.True(match.Success, "ReaderMode.cs 에 본문 max-width 제한이 선언되어야 합니다.");
var maxWidth = int.Parse(match.Groups[1].Value);
Assert.True(maxWidth >= 600 && maxWidth <= 850,
$"ReaderMode 본문 MaxWidth({maxWidth}px)는 CPL 50~75 최적 독서 규격(600~850px) 범위 내여야 합니다.");
}
}