251 lines
10 KiB
C#
251 lines
10 KiB
C#
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);
|
|
}
|
|
}
|