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 = @"
Breaking Tech News - PACA Browser
The Future of Web Navigation
By Jane Doe
Modern web browsers require high performance and deep privacy protection.
Buy now 50% off!
Readers deserve an uncompromised experience without intrusive trackers.
";
// 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
{
"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 = "Archive TestSaved Offline
";
var resources = new List
{
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"));
}
}