런처 워크스페이스 복원 매칭과 ~restore 명령을 보강
변경 목적: - 런처 ~ 예약어의 창 복원 품질을 점검하고, 같은 exe의 첫 창만 반복 재사용되어 브라우저/다중 창 배치가 꼬이던 문제를 줄입니다. - 도움말에만 있던 ~restore, ~list 명령을 실제 핸들러 동작과 맞춥니다. 핵심 수정사항: - ContextManager가 열린 창 후보를 exe + 제목 유사도 기준으로 매칭하고, 이미 다른 스냅샷에 배정된 핸들은 재사용하지 않도록 변경했습니다. - WorkspaceHandler에 ~restore <이름>, ~list 지원과 최근 저장 순 프로필 목록 복원 액션 정리를 추가했습니다. - WorkspaceHandlerTests, ContextManagerTests를 추가해 명령 파싱과 창 매칭 우선순위를 회귀 검증합니다. - README.md, docs/DEVELOPMENT.md에 검토 결과와 검증 이력을 기록했습니다. 검증 결과: - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_workspace_restore_review\\ -p:IntermediateOutputPath=obj\\verify_workspace_restore_review\\ - dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter WorkspaceHandlerTests^|ContextManagerTests -p:OutputPath=bin\\verify_workspace_restore_review_tests\\ -p:IntermediateOutputPath=obj\\verify_workspace_restore_review_tests\\ - 경고 0 / 오류 0, 테스트 6건 통과
This commit is contained in:
65
src/AxCopilot.Tests/Core/ContextManagerTests.cs
Normal file
65
src/AxCopilot.Tests/Core/ContextManagerTests.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using AxCopilot.Core;
|
||||
using AxCopilot.Models;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Core;
|
||||
|
||||
public class ContextManagerTests
|
||||
{
|
||||
[Fact]
|
||||
public void NormalizeWindowTitle_StripsKnownBrowserSuffix()
|
||||
{
|
||||
var normalized = ContextManager.NormalizeWindowTitle("Inbox - Google Chrome");
|
||||
|
||||
normalized.Should().Be("inbox");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateTitleMatchScore_ExactTitle_BeatsPartialTitle()
|
||||
{
|
||||
var exact = ContextManager.CalculateTitleMatchScore("Inbox - Google Chrome", "Inbox - Google Chrome");
|
||||
var partial = ContextManager.CalculateTitleMatchScore("Inbox - Google Chrome", "Inbox - Chrome");
|
||||
|
||||
exact.Should().BeGreaterThan(partial);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectBestMatchingWindow_PrefersExactTitleAmongSameExe()
|
||||
{
|
||||
var snapshot = new WindowSnapshot
|
||||
{
|
||||
Exe = @"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
||||
Title = "Inbox - Google Chrome"
|
||||
};
|
||||
|
||||
var candidates = new[]
|
||||
{
|
||||
new ContextManager.WindowCandidate(new IntPtr(1), snapshot.Exe, "Docs - Google Chrome"),
|
||||
new ContextManager.WindowCandidate(new IntPtr(2), snapshot.Exe, "Inbox - Google Chrome")
|
||||
};
|
||||
|
||||
var selected = ContextManager.SelectBestMatchingWindow(snapshot, candidates);
|
||||
|
||||
selected.Should().Be(new IntPtr(2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectBestMatchingWindow_SkipsAlreadyUsedHandle()
|
||||
{
|
||||
var snapshot = new WindowSnapshot
|
||||
{
|
||||
Exe = @"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
||||
Title = "Inbox - Google Chrome"
|
||||
};
|
||||
|
||||
var candidates = new[]
|
||||
{
|
||||
new ContextManager.WindowCandidate(new IntPtr(1), snapshot.Exe, "Inbox - Google Chrome")
|
||||
};
|
||||
|
||||
var selected = ContextManager.SelectBestMatchingWindow(snapshot, candidates, new HashSet<IntPtr> { new(1) });
|
||||
|
||||
selected.Should().Be(IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
64
src/AxCopilot.Tests/Handlers/WorkspaceHandlerTests.cs
Normal file
64
src/AxCopilot.Tests/Handlers/WorkspaceHandlerTests.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using AxCopilot.Core;
|
||||
using AxCopilot.Handlers;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.Services;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Handlers;
|
||||
|
||||
public class WorkspaceHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetItemsAsync_RestoreSubcommand_ReturnsRestoreAction()
|
||||
{
|
||||
var settings = new SettingsService();
|
||||
settings.Settings.Profiles.Add(new WorkspaceProfile
|
||||
{
|
||||
Name = "업무",
|
||||
Windows = new List<WindowSnapshot> { new() { Exe = "chrome.exe", Title = "메일" } },
|
||||
CreatedAt = new DateTime(2026, 4, 15, 9, 0, 0)
|
||||
});
|
||||
|
||||
var handler = new WorkspaceHandler(new ContextManager(settings), settings);
|
||||
|
||||
var items = (await handler.GetItemsAsync("restore 업무", CancellationToken.None)).ToList();
|
||||
|
||||
items.Should().ContainSingle();
|
||||
items[0].Title.Should().Contain("업무");
|
||||
items[0].Data.Should().BeOfType<WorkspaceAction>();
|
||||
|
||||
var action = (WorkspaceAction)items[0].Data!;
|
||||
action.Type.Should().Be(WorkspaceActionType.Restore);
|
||||
action.Name.Should().Be("업무");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetItemsAsync_ListSubcommand_ReturnsSavedProfiles()
|
||||
{
|
||||
var settings = new SettingsService();
|
||||
settings.Settings.Profiles.Add(new WorkspaceProfile
|
||||
{
|
||||
Name = "업무",
|
||||
Windows = new List<WindowSnapshot> { new() { Exe = "chrome.exe", Title = "메일" } },
|
||||
CreatedAt = new DateTime(2026, 4, 15, 9, 0, 0)
|
||||
});
|
||||
settings.Settings.Profiles.Add(new WorkspaceProfile
|
||||
{
|
||||
Name = "개발",
|
||||
Windows = new List<WindowSnapshot> { new() { Exe = "code.exe", Title = "AX Copilot" } },
|
||||
CreatedAt = new DateTime(2026, 4, 15, 10, 0, 0)
|
||||
});
|
||||
|
||||
var handler = new WorkspaceHandler(new ContextManager(settings), settings);
|
||||
|
||||
var items = (await handler.GetItemsAsync("list", CancellationToken.None)).ToList();
|
||||
|
||||
items.Should().HaveCount(2);
|
||||
items[0].Title.Should().Be("~개발");
|
||||
items[1].Title.Should().Be("~업무");
|
||||
items.All(item => item.Data is WorkspaceAction action && action.Type == WorkspaceActionType.Restore)
|
||||
.Should()
|
||||
.BeTrue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user