feat(ads/seo/release): v1.2.0 with live Google Ads, isolated ad profile, ads.txt, and SEO/AEO/GEO optimization

This commit is contained in:
Yun Chan 2026-09-14 00:39:44 +09:00
parent a88aafa1e5
commit 0e568f1f0a
975 changed files with 130593 additions and 16783 deletions

View file

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Paca.Browser;
using Paca.Browser.Tabs;
using Paca.Tests.Infrastructure;
using Xunit;
namespace Paca.Tests.Browser;
public class ChromeParityNavigationAndToolsTests
{
// =========================================================================
// 1. 소스 보기 (View Page Source in New Tab)
// =========================================================================
[Fact]
public void ViewSourceInNewTab_기존_탭을_보존하고_우측에_새_소스_탭을_생성한다()
{
var mgr = new TabManager(() => new TabItem());
var original = mgr.NewTab("https://github.com/Paca");
original.Title = "GitHub - Paca";
// 소스 보기 전용 탭 생성 헬퍼
var sourceTab = mgr.NewTabAfter(original, "view-source:" + original.Url, activate: true);
sourceTab.Title = $"소스: {original.Title}";
Assert.Equal(2, mgr.Tabs.Count);
Assert.Equal(original, mgr.Tabs[0]);
Assert.Equal(sourceTab, mgr.Tabs[1]);
Assert.Equal("view-source:https://github.com/Paca", sourceTab.Url);
Assert.Equal("소스: GitHub - Paca", sourceTab.Title);
Assert.Same(sourceTab, mgr.ActiveTab);
}
// =========================================================================
// 2. 인터넷 사용 기록 삭제 (Clear Browsing Data Filter & Range)
// =========================================================================
[Fact]
public void BrowsingDataCleaner_시간_범위별_기준시각을_정확히_산출한다()
{
var now = new DateTime(2026, 9, 9, 15, 0, 0, DateTimeKind.Utc);
Assert.Equal(now.AddHours(-1), BrowsingDataCleaner.CalculateCutoff(ClearBrowsingTimeRange.LastHour, now));
Assert.Equal(now.AddHours(-24), BrowsingDataCleaner.CalculateCutoff(ClearBrowsingTimeRange.Last24Hours, now));
Assert.Equal(now.AddDays(-7), BrowsingDataCleaner.CalculateCutoff(ClearBrowsingTimeRange.Last7Days, now));
Assert.Equal(now.AddDays(-28), BrowsingDataCleaner.CalculateCutoff(ClearBrowsingTimeRange.Last4Weeks, now));
Assert.Null(BrowsingDataCleaner.CalculateCutoff(ClearBrowsingTimeRange.AllTime, now));
}
[Fact]
public void BrowsingDataCleaner_시간_범위_기준으로_방문_기록을_정밀_필터링하여_삭제한다()
{
var now = new DateTime(2026, 9, 9, 15, 0, 0, DateTimeKind.Utc);
var history = new List<HistoryEntry>
{
new("10분 전", "https://recent.com", now.AddMinutes(-10)),
new("3시간 전", "https://earlier.com", now.AddHours(-3)),
new("2일 전", "https://old.com", now.AddDays(-2)),
new("한달 전", "https://ancient.com", now.AddDays(-30))
};
// 지난 1시간 삭제 -> 10분 전 기록만 삭제되고 나머지는 보존
var remainingAfter1Hour = BrowsingDataCleaner.FilterHistory(history, ClearBrowsingTimeRange.LastHour, now);
Assert.Equal(3, remainingAfter1Hour.Count);
Assert.DoesNotContain(remainingAfter1Hour, h => h.Url == "https://recent.com");
Assert.Contains(remainingAfter1Hour, h => h.Url == "https://earlier.com");
// 지난 24시간 삭제 -> 10분 전, 3시간 전 삭제, 2일 전/한달 전 보존
var remainingAfter24Hours = BrowsingDataCleaner.FilterHistory(history, ClearBrowsingTimeRange.Last24Hours, now);
Assert.Equal(2, remainingAfter24Hours.Count);
Assert.DoesNotContain(remainingAfter24Hours, h => h.Url == "https://earlier.com");
Assert.Contains(remainingAfter24Hours, h => h.Url == "https://old.com");
// 전체 기간 삭제 -> 모든 기록 제거
var remainingAfterAll = BrowsingDataCleaner.FilterHistory(history, ClearBrowsingTimeRange.AllTime, now);
Assert.Empty(remainingAfterAll);
}
// =========================================================================
// 3. 주소창 QR 코드 생성기 (Chrome Parity URL QR Code Generator)
// =========================================================================
[Fact]
public void QrCodeSvgGenerator_유효한_URL_입력시_규격_SVG_마크업을_반환한다()
{
string testUrl = "https://github.com/Paca/browser";
string svg = QrCodeSvgGenerator.GenerateSvg(testUrl, size: 240);
Assert.NotNull(svg);
Assert.StartsWith("<svg", svg.Trim());
Assert.EndsWith("</svg>", svg.Trim());
Assert.Contains("width=\"240\"", svg);
Assert.Contains("height=\"240\"", svg);
Assert.Contains("<rect", svg); // QR 코드 모듈(사각형) 렌더링 검증
}
[Fact]
public void QrCodeSvgGenerator_빈_문자열_또는_공백_입력시_빈_SVG_또는_안전_마크업_반환()
{
string svg = QrCodeSvgGenerator.GenerateSvg("", size: 200);
Assert.NotNull(svg);
Assert.Contains("<svg", svg);
}
// =========================================================================
// 4. 크롬 작업 관리자 (Chrome Browser Task Manager: Shift+Esc)
// =========================================================================
[Fact]
public void BrowserTaskManager_열린_탭과_백그라운드_프로세스_목록을_정상_수집한다()
{
var tabMgr = new TabManager(() => new TabItem());
var tab1 = tabMgr.NewTab("https://github.com");
tab1.Title = "GitHub";
var tab2 = tabMgr.NewTab("https://youtube.com");
tab2.Title = "YouTube";
var tasks = BrowserTaskManager.CollectTasks(tabMgr);
// 최소 브라우저 메인 프로세스 1개 + 탭 2개 = 3개 태스크
Assert.True(tasks.Count >= 3);
var mainProcess = tasks.FirstOrDefault(t => t.Type == BrowserTaskType.Browser);
Assert.NotNull(mainProcess);
Assert.Equal("브라우저 (Paca)", mainProcess!.Title);
var tabTasks = tasks.Where(t => t.Type == BrowserTaskType.Tab).ToList();
Assert.Equal(2, tabTasks.Count);
Assert.Contains(tabTasks, t => t.Title == "GitHub");
Assert.Contains(tabTasks, t => t.Title == "YouTube");
}
[Fact]
public void BrowserTaskManager_EndTask_호출시_해당_탭을_안전하게_종료한다()
{
var tabMgr = new TabManager(() => new TabItem());
var tab1 = tabMgr.NewTab("https://tab1.com");
var tab2 = tabMgr.NewTab("https://tab2.com");
var tasks = BrowserTaskManager.CollectTasks(tabMgr);
var taskToKill = tasks.First(t => t.Title == "tab2.com" || t.Url == "https://tab2.com");
bool success = BrowserTaskManager.EndTask(taskToKill, tabMgr);
Assert.True(success);
Assert.Single(tabMgr.Tabs);
Assert.Equal(tab1, tabMgr.Tabs[0]);
}
}