releases/VideoDownloader.Tests/E2E/UiUsecaseLiveE2ETests.cs

249 lines
13 KiB
C#

using System.Windows;
using System.Windows.Controls;
using VideoDownloader.App;
using VideoDownloader.App.Browser.Features;
using VideoDownloader.App.Core;
using VideoDownloader.App.Services;
using VideoDownloader.App.ViewModels;
using VideoDownloader.Core.Config;
using VideoDownloader.Tests.Infrastructure;
using WpfApp = VideoDownloader.App.App;
namespace VideoDownloader.Tests.E2E;
/// <summary>
/// E2E: "UI 복잡 버전"(docs/UI_USECASES.md §A~§J) GREEN 구현의 실창 실증 —
/// MainWindowSmokeE2ETests 와 동일한 StaDispatcher/WpfTestApp 패턴으로 실제 윈도우를 띄워
/// ① 신규 유즈케이스 컨트롤이 실 트리에 존재하는지 ② 테마가 재시작 없이 라이브 전환되는지(UC-T03/T04)
/// ③ 다운로드 일시정지/재개 상태 전이(UC-D06/D07) ④ 설정창 검색 필터·테마 즉시 적용(UC-S01)
/// ⑤ 토스트 방해 금지 억제·알림센터 히스토리(UC-N14/N15)를 검증한다.
/// </summary>
[Collection("E2E")]
public class UiUsecaseLiveE2ETests
{
private static string TempDataDir()
{
var dir = Path.Combine(Path.GetTempPath(), $"vd_uc_e2e_{Guid.NewGuid():N}");
Environment.SetEnvironmentVariable("VD_DATA_DIR", dir);
return dir;
}
[Fact]
public async Task MainWindow_유즈케이스_컨트롤_실존과_라이브_테마_전환_E2E()
{
var dataDir = TempDataDir();
try
{
using var sta = StaDispatcher.Start();
WpfTestApp.Ensure(sta.Dispatcher);
var ok = await sta.InvokeAsync(async () =>
{
var window = new MainWindow();
window.Show();
var first = await Wait.Until(
() => Task.FromResult(window.Tabs.ActiveTab),
t => t?.Core != null, 30000);
// ── §C/§D/§E/§I/§J 신규 컨트롤이 실 트리에 존재(최상위 x:Name 필드) ──
var mustExist = new FrameworkElement?[]
{
// 툴바
window.CompactModeBtn, window.LibraryBtn, window.PairingBtn,
window.DownloadsBtnBadge, window.UpdateBadge,
// 탭 스트립
window.WorkspaceIndicator, window.WorkspaceSwitcher, window.NewTabMoreBtn,
window.ArchiveBtn, window.TabInsertIndicator,
// 다운로드 패널
window.DownloadFilterTabs, window.DownloadSearchBox, window.QueueSummaryText,
window.GlobalSpeedText, window.SpeedSparkline,
// 상태바·오버레이
window.StatusRight, window.ZoomIndicator, window.ServerStatusIcon,
window.NotificationCenter, window.ShortcutHelpBtn, window.AggregateProgress,
window.ToastHost, window.ErrorBanner, window.ErrorDetailExpander,
window.ClipboardHint, window.Onboarding,
// 팝업 본문
window.ArchivePopup, window.ArchiveSearchBox,
window.LibraryPopup, window.LibrarySearchBox, window.LibraryViewToggle, window.LibrarySortMenu,
window.BookmarkSearchBox, window.HistorySearchBox, window.NotesSearchBox,
window.PlaylistSearchBox, window.PlaylistShuffle,
window.ShortcutHelpPopup, window.NotificationCenterPopup, window.TabVolumePopup,
};
var missing = mustExist.Count(e => e == null);
// ── §J 초기 상태 계약: 온보딩 표시(세션 없음)·오류배너·클립보드힌트 숨김 ──
var onboardingShown = window.Onboarding.Visibility == Visibility.Visible;
var errorHidden = window.ErrorBanner.Visibility == Visibility.Collapsed;
var clipboardHidden = window.ClipboardHint.Visibility == Visibility.Collapsed;
// ── §A 라이브 테마 전환(UC-T03/T04) — 창을 다시 만들지 않고 즉시 ──
window.ApplyTheme("light");
var lightApplied = WpfApp.CurrentTheme == "light" && WpfApp.IsLightTheme;
var aliveInLight = window.IsLoaded;
window.ApplyTheme("dark");
var darkApplied = WpfApp.CurrentTheme == "dark" && !WpfApp.IsLightTheme;
var aliveInDark = window.IsLoaded;
var lightRestore = WpfApp.CurrentTheme == "dark"; // 원복 확인
window.Close();
await Task.Delay(800); // WebView2 파일 핸들 해제 여유(smoke 테스트 패턴)
var fails = new List<string>();
if (missing != 0) fails.Add($"컨트롤 누락 {missing}개");
if (!onboardingShown) fails.Add("온보딩 미표시");
if (!errorHidden) fails.Add("오류배너 초기 미숨김");
if (!clipboardHidden) fails.Add("클립보드힌트 초기 미숨김");
if (!lightApplied) fails.Add("라이트 테마 미적용");
if (!aliveInLight) fails.Add("라이트 전환 후 창 사망");
if (!darkApplied) fails.Add("다크 원복 미적용");
if (!aliveInDark) fails.Add("다크 전환 후 창 사망");
return string.Join(" | ", fails);
});
Assert.True(string.IsNullOrEmpty(ok), $"MainWindow 유즈케이스 E2E 실패: {ok}");
}
finally
{
Environment.SetEnvironmentVariable("VD_DATA_DIR", null);
try { Directory.Delete(dataDir, recursive: true); } catch { } // WebView2 파일 점유 시 남김(smoke 테스트 패턴)
}
}
[Fact]
public void DownloadManager_전체_일시정지_및_재개_상태전이_E2E()
{
var dataDir = TempDataDir();
var outDir = Path.Combine(dataDir, "out");
try
{
using var sta = StaDispatcher.Start();
WpfTestApp.Ensure(sta.Dispatcher);
var ok = sta.Invoke(() =>
{
var config = new AppConfig { OutputFolder = outDir, MaxConcurrentDownloads = 2 };
var manager = new DownloadManager(
config,
_ => Task.FromResult(new List<Microsoft.Web.WebView2.Core.CoreWebView2Cookie>()),
_ => { });
var running = new DownloadItemViewModel("진행 중 항목")
{
Status = "다운로드 중",
Started = true,
MediaUrl = new Uri("https://127.0.0.1:9/video.mp4"), // 비라우팅 — 재개가 네트워크를 치지 않게
OutputBase = Path.Combine(outDir, "video"),
};
var queued = new DownloadItemViewModel("대기 항목")
{
Status = "대기",
MediaUrl = new Uri("https://example.com/video2.mp4"),
OutputBase = Path.Combine(outDir, "video2"),
};
manager.Items.Add(running);
manager.Items.Add(queued);
// UC-D06: 전체 일시정지 — 진행 중만 "일시정지" 전이, 대기 항목은 그대로
manager.PauseAll();
var pausedStatus = running.Status == "일시정지" && running.PausedForResume;
var queuedUntouched = queued.Status == "대기";
// UC-D07: 전체 재개 — 일시정지 항목만 재투입(PausedForResume 해제 + Started 재설정)
manager.ResumeAll();
var resumed = !running.PausedForResume && running.Started; // 재시작 큐 진입
manager.Cancel(running); // 실측 목적 달성 — 원격 접속 시도 중단
var queuedStillQueued = queued.Started == false && queued.PausedForResume == false;
var fails = new List<string>();
if (!pausedStatus) fails.Add($"PauseAll 후 상태={running.Status}/PausedForResume={running.PausedForResume}");
if (!queuedUntouched) fails.Add($"대기 항목이 건드려짐(상태={queued.Status})");
if (!resumed) fails.Add($"ResumeAll 후 재투입 안 됨(Started={running.Started}/PausedForResume={running.PausedForResume})");
if (!queuedStillQueued) fails.Add("대기 항목이 재개 대상이 됨");
return string.Join(" | ", fails);
});
Assert.True(string.IsNullOrEmpty(ok), $"일시정지/재개 E2E 실패: {ok}");
}
finally
{
Environment.SetEnvironmentVariable("VD_DATA_DIR", null);
try { Directory.Delete(dataDir, recursive: true); } catch { } // WebView2 파일 점유 시 남김(smoke 테스트 패턴)
}
}
[Fact]
public void SettingsWindow_검색_필터와_테마_즉시적용_E2E()
{
var dataDir = TempDataDir();
try
{
using var sta = StaDispatcher.Start();
WpfTestApp.Ensure(sta.Dispatcher);
var ok = sta.Invoke(() =>
{
var config = new AppConfig(); // 기본 테마 dark
var win = new SettingsWindow(config);
// 생성 시점 로드: 테마 dark 반영 + 검색창 비어 전 섹션 표시
var initialTheme = WpfApp.CurrentTheme == "dark";
var allVisible = win.SectionFont.Visibility == Visibility.Visible
&& win.SectionDownload.Visibility == Visibility.Visible;
// UC-T01/T02: 콤보박스로 라이트 선택 → 앱 테마가 즉시 전환(재시작 없음)
win.ThemeModeBox.SelectedValue = "light";
var lightLive = WpfApp.CurrentTheme == "light" && WpfApp.IsLightTheme;
win.ThemeModeBox.SelectedValue = "dark";
var backToDark = WpfApp.CurrentTheme == "dark";
// UC-S01: 설정 검색 — 해당 섹션만 노출
win.SettingsSearchBox.Text = "글꼴";
var fontOnly = win.SectionFont.Visibility == Visibility.Visible
&& win.SectionDownload.Visibility == Visibility.Collapsed
&& win.SectionNotify.Visibility == Visibility.Collapsed;
win.SettingsSearchBox.Text = "알림";
var downloadOnly = win.SectionNotify.Visibility == Visibility.Visible
&& win.SectionDownload.Visibility == Visibility.Collapsed
&& win.SectionAccessibility.Visibility == Visibility.Collapsed;
win.SettingsSearchBox.Text = "";
var restored = win.SectionFont.Visibility == Visibility.Visible
&& win.SectionDownload.Visibility == Visibility.Visible;
win.Close();
var fails = new List<string>();
if (!initialTheme) fails.Add($"초기 테마 dark 아님(현재 {WpfApp.CurrentTheme})");
if (!allVisible) fails.Add("초기 전 섹션 표시 아님");
if (!lightLive) fails.Add($"라이트 즉시 적용 실패(현재 {WpfApp.CurrentTheme}/{WpfApp.IsLightTheme})");
if (!backToDark) fails.Add($"다크 원복 실패(현재 {WpfApp.CurrentTheme})");
if (!fontOnly) fails.Add("'글꼴' 검색 시 글꼴 섹션만 표시 아님");
if (!downloadOnly) fails.Add("'알림' 검색 시 알림 섹션만 표시 아님");
if (!restored) fails.Add("빈 검색어로 복원 안 됨");
return string.Join(" | ", fails);
});
Assert.True(string.IsNullOrEmpty(ok), $"설정창 E2E 실패: {ok}");
}
finally
{
Environment.SetEnvironmentVariable("VD_DATA_DIR", null);
try { Directory.Delete(dataDir, recursive: true); } catch { } // WebView2 파일 점유 시 남김(smoke 테스트 패턴)
}
}
[Fact]
public void ToastNotifier_방해금지_억제와_알림센터_히스토리_E2E()
{
using var sta = StaDispatcher.Start();
WpfTestApp.Ensure(sta.Dispatcher);
var ok = sta.Invoke(() =>
{
var host = new StackPanel();
var toast = new ToastNotifier(host) { Duration = TimeSpan.FromMilliseconds(500) };
// 일반 표시: 토스트 1장 + 히스토리 1건(UC-N02)
toast.Show("완료: 첫 번째", "success");
var shown = host.Children.Count == 1 && toast.History.Count == 1;
// UC-N15 방해 금지: 표시는 억제되고 히스토리에만 기록(UC-N14 알림 센터 원천)
toast.DoNotDisturb = true;
toast.Show("실패: 두 번째", "error");
var suppressed = host.Children.Count == 1 && toast.History.Count == 2;
return shown && suppressed;
});
Assert.True(ok, "토스트 방해 금지/히스토리 계약 위반");
}
}