diff --git a/Assets/Tests/Editor/ThirdPartyToolMigrationFileServiceTests.cs b/Assets/Tests/Editor/ThirdPartyToolMigrationFileServiceTests.cs index 8fddb712af..ce74fc2293 100644 --- a/Assets/Tests/Editor/ThirdPartyToolMigrationFileServiceTests.cs +++ b/Assets/Tests/Editor/ThirdPartyToolMigrationFileServiceTests.cs @@ -459,9 +459,7 @@ public sealed class HelloResponse : BaseToolResponse }"); File.WriteAllText(asmdefPath, @"{ ""name"": ""VendorTools.Editor"", - ""references"": [ - ""GUID:214998e563c124e8a88199b2dd1f522d"" - ] + ""references"": [] }"); ThirdPartyToolMigrationFileService service = new(); @@ -612,12 +610,12 @@ public static void Register(object tool) ThirdPartyToolMigrationFileService service = new(); ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); - Assert.That(result.FileCount, Is.EqualTo(3)); + Assert.That(result.FileCount, Is.EqualTo(2)); Assert.That(File.ReadAllText(toolPath), Does.Contain("UnityCliLoopTool")); Assert.That(File.ReadAllText(registrationPath), Does.Contain( - "using io.github.hatayama.UnityCliLoop.ToolContracts;")); + "using io.github.hatayama.UnityCliLoop.Application;")); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(File.ReadAllText(asmdefPath), Does.Not.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); + Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); Assert.That(File.ReadAllText(asmdefPath), Does.Not.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); } finally @@ -657,11 +655,10 @@ public static void Register(object tool) ThirdPartyToolMigrationFileService service = new(); ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); - Assert.That(result.FileCount, Is.EqualTo(2)); - Assert.That(File.ReadAllText(registrationPath), Does.Contain( - "using io.github.hatayama.UnityCliLoop.ToolContracts;")); + Assert.That(result.FileCount, Is.EqualTo(1)); + Assert.That(File.ReadAllText(registrationPath), Is.EqualTo(registrationSource)); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(File.ReadAllText(asmdefPath), Does.Not.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); + Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); Assert.That(File.ReadAllText(asmdefPath), Does.Not.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); } finally @@ -732,10 +729,10 @@ public static void Register(IUnityCliLoopTool tool) ThirdPartyToolMigrationFileService service = new(); ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); - Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(result.FileCount, Is.EqualTo(1)); Assert.That(File.ReadAllText(registrationPath), Does.Contain( "using io.github.hatayama.UnityCliLoop.ToolContracts;")); - Assert.That(File.ReadAllText(registrationPath), Does.Not.Contain( + Assert.That(File.ReadAllText(registrationPath), Does.Contain( "using io.github.hatayama.UnityCliLoop.Application;")); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); @@ -951,10 +948,10 @@ public static void Register(IUnityCliLoopTool tool) Assert.That(result.FileCount, Is.EqualTo(1)); Assert.That(File.ReadAllText(registrationPath), Does.Contain( "using io.github.hatayama.UnityCliLoop.ToolContracts;")); - Assert.That(File.ReadAllText(registrationPath), Does.Not.Contain( + Assert.That(File.ReadAllText(registrationPath), Does.Contain( "using io.github.hatayama.UnityCliLoop.Application;")); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(File.ReadAllText(asmdefPath), Does.Not.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); + Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); Assert.That(File.ReadAllText(asmdefPath), Does.Not.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); } finally @@ -1132,9 +1129,11 @@ public static ToolInfo[] GetTools() Assert.That(result.FileCount, Is.EqualTo(2)); Assert.That(File.ReadAllText(metadataPath), Does.Contain( - "using io.github.hatayama.UnityCliLoop.ToolContracts;")); + "using io.github.hatayama.UnityCliLoop.Domain;")); + Assert.That(File.ReadAllText(metadataPath), Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.ToolInfo[] GetTools()")); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(File.ReadAllText(asmdefPath), Does.Not.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); + Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); } finally @@ -1177,10 +1176,57 @@ public static ToolInfo[] GetTools() Assert.That(result.FileCount, Is.EqualTo(2)); Assert.That(File.ReadAllText(globalUsingPath), Does.Contain( - "global using io.github.hatayama.UnityCliLoop.ToolContracts;")); - Assert.That(File.ReadAllText(metadataPath), Is.EqualTo(metadataSource)); + "global using io.github.hatayama.UnityCliLoop.Domain;")); + Assert.That(File.ReadAllText(metadataPath), Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.ToolInfo[] GetTools()")); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(File.ReadAllText(asmdefPath), Does.Not.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); + Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); + Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); + } + finally + { + Directory.Delete(projectRoot, recursive: true); + } + } + + [Test] + public void ApplyMigration_WhenAssemblyUsesCurrentDomainGlobalAliasAndSplitMetadata_RewritesDomainAliasContracts() + { + // Verifies that split V3 Domain aliases do not leave moved contract types in the Domain namespace. + string projectRoot = CreateProjectRoot(); + try + { + string toolDirectory = Path.Combine(projectRoot, "Assets", "VendorTools"); + Directory.CreateDirectory(toolDirectory); + string globalUsingPath = Path.Combine(toolDirectory, "GlobalUsings.cs"); + string metadataPath = Path.Combine(toolDirectory, "ToolMetadataProvider.cs"); + string asmdefPath = Path.Combine(toolDirectory, "VendorTools.Editor.asmdef"); + File.WriteAllText(globalUsingPath, "global using Dom = io.github.hatayama.UnityCliLoop.Domain;"); + File.WriteAllText(metadataPath, @"public static class ToolMetadataProvider +{ + public static Dom.ToolInfo[] GetTools() + { + return new Dom.ToolInfo[0]; + } +}"); + File.WriteAllText(asmdefPath, @"{ + ""name"": ""VendorTools.Editor"", + ""references"": [ + ""GUID:214998e563c124e8a88199b2dd1f522d"" + ] +}"); + + ThirdPartyToolMigrationFileService service = new(); + ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); + + Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(File.ReadAllText(globalUsingPath), Does.Contain( + "global using Dom = io.github.hatayama.UnityCliLoop.Domain;")); + Assert.That(File.ReadAllText(metadataPath), Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.ToolInfo[] GetTools()")); + Assert.That(File.ReadAllText(metadataPath), Does.Not.Contain("Dom.ToolInfo")); + Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); } finally @@ -1405,7 +1451,7 @@ public sealed class ScreenshotHelper Assert.That(result.FileCount, Is.EqualTo(3)); Assert.That(File.ReadAllText(helperPath), Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.FindWindowsByName")); + "EditorWindowCaptureUtility.FindWindowsByName")); Assert.That(File.ReadAllText(helperPath), Does.Contain( "io.github.hatayama.UnityCliLoop.ToolContracts.WindowMatchMode.exact")); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); @@ -1467,7 +1513,7 @@ public sealed class CaptureResponse : UnityCliLoopToolResponse Assert.That(result.FileCount, Is.EqualTo(1)); Assert.That(File.ReadAllText(toolPath), Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureWindowAsync")); + "EditorWindowCaptureUtility.CaptureWindowAsync")); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); Assert.That(File.ReadAllText(asmdefPath), Does.Not.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); } @@ -1520,7 +1566,7 @@ public sealed class MainThreadResponse : UnityCliLoopToolResponse Assert.That(result.FileCount, Is.EqualTo(1)); Assert.That(File.ReadAllText(toolPath), Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.MainThreadSwitcher.SwitchToMainThread(ct)")); + "MainThreadSwitcher.SwitchToMainThread(ct)")); Assert.That(File.ReadAllText(toolPath), Does.Not.Contain("PlayerLoopTiming")); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); Assert.That(File.ReadAllText(asmdefPath), Does.Not.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); @@ -1652,13 +1698,13 @@ public async Task RunAsync(CancellationToken ct) ThirdPartyToolMigrationFileService service = new(); ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); - Assert.That(result.FileCount, Is.EqualTo(1)); + Assert.That(result.FileCount, Is.EqualTo(2)); Assert.That(File.ReadAllText(globalUsingPath), Does.Contain( - "global using io.github.hatayama.UnityCliLoop.ToolContracts;")); + "global using io.github.hatayama.UnityCliLoop.Application;")); Assert.That(File.ReadAllText(mainThreadPath), Does.Contain( - "await MainThreadSwitcher.SwitchToMainThread(ct);")); + "await io.github.hatayama.UnityCliLoop.ToolContracts.MainThreadSwitcher.SwitchToMainThread(ct);")); Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(File.ReadAllText(asmdefPath), Does.Not.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); + Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); } finally { @@ -1669,7 +1715,7 @@ public async Task RunAsync(CancellationToken ct) [Test] public void ApplyMigration_WhenAssemblyUsesCurrentApplicationGlobalUsing_RewritesBareMainThreadSwitcherTiming() { - // Verifies that split files relying on a current Application global using drop removed timing arguments. + // Verifies that split files relying on a current Application global using rebind moved switcher calls. string projectRoot = CreateProjectRoot(); try { @@ -1701,9 +1747,11 @@ public async Task RunAsync(CancellationToken ct) string migratedSource = File.ReadAllText(mainThreadPath); Assert.That(result.FilePaths.Contains(mainThreadPath), Is.True); + Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); Assert.That(migratedSource, Does.Contain( - "await MainThreadSwitcher.SwitchToMainThread(ct);")); + "await io.github.hatayama.UnityCliLoop.ToolContracts.MainThreadSwitcher.SwitchToMainThread(ct);")); Assert.That(migratedSource, Does.Not.Contain("PlayerLoopTiming")); + Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); } finally { @@ -1714,7 +1762,7 @@ public async Task RunAsync(CancellationToken ct) [Test] public void ApplyMigration_WhenAssemblyUsesCurrentApplicationGlobalAlias_RewritesQualifiedMainThreadSwitcherTiming() { - // Verifies that split files relying on a current Application global alias drop removed timing arguments. + // Verifies that split files relying on a current Application global alias rebind moved switcher calls. string projectRoot = CreateProjectRoot(); try { @@ -1745,13 +1793,15 @@ public async Task RunAsync(CancellationToken ct) ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); string migratedSource = File.ReadAllText(mainThreadPath); - Assert.That(result.FilePaths.Contains(globalUsingPath), Is.True); Assert.That(result.FilePaths.Contains(mainThreadPath), Is.True); + Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); Assert.That(File.ReadAllText(globalUsingPath), Does.Contain( - "global using App = io.github.hatayama.UnityCliLoop.ToolContracts;")); + "global using App = io.github.hatayama.UnityCliLoop.Application;")); Assert.That(migratedSource, Does.Contain( - "await App.MainThreadSwitcher.SwitchToMainThread(ct);")); + "await io.github.hatayama.UnityCliLoop.ToolContracts.MainThreadSwitcher.SwitchToMainThread(ct);")); + Assert.That(migratedSource, Does.Not.Contain("App.MainThreadSwitcher")); Assert.That(migratedSource, Does.Not.Contain("PlayerLoopTiming")); + Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); } finally { @@ -2095,9 +2145,9 @@ public sealed class PartialResponse : UnityCliLoopToolResponse "EditorFrameWaiter.WaitFramesOrTimeoutAsync(2, UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS, ct)")); Assert.That(migratedSource, Does.Contain("TimerDelay.Wait(10, ct: ct)")); Assert.That(migratedSource, Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.MainThreadSwitcher.SwitchToMainThread(ct)")); + "MainThreadSwitcher.SwitchToMainThread(ct)")); Assert.That(migratedSource, Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureWindowAsync")); + "EditorWindowCaptureUtility.CaptureWindowAsync")); Assert.That(migratedSource, Does.Not.Contain("EditorDelay")); Assert.That(migratedSource, Does.Not.Contain("PlayerLoopTiming")); Assert.That(migratedSource, Does.Not.Contain("cancellationToken:")); @@ -2146,10 +2196,12 @@ public async Task CaptureAsync(EditorWindow window, CancellationToken string migratedAsmdef = File.ReadAllText(asmdefPath); Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(migratedSource, Does.Contain( + "EditorWindowCaptureUtility.CaptureWindowAsync(window, 1.0f, io.github.hatayama.UnityCliLoop.ToolContracts.UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS, ct)")); Assert.That(migratedSource, Does.Contain( "io.github.hatayama.UnityCliLoop.ToolContracts.UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS")); Assert.That(migratedAsmdef, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(migratedAsmdef, Does.Not.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); + Assert.That(migratedAsmdef, Does.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); } finally { @@ -2192,16 +2244,16 @@ public async Task CaptureAsync(EditorWindow window, CancellationToken string migratedSource = File.ReadAllText(toolPath); string migratedAsmdef = File.ReadAllText(asmdefPath); - Assert.That(result.FileCount, Is.EqualTo(3)); + Assert.That(result.FileCount, Is.EqualTo(2)); Assert.That(File.ReadAllText(globalUsingPath), Does.Contain( - "global using io.github.hatayama.UnityCliLoop.ToolContracts;")); + "global using io.github.hatayama.UnityCliLoop.FirstPartyTools;")); Assert.That(migratedSource, Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureWindowAsync")); + "EditorWindowCaptureUtility.CaptureWindowAsync")); Assert.That(migratedSource, Does.Contain( "io.github.hatayama.UnityCliLoop.ToolContracts.UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS")); Assert.That(migratedSource, Does.Not.Contain("return await EditorWindowCaptureUtility.CaptureWindowAsync")); Assert.That(migratedAsmdef, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(migratedAsmdef, Does.Not.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); + Assert.That(migratedAsmdef, Does.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); } finally { @@ -2246,13 +2298,13 @@ public async Task CaptureAsync(EditorWindow window, CancellationToken ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); string migratedSource = File.ReadAllText(toolPath); - Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(result.FileCount, Is.EqualTo(1)); Assert.That(result.FilePaths.Contains(toolPath), Is.True); - Assert.That(result.FilePaths.Contains(globalUsingPath), Is.True); + Assert.That(result.FilePaths.Contains(globalUsingPath), Is.False); Assert.That(File.ReadAllText(globalUsingPath), Does.Contain( - "global using Fpt = io.github.hatayama.UnityCliLoop.ToolContracts;")); + "global using Fpt = io.github.hatayama.UnityCliLoop.FirstPartyTools;")); Assert.That(migratedSource, Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureWindowAsync")); + "Fpt.EditorWindowCaptureUtility.CaptureWindowAsync")); Assert.That(migratedSource, Does.Contain( "io.github.hatayama.UnityCliLoop.ToolContracts.UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS")); Assert.That(migratedSource, Does.Not.Contain( @@ -2265,9 +2317,9 @@ public async Task CaptureAsync(EditorWindow window, CancellationToken } [Test] - public void ApplyMigration_WhenCurrentFirstPartyToolsGlobalUsingOnlyNeedsReference_RewritesFirstPartyNamespace() + public void ApplyMigration_WhenCurrentFirstPartyToolsGlobalUsingOnlyNeedsReference_AddsFirstPartyReference() { - // Verifies that a current FirstPartyTools global using by itself moves to ToolContracts. + // Verifies that a current FirstPartyTools global using by itself only needs the FirstParty asmdef reference. string projectRoot = CreateProjectRoot(); try { @@ -2285,13 +2337,13 @@ public void ApplyMigration_WhenCurrentFirstPartyToolsGlobalUsingOnlyNeedsReferen ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); string migratedAsmdef = File.ReadAllText(asmdefPath); - Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(result.FileCount, Is.EqualTo(1)); Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); - Assert.That(result.FilePaths.Contains(globalUsingPath), Is.True); - Assert.That(migratedAsmdef, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(migratedAsmdef, Does.Not.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); + Assert.That(result.FilePaths.Contains(globalUsingPath), Is.False); + Assert.That(migratedAsmdef, Does.Not.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + Assert.That(migratedAsmdef, Does.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); Assert.That(File.ReadAllText(globalUsingPath), Does.Contain( - "global using io.github.hatayama.UnityCliLoop.ToolContracts;")); + "global using io.github.hatayama.UnityCliLoop.FirstPartyTools;")); } finally { @@ -2300,9 +2352,9 @@ public void ApplyMigration_WhenCurrentFirstPartyToolsGlobalUsingOnlyNeedsReferen } [Test] - public void ApplyMigration_WhenCurrentFirstPartyToolsAliasOnlyNeedsReference_RewritesFirstPartyNamespace() + public void ApplyMigration_WhenCurrentFirstPartyToolsAliasOnlyNeedsReference_AddsFirstPartyReference() { - // Verifies that a current FirstPartyTools namespace alias by itself moves to ToolContracts. + // Verifies that a current FirstPartyTools namespace alias by itself only needs the FirstParty asmdef reference. string projectRoot = CreateProjectRoot(); try { @@ -2320,13 +2372,13 @@ public void ApplyMigration_WhenCurrentFirstPartyToolsAliasOnlyNeedsReference_Rew ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); string migratedAsmdef = File.ReadAllText(asmdefPath); - Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(result.FileCount, Is.EqualTo(1)); Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); - Assert.That(result.FilePaths.Contains(aliasUsingPath), Is.True); - Assert.That(migratedAsmdef, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(migratedAsmdef, Does.Not.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); + Assert.That(result.FilePaths.Contains(aliasUsingPath), Is.False); + Assert.That(migratedAsmdef, Does.Not.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + Assert.That(migratedAsmdef, Does.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); Assert.That(File.ReadAllText(aliasUsingPath), Does.Contain( - "using Fpt = io.github.hatayama.UnityCliLoop.ToolContracts;")); + "using Fpt = io.github.hatayama.UnityCliLoop.FirstPartyTools;")); } finally { @@ -2884,7 +2936,7 @@ public sealed class UIElementInfo Assert.That(migratedToolSource, Does.Contain("Task ExecuteAsync")); Assert.That(migratedToolSource, Does.Contain("List elements")); Assert.That(migratedToolSource, Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureWindowAsync")); + "EditorWindowCaptureUtility.CaptureWindowAsync")); Assert.That(migratedToolSource, Does.Not.Contain( "io.github.hatayama.UnityCliLoop.ToolContracts.ScreenshotSchema")); Assert.That(migratedToolSource, Does.Not.Contain( @@ -2960,10 +3012,11 @@ public async Task CaptureAsync(EditorWindow window, CancellationToken Assert.That(result.FileCount, Is.EqualTo(1)); Assert.That(migratedSource, Does.Contain( - "UnityCliLoopTool")); + "UnityCliLoopTool")); Assert.That(migratedSource, Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureWindowAsync")); - Assert.That(migratedSource, Does.Not.Contain("await EditorWindowCaptureUtility.CaptureWindowAsync")); + "EditorWindowCaptureUtility.CaptureWindowAsync")); + Assert.That(migratedSource, Does.Not.Contain( + "EditorWindowCaptureUtility.CaptureWindowAsync(window, 1.0f, ct)")); } finally { @@ -3551,6 +3604,37 @@ public static ToolInfo[] GetTools() } } + [Test] + public async Task HasMigrationTargetsAsync_WhenCurrentDomainGlobalUsingOnlyNeedsReference_ReturnsTrue() + { + // Verifies that startup detection reports a missing Domain reference for current Domain global using. + string projectRoot = CreateProjectRoot(); + try + { + string toolDirectory = Path.Combine(projectRoot, "Assets", "VendorTools"); + Directory.CreateDirectory(toolDirectory); + File.WriteAllText( + Path.Combine(toolDirectory, "GlobalUsings.cs"), + "global using io.github.hatayama.UnityCliLoop.Domain;"); + File.WriteAllText( + Path.Combine(toolDirectory, "VendorTools.Editor.asmdef"), + @"{ + ""name"": ""VendorTools.Editor"", + ""references"": [] +}"); + + ThirdPartyToolMigrationFileService service = new(); + + bool hasTargets = await service.HasMigrationTargetsAsync(projectRoot, CancellationToken.None); + + Assert.That(hasTargets, Is.True); + } + finally + { + Directory.Delete(projectRoot, recursive: true); + } + } + [Test] public async Task HasMigrationTargetsAsync_WhenCurrentFirstPartyToolsGlobalUsingOnlyNeedsReference_ReturnsTrue() { @@ -3613,6 +3697,272 @@ public async Task HasMigrationTargetsAsync_WhenCurrentFirstPartyToolsAliasOnlyNe } } + [Test] + public void ApplyMigration_WhenCurrentDomainLocalAliasUsesMovedContractTypes_AddsToolContractsReference() + { + // Verifies that local Domain aliases rewritten to ToolContracts also update asmdef references. + string projectRoot = CreateProjectRoot(); + try + { + string toolDirectory = Path.Combine(projectRoot, "Assets", "VendorTools"); + Directory.CreateDirectory(toolDirectory); + string toolPath = Path.Combine(toolDirectory, "ToolMetadataProvider.cs"); + string asmdefPath = Path.Combine(toolDirectory, "VendorTools.Editor.asmdef"); + File.WriteAllText(toolPath, @"using Dom = io.github.hatayama.UnityCliLoop.Domain; + +public static class ToolMetadataProvider +{ + public static Dom.ToolInfo[] GetTools() + { + return new Dom.ToolInfo[0]; + } +}"); + File.WriteAllText(asmdefPath, @"{ + ""name"": ""VendorTools.Editor"", + ""references"": [ + ""GUID:5c4588558a3624eacbce0f50007cf1eb"" + ] +}"); + + ThirdPartyToolMigrationFileService service = new(); + ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); + string migratedSource = File.ReadAllText(toolPath); + string migratedAsmdef = File.ReadAllText(asmdefPath); + + Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(result.FilePaths.Contains(toolPath), Is.True); + Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); + Assert.That(migratedSource, Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.ToolInfo[] GetTools")); + Assert.That(migratedAsmdef, Does.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); + Assert.That(migratedAsmdef, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + } + finally + { + Directory.Delete(projectRoot, recursive: true); + } + } + + [Test] + public void ApplyMigration_WhenCurrentFirstPartyToolsAliasUsesMovedContractTypes_AddsToolContractsReference() + { + // Verifies that current FirstPartyTools contract rewrites also add the ToolContracts asmdef reference. + string projectRoot = CreateProjectRoot(); + try + { + string toolDirectory = Path.Combine(projectRoot, "Assets", "VendorTools"); + Directory.CreateDirectory(toolDirectory); + string toolPath = Path.Combine(toolDirectory, "ScreenshotMetadata.cs"); + string asmdefPath = Path.Combine(toolDirectory, "VendorTools.Editor.asmdef"); + File.WriteAllText(toolPath, @"using Fpt = io.github.hatayama.UnityCliLoop.FirstPartyTools; + +public sealed class ScreenshotMetadata +{ + public Fpt.WindowMatchMode MatchMode => Fpt.WindowMatchMode.contains; +}"); + File.WriteAllText(asmdefPath, @"{ + ""name"": ""VendorTools.Editor"", + ""references"": [ + ""GUID:a0bdbd2c5705643fbb9aef9fac8fd46a"" + ] +}"); + + ThirdPartyToolMigrationFileService service = new(); + ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); + string migratedSource = File.ReadAllText(toolPath); + string migratedAsmdef = File.ReadAllText(asmdefPath); + + Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(result.FilePaths.Contains(toolPath), Is.True); + Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); + Assert.That(migratedSource, Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.WindowMatchMode MatchMode")); + Assert.That(migratedSource, Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.WindowMatchMode.contains")); + Assert.That(migratedAsmdef, Does.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); + Assert.That(migratedAsmdef, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + } + finally + { + Directory.Delete(projectRoot, recursive: true); + } + } + + [Test] + public void ApplyMigration_WhenCurrentFirstPartyFindWindowsUsesFirstPartyReference_AddsToolContractsReference() + { + // Verifies that preserved FirstParty capture APIs keep direct ToolContracts asmdef access. + string projectRoot = CreateProjectRoot(); + try + { + string toolDirectory = Path.Combine(projectRoot, "Assets", "VendorTools"); + Directory.CreateDirectory(toolDirectory); + string sourcePath = Path.Combine(toolDirectory, "WindowFinder.cs"); + string asmdefPath = Path.Combine(toolDirectory, "VendorTools.Editor.asmdef"); + File.WriteAllText(sourcePath, @"using io.github.hatayama.UnityCliLoop.FirstPartyTools; + +public sealed class WindowFinder +{ + public object Find() + { + return EditorWindowCaptureUtility.FindWindowsByName(""Game""); + } +}"); + File.WriteAllText(asmdefPath, @"{ + ""name"": ""VendorTools.Editor"", + ""references"": [ + ""GUID:a0bdbd2c5705643fbb9aef9fac8fd46a"" + ] +}"); + + ThirdPartyToolMigrationFileService service = new(); + ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); + string migratedSource = File.ReadAllText(sourcePath); + string migratedAsmdef = File.ReadAllText(asmdefPath); + + Assert.That(result.FileCount, Is.EqualTo(1)); + Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); + Assert.That(result.FilePaths.Contains(sourcePath), Is.False); + Assert.That(migratedSource, Does.Contain("EditorWindowCaptureUtility.FindWindowsByName")); + Assert.That(migratedAsmdef, Does.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); + Assert.That(migratedAsmdef, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + } + finally + { + Directory.Delete(projectRoot, recursive: true); + } + } + + [Test] + public void ApplyMigration_WhenCurrentApplicationLocalUsingUsesMovedSwitcher_AddsApplicationReference() + { + // Verifies that preserved local Application using directives keep their asmdef reference. + string projectRoot = CreateProjectRoot(); + try + { + string toolDirectory = Path.Combine(projectRoot, "Assets", "VendorTools"); + Directory.CreateDirectory(toolDirectory); + string toolPath = Path.Combine(toolDirectory, "MainThreadUsage.cs"); + string asmdefPath = Path.Combine(toolDirectory, "VendorTools.Editor.asmdef"); + File.WriteAllText(toolPath, @"using System.Threading; +using System.Threading.Tasks; +using io.github.hatayama.UnityCliLoop.Application; + +public sealed class MainThreadUsage +{ + public async Task RunAsync(CancellationToken ct) + { + await MainThreadSwitcher.SwitchToMainThread(PlayerLoopTiming.Update, ct); + } +}"); + File.WriteAllText(asmdefPath, @"{ + ""name"": ""VendorTools.Editor"", + ""references"": [ + ""GUID:214998e563c124e8a88199b2dd1f522d"" + ] +}"); + + ThirdPartyToolMigrationFileService service = new(); + ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); + string migratedSource = File.ReadAllText(toolPath); + string migratedAsmdef = File.ReadAllText(asmdefPath); + + Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(result.FilePaths.Contains(toolPath), Is.True); + Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); + Assert.That(migratedSource, Does.Contain( + "using io.github.hatayama.UnityCliLoop.Application;")); + Assert.That(migratedSource, Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.MainThreadSwitcher.SwitchToMainThread(ct)")); + Assert.That(migratedAsmdef, Does.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); + Assert.That(migratedAsmdef, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + } + finally + { + Directory.Delete(projectRoot, recursive: true); + } + } + + [Test] + public void ApplyMigration_WhenCurrentApplicationLocalUsingOnlyNeedsReference_AddsApplicationReference() + { + // Verifies that Application types preserved in Application still keep their asmdef reference. + string projectRoot = CreateProjectRoot(); + try + { + string toolDirectory = Path.Combine(projectRoot, "Assets", "VendorTools"); + Directory.CreateDirectory(toolDirectory); + string sourcePath = Path.Combine(toolDirectory, "SkillStateProbe.cs"); + string asmdefPath = Path.Combine(toolDirectory, "VendorTools.Editor.asmdef"); + File.WriteAllText(sourcePath, @"using io.github.hatayama.UnityCliLoop.Application; + +public sealed class SkillStateProbe +{ + public SkillInstallState State; +}"); + File.WriteAllText(asmdefPath, @"{ + ""name"": ""VendorTools.Editor"", + ""references"": [] +}"); + + ThirdPartyToolMigrationFileService service = new(); + ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); + string migratedSource = File.ReadAllText(sourcePath); + string migratedAsmdef = File.ReadAllText(asmdefPath); + + Assert.That(result.FileCount, Is.EqualTo(1)); + Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); + Assert.That(result.FilePaths.Contains(sourcePath), Is.False); + Assert.That(migratedSource, Does.Contain("SkillInstallState State;")); + Assert.That(migratedAsmdef, Does.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); + Assert.That(migratedAsmdef, Does.Not.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + } + finally + { + Directory.Delete(projectRoot, recursive: true); + } + } + + [Test] + public void ApplyMigration_WhenCurrentDomainLocalUsingOnlyNeedsReference_AddsDomainReference() + { + // Verifies that Domain types preserved in Domain still keep their asmdef reference. + string projectRoot = CreateProjectRoot(); + try + { + string toolDirectory = Path.Combine(projectRoot, "Assets", "VendorTools"); + Directory.CreateDirectory(toolDirectory); + string sourcePath = Path.Combine(toolDirectory, "ToolSettingsProbe.cs"); + string asmdefPath = Path.Combine(toolDirectory, "VendorTools.Editor.asmdef"); + File.WriteAllText(sourcePath, @"using io.github.hatayama.UnityCliLoop.Domain; + +public sealed class ToolSettingsProbe +{ + public ToolSettingsService Service; +}"); + File.WriteAllText(asmdefPath, @"{ + ""name"": ""VendorTools.Editor"", + ""references"": [] +}"); + + ThirdPartyToolMigrationFileService service = new(); + ThirdPartyToolMigrationResult result = service.ApplyMigration(projectRoot); + string migratedSource = File.ReadAllText(sourcePath); + string migratedAsmdef = File.ReadAllText(asmdefPath); + + Assert.That(result.FileCount, Is.EqualTo(1)); + Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); + Assert.That(result.FilePaths.Contains(sourcePath), Is.False); + Assert.That(migratedSource, Does.Contain("ToolSettingsService Service;")); + Assert.That(migratedAsmdef, Does.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); + Assert.That(migratedAsmdef, Does.Not.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + } + finally + { + Directory.Delete(projectRoot, recursive: true); + } + } + [Test] public async Task HasMigrationTargetsAsync_WhenAssemblyUsesCurrentApplicationGlobalAlias_ReturnsTrue() { @@ -4063,9 +4413,9 @@ public sealed class ScreenshotResponse : UnityCliLoopToolResponse } [Test] - public async Task HasMigrationTargetsAsync_WhenCurrentApplicationGuidReferenceExists_ReturnsTrue() + public async Task HasMigrationTargetsAsync_WhenCurrentApplicationGuidReferenceExists_ReturnsFalse() { - // Verifies that current V3 Application namespace usage still triggers source migration. + // Verifies that already-correct V3 Application namespace usage does not trigger migration. string projectRoot = CreateProjectRoot(); try { @@ -4097,7 +4447,7 @@ public static void Register(IUnityCliLoopTool tool) bool hasTargets = await service.HasMigrationTargetsAsync(projectRoot, CancellationToken.None); - Assert.That(hasTargets, Is.True); + Assert.That(hasTargets, Is.False); } finally { @@ -4106,9 +4456,9 @@ public static void Register(IUnityCliLoopTool tool) } [Test] - public async Task HasMigrationTargetsAsync_WhenCurrentRegistrarReturnMissingDomainReference_ReturnsTrue() + public async Task HasMigrationTargetsAsync_WhenCurrentRegistrarReturnMissingDomainReference_ReturnsFalse() { - // Verifies that startup detection catches registrar return calls that still need Domain asmdef refs. + // Verifies that registrar return calls only need ToolContracts for their public contract return type. string projectRoot = CreateProjectRoot(); try { @@ -4139,7 +4489,7 @@ public static string GetLabel() bool hasTargets = await service.HasMigrationTargetsAsync(projectRoot, CancellationToken.None); - Assert.That(hasTargets, Is.True); + Assert.That(hasTargets, Is.False); } finally { @@ -4493,9 +4843,9 @@ public sealed class HelloResponse : UnityCliLoopToolResponse } [Test] - public void PreviewMigration_WhenCurrentApplicationGuidReferenceExists_ReportsSourceMigration() + public void PreviewMigration_WhenCurrentApplicationGuidReferenceExists_ReportsNoMigration() { - // Verifies that V3 Application namespace usage is reported as source migration. + // Verifies that already-correct V3 Application namespace usage is not reported as migration. string projectRoot = CreateProjectRoot(); try { @@ -4526,9 +4876,9 @@ public static void Register(IUnityCliLoopTool tool) ThirdPartyToolMigrationFileService service = new(); ThirdPartyToolMigrationPreview preview = service.PreviewMigration(projectRoot); - Assert.That(preview.HasTargets, Is.True); - Assert.That(preview.FileCount, Is.EqualTo(1)); - Assert.That(preview.FilePaths.Contains(registrationPath), Is.True); + Assert.That(preview.HasTargets, Is.False); + Assert.That(preview.FileCount, Is.EqualTo(0)); + Assert.That(preview.FilePaths.Contains(registrationPath), Is.False); Assert.That(preview.FilePaths.Contains(asmdefPath), Is.False); Assert.That(File.ReadAllText(registrationPath), Is.EqualTo(registrationSource)); Assert.That(File.ReadAllText(asmdefPath), Is.EqualTo(asmdefSource)); @@ -4719,7 +5069,7 @@ await service.ApplyMigrationAsync( [Test] public async Task ApplyMigrationAsync_WhenAssemblyUsesCurrentApplicationGlobalAlias_RewritesQualifiedMainThreadSwitcherTiming() { - // Verifies that async migration uses current Application global aliases from sibling files. + // Verifies that async migration rebinds moved switcher calls from current Application global aliases. string projectRoot = CreateProjectRoot(); try { @@ -4754,13 +5104,15 @@ await service.ApplyMigrationAsync( CancellationToken.None); string migratedSource = File.ReadAllText(mainThreadPath); - Assert.That(result.FilePaths.Contains(globalUsingPath), Is.True); Assert.That(result.FilePaths.Contains(mainThreadPath), Is.True); + Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); Assert.That(File.ReadAllText(globalUsingPath), Does.Contain( - "global using App = io.github.hatayama.UnityCliLoop.ToolContracts;")); + "global using App = io.github.hatayama.UnityCliLoop.Application;")); Assert.That(migratedSource, Does.Contain( - "await App.MainThreadSwitcher.SwitchToMainThread(ct);")); + "await io.github.hatayama.UnityCliLoop.ToolContracts.MainThreadSwitcher.SwitchToMainThread(ct);")); + Assert.That(migratedSource, Does.Not.Contain("App.MainThreadSwitcher")); Assert.That(migratedSource, Does.Not.Contain("PlayerLoopTiming")); + Assert.That(File.ReadAllText(asmdefPath), Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); } finally { @@ -4769,9 +5121,9 @@ await service.ApplyMigrationAsync( } [Test] - public async Task ApplyMigrationAsync_WhenCurrentFirstPartyToolsGlobalUsingOnlyNeedsReference_RewritesFirstPartyNamespace() + public async Task ApplyMigrationAsync_WhenCurrentFirstPartyToolsGlobalUsingOnlyNeedsReference_AddsFirstPartyReference() { - // Verifies that async migration rewrites current FirstPartyTools global using to ToolContracts. + // Verifies that async migration only adds the FirstParty asmdef reference for current FirstPartyTools global using. string projectRoot = CreateProjectRoot(); try { @@ -4793,13 +5145,13 @@ await service.ApplyMigrationAsync( CancellationToken.None); string migratedAsmdef = File.ReadAllText(asmdefPath); - Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(result.FileCount, Is.EqualTo(1)); Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); - Assert.That(result.FilePaths.Contains(globalUsingPath), Is.True); - Assert.That(migratedAsmdef, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(migratedAsmdef, Does.Not.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); + Assert.That(result.FilePaths.Contains(globalUsingPath), Is.False); + Assert.That(migratedAsmdef, Does.Not.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + Assert.That(migratedAsmdef, Does.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); Assert.That(File.ReadAllText(globalUsingPath), Does.Contain( - "global using io.github.hatayama.UnityCliLoop.ToolContracts;")); + "global using io.github.hatayama.UnityCliLoop.FirstPartyTools;")); } finally { @@ -4848,13 +5200,13 @@ await service.ApplyMigrationAsync( CancellationToken.None); string migratedSource = File.ReadAllText(toolPath); - Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(result.FileCount, Is.EqualTo(1)); Assert.That(result.FilePaths.Contains(toolPath), Is.True); - Assert.That(result.FilePaths.Contains(globalUsingPath), Is.True); + Assert.That(result.FilePaths.Contains(globalUsingPath), Is.False); Assert.That(File.ReadAllText(globalUsingPath), Does.Contain( - "global using Fpt = io.github.hatayama.UnityCliLoop.ToolContracts;")); + "global using Fpt = io.github.hatayama.UnityCliLoop.FirstPartyTools;")); Assert.That(migratedSource, Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureWindowAsync")); + "Fpt.EditorWindowCaptureUtility.CaptureWindowAsync")); Assert.That(migratedSource, Does.Contain( "io.github.hatayama.UnityCliLoop.ToolContracts.UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS")); Assert.That(migratedSource, Does.Not.Contain( @@ -4867,9 +5219,9 @@ await service.ApplyMigrationAsync( } [Test] - public async Task ApplyMigrationAsync_WhenCurrentFirstPartyToolsAliasOnlyNeedsReference_RewritesFirstPartyNamespace() + public async Task ApplyMigrationAsync_WhenCurrentFirstPartyToolsAliasOnlyNeedsReference_AddsFirstPartyReference() { - // Verifies that async migration rewrites current FirstPartyTools alias using to ToolContracts. + // Verifies that async migration only adds the FirstParty asmdef reference for current FirstPartyTools alias using. string projectRoot = CreateProjectRoot(); try { @@ -4891,13 +5243,115 @@ await service.ApplyMigrationAsync( CancellationToken.None); string migratedAsmdef = File.ReadAllText(asmdefPath); + Assert.That(result.FileCount, Is.EqualTo(1)); + Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); + Assert.That(result.FilePaths.Contains(aliasUsingPath), Is.False); + Assert.That(migratedAsmdef, Does.Not.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + Assert.That(migratedAsmdef, Does.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); + Assert.That(File.ReadAllText(aliasUsingPath), Does.Contain( + "using Fpt = io.github.hatayama.UnityCliLoop.FirstPartyTools;")); + } + finally + { + Directory.Delete(projectRoot, recursive: true); + } + } + + [Test] + public async Task ApplyMigrationAsync_WhenCurrentDomainLocalAliasUsesMovedContractTypes_AddsToolContractsReference() + { + // Verifies that async migration adds ToolContracts refs for local Domain alias rewrites. + string projectRoot = CreateProjectRoot(); + try + { + string toolDirectory = Path.Combine(projectRoot, "Assets", "VendorTools"); + Directory.CreateDirectory(toolDirectory); + string toolPath = Path.Combine(toolDirectory, "ToolMetadataProvider.cs"); + string asmdefPath = Path.Combine(toolDirectory, "VendorTools.Editor.asmdef"); + File.WriteAllText(toolPath, @"using Dom = io.github.hatayama.UnityCliLoop.Domain; + +public static class ToolMetadataProvider +{ + public static Dom.ServiceResult Create() + { + return null; + } +}"); + File.WriteAllText(asmdefPath, @"{ + ""name"": ""VendorTools.Editor"", + ""references"": [ + ""GUID:5c4588558a3624eacbce0f50007cf1eb"" + ] +}"); + + ThirdPartyToolMigrationFileService service = new(); + ThirdPartyToolMigrationResult result = + await service.ApplyMigrationAsync( + projectRoot, + new Progress(), + CancellationToken.None); + string migratedSource = File.ReadAllText(toolPath); + string migratedAsmdef = File.ReadAllText(asmdefPath); + Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(result.FilePaths.Contains(toolPath), Is.True); Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); - Assert.That(result.FilePaths.Contains(aliasUsingPath), Is.True); + Assert.That(migratedSource, Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.ServiceResult Create")); + Assert.That(migratedAsmdef, Does.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); + Assert.That(migratedAsmdef, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + } + finally + { + Directory.Delete(projectRoot, recursive: true); + } + } + + [Test] + public async Task ApplyMigrationAsync_WhenCurrentFirstPartyToolsAliasUsesMovedContractTypes_AddsToolContractsReference() + { + // Verifies that async migration adds ToolContracts refs for current FirstPartyTools contract rewrites. + string projectRoot = CreateProjectRoot(); + try + { + string toolDirectory = Path.Combine(projectRoot, "Assets", "VendorTools"); + Directory.CreateDirectory(toolDirectory); + string toolPath = Path.Combine(toolDirectory, "ScreenshotMetadata.cs"); + string asmdefPath = Path.Combine(toolDirectory, "VendorTools.Editor.asmdef"); + File.WriteAllText(toolPath, @"using Fpt = io.github.hatayama.UnityCliLoop.FirstPartyTools; + +public sealed class ScreenshotMetadata +{ + public Fpt.ScreenshotSchema CreateSchema() + { + return new Fpt.ScreenshotSchema(); + } +}"); + File.WriteAllText(asmdefPath, @"{ + ""name"": ""VendorTools.Editor"", + ""references"": [ + ""GUID:a0bdbd2c5705643fbb9aef9fac8fd46a"" + ] +}"); + + ThirdPartyToolMigrationFileService service = new(); + ThirdPartyToolMigrationResult result = + await service.ApplyMigrationAsync( + projectRoot, + new Progress(), + CancellationToken.None); + string migratedSource = File.ReadAllText(toolPath); + string migratedAsmdef = File.ReadAllText(asmdefPath); + + Assert.That(result.FileCount, Is.EqualTo(2)); + Assert.That(result.FilePaths.Contains(toolPath), Is.True); + Assert.That(result.FilePaths.Contains(asmdefPath), Is.True); + Assert.That(migratedSource, Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.ScreenshotSchema CreateSchema")); + Assert.That(migratedSource, Does.Contain( + "new io.github.hatayama.UnityCliLoop.ToolContracts.ScreenshotSchema()")); + Assert.That(migratedAsmdef, Does.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); Assert.That(migratedAsmdef, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(migratedAsmdef, Does.Not.Contain("GUID:a0bdbd2c5705643fbb9aef9fac8fd46a")); - Assert.That(File.ReadAllText(aliasUsingPath), Does.Contain( - "using Fpt = io.github.hatayama.UnityCliLoop.ToolContracts;")); } finally { diff --git a/Assets/Tests/Editor/ThirdPartyToolMigrationRulesTests.cs b/Assets/Tests/Editor/ThirdPartyToolMigrationRulesTests.cs index 840d363c32..2836e0f906 100644 --- a/Assets/Tests/Editor/ThirdPartyToolMigrationRulesTests.cs +++ b/Assets/Tests/Editor/ThirdPartyToolMigrationRulesTests.cs @@ -60,6 +60,169 @@ public void MigrateCSharpSource_WhenNoLegacyToolApiIsUsed_KeepsContent() Assert.That(result.ReplacementCount, Is.EqualTo(0)); } + [Test] + public void MigrateCSharpSource_WhenCurrentFirstPartyToolTypesAreUsed_KeepsNamespaceImport() + { + // Verifies that first-party tool implementations are not rebound to the public contract namespace. + string source = @"using io.github.hatayama.UnityCliLoop.FirstPartyTools; +using io.github.hatayama.UnityCliLoop.ToolContracts; + +public sealed class InputTest +{ + private SimulateKeyboardTool tool = null; + private SimulateKeyboardResponse response = null; +}"; + + ThirdPartyToolMigrationContentResult result = + ThirdPartyToolMigrationRules.MigrateCSharpSource(source); + + Assert.That(result.Changed, Is.False); + Assert.That(result.Content, Is.EqualTo(source)); + Assert.That(result.ReplacementCount, Is.EqualTo(0)); + } + + [Test] + public void MigrateCSharpSource_WhenCurrentApplicationTypesAreUsed_KeepsNamespaceImport() + { + // Verifies that application-layer implementation references are not rebound to the public contract namespace. + string source = @"using io.github.hatayama.UnityCliLoop.Application; +using io.github.hatayama.UnityCliLoop.ToolContracts; + +public sealed class SetupTest +{ + private SkillInstallState installState; +}"; + + ThirdPartyToolMigrationContentResult result = + ThirdPartyToolMigrationRules.MigrateCSharpSource(source); + + Assert.That(result.Changed, Is.False); + Assert.That(result.Content, Is.EqualTo(source)); + Assert.That(result.ReplacementCount, Is.EqualTo(0)); + } + + [Test] + public void MigrateCSharpSource_WhenCurrentDomainTypesAreUsed_KeepsNamespaceImport() + { + // Verifies that domain-layer implementation references are not rebound to the public contract namespace. + string source = @"using io.github.hatayama.UnityCliLoop.Domain; +using io.github.hatayama.UnityCliLoop.ToolContracts; + +public sealed class SettingsTest +{ + private ToolSettingsService settingsService; +}"; + + ThirdPartyToolMigrationContentResult result = + ThirdPartyToolMigrationRules.MigrateCSharpSource(source); + + Assert.That(result.Changed, Is.False); + Assert.That(result.Content, Is.EqualTo(source)); + Assert.That(result.ReplacementCount, Is.EqualTo(0)); + } + + [Test] + public void MigrateCSharpSource_WhenCurrentDomainAliasUsesMovedContractTypes_RewritesContracts() + { + // Verifies that current Domain aliases do not leave moved public contract types in the Domain namespace. + string source = @"using Dom = io.github.hatayama.UnityCliLoop.Domain; + +public static class ToolMetadataProvider +{ + public static Dom.ToolInfo[] GetTools() + { + return new Dom.ToolInfo[0]; + } + + public static Dom.ServiceResult Create() + { + return null; + } +}"; + + ThirdPartyToolMigrationContentResult result = + ThirdPartyToolMigrationRules.MigrateCSharpSource(source); + + Assert.That(result.Changed, Is.True); + Assert.That(result.Content, Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.ToolInfo[] GetTools")); + Assert.That(result.Content, Does.Contain( + "new io.github.hatayama.UnityCliLoop.ToolContracts.ToolInfo[0]")); + Assert.That(result.Content, Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.ServiceResult Create")); + Assert.That(result.Content, Does.Not.Contain("Dom.ToolInfo")); + Assert.That(result.Content, Does.Not.Contain("Dom.ServiceResult")); + } + + [Test] + public void MigrateCSharpSource_WhenCurrentDomainAliasAndQualifiedReferenceExist_KeepsUnrelatedBareContracts() + { + // Verifies that Domain aliases and qualified references do not enable unrelated bare type rewrites. + string source = @"using Dom = io.github.hatayama.UnityCliLoop.Domain; +using Vendor.Metadata; + +public sealed class MetadataConsumer +{ + private ToolInfo info; + private ServiceResult result; + + public Dom.ToolSettingsCatalogItem[] AliasCatalog() + { + return new Dom.ToolSettingsCatalogItem[0]; + } + + public io.github.hatayama.UnityCliLoop.Domain.ToolSettingsCatalogItem[] QualifiedCatalog() + { + return new io.github.hatayama.UnityCliLoop.Domain.ToolSettingsCatalogItem[0]; + } +}"; + + ThirdPartyToolMigrationContentResult result = + ThirdPartyToolMigrationRules.MigrateCSharpSource(source); + + Assert.That(result.Changed, Is.True); + Assert.That(result.Content, Does.Contain("private ToolInfo info;")); + Assert.That(result.Content, Does.Contain("private ServiceResult result;")); + Assert.That(result.Content, Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.ToolSettingsCatalogItem[] AliasCatalog")); + Assert.That(result.Content, Does.Contain( + "new io.github.hatayama.UnityCliLoop.ToolContracts.ToolSettingsCatalogItem[0]")); + Assert.That(result.Content, Does.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.ToolSettingsCatalogItem[] QualifiedCatalog")); + Assert.That(result.Content, Does.Not.Contain( + "private io.github.hatayama.UnityCliLoop.ToolContracts.ToolInfo info;")); + Assert.That(result.Content, Does.Not.Contain( + "private io.github.hatayama.UnityCliLoop.ToolContracts.ServiceResult result;")); + } + + [Test] + public void MigrateCSharpSource_WhenCurrentToolContractsReferencesUseBareNames_KeepsContent() + { + // Verifies that already-current ToolContracts references are not rewritten to fully qualified names. + string source = @"using System.Collections.Generic; +using io.github.hatayama.UnityCliLoop.ToolContracts; + +public sealed class CurrentReferences +{ + private WindowMatchMode matchMode = WindowMatchMode.exact; + + public void Run() + { + EditorWindowCaptureUtility.GetOpenWindowNames(); + MainThreadSwitcher.RegisterService(null); + ScreenshotResponse response = new(); + List elements = new(); + } +}"; + + ThirdPartyToolMigrationContentResult result = + ThirdPartyToolMigrationRules.MigrateCSharpSource(source); + + Assert.That(result.Changed, Is.False); + Assert.That(result.Content, Is.EqualTo(source)); + Assert.That(result.ReplacementCount, Is.EqualTo(0)); + } + [Test] public void MigrateCSharpSource_WhenLegacyToolAttributeSuffixHasArguments_DropsUnsupportedArguments() { @@ -390,10 +553,12 @@ public async Task CaptureAsync(CancellationToken ct) hasLegacyAssemblySource: true, hasAssemblyScopedCurrentToolContractsUsing: false, hasAssemblyScopedCurrentApplicationUsing: false, + hasAssemblyScopedCurrentDomainUsing: false, hasAssemblyScopedCurrentFirstPartyToolsUsing: false, legacyAssemblyAliases: System.Array.Empty(), legacyAssemblyToolInfoAliases: System.Array.Empty(), currentApplicationAssemblyAliases: System.Array.Empty(), + currentDomainAssemblyAliases: System.Array.Empty(), currentFirstPartyToolsAssemblyAliases: System.Array.Empty(), assemblyDeclaredTypeNames: System.Array.Empty()); @@ -454,10 +619,12 @@ public async Task CaptureAsync(EditorWindow window, CancellationToken hasLegacyAssemblySource: true, hasAssemblyScopedCurrentToolContractsUsing: false, hasAssemblyScopedCurrentApplicationUsing: false, + hasAssemblyScopedCurrentDomainUsing: false, hasAssemblyScopedCurrentFirstPartyToolsUsing: false, legacyAssemblyAliases: System.Array.Empty(), legacyAssemblyToolInfoAliases: System.Array.Empty(), currentApplicationAssemblyAliases: System.Array.Empty(), + currentDomainAssemblyAliases: System.Array.Empty(), currentFirstPartyToolsAssemblyAliases: System.Array.Empty(), assemblyDeclaredTypeNames: System.Array.Empty()); @@ -490,19 +657,52 @@ public async Task CaptureAsync(EditorWindow window, CancellationToken hasLegacyAssemblySource: false, hasAssemblyScopedCurrentToolContractsUsing: false, hasAssemblyScopedCurrentApplicationUsing: false, + hasAssemblyScopedCurrentDomainUsing: false, hasAssemblyScopedCurrentFirstPartyToolsUsing: true, legacyAssemblyAliases: System.Array.Empty(), legacyAssemblyToolInfoAliases: System.Array.Empty(), currentApplicationAssemblyAliases: System.Array.Empty(), + currentDomainAssemblyAliases: System.Array.Empty(), currentFirstPartyToolsAssemblyAliases: System.Array.Empty(), assemblyDeclaredTypeNames: System.Array.Empty()); Assert.That(result.Changed, Is.True); Assert.That(result.Content, Does.Contain( - "return (await io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureWindowAsync(window, 1.0f, io.github.hatayama.UnityCliLoop.ToolContracts.UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS, ct)).texture;")); + "return (await EditorWindowCaptureUtility.CaptureWindowAsync(window, 1.0f, io.github.hatayama.UnityCliLoop.ToolContracts.UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS, ct)).texture;")); Assert.That(result.Content, Does.Not.Contain("return await EditorWindowCaptureUtility.CaptureWindowAsync")); } + [Test] + public void MigrateCSharpSource_WhenCurrentFirstPartyToolsQualifiedReferenceExists_QualifiesBareCaptureMigration() + { + // Verifies that fully qualified FirstPartyTools references do not make unrelated bare capture calls valid. + string source = @"using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +public sealed class ScreenshotTool +{ + public async Task CaptureAsync(EditorWindow window, CancellationToken ct) + { + _ = io.github.hatayama.UnityCliLoop.FirstPartyTools.EditorWindowCaptureUtility.GetOpenWindowNames(); + return await EditorWindowCaptureUtility.CaptureWindowAsync(window, 1.0f, ct); + } +}"; + + ThirdPartyToolMigrationContentResult result = + ThirdPartyToolMigrationRules.MigrateCSharpSource(source); + string expectedCaptureCall = + "return (await io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility." + + "CaptureWindowAsync(window, 1.0f, io.github.hatayama.UnityCliLoop.ToolContracts." + + "UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS, ct)).texture;"; + + Assert.That(result.Changed, Is.True); + Assert.That(result.Content, Does.Contain(expectedCaptureCall)); + Assert.That(result.Content, Does.Not.Contain( + "return (await EditorWindowCaptureUtility.CaptureWindowAsync")); + } + [Test] public void MigrateCSharpSourceForLegacyAssembly_WhenAssemblyDeclaresCaptureUtility_KeepsBareLocalHelperCall() { @@ -537,10 +737,12 @@ public static Task CaptureWindowAsync( hasLegacyAssemblySource: true, hasAssemblyScopedCurrentToolContractsUsing: false, hasAssemblyScopedCurrentApplicationUsing: false, + hasAssemblyScopedCurrentDomainUsing: false, hasAssemblyScopedCurrentFirstPartyToolsUsing: false, legacyAssemblyAliases: System.Array.Empty(), legacyAssemblyToolInfoAliases: System.Array.Empty(), currentApplicationAssemblyAliases: System.Array.Empty(), + currentDomainAssemblyAliases: System.Array.Empty(), currentFirstPartyToolsAssemblyAliases: System.Array.Empty(), assemblyDeclaredTypeNames: new[] { "EditorWindowCaptureUtility" }); @@ -574,7 +776,7 @@ public async Task CaptureAsync(EditorWindow window, CancellationToken Assert.That(result.Changed, Is.True); Assert.That(result.Content, Does.Contain( - "return (await io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureWindowAsync(window, 1.0f, UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS, ct)).texture;")); + "return (await EditorWindowCaptureUtility.CaptureWindowAsync(window, 1.0f, UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS, ct)).texture;")); Assert.That(result.Content, Does.Not.Contain("return await EditorWindowCaptureUtility.CaptureWindowAsync")); } @@ -601,7 +803,7 @@ public async Task CaptureAsync(EditorWindow window, CancellationToken Assert.That(result.Changed, Is.True); Assert.That(result.Content, Does.Contain( - "return (await io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureWindowAsync(window, 1.0f, io.github.hatayama.UnityCliLoop.ToolContracts.UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS, ct)).texture;")); + "return (await EditorWindowCaptureUtility.CaptureWindowAsync(window, 1.0f, io.github.hatayama.UnityCliLoop.ToolContracts.UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS, ct)).texture;")); Assert.That(result.Content, Does.Not.Contain("return await EditorWindowCaptureUtility.CaptureWindowAsync")); } @@ -636,7 +838,7 @@ public async Task CaptureAsync(EditorWindow window, CancellationToken [Test] public void MigrateCSharpSource_WhenCurrentFirstPartyToolsAliasCaptureLacksTimeout_RewritesCaptureCall() { - // Verifies that current FirstPartyTools namespace aliases move to ToolContracts and receive the V3 timeout argument. + // Verifies that current FirstPartyTools aliases receive the V3 timeout argument without rebinding the namespace. string source = @"using System.Threading; using System.Threading.Tasks; using UnityEditor; @@ -656,9 +858,9 @@ public async Task CaptureAsync(EditorWindow window, CancellationToken Assert.That(result.Changed, Is.True); Assert.That(result.Content, Does.Contain( - "using Fpt = io.github.hatayama.UnityCliLoop.ToolContracts;")); + "using Fpt = io.github.hatayama.UnityCliLoop.FirstPartyTools;")); Assert.That(result.Content, Does.Contain( - "return (await io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureWindowAsync(window, 1.0f, io.github.hatayama.UnityCliLoop.ToolContracts.UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS, ct)).texture;")); + "return (await Fpt.EditorWindowCaptureUtility.CaptureWindowAsync(window, 1.0f, io.github.hatayama.UnityCliLoop.ToolContracts.UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS, ct)).texture;")); Assert.That(result.Content, Does.Not.Contain("Fpt.UnityCliLoopConstants")); } @@ -926,9 +1128,9 @@ public sealed class ScreenshotHelper Assert.That(result.Content, Does.Contain( "new io.github.hatayama.UnityCliLoop.ToolContracts.UIElementInfo()")); Assert.That(result.Content, Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.FindWindowsByName")); + "EditorWindowCaptureUtility.FindWindowsByName")); Assert.That(result.Content, Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.GetOpenWindowNames")); + "EditorWindowCaptureUtility.GetOpenWindowNames")); } [Test] @@ -981,7 +1183,7 @@ public sealed class UIElementInfo Assert.That(result.Changed, Is.True); Assert.That(result.Content, Does.Contain( - "io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureWindowAsync")); + "EditorWindowCaptureUtility.CaptureWindowAsync(window, 1.0f, UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS, ct)")); Assert.That(result.Content, Does.Contain("private UIElementInfo CreateElementInfo()")); Assert.That(result.Content, Does.Contain("List clickableElements")); Assert.That(result.Content, Does.Not.Contain( @@ -1135,14 +1337,14 @@ public async Task CaptureAsync(CancellationToken ct) Assert.That(result.Changed, Is.True); Assert.That(result.Content, Does.Contain( - "(texture, yOffset, _) = await io.github.hatayama.UnityCliLoop.ToolContracts.EditorWindowCaptureUtility.CaptureGameRenderingAsync")); + "(texture, yOffset, _) = await EditorWindowCaptureUtility.CaptureGameRenderingAsync")); Assert.That(result.Content, Does.Not.Contain("(texture, yOffset) = await")); } [Test] public void MigrateCSharpSource_WhenCurrentRenderingCaptureUsesFirstPartyAlias_AddsDiscard() { - // Verifies that current FirstPartyTools aliases move to ToolContracts and receive the V3 rendering discard. + // Verifies that current FirstPartyTools aliases receive the V3 rendering discard without rebinding the namespace. string source = @"using System.Threading; using System.Threading.Tasks; using UnityEngine; @@ -1163,7 +1365,7 @@ public async Task CaptureAsync(CancellationToken ct) Assert.That(result.Changed, Is.True); Assert.That(result.Content, Does.Contain( - "using Fpt = io.github.hatayama.UnityCliLoop.ToolContracts;")); + "using Fpt = io.github.hatayama.UnityCliLoop.FirstPartyTools;")); Assert.That(result.Content, Does.Contain( "(texture, yOffset, _) = await Fpt.EditorWindowCaptureUtility.CaptureGameRenderingAsync")); } @@ -1333,7 +1535,7 @@ public async Task RunAsync(CancellationToken ct) [Test] public void MigrateCSharpSource_WhenCurrentApplicationAliasSwitcherHasLegacyTiming_RemovesTimingArgument() { - // Verifies that partially migrated Application aliases move to ToolContracts and receive the V3 argument shape. + // Verifies that moved public contract types are rebound when a partially migrated Application alias is used. string source = @"using System.Threading; using System.Threading.Tasks; using App = io.github.hatayama.UnityCliLoop.Application; @@ -1351,9 +1553,37 @@ public async Task RunAsync(CancellationToken ct) Assert.That(result.Changed, Is.True); Assert.That(result.Content, Does.Contain( - "using App = io.github.hatayama.UnityCliLoop.ToolContracts;")); + "using App = io.github.hatayama.UnityCliLoop.Application;")); Assert.That(result.Content, Does.Contain( - "await App.MainThreadSwitcher.SwitchToMainThread(ct);")); + "await io.github.hatayama.UnityCliLoop.ToolContracts.MainThreadSwitcher.SwitchToMainThread(ct);")); + Assert.That(result.Content, Does.Not.Contain("App.MainThreadSwitcher")); + Assert.That(result.Content, Does.Not.Contain("PlayerLoopTiming.Update")); + } + + [Test] + public void MigrateCSharpSource_WhenCurrentToolContractsSwitcherHasLegacyTiming_PreservesBareReference() + { + // Verifies that partially migrated ToolContracts calls drop legacy timing without adding full qualifiers. + string source = @"using System.Threading; +using System.Threading.Tasks; +using io.github.hatayama.UnityCliLoop.ToolContracts; + +public sealed class MainThreadTool +{ + public async Task RunAsync(CancellationToken ct) + { + await MainThreadSwitcher.SwitchToMainThread(PlayerLoopTiming.Update, ct); + } +}"; + + ThirdPartyToolMigrationContentResult result = + ThirdPartyToolMigrationRules.MigrateCSharpSource(source); + + Assert.That(result.Changed, Is.True); + Assert.That(result.Content, Does.Contain( + "await MainThreadSwitcher.SwitchToMainThread(ct);")); + Assert.That(result.Content, Does.Not.Contain( + "io.github.hatayama.UnityCliLoop.ToolContracts.MainThreadSwitcher")); Assert.That(result.Content, Does.Not.Contain("PlayerLoopTiming.Update")); } @@ -1621,10 +1851,12 @@ public Task RunAsync(PlayerLoopTiming loop) hasLegacyAssemblySource: true, hasAssemblyScopedCurrentToolContractsUsing: false, hasAssemblyScopedCurrentApplicationUsing: false, + hasAssemblyScopedCurrentDomainUsing: false, hasAssemblyScopedCurrentFirstPartyToolsUsing: false, legacyAssemblyAliases: System.Array.Empty(), legacyAssemblyToolInfoAliases: System.Array.Empty(), currentApplicationAssemblyAliases: System.Array.Empty(), + currentDomainAssemblyAliases: System.Array.Empty(), currentFirstPartyToolsAssemblyAliases: System.Array.Empty(), assemblyDeclaredTypeNames: new[] { "MainThreadSwitcher" }); @@ -3677,10 +3909,12 @@ public void MigrateCSharpSourceForLegacyAssembly_WhenFileReliesOnGlobalUsing_Rew hasLegacyAssemblySource: true, hasAssemblyScopedCurrentToolContractsUsing: false, hasAssemblyScopedCurrentApplicationUsing: false, + hasAssemblyScopedCurrentDomainUsing: false, hasAssemblyScopedCurrentFirstPartyToolsUsing: false, legacyAssemblyAliases: System.Array.Empty(), legacyAssemblyToolInfoAliases: System.Array.Empty(), currentApplicationAssemblyAliases: System.Array.Empty(), + currentDomainAssemblyAliases: System.Array.Empty(), currentFirstPartyToolsAssemblyAliases: System.Array.Empty(), assemblyDeclaredTypeNames: System.Array.Empty()); @@ -3701,10 +3935,12 @@ public void MigrateCSharpSourceForLegacyAssembly_WhenFileReliesOnGlobalUsing_Rew hasLegacyAssemblySource: true, hasAssemblyScopedCurrentToolContractsUsing: false, hasAssemblyScopedCurrentApplicationUsing: false, + hasAssemblyScopedCurrentDomainUsing: false, hasAssemblyScopedCurrentFirstPartyToolsUsing: false, legacyAssemblyAliases: System.Array.Empty(), legacyAssemblyToolInfoAliases: System.Array.Empty(), currentApplicationAssemblyAliases: System.Array.Empty(), + currentDomainAssemblyAliases: System.Array.Empty(), currentFirstPartyToolsAssemblyAliases: System.Array.Empty(), assemblyDeclaredTypeNames: System.Array.Empty()); @@ -3954,9 +4190,9 @@ public void MigrateAsmdefSource_WhenCurrentApplicationGuidAlreadyExists_DoesNotA } [Test] - public void MigrateAsmdefSource_WhenManualRegistrationIsUsed_RewritesToToolContractsReference() + public void MigrateAsmdefSource_WhenManualRegistrationIsUsed_AddsApplicationReference() { - // Verifies that migrated manual registration code references the public ToolContracts assembly. + // Verifies that manual registration code keeps the Application assembly when requested. string source = @"{ ""name"": ""MyCompany.Tools.Editor"", ""references"": [ @@ -3975,14 +4211,14 @@ public void MigrateAsmdefSource_WhenManualRegistrationIsUsed_RewritesToToolContr Assert.That(result.Changed, Is.True); Assert.That(result.Content, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(result.Content, Does.Not.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); + Assert.That(result.Content, Does.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); Assert.That(result.Content, Does.Not.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); } [Test] - public void MigrateAsmdefSource_WhenDomainMetadataIsUsed_RewritesToToolContractsReference() + public void MigrateAsmdefSource_WhenDomainMetadataIsUsed_AddsDomainReference() { - // Verifies that ToolInfo-only helper assemblies can resolve metadata through ToolContracts. + // Verifies that domain metadata code keeps the Domain assembly when requested. string source = @"{ ""name"": ""MyCompany.Tools.Editor"", ""references"": [ @@ -4001,14 +4237,14 @@ public void MigrateAsmdefSource_WhenDomainMetadataIsUsed_RewritesToToolContracts Assert.That(result.Changed, Is.True); Assert.That(result.Content, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(result.Content, Does.Not.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); + Assert.That(result.Content, Does.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); Assert.That(result.Content, Does.Not.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); } [Test] - public void MigrateAsmdefSource_WhenCurrentDomainMetadataRequiresReference_AddsToolContractsReference() + public void MigrateAsmdefSource_WhenCurrentDomainMetadataRequiresReference_AddsDomainReference() { - // Verifies that direct V3 Domain consumers are redirected through ToolContracts access. + // Verifies that direct V3 Domain consumers receive the Domain assembly reference. string source = @"{ ""name"": ""MyCompany.Tools.Editor"", ""references"": [] @@ -4024,8 +4260,8 @@ public void MigrateAsmdefSource_WhenCurrentDomainMetadataRequiresReference_AddsT requiresFirstPartyScreenshotReference: false); Assert.That(result.Changed, Is.True); - Assert.That(result.Content, Does.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); - Assert.That(result.Content, Does.Not.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); + Assert.That(result.Content, Does.Not.Contain("GUID:fc3fd32eddbee40e39c2d76dc184957b")); + Assert.That(result.Content, Does.Contain("GUID:5c4588558a3624eacbce0f50007cf1eb")); Assert.That(result.Content, Does.Not.Contain("GUID:214998e563c124e8a88199b2dd1f522d")); } diff --git a/Assets/Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs b/Assets/Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs index cd97a7b318..6e09233f74 100644 --- a/Assets/Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs +++ b/Assets/Tests/Editor/ThirdPartyToolMigrationWizardWindowTests.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using UnityEditor; using UnityEngine; +using UnityEngine.UIElements; using io.github.hatayama.UnityCliLoop.Domain; using io.github.hatayama.UnityCliLoop.Presentation; @@ -180,6 +181,125 @@ public void GetMigrationSkillButtonText_ReturnsExpectedLabel( Assert.That(label, Is.EqualTo(expectedLabel)); } + [Test] + public void GetMigrationSkillPromptText_ReturnsActionablePrompt() + { + // Verifies that the wizard provides a copyable prompt for asking an AI agent to run the skill. + string prompt = ThirdPartyToolMigrationWizardWindow.GetMigrationSkillPromptText(); + + Assert.That(prompt, Does.Contain("v3-cli-invocation-migration")); + Assert.That(prompt, Does.Contain("SKILL.md, Markdown, POSIX shell scripts, and PowerShell scripts")); + Assert.That(prompt, Does.Contain("Do not change C# snippets")); + Assert.That(prompt, Does.Contain("summarize changed files")); + } + + [Test] + public void GetMigrationSkillPromptCopyButtonText_ReturnsClearLabel() + { + // Verifies that the copy button label describes the clipboard action. + string label = ThirdPartyToolMigrationWizardWindow.GetMigrationSkillPromptCopyButtonText(); + + Assert.That(label, Is.EqualTo("Copy AI Prompt")); + } + + [Test] + public void CopyMigrationSkillPromptToClipboard_WritesPrompt() + { + // Verifies that the copy action places the AI prompt on the system clipboard. + string originalClipboard = EditorGUIUtility.systemCopyBuffer; + try + { + ThirdPartyToolMigrationWizardWindow.CopyMigrationSkillPromptToClipboard(); + + Assert.That( + EditorGUIUtility.systemCopyBuffer, + Is.EqualTo(ThirdPartyToolMigrationWizardWindow.GetMigrationSkillPromptText())); + } + finally + { + EditorGUIUtility.systemCopyBuffer = originalClipboard; + } + } + + [Test] + public void Create_WhenMigrationSkillSectionExists_PlacesPromptAfterInstallButton() + { + // Verifies that the AI prompt is shown below the migration skill install action. + VisualElement root = new(); + ThirdPartyToolMigrationWizardView.Create( + root, + () => { }, + () => { }, + _ => { }, + () => { }, + () => { }); + + Button installButton = root.Query