Skip to content

Commit b1ee7fb

Browse files
committed
Fix test
1 parent 7267883 commit b1ee7fb

3 files changed

Lines changed: 155 additions & 159 deletions

File tree

packages/react/src/stores/__tests__/theme-store.browser.test.ts

Lines changed: 106 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -22,162 +22,116 @@ afterAll(() => {
2222
document.documentElement.removeAttribute('data-theme')
2323
})
2424

25-
describe.each([
26-
[false, 'server'],
27-
[true, 'browser'],
28-
])('themeStore %s', (_isBrowser, _title) => {
29-
const originalWindow = globalThis.window
25+
describe('themeStore', () => {
26+
// Loader.registry.delete(require.resolve('../theme-store'))
3027
beforeEach(() => {
31-
// Remove window to simulate SSR
32-
if (!_isBrowser) {
33-
// @ts-expect-error - Temporarily remove window for SSR test
34-
delete globalThis.window
35-
} else {
36-
document.documentElement.removeAttribute('data-theme')
37-
}
28+
document.documentElement.removeAttribute('data-theme')
3829
})
3930

4031
afterEach(() => {
41-
if (_isBrowser) {
42-
document.documentElement.removeAttribute('data-theme')
43-
}
44-
globalThis.window = originalWindow
32+
document.documentElement.removeAttribute('data-theme')
4533
})
46-
if (_isBrowser) {
47-
it('should return themeStore object for browser', () => {
48-
// const modulePath = require.resolve('../theme-store')
49-
// delete require.cache[modulePath]
50-
const themeStore = createThemeStore()
51-
expect(themeStore).toBeDefined()
52-
expect(themeStore.get).toEqual(expect.any(Function))
53-
expect(themeStore.set).toEqual(expect.any(Function))
54-
expect(themeStore.subscribe).toEqual(expect.any(Function))
55-
expect(themeStore.get()).toBeNull()
56-
expect(themeStore.set('dark' as any)).toBeUndefined()
57-
// subscribe returns an unsubscribe function, which returns boolean when called
58-
const unsubscribe = themeStore.subscribe(() => {})
59-
expect(typeof unsubscribe).toBe('function')
60-
themeStore.subscribe(() => {})
61-
expect(themeStore.set('dark' as any)).toBeUndefined()
62-
})
63-
64-
it('should call subscriber when theme changes via set', () => {
65-
// const modulePath = require.resolve('../theme-store')
66-
// delete require.cache[modulePath]
67-
const themeStore = createThemeStore()
68-
const callback = mock()
69-
70-
themeStore.subscribe(callback)
71-
72-
// First call is from subscribe itself (reads current data-theme)
73-
expect(callback).toHaveBeenCalledTimes(1)
74-
75-
themeStore.set('light' as any)
76-
expect(callback).toHaveBeenCalledTimes(2)
77-
expect(callback).toHaveBeenLastCalledWith('light')
78-
expect(themeStore.get()).toBe('light' as any)
79-
})
80-
81-
it('should unsubscribe correctly', () => {
82-
// const modulePath = require.resolve('../theme-store')
83-
// delete require.cache[modulePath]
84-
const themeStore = createThemeStore()
85-
const callback = mock()
86-
87-
const unsubscribe = themeStore.subscribe(callback)
88-
expect(callback).toHaveBeenCalledTimes(1)
89-
90-
// Unsubscribe
91-
const result = unsubscribe()
92-
expect(result).toBe(true as any)
93-
94-
// Should not be called after unsubscribe
95-
themeStore.set('dark' as any)
96-
expect(callback).toHaveBeenCalledTimes(1)
97-
})
98-
99-
it('should read initial theme from data-theme attribute', () => {
100-
document.documentElement.setAttribute('data-theme', 'dark')
101-
102-
// const modulePath = require.resolve('../theme-store')
103-
// delete require.cache[modulePath]
104-
const themeStore = createThemeStore()
105-
const callback = mock()
106-
107-
themeStore.subscribe(callback)
108-
109-
// Should be called with 'dark' from the attribute
110-
expect(callback).toHaveBeenCalledWith('dark')
111-
})
112-
113-
it('should update theme when data-theme attribute changes via MutationObserver', async () => {
114-
// const modulePath = require.resolve('../theme-store')
115-
// delete require.cache[modulePath]
116-
const themeStore = createThemeStore()
117-
const callback = mock()
118-
119-
themeStore.subscribe(callback)
120-
expect(callback).toHaveBeenCalledTimes(1)
121-
122-
// Change the attribute - MutationObserver should trigger
123-
document.documentElement.setAttribute('data-theme', 'dark')
124-
125-
// Wait for MutationObserver to fire (it's async)
126-
await new Promise((resolve) => setTimeout(resolve, 10))
127-
128-
expect(callback).toHaveBeenCalledWith('dark')
129-
expect(themeStore.get()).toBe('dark' as any)
130-
})
131-
132-
it('should handle multiple subscribers', async () => {
133-
// const modulePath = require.resolve('../theme-store')
134-
// delete require.cache[modulePath]
135-
const themeStore = createThemeStore()
136-
const callback1 = mock()
137-
const callback2 = mock()
138-
139-
themeStore.subscribe(callback1)
140-
themeStore.subscribe(callback2)
141-
142-
themeStore.set('light' as any)
143-
144-
expect(callback1).toHaveBeenLastCalledWith('light')
145-
expect(callback2).toHaveBeenLastCalledWith('light')
146-
})
147-
148-
it('should filter mutations by type and target', async () => {
149-
// const modulePath = require.resolve('../theme-store')
150-
// delete require.cache[modulePath]
151-
const themeStore = createThemeStore()
152-
const callback = mock()
153-
154-
themeStore.subscribe(callback)
155-
156-
// Change data-theme attribute (should trigger)
157-
document.documentElement.setAttribute('data-theme', 'system')
158-
159-
await new Promise((resolve) => setTimeout(resolve, 10))
160-
161-
expect(themeStore.get()).toBe('system' as any)
162-
})
163-
} else {
164-
it('should return themeStore object for server', async () => {
165-
const { createThemeStore: createThemeStoreFn } = require.cache[
166-
require.resolve('../theme-store')
167-
]?.exports as unknown as { createThemeStore: typeof createThemeStore }
168-
const themeStore = createThemeStoreFn()
16934

170-
// Test SSR store behavior
171-
expect(themeStore).toBeDefined()
172-
expect(themeStore.get()).toBeNull()
173-
expect(themeStore.set('dark' as any)).toBeUndefined()
174-
175-
const unsubscribe = themeStore.subscribe(() => {})
176-
expect(typeof unsubscribe).toBe('function')
177-
178-
// The unsubscribe should return a no-op function
179-
const innerUnsubscribe = unsubscribe()
180-
expect(innerUnsubscribe).toBeUndefined()
181-
})
182-
}
35+
it('should return themeStore object for browser', () => {
36+
const themeStore = createThemeStore()
37+
expect(themeStore).toBeDefined()
38+
expect(themeStore.get).toEqual(expect.any(Function))
39+
expect(themeStore.set).toEqual(expect.any(Function))
40+
expect(themeStore.subscribe).toEqual(expect.any(Function))
41+
expect(themeStore.get()).toBeNull()
42+
expect(themeStore.set('dark' as any)).toBeUndefined()
43+
// subscribe returns an unsubscribe function, which returns boolean when called
44+
const unsubscribe = themeStore.subscribe(() => {})
45+
expect(typeof unsubscribe).toBe('function')
46+
themeStore.subscribe(() => {})
47+
expect(themeStore.set('dark' as any)).toBeUndefined()
48+
})
49+
50+
it('should call subscriber when theme changes via set', () => {
51+
const themeStore = createThemeStore()
52+
const callback = mock()
53+
54+
themeStore.subscribe(callback)
55+
56+
// First call is from subscribe itself (reads current data-theme)
57+
expect(callback).toHaveBeenCalledTimes(1)
58+
59+
themeStore.set('light' as any)
60+
expect(callback).toHaveBeenCalledTimes(2)
61+
expect(callback).toHaveBeenLastCalledWith('light')
62+
expect(themeStore.get()).toBe('light' as any)
63+
})
64+
65+
it('should unsubscribe correctly', () => {
66+
const themeStore = createThemeStore()
67+
const callback = mock()
68+
69+
const unsubscribe = themeStore.subscribe(callback)
70+
expect(callback).toHaveBeenCalledTimes(1)
71+
72+
// Unsubscribe
73+
const result = unsubscribe()
74+
expect(result).toBe(true as any)
75+
76+
// Should not be called after unsubscribe
77+
themeStore.set('dark' as any)
78+
expect(callback).toHaveBeenCalledTimes(1)
79+
})
80+
81+
it('should read initial theme from data-theme attribute', () => {
82+
document.documentElement.setAttribute('data-theme', 'dark')
83+
84+
const themeStore = createThemeStore()
85+
const callback = mock()
86+
87+
themeStore.subscribe(callback)
88+
89+
// Should be called with 'dark' from the attribute
90+
expect(callback).toHaveBeenCalledWith('dark')
91+
})
92+
93+
it('should update theme when data-theme attribute changes via MutationObserver', async () => {
94+
const themeStore = createThemeStore()
95+
const callback = mock()
96+
97+
themeStore.subscribe(callback)
98+
expect(callback).toHaveBeenCalledTimes(1)
99+
100+
// Change the attribute - MutationObserver should trigger
101+
document.documentElement.setAttribute('data-theme', 'dark')
102+
103+
// Wait for MutationObserver to fire (it's async)
104+
await new Promise((resolve) => setTimeout(resolve, 10))
105+
106+
expect(callback).toHaveBeenCalledWith('dark')
107+
expect(themeStore.get()).toBe('dark' as any)
108+
})
109+
110+
it('should handle multiple subscribers', () => {
111+
const themeStore = createThemeStore()
112+
const callback1 = mock()
113+
const callback2 = mock()
114+
115+
themeStore.subscribe(callback1)
116+
themeStore.subscribe(callback2)
117+
118+
themeStore.set('light' as any)
119+
120+
expect(callback1).toHaveBeenLastCalledWith('light')
121+
expect(callback2).toHaveBeenLastCalledWith('light')
122+
})
123+
124+
it('should filter mutations by type and target', async () => {
125+
const themeStore = createThemeStore()
126+
const callback = mock()
127+
128+
themeStore.subscribe(callback)
129+
130+
// Change data-theme attribute (should trigger)
131+
document.documentElement.setAttribute('data-theme', 'system')
132+
133+
await new Promise((resolve) => setTimeout(resolve, 10))
134+
135+
expect(themeStore.get()).toBe('system' as any)
136+
})
183137
})
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Import from bun-test-env-dom to enable DOM environment
2+
import 'bun-test-env-dom'
3+
4+
import { beforeAll } from 'bun:test'
5+
import { afterAll } from 'bun:test'
6+
import { describe, expect, it } from 'bun:test'
7+
8+
import { createServerThemeStore } from '../theme-store'
9+
10+
describe('themeStore ssr', () => {
11+
const originalWindow = globalThis.window
12+
beforeAll(() => {
13+
globalThis.window = undefined
14+
})
15+
16+
afterAll(() => {
17+
globalThis.window = originalWindow
18+
})
19+
20+
it('should filter mutations by type and target', async () => {
21+
// const { createThemeStore } = await import('../theme-store')
22+
const themeStore = createServerThemeStore()
23+
24+
// Test SSR store behavior
25+
expect(themeStore).toBeDefined()
26+
expect(themeStore.get()).toBeNull()
27+
expect(themeStore.set('dark' as any)).toBeUndefined()
28+
29+
const unsubscribe = themeStore.subscribe(() => {})
30+
expect(typeof unsubscribe).toBe('function')
31+
32+
// The unsubscribe should return a no-op function
33+
const innerUnsubscribe = unsubscribe()
34+
expect(innerUnsubscribe).toBeUndefined()
35+
})
36+
})

packages/react/src/stores/theme-store.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ type StoreChangeEvent = (newTheme: Theme) => void
66

77
const initTheme = null
88

9-
export function createThemeStore() {
10-
if (typeof window === 'undefined')
11-
return {
12-
get: () => initTheme,
13-
set: () => {},
14-
subscribe: () => () => {},
15-
}
9+
export function createServerThemeStore() {
10+
return {
11+
get: () => initTheme,
12+
set: () => {},
13+
subscribe: () => () => {},
14+
}
15+
}
1616

17+
function createClientThemeStore() {
1718
const el = document.documentElement
1819
const subscribers: Set<StoreChangeEvent> = new Set()
1920
let theme: Theme = initTheme
@@ -47,3 +48,8 @@ export function createThemeStore() {
4748
subscribe,
4849
}
4950
}
51+
52+
export const createThemeStore: typeof createClientThemeStore =
53+
typeof window === 'undefined'
54+
? (createServerThemeStore as unknown as typeof createClientThemeStore)
55+
: createClientThemeStore

0 commit comments

Comments
 (0)