@sanity/sdk-react 0.0.0-alpha.7 → 0.0.0-alpha.9

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.
@@ -0,0 +1,134 @@
1
+ // tests/useEditDocument.test.ts
2
+ import {
3
+ createSanityInstance,
4
+ editDocument,
5
+ getDocumentState,
6
+ resolveDocument,
7
+ type StateSource,
8
+ } from '@sanity/sdk'
9
+ import {type SanityDocument} from '@sanity/types'
10
+ import {renderHook} from '@testing-library/react'
11
+ import {beforeEach, describe, expect, it, vi} from 'vitest'
12
+
13
+ import {useSanityInstance} from '../context/useSanityInstance'
14
+ import {useApplyActions} from './useApplyActions'
15
+ import {useEditDocument} from './useEditDocument'
16
+
17
+ vi.mock('@sanity/sdk', async (importOriginal) => {
18
+ const original = await importOriginal<typeof import('@sanity/sdk')>()
19
+ return {
20
+ ...original,
21
+ getDocumentState: vi.fn(),
22
+ resolveDocument: vi.fn(),
23
+ editDocument: vi.fn(original.editDocument),
24
+ }
25
+ })
26
+
27
+ vi.mock('../context/useSanityInstance', () => ({
28
+ useSanityInstance: vi.fn(),
29
+ }))
30
+
31
+ vi.mock('./useApplyActions', () => ({
32
+ useApplyActions: vi.fn(),
33
+ }))
34
+
35
+ // Create a fake instance to be returned by useSanityInstance.
36
+ const instance = createSanityInstance({projectId: 'p', dataset: 'd'})
37
+
38
+ const doc: SanityDocument = {
39
+ _id: 'doc1',
40
+ foo: 'bar',
41
+ _type: 'book',
42
+ _rev: 'tx0',
43
+ _createdAt: '2025-02-06T00:11:00.000Z',
44
+ _updatedAt: '2025-02-06T00:11:00.000Z',
45
+ }
46
+
47
+ describe('useEditDocument hook', () => {
48
+ beforeEach(() => {
49
+ vi.clearAllMocks()
50
+ vi.mocked(useSanityInstance).mockReturnValue(instance)
51
+ })
52
+
53
+ it('applies a single edit action for the given path', async () => {
54
+ const getCurrent = vi.fn().mockReturnValue(doc)
55
+ const subscribe = vi.fn().mockReturnValue(vi.fn())
56
+ vi.mocked(getDocumentState).mockReturnValue({
57
+ getCurrent,
58
+ subscribe,
59
+ } as unknown as StateSource<SanityDocument>)
60
+
61
+ const apply = vi.fn().mockResolvedValue({transactionId: 'tx1'})
62
+ vi.mocked(useApplyActions).mockReturnValue(apply)
63
+
64
+ const {result} = renderHook(() => useEditDocument('doc1', 'foo'))
65
+ const promise = result.current('newValue')
66
+ expect(editDocument).toHaveBeenCalledWith('doc1', {set: {foo: 'newValue'}})
67
+ expect(apply).toHaveBeenCalledWith(editDocument('doc1', {set: {foo: 'newValue'}}))
68
+ const actionsResult = await promise
69
+ expect(actionsResult).toEqual({transactionId: 'tx1'})
70
+ })
71
+
72
+ it('applies edit actions for changed fields', async () => {
73
+ // Set up current document state.
74
+ const currentDoc = {...doc, foo: 'bar', extra: 'old'}
75
+ const getCurrent = vi.fn().mockReturnValue(currentDoc)
76
+ const subscribe = vi.fn().mockReturnValue(vi.fn())
77
+ vi.mocked(getDocumentState).mockReturnValue({
78
+ getCurrent,
79
+ subscribe,
80
+ } as unknown as StateSource<SanityDocument>)
81
+
82
+ const apply = vi.fn().mockResolvedValue({transactionId: 'tx2'})
83
+ vi.mocked(useApplyActions).mockReturnValue(apply)
84
+
85
+ const {result} = renderHook(() => useEditDocument('doc1'))
86
+ const promise = result.current({foo: 'baz', extra: 'old', _id: 'doc1'})
87
+ expect(apply).toHaveBeenCalledWith([editDocument('doc1', {set: {foo: 'baz'}})])
88
+ const actionsResult = await promise
89
+ expect(actionsResult).toEqual({transactionId: 'tx2'})
90
+ })
91
+
92
+ it('throws an error if next value is not an object', () => {
93
+ const getCurrent = vi.fn().mockReturnValue(doc)
94
+ const subscribe = vi.fn().mockReturnValue(vi.fn())
95
+ vi.mocked(getDocumentState).mockReturnValue({
96
+ getCurrent,
97
+ subscribe,
98
+ } as unknown as StateSource<SanityDocument>)
99
+
100
+ const fakeApply = vi.fn()
101
+ vi.mocked(useApplyActions).mockReturnValue(fakeApply)
102
+
103
+ const {result} = renderHook(() => useEditDocument('doc1'))
104
+ expect(() => result.current('notAnObject' as unknown as Partial<SanityDocument>)).toThrowError(
105
+ 'No path was provided to `useEditDocument` and the value provided was not a document object.',
106
+ )
107
+ })
108
+
109
+ it('throws a promise (suspends) when the document is not ready', () => {
110
+ const getCurrent = vi.fn().mockReturnValue(undefined)
111
+ const subscribe = vi.fn().mockReturnValue(vi.fn())
112
+ vi.mocked(getDocumentState).mockReturnValue({
113
+ getCurrent,
114
+ subscribe,
115
+ } as unknown as StateSource<unknown>)
116
+
117
+ const resolveDocPromise = Promise.resolve(doc)
118
+
119
+ // Also, simulate resolveDocument to return a known promise.
120
+ vi.mocked(resolveDocument).mockReturnValue(resolveDocPromise)
121
+
122
+ // Render the hook and capture the thrown promise.
123
+ const {result} = renderHook(() => {
124
+ try {
125
+ return useEditDocument('doc1')
126
+ } catch (e) {
127
+ return e
128
+ }
129
+ })
130
+
131
+ // When the document is not ready, the hook throws the promise from resolveDocument.
132
+ expect(result.current).toBe(resolveDocPromise)
133
+ })
134
+ })
@@ -0,0 +1,67 @@
1
+ import {
2
+ type ActionsResult,
3
+ type DocumentHandle,
4
+ editDocument,
5
+ getDocumentState,
6
+ type JsonMatch,
7
+ type JsonMatchPath,
8
+ resolveDocument,
9
+ } from '@sanity/sdk'
10
+ import {type SanityDocument} from '@sanity/types'
11
+ import {useCallback} from 'react'
12
+
13
+ import {useSanityInstance} from '../context/useSanityInstance'
14
+ import {useApplyActions} from './useApplyActions'
15
+
16
+ const ignoredKeys = ['_id', '_type', '_createdAt', '_updatedAt', '_rev']
17
+
18
+ /** @beta */
19
+ export function useEditDocument<
20
+ TDocument extends SanityDocument,
21
+ TPath extends JsonMatchPath<TDocument>,
22
+ >(
23
+ doc: string | DocumentHandle<TDocument>,
24
+ path: TPath,
25
+ ): (nextValue: JsonMatch<TDocument, TPath>) => Promise<ActionsResult<TDocument>>
26
+ /** @beta */
27
+ export function useEditDocument<TDocument extends SanityDocument>(
28
+ doc: string | DocumentHandle<TDocument>,
29
+ ): (nextValue: Partial<TDocument>) => Promise<ActionsResult<TDocument>>
30
+ /** @beta */
31
+ export function useEditDocument(
32
+ doc: string | DocumentHandle,
33
+ path?: string,
34
+ ): (nextValue: unknown) => Promise<ActionsResult> {
35
+ const documentId = typeof doc === 'string' ? doc : doc._id
36
+ const instance = useSanityInstance()
37
+ const apply = useApplyActions()
38
+ const isDocumentReady = useCallback(
39
+ () => getDocumentState(instance, documentId).getCurrent() !== undefined,
40
+ [instance, documentId],
41
+ )
42
+ if (!isDocumentReady()) throw resolveDocument(instance, documentId)
43
+
44
+ return useCallback(
45
+ (next: unknown) => {
46
+ if (path) {
47
+ return apply(editDocument(documentId, {set: {[path]: next}}))
48
+ }
49
+
50
+ const current = getDocumentState(instance, documentId).getCurrent()
51
+
52
+ if (typeof next !== 'object' || !next) {
53
+ throw new Error(
54
+ `No path was provided to \`useEditDocument\` and the value provided was not a document object.`,
55
+ )
56
+ }
57
+
58
+ const editActions = Object.entries(next)
59
+ .filter(([key]) => !ignoredKeys.includes(key))
60
+ .filter(([key, value]) => current?.[key] !== value)
61
+ .map(([key, value]) => editDocument(documentId, {set: {[key]: value}}))
62
+
63
+ return apply(editActions)
64
+ },
65
+ [apply, documentId, instance, path],
66
+ )
67
+ }