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
251
tests/Paca.Tests/ModernBrowsingMilestone2Phase102Tests.cs
Normal file
251
tests/Paca.Tests/ModernBrowsingMilestone2Phase102Tests.cs
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Paca.Browser.Interaction;
|
||||
using Xunit;
|
||||
|
||||
namespace Paca.Tests;
|
||||
|
||||
public sealed class ModernBrowsingMilestone2Phase102Tests : IDisposable
|
||||
{
|
||||
private readonly string _tempDir;
|
||||
|
||||
public ModernBrowsingMilestone2Phase102Tests()
|
||||
{
|
||||
_tempDir = Path.Combine(Path.GetTempPath(), "paca_m2_test_" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(_tempDir);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(_tempDir)) Directory.Delete(_tempDir, true);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate109_MouseGestureEngine_RecognizesGesturesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var engine = new MouseGestureEngine(minStrokeDistance: 15);
|
||||
|
||||
// Act & Assert 1: Right drag -> Forward
|
||||
engine.BeginStroke(100, 100);
|
||||
engine.MoveStroke(160, 100);
|
||||
var resRight = engine.EndStroke();
|
||||
Assert.Equal("R", resRight.Pattern);
|
||||
Assert.Equal(BrowserGestureAction.Forward, resRight.Action);
|
||||
|
||||
// Act & Assert 2: Left drag -> Back
|
||||
engine.BeginStroke(200, 100);
|
||||
engine.MoveStroke(130, 100);
|
||||
var resLeft = engine.EndStroke();
|
||||
Assert.Equal("L", resLeft.Pattern);
|
||||
Assert.Equal(BrowserGestureAction.Back, resLeft.Action);
|
||||
|
||||
// Act & Assert 3: Down then Right -> CloseTab (DR)
|
||||
engine.BeginStroke(100, 100);
|
||||
engine.MoveStroke(100, 160);
|
||||
engine.MoveStroke(160, 160);
|
||||
var resDownRight = engine.EndStroke();
|
||||
Assert.Equal("DR", resDownRight.Pattern);
|
||||
Assert.Equal(BrowserGestureAction.CloseTab, resDownRight.Action);
|
||||
|
||||
// Act & Assert 4: Custom gesture registration
|
||||
engine.RegisterGesture("U", BrowserGestureAction.Refresh);
|
||||
engine.BeginStroke(100, 100);
|
||||
engine.MoveStroke(100, 50);
|
||||
var resUp = engine.EndStroke();
|
||||
Assert.Equal("U", resUp.Pattern);
|
||||
Assert.Equal(BrowserGestureAction.Refresh, resUp.Action);
|
||||
|
||||
// Act & Assert 5: Too short gesture -> None
|
||||
engine.BeginStroke(100, 100);
|
||||
engine.MoveStroke(105, 102);
|
||||
var resNone = engine.EndStroke();
|
||||
Assert.Equal(string.Empty, resNone.Pattern);
|
||||
Assert.Equal(BrowserGestureAction.None, resNone.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate110_SuperDragDropHandler_DeterminesLinkAndTextActions()
|
||||
{
|
||||
// Arrange
|
||||
var handler = new SuperDragDropHandler(searchUrlTemplate: "https://www.google.com/search?q={0}");
|
||||
|
||||
// Act & Assert 1: Dragging a URL upward -> Open in background
|
||||
var linkBg = handler.EvaluateDragDrop("https://example.com/docs", deltaX: 0, deltaY: -60);
|
||||
Assert.Equal(SuperDragActionType.OpenLinkInBackground, linkBg.ActionType);
|
||||
Assert.Equal("https://example.com/docs", linkBg.Payload);
|
||||
|
||||
// Act & Assert 2: Dragging a URL downward -> Open in foreground
|
||||
var linkFg = handler.EvaluateDragDrop("https://example.com/docs", deltaX: 0, deltaY: 60);
|
||||
Assert.Equal(SuperDragActionType.OpenLinkInForeground, linkFg.ActionType);
|
||||
Assert.Equal("https://example.com/docs", linkFg.Payload);
|
||||
|
||||
// Act & Assert 3: Dragging text -> Search text
|
||||
var textSearch = handler.EvaluateDragDrop("csharp modern browsing", deltaX: 40, deltaY: 0);
|
||||
Assert.Equal(SuperDragActionType.SearchText, textSearch.ActionType);
|
||||
Assert.Equal("https://www.google.com/search?q=csharp%20modern%20browsing", textSearch.Payload);
|
||||
|
||||
// Act & Assert 4: Empty string or whitespace -> None
|
||||
var emptyAction = handler.EvaluateDragDrop(" ", deltaX: 10, deltaY: 10);
|
||||
Assert.Equal(SuperDragActionType.None, emptyAction.ActionType);
|
||||
Assert.Empty(emptyAction.Payload);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate111_SelectionMiniToolbarCoordinator_ExtractsActionsForText()
|
||||
{
|
||||
// Arrange
|
||||
var coordinator = new SelectionMiniToolbarCoordinator();
|
||||
|
||||
// Act 1: Plain text
|
||||
var textModel = coordinator.BuildToolbar(
|
||||
selectedText: "PACA modern browser",
|
||||
selectionBounds: new ScreenRect(100, 100, 120, 24),
|
||||
viewportBounds: new ScreenRect(0, 0, 1920, 1080));
|
||||
|
||||
// Assert 1
|
||||
Assert.True(textModel.IsVisible);
|
||||
Assert.Contains(textModel.Actions, a => a.Kind == SelectionActionKind.Copy);
|
||||
Assert.Contains(textModel.Actions, a => a.Kind == SelectionActionKind.WebSearch);
|
||||
Assert.Contains(textModel.Actions, a => a.Kind == SelectionActionKind.Translate);
|
||||
Assert.Contains(textModel.Actions, a => a.Kind == SelectionActionKind.TextToSpeech);
|
||||
Assert.DoesNotContain(textModel.Actions, a => a.Kind == SelectionActionKind.Navigate);
|
||||
|
||||
// Act 2: URL text
|
||||
var urlModel = coordinator.BuildToolbar(
|
||||
selectedText: "https://github.com/dotnet",
|
||||
selectionBounds: new ScreenRect(200, 200, 180, 24),
|
||||
viewportBounds: new ScreenRect(0, 0, 1920, 1080));
|
||||
|
||||
// Assert 2
|
||||
Assert.True(urlModel.IsVisible);
|
||||
Assert.Contains(urlModel.Actions, a => a.Kind == SelectionActionKind.Navigate);
|
||||
|
||||
// Act 3: Whitespace or empty
|
||||
var emptyModel = coordinator.BuildToolbar(
|
||||
selectedText: " ",
|
||||
selectionBounds: new ScreenRect(200, 200, 10, 10),
|
||||
viewportBounds: new ScreenRect(0, 0, 1920, 1080));
|
||||
Assert.False(emptyModel.IsVisible);
|
||||
Assert.Empty(emptyModel.Actions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate112_PeekPreviewCoordinator_ValidatesPeekableUrls()
|
||||
{
|
||||
// Arrange
|
||||
var coordinator = new PeekPreviewCoordinator();
|
||||
|
||||
// Act & Assert 1: Valid HTTP/HTTPS URLs can be peeked
|
||||
var sessionValid = coordinator.TryCreatePeekSession("https://example.org/article", new ScreenRect(50, 50, 200, 20));
|
||||
Assert.True(sessionValid.CanPeek);
|
||||
Assert.Equal("https://example.org/article", sessionValid.Url);
|
||||
Assert.NotNull(sessionValid.SessionId);
|
||||
|
||||
// Act & Assert 2: Dangerous or non-web schemes cannot be peeked
|
||||
var sessionJs = coordinator.TryCreatePeekSession("javascript:alert(1)", new ScreenRect(50, 50, 200, 20));
|
||||
Assert.False(sessionJs.CanPeek);
|
||||
|
||||
var sessionData = coordinator.TryCreatePeekSession("data:text/html,<h1>Hello</h1>", new ScreenRect(50, 50, 200, 20));
|
||||
Assert.False(sessionData.CanPeek);
|
||||
|
||||
var sessionBlank = coordinator.TryCreatePeekSession("about:blank", new ScreenRect(50, 50, 200, 20));
|
||||
Assert.False(sessionBlank.CanPeek);
|
||||
|
||||
// Act & Assert 3: Promote to tab
|
||||
var promoteEvent = coordinator.PromoteToTab(sessionValid.SessionId);
|
||||
Assert.True(promoteEvent.Success);
|
||||
Assert.Equal("https://example.org/article", promoteEvent.TargetUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate113_CustomShortcutRemapper_DetectsConflictsAndBindsActions()
|
||||
{
|
||||
// Arrange
|
||||
var mappingPath = Path.Combine(_tempDir, "shortcuts.json");
|
||||
var remapper = new CustomShortcutRemapper(mappingPath);
|
||||
|
||||
// Act 1: Verify defaults
|
||||
Assert.Equal(BrowserActionCommand.NewTab, remapper.GetCommand("Ctrl+T"));
|
||||
Assert.Equal(BrowserActionCommand.CloseTab, remapper.GetCommand("Ctrl+W"));
|
||||
|
||||
// Act 2: Detect conflict
|
||||
var conflict = remapper.FindConflict("Ctrl+T");
|
||||
Assert.NotNull(conflict);
|
||||
Assert.Equal(BrowserActionCommand.NewTab, conflict.ExistingCommand);
|
||||
|
||||
// Act 3: Bind custom shortcut
|
||||
remapper.BindShortcut("Ctrl+Alt+N", BrowserActionCommand.NewTab);
|
||||
Assert.Equal(BrowserActionCommand.NewTab, remapper.GetCommand("Ctrl+Alt+N"));
|
||||
|
||||
// Act 4: Save & Reload
|
||||
remapper.Save();
|
||||
var reloaded = new CustomShortcutRemapper(mappingPath);
|
||||
Assert.Equal(BrowserActionCommand.NewTab, reloaded.GetCommand("Ctrl+Alt+N"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate114_ClipboardPasteAndGoSuggester_InspectsUrlPayload()
|
||||
{
|
||||
// Arrange
|
||||
var suggester = new ClipboardPasteAndGoSuggester(searchEngineUrlTemplate: "https://www.google.com/search?q={0}");
|
||||
|
||||
// Act & Assert 1: URL in clipboard -> Navigate suggestion
|
||||
var urlSug = suggester.EvaluateClipboard("https://paca.dev/guide");
|
||||
Assert.NotNull(urlSug);
|
||||
Assert.Equal(ClipboardSuggestionType.Navigate, urlSug.SuggestionType);
|
||||
Assert.Equal("https://paca.dev/guide", urlSug.TargetUrl);
|
||||
Assert.Contains("paca.dev", urlSug.DisplayLabel);
|
||||
|
||||
// Act & Assert 2: Text in clipboard -> Search suggestion
|
||||
var textSug = suggester.EvaluateClipboard("modern browser features");
|
||||
Assert.NotNull(textSug);
|
||||
Assert.Equal(ClipboardSuggestionType.Search, textSug.SuggestionType);
|
||||
Assert.Equal("https://www.google.com/search?q=modern%20browser%20features", textSug.TargetUrl);
|
||||
Assert.Contains("modern browser features", textSug.DisplayLabel);
|
||||
|
||||
// Act & Assert 3: Empty / whitespace -> null
|
||||
var emptySug = suggester.EvaluateClipboard(" ");
|
||||
Assert.Null(emptySug);
|
||||
|
||||
// Act & Assert 4: Overly long text gets truncated safely
|
||||
var longText = new string('A', 120);
|
||||
var longSug = suggester.EvaluateClipboard(longText);
|
||||
Assert.NotNull(longSug);
|
||||
Assert.True(longSug.DisplayLabel.Length <= 70);
|
||||
Assert.Contains("...", longSug.DisplayLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate115_TaskbarJumpListBuilder_ConstructsShellJumpList()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new TaskbarJumpListBuilder();
|
||||
|
||||
// Act
|
||||
builder.AddFrequentSite("GitHub", "https://github.com");
|
||||
builder.AddFrequentSite("YouTube", "https://youtube.com");
|
||||
var jumpList = builder.Build();
|
||||
|
||||
// Assert 1: Standard Tasks category
|
||||
var tasksCategory = jumpList.Categories.FirstOrDefault(c => c.CategoryType == JumpListCategoryType.Tasks);
|
||||
Assert.NotNull(tasksCategory);
|
||||
Assert.Contains(tasksCategory.Items, i => i.Arguments.Contains("--new-window"));
|
||||
Assert.Contains(tasksCategory.Items, i => i.Arguments.Contains("--inprivate"));
|
||||
Assert.Contains(tasksCategory.Items, i => i.Arguments.Contains("--downloads"));
|
||||
Assert.Contains(tasksCategory.Items, i => i.Arguments.Contains("--history"));
|
||||
|
||||
// Assert 2: Frequent Sites category
|
||||
var frequentCategory = jumpList.Categories.FirstOrDefault(c => c.CategoryType == JumpListCategoryType.Frequent);
|
||||
Assert.NotNull(frequentCategory);
|
||||
Assert.Equal(2, frequentCategory.Items.Count);
|
||||
Assert.Equal("GitHub", frequentCategory.Items[0].Title);
|
||||
Assert.Equal("https://github.com", frequentCategory.Items[0].TargetUrl);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue