releases/tests/Paca.Tests/App/QuickActionNextGenUxAndAnimationGateTests.cs

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);
}
}