diff --git a/gui/src/main/java/com/devonfw/ide/gui/core/context/GuiStateManager.java b/gui/src/main/java/com/devonfw/ide/gui/core/context/GuiStateManager.java index e166ffb5c8..5e3e0ca941 100644 --- a/gui/src/main/java/com/devonfw/ide/gui/core/context/GuiStateManager.java +++ b/gui/src/main/java/com/devonfw/ide/gui/core/context/GuiStateManager.java @@ -17,6 +17,8 @@ import com.devonfw.ide.gui.core.factory.TabFactory; import com.devonfw.ide.gui.core.service.CommandletService; import com.devonfw.ide.gui.core.service.NlsService; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.context.IdeContextConsole; import com.devonfw.tools.ide.context.IdeStartContextImpl; import com.devonfw.tools.ide.log.IdeLogLevel; import com.devonfw.tools.ide.log.IdeLogListenerBuffer; @@ -62,23 +64,41 @@ public class GuiStateManager { /** + * Creates the state manager for the given root directory, deriving the {@link NlsService} context from the GUI's own start context. + * * @param ideRoot the root directory of IDEasy. If null, the IDE_ROOT environment variable is used. */ public GuiStateManager(String ideRoot) { + // production derives the NlsService context from the GUI start context (which resolves the real user configuration); tests inject an isolated context + this(ideRoot, null); + } + + /** + * Creates the state manager for the given root directory using the supplied {@link NlsService} context. + * + * @param ideRoot the root directory of IDEasy. If null, the IDE_ROOT environment variable is used. + * @param nlsContext the {@link IdeContext} handed to the {@link NlsService}, or {@code null} to derive one from the GUI start context (production default). + * Injecting an isolated context (for example an {@link com.devonfw.tools.ide.context.IdeTestContext}) keeps the GUI test independent from the real + * user home and the {@code IDE_OPTIONS} environment variable. + */ + public GuiStateManager(String ideRoot, IdeContext nlsContext) { + this.ideRootDir = Path.of(ideRoot != null ? ideRoot : System.getenv(IdeVariables.IDE_ROOT.getName())); //This specific ordering needs to be kept + final IdeLogListenerBuffer buffer = new IdeLogListenerBuffer(); + IdeLogLevel logLevel = IdeLogLevel.DEBUG; + startContext = new IdeStartContextImpl(logLevel, buffer); + this.projectManager = new ProjectManager(ideRootDir); this.taskManager = new TaskManager(); this.eventBus = new GuiEventBus(); - this.nlsService = new NlsService(null); + // inject an application-level context so NlsService never has to create one itself; binding the default to startContext keeps the GUI logging context + IdeContext resolvedNlsContext = (nlsContext != null) ? nlsContext : new IdeContextConsole(startContext); + this.nlsService = new NlsService(resolvedNlsContext, null); this.commandletService = new CommandletService(this); this.tabFactory = new TabFactory(this); - - final IdeLogListenerBuffer buffer = new IdeLogListenerBuffer(); - IdeLogLevel logLevel = IdeLogLevel.DEBUG; - startContext = new IdeStartContextImpl(logLevel, buffer); } /** diff --git a/gui/src/main/java/com/devonfw/ide/gui/core/service/NlsService.java b/gui/src/main/java/com/devonfw/ide/gui/core/service/NlsService.java index 570347eed9..a11b7d22b1 100644 --- a/gui/src/main/java/com/devonfw/ide/gui/core/service/NlsService.java +++ b/gui/src/main/java/com/devonfw/ide/gui/core/service/NlsService.java @@ -23,7 +23,6 @@ import org.slf4j.LoggerFactory; import com.devonfw.tools.ide.context.IdeContext; -import com.devonfw.tools.ide.context.IdeContextConsole; import com.devonfw.tools.ide.environment.EnvironmentVariables; import com.devonfw.tools.ide.environment.EnvironmentVariablesType; @@ -59,32 +58,17 @@ public class NlsService { private volatile EnvironmentVariables userEnvironmentVariables; - /** - * The {@link IdeContext} used to resolve the user environment variables. If {@code null}, an {@link IdeContextConsole} is created lazily on first - * persistence, so that merely constructing the service has no side effects. - */ + /** The {@link IdeContext} used to resolve the user environment variables. */ private final IdeContext context; private static final Pattern USER_LANG_PATTERN = Pattern.compile("-Duser\\.language=(\\S*)"); - /** - * Creates the service and initializes it with the given locale. The user environment variables are resolved from an {@link IdeContextConsole} that is created - * lazily on first persistence, so that merely constructing the service has no side effects. - * - * @param locale the preferred locale, or {@code null} to use persisted or default locale. - */ - public NlsService(Locale locale) { - - this.context = null; - initialize(locale); - } - /** * Creates the service using the given {@link IdeContext} to resolve the user configuration and initializes it with the given locale. *

- * This constructor enables "design for testability" (see {@code documentation/contributing/junit-testing.adoc}): by injecting an {@link IdeContext} (for - * example an {@link com.devonfw.tools.ide.context.IdeTestContext}) the service can be isolated from the real user home and the environment of the machine - * running the test. + * The {@link IdeContext} is always injected by the caller (see the {@code documentation/contributing/junit-testing.adoc} guidance): production code + * passes an application-level context that resolves the real user configuration, while tests pass an isolated {@code IdeTestContext} to stay independent + * from the machine's user home and the {@code IDE_OPTIONS} environment variable. * * @param context the {@link IdeContext} to use for resolving the user environment variables. * @param locale the preferred locale, or {@code null} to use persisted or default locale. @@ -388,9 +372,7 @@ private EnvironmentVariables getUserEnvironmentVariables() { private EnvironmentVariables createUserEnvironmentVariables() { - // if no context was injected (see NlsService(Locale)) we create a console context lazily here, so constructing the service has no side effects - IdeContext context = (this.context != null) ? this.context : new IdeContextConsole(); - EnvironmentVariables userVariables = context.getVariables().getByType(EnvironmentVariablesType.USER); + EnvironmentVariables userVariables = this.context.getVariables().getByType(EnvironmentVariablesType.USER); if (userVariables == null) { throw new IllegalStateException("Failed to resolve USER environment variables from IDE context"); } diff --git a/gui/src/test/java/com/devonfw/ide/gui/AppBaseTest.java b/gui/src/test/java/com/devonfw/ide/gui/AppBaseTest.java index 139cb3e4c1..551bdde653 100644 --- a/gui/src/test/java/com/devonfw/ide/gui/AppBaseTest.java +++ b/gui/src/test/java/com/devonfw/ide/gui/AppBaseTest.java @@ -20,6 +20,7 @@ import org.assertj.core.data.Offset; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import com.devonfw.ide.gui.core.context.GuiStateManager; import com.devonfw.ide.gui.core.context.TaskManager; @@ -28,6 +29,8 @@ import com.devonfw.ide.gui.core.mainwindow.MainWindowViewModel; import com.devonfw.ide.gui.core.progress.ProgressBarTask; import com.devonfw.ide.gui.core.progress.taskwindow.TaskOverviewWindow; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.environment.IdeSystemTestImpl; /** * Basic UI Test for the main screen @@ -58,13 +61,22 @@ public class AppBaseTest extends UIBasedApplicationTest { private MainWindowViewModel viewModel; + /** Throw-away user home for the isolated NlsService context so the persisted locale never rewrites the real {@code ~/.ide/ide.properties}. */ + @TempDir + private static Path nlsUserHome; + @Override public void start(Stage stage) { - //We simply use project-0 here, as we only use the ide root for this test + // We simply use project-0 here, as we only use the ide root for this test setTestContext("testProject", "project-0"); - this.guiStateManager = new GuiStateManager(getTestContext().getIdeRoot().toString()); + // isolate the NlsService from the developer's real user home and the IDE_OPTIONS environment variable: persisting the locale must not rewrite the + // real ~/.ide/ide.properties, so point an isolated IdeTestContext at a throw-away home and hand it to the NlsService via the state manager + final IdeTestContext nlsContext = new IdeTestContext(); + nlsContext.setSystem(new IdeSystemTestImpl()); + nlsContext.setUserHome(nlsUserHome); + this.guiStateManager = new GuiStateManager(getTestContext().getIdeRoot().toString(), nlsContext); this.guiStateManager.getNlsService().setLocale(Locale.ENGLISH); this.taskManager = guiStateManager.getTaskManager(); this.viewModel = new MainWindowViewModel(guiStateManager); diff --git a/gui/src/test/java/com/devonfw/ide/gui/core/mainwindow/console/ConsolePanelTest.java b/gui/src/test/java/com/devonfw/ide/gui/core/mainwindow/console/ConsolePanelTest.java index 50d101487f..dc0c24021a 100644 --- a/gui/src/test/java/com/devonfw/ide/gui/core/mainwindow/console/ConsolePanelTest.java +++ b/gui/src/test/java/com/devonfw/ide/gui/core/mainwindow/console/ConsolePanelTest.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.net.URL; +import java.nio.file.Path; import java.util.List; import java.util.Locale; @@ -15,15 +16,22 @@ import javafx.stage.Stage; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import com.devonfw.ide.gui.UIBasedApplicationTest; import com.devonfw.ide.gui.core.context.IdeGuiLogListener; import com.devonfw.ide.gui.core.event.GuiEventBus; import com.devonfw.ide.gui.core.service.NlsService; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.environment.IdeSystemTestImpl; import com.devonfw.tools.ide.log.IdeLogLevel; class ConsolePanelTest extends UIBasedApplicationTest { + /** Throw-away user home for the isolated NlsService context so a persisted locale never rewrites the real {@code ~/.ide/ide.properties}. */ + @TempDir + private static Path nlsUserHome; + private ConsoleViewModel consoleViewModel; private GuiEventBus eventBus; @@ -34,7 +42,11 @@ public void start(Stage stage) throws IOException { URL consoleViewUrl = getClass().getResource("console.fxml"); assertThat(consoleViewUrl).as("Cannot resolve console UI FXML resource!").isNotNull(); - NlsService nlsService = new NlsService(Locale.ENGLISH); + // isolate the NlsService from the developer's real user home and the IDE_OPTIONS environment variable so constructing it has no side effects + final IdeTestContext nlsContext = new IdeTestContext(); + nlsContext.setSystem(new IdeSystemTestImpl()); + nlsContext.setUserHome(nlsUserHome); + NlsService nlsService = new NlsService(nlsContext, Locale.ENGLISH); this.eventBus = new GuiEventBus(); FXMLLoader fxmlLoader = new FXMLLoader(consoleViewUrl); diff --git a/gui/src/test/java/com/devonfw/ide/gui/core/service/NlsServiceTest.java b/gui/src/test/java/com/devonfw/ide/gui/core/service/NlsServiceTest.java index 3385dcdf78..83fa1ec6dc 100644 --- a/gui/src/test/java/com/devonfw/ide/gui/core/service/NlsServiceTest.java +++ b/gui/src/test/java/com/devonfw/ide/gui/core/service/NlsServiceTest.java @@ -87,7 +87,7 @@ private NlsService newService(Locale locale) { @MethodSource("testGetInstanceWithLocaleProvider") public void testGetInstanceWithLocale(Locale systemLocale, Locale providedLocale, Locale expectedServiceLocale) { Locale.setDefault(systemLocale); - NlsService service = new NlsService(providedLocale); + NlsService service = newService(providedLocale); assertThat(service.getLocale()).isEqualTo(expectedServiceLocale); assertThat(service.getResourceBundle()).isNotNull();