feat(tools): add first-run external tool installer wizard and catalog
첫 실행 시 외부 도구 설치 마법사를 띄우고 7종(FFmpeg·LibreOffice·Pandoc·ImageMagick·H2Orestart·agy·Codex)을 기본 전체 선택 상태로 순차 설치한다. - Core: ExternalToolCatalog(정적 카탈로그, winget ID 실검증), ExternalToolInstaller(winget 무인 실행·재감지 판정, IExternalCommandRunner 시드로 테스트 가능) - Core: ExternalToolDetector에 winget FFmpeg 스캔 + Pandoc/ImageMagick 감지 추가 - App: ToolSetupWindow + ToolSetupViewModel, 첫 실행 플래그(setup.tools.done), 설정 창 '외부 도구 설치/검증' 재진입 버튼 - Tests: ExternalToolInstallerTests 5건 - 기존 미커밋 작업(광고 Ads 지원: AdBannerControl/AdLargeCardControl/AdService, AI 백엔드 agy·switchboard·codex 채팅 클라이언트, 관련 테스트) 포함
This commit is contained in:
parent
f00f6567f4
commit
6a41c4a0dc
37 changed files with 2545 additions and 69 deletions
105
src/Everything2Everything.Tests/AdDesignAuditTests.cs
Normal file
105
src/Everything2Everything.Tests/AdDesignAuditTests.cs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Everything2Everything.Tests;
|
||||
|
||||
public class AdDesignAuditTests
|
||||
{
|
||||
private static readonly string SolutionRoot = FindSolutionRoot();
|
||||
private static readonly string ViewsDir = Path.Combine(SolutionRoot, "src", "Everything2Everything.App", "Views");
|
||||
|
||||
private static string FindSolutionRoot()
|
||||
{
|
||||
var dir = Directory.GetCurrentDirectory();
|
||||
while (dir != null && !File.Exists(Path.Combine(dir, "Everything2Everything.slnx")))
|
||||
{
|
||||
dir = Directory.GetParent(dir)?.FullName;
|
||||
}
|
||||
return dir ?? throw new DirectoryNotFoundException("솔루션 루트를 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdControls_MustExistAndBeWellFormed()
|
||||
{
|
||||
var bannerPath = Path.Combine(ViewsDir, "AdBannerControl.xaml");
|
||||
var largeCardPath = Path.Combine(ViewsDir, "AdLargeCardControl.xaml");
|
||||
|
||||
Assert.True(File.Exists(bannerPath), "AdBannerControl.xaml 이 존재해야 합니다.");
|
||||
Assert.True(File.Exists(largeCardPath), "AdLargeCardControl.xaml 이 존재해야 합니다.");
|
||||
|
||||
var bannerDoc = XDocument.Parse(File.ReadAllText(bannerPath));
|
||||
var largeCardDoc = XDocument.Parse(File.ReadAllText(largeCardPath));
|
||||
|
||||
Assert.NotNull(bannerDoc.Root);
|
||||
Assert.NotNull(largeCardDoc.Root);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdControls_MustComplyWithStrictDesignAudit()
|
||||
{
|
||||
var targetFiles = new[]
|
||||
{
|
||||
Path.Combine(ViewsDir, "AdBannerControl.xaml"),
|
||||
Path.Combine(ViewsDir, "AdLargeCardControl.xaml")
|
||||
};
|
||||
|
||||
var rawEmojis = new[] { "⚙", "📁", "🗑", "✖", "🔧", "✨", "🚀", "⚡" };
|
||||
var violations = new List<string>();
|
||||
|
||||
foreach (var file in targetFiles)
|
||||
{
|
||||
if (!File.Exists(file)) continue;
|
||||
|
||||
var doc = XDocument.Parse(File.ReadAllText(file));
|
||||
var fileName = Path.GetFileName(file);
|
||||
|
||||
// 1. 유니코드 이모지 금지 검증
|
||||
var buttons = doc.Descendants().Where(e => e.Name.LocalName is "Button" or "ToggleButton");
|
||||
foreach (var btn in buttons)
|
||||
{
|
||||
var content = btn.Attribute("Content")?.Value;
|
||||
if (!string.IsNullOrEmpty(content) && rawEmojis.Any(emoji => content.Contains(emoji)))
|
||||
{
|
||||
violations.Add($"[{fileName}] 버튼에 이모지 사용 감지: {content}");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 가로 StackPanel 내 아이콘/텍스트 VerticalAlignment="Center" 검증
|
||||
var stackPanels = doc.Descendants().Where(e => e.Name.LocalName == "StackPanel" &&
|
||||
e.Attribute("Orientation")?.Value == "Horizontal");
|
||||
foreach (var sp in stackPanels)
|
||||
{
|
||||
foreach (var child in sp.Elements())
|
||||
{
|
||||
var va = child.Attribute("VerticalAlignment")?.Value;
|
||||
if (va != "Center")
|
||||
{
|
||||
violations.Add($"[{fileName}] 가로 StackPanel의 자식 <{child.Name.LocalName}>에 VerticalAlignment=\"Center\" 누락");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. TextBlock 오버플로 방지 (TextTrimming 또는 TextWrapping) 검증
|
||||
var textBlocks = doc.Descendants().Where(e => e.Name.LocalName == "TextBlock");
|
||||
foreach (var tb in textBlocks)
|
||||
{
|
||||
var text = tb.Attribute("Text")?.Value ?? "";
|
||||
// 바인딩된 동적 텍스트는 Trimming이나 Wrapping 필수
|
||||
if (text.Contains("{Binding") || text.Contains("{x:Bind"))
|
||||
{
|
||||
var trimming = tb.Attribute("TextTrimming")?.Value;
|
||||
var wrapping = tb.Attribute("TextWrapping")?.Value;
|
||||
if (string.IsNullOrEmpty(trimming) && string.IsNullOrEmpty(wrapping))
|
||||
{
|
||||
violations.Add($"[{fileName}] 바인딩 TextBlock에 TextTrimming 또는 TextWrapping 누락: {text}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(violations.Count == 0,
|
||||
"광고 컨트롤 디자인 감사 위반이 발견되었습니다:\n" + string.Join("\n", violations));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue