zustand/tests/vanilla/basic.test.ts
wulimao49 949b505a4b
fix(vanilla): unexpected null state update behavior (#2213)
* fix: unexpected null state update behavior

* chore: lint code

* test: add test for setting state to null

---------

Co-authored-by: wulimao <tao@trlab.com>
2023-11-27 18:35:14 +09:00

155 lines
3.3 KiB
TypeScript

import { afterEach, expect, it, vi } from 'vitest'
import { createStore } from 'zustand/vanilla'
import type { StoreApi } from 'zustand/vanilla'
// To avoid include react deps on vanilla version
vi.mock('react', () => ({}))
const consoleError = console.error
afterEach(() => {
console.error = consoleError
})
it('create a store', () => {
let params
const result = createStore((...args) => {
params = args
return { value: null }
})
expect({ params, result }).toMatchInlineSnapshot(`
{
"params": [
[Function],
[Function],
{
"destroy": [Function],
"getState": [Function],
"setState": [Function],
"subscribe": [Function],
},
],
"result": {
"destroy": [Function],
"getState": [Function],
"setState": [Function],
"subscribe": [Function],
},
}
`)
})
type CounterState = {
count: number
inc: () => void
}
it('uses the store', async () => {
const store = createStore<CounterState>((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
}))
store.getState().inc()
expect(store.getState().count).toBe(1)
})
it('can get the store', async () => {
type State = {
value: number
getState1: () => State
getState2: () => State
}
const store = createStore<State>((_, get) => ({
value: 1,
getState1: () => get(),
getState2: (): State => store.getState(),
}))
expect(store.getState().getState1().value).toBe(1)
expect(store.getState().getState2().value).toBe(1)
})
it('can set the store', async () => {
type State = {
value: number
setState1: StoreApi<State>['setState']
setState2: StoreApi<State>['setState']
}
const store = createStore<State>((set) => ({
value: 1,
setState1: (v) => set(v),
setState2: (v): void => store.setState(v),
}))
store.getState().setState1({ value: 2 })
expect(store.getState().value).toBe(2)
store.getState().setState2({ value: 3 })
expect(store.getState().value).toBe(3)
})
it('both NaN should not update', () => {
const store = createStore<number>(() => NaN)
const fn = vi.fn()
store.subscribe(fn)
store.setState(NaN)
expect(fn).not.toBeCalled()
})
it('can set the store without merging', () => {
const { setState, getState } = createStore<{ a: number } | { b: number }>(
(_set) => ({
a: 1,
}),
)
// Should override the state instead of merging.
setState({ b: 2 }, true)
expect(getState()).toEqual({ b: 2 })
})
it('can set the object store to null', () => {
const { setState, getState } = createStore<{ a: number } | null>(() => ({
a: 1,
}))
setState(null)
expect(getState()).toEqual(null)
})
it('can set the non-object store to null', () => {
const { setState, getState } = createStore<string | null>(() => 'value')
setState(null)
expect(getState()).toEqual(null)
})
it('works with non-object state', () => {
const store = createStore<number>(() => 1)
const inc = () => store.setState((c) => c + 1)
inc()
expect(store.getState()).toBe(2)
})
it('can destroy the store', () => {
const { destroy, getState, setState, subscribe } = createStore(() => ({
value: 1,
}))
subscribe(() => {
throw new Error('did not clear listener on destroy')
})
destroy()
setState({ value: 2 })
expect(getState().value).toEqual(2)
})