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:
Yun Chan 2026-09-14 00:39:44 +09:00
parent a88aafa1e5
commit 0e568f1f0a
975 changed files with 130593 additions and 16783 deletions

View file

@ -0,0 +1,203 @@
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;
}
}