@sanity/sdk 0.0.0-alpha.1

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.
Files changed (36) hide show
  1. package/dist/index.d.ts +339 -0
  2. package/dist/index.js +492 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +77 -0
  5. package/src/_exports/index.ts +39 -0
  6. package/src/auth/authStore.test.ts +296 -0
  7. package/src/auth/authStore.ts +125 -0
  8. package/src/auth/getAuthStore.test.ts +14 -0
  9. package/src/auth/getInternalAuthStore.ts +20 -0
  10. package/src/auth/internalAuthStore.test.ts +334 -0
  11. package/src/auth/internalAuthStore.ts +519 -0
  12. package/src/client/getClient.test.ts +41 -0
  13. package/src/client/getClient.ts +13 -0
  14. package/src/client/getSubscribableClient.test.ts +71 -0
  15. package/src/client/getSubscribableClient.ts +17 -0
  16. package/src/client/store/actions/getClientEvents.test.ts +95 -0
  17. package/src/client/store/actions/getClientEvents.ts +33 -0
  18. package/src/client/store/actions/getOrCreateClient.test.ts +56 -0
  19. package/src/client/store/actions/getOrCreateClient.ts +40 -0
  20. package/src/client/store/actions/receiveToken.test.ts +18 -0
  21. package/src/client/store/actions/receiveToken.ts +31 -0
  22. package/src/client/store/clientStore.test.ts +152 -0
  23. package/src/client/store/clientStore.ts +98 -0
  24. package/src/documentList/documentListStore.test.ts +575 -0
  25. package/src/documentList/documentListStore.ts +269 -0
  26. package/src/documents/.keep +0 -0
  27. package/src/instance/identity.test.ts +46 -0
  28. package/src/instance/identity.ts +28 -0
  29. package/src/instance/sanityInstance.test.ts +66 -0
  30. package/src/instance/sanityInstance.ts +64 -0
  31. package/src/instance/types.d.ts +29 -0
  32. package/src/schema/schemaStore.test.ts +30 -0
  33. package/src/schema/schemaStore.ts +32 -0
  34. package/src/store/createStore.test.ts +108 -0
  35. package/src/store/createStore.ts +106 -0
  36. package/src/tsdoc.json +39 -0
@@ -0,0 +1,269 @@
1
+ import type {SyncTag} from '@sanity/client'
2
+ import {isEqual} from 'lodash-es'
3
+ import {
4
+ distinctUntilChanged,
5
+ map,
6
+ Observable,
7
+ pairwise,
8
+ startWith,
9
+ type Subscribable,
10
+ switchMap,
11
+ tap,
12
+ } from 'rxjs'
13
+ import {devtools} from 'zustand/middleware'
14
+ import {createStore} from 'zustand/vanilla'
15
+
16
+ import {getClientStore} from '../client/store/clientStore'
17
+ import type {SanityInstance} from '../instance/types'
18
+
19
+ const PAGE_SIZE = 50
20
+
21
+ const API_VERSION = 'vX'
22
+
23
+ /**
24
+ * Represents an identifier to a Sanity document, containing its `_id` to pull
25
+ * the document from content lake and its `_type` to look up its schema type.
26
+ * @public
27
+ */
28
+ export interface DocumentHandle {
29
+ _id: string
30
+ _type: string
31
+ }
32
+
33
+ /**
34
+ * Represents the current state of a document list, including the query options
35
+ * and loading status.
36
+ * @public
37
+ */
38
+ export interface DocumentListState extends DocumentListOptions {
39
+ /** Array of document handles in the current result set, or null if not yet loaded */
40
+ result: DocumentHandle[] | null
41
+ /** Indicates whether the document list is currently loading */
42
+ isPending: boolean
43
+ }
44
+
45
+ /**
46
+ * Represents a sort ordering configuration.
47
+ * @public
48
+ */
49
+ export interface SortOrderingItem {
50
+ field: string
51
+ direction: 'asc' | 'desc'
52
+ }
53
+
54
+ /**
55
+ * Configuration options for filtering and sorting documents in a document list.
56
+ * @public
57
+ */
58
+ export interface DocumentListOptions {
59
+ /** GROQ filter expression to query specific documents */
60
+ filter?: string
61
+ /** Array of sort ordering specifications to determine the order of results */
62
+ sort?: SortOrderingItem[]
63
+ }
64
+
65
+ /**
66
+ * Manages the state and operations for a list of Sanity documents.
67
+ * Provides methods to update options, load more documents, and subscribe to
68
+ * state changes.
69
+ *
70
+ * Implements a subscription model where you can register callback functions
71
+ * to be notified when the document list state changes.
72
+ * @public
73
+ */
74
+ export interface DocumentListStore extends Subscribable<DocumentListState> {
75
+ /** Updates the filtering and sorting options for the document list */
76
+ setOptions: (options: DocumentListOptions) => void
77
+ /** Retrieves the current state of the document list synchronously */
78
+ getCurrent: () => DocumentListState
79
+ /** Loads the next page of documents */
80
+ loadMore: () => void
81
+ /** Cleans up resources and subscriptions when the store is no longer needed */
82
+ dispose: () => void
83
+ }
84
+
85
+ interface DocumentListInternalState extends DocumentListOptions {
86
+ isPending: boolean
87
+ lastLiveEventId?: string
88
+ syncTags: Set<SyncTag>
89
+ limit: number
90
+ result: DocumentHandle[] | null
91
+ }
92
+
93
+ /**
94
+ * Creates a `DocumentListStore` from a `SanityInstance`.
95
+ *
96
+ * @public
97
+ *
98
+ * See {@link SanityInstance} and {@link DocumentListStore}
99
+ */
100
+ export function createDocumentListStore(instance: SanityInstance): DocumentListStore {
101
+ const clientStore = getClientStore(instance)
102
+ const clientStream$ = new Observable(
103
+ clientStore.getClientEvents({apiVersion: API_VERSION}).subscribe,
104
+ )
105
+
106
+ const initialState: DocumentListInternalState = {
107
+ syncTags: new Set<SyncTag>(),
108
+ isPending: false,
109
+ result: null,
110
+ limit: PAGE_SIZE,
111
+ }
112
+
113
+ const store = createStore<DocumentListInternalState>()(
114
+ devtools((..._unusedArgs) => initialState, {
115
+ name: 'SanityDocumentListStore',
116
+ enabled: true, // Should be process.env.NODE_ENV === 'development'
117
+ }),
118
+ )
119
+
120
+ const liveSubscription = clientStream$
121
+ .pipe(
122
+ switchMap((client) => client.live.events({includeDrafts: true, tag: 'sdk.live-listener'})),
123
+ )
124
+ .subscribe({
125
+ next: (event) => {
126
+ const {syncTags} = store.getState()
127
+ if (event.type === 'message' && event.tags.some((tag) => syncTags.has(tag))) {
128
+ store.setState(
129
+ {lastLiveEventId: event.id},
130
+ false,
131
+ 'UPDATE_EVENT_ID_FROM_LIVE_CONTENT_API',
132
+ )
133
+ }
134
+ },
135
+ })
136
+
137
+ const resultSubscription = new Observable<DocumentListInternalState>((observer) =>
138
+ store.subscribe((state) => observer.next(state)),
139
+ )
140
+ .pipe(
141
+ distinctUntilChanged((prev, current) => {
142
+ if (prev.filter !== current.filter) return false
143
+ if (prev.lastLiveEventId !== current.lastLiveEventId) return false
144
+ if (!isEqual(prev.sort, current.sort)) return false
145
+ if (prev.limit !== current.limit) return false
146
+ return true
147
+ }),
148
+ tap(() => store.setState({isPending: true}, false, {type: 'START_FETCH'})),
149
+ switchMap((state) => {
150
+ const filter = state.filter ? `[${state.filter}]` : ''
151
+ const order = state.sort
152
+ ? `| order(${state.sort
153
+ .map((ordering) =>
154
+ [ordering.field, ordering.direction.toLowerCase()]
155
+ .map((str) => str.trim())
156
+ .filter(Boolean)
157
+ .join(' '),
158
+ )
159
+ .join(',')})`
160
+ : ''
161
+
162
+ return clientStream$.pipe(
163
+ switchMap((client) =>
164
+ client.observable.fetch(
165
+ `*${filter}${order}[0..$__limit]{_id, _type}`,
166
+ {__limit: state.limit},
167
+ {
168
+ filterResponse: false,
169
+ returnQuery: false,
170
+ lastLiveEventId: state.lastLiveEventId,
171
+ tag: 'sdk.document-list',
172
+ // // TODO: this should use the `previewDrafts` perspective for
173
+ // // removing duplicates in the result set but the live content API
174
+ // // does not currently return the correct sync tags. CLDX has
175
+ // // planned to add perspective support to the live content API
176
+ // // in december of 2024
177
+ // perspective: 'previewDrafts'
178
+ },
179
+ ),
180
+ ),
181
+ )
182
+ }),
183
+ )
184
+ .subscribe({
185
+ next: ({syncTags, result}) => {
186
+ store.setState(
187
+ {
188
+ syncTags: new Set(syncTags),
189
+ result,
190
+ isPending: false,
191
+ },
192
+ false,
193
+ {type: 'UPDATE_FROM_FETCH'},
194
+ )
195
+ },
196
+ })
197
+
198
+ function setOptions({filter, sort}: DocumentListOptions) {
199
+ store.setState(
200
+ {
201
+ // spreads properties only if they exist, preserving other state
202
+ // properties set in the zustand store already
203
+ ...(filter && {filter}),
204
+ ...(sort && {sort}),
205
+ },
206
+ false,
207
+ {type: 'SET_OPTIONS'},
208
+ )
209
+ }
210
+
211
+ function loadMore() {
212
+ store.setState((prev) => ({limit: prev.limit + PAGE_SIZE}), undefined, {type: 'LOAD_MORE'})
213
+ }
214
+
215
+ function getCurrent() {
216
+ const {isPending, result, filter, sort} = store.getState()
217
+ return {isPending, result, filter, sort}
218
+ }
219
+
220
+ const state$ = new Observable<DocumentListState>((observer) => {
221
+ function emitCurrent() {
222
+ const {isPending, result, filter, sort} = store.getState()
223
+ observer.next({
224
+ isPending,
225
+ result,
226
+ filter,
227
+ sort,
228
+ })
229
+ }
230
+
231
+ emitCurrent()
232
+ return store.subscribe(emitCurrent)
233
+ }).pipe(
234
+ distinctUntilChanged((prev, curr) => {
235
+ if (!isEqual(prev.sort, curr.sort)) return false
236
+ if (prev.result !== curr.result) return false
237
+ if (prev.filter !== curr.filter) return false
238
+ if (prev.isPending !== curr.isPending) return false
239
+ return true
240
+ }),
241
+ startWith(null),
242
+ pairwise(),
243
+ map((arg): DocumentListState => {
244
+ // casting this since `curr` will never be null
245
+ const [prev, curr] = arg as [DocumentListState | null, DocumentListState]
246
+ if (!prev?.result) return curr
247
+ if (!curr?.result) return curr
248
+
249
+ const prevMap = prev.result.reduce<Map<string, DocumentHandle>>((acc, handle) => {
250
+ acc.set(handle._id, handle)
251
+ return acc
252
+ }, new Map())
253
+
254
+ return {
255
+ ...curr,
256
+ result: curr.result.map((i) => prevMap.get(i._id) ?? i),
257
+ }
258
+ }),
259
+ )
260
+
261
+ const subscribe = state$.subscribe.bind(state$)
262
+
263
+ function dispose() {
264
+ liveSubscription.unsubscribe()
265
+ resultSubscription.unsubscribe()
266
+ }
267
+
268
+ return {setOptions, getCurrent, loadMore, subscribe, dispose}
269
+ }
File without changes
@@ -0,0 +1,46 @@
1
+ import {describe, expect, it} from 'vitest'
2
+
3
+ import {getSdkIdentity} from './identity'
4
+
5
+ describe('identity', () => {
6
+ describe('getSdkIdentity', () => {
7
+ it('creates a frozen object with expected properties', () => {
8
+ const identity = getSdkIdentity({
9
+ projectId: 'test-project',
10
+ dataset: 'test-dataset',
11
+ })
12
+
13
+ // Check if object is frozen
14
+ expect(Object.isFrozen(identity)).toBe(true)
15
+
16
+ // Check if all expected properties exist
17
+ expect(identity).toHaveProperty('id')
18
+ expect(identity).toHaveProperty('projectId', 'test-project')
19
+ expect(identity).toHaveProperty('dataset', 'test-dataset')
20
+ })
21
+
22
+ it('generates unique ids for different instances', () => {
23
+ const identity1 = getSdkIdentity({
24
+ projectId: 'test-project',
25
+ dataset: 'test-dataset',
26
+ })
27
+
28
+ const identity2 = getSdkIdentity({
29
+ projectId: 'test-project',
30
+ dataset: 'test-dataset',
31
+ })
32
+
33
+ expect(identity1.id).not.toBe(identity2.id)
34
+ })
35
+
36
+ it('generates id with correct format', () => {
37
+ const identity = getSdkIdentity({
38
+ projectId: 'test-project',
39
+ dataset: 'test-dataset',
40
+ })
41
+
42
+ // ID should be 16 characters long (8 pairs of hex digits)
43
+ expect(identity.id).toMatch(/^[0-9a-f]{16}$/)
44
+ })
45
+ })
46
+ })
@@ -0,0 +1,28 @@
1
+ import type {SdkIdentity} from './types'
2
+
3
+ /**
4
+ * thoughtLevel 2 - Primarily what we want is to have an object that we can bind stores/memoizations to, but let the things that depend on it (eg projectId, dataset) be internals that can't be overwritten by accident/intentionally
5
+ * @public
6
+ */
7
+ export function getSdkIdentity({
8
+ projectId,
9
+ dataset,
10
+ }: {
11
+ projectId: string
12
+ dataset: string
13
+ }): SdkIdentity {
14
+ const id = generateId()
15
+ return Object.freeze({
16
+ id,
17
+ projectId,
18
+ dataset,
19
+ })
20
+ }
21
+
22
+ function generateId() {
23
+ return Array.from({length: 8}, () =>
24
+ Math.floor(Math.random() * 16)
25
+ .toString(16)
26
+ .padStart(2, '0'),
27
+ ).join('')
28
+ }
@@ -0,0 +1,66 @@
1
+ import {beforeEach, describe, expect, test} from 'vitest'
2
+
3
+ import {createSanityInstance, getOrCreateResource, type SanityConfig} from './sanityInstance'
4
+
5
+ describe('sanityInstance', () => {
6
+ let config: SanityConfig
7
+
8
+ beforeEach(() => {
9
+ config = {
10
+ projectId: 'test-project',
11
+ dataset: 'test-dataset',
12
+ }
13
+ })
14
+
15
+ describe('createSanityInstance', () => {
16
+ test('creates instance with correct configuration', () => {
17
+ const instance = createSanityInstance(config)
18
+
19
+ expect(instance.identity).toEqual(
20
+ expect.objectContaining({
21
+ projectId: 'test-project',
22
+ dataset: 'test-dataset',
23
+ }),
24
+ )
25
+ })
26
+ })
27
+
28
+ describe('getOrCreateResource', () => {
29
+ test('creates and caches resource', () => {
30
+ const instance = createSanityInstance(config)
31
+ let createCount = 0
32
+
33
+ const creator = () => {
34
+ createCount++
35
+ return {value: 'test-resource'}
36
+ }
37
+
38
+ // First call should create new resource
39
+ const resource1 = getOrCreateResource(instance, 'test-key', creator)
40
+ expect(resource1).toEqual({value: 'test-resource'})
41
+ expect(createCount).toBe(1)
42
+
43
+ // Second call should return cached resource
44
+ const resource2 = getOrCreateResource(instance, 'test-key', creator)
45
+ expect(resource2).toBe(resource1)
46
+ expect(createCount).toBe(1)
47
+ })
48
+
49
+ test('different instances have separate resource caches', () => {
50
+ const instance1 = createSanityInstance({...config, projectId: 'project1'})
51
+ const instance2 = createSanityInstance({...config, projectId: 'project2'})
52
+ let createCount = 0
53
+
54
+ const creator = () => {
55
+ createCount++
56
+ return {value: 'test-resource'}
57
+ }
58
+
59
+ const resource1 = getOrCreateResource(instance1, 'test-key', creator)
60
+ const resource2 = getOrCreateResource(instance2, 'test-key', creator)
61
+
62
+ expect(resource1).not.toBe(resource2)
63
+ expect(createCount).toBe(2)
64
+ })
65
+ })
66
+ })
@@ -0,0 +1,64 @@
1
+ import type {AuthConfig} from '../auth/internalAuthStore'
2
+ import {getSdkIdentity} from './identity'
3
+ import type {SanityInstance, SdkIdentity} from './types'
4
+
5
+ /**
6
+ * @public
7
+ */
8
+ export interface SanityConfig {
9
+ projectId: string
10
+ dataset: string
11
+ auth?: AuthConfig
12
+ }
13
+
14
+ /**
15
+ * Returns a new instance of dependencies required for SanitySDK.
16
+ *
17
+ * @public
18
+ *
19
+ * @param config - The configuration for this instance
20
+ *
21
+ * @returns A new "instance" of a Sanity SDK, used to bind resources/configuration to it
22
+ */
23
+ export function createSanityInstance({
24
+ projectId = '',
25
+ dataset = '',
26
+ ...config
27
+ }: SanityConfig): SanityInstance {
28
+ return {
29
+ identity: getSdkIdentity({projectId, dataset}),
30
+ config,
31
+ }
32
+ }
33
+
34
+ const resourceStorage = new WeakMap<SdkIdentity, Map<string, unknown>>()
35
+
36
+ function getResource(instance: SanityInstance, key: string) {
37
+ const instanceMap = resourceStorage.get(instance.identity)
38
+ return instanceMap?.get(key)
39
+ }
40
+
41
+ function setResource(instance: SanityInstance, key: string, value: unknown) {
42
+ let instanceMap = resourceStorage.get(instance.identity)
43
+ if (!instanceMap) {
44
+ instanceMap = new Map()
45
+ resourceStorage.set(instance.identity, instanceMap)
46
+ }
47
+ instanceMap.set(key, value)
48
+ }
49
+
50
+ /**
51
+ * This is an internal function that retrieves or creates a Zustand store resource.
52
+ * @internal
53
+ */
54
+ export function getOrCreateResource<T>(instance: SanityInstance, key: string, creator: () => T): T {
55
+ const cached = getResource(instance, key)
56
+
57
+ if (cached) {
58
+ return cached as T
59
+ }
60
+
61
+ const resource = creator()
62
+ setResource(instance, key, resource)
63
+ return resource
64
+ }
@@ -0,0 +1,29 @@
1
+ import type {ClientStore} from '../client/store/clientStore'
2
+ import type {SchemaStore} from '../schema/schemaStore'
3
+ import type {SanityConfig} from './sanityInstance'
4
+
5
+ /** @internal */
6
+ export interface InternalStores {
7
+ clientStore?: ClientStore
8
+ schemaStore?: SchemaStore
9
+ }
10
+
11
+ /** @public */
12
+ export interface SanityInstance {
13
+ /**
14
+ * The following is used to look up resources associated with this instance,
15
+ * and can be used to retrieve an "id" for the instance - useful in debugging.
16
+ *
17
+ * @public
18
+ */
19
+ readonly identity: SdkIdentity
20
+
21
+ config: Omit<SanityConfig, 'projectId' | 'dataset'>
22
+ }
23
+
24
+ /** @public */
25
+ export interface SdkIdentity {
26
+ readonly id: string
27
+ readonly projectId: string
28
+ readonly dataset: string
29
+ }
@@ -0,0 +1,30 @@
1
+ import {describe, expect, it} from 'vitest'
2
+
3
+ import {createSchemaStore} from './schemaStore'
4
+
5
+ describe('schemaStore', () => {
6
+ it('should create a store with initial schema types', () => {
7
+ const mockTypes = [{name: 'post', type: 'document'}]
8
+ const store = createSchemaStore(mockTypes)
9
+
10
+ expect(store.getState().schema).toEqual({types: mockTypes})
11
+ })
12
+
13
+ it('should update schema when setSchema is called', () => {
14
+ const store = createSchemaStore([])
15
+ const newSchema = {types: [{name: 'author', type: 'document'}]}
16
+
17
+ store.getState().setSchema(newSchema)
18
+
19
+ expect(store.getState().schema).toEqual(newSchema)
20
+ })
21
+
22
+ it('should maintain store reference when updating schema', () => {
23
+ const store = createSchemaStore([])
24
+ const initialStoreRef = store
25
+
26
+ store.getState().setSchema({types: []})
27
+
28
+ expect(store).toBe(initialStoreRef)
29
+ })
30
+ })
@@ -0,0 +1,32 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type {StoreApi} from 'zustand'
3
+ import {devtools} from 'zustand/middleware'
4
+ import {createStore} from 'zustand/vanilla'
5
+
6
+ /** @public */
7
+ export interface SchemaState {
8
+ schema: any
9
+ setSchema: (newSchema: any) => void
10
+ }
11
+
12
+ /** @public */
13
+ export type SchemaStore = StoreApi<SchemaState>
14
+
15
+ /**
16
+ * This is an internal function that creates a Zustand store for the schema.
17
+ * @internal
18
+ */
19
+ export const createSchemaStore = (schemaTypes: any[]): SchemaStore => {
20
+ return createStore<SchemaState>()(
21
+ devtools(
22
+ (set) => ({
23
+ schema: {types: schemaTypes},
24
+ setSchema: (newSchema) => set({schema: newSchema}, false, 'setSchema'),
25
+ }),
26
+ {
27
+ name: 'SanitySchemaStore',
28
+ enabled: true, // Should be process.env.NODE_ENV === 'development'
29
+ },
30
+ ),
31
+ )
32
+ }
@@ -0,0 +1,108 @@
1
+ import {describe, expect, it, vi} from 'vitest'
2
+
3
+ import type {SanityInstance} from '../instance/types'
4
+ import {createStore, type StoreActionContext} from './createStore'
5
+
6
+ // Mock the devtools middleware
7
+ vi.mock('zustand/middleware', () => ({
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ devtools: (storeFunction: any) => {
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ return (...args: any[]) => storeFunction(...args)
12
+ },
13
+ }))
14
+
15
+ beforeEach(() => {
16
+ vi.clearAllMocks()
17
+ })
18
+
19
+ describe('createStore', () => {
20
+ // Setup mock instance
21
+ const mockInstance = {
22
+ identity: {
23
+ id: 'test-id',
24
+ },
25
+ } as SanityInstance
26
+
27
+ // Setup types for our test store
28
+ interface TestState {
29
+ count: number
30
+ text: string
31
+ }
32
+
33
+ const initialState: TestState = {
34
+ count: 0,
35
+ text: '',
36
+ }
37
+
38
+ // Setup test actions
39
+ const createTestActions = () => ({
40
+ increment: ({store}: StoreActionContext<TestState>) => {
41
+ const state = store.getState()
42
+ store.setState({...state, count: state.count + 1})
43
+ },
44
+ setText: ({store}: StoreActionContext<TestState>, newText: string) => {
45
+ const state = store.getState()
46
+ store.setState({...state, text: newText})
47
+ return newText
48
+ },
49
+ getCount: ({store}: StoreActionContext<TestState>) => {
50
+ return store.getState().count
51
+ },
52
+ getText: ({store}: StoreActionContext<TestState>) => {
53
+ return store.getState().text
54
+ },
55
+ })
56
+
57
+ it('creates a store with initial state with all properties', () => {
58
+ const actions = createTestActions()
59
+ const store = createStore(initialState, actions, {
60
+ name: 'test-store',
61
+ instance: mockInstance,
62
+ })
63
+
64
+ // Verify store has all action methods
65
+ expect(store).toHaveProperty('increment')
66
+ expect(store).toHaveProperty('setText')
67
+ expect(store).toHaveProperty('getCount')
68
+ })
69
+
70
+ it('maintains separate state for different stores', () => {
71
+ const actions = createTestActions()
72
+ const store1 = createStore(initialState, actions, {
73
+ name: 'store-1',
74
+ instance: mockInstance,
75
+ })
76
+ const store2 = createStore(initialState, actions, {
77
+ name: 'store-2',
78
+ instance: mockInstance,
79
+ })
80
+
81
+ store1.increment()
82
+ store2.setText('hello')
83
+
84
+ // Verify stores are unaffected
85
+ expect(store1.getCount()).toBe(1)
86
+ expect(store2.getCount()).toBe(0)
87
+
88
+ expect(store1.getText()).toBe('')
89
+ expect(store2.getText()).toBe('hello')
90
+ })
91
+
92
+ it('correctly updates state through actions', () => {
93
+ const actions = createTestActions()
94
+ const store = createStore(initialState, actions, {
95
+ name: 'test-store',
96
+ instance: mockInstance,
97
+ })
98
+
99
+ store.increment()
100
+ expect(store.getCount()).toBe(1)
101
+
102
+ store.increment()
103
+ expect(store.getCount()).toBe(2)
104
+
105
+ store.setText('hello world')
106
+ expect(store.getText()).toBe('hello world')
107
+ })
108
+ })