Compare commits

...

4 Commits

Author SHA1 Message Date
Steven Wexler
c61999bacd
docs: fix getBoth return type (#3167) 2025-07-02 19:54:08 -05:00
Gabriel Saunders
747e97f334
Zustorm (#3166)
* docs: added zustorm to 3rd party

* docs: update zustorm description
2025-07-02 23:59:37 +09:00
Wonsuk Choi
295bf970a3
test(*): add 'expect' with 'toBeInTheDocument' (#3162)
* test(*): add 'expect' with 'toBeInTheDocument'

* chore(eslint.config): enable 'vitest/expect-expect' rule
2025-06-29 11:49:16 +09:00
Noritaka Kobayashi
73b91d3700
refactor: remove unused import in example & fix typo in tests (#3161) 2025-06-28 22:44:50 +09:00
7 changed files with 77 additions and 57 deletions

View File

@ -428,7 +428,7 @@ interface FishSlice {
interface SharedSlice {
addBoth: () => void
getBoth: () => void
getBoth: () => number
}
const createBearSlice: StateCreator<

View File

@ -72,4 +72,5 @@ This can be done using third-party libraries created by the community.
- [zustand-xs](https://github.com/zustandjs/zustand-xs) — XState/store compabile middleware for Zustand
- [zustand-yjs](https://github.com/tandem-pt/zustand-yjs) — Zustand stores for Yjs structures.
- [zusteller](https://github.com/timkindberg/zusteller) — Your global state savior. "Just hooks" + Zustand.
- [zustorm](https://github.com/mooalot/zustorm) — A simple and powerful form library for Zustand.
- [zusty](https://github.com/oslabs-beta/Zusty) — Zustand tool to assist debugging with time travel, action logs, state snapshots, store view, render time metrics and state component tree.

View File

@ -82,7 +82,6 @@ export default tseslint.config(
'import/extensions': ['error', 'never'],
'@typescript-eslint/no-unused-vars': 'off',
'testing-library/no-node-access': 'off',
'vitest/expect-expect': 'off',
'vitest/consistent-test-it': [
'error',
{ fn: 'it', withinDescribe: 'it' },

View File

@ -1,5 +1,5 @@
import { Mesh, PlaneGeometry, Group, Vector3, MathUtils } from 'three'
import { memo, useRef, useState, useLayoutEffect } from 'react'
import { useRef, useState, useLayoutEffect } from 'react'
import { createRoot, events, extend, useFrame } from '@react-three/fiber'
import { Plane, useAspect, useTexture } from '@react-three/drei'
import {

View File

@ -64,7 +64,7 @@ it('uses the store with no args', async () => {
</>,
)
await screen.findByText('count: 1')
expect(await screen.findByText('count: 1')).toBeInTheDocument()
})
it('uses the store with selectors', async () => {
@ -86,7 +86,7 @@ it('uses the store with selectors', async () => {
</>,
)
await screen.findByText('count: 1')
expect(await screen.findByText('count: 1')).toBeInTheDocument()
})
it('uses the store with a selector and equality checker', async () => {
@ -116,15 +116,21 @@ it('uses the store with a selector and equality checker', async () => {
</>,
)
await screen.findByText('renderCount: 1, value: 0')
expect(
await screen.findByText('renderCount: 1, value: 0'),
).toBeInTheDocument()
// This will not cause a re-render.
act(() => setState({ item: { value: 1 } }))
await screen.findByText('renderCount: 1, value: 0')
expect(
await screen.findByText('renderCount: 1, value: 0'),
).toBeInTheDocument()
// This will cause a re-render.
act(() => setState({ item: { value: 2 } }))
await screen.findByText('renderCount: 2, value: 2')
expect(
await screen.findByText('renderCount: 2, value: 2'),
).toBeInTheDocument()
})
it('only re-renders if selected state has changed', async () => {
@ -156,7 +162,7 @@ it('only re-renders if selected state has changed', async () => {
fireEvent.click(screen.getByText('button'))
await screen.findByText('count: 1')
expect(await screen.findByText('count: 1')).toBeInTheDocument()
expect(counterRenderCount).toBe(2)
expect(controlRenderCount).toBe(1)
@ -185,7 +191,7 @@ it('can batch updates', async () => {
</>,
)
await screen.findByText('count: 2')
expect(await screen.findByText('count: 2')).toBeInTheDocument()
})
it('can update the selector', async () => {
@ -205,14 +211,14 @@ it('can update the selector', async () => {
<Component selector={(s) => s.one} />
</StrictMode>,
)
await screen.findByText('one')
expect(await screen.findByText('one')).toBeInTheDocument()
rerender(
<StrictMode>
<Component selector={(s) => s.two} />
</StrictMode>,
)
await screen.findByText('two')
expect(await screen.findByText('two')).toBeInTheDocument()
})
it('can update the equality checker', async () => {
@ -244,7 +250,9 @@ it('can update the equality checker', async () => {
// This will cause a re-render due to the equality checker.
act(() => setState({ value: 0 }))
await screen.findByText('renderCount: 2, value: 0')
expect(
await screen.findByText('renderCount: 2, value: 0'),
).toBeInTheDocument()
// Set an equality checker that always returns true to never re-render.
rerender(
@ -255,7 +263,9 @@ it('can update the equality checker', async () => {
// This will NOT cause a re-render due to the equality checker.
act(() => setState({ value: 1 }))
await screen.findByText('renderCount: 3, value: 0')
expect(
await screen.findByText('renderCount: 3, value: 0'),
).toBeInTheDocument()
})
it('can call useBoundStore with progressively more arguments', async () => {
@ -287,7 +297,9 @@ it('can call useBoundStore with progressively more arguments', async () => {
<Component />
</>,
)
await screen.findByText('renderCount: 1, value: {"value":0}')
expect(
await screen.findByText('renderCount: 1, value: {"value":0}'),
).toBeInTheDocument()
// Render with selector.
rerender(
@ -295,7 +307,9 @@ it('can call useBoundStore with progressively more arguments', async () => {
<Component selector={(s) => s.value} />
</>,
)
await screen.findByText('renderCount: 2, value: 0')
expect(
await screen.findByText('renderCount: 2, value: 0'),
).toBeInTheDocument()
// Render with selector and equality checker.
rerender(
@ -309,10 +323,14 @@ it('can call useBoundStore with progressively more arguments', async () => {
// Should not cause a re-render because new value is less than previous.
act(() => setState({ value: -1 }))
await screen.findByText('renderCount: 3, value: 0')
expect(
await screen.findByText('renderCount: 3, value: 0'),
).toBeInTheDocument()
act(() => setState({ value: 1 }))
await screen.findByText('renderCount: 4, value: 1')
expect(
await screen.findByText('renderCount: 4, value: 1'),
).toBeInTheDocument()
})
it('can throw an error in selector', async () => {
@ -355,12 +373,12 @@ it('can throw an error in selector', async () => {
</StrictMode>,
)
await screen.findByText('no error')
expect(await screen.findByText('no error')).toBeInTheDocument()
act(() => {
setState({ value: 123 })
})
await screen.findByText('errored')
expect(await screen.findByText('errored')).toBeInTheDocument()
})
it('can throw an error in equality checker', async () => {
@ -404,12 +422,12 @@ it('can throw an error in equality checker', async () => {
</StrictMode>,
)
await screen.findByText('no error')
expect(await screen.findByText('no error')).toBeInTheDocument()
act(() => {
setState({ value: 123 })
})
await screen.findByText('errored')
expect(await screen.findByText('errored')).toBeInTheDocument()
})
it('can get the store', () => {
@ -499,17 +517,17 @@ it('only calls selectors when necessary with static selector', async () => {
<Component />
</>,
)
await screen.findByText('static: 1')
expect(await screen.findByText('static: 1')).toBeInTheDocument()
rerender(
<>
<Component />
</>,
)
await screen.findByText('static: 1')
expect(await screen.findByText('static: 1')).toBeInTheDocument()
act(() => setState({ a: 1, b: 1 }))
await screen.findByText('static: 2')
expect(await screen.findByText('static: 2')).toBeInTheDocument()
})
it('only calls selectors when necessary (traditional)', async () => {
@ -540,20 +558,20 @@ it('only calls selectors when necessary (traditional)', async () => {
<Component />
</>,
)
await screen.findByText('inline: 1')
await screen.findByText('static: 1')
expect(await screen.findByText('inline: 1')).toBeInTheDocument()
expect(await screen.findByText('static: 1')).toBeInTheDocument()
rerender(
<>
<Component />
</>,
)
await screen.findByText('inline: 2')
await screen.findByText('static: 1')
expect(await screen.findByText('inline: 2')).toBeInTheDocument()
expect(await screen.findByText('static: 1')).toBeInTheDocument()
act(() => setState({ a: 1, b: 1 }))
await screen.findByText('inline: 4')
await screen.findByText('static: 2')
expect(await screen.findByText('inline: 4')).toBeInTheDocument()
expect(await screen.findByText('static: 2')).toBeInTheDocument()
})
it('ensures parent components subscribe before children', async () => {
@ -602,7 +620,7 @@ it('ensures parent components subscribe before children', async () => {
fireEvent.click(screen.getByText('change state'))
await screen.findByText('child 3')
expect(await screen.findByText('child 3')).toBeInTheDocument()
})
// https://github.com/pmndrs/zustand/issues/84
@ -715,10 +733,10 @@ it('works with non-object state', async () => {
</StrictMode>,
)
await screen.findByText('count: 1')
expect(await screen.findByText('count: 1')).toBeInTheDocument()
fireEvent.click(screen.getByText('button'))
await screen.findByText('count: 2')
expect(await screen.findByText('count: 2')).toBeInTheDocument()
})
it('works with "undefined" state', async () => {
@ -735,5 +753,5 @@ it('works with "undefined" state', async () => {
</StrictMode>,
)
await screen.findByText('str: undefined')
expect(await screen.findByText('str: undefined')).toBeInTheDocument()
})

View File

@ -81,8 +81,10 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 0, name: empty')
await screen.findByText('count: 42, name: test-storage')
expect(await screen.findByText('count: 0, name: empty')).toBeInTheDocument()
expect(
await screen.findByText('count: 42, name: test-storage'),
).toBeInTheDocument()
expect(onRehydrateStorageSpy).toBeCalledWith(
{ count: 42, name: 'test-storage' },
undefined,
@ -119,7 +121,7 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 0')
expect(await screen.findByText('count: 0')).toBeInTheDocument()
await waitFor(() => {
expect(onRehydrateStorageSpy).toBeCalledWith(
undefined,
@ -157,14 +159,14 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 0')
expect(await screen.findByText('count: 0')).toBeInTheDocument()
await waitFor(() => {
expect(onRehydrateStorageSpy).toBeCalledWith({ count: 0 }, undefined)
})
// Write something to the store
act(() => useBoundStore.setState({ count: 42 }))
await screen.findByText('count: 42')
expect(await screen.findByText('count: 42')).toBeInTheDocument()
expect(setItemSpy).toBeCalledWith(
'test-storage',
JSON.stringify({ state: { count: 42 }, version: 0 }),
@ -187,7 +189,7 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 42')
expect(await screen.findByText('count: 42')).toBeInTheDocument()
await waitFor(() => {
expect(onRehydrateStorageSpy2).toBeCalledWith({ count: 42 }, undefined)
})
@ -229,8 +231,8 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 0')
await screen.findByText('count: 99')
expect(await screen.findByText('count: 0')).toBeInTheDocument()
expect(await screen.findByText('count: 99')).toBeInTheDocument()
expect(migrateSpy).toBeCalledWith({ count: 42 }, 12)
expect(setItemSpy).toBeCalledWith(
'test-storage',
@ -291,8 +293,8 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 42')
await screen.findByText('name: test')
expect(await screen.findByText('count: 42')).toBeInTheDocument()
expect(await screen.findByText('name: test')).toBeInTheDocument()
expect(useBoundStore.getState()).toEqual(
expect.objectContaining({
@ -335,7 +337,7 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 0')
expect(await screen.findByText('count: 0')).toBeInTheDocument()
await waitFor(() => {
expect(console.error).toHaveBeenCalled()
@ -383,7 +385,7 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 0')
expect(await screen.findByText('count: 0')).toBeInTheDocument()
await waitFor(() => {
expect(onRehydrateStorageSpy).toBeCalledWith(
undefined,
@ -432,7 +434,7 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 1')
expect(await screen.findByText('count: 1')).toBeInTheDocument()
// The 'onRehydrateStorage' spy is invoked prior to rehydration, so it should
// be passed the default state.
@ -475,7 +477,7 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 0')
expect(await screen.findByText('count: 0')).toBeInTheDocument()
await waitFor(() => {
expect(onRehydrateStorageSpy).toBeCalledWith(
{ count: 1, unstorableMethod },
@ -527,7 +529,7 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 1')
expect(await screen.findByText('count: 1')).toBeInTheDocument()
expect(useBoundStore.getState()).toEqual({
count: 1,
actions: {
@ -566,7 +568,7 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 1')
expect(await screen.findByText('count: 1')).toBeInTheDocument()
expect(useBoundStore.getState()).toEqual({
count: 1,
})
@ -701,7 +703,7 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('count: 2')
expect(await screen.findByText('count: 2')).toBeInTheDocument()
expect(useBoundStore.getState().count).toEqual(2)
})
@ -742,7 +744,7 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('map: bar')
expect(await screen.findByText('map: bar')).toBeInTheDocument()
expect(onRehydrateStorageSpy).toBeCalledWith(
{ map: new Map([['foo', 'bar']]) },
undefined,
@ -779,7 +781,7 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('map-content:')
expect(await screen.findByText('map-content:')).toBeInTheDocument()
await waitFor(() => {
expect(onRehydrateStorageSpy).toBeCalledWith({ map }, undefined)
})
@ -787,7 +789,7 @@ describe('persist middleware with async configuration', () => {
// Write something to the store
const updatedMap = new Map(map).set('foo', 'bar')
act(() => useBoundStore.setState({ map: updatedMap }))
await screen.findByText('map-content: bar')
expect(await screen.findByText('map-content: bar')).toBeInTheDocument()
expect(setItemSpy).toBeCalledWith(
'test-storage',
@ -814,7 +816,7 @@ describe('persist middleware with async configuration', () => {
</StrictMode>,
)
await screen.findByText('map-content: bar')
expect(await screen.findByText('map-content: bar')).toBeInTheDocument()
await waitFor(() => {
expect(onRehydrateStorageSpy2).toBeCalledWith(
{ map: updatedMap },

View File

@ -42,7 +42,7 @@ it('can use exposed types', () => {
num: 1,
numGet: () => get().num,
numGetState: () => {
// TypeScript can't get the type of storeApi when it trys to enforce the signature of numGetState.
// TypeScript can't get the type of storeApi when it tries to enforce the signature of numGetState.
// Need to explicitly state the type of storeApi.getState().num or storeApi type will be type 'any'.
const result: number = storeApi.getState().num
return result