From b0b9d9b4f54853e196e5d26c7579c084db04cc8b Mon Sep 17 00:00:00 2001 From: extio-de Date: Sun, 28 Dec 2025 13:24:02 +0100 Subject: [PATCH] feat(theme): enhance theme management with storage capabilities and add unit tests --- .../game_engine/renderer/ThemeManager.java | 8 + .../renderer/g2d/G2DAutoConfiguration.java | 7 +- .../renderer/g2d/theme/G2DThemeManager.java | 112 ++++++++- .../renderer/g2d/theme/HSBColor.java | 9 +- .../game_engine/renderer/g2d/theme/Theme.java | 64 +++-- .../de/extio/game_engine/demo/DemoModule.java | 11 - .../g2d/theme/G2DThemeManagerTest.java | 222 ++++++++++++++++++ 7 files changed, 398 insertions(+), 35 deletions(-) create mode 100644 src/test/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManagerTest.java diff --git a/src/main/java/de/extio/game_engine/renderer/ThemeManager.java b/src/main/java/de/extio/game_engine/renderer/ThemeManager.java index 0b7d419..251edc7 100644 --- a/src/main/java/de/extio/game_engine/renderer/ThemeManager.java +++ b/src/main/java/de/extio/game_engine/renderer/ThemeManager.java @@ -1,12 +1,20 @@ package de.extio.game_engine.renderer; import java.util.List; +import java.util.Optional; +import de.extio.game_engine.resource.StaticResource; import de.extio.game_engine.renderer.g2d.theme.Theme; public interface ThemeManager { List getAvailableThemeNames(); + + Optional loadThemeFromStorage(String themeName); + + void saveThemeToStorage(Theme theme); + + Optional loadThemeFromStaticResource(StaticResource themeResource); Theme getCurrentTheme(); diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/G2DAutoConfiguration.java b/src/main/java/de/extio/game_engine/renderer/g2d/G2DAutoConfiguration.java index 1353bdf..0d884d3 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/G2DAutoConfiguration.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/G2DAutoConfiguration.java @@ -5,7 +5,6 @@ import java.util.Map; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Value; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -26,6 +25,7 @@ import de.extio.game_engine.renderer.g2d.theme.Theme; import de.extio.game_engine.renderer.g2d.theme.UrbanThemeFactoryBean; import de.extio.game_engine.renderer.g2d.theme.VintageThemeFactoryBean; import de.extio.game_engine.resource.StaticResourceService; +import de.extio.game_engine.storage.StorageService; @AutoConfiguration @ConditionalOnProperty(name = "game-engine.renderer.enabled", havingValue = "true", matchIfMissing = true) @@ -113,10 +113,11 @@ public class G2DAutoConfiguration { @Bean G2DThemeManager g2dThemeManager( final StaticResourceService staticResourceService, + final StorageService storageService, final List patternRendererList, final Map themes, - @Qualifier("spacecraftTheme") final Theme defaultTheme) { - return new G2DThemeManager(staticResourceService, patternRendererList, themes, defaultTheme); + @Value("${game-engine.renderer.default-theme:urbanTheme}") final String defaultThemeName) { + return new G2DThemeManager(staticResourceService, storageService, patternRendererList, themes, defaultThemeName); } } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManager.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManager.java index a408905..45041b4 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManager.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManager.java @@ -2,7 +2,9 @@ package de.extio.game_engine.renderer.g2d.theme; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.Objects; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -11,15 +13,21 @@ import org.slf4j.LoggerFactory; import de.extio.game_engine.renderer.ThemeManager; import de.extio.game_engine.renderer.g2d.G2DRenderer; +import de.extio.game_engine.resource.StaticResource; import de.extio.game_engine.resource.StaticResourceService; +import de.extio.game_engine.storage.StorageService; public class G2DThemeManager implements ThemeManager { private static final Logger LOGGER = LoggerFactory.getLogger(G2DThemeManager.class); + private static final List THEME_STORAGE_PATH = List.of("gameEngine", "themes"); + private static final String THEME_STORAGE_NAME = "lastTheme"; + private G2DRenderer g2dRenderer; private final StaticResourceService staticResourceService; + private final StorageService storageService; private final Map patternRenderers; private final Map themes; @@ -29,20 +37,48 @@ public class G2DThemeManager implements ThemeManager { public G2DThemeManager( final StaticResourceService staticResourceService, + final StorageService storageService, final List patternRendererList, final Map themes, - final Theme defaultTheme) { + final String defaultThemeName) { this.staticResourceService = staticResourceService; + this.storageService = storageService; this.patternRenderers = patternRendererList.stream() .collect(Collectors.toMap( renderer -> renderer.getClass().getSimpleName().substring(0, 1).toLowerCase() + renderer.getClass().getSimpleName().substring(1), Function.identity())); - this.themes = Objects.requireNonNull(themes, "themes cannot be null"); - this.defaultTheme = Objects.requireNonNull(defaultTheme, "defaultTheme cannot be null"); + this.themes = new ConcurrentHashMap<>(Objects.requireNonNull(themes, "themes cannot be null")); + this.defaultTheme = Objects.requireNonNull(this.themes.get(defaultThemeName), "defaultTheme cannot be null"); LOGGER.info("G2DThemeManager initialized with {} pattern renderers: {}", this.patternRenderers.size(), this.patternRenderers.keySet()); LOGGER.info("G2DThemeManager initialized with {} themes: {}", this.themes.size(), this.themes.keySet()); this.currentTheme = this.defaultTheme; + this.loadLastUsedTheme(); + } + + private void registerTheme(final Theme theme) { + if (theme == null || theme.getName() == null || theme.getName().isBlank()) { + return; + } + this.themes.put(theme.getName(), theme); + } + + private void loadLastUsedTheme() { + if (this.storageService == null) { + return; + } + try { + final Optional persistedTheme = this.storageService.loadByPath(Theme.class, THEME_STORAGE_PATH, THEME_STORAGE_NAME); + if (persistedTheme.isPresent()) { + this.currentTheme = persistedTheme.get(); + this.registerTheme(this.currentTheme); + LOGGER.info("Loaded last used theme: {}", this.currentTheme.getName()); + return; + } + } + catch (final Exception e) { + LOGGER.warn("Could not load last used theme. Using default.", e); + } } @Override @@ -54,6 +90,44 @@ public class G2DThemeManager implements ThemeManager { .toList(); } + @Override + public Optional loadThemeFromStorage(final String themeName) { + if (themeName == null || themeName.isBlank()) { + throw new IllegalArgumentException("themeName cannot be null/blank"); + } + if (this.storageService == null) { + return Optional.empty(); + } + final Optional loaded = this.storageService.loadByPath(Theme.class, THEME_STORAGE_PATH, themeName); + loaded.ifPresent(this::registerTheme); + return loaded; + } + + @Override + public void saveThemeToStorage(final Theme theme) { + if (theme == null) { + throw new IllegalArgumentException("theme cannot be null"); + } + if (theme.getName() == null || theme.getName().isBlank()) { + throw new IllegalArgumentException("theme.name cannot be null/blank"); + } + if (this.storageService == null) { + throw new IllegalStateException("storageService not available"); + } + this.storageService.store(THEME_STORAGE_PATH, theme.getName(), theme); + } + + @Override + public Optional loadThemeFromStaticResource(final StaticResource themeResource) { + Objects.requireNonNull(themeResource, "themeResource"); + if (this.staticResourceService == null) { + return Optional.empty(); + } + final Optional loaded = this.staticResourceService.loadByPath(Theme.class, themeResource); + loaded.ifPresent(this::registerTheme); + return loaded; + } + @Override public void setCurrentTheme(final String themeName) { if (themeName == null || themeName.isBlank()) { @@ -94,14 +168,40 @@ public class G2DThemeManager implements ThemeManager { } this.currentTheme = theme; + this.registerTheme(theme); LOGGER.info("Theme switched to: {}", theme.getName()); + this.persistLastUsedTheme(theme); - this.g2dRenderer.reset(); + if (this.g2dRenderer != null) { + this.g2dRenderer.reset(); + } + } + + private void persistLastUsedTheme(final Theme theme) { + if (this.storageService == null) { + return; + } + try { + this.storageService.store(THEME_STORAGE_PATH, THEME_STORAGE_NAME, theme); + } + catch (final Exception e) { + LOGGER.warn("Could not persist last used theme", e); + } } public Theme loadTheme(final String themeName) { - Objects.requireNonNull(this.staticResourceService, "staticResourceService"); - LOGGER.warn("Theme loading from StaticResourceService not yet implemented. Requested theme: {}. Using default theme.", themeName); + try { + final Optional fromStorage = this.loadThemeFromStorage(themeName); + if (fromStorage.isPresent()) { + LOGGER.info("Loaded theme '{}' from storage", themeName); + return fromStorage.get(); + } + } + catch (final Exception e) { + LOGGER.warn("Could not load theme '{}' from storage. Using default.", themeName, e); + } + + LOGGER.warn("Theme '{}' not found in storage. Using default theme.", themeName); return this.defaultTheme; } diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/HSBColor.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/HSBColor.java index d3d0cc2..8010622 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/theme/HSBColor.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/HSBColor.java @@ -3,6 +3,9 @@ package de.extio.game_engine.renderer.g2d.theme; import java.awt.Color; import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * Represents a color in HSB (Hue, Saturation, Brightness) color space. * This allows for easier runtime color manipulation (e.g., brightening on hover). @@ -20,7 +23,11 @@ public final class HSBColor { * @param saturation Saturation component (0.0 to 1.0) * @param brightness Brightness component (0.0 to 1.0) */ - public HSBColor(final float hue, final float saturation, final float brightness) { + @JsonCreator + public HSBColor( + @JsonProperty("hue") final float hue, + @JsonProperty("saturation") final float saturation, + @JsonProperty("brightness") final float brightness) { this.hue = clamp(hue); this.saturation = clamp(saturation); this.brightness = clamp(brightness); diff --git a/src/main/java/de/extio/game_engine/renderer/g2d/theme/Theme.java b/src/main/java/de/extio/game_engine/renderer/g2d/theme/Theme.java index 672bd3e..9a7af99 100644 --- a/src/main/java/de/extio/game_engine/renderer/g2d/theme/Theme.java +++ b/src/main/java/de/extio/game_engine/renderer/g2d/theme/Theme.java @@ -1,5 +1,8 @@ package de.extio.game_engine.renderer.g2d.theme; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * Represents a complete UI theme with all color definitions and pattern renderer. * Themes are element-based rather than component-based, allowing consistent styling across different components. @@ -33,21 +36,54 @@ public class Theme { private final float hoverBrightnessAdjustment; private final float pressedBrightnessAdjustment; + @JsonCreator + public Theme( + @JsonProperty("name") final String name, + @JsonProperty("patternRendererName") final String patternRendererName, + @JsonProperty("borderOuter") final HSBColor borderOuter, + @JsonProperty("borderInner") final HSBColor borderInner, + @JsonProperty("borderInnerDisabled") final HSBColor borderInnerDisabled, + @JsonProperty("backgroundNormal") final HSBColor backgroundNormal, + @JsonProperty("backgroundSelected") final HSBColor backgroundSelected, + @JsonProperty("textNormal") final HSBColor textNormal, + @JsonProperty("textDisabled") final HSBColor textDisabled, + @JsonProperty("selectionPrimary") final HSBColor selectionPrimary, + @JsonProperty("selectionSecondary") final HSBColor selectionSecondary, + @JsonProperty("windowBackground") final HSBColor windowBackground, + @JsonProperty("hoverBrightnessAdjustment") final float hoverBrightnessAdjustment, + @JsonProperty("pressedBrightnessAdjustment") final float pressedBrightnessAdjustment) { + this.name = name; + this.patternRendererName = patternRendererName; + this.borderOuter = borderOuter; + this.borderInner = borderInner; + this.borderInnerDisabled = borderInnerDisabled; + this.backgroundNormal = backgroundNormal; + this.backgroundSelected = backgroundSelected; + this.textNormal = textNormal; + this.textDisabled = textDisabled; + this.selectionPrimary = selectionPrimary; + this.selectionSecondary = selectionSecondary; + this.windowBackground = windowBackground; + this.hoverBrightnessAdjustment = hoverBrightnessAdjustment; + this.pressedBrightnessAdjustment = pressedBrightnessAdjustment; + } + private Theme(final Builder builder) { - this.name = builder.name; - this.patternRendererName = builder.patternRendererName; - this.borderOuter = builder.borderOuter; - this.borderInner = builder.borderInner; - this.borderInnerDisabled = builder.borderInnerDisabled; - this.backgroundNormal = builder.backgroundNormal; - this.backgroundSelected = builder.backgroundSelected; - this.textNormal = builder.textNormal; - this.textDisabled = builder.textDisabled; - this.selectionPrimary = builder.selectionPrimary; - this.selectionSecondary = builder.selectionSecondary; - this.windowBackground = builder.windowBackground; - this.hoverBrightnessAdjustment = builder.hoverBrightnessAdjustment; - this.pressedBrightnessAdjustment = builder.pressedBrightnessAdjustment; + this( + builder.name, + builder.patternRendererName, + builder.borderOuter, + builder.borderInner, + builder.borderInnerDisabled, + builder.backgroundNormal, + builder.backgroundSelected, + builder.textNormal, + builder.textDisabled, + builder.selectionPrimary, + builder.selectionSecondary, + builder.windowBackground, + builder.hoverBrightnessAdjustment, + builder.pressedBrightnessAdjustment); } public String getName() { diff --git a/src/test/java/de/extio/game_engine/demo/DemoModule.java b/src/test/java/de/extio/game_engine/demo/DemoModule.java index 55e9775..debbcc7 100644 --- a/src/test/java/de/extio/game_engine/demo/DemoModule.java +++ b/src/test/java/de/extio/game_engine/demo/DemoModule.java @@ -26,7 +26,6 @@ import de.extio.game_engine.renderer.model.event.UiControlEvent; import de.extio.game_engine.renderer.work.RenderingBoPool; import de.extio.game_engine.resource.StaticResource; import de.extio.game_engine.spatial2.model.ImmutableCoordI2; -import de.extio.game_engine.storage.StorageService; public class DemoModule extends AbstractClientModule { @@ -48,9 +47,6 @@ public class DemoModule extends AbstractClientModule { @Autowired private ThemeManager themeManager; - @Autowired - private StorageService storageService; - private Window mainWindow; private Window secondaryWindow; @@ -93,12 +89,6 @@ public class DemoModule extends AbstractClientModule { @Override public void onActivate() { - this.storageService.loadByPath(String.class, List.of("gameEngine"), "theme").ifPresent(themeName -> { - if (themeName != null && !themeName.isBlank()) { - this.themeManager.setCurrentTheme(themeName); - } - }); - final var audioOptions = this.audioController.getAudioOptions(); this.audioMuted = isAudioMuted(audioOptions); @@ -158,7 +148,6 @@ public class DemoModule extends AbstractClientModule { if (themeName != null) { this.audioController.play(new StaticResource(List.of("audio"), "alert0.ogg")); this.themeManager.setCurrentTheme(themeName); - this.storageService.store(List.of("gameEngine"), "theme", themeName); } } } diff --git a/src/test/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManagerTest.java b/src/test/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManagerTest.java new file mode 100644 index 0000000..e597cb8 --- /dev/null +++ b/src/test/java/de/extio/game_engine/renderer/g2d/theme/G2DThemeManagerTest.java @@ -0,0 +1,222 @@ +package de.extio.game_engine.renderer.g2d.theme; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import de.extio.game_engine.resource.StaticResource; +import de.extio.game_engine.resource.StaticResourceService; +import de.extio.game_engine.storage.StorageService; + +public class G2DThemeManagerTest { + + private static Theme theme(final String name) { + return Theme.builder() + .name(name) + .patternRendererName("testPatternRenderer") + .borderOuter(new HSBColor(0.1f, 0.2f, 0.3f)) + .borderInner(new HSBColor(0.1f, 0.2f, 0.3f)) + .borderInnerDisabled(new HSBColor(0.1f, 0.2f, 0.3f)) + .backgroundNormal(new HSBColor(0.1f, 0.2f, 0.3f)) + .backgroundSelected(new HSBColor(0.1f, 0.2f, 0.3f)) + .textNormal(new HSBColor(0.1f, 0.2f, 0.3f)) + .textDisabled(new HSBColor(0.1f, 0.2f, 0.3f)) + .selectionPrimary(new HSBColor(0.1f, 0.2f, 0.3f)) + .selectionSecondary(new HSBColor(0.1f, 0.2f, 0.3f)) + .windowBackground(new HSBColor(0.1f, 0.2f, 0.3f)) + .hoverBrightnessAdjustment(0.25f) + .pressedBrightnessAdjustment(0.40f) + .build(); + } + + @Test + void loadsLastUsedThemeFromStorageOnConstruction() { + final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class); + final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); + final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); + + final Theme defaultTheme = theme("Default"); + final Theme persistedTheme = theme("Persisted"); + + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme"))) + .thenReturn(Optional.of(persistedTheme)); + + final var manager = new G2DThemeManager( + staticResourceService, + storageService, + List.of(patternRenderer), + Map.of("defaultKey", defaultTheme), + "defaultKey"); + + assertEquals("Persisted", manager.getCurrentTheme().getName()); + assertTrue(manager.getAvailableThemeNames().contains("Persisted")); + } + + @Test + void setCurrentThemePersistsLastThemeObject() { + final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class); + final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); + final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); + + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme"))) + .thenReturn(Optional.empty()); + + final Theme defaultTheme = theme("Default"); + final var manager = new G2DThemeManager( + staticResourceService, + storageService, + List.of(patternRenderer), + Map.of("defaultKey", defaultTheme), + "defaultKey"); + + final Theme selected = theme("Selected"); + manager.setCurrentTheme(selected); + + verify(storageService).store(eq(List.of("gameEngine", "themes")), eq("lastTheme"), eq(selected)); + assertTrue(manager.getAvailableThemeNames().contains("Selected")); + } + + @Test + void loadThemeFromStorageRegistersTheme() { + final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class); + final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); + final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); + + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme"))) + .thenReturn(Optional.empty()); + + final Theme defaultTheme = theme("Default"); + final var manager = new G2DThemeManager( + staticResourceService, + storageService, + List.of(patternRenderer), + Map.of("defaultKey", defaultTheme), + "defaultKey"); + + final Theme loaded = theme("FromStorage"); + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("FromStorage"))) + .thenReturn(Optional.of(loaded)); + + final Optional result = manager.loadThemeFromStorage("FromStorage"); + assertTrue(result.isPresent()); + assertEquals("FromStorage", result.get().getName()); + assertTrue(manager.getAvailableThemeNames().contains("FromStorage")); + } + + @Test + void loadThemeFromStaticResourceRegistersTheme() { + final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class); + final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); + final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); + + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme"))) + .thenReturn(Optional.empty()); + + final Theme defaultTheme = theme("Default"); + final var manager = new G2DThemeManager( + staticResourceService, + storageService, + List.of(patternRenderer), + Map.of("defaultKey", defaultTheme), + "defaultKey"); + + final Theme loaded = theme("FromStaticResource"); + final StaticResource resource = new StaticResource(List.of("themes"), "fromStatic.theme"); + when(staticResourceService.loadByPath(eq(Theme.class), eq(resource))) + .thenReturn(Optional.of(loaded)); + + final Optional result = manager.loadThemeFromStaticResource(resource); + assertTrue(result.isPresent()); + assertEquals("FromStaticResource", result.get().getName()); + assertTrue(manager.getAvailableThemeNames().contains("FromStaticResource")); + } + + @Test + void saveThemeToStorageUsesThemeNameAsStorageName() { + final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class); + final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); + final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); + + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme"))) + .thenReturn(Optional.empty()); + + final Theme defaultTheme = theme("Default"); + final var manager = new G2DThemeManager( + staticResourceService, + storageService, + List.of(patternRenderer), + Map.of("defaultKey", defaultTheme), + "defaultKey"); + + final Theme themeToSave = theme("UserTheme"); + manager.saveThemeToStorage(themeToSave); + + verify(storageService).store(eq(List.of("gameEngine", "themes")), eq("UserTheme"), eq(themeToSave)); + } + + @Test + void saveThemeToStorageThrowsIfNoStorageService() { + final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class); + final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); + + final Theme defaultTheme = theme("Default"); + final var manager = new G2DThemeManager( + staticResourceService, + null, + List.of(patternRenderer), + Map.of("defaultKey", defaultTheme), + "defaultKey"); + + assertThrows(IllegalStateException.class, () -> manager.saveThemeToStorage(theme("SomeTheme"))); + } + + @Test + void loadThemeFromStorageReturnsEmptyWhenStorageNotAvailable() { + final StaticResourceService staticResourceService = org.mockito.Mockito.mock(StaticResourceService.class); + final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); + + final Theme defaultTheme = theme("Default"); + final var manager = new G2DThemeManager( + staticResourceService, + null, + List.of(patternRenderer), + Map.of("defaultKey", defaultTheme), + "defaultKey"); + + final Optional loaded = manager.loadThemeFromStorage("Anything"); + assertFalse(loaded.isPresent()); + } + + @Test + void loadThemeFromStaticResourceReturnsEmptyWhenStaticResourceServiceNotAvailable() { + final StorageService storageService = org.mockito.Mockito.mock(StorageService.class); + final PatternRenderer patternRenderer = org.mockito.Mockito.mock(PatternRenderer.class); + + when(storageService.loadByPath(eq(Theme.class), eq(List.of("gameEngine", "themes")), eq("lastTheme"))) + .thenReturn(Optional.empty()); + + final Theme defaultTheme = theme("Default"); + final var manager = new G2DThemeManager( + null, + storageService, + List.of(patternRenderer), + Map.of("defaultKey", defaultTheme), + "defaultKey"); + + final Optional loaded = manager.loadThemeFromStaticResource(new StaticResource(List.of("themes"), "x")); + assertFalse(loaded.isPresent()); + verify(storageService, never()).store(any(), any(), any()); + } +}