zustand/tests/types.test.tsx
Daishi Kato e247220ece
v5 (#2138)
* prepare for the next major version

* [v5] breaking: drop default exports (#2238)

* fix: drop default exports for v5

* chore: remove default from cjs build

* refactor: export shallow in v5

* fix: remove `addModuleExport` option for cjs.

* [v5] breaking: drop deprecated features (#2235)

* fix: remove deprecated v4 features

* chore(build): remove context

* docs(typescript): remove deprecated equals api

* docs(persist): remove old persist api

* chore: run yarn prettier on typescript docs

* Discard changes to docs/guides/typescript.md

* Discard changes to docs/integrations/persisting-store-data.md

* Discard changes to tests/shallow.test.tsx

* Discard changes to tests/vanilla/subscribe.test.tsx

* [v5] breaking: make React 18 as minimal requirement (#2236)

* fix: update package.json to require react 18+

* chore: update github actions to test on react 18+

* chore: remove devtools-skip hack from actions

* chore(test): remove CI-SKIP from devtools tests

* [v5] breaking: make use-sync-external-store an optional peer dependency (#2237)

* chore: make use-sync-external-store optional peerDep

* fix: use correct versions in package.json

* [v5] breaking: require TypeScript 4.5 and update tests (#2257)

* breaking(types): TS requirement

* wip: latest only

* wip: latest only 2

* drop ts <4.4

* wip: do not skip lib checkes

* use latest node types

* drop ts 4.4

* [v5]: drop "module" condition  (#2270)

* Update package json in order to remove module

* Update rollup config in order to remove module config

* Update patch esm script

* Update package json to general exports and update node version (#2272)

* [v5]: drop UMD/SystemJS builds (#2287)

* Update rollup config in order to drop system js and umd builds

* Update packages

* Clean up files

* Update rollup config

* Update gh workflows

* Minor fixes

* Minor fixes

* Minor fixes

* Minor fixes

* Testing

* Minor changes

* Minor fixes

* remove `WithReact` type (#2300)

* 5.0.0-alpha.0

* [v5]: do not depend on use-sync-external-store (#2301)

* [v5]: do not depend on use-sync-external-store

* memo get(server)snapshot

* 5.0.0-alpha.1

* [v5]: refactor useMemoSelector (#2302)

* [v5]: refactor useMemoSelector

* add a test

* Revert "[v5]: refactor useMemoSelector"

This reverts commit b3c8b15586a270d12c335e566975021adf86c815.

* Revert "Revert "[v5]: refactor useMemoSelector""

This reverts commit 3c47301d23e18dffb7d72df36595f83570d15d08.

* [v5]: separate react entry point (#2303)

* 5.0.0-alpha.2

* 5.0.0-alpha.3

* refactor: Switch to Object.hasOwn (#2365)

* [v5] drop es5 (#2380)

* update yarn lock

* 5.0.0-alpha.4

* [v5]: follow React "standard" way with breaking behavioral change (#2395)

* [v5]: follow React "standard" way with breaking behavioral change

* add test

* 5.0.0-alpha.5

* [v5] Rewrite shallow to support iterables (#2427)

* [v5] fix rollup config for cjs (#2433)

* 5.0.0-alpha.6

* no production build test

* recover types that are dropped in #2462

* remove unused replacement

* [v5] Remove Devtools warning (#2466)

* chore: remove devtools extension warning

* docs: add devtools link to readme

* chore: remove unused test

* chrome: remove unused tests

* chore: remove unused test

* Revert "chore: remove unused test"

This reverts commit 0fa2a75f4936d960f703bf19e8f3505962cd628e.

* update test name

* update pnpm lock

* fix merge main

* add migration guide

* fix typos

* 5.0.0-beta.0

* update migration doc

* fix merge main

* fix merge main (prettier)

* 5.0.0-beta.1

* fix(types)!: require complete state if `setState`'s `replace` flag is set (#2580)

* fix(types): require complete state if `setState`'s `replace` flag is set

* switch to variant 2

* fix type errors

* update setState types for devtools and immer

* make devtools setState non-generic

* add migration guide

* merge migration guides

* run prettier

* Update tests/middlewareTypes.test.tsx

---------

Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com>
Co-authored-by: daishi <daishi@axlight.com>

* 5.0.0-beta.2

* move v5 migration doc

* fix ci

* missing commmit

* remove unused rule exclusion

* comment about react compiler

* revert eslint config

---------

Co-authored-by: Charles Kornoelje <33156025+charkour@users.noreply.github.com>
Co-authored-by: Danilo Britto <dbritto.dev@gmail.com>
Co-authored-by: Ekin Dursun <ekindursun@gmail.com>
Co-authored-by: Simon Farshid <simon.farshid@outlook.com>
2024-08-16 09:41:00 +09:00

249 lines
6.1 KiB
TypeScript

import { expect, it } from 'vitest'
import { create } from 'zustand'
import type {
StateCreator,
StoreApi,
StoreMutatorIdentifier,
UseBoundStore,
} from 'zustand'
import { persist } from 'zustand/middleware'
it('can use exposed types', () => {
type ExampleState = {
num: number
numGet: () => number
numGetState: () => number
numSet: (v: number) => void
numSetState: (v: number) => void
}
const listener = (state: ExampleState) => {
if (state) {
const value = state.num * state.numGet() * state.numGetState()
state.numSet(value)
state.numSetState(value)
}
}
const selector = (state: ExampleState) => state.num
const partial: Partial<ExampleState> = {
num: 2,
numGet: () => 2,
}
const partialFn: (state: ExampleState) => Partial<ExampleState> = (
state,
) => ({
...state,
num: 2,
})
const equalityFn = (state: ExampleState, newState: ExampleState) =>
state !== newState
const storeApi = create<ExampleState>((set, get) => ({
num: 1,
numGet: () => get().num,
numGetState: () => {
// TypeScript can't get the type of storeApi when it trys 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
},
numSet: (v) => {
set({ num: v })
},
numSetState: (v) => {
storeApi.setState({ num: v })
},
}))
const useBoundStore = storeApi
const stateCreator: StateCreator<ExampleState> = (set, get) => ({
num: 1,
numGet: () => get().num,
numGetState: () => get().num,
numSet: (v) => {
set({ num: v })
},
numSetState: (v) => {
set({ num: v })
},
})
function checkAllTypes(
_getState: StoreApi<ExampleState>['getState'],
_partialState:
| Partial<ExampleState>
| ((s: ExampleState) => Partial<ExampleState>),
_setState: StoreApi<ExampleState>['setState'],
_state: object,
_stateListener: (state: ExampleState, previousState: ExampleState) => void,
_stateSelector: (state: ExampleState) => number,
_storeApi: StoreApi<ExampleState>,
_subscribe: StoreApi<ExampleState>['subscribe'],
_equalityFn: (a: ExampleState, b: ExampleState) => boolean,
_stateCreator: StateCreator<ExampleState>,
_useBoundStore: UseBoundStore<StoreApi<ExampleState>>,
) {
expect(true).toBeTruthy()
}
checkAllTypes(
storeApi.getState,
Math.random() > 0.5 ? partial : partialFn,
storeApi.setState,
storeApi.getState(),
listener,
selector,
storeApi,
storeApi.subscribe,
equalityFn,
stateCreator,
useBoundStore,
)
})
type AssertEqual<Type, Expected> = Type extends Expected
? Expected extends Type
? true
: never
: never
it('should have correct (partial) types for setState', () => {
type Count = { count: number }
const store = create<Count>((set) => ({
count: 0,
// @ts-expect-error we shouldn't be able to set count to undefined
a: () => set(() => ({ count: undefined })),
// @ts-expect-error we shouldn't be able to set count to undefined
b: () => set({ count: undefined }),
c: () => set({ count: 1 }),
}))
const setState: AssertEqual<
typeof store.setState,
StoreApi<Count>['setState']
> = true
expect(setState).toEqual(true)
// ok, should not error
store.setState({ count: 1 })
store.setState({})
store.setState((previous) => previous)
// @ts-expect-error type undefined is not assignable to type number
store.setState({ count: undefined })
// @ts-expect-error type undefined is not assignable to type number
store.setState((state) => ({ ...state, count: undefined }))
})
it('should allow for different partial keys to be returnable from setState', () => {
type State = {
count: number
something: string
}
const store = create<State>(() => ({
count: 0,
something: 'foo',
}))
const setState: AssertEqual<
typeof store.setState,
StoreApi<State>['setState']
> = true
expect(setState).toEqual(true)
// ok, should not error
store.setState((previous) => {
if (previous.count === 0) {
return { count: 1 }
}
return { count: 0 }
})
store.setState((previous) => {
if (previous.count === 0) {
return { count: 1 }
}
if (previous.count === 1) {
return previous
}
return { something: 'foo' }
})
// @ts-expect-error Type '{ something: boolean; count?: undefined; }' is not assignable to type 'State'.
store.setState((previous) => {
if (previous.count === 0) {
return { count: 1 }
}
return { something: true }
})
})
it('state is covariant', () => {
const store = create<{ count: number; foo: string }>()(() => ({
count: 0,
foo: '',
}))
const _testIsCovariant: StoreApi<{ count: number }> = store
// @ts-expect-error should not compile
const _testIsNotContravariant: StoreApi<{
count: number
foo: string
baz: string
}> = store
})
it('StateCreator<T, [StoreMutatorIdentfier, unknown][]> is StateCreator<T, []>', () => {
interface State {
count: number
increment: () => void
}
const foo: <M extends [StoreMutatorIdentifier, unknown][]>() => StateCreator<
State,
M
> = () => (set, get) => ({
count: 0,
increment: () => {
set({ count: get().count + 1 })
},
})
create<State>()(persist(foo(), { name: 'prefix' }))
})
it('StateCreator subtyping', () => {
interface State {
count: number
increment: () => void
}
const foo: () => StateCreator<State, []> = () => (set, get) => ({
count: 0,
increment: () => {
set({ count: get().count + 1 })
},
})
create<State>()(persist(foo(), { name: 'prefix' }))
const _testSubtyping: StateCreator<State, [['zustand/persist', unknown]]> =
{} as StateCreator<State, []>
})
it('set state exists on store with readonly store', () => {
interface State {
count: number
increment: () => void
}
const useStore = create<State>()((set, get) => ({
count: 0,
increment: () => set({ count: get().count + 1 }),
}))
useStore.setState((state) => ({ ...state, count: state.count + 1 }))
})