1
0
Fork 0

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:
Yun Chan 2026-09-07 21:16:49 +09:00
parent f00f6567f4
commit 6a41c4a0dc
37 changed files with 2545 additions and 69 deletions

View file

@ -5,6 +5,7 @@ using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Everything2Everything.App.Views;
using Everything2Everything.Core;
using Everything2Everything.Core.Filters;
using Xunit;
namespace Everything2Everything.Tests;
@ -398,8 +399,9 @@ public class DesignAuditVisualTreeTests
var encoder = new System.Windows.Media.Imaging.PngBitmapEncoder();
encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(rtb));
var outDir = @"C:\Users\encep\.gemini\antigravity\brain\a5abaf02-dd4b-45f7-8890-144e9da36bcc";
var outPath = System.IO.Path.Combine(outDir, "app_rendered_preview.png");
var outDir = @"C:\Users\encep\.gemini\antigravity\brain\b58fd023-a52b-4f4b-aa23-e6df654aa1fb";
if (!System.IO.Directory.Exists(outDir)) System.IO.Directory.CreateDirectory(outDir);
var outPath = System.IO.Path.Combine(outDir, "rendered_toolbar_fixed.png");
using (var fs = new System.IO.FileStream(outPath, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite))
{
encoder.Save(fs);
@ -410,6 +412,86 @@ public class DesignAuditVisualTreeTests
});
}
[Fact]
public void MainWindow_CategoryFilterCombo_ChangesCategory_And_FiltersQueue()
{
RunOnSta(() =>
{
var engine = Everything2EverythingBootstrap.CreateDefault();
var settings = new FakeSettingsStore();
var window = new MainWindow(engine, settings);
window.ActiveQueue.Add(new QueueItem { SourcePath = @"C:\test.png", FileName = "test.png" });
window.ActiveQueue.Add(new QueueItem { SourcePath = @"C:\doc.pdf", FileName = "doc.pdf" });
window.ActiveQueue.Add(new QueueItem { SourcePath = @"C:\clip.mp4", FileName = "clip.mp4" });
window.ActiveQueue.Add(new QueueItem { SourcePath = @"C:\sheet.xlsx", FileName = "sheet.xlsx" });
var combo = (ComboBox)window.FindName("CategoryFilterCombo");
Assert.NotNull(combo);
Assert.Equal(5, combo.Items.Count);
// 0: 전체 (All)
Assert.Equal(FilterCategory.All, window.SelectedCategory);
// 1: 이미지 (Image)
combo.SelectedIndex = 1;
Assert.Equal(FilterCategory.Image, window.SelectedCategory);
var view = System.Windows.Data.CollectionViewSource.GetDefaultView(window.ActiveQueue);
var filteredItems = view.Cast<QueueItem>().ToList();
Assert.Single(filteredItems);
Assert.Equal("test.png", filteredItems[0].FileName);
// 0: 전체 복귀 (All)
combo.SelectedIndex = 0;
Assert.Equal(FilterCategory.All, window.SelectedCategory);
filteredItems = view.Cast<QueueItem>().ToList();
Assert.Equal(4, filteredItems.Count);
// 가상 레이아웃 검증 (너비 360px 환경에서도 DesiredSize가 정상 계산되는지)
var content = (UIElement)window.Content;
content.Measure(new Size(360, 600));
content.Arrange(new Rect(0, 0, 360, 600));
Assert.True(content.DesiredSize.Width > 0);
Assert.False(double.IsNaN(content.DesiredSize.Width));
});
}
[Fact]
public void AdBannerControl_MeasureAndArrange_HasValidLayoutBounds()
{
RunOnSta(() =>
{
var adService = new Everything2Everything.Core.Ads.AdService();
var vm = new Everything2Everything.App.ViewModels.AdViewModel(adService);
var banner = new AdBannerControl { DataContext = vm };
banner.Measure(new Size(800, 100));
banner.Arrange(new Rect(0, 0, 800, 100));
Assert.True(banner.DesiredSize.Width > 0);
Assert.True(banner.DesiredSize.Height > 0);
Assert.False(double.IsNaN(banner.DesiredSize.Width));
Assert.False(double.IsNaN(banner.DesiredSize.Height));
});
}
[Fact]
public void AdLargeCardControl_MeasureAndArrange_HasValidLayoutBounds()
{
RunOnSta(() =>
{
var adService = new Everything2Everything.Core.Ads.AdService();
var vm = new Everything2Everything.App.ViewModels.AdViewModel(adService);
var card = new AdLargeCardControl { DataContext = vm };
card.Measure(new Size(340, 300));
card.Arrange(new Rect(0, 0, 340, 300));
Assert.True(card.DesiredSize.Width > 0);
Assert.True(card.DesiredSize.Height > 0);
Assert.False(double.IsNaN(card.DesiredSize.Width));
Assert.False(double.IsNaN(card.DesiredSize.Height));
});
}
private static IEnumerable<T> FindLogicalChildren<T>(object parent) where T : DependencyObject
{
if (parent is ContentControl cc && cc.Content != null)