@storve/react 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -16
- package/package.json +9 -3
- package/benchmarks/week3.ts +0 -102
- package/coverage/coverage-summary.json +0 -5
- package/dist/index.cjs.js +0 -9
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.esm.js +0 -7
- package/dist/index.esm.js.map +0 -1
- package/rollup.config.mjs +0 -25
- package/src/index.ts +0 -4
- package/src/types.ts +0 -16
- package/src/useDevtools.ts +0 -74
- package/src/useStore.ts +0 -83
- package/test_output.txt +0 -234
- package/tests/computed.react.test.tsx +0 -71
- package/tests/concurrent.test.tsx +0 -101
- package/tests/index.test.tsx +0 -29
- package/tests/integration.test.tsx +0 -135
- package/tests/lifecycle.test.tsx +0 -148
- package/tests/selector.test.tsx +0 -288
- package/tests/setup.ts +0 -7
- package/tests/useDevtools.test.tsx +0 -80
- package/tests/useStore.test.tsx +0 -233
- package/tsconfig.json +0 -16
- package/vitest.config.mts +0 -30
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { render, screen, act } from '@testing-library/react'
|
|
2
|
-
import { createStore } from '@storve/core'
|
|
3
|
-
import { computed } from '@storve/core/computed'
|
|
4
|
-
import { useStore } from '../src/useStore'
|
|
5
|
-
import { expect, it, describe, vi } from 'vitest'
|
|
6
|
-
import React from 'react'
|
|
7
|
-
|
|
8
|
-
describe('useStore with computed values', () => {
|
|
9
|
-
it('component using computed selector re-renders when computed result changes', () => {
|
|
10
|
-
const store = createStore({
|
|
11
|
-
count: 0,
|
|
12
|
-
doubled: computed((s: { count: number }) => s.count * 2),
|
|
13
|
-
})
|
|
14
|
-
const renderSpy = vi.fn()
|
|
15
|
-
function Test() {
|
|
16
|
-
renderSpy()
|
|
17
|
-
const doubled = useStore(store, (s) => s.doubled)
|
|
18
|
-
return <div data-testid="val">{doubled}</div>
|
|
19
|
-
}
|
|
20
|
-
render(<Test />)
|
|
21
|
-
expect(screen.getByTestId('val')).toHaveTextContent('0')
|
|
22
|
-
expect(renderSpy).toHaveBeenCalledTimes(1)
|
|
23
|
-
|
|
24
|
-
act(() => {
|
|
25
|
-
store.setState({ count: 1 })
|
|
26
|
-
})
|
|
27
|
-
expect(screen.getByTestId('val')).toHaveTextContent('2')
|
|
28
|
-
expect(renderSpy).toHaveBeenCalledTimes(2)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('component does NOT re-render when only unrelated state changes', () => {
|
|
32
|
-
const store = createStore({
|
|
33
|
-
count: 0,
|
|
34
|
-
other: 0,
|
|
35
|
-
doubled: computed((s: { count: number }) => s.count * 2),
|
|
36
|
-
})
|
|
37
|
-
const renderSpy = vi.fn()
|
|
38
|
-
function Test() {
|
|
39
|
-
renderSpy()
|
|
40
|
-
const doubled = useStore(store, (s) => s.doubled)
|
|
41
|
-
return <div data-testid="val">{doubled}</div>
|
|
42
|
-
}
|
|
43
|
-
render(<Test />)
|
|
44
|
-
expect(renderSpy).toHaveBeenCalledTimes(1)
|
|
45
|
-
|
|
46
|
-
act(() => {
|
|
47
|
-
store.setState({ other: 1 })
|
|
48
|
-
})
|
|
49
|
-
expect(renderSpy).toHaveBeenCalledTimes(1)
|
|
50
|
-
expect(screen.getByTestId('val')).toHaveTextContent('0')
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('component re-renders when computed result changes after dependency update', () => {
|
|
54
|
-
const store = createStore({
|
|
55
|
-
a: 1,
|
|
56
|
-
b: 2,
|
|
57
|
-
sum: computed((s: { a: number; b: number }) => s.a + s.b),
|
|
58
|
-
})
|
|
59
|
-
function Test() {
|
|
60
|
-
const sum = useStore(store, (s) => s.sum)
|
|
61
|
-
return <div data-testid="sum">{sum}</div>
|
|
62
|
-
}
|
|
63
|
-
render(<Test />)
|
|
64
|
-
expect(screen.getByTestId('sum')).toHaveTextContent('3')
|
|
65
|
-
|
|
66
|
-
act(() => {
|
|
67
|
-
store.setState({ a: 10 })
|
|
68
|
-
})
|
|
69
|
-
expect(screen.getByTestId('sum')).toHaveTextContent('12')
|
|
70
|
-
})
|
|
71
|
-
})
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { render, screen, act } from '@testing-library/react'
|
|
2
|
-
import { createStore } from '@storve/core'
|
|
3
|
-
import { useStore } from '../src/useStore'
|
|
4
|
-
import { expect, it, describe, vi } from 'vitest'
|
|
5
|
-
import React, { StrictMode, Suspense, startTransition } from 'react'
|
|
6
|
-
|
|
7
|
-
describe('concurrent mode', () => {
|
|
8
|
-
it('works with React.StrictMode — double invoke safe', () => {
|
|
9
|
-
const store = createStore({ count: 0 })
|
|
10
|
-
const subscribeSpy = vi.spyOn(store, 'subscribe')
|
|
11
|
-
|
|
12
|
-
function Test() {
|
|
13
|
-
useStore(store)
|
|
14
|
-
return null
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
render(
|
|
18
|
-
<StrictMode>
|
|
19
|
-
<Test />
|
|
20
|
-
</StrictMode>
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
// StrictMode might double-invoke effects/hooks initialization in dev
|
|
24
|
-
// useSyncExternalStore handles this internally.
|
|
25
|
-
expect(subscribeSpy).toHaveBeenCalled()
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('no tearing — all components see consistent state snapshot', () => {
|
|
29
|
-
const store = createStore({ count: 0 })
|
|
30
|
-
|
|
31
|
-
function Display() {
|
|
32
|
-
const count = useStore(store, s => s.count)
|
|
33
|
-
return <div data-testid="count">{count}</div>
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
render(
|
|
37
|
-
<>
|
|
38
|
-
<Display />
|
|
39
|
-
<Display />
|
|
40
|
-
<Display />
|
|
41
|
-
</>
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
act(() => {
|
|
45
|
-
store.setState({ count: 1 })
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
const elements = screen.getAllByTestId('count')
|
|
49
|
-
const values = elements.map(el => el.textContent)
|
|
50
|
-
expect(new Set(values).size).toBe(1)
|
|
51
|
-
expect(values[0]).toBe('1')
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('works inside Suspense boundary', async () => {
|
|
55
|
-
const store = createStore({ data: 'ready' })
|
|
56
|
-
|
|
57
|
-
function Content() {
|
|
58
|
-
const data = useStore(store, s => s.data)
|
|
59
|
-
return <div>{data}</div>
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
render(
|
|
63
|
-
<Suspense fallback={<div>Loading...</div>}>
|
|
64
|
-
<Content />
|
|
65
|
-
</Suspense>
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
expect(screen.getByText('ready')).toBeInTheDocument()
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('state update inside startTransition updates correctly', () => {
|
|
72
|
-
const store = createStore({ count: 0 })
|
|
73
|
-
|
|
74
|
-
function Counter() {
|
|
75
|
-
const count = useStore(store, s => s.count)
|
|
76
|
-
return <div data-testid="count">{count}</div>
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
render(<Counter />)
|
|
80
|
-
|
|
81
|
-
act(() => {
|
|
82
|
-
startTransition(() => {
|
|
83
|
-
store.setState({ count: 1 })
|
|
84
|
-
})
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
expect(screen.getByTestId('count')).toHaveTextContent('1')
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('getSnapshot returns stable reference when state unchanged', () => {
|
|
91
|
-
const store = createStore({ a: 1 })
|
|
92
|
-
const state1 = store.getState()
|
|
93
|
-
const state2 = store.getState()
|
|
94
|
-
|
|
95
|
-
// Storve core ensures getState returns same Proxy reference if no changes
|
|
96
|
-
expect(state1).toBe(state2)
|
|
97
|
-
|
|
98
|
-
// useStore uses store.getState() internally via useSyncExternalStore's getSnapshot
|
|
99
|
-
// We can't easily inspect useSyncExternalStore internal state, but we verified getState() stability.
|
|
100
|
-
})
|
|
101
|
-
})
|
package/tests/index.test.tsx
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react'
|
|
2
|
-
import { createStore } from '@storve/core'
|
|
3
|
-
import { useStore } from '../src/index'
|
|
4
|
-
import type { Selector } from '../src/index'
|
|
5
|
-
import { expect, it, describe } from 'vitest'
|
|
6
|
-
import React from 'react'
|
|
7
|
-
|
|
8
|
-
describe('index (public API)', () => {
|
|
9
|
-
it('useStore exported from index works correctly', () => {
|
|
10
|
-
const store = createStore({ count: 0 })
|
|
11
|
-
function Counter() {
|
|
12
|
-
const state = useStore(store)
|
|
13
|
-
return <div data-testid="count">{state.count}</div>
|
|
14
|
-
}
|
|
15
|
-
render(<Counter />)
|
|
16
|
-
expect(screen.getByTestId('count')).toHaveTextContent('0')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('Selector type can be used with useStore', () => {
|
|
20
|
-
const store = createStore({ a: 1, b: 2 })
|
|
21
|
-
const selector: Selector<{ a: number; b: number }, number> = (s) => s.a + s.b
|
|
22
|
-
function Test() {
|
|
23
|
-
const sum = useStore(store, selector)
|
|
24
|
-
return <div data-testid="sum">{sum}</div>
|
|
25
|
-
}
|
|
26
|
-
render(<Test />)
|
|
27
|
-
expect(screen.getByTestId('sum')).toHaveTextContent('3')
|
|
28
|
-
})
|
|
29
|
-
})
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { render, screen, act } from '@testing-library/react'
|
|
2
|
-
import { createStore } from '@storve/core'
|
|
3
|
-
import { useStore } from '../src/useStore'
|
|
4
|
-
import { expect, it, describe, vi } from 'vitest'
|
|
5
|
-
import React from 'react'
|
|
6
|
-
|
|
7
|
-
describe('integration scenarios', () => {
|
|
8
|
-
it('counter component — increment updates UI', () => {
|
|
9
|
-
const store = createStore({ count: 0 })
|
|
10
|
-
const increment = () => store.setState((s) => ({ count: s.count + 1 }))
|
|
11
|
-
|
|
12
|
-
function Counter() {
|
|
13
|
-
const count = useStore(store, s => s.count)
|
|
14
|
-
return (
|
|
15
|
-
<div>
|
|
16
|
-
<span data-testid="count">{count}</span>
|
|
17
|
-
<button onClick={increment}>+</button>
|
|
18
|
-
</div>
|
|
19
|
-
)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
render(<Counter />)
|
|
23
|
-
expect(screen.getByTestId('count')).toHaveTextContent('0')
|
|
24
|
-
|
|
25
|
-
act(() => {
|
|
26
|
-
screen.getByText('+').click()
|
|
27
|
-
})
|
|
28
|
-
expect(screen.getByTestId('count')).toHaveTextContent('1')
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('todo list — add item renders new item', () => {
|
|
32
|
-
const store = createStore({ todos: [] as string[] })
|
|
33
|
-
const addTodo = (text: string) => store.setState(s => ({ todos: [...s.todos, text] }))
|
|
34
|
-
|
|
35
|
-
function TodoList() {
|
|
36
|
-
const todos = useStore(store, s => s.todos)
|
|
37
|
-
return (
|
|
38
|
-
<ul>
|
|
39
|
-
{todos.map((t, i) => <li key={i}>{t}</li>)}
|
|
40
|
-
</ul>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
render(<TodoList />)
|
|
45
|
-
expect(screen.queryByRole('listitem')).not.toBeInTheDocument()
|
|
46
|
-
|
|
47
|
-
act(() => {
|
|
48
|
-
addTodo('Learn Storve')
|
|
49
|
-
})
|
|
50
|
-
expect(screen.getByRole('listitem')).toHaveTextContent('Learn Storve')
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('todo list — filter selector shows correct items', () => {
|
|
54
|
-
const store = createStore({
|
|
55
|
-
todos: [
|
|
56
|
-
{ id: 1, text: 'A', done: true },
|
|
57
|
-
{ id: 2, text: 'B', done: false }
|
|
58
|
-
],
|
|
59
|
-
filter: 'all' as 'all' | 'done' | 'pending'
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
function VisibleTodos() {
|
|
63
|
-
const filtered = useStore(store, s => {
|
|
64
|
-
if (s.filter === 'done') return s.todos.filter(t => t.done)
|
|
65
|
-
if (s.filter === 'pending') return s.todos.filter(t => !t.done)
|
|
66
|
-
return s.todos
|
|
67
|
-
})
|
|
68
|
-
return (
|
|
69
|
-
<ul>
|
|
70
|
-
{filtered.map(t => <li key={t.id}>{t.text}</li>)}
|
|
71
|
-
</ul>
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
render(<VisibleTodos />)
|
|
76
|
-
expect(screen.getAllByRole('listitem')).toHaveLength(2)
|
|
77
|
-
|
|
78
|
-
act(() => {
|
|
79
|
-
store.setState({ filter: 'done' })
|
|
80
|
-
})
|
|
81
|
-
expect(screen.getAllByRole('listitem')).toHaveLength(1)
|
|
82
|
-
expect(screen.getByRole('listitem')).toHaveTextContent('A')
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('rapid state updates (100 in loop) — UI stays consistent', () => {
|
|
86
|
-
const store = createStore({ count: 0 })
|
|
87
|
-
function Counter() {
|
|
88
|
-
const count = useStore(store, s => s.count)
|
|
89
|
-
return <div data-testid="count">{count}</div>
|
|
90
|
-
}
|
|
91
|
-
render(<Counter />)
|
|
92
|
-
|
|
93
|
-
act(() => {
|
|
94
|
-
for (let i = 0; i < 100; i++) {
|
|
95
|
-
store.setState(s => ({ count: s.count + 1 }))
|
|
96
|
-
}
|
|
97
|
-
})
|
|
98
|
-
expect(screen.getByTestId('count')).toHaveTextContent('100')
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('sibling components — one updates, only affected re-renders', () => {
|
|
102
|
-
const store = createStore({ a: 0, b: 0 })
|
|
103
|
-
const renderSpyA = vi.fn()
|
|
104
|
-
const renderSpyB = vi.fn()
|
|
105
|
-
|
|
106
|
-
function ComponentA() {
|
|
107
|
-
renderSpyA()
|
|
108
|
-
const a = useStore(store, s => s.a)
|
|
109
|
-
return <div>{a}</div>
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function ComponentB() {
|
|
113
|
-
renderSpyB()
|
|
114
|
-
const b = useStore(store, s => s.b)
|
|
115
|
-
return <div>{b}</div>
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
render(
|
|
119
|
-
<>
|
|
120
|
-
<ComponentA />
|
|
121
|
-
<ComponentB />
|
|
122
|
-
</>
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
renderSpyA.mockClear()
|
|
126
|
-
renderSpyB.mockClear()
|
|
127
|
-
|
|
128
|
-
act(() => {
|
|
129
|
-
store.setState({ a: 1 })
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
expect(renderSpyA).toHaveBeenCalledTimes(1)
|
|
133
|
-
expect(renderSpyB).toHaveBeenCalledTimes(0)
|
|
134
|
-
})
|
|
135
|
-
})
|
package/tests/lifecycle.test.tsx
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { render, act } from '@testing-library/react'
|
|
2
|
-
import { createStore } from '@storve/core'
|
|
3
|
-
import { useStore } from '../src/useStore'
|
|
4
|
-
import { expect, it, describe, vi } from 'vitest'
|
|
5
|
-
import React, { useEffect } from 'react'
|
|
6
|
-
|
|
7
|
-
describe('lifecycle', () => {
|
|
8
|
-
it('subscription created on component mount', () => {
|
|
9
|
-
const store = createStore({ count: 0 })
|
|
10
|
-
const subscribeSpy = vi.spyOn(store, 'subscribe')
|
|
11
|
-
function Test() {
|
|
12
|
-
useStore(store)
|
|
13
|
-
return null
|
|
14
|
-
}
|
|
15
|
-
render(<Test />)
|
|
16
|
-
expect(subscribeSpy).toHaveBeenCalled()
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('subscription cleaned up on component unmount', () => {
|
|
20
|
-
const store = createStore({ count: 0 })
|
|
21
|
-
const unsubscribeSpy = vi.fn()
|
|
22
|
-
vi.spyOn(store, 'subscribe').mockReturnValue(unsubscribeSpy)
|
|
23
|
-
|
|
24
|
-
function Test() {
|
|
25
|
-
useStore(store)
|
|
26
|
-
return null
|
|
27
|
-
}
|
|
28
|
-
const { unmount } = render(<Test />)
|
|
29
|
-
unmount()
|
|
30
|
-
expect(unsubscribeSpy).toHaveBeenCalled()
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('no memory leak after 100 mount/unmount cycles', () => {
|
|
34
|
-
const store = createStore({ count: 0 })
|
|
35
|
-
const subscribeSpy = vi.spyOn(store, 'subscribe')
|
|
36
|
-
const unsubscribeSpy = vi.fn()
|
|
37
|
-
subscribeSpy.mockReturnValue(unsubscribeSpy)
|
|
38
|
-
|
|
39
|
-
function Test() {
|
|
40
|
-
useStore(store)
|
|
41
|
-
return null
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for (let i = 0; i < 100; i++) {
|
|
45
|
-
const { unmount } = render(<Test />)
|
|
46
|
-
unmount()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
expect(subscribeSpy).toHaveBeenCalledTimes(100)
|
|
50
|
-
expect(unsubscribeSpy).toHaveBeenCalledTimes(100)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('unmounted component does not receive state updates', () => {
|
|
54
|
-
const store = createStore({ count: 0 })
|
|
55
|
-
const renderSpy = vi.fn()
|
|
56
|
-
function Test() {
|
|
57
|
-
renderSpy()
|
|
58
|
-
useStore(store)
|
|
59
|
-
return null
|
|
60
|
-
}
|
|
61
|
-
const { unmount } = render(<Test />)
|
|
62
|
-
expect(renderSpy).toHaveBeenCalledTimes(1)
|
|
63
|
-
unmount()
|
|
64
|
-
|
|
65
|
-
act(() => {
|
|
66
|
-
store.setState({ count: 1 })
|
|
67
|
-
})
|
|
68
|
-
expect(renderSpy).toHaveBeenCalledTimes(1)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('re-mounted component resubscribes correctly', () => {
|
|
72
|
-
const store = createStore({ count: 0 })
|
|
73
|
-
const subscribeSpy = vi.spyOn(store, 'subscribe')
|
|
74
|
-
function Test() {
|
|
75
|
-
useStore(store)
|
|
76
|
-
return null
|
|
77
|
-
}
|
|
78
|
-
const { unmount } = render(<Test />)
|
|
79
|
-
unmount()
|
|
80
|
-
render(<Test />)
|
|
81
|
-
expect(subscribeSpy).toHaveBeenCalledTimes(2)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('multiple mounts of same component — each has own subscription', () => {
|
|
85
|
-
const store = createStore({ count: 0 })
|
|
86
|
-
const subscribeSpy = vi.spyOn(store, 'subscribe')
|
|
87
|
-
function Test() {
|
|
88
|
-
useStore(store)
|
|
89
|
-
return null
|
|
90
|
-
}
|
|
91
|
-
render(
|
|
92
|
-
<>
|
|
93
|
-
<Test />
|
|
94
|
-
<Test />
|
|
95
|
-
</>
|
|
96
|
-
)
|
|
97
|
-
expect(subscribeSpy).toHaveBeenCalledTimes(2)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('component unmount during state update — no crash', () => {
|
|
101
|
-
const store = createStore({ count: 0 })
|
|
102
|
-
function Test() {
|
|
103
|
-
useStore(store)
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
// Trigger update on mount then unmount
|
|
106
|
-
// This is a bit tricky to simulate exactly but we can try
|
|
107
|
-
}, [])
|
|
108
|
-
return null
|
|
109
|
-
}
|
|
110
|
-
const { unmount } = render(<Test />)
|
|
111
|
-
act(() => {
|
|
112
|
-
store.setState({ count: 1 })
|
|
113
|
-
unmount()
|
|
114
|
-
})
|
|
115
|
-
// Should not crash
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('setState after all consumers unmounted — no crash', () => {
|
|
119
|
-
const store = createStore({ count: 0 })
|
|
120
|
-
function Test() {
|
|
121
|
-
useStore(store)
|
|
122
|
-
return null
|
|
123
|
-
}
|
|
124
|
-
const { unmount } = render(<Test />)
|
|
125
|
-
unmount()
|
|
126
|
-
act(() => {
|
|
127
|
-
store.setState({ count: 1 })
|
|
128
|
-
})
|
|
129
|
-
// Should not crash
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('parent unmount cleans up child subscriptions', () => {
|
|
133
|
-
const store = createStore({ count: 0 })
|
|
134
|
-
const unsubscribeSpy = vi.fn()
|
|
135
|
-
vi.spyOn(store, 'subscribe').mockReturnValue(unsubscribeSpy)
|
|
136
|
-
|
|
137
|
-
function Child() {
|
|
138
|
-
useStore(store)
|
|
139
|
-
return null
|
|
140
|
-
}
|
|
141
|
-
function Parent() {
|
|
142
|
-
return <Child />
|
|
143
|
-
}
|
|
144
|
-
const { unmount } = render(<Parent />)
|
|
145
|
-
unmount()
|
|
146
|
-
expect(unsubscribeSpy).toHaveBeenCalled()
|
|
147
|
-
})
|
|
148
|
-
})
|