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 testDefaultThemeFallsBackToLightWithoutTenant(): void { $this->assertSame('light', appDefaultTheme()); } public function testDefaultThemeIgnoresGlobalAppSetting(): void { // A stale global setting in the cache must no longer influence the // resolution — appearance is tenant-scoped, no global fallback. $this->setAppSettings(['app_theme' => 'dark']); $this->assertSame('light', appDefaultTheme()); } public function testDefaultThemeUsesTenantValue(): void { $_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 { $_SESSION['current_tenant'] = ['default_theme' => '']; $this->assertSame('light', 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 testAllowUserThemeDefaultsToTrueWithoutTenant(): 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 testAllowUserThemeIgnoresGlobalAppSetting(): void { // Legacy global setting must not override the tenant-null default. $this->setAppSettings(['app_theme_user' => '0']); $this->assertTrue(allowUserTheme()); } public function testAppPrimaryColorIgnoresGlobalAppSetting(): void { $this->setAppSettings(['app_primary_color' => '#112233']); $this->assertNull(appPrimaryColor()); } public function testAppPrimaryColorReadsTenantValue(): void { $_SESSION['current_tenant'] = ['primary_color' => '#abcdef']; $this->assertSame('#abcdef', appPrimaryColor()); } public function testAppPrimaryColorRejectsInvalidTenantValue(): void { $_SESSION['current_tenant'] = ['primary_color' => 'not-a-color']; $this->assertNull(appPrimaryColor()); } // -- 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); } }