177 lines
7 KiB
C#
177 lines
7 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using Paca.Core.QuickActions;
|
|
using Xunit;
|
|
|
|
namespace Paca.Tests.App;
|
|
|
|
public class QuickActionNextGenUxAndAnimationGateTests
|
|
{
|
|
private static readonly string SolutionRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", ".."));
|
|
private readonly string _xamlPath;
|
|
private readonly string _csPath;
|
|
|
|
public QuickActionNextGenUxAndAnimationGateTests()
|
|
{
|
|
_xamlPath = Path.Combine(SolutionRoot, "src", "Paca.App", "QuickActionLauncherWindow.xaml");
|
|
_csPath = Path.Combine(SolutionRoot, "src", "Paca.App", "QuickActionLauncherWindow.xaml.cs");
|
|
}
|
|
|
|
[Fact]
|
|
public void Gate01_QuickActionFuzzyMatcher_MatchesSubsequenceAndTypos()
|
|
{
|
|
// Subsequence abbreviation tests
|
|
Assert.True(QuickActionFuzzyMatcher.IsSubsequenceMatch("sct", "screenshot"));
|
|
Assert.True(QuickActionFuzzyMatcher.IsSubsequenceMatch("dwn", "download_manager"));
|
|
Assert.True(QuickActionFuzzyMatcher.IsSubsequenceMatch("clp", "clipboard"));
|
|
Assert.False(QuickActionFuzzyMatcher.IsSubsequenceMatch("xyz", "screenshot"));
|
|
|
|
// Levenshtein distance tests
|
|
int dist1 = QuickActionFuzzyMatcher.LevenshteinDistance("screenshot", "screnshot"); // 1 deletion
|
|
Assert.Equal(1, dist1);
|
|
|
|
int dist2 = QuickActionFuzzyMatcher.LevenshteinDistance("download", "downlaod"); // 2 transposition
|
|
Assert.True(dist2 <= 2);
|
|
|
|
// Search score evaluation
|
|
int scoreSub = QuickActionFuzzyMatcher.CalculateFuzzyScore("sct", "screenshot");
|
|
Assert.True(scoreSub > 0, "Subsequence should yield positive score");
|
|
|
|
int scoreTypo = QuickActionFuzzyMatcher.CalculateFuzzyScore("screnshot", "screenshot");
|
|
Assert.True(scoreTypo > 0, "Typo with distance 1 should yield positive score");
|
|
}
|
|
|
|
[Fact]
|
|
public void Gate02_QuickActionUsageTracker_RecordsExecutionAndBoostsScore()
|
|
{
|
|
string tempFile = Path.Combine(Path.GetTempPath(), $"paca_usage_test_{Guid.NewGuid():N}.json");
|
|
try
|
|
{
|
|
var tracker = new QuickActionUsageTracker(tempFile);
|
|
Assert.Equal(0, tracker.GetScoreBoost("browser.new_tab"));
|
|
|
|
// Record execution
|
|
tracker.RecordExecution("browser.new_tab");
|
|
tracker.RecordExecution("browser.new_tab");
|
|
tracker.RecordExecution("sys.screenshot_full");
|
|
|
|
// Verify count & boosts
|
|
Assert.True(tracker.GetScoreBoost("browser.new_tab") >= 100);
|
|
Assert.True(tracker.GetScoreBoost("sys.screenshot_full") >= 50);
|
|
Assert.Equal(0, tracker.GetScoreBoost("media.unrecorded_action"));
|
|
|
|
// Verify persistence
|
|
Assert.True(File.Exists(tempFile));
|
|
var tracker2 = new QuickActionUsageTracker(tempFile);
|
|
Assert.True(tracker2.GetScoreBoost("browser.new_tab") >= 100);
|
|
|
|
// Recent actions list
|
|
var recent = tracker2.GetRecentActionIds(5);
|
|
Assert.Contains("browser.new_tab", recent);
|
|
Assert.Contains("sys.screenshot_full", recent);
|
|
}
|
|
finally
|
|
{
|
|
if (File.Exists(tempFile)) File.Delete(tempFile);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Gate03_QuickActionParameterRouter_RoutesSubCommandsCorrectly()
|
|
{
|
|
var router = new QuickActionParameterRouter();
|
|
|
|
// 1. Open URL
|
|
Assert.True(router.TryParseCommand("open https://example.com", out var openAction));
|
|
Assert.NotNull(openAction);
|
|
Assert.Contains("https://example.com", openAction.Title);
|
|
|
|
// 2. Base64 encode
|
|
Assert.True(router.TryParseCommand("b64 hello", out var b64Action));
|
|
Assert.NotNull(b64Action);
|
|
string? copied = null;
|
|
var ctx = new QuickActionContext(ClipboardSetter: text => copied = text);
|
|
Assert.NotNull(b64Action.Handler);
|
|
var result = await b64Action.Handler.Invoke(ctx);
|
|
Assert.True(result.Success);
|
|
Assert.Equal("aGVsbG8=", copied);
|
|
|
|
// 3. Base64 decode
|
|
Assert.True(router.TryParseCommand("b64d aGVsbG8=", out var b64dAction));
|
|
Assert.NotNull(b64dAction);
|
|
Assert.NotNull(b64dAction.Handler);
|
|
var resultD = await b64dAction.Handler.Invoke(ctx);
|
|
Assert.True(resultD.Success);
|
|
Assert.Equal("hello", copied);
|
|
|
|
// 4. SHA-256 hash
|
|
Assert.True(router.TryParseCommand("hash test", out var hashAction));
|
|
Assert.NotNull(hashAction);
|
|
Assert.True(hashAction.Title.Contains("SHA-256") || hashAction.Description?.Contains("SHA-256") == true);
|
|
|
|
// 5. Normal non-command query should return false
|
|
Assert.False(router.TryParseCommand("새 탭 열기", out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void Gate04_QuickActionLauncher_ContainsScaleAndFadeAnimationResources()
|
|
{
|
|
Assert.True(File.Exists(_xamlPath), $"XAML not found at {_xamlPath}");
|
|
string xaml = File.ReadAllText(_xamlPath);
|
|
|
|
// 1. Check window width is 860 for split view
|
|
Assert.Contains("Width=\"860\"", xaml);
|
|
|
|
// 2. Check ScaleTransform and TranslateTransform are defined in RootCard
|
|
Assert.Contains("<ScaleTransform", xaml);
|
|
Assert.Contains("<TranslateTransform", xaml);
|
|
|
|
// 3. Check CubicEase or QuadraticEase is used for smooth animations
|
|
Assert.Contains("CubicEase", xaml);
|
|
|
|
// 4. Check animation duration <= 250ms for vestibulo-ocular reflex protection (WCAG SC 2.3.3)
|
|
var durationMatches = Regex.Matches(xaml, @"Duration=""0:0:0\.([0-9]+)""");
|
|
Assert.NotEmpty(durationMatches);
|
|
foreach (Match match in durationMatches)
|
|
{
|
|
int ms = int.Parse(match.Groups[1].Value.PadRight(3, '0')[..3]);
|
|
Assert.True(ms <= 250, $"Animation duration {ms}ms exceeds safe threshold (250ms)");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Gate05_QuickActionLauncher_ContainsActionDetailPreviewPane()
|
|
{
|
|
Assert.True(File.Exists(_xamlPath), $"XAML not found at {_xamlPath}");
|
|
string xaml = File.ReadAllText(_xamlPath);
|
|
|
|
// Check for ActionDetailPane elements
|
|
Assert.Contains("ActionDetailPane", xaml);
|
|
Assert.Contains("DetailActionTitle", xaml);
|
|
Assert.Contains("DetailActionId", xaml);
|
|
Assert.Contains("DetailActionDesc", xaml);
|
|
Assert.Contains("DetailCategoryBadge", xaml);
|
|
}
|
|
|
|
[Fact]
|
|
public void Gate06_QuickActionLauncher_ContainsDragMoveAndMouseHoverGuards()
|
|
{
|
|
Assert.True(File.Exists(_csPath), $"Code-behind not found at {_csPath}");
|
|
string cs = File.ReadAllText(_csPath);
|
|
|
|
// 1. DragMove support for dragging launcher window
|
|
Assert.Contains("DragMove()", cs);
|
|
|
|
// 2. Mouse move guard to avoid keyboard navigation conflict
|
|
Assert.Contains("_lastMousePosition", cs);
|
|
|
|
// 3. Micro-feedback for action completion / clipboard copy
|
|
Assert.Contains("ShowMicroFeedback", cs);
|
|
|
|
// 4. Animated show / hide
|
|
Assert.Contains("ShowLauncherAnimated", cs);
|
|
Assert.Contains("HideLauncherAnimated", cs);
|
|
}
|
|
}
|