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:
parent
a88aafa1e5
commit
0e568f1f0a
975 changed files with 130593 additions and 16783 deletions
73
tests/Paca.Tests/Infrastructure/StaDispatcher.cs
Normal file
73
tests/Paca.Tests/Infrastructure/StaDispatcher.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
using System.Windows.Threading;
|
||||
|
||||
namespace Paca.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// WPF/WebView2 E2E 테스트용 STA 디스패처. 전용 STA 스레드에서 Dispatcher.Run() 으로
|
||||
/// 메시지 펌프를 돌리며, 테스트 코드는 InvokeAsync 로 UI 스레드에 작업을 올린다.
|
||||
/// WebView2 이벤트(비동기)는 UI 스레드 디스패처에서 발생하므로 async 흐름도 이 펌프 위에서 완결된다.
|
||||
/// </summary>
|
||||
public sealed class StaDispatcher : IDisposable
|
||||
{
|
||||
private readonly Thread _thread;
|
||||
private readonly TaskCompletionSource _ready = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private Dispatcher? _dispatcher;
|
||||
|
||||
private StaDispatcher(Thread thread)
|
||||
{
|
||||
_thread = thread;
|
||||
}
|
||||
|
||||
public Dispatcher Dispatcher =>
|
||||
_dispatcher ?? throw new InvalidOperationException("STA dispatcher not started");
|
||||
|
||||
public static StaDispatcher Start()
|
||||
{
|
||||
var sta = default(StaDispatcher)!;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
sta._dispatcher = Dispatcher.CurrentDispatcher;
|
||||
WpfTestApp.Ensure(sta._dispatcher);
|
||||
sta._ready.TrySetResult();
|
||||
Dispatcher.Run();
|
||||
});
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.IsBackground = true;
|
||||
sta = new StaDispatcher(thread);
|
||||
thread.Start();
|
||||
sta._ready.Task.Wait(5000);
|
||||
return sta;
|
||||
}
|
||||
|
||||
public Task<T> InvokeAsync<T>(Func<Task<T>> func)
|
||||
=> Dispatcher.InvokeAsync(func).Task.Unwrap();
|
||||
|
||||
public Task InvokeAsync(Func<Task> func)
|
||||
=> Dispatcher.InvokeAsync(func).Task.Unwrap();
|
||||
|
||||
public T Invoke<T>(Func<T> func) => Dispatcher.Invoke(func);
|
||||
|
||||
public void Invoke(Action action) => Dispatcher.Invoke(action);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try { Dispatcher.InvokeShutdown(); } catch { }
|
||||
try { _thread.Join(5000); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>WebView2 비동기 초기화/이벤트 대기 공통 유틸.</summary>
|
||||
public static class Wait
|
||||
{
|
||||
public static async Task<T> Until<T>(Func<Task<T>> poll, Func<T, bool> done, int timeoutMs = 15000, int intervalMs = 100)
|
||||
{
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
while (sw.ElapsedMilliseconds < timeoutMs)
|
||||
{
|
||||
var v = await poll();
|
||||
if (done(v)) return v;
|
||||
await Task.Delay(intervalMs);
|
||||
}
|
||||
throw new TimeoutException($"조건 미충족 ({timeoutMs}ms 초과)");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue