diff --git a/tests/ClaudeDo.Ui.Tests/TestSetup.cs b/tests/ClaudeDo.Ui.Tests/TestSetup.cs index 79ef3e65..90277d81 100644 --- a/tests/ClaudeDo.Ui.Tests/TestSetup.cs +++ b/tests/ClaudeDo.Ui.Tests/TestSetup.cs @@ -1,5 +1,12 @@ using System.Runtime.CompilerServices; +using ClaudeDo.Localization; +using ClaudeDo.Ui.Localization; using ClaudeDo.Ui.Services; +using Xunit; + +// Loc.Current (src/ClaudeDo.Ui/Localization/Loc.cs) is a process-wide mutable static that many +// ViewModel test constructors assign — parallel collections race on which localizer is active. +[assembly: CollectionBehavior(DisableTestParallelization = true)] namespace ClaudeDo.Ui.Tests; @@ -17,4 +24,17 @@ internal static class TestSetup var path = Path.Combine(Path.GetTempPath(), "claudedo-ui-tests", $"operation-timing-{Environment.ProcessId}.ndjson"); OperationTiming.Shared = new OperationTiming(path); } + + // Installs the real localizer before any test runs, so a class that reads Loc.T/Loc.Current + // without setting it up itself (e.g. via a ViewModel error message) doesn't fall back to + // Loc's key-echo default depending on which test class happens to run first. + [ModuleInitializer] + internal static void InstallRealLocalizer() + { + var dir = AppContext.BaseDirectory; + while (dir is not null && !Directory.Exists(Path.Combine(dir, "src", "ClaudeDo.Localization", "locales"))) + dir = Path.GetDirectoryName(dir); + Loc.Current = new Localizer( + LocaleStore.Load(Path.Combine(dir!, "src", "ClaudeDo.Localization", "locales")), "en"); + } } diff --git a/tests/ClaudeDo.Ui.Tests/ViewModels/AgentConfigEditorViewModelTests.cs b/tests/ClaudeDo.Ui.Tests/ViewModels/AgentConfigEditorViewModelTests.cs index 12101a51..b6cdbdc6 100644 --- a/tests/ClaudeDo.Ui.Tests/ViewModels/AgentConfigEditorViewModelTests.cs +++ b/tests/ClaudeDo.Ui.Tests/ViewModels/AgentConfigEditorViewModelTests.cs @@ -1,4 +1,5 @@ using ClaudeDo.Data.Models; +using ClaudeDo.Localization; using ClaudeDo.Ui.Localization; using ClaudeDo.Ui.Services; using ClaudeDo.Ui.ViewModels.Agent; @@ -9,6 +10,18 @@ namespace ClaudeDo.Ui.Tests.ViewModels; public class AgentConfigEditorViewModelTests { + public AgentConfigEditorViewModelTests() + { + // Loc.T(...) is read below without installing a localizer via a ViewModel constructor + // (unlike its sibling test classes) — install one directly so this class doesn't depend + // on whichever neighbour test happened to run first and set Loc.Current. + var dir = AppContext.BaseDirectory; + while (dir is not null && !Directory.Exists(Path.Combine(dir, "src", "ClaudeDo.Localization", "locales"))) + dir = Path.GetDirectoryName(dir); + Loc.Current = new Localizer( + LocaleStore.Load(Path.Combine(dir!, "src", "ClaudeDo.Localization", "locales")), "en"); + } + private sealed class FakeWorker : StubWorkerClient { public AppSettingsDto? App; diff --git a/tests/ClaudeDo.Worker.Tests/TestSetup.cs b/tests/ClaudeDo.Worker.Tests/TestSetup.cs index ef2daba8..64564ee7 100644 --- a/tests/ClaudeDo.Worker.Tests/TestSetup.cs +++ b/tests/ClaudeDo.Worker.Tests/TestSetup.cs @@ -1,4 +1,6 @@ using System.Runtime.CompilerServices; +using ClaudeDo.Localization; +using ClaudeDo.Ui.Localization; using ClaudeDo.Ui.Services; namespace ClaudeDo.Worker.Tests; @@ -16,4 +18,20 @@ internal static class TestSetup var path = Path.Combine(Path.GetTempPath(), "claudedo-worker-tests", $"operation-timing-{Environment.ProcessId}.ndjson"); OperationTiming.Shared = new OperationTiming(path); } + + // Installs the real localizer before any test runs, mirroring ClaudeDo.Ui.Tests' TestSetup, + // so a UiVm test that reads Loc.T without setting up its own localizer doesn't depend on + // ambient state left by whichever test class happened to run first. Deliberately does NOT + // also disable test parallelization here (unlike Ui.Tests) — measured 14m49s -> 18m46s + // (+27%) on this project's real-SQLite/git suite, too costly for a test-only ordering issue + // that the module initializer alone already closes. + [ModuleInitializer] + internal static void InstallRealLocalizer() + { + var dir = AppContext.BaseDirectory; + while (dir is not null && !Directory.Exists(Path.Combine(dir, "src", "ClaudeDo.Localization", "locales"))) + dir = Path.GetDirectoryName(dir); + Loc.Current = new Localizer( + LocaleStore.Load(Path.Combine(dir!, "src", "ClaudeDo.Localization", "locales")), "en"); + } }