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:
parent
a88aafa1e5
commit
0e568f1f0a
975 changed files with 130593 additions and 16783 deletions
319
tests/Paca.Tests/ModernBrowsingMilestone3Phase103Tests.cs
Normal file
319
tests/Paca.Tests/ModernBrowsingMilestone3Phase103Tests.cs
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Paca.Browser.Content;
|
||||
using Paca.Browser.Interaction;
|
||||
using Xunit;
|
||||
|
||||
namespace Paca.Tests;
|
||||
|
||||
public sealed class ModernBrowsingMilestone3Phase103Tests : IDisposable
|
||||
{
|
||||
private readonly string _tempDir;
|
||||
|
||||
public ModernBrowsingMilestone3Phase103Tests()
|
||||
{
|
||||
_tempDir = Path.Combine(Path.GetTempPath(), "paca_m3_test_" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(_tempDir);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(_tempDir)) Directory.Delete(_tempDir, true);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate116_ImmersiveReaderExtractor_ExtractsArticleAndFormatsReaderDocument()
|
||||
{
|
||||
// Arrange
|
||||
var extractor = new ImmersiveReaderExtractor();
|
||||
var rawHtml = @"
|
||||
<html>
|
||||
<head><title>Breaking Tech News - PACA Browser</title></head>
|
||||
<body>
|
||||
<header><nav><a href='/home'>Home</a></nav></header>
|
||||
<div class='sidebar'>Sidebar stuff</div>
|
||||
<article>
|
||||
<h1>The Future of Web Navigation</h1>
|
||||
<div class='byline'>By Jane Doe</div>
|
||||
<p>Modern web browsers require high performance and deep privacy protection.</p>
|
||||
<div class='advertisement'>Buy now 50% off!</div>
|
||||
<p>Readers deserve an uncompromised experience without intrusive trackers.</p>
|
||||
</article>
|
||||
<footer>Copyright 2026</footer>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
// Act 1: Extract article
|
||||
var article = extractor.Extract(rawHtml, "https://example.com/tech-news");
|
||||
|
||||
// Assert 1
|
||||
Assert.NotNull(article);
|
||||
Assert.Equal("The Future of Web Navigation", article.Title);
|
||||
Assert.Equal("Jane Doe", article.Byline);
|
||||
Assert.DoesNotContain("Buy now 50% off!", article.CleanedContentHtml);
|
||||
Assert.Contains("Modern web browsers require high performance", article.CleanedContentHtml);
|
||||
Assert.True(article.WordCount > 10);
|
||||
Assert.True(article.EstimatedReadingMinutes >= 1);
|
||||
|
||||
// Act 2: Format for reader view
|
||||
var readerHtml = extractor.FormatReaderDocument(
|
||||
article,
|
||||
theme: ReaderTheme.Sepia,
|
||||
fontSizePx: 18,
|
||||
fontFamily: "Georgia",
|
||||
columnWidthPx: 680);
|
||||
|
||||
// Assert 2
|
||||
Assert.Contains("#FBF0D9", readerHtml); // Sepia background
|
||||
Assert.Contains("Georgia", readerHtml);
|
||||
Assert.Contains("680px", readerHtml);
|
||||
Assert.Contains("The Future of Web Navigation", readerHtml);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate117_ReadAloudSpeechCoordinator_ChunksSentencesAndTracksSpeechState()
|
||||
{
|
||||
// Arrange
|
||||
var coordinator = new ReadAloudSpeechCoordinator();
|
||||
var text = "Hello world! This is PACA browser. Welcome to the modern web.";
|
||||
|
||||
// Act
|
||||
coordinator.SetText(text);
|
||||
|
||||
// Assert 1: Sentence chunking
|
||||
Assert.Equal(3, coordinator.TotalSentences);
|
||||
Assert.Equal("Hello world!", coordinator.GetSentence(0));
|
||||
Assert.Equal("This is PACA browser.", coordinator.GetSentence(1));
|
||||
Assert.Equal("Welcome to the modern web.", coordinator.GetSentence(2));
|
||||
|
||||
// Assert 2: Playback state
|
||||
coordinator.Play();
|
||||
Assert.True(coordinator.IsPlaying);
|
||||
Assert.False(coordinator.IsPaused);
|
||||
Assert.Equal(0, coordinator.CurrentIndex);
|
||||
|
||||
// Step forward
|
||||
coordinator.NextSentence();
|
||||
Assert.Equal(1, coordinator.CurrentIndex);
|
||||
Assert.Equal("This is PACA browser.", coordinator.CurrentSentence);
|
||||
|
||||
// Playback rate
|
||||
coordinator.SetPlaybackRate(1.5);
|
||||
Assert.Equal(1.5, coordinator.PlaybackRate);
|
||||
|
||||
// Pause & Resume & Stop
|
||||
coordinator.Pause();
|
||||
Assert.True(coordinator.IsPaused);
|
||||
coordinator.Resume();
|
||||
Assert.False(coordinator.IsPaused);
|
||||
coordinator.Stop();
|
||||
Assert.False(coordinator.IsPlaying);
|
||||
Assert.Equal(0, coordinator.CurrentIndex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate118_PictureInPictureCoordinator_ManagesFloatingPiPSession()
|
||||
{
|
||||
// Arrange
|
||||
var coordinator = new PictureInPictureCoordinator();
|
||||
|
||||
// Act 1: Open PiP
|
||||
var session = coordinator.RequestPiP(
|
||||
tabId: "tab-media-42",
|
||||
videoSource: "https://example.com/stream.mp4",
|
||||
videoBounds: new ScreenRect(100, 100, 640, 360));
|
||||
|
||||
// Assert 1
|
||||
Assert.NotNull(session);
|
||||
Assert.True(session.IsTopMost);
|
||||
Assert.True(session.IsPlaying);
|
||||
Assert.Equal("tab-media-42", session.TabId);
|
||||
Assert.Equal("https://example.com/stream.mp4", session.VideoSource);
|
||||
|
||||
// Act 2: Controls
|
||||
coordinator.TogglePlayPause(session.SessionId);
|
||||
var active = coordinator.GetSession(session.SessionId);
|
||||
Assert.NotNull(active);
|
||||
Assert.False(active.IsPlaying);
|
||||
|
||||
coordinator.SetVolume(session.SessionId, 0.75);
|
||||
active = coordinator.GetSession(session.SessionId);
|
||||
Assert.NotNull(active);
|
||||
Assert.Equal(0.75, active.Volume);
|
||||
|
||||
coordinator.Seek(session.SessionId, 45.5);
|
||||
active = coordinator.GetSession(session.SessionId);
|
||||
Assert.NotNull(active);
|
||||
Assert.Equal(45.5, active.CurrentPositionSeconds);
|
||||
|
||||
// Act 3: Close PiP
|
||||
var closeResult = coordinator.ClosePiP(session.SessionId);
|
||||
Assert.True(closeResult.Success);
|
||||
Assert.Equal(45.5, closeResult.LastPositionSeconds);
|
||||
Assert.Null(coordinator.GetSession(session.SessionId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Gate119_WebTranslationService_DetectsLanguageAndTranslatesSegments()
|
||||
{
|
||||
// Arrange
|
||||
var service = new WebTranslationService();
|
||||
|
||||
// Act 1: Detect language
|
||||
var langEn = service.DetectLanguage("This is a technology article about distributed systems.");
|
||||
var langKo = service.DetectLanguage("이것은 분산 시스템에 관한 기술 기사입니다.");
|
||||
var langJa = service.DetectLanguage("これは分散システムに関する技術記事です。");
|
||||
|
||||
// Assert 1
|
||||
Assert.Equal("en", langEn);
|
||||
Assert.Equal("ko", langKo);
|
||||
Assert.Equal("ja", langJa);
|
||||
|
||||
// Act 2: Translate segments
|
||||
var segments = new List<string>
|
||||
{
|
||||
"Hello world",
|
||||
"Privacy first browser"
|
||||
};
|
||||
var results = await service.TranslateSegmentsAsync(segments, sourceLang: "en", targetLang: "ko");
|
||||
|
||||
// Assert 2
|
||||
Assert.Equal(2, results.Count);
|
||||
Assert.All(results, r => Assert.False(string.IsNullOrWhiteSpace(r.TranslatedText)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate120_ForceContentDarkModeEngine_GeneratesInversionCssAndManagesWhitelist()
|
||||
{
|
||||
// Arrange
|
||||
var configPath = Path.Combine(_tempDir, "darkmode_policy.json");
|
||||
var engine = new ForceContentDarkModeEngine(configPath);
|
||||
|
||||
// Act 1: Generate CSS
|
||||
var css = engine.GenerateDarkModeCss();
|
||||
Assert.Contains("invert(", css);
|
||||
Assert.Contains("hue-rotate(180deg)", css);
|
||||
Assert.Contains("img", css);
|
||||
Assert.Contains("video", css);
|
||||
|
||||
// Act 2: Site policies
|
||||
engine.SetSiteMode("example.com", ForceDarkSiteMode.Enabled);
|
||||
engine.SetSiteMode("github.com", ForceDarkSiteMode.Disabled);
|
||||
|
||||
// Assert 2
|
||||
Assert.True(engine.ShouldForceDark("https://example.com/some/article"));
|
||||
Assert.False(engine.ShouldForceDark("https://github.com/trending"));
|
||||
Assert.True(engine.ShouldForceDark("https://unknown-bright-site.org")); // Default is enabled
|
||||
|
||||
// Act 3: Save & Reload
|
||||
engine.Save();
|
||||
var reloaded = new ForceContentDarkModeEngine(configPath);
|
||||
Assert.True(reloaded.ShouldForceDark("https://example.com/some/article"));
|
||||
Assert.False(reloaded.ShouldForceDark("https://github.com/trending"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate121_MhtmlWebArchivePackager_PackagesAndParsesSingleFileArchive()
|
||||
{
|
||||
// Arrange
|
||||
var packager = new MhtmlWebArchivePackager();
|
||||
var html = "<html><head><title>Archive Test</title></head><body><h1>Saved Offline</h1></body></html>";
|
||||
var resources = new List<WebArchiveResource>
|
||||
{
|
||||
new("https://example.com/style.css", "text/css", System.Text.Encoding.UTF8.GetBytes("body { color: red; }"))
|
||||
};
|
||||
|
||||
var packageRequest = new WebArchivePackageRequest(
|
||||
Title: "Archive Test",
|
||||
Url: "https://example.com/page",
|
||||
HtmlContent: html,
|
||||
Resources: resources);
|
||||
|
||||
// Act: Package
|
||||
var mhtmlString = packager.Package(packageRequest);
|
||||
|
||||
// Assert 1: MHTML format verification
|
||||
Assert.Contains("multipart/related", mhtmlString);
|
||||
Assert.Contains("Snapshot-Content-Location: https://example.com/page", mhtmlString);
|
||||
Assert.Contains("text/html", mhtmlString);
|
||||
Assert.Contains("Saved Offline", mhtmlString);
|
||||
|
||||
// Act 2: Unpack
|
||||
var unpacked = packager.Unpack(mhtmlString);
|
||||
Assert.Equal("Archive Test", unpacked.Title);
|
||||
Assert.Equal("https://example.com/page", unpacked.LocationUrl);
|
||||
Assert.Contains("Saved Offline", unpacked.HtmlContent);
|
||||
Assert.Single(unpacked.Resources);
|
||||
Assert.Equal("text/css", unpacked.Resources[0].MimeType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate122_PdfViewerToolbarCoordinator_ControlsPageLayoutAndAnnotations()
|
||||
{
|
||||
// Arrange
|
||||
var coordinator = new PdfViewerToolbarCoordinator(totalPages: 12);
|
||||
|
||||
// Act 1: Page Navigation
|
||||
Assert.Equal(1, coordinator.CurrentPage);
|
||||
coordinator.NextPage();
|
||||
Assert.Equal(2, coordinator.CurrentPage);
|
||||
coordinator.GoToPage(10);
|
||||
Assert.Equal(10, coordinator.CurrentPage);
|
||||
coordinator.GoToPage(999); // Clamping
|
||||
Assert.Equal(12, coordinator.CurrentPage);
|
||||
|
||||
// Act 2: Rotation
|
||||
Assert.Equal(0, coordinator.RotationDegrees);
|
||||
coordinator.RotateClockwise();
|
||||
Assert.Equal(90, coordinator.RotationDegrees);
|
||||
coordinator.RotateClockwise();
|
||||
Assert.Equal(180, coordinator.RotationDegrees);
|
||||
|
||||
// Act 3: Zoom
|
||||
coordinator.SetZoom(1.5);
|
||||
Assert.Equal(1.5, coordinator.ZoomLevel);
|
||||
|
||||
// Act 4: View Mode
|
||||
coordinator.SetViewMode(PdfViewMode.TwoPages);
|
||||
Assert.Equal(PdfViewMode.TwoPages, coordinator.ViewMode);
|
||||
|
||||
// Act 5: Highlight Annotation
|
||||
coordinator.AddHighlight(pageNumber: 3, new ScreenRect(50, 100, 200, 20), "#FFFF00");
|
||||
var annotations = coordinator.GetAnnotations(3);
|
||||
Assert.Single(annotations);
|
||||
Assert.Equal("#FFFF00", annotations[0].ColorHex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate123_SiteAutoMutePolicy_ManagesMuteRulesAndPersistence()
|
||||
{
|
||||
// Arrange
|
||||
var policyPath = Path.Combine(_tempDir, "auto_mute_rules.json");
|
||||
var policy = new SiteAutoMutePolicy(policyPath);
|
||||
|
||||
// Act 1: Add rules
|
||||
policy.AddAutoMutePattern("*.noisy-news.com");
|
||||
policy.AddAutoMutePattern("annoying-media.org");
|
||||
|
||||
// Assert 1: Match
|
||||
Assert.True(policy.IsAutoMute("https://live.noisy-news.com/stream"));
|
||||
Assert.True(policy.IsAutoMute("https://annoying-media.org/video"));
|
||||
Assert.False(policy.IsAutoMute("https://youtube.com/watch"));
|
||||
|
||||
// Act 2: Remove rule
|
||||
policy.RemoveAutoMutePattern("annoying-media.org");
|
||||
Assert.False(policy.IsAutoMute("https://annoying-media.org/video"));
|
||||
|
||||
// Act 3: Persistence
|
||||
var reloaded = new SiteAutoMutePolicy(policyPath);
|
||||
Assert.True(reloaded.IsAutoMute("https://live.noisy-news.com/stream"));
|
||||
Assert.False(reloaded.IsAutoMute("https://annoying-media.org/video"));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue