203 lines
7.8 KiB
C#
203 lines
7.8 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Paca.Core.QuickActions;
|
|
using Xunit;
|
|
|
|
namespace Paca.Tests.App;
|
|
|
|
public class QuickActionPillModeAndTabSearchTests
|
|
{
|
|
private static readonly string SolutionRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", ".."));
|
|
private readonly string _xamlPath;
|
|
private readonly string _launcherCsPath;
|
|
private readonly string _mwCsPath;
|
|
|
|
public QuickActionPillModeAndTabSearchTests()
|
|
{
|
|
_xamlPath = Path.Combine(SolutionRoot, "src", "Paca.App", "QuickActionLauncherWindow.xaml");
|
|
_launcherCsPath = Path.Combine(SolutionRoot, "src", "Paca.App", "QuickActionLauncherWindow.xaml.cs");
|
|
_mwCsPath = Path.Combine(SolutionRoot, "src", "Paca.App", "MainWindow.xaml.cs");
|
|
}
|
|
|
|
[Fact]
|
|
public void Gate211_KakaoMapSearchAction_GeneratesKakaoMapUrl_AndNavigatesTabOpener()
|
|
{
|
|
var catalog = new QuickActionCatalog();
|
|
var kakaoMapAction = catalog.FindById("search.kakao_map");
|
|
|
|
Assert.NotNull(kakaoMapAction);
|
|
Assert.Equal("카카오맵 장소 검색", kakaoMapAction.Title);
|
|
Assert.Equal(QuickActionCategory.Search, kakaoMapAction.Category);
|
|
Assert.NotNull(kakaoMapAction.SearchUrlTemplate);
|
|
Assert.Contains("map.kakao.com", kakaoMapAction.SearchUrlTemplate);
|
|
|
|
// 1. With search parameter
|
|
string? openedUrlWithParam = null;
|
|
var ctxWithParam = new QuickActionContext(
|
|
Parameter: "강남역 맛집",
|
|
TabOpener: url => openedUrlWithParam = url
|
|
);
|
|
|
|
kakaoMapAction.Handler?.Invoke(ctxWithParam);
|
|
Assert.NotNull(openedUrlWithParam);
|
|
Assert.Contains("map.kakao.com", openedUrlWithParam);
|
|
Assert.Contains(Uri.EscapeDataString("강남역 맛집"), openedUrlWithParam);
|
|
|
|
// 2. Without search parameter (direct site landing)
|
|
string? openedUrlEmpty = null;
|
|
var ctxEmpty = new QuickActionContext(
|
|
Parameter: "",
|
|
TabOpener: url => openedUrlEmpty = url
|
|
);
|
|
|
|
kakaoMapAction.Handler?.Invoke(ctxEmpty);
|
|
Assert.NotNull(openedUrlEmpty);
|
|
Assert.Equal("https://map.kakao.com/", openedUrlEmpty);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("search.google", "google.com")]
|
|
[InlineData("search.youtube", "youtube.com")]
|
|
[InlineData("search.naver", "search.naver.com")]
|
|
[InlineData("search.daum", "search.daum.net")]
|
|
[InlineData("search.namuwiki", "namu.wiki")]
|
|
[InlineData("search.wikipedia", "wikipedia.org")]
|
|
[InlineData("search.github", "github.com")]
|
|
[InlineData("search.naver_map", "map.naver.com")]
|
|
[InlineData("search.kakao_map", "map.kakao.com")]
|
|
[InlineData("search.coupang", "coupang.com")]
|
|
[InlineData("search.duckduckgo", "duckduckgo.com")]
|
|
public void Gate212_CoreSearchEngines_AllConfiguredWithSearchUrlTemplates(string actionId, string expectedHost)
|
|
{
|
|
var catalog = new QuickActionCatalog();
|
|
var action = catalog.FindById(actionId);
|
|
|
|
Assert.NotNull(action);
|
|
Assert.False(string.IsNullOrWhiteSpace(action.SearchUrlTemplate), $"SearchUrlTemplate must not be empty for {actionId}");
|
|
Assert.Contains(expectedHost, action.SearchUrlTemplate);
|
|
Assert.Contains("{query}", action.SearchUrlTemplate);
|
|
|
|
string? openedUrl = null;
|
|
var ctx = new QuickActionContext(
|
|
Parameter: "test query",
|
|
TabOpener: url => openedUrl = url
|
|
);
|
|
|
|
action.Handler?.Invoke(ctx);
|
|
Assert.NotNull(openedUrl);
|
|
Assert.Contains(expectedHost, openedUrl);
|
|
Assert.Contains(Uri.EscapeDataString("test query"), openedUrl);
|
|
}
|
|
|
|
[Fact]
|
|
public void Gate213_QuickActionLauncherXaml_ContainsActionPillElement_AndSubComponents()
|
|
{
|
|
Assert.True(File.Exists(_xamlPath), $"XAML must exist at {_xamlPath}");
|
|
var xaml = File.ReadAllText(_xamlPath);
|
|
|
|
// ActionPill container and components
|
|
Assert.Contains("x:Name=\"ActionPill\"", xaml);
|
|
Assert.Contains("x:Name=\"ActionPillIcon\"", xaml);
|
|
Assert.Contains("x:Name=\"ActionPillTitle\"", xaml);
|
|
Assert.Contains("x:Name=\"ActionPillColon\"", xaml);
|
|
|
|
// Pill visual styling tokens
|
|
Assert.Contains("CornerRadius=\"6\"", xaml);
|
|
Assert.Contains("AccentFadeBrush", xaml);
|
|
Assert.Contains("AccentBrush", xaml);
|
|
Assert.Contains("Visibility=\"Collapsed\"", xaml);
|
|
}
|
|
|
|
[Fact]
|
|
public void Gate214_QuickActionLauncherCodeBehind_SupportsPillModeLifecycle()
|
|
{
|
|
Assert.True(File.Exists(_launcherCsPath), $"Launcher CS must exist at {_launcherCsPath}");
|
|
var code = File.ReadAllText(_launcherCsPath);
|
|
|
|
// Pill mode lifecycle API
|
|
Assert.Contains("EnterPillMode", code);
|
|
Assert.Contains("ExitPillMode", code);
|
|
Assert.Contains("ActivePillAction", code);
|
|
Assert.Contains("IsInPillMode", code);
|
|
|
|
// Key bindings: Tab triggers pill mode, Backspace when empty exits pill mode
|
|
Assert.Contains("Key.Tab", code);
|
|
Assert.Contains("Key.Back", code);
|
|
Assert.Contains("TabOpener", code);
|
|
}
|
|
|
|
[Fact]
|
|
public void Gate215_MainWindow_Provides_OpenUrlInNewTab()
|
|
{
|
|
Assert.True(File.Exists(_mwCsPath), $"MainWindow CS must exist at {_mwCsPath}");
|
|
var code = File.ReadAllText(_mwCsPath);
|
|
|
|
Assert.Contains("OpenUrlInNewTab", code);
|
|
Assert.Contains("Cv2Navigate", code);
|
|
}
|
|
|
|
[Fact]
|
|
public void Gate216_LiveLauncher_EndToEnd_PillMode_Tab_Typing_Execution_And_Backspace()
|
|
{
|
|
Exception? threadEx = null;
|
|
var thread = new System.Threading.Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
var catalog = new QuickActionCatalog();
|
|
var launcher = new Paca.App.QuickActionLauncherWindow(catalog);
|
|
|
|
// 1. Initial state: Pill is collapsed
|
|
Assert.False(launcher.IsInPillMode);
|
|
Assert.Null(launcher.ActivePillAction);
|
|
|
|
// 2. Select kakao map action
|
|
var kakaoAction = catalog.FindById("search.kakao_map");
|
|
Assert.NotNull(kakaoAction);
|
|
|
|
// 3. Enter Pill Mode
|
|
launcher.EnterPillMode(kakaoAction);
|
|
Assert.True(launcher.IsInPillMode);
|
|
Assert.Equal(kakaoAction, launcher.ActivePillAction);
|
|
|
|
// 4. Input query text "강남역 맛집"
|
|
launcher.QuickActionSearchBox.Text = "강남역 맛집";
|
|
|
|
// 5. Execute action and verify ActionExecuted event
|
|
string? executedActionId = null;
|
|
string? executedQuery = null;
|
|
launcher.ActionExecuted += (act, q) =>
|
|
{
|
|
executedActionId = act.Id;
|
|
executedQuery = q;
|
|
};
|
|
|
|
launcher.ExecuteSelectedAction();
|
|
Assert.Equal("search.kakao_map", executedActionId);
|
|
Assert.Equal("강남역 맛집", executedQuery);
|
|
Assert.False(launcher.IsInPillMode); // Resets after execution
|
|
|
|
// 6. Test Backspace exiting Pill Mode
|
|
launcher.EnterPillMode(kakaoAction);
|
|
Assert.True(launcher.IsInPillMode);
|
|
launcher.QuickActionSearchBox.Text = "";
|
|
launcher.ExitPillMode(restoreActionTitle: true);
|
|
Assert.False(launcher.IsInPillMode);
|
|
Assert.Equal(kakaoAction.Title, launcher.QuickActionSearchBox.Text);
|
|
|
|
launcher.Close();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
threadEx = ex;
|
|
}
|
|
});
|
|
thread.SetApartmentState(System.Threading.ApartmentState.STA);
|
|
thread.Start();
|
|
bool finished = thread.Join(8000);
|
|
Assert.True(finished, "STA thread must finish within timeout");
|
|
|
|
if (threadEx != null) throw threadEx;
|
|
}
|
|
}
|