container = new AppContainer(); $themeConfig = $this->createMock(ThemeConfigGateway::class); $themeConfig->method('all')->willReturn(['light' => 'Light', 'dark' => 'Dark']); $this->container->set(ThemeConfigGateway::class, fn () => $themeConfig); $this->setAppSettings([]); $this->pushAppContainer($this->container); } protected function tearDown(): void { $this->restoreAppContainer(); $_SESSION = []; } // -- appDefaultTheme priority chain -- public function testDefaultThemeFallsBackToLight(): void { $this->assertSame('light', appDefaultTheme()); } public function testDefaultThemeReadsAppSetting(): void { $this->setAppSettings(['app_theme' => 'dark']); $this->assertSame('dark', appDefaultTheme()); } public function testDefaultThemeTenantOverridesAppSetting(): void { $this->setAppSettings(['app_theme' => 'light']); $_SESSION['current_tenant'] = ['default_theme' => 'dark']; $this->assertSame('dark', appDefaultTheme()); } public function testDefaultThemeIgnoresInvalidTenantTheme(): void { $_SESSION['current_tenant'] = ['default_theme' => 'nonexistent']; $this->assertSame('light', appDefaultTheme()); } public function testDefaultThemeIgnoresEmptyTenantTheme(): void { $this->setAppSettings(['app_theme' => 'dark']); $_SESSION['current_tenant'] = ['default_theme' => '']; $this->assertSame('dark', appDefaultTheme()); } // -- currentTheme with user override -- public function testCurrentThemeReturnsDefaultWhenUserThemeDisabled(): void { $_SESSION['current_tenant'] = ['allow_user_theme' => '0', 'default_theme' => 'dark']; $_SESSION['user'] = ['theme' => 'light']; $this->assertSame('dark', currentTheme()); } public function testCurrentThemeReturnsUserThemeWhenAllowed(): void { $_SESSION['current_tenant'] = ['allow_user_theme' => '1', 'default_theme' => 'light']; $_SESSION['user'] = ['theme' => 'dark']; $this->assertSame('dark', currentTheme()); } public function testCurrentThemeFallsBackToDefaultForEmptyUserTheme(): void { $_SESSION['current_tenant'] = ['allow_user_theme' => '1', 'default_theme' => 'dark']; $_SESSION['user'] = ['theme' => '']; $this->assertSame('dark', currentTheme()); } public function testCurrentThemeFallsBackToDefaultForInvalidUserTheme(): void { $_SESSION['current_tenant'] = ['allow_user_theme' => '1']; $_SESSION['user'] = ['theme' => 'nonexistent']; $this->assertSame('light', currentTheme()); } // -- allowUserTheme feature flag -- public function testAllowUserThemeDefaultsToTrue(): void { $this->assertTrue(allowUserTheme()); } public function testAllowUserThemeTenantExplicitlyEnabled(): void { $_SESSION['current_tenant'] = ['allow_user_theme' => '1']; $this->assertTrue(allowUserTheme()); } public function testAllowUserThemeTenantExplicitlyDisabled(): void { $_SESSION['current_tenant'] = ['allow_user_theme' => '0']; $this->assertFalse(allowUserTheme()); } public function testAllowUserThemeTenantOverridesAppSetting(): void { $this->setAppSettings(['app_theme_user' => '1']); $_SESSION['current_tenant'] = ['allow_user_theme' => '0']; $this->assertFalse(allowUserTheme()); } public function testAllowUserThemeFallsBackToAppSettingWhenTenantNull(): void { $this->setAppSettings(['app_theme_user' => '0']); $this->assertFalse(allowUserTheme()); } // -- Tenant switch scenarios -- public function testThemeResolvesCorrectlyAfterTenantSwitch(): void { $_SESSION['user'] = ['theme' => 'dark']; $_SESSION['current_tenant'] = ['allow_user_theme' => '1', 'default_theme' => 'light']; $this->assertSame('dark', currentTheme()); // Simulate tenant switch: new tenant disallows user theme $_SESSION['current_tenant'] = ['allow_user_theme' => '0', 'default_theme' => 'light']; $this->assertSame('light', currentTheme()); } public function testThemeResolvesNewDefaultAfterTenantSwitch(): void { $_SESSION['user'] = ['theme' => 'light']; $_SESSION['current_tenant'] = ['allow_user_theme' => '0', 'default_theme' => 'light']; $this->assertSame('light', currentTheme()); // Switch to tenant with dark default $_SESSION['current_tenant'] = ['allow_user_theme' => '0', 'default_theme' => 'dark']; $this->assertSame('dark', currentTheme()); } /** * @param array $settings */ private function setAppSettings(array $settings): void { $cache = $this->createMock(SettingCacheService::class); $cache->method('get')->willReturnCallback(fn (string $key) => $settings[$key] ?? null); $this->container->set(SettingCacheService::class, fn () => $cache); } }