refactor(theme): replace themeManager singleton with lazy getThemeManager
Convert the eager themeManager singleton to a getThemeManager() lazy accessor (+ __resetThemeManager for tests), so the persistent-store subscription is set up on first access rather than at module load. Update the barrel and consumers (Layout init/destroy, ThemeSwitch, story, test).
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
Application shell with providers and page wrapper
|
Application shell with providers and page wrapper
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { themeManager } from '$features/ChangeAppTheme';
|
import { getThemeManager } from '$features/ChangeAppTheme';
|
||||||
import G from '$shared/assets/G.svg';
|
import G from '$shared/assets/G.svg';
|
||||||
import { ResponsiveProvider } from '$shared/lib';
|
import { ResponsiveProvider } from '$shared/lib';
|
||||||
import { cn } from '$shared/lib';
|
import { cn } from '$shared/lib';
|
||||||
@@ -32,6 +32,8 @@ interface Props {
|
|||||||
|
|
||||||
let { children }: Props = $props();
|
let { children }: Props = $props();
|
||||||
let fontsReady = $state(true);
|
let fontsReady = $state(true);
|
||||||
|
|
||||||
|
const themeManager = getThemeManager();
|
||||||
const theme = $derived(themeManager.value);
|
const theme = $derived(themeManager.value);
|
||||||
|
|
||||||
onMount(() => themeManager.init());
|
onMount(() => themeManager.init());
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { themeManager } from './store/ThemeManager/ThemeManager.svelte';
|
export { getThemeManager } from './store/ThemeManager/ThemeManager.svelte';
|
||||||
|
|||||||
@@ -194,15 +194,26 @@ class ThemeManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _themeManager: ThemeManager | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singleton theme manager instance
|
* App-wide theme manager, created on first access.
|
||||||
*
|
*
|
||||||
* Use throughout the app for consistent theme state.
|
* Lazy so its persistent-store subscription isn't set up at module load.
|
||||||
|
* Call init() on mount and destroy() on unmount (see Layout).
|
||||||
*/
|
*/
|
||||||
export const themeManager = new ThemeManager();
|
export function getThemeManager(): ThemeManager {
|
||||||
|
return (_themeManager ??= new ThemeManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
// test-only reset, so specs don't share persisted theme state
|
||||||
|
export function __resetThemeManager() {
|
||||||
|
_themeManager?.destroy();
|
||||||
|
_themeManager = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ThemeManager class exported for testing purposes
|
* ThemeManager class exported for testing purposes
|
||||||
* Use the singleton `themeManager` in application code.
|
* Use the `getThemeManager()` accessor in application code.
|
||||||
*/
|
*/
|
||||||
export { ThemeManager };
|
export { ThemeManager };
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ const { Story } = defineMeta({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { themeManager } from '$features/ChangeAppTheme';
|
import { getThemeManager } from '$features/ChangeAppTheme';
|
||||||
|
|
||||||
|
const themeManager = getThemeManager();
|
||||||
// Current theme state for display
|
// Current theme state for display
|
||||||
const currentTheme = $derived(themeManager.value);
|
const currentTheme = $derived(themeManager.value);
|
||||||
const themeSource = $derived(themeManager.source);
|
const themeSource = $derived(themeManager.source);
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import { IconButton } from '$shared/ui';
|
|||||||
import MoonIcon from '@lucide/svelte/icons/moon';
|
import MoonIcon from '@lucide/svelte/icons/moon';
|
||||||
import SunIcon from '@lucide/svelte/icons/sun';
|
import SunIcon from '@lucide/svelte/icons/sun';
|
||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import { themeManager } from '../../model';
|
import { getThemeManager } from '../../model';
|
||||||
|
|
||||||
const responsive = getContext<ResponsiveManager>('responsive');
|
const responsive = getContext<ResponsiveManager>('responsive');
|
||||||
|
|
||||||
|
const themeManager = getThemeManager();
|
||||||
const theme = $derived(themeManager.value);
|
const theme = $derived(themeManager.value);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,25 @@ import {
|
|||||||
render,
|
render,
|
||||||
screen,
|
screen,
|
||||||
} from '@testing-library/svelte';
|
} from '@testing-library/svelte';
|
||||||
import { themeManager } from '../../model';
|
import { afterEach } from 'vitest';
|
||||||
|
import { getThemeManager } from '../../model';
|
||||||
|
import { __resetThemeManager } from '../../model/store/ThemeManager/ThemeManager.svelte';
|
||||||
import ThemeSwitch from './ThemeSwitch.svelte';
|
import ThemeSwitch from './ThemeSwitch.svelte';
|
||||||
|
|
||||||
const context = new Map([['responsive', { isMobile: false }]]);
|
const context = new Map([['responsive', { isMobile: false }]]);
|
||||||
|
|
||||||
describe('ThemeSwitch', () => {
|
describe('ThemeSwitch', () => {
|
||||||
|
let themeManager: ReturnType<typeof getThemeManager>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
themeManager = getThemeManager();
|
||||||
themeManager.setTheme('light');
|
themeManager.setTheme('light');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
__resetThemeManager();
|
||||||
|
});
|
||||||
|
|
||||||
describe('Rendering', () => {
|
describe('Rendering', () => {
|
||||||
it('renders an icon button', () => {
|
it('renders an icon button', () => {
|
||||||
render(ThemeSwitch, { context });
|
render(ThemeSwitch, { context });
|
||||||
|
|||||||
Reference in New Issue
Block a user