using System; using System.IO; using System.Linq; using System.Windows; using Paca.App; using Paca.App.Browser.Features; using Paca.Browser.Tabs; using Paca.Tests.Browser; using Xunit; namespace Paca.Tests.Browser; /// /// 크롬 파리티: 창 내 모든 탭이 완전히 닫혔을 때(남은 탭 0개) 빈 화면이나 빈 탭이 아닌 /// 올바른 Home Tab(일반: Home/NTP, 시크릿: InPrivate NTP)이 자동으로 나타나는지 검증하는 테스트 게이트. /// public sealed class ChromeParityLastTabCloseHomeTabGateTests { [Fact] public void TabManager_일반탭_마지막탭_닫으면_HomeUrl과_제목을_가진_홈탭이_생성된다() { // Arrange var mgr = new TabManager(() => new TabItem()) { HomeUrl = "paca://newtab" }; var solo = mgr.NewTab("https://www.google.com"); solo.Title = "구글 검색"; Assert.Single(mgr.Tabs); Assert.Same(solo, mgr.ActiveTab); // Act: 유일한 탭 닫기 var closed = mgr.CloseTab(solo); // Assert: 탭이 완전히 닫혔지만 탭이 0개가 되지 않고 홈 탭이 생성됨 Assert.True(closed); Assert.Single(mgr.Tabs); var homeTab = mgr.ActiveTab; Assert.NotNull(homeTab); Assert.NotSame(solo, homeTab); Assert.Equal("paca://newtab", homeTab!.Url); Assert.Equal("새 탭", homeTab.Title); Assert.False(homeTab.IsPrivate); Assert.True(homeTab.IsActive); } [Fact] public void TabManager_시크릿탭_마지막탭_닫으면_InPrivate_HomeUrl과_제목을_가진_홈탭이_생성된다() { // Arrange var mgr = new TabManager(() => new TabItem()) { DefaultIsPrivate = true, HomeUrl = "paca://newtab", HomeUrlProvider = isPriv => isPriv ? "paca://inprivate" : "paca://newtab" }; var privTab = mgr.NewTab("https://secret.example.org", isPrivate: true); privTab.Title = "비밀 페이지"; Assert.Single(mgr.Tabs); // Act var closed = mgr.CloseTab(privTab); // Assert Assert.True(closed); Assert.Single(mgr.Tabs); var homeTab = mgr.ActiveTab; Assert.NotNull(homeTab); Assert.NotSame(privTab, homeTab); Assert.Equal("paca://inprivate", homeTab!.Url); Assert.Equal("InPrivate 새 탭", homeTab.Title); Assert.True(homeTab.IsPrivate); Assert.True(homeTab.IsActive); } [Fact] public void TabManager_CloseAllTabs_호출시_모든탭이_닫히고_홈탭_1개로_초기화된다() { // Arrange var mgr = new TabManager(() => new TabItem()) { HomeUrl = "paca://newtab" }; mgr.NewTab("https://site1.com"); mgr.NewTab("https://site2.com"); mgr.NewTab("https://site3.com"); Assert.Equal(3, mgr.Tabs.Count); // Act var homeTab = mgr.CloseAllTabs(); // Assert Assert.Single(mgr.Tabs); Assert.Same(homeTab, mgr.Tabs[0]); Assert.Same(homeTab, mgr.ActiveTab); Assert.Equal("paca://newtab", homeTab.Url); Assert.Equal("새 탭", homeTab.Title); } [Fact] public void MainWindow_단일탭_닫기시_홈탭이_자동생성되고_IsNewTabUrl_조건을_충족한다() { using var sta = StaDispatcher.Start(); WpfTestApp.Ensure(sta.Dispatcher); sta.Invoke(() => { var dataDir = Path.Combine(Path.GetTempPath(), $"vd_home_tab_close_{Guid.NewGuid():N}"); Environment.SetEnvironmentVariable("VD_DATA_DIR", dataDir); try { var win = new MainWindow(isPrivateWindow: false); win.TabsManager.Tabs.Clear(); var solo = win.TabsManager.NewTab("https://example.com"); solo.Title = "예제 사이트"; win.TabsManager.Activate(solo); Assert.Single(win.TabsManager.Tabs); // Act: 닫기 win.TabsManager.CloseTab(solo); // Assert: 닫힌 후 새 홈 탭이 생성되어 있으며 NewTabUrl 규격을 만족함 Assert.Single(win.TabsManager.Tabs); var active = win.TabsManager.ActiveTab; Assert.NotNull(active); Assert.True(NewTabPageBuilder.IsNewTabUrl(active!.Url), $"Expected NewTabUrl but was '{active.Url}'"); Assert.Equal("새 탭", active.Title); Assert.False(active.IsPrivate); } finally { try { if (Directory.Exists(dataDir)) Directory.Delete(dataDir, true); } catch { } } }); } [Fact] public void MainWindow_시크릿창_단일탭_닫기시_InPrivate홈탭이_생성된다() { using var sta = StaDispatcher.Start(); WpfTestApp.Ensure(sta.Dispatcher); sta.Invoke(() => { var dataDir = Path.Combine(Path.GetTempPath(), $"vd_priv_home_close_{Guid.NewGuid():N}"); Environment.SetEnvironmentVariable("VD_DATA_DIR", dataDir); try { var win = new MainWindow(isPrivateWindow: true); win.TabsManager.Tabs.Clear(); var privTab = win.TabsManager.NewTab("https://secret.com", isPrivate: true); privTab.Title = "보안 세션"; win.TabsManager.Activate(privTab); Assert.Single(win.TabsManager.Tabs); // Act win.TabsManager.CloseTab(privTab); // Assert Assert.Single(win.TabsManager.Tabs); var active = win.TabsManager.ActiveTab; Assert.NotNull(active); Assert.Equal(NewTabPageBuilder.InPrivateNtpUrl, active!.Url); Assert.Equal("InPrivate 새 탭", active.Title); Assert.True(active.IsPrivate); } finally { try { if (Directory.Exists(dataDir)) Directory.Delete(dataDir, true); } catch { } } }); } [Fact] public void MainWindow_다중창에서_각각_마지막탭_닫을때_독립적으로_홈탭이_생성된다() { using var sta = StaDispatcher.Start(); WpfTestApp.Ensure(sta.Dispatcher); sta.Invoke(() => { var dataDir = Path.Combine(Path.GetTempPath(), $"vd_multi_win_close_{Guid.NewGuid():N}"); Environment.SetEnvironmentVariable("VD_DATA_DIR", dataDir); try { // Window 1: 일반 창 var win1 = new MainWindow(isPrivateWindow: false); win1.TabsManager.Tabs.Clear(); var tab1 = win1.TabsManager.NewTab("https://site-window-1.com"); win1.TabsManager.Activate(tab1); // Window 2: 시크릿 창 var win2 = new MainWindow(isPrivateWindow: true); win2.TabsManager.Tabs.Clear(); var tab2 = win2.TabsManager.NewTab("https://site-window-2.com", isPrivate: true); win2.TabsManager.Activate(tab2); // Act: 두 창의 마지막 탭을 각각 닫음 win1.TabsManager.CloseTab(tab1); win2.TabsManager.CloseTab(tab2); // Assert: Window 1은 일반 홈 탭 Assert.Single(win1.TabsManager.Tabs); Assert.True(NewTabPageBuilder.IsNewTabUrl(win1.TabsManager.ActiveTab?.Url)); Assert.False(win1.TabsManager.ActiveTab?.IsPrivate); Assert.Equal("새 탭", win1.TabsManager.ActiveTab?.Title); // Assert: Window 2는 시크릿 홈 탭 Assert.Single(win2.TabsManager.Tabs); Assert.Equal(NewTabPageBuilder.InPrivateNtpUrl, win2.TabsManager.ActiveTab?.Url); Assert.True(win2.TabsManager.ActiveTab?.IsPrivate); Assert.Equal("InPrivate 새 탭", win2.TabsManager.ActiveTab?.Title); } finally { try { if (Directory.Exists(dataDir)) Directory.Delete(dataDir, true); } catch { } } }); } [Fact] public void TabManager_마지막_탭이_홈탭이면_CloseTab이_거부되고_탭이_유지된다() { // Arrange: 홈 탭 1개만 존재하는 상태 var mgr = new TabManager(() => new TabItem()) { HomeUrl = "paca://newtab" }; var homeTab = mgr.NewHomeTab(); Assert.Single(mgr.Tabs); Assert.Same(homeTab, mgr.ActiveTab); // Act: 마지막 탭이자 홈 탭인 탭 닫기 시도 var canClose = mgr.CanCloseTab(homeTab); var closed = mgr.CloseTab(homeTab); // Assert: 닫기가 거부되고 탭이 그대로 유지됨 Assert.False(canClose); Assert.False(closed); Assert.Single(mgr.Tabs); Assert.Same(homeTab, mgr.Tabs[0]); Assert.Same(homeTab, mgr.ActiveTab); } [Fact] public void TabManager_마지막_탭이_InPrivate_홈탭이면_CloseTab이_거부된다() { // Arrange var mgr = new TabManager(() => new TabItem()) { DefaultIsPrivate = true, HomeUrlProvider = p => p ? "paca://inprivate" : "paca://newtab" }; var privHome = mgr.NewHomeTab(isPrivate: true); Assert.Single(mgr.Tabs); Assert.Same(privHome, mgr.ActiveTab); // Act var canClose = mgr.CanCloseTab(privHome); var closed = mgr.CloseTab(privHome); // Assert Assert.False(canClose); Assert.False(closed); Assert.Single(mgr.Tabs); Assert.Same(privHome, mgr.Tabs[0]); Assert.Same(privHome, mgr.ActiveTab); } [Fact] public void TabManager_다중탭일때_홈탭은_정상적으로_닫힌다() { // Arrange: 홈 탭과 일반 웹페이지 탭이 모두 존재하는 다중 탭 상태 var mgr = new TabManager(() => new TabItem()) { HomeUrl = "paca://newtab" }; var home = mgr.NewHomeTab(); var web = mgr.NewTab("https://example.com"); Assert.Equal(2, mgr.Tabs.Count); // Act: 홈 탭 닫기 var canClose = mgr.CanCloseTab(home); var closed = mgr.CloseTab(home); // Assert: 다중 탭 상태이므로 홈 탭이라도 정상적으로 닫힘 Assert.True(canClose); Assert.True(closed); Assert.Single(mgr.Tabs); Assert.Same(web, mgr.ActiveTab); } [Fact] public void TabManager_일반사이트_마지막탭_닫기후_생성된_홈탭은_연속닫기_시도시_닫히지_않는다() { // Arrange: 일반 웹페이지 단일 탭 var mgr = new TabManager(() => new TabItem()) { HomeUrl = "paca://newtab" }; var solo = mgr.NewTab("https://news.com"); Assert.Single(mgr.Tabs); // Act 1: 일반 웹페이지 단일 탭 닫기 -> 홈 탭으로 자동 대체되어 창 유지 var canClose1 = mgr.CanCloseTab(solo); var closed1 = mgr.CloseTab(solo); Assert.True(canClose1); Assert.True(closed1); Assert.Single(mgr.Tabs); var autoHome = mgr.ActiveTab; Assert.NotNull(autoHome); Assert.NotSame(solo, autoHome); // Act 2: 이제 마지막 탭이 홈 탭이 된 상태에서 다시 닫기 시도 -> 거부되어야 함 var canClose2 = mgr.CanCloseTab(autoHome); var closed2 = mgr.CloseTab(autoHome); // Assert 2 Assert.False(canClose2); Assert.False(closed2); Assert.Single(mgr.Tabs); Assert.Same(autoHome, mgr.ActiveTab); } [Fact] public void MainWindow_마지막_홈탭_닫기_시도시_거부되고_탭이_유지된다() { using var sta = StaDispatcher.Start(); WpfTestApp.Ensure(sta.Dispatcher); sta.Invoke(() => { var dataDir = Path.Combine(Path.GetTempPath(), $"vd_last_home_close_guard_{Guid.NewGuid():N}"); Environment.SetEnvironmentVariable("VD_DATA_DIR", dataDir); try { var win = new MainWindow(isPrivateWindow: false); win.TabsManager.Tabs.Clear(); var home = win.TabsManager.NewHomeTab(); win.TabsManager.Activate(home); Assert.Single(win.TabsManager.Tabs); // Act: 마지막 홈 탭 닫기 시도 var canClose = win.TabsManager.CanCloseTab(home); var closed = win.TabsManager.CloseTab(home); // Assert: 닫기가 거부되고 탭이 유지됨 Assert.False(canClose); Assert.False(closed); Assert.Single(win.TabsManager.Tabs); Assert.Same(home, win.TabsManager.ActiveTab); } finally { try { if (Directory.Exists(dataDir)) Directory.Delete(dataDir, true); } catch { } } }); } }