@sanity/sdk-react 0.0.1 → 0.0.2
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.
- package/dist/index.d.ts +62 -14
- package/dist/index.js +112 -143
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/_exports/sdk-react.ts +2 -2
- package/src/hooks/comlink/useWindowConnection.test.tsx +145 -0
- package/src/hooks/comlink/useWindowConnection.ts +32 -30
- package/src/hooks/{comlink → dashboard}/useManageFavorite.test.ts +84 -134
- package/src/hooks/{comlink → dashboard}/useManageFavorite.ts +4 -39
- package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +2 -73
- package/src/hooks/dashboard/useNavigateToStudioDocument.ts +20 -27
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +69 -0
- package/src/hooks/{comlink → dashboard}/useRecordDocumentHistoryEvent.ts +17 -10
- package/src/hooks/dashboard/useStudioWorkspacesByProjectIdDataset.test.tsx +14 -85
- package/src/hooks/dashboard/useStudioWorkspacesByProjectIdDataset.ts +33 -8
- package/src/hooks/comlink/useRecordDocumentHistoryEvent.test.ts +0 -85
- package/src/hooks/comlink/useWindowConnection.test.ts +0 -135
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/sdk-react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Sanity SDK React toolkit for Content OS",
|
|
6
6
|
"keywords": [
|
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
"@types/lodash-es": "^4.17.12",
|
|
49
49
|
"groq": "3.86.2-experimental.0",
|
|
50
50
|
"lodash-es": "^4.17.21",
|
|
51
|
-
"react-compiler-runtime": "19.
|
|
51
|
+
"react-compiler-runtime": "19.1.0-rc.1",
|
|
52
52
|
"react-error-boundary": "^5.0.0",
|
|
53
53
|
"rxjs": "^7.8.2",
|
|
54
|
-
"@sanity/sdk": "0.0.
|
|
54
|
+
"@sanity/sdk": "0.0.2"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@sanity/browserslist-config": "^1.0.5",
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
"vitest": "^3.1.2",
|
|
77
77
|
"@repo/config-eslint": "0.0.0",
|
|
78
78
|
"@repo/package.bundle": "3.82.0",
|
|
79
|
-
"@repo/package.config": "0.0.1",
|
|
80
79
|
"@repo/config-test": "0.0.1",
|
|
80
|
+
"@repo/package.config": "0.0.1",
|
|
81
81
|
"@repo/tsconfig": "0.0.1"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
@@ -20,8 +20,6 @@ export {
|
|
|
20
20
|
useFrameConnection,
|
|
21
21
|
type UseFrameConnectionOptions,
|
|
22
22
|
} from '../hooks/comlink/useFrameConnection'
|
|
23
|
-
export {useManageFavorite} from '../hooks/comlink/useManageFavorite'
|
|
24
|
-
export {useRecordDocumentHistoryEvent} from '../hooks/comlink/useRecordDocumentHistoryEvent'
|
|
25
23
|
export {
|
|
26
24
|
useWindowConnection,
|
|
27
25
|
type UseWindowConnectionOptions,
|
|
@@ -29,10 +27,12 @@ export {
|
|
|
29
27
|
type WindowMessageHandler,
|
|
30
28
|
} from '../hooks/comlink/useWindowConnection'
|
|
31
29
|
export {useSanityInstance} from '../hooks/context/useSanityInstance'
|
|
30
|
+
export {useManageFavorite} from '../hooks/dashboard/useManageFavorite'
|
|
32
31
|
export {
|
|
33
32
|
type NavigateToStudioResult,
|
|
34
33
|
useNavigateToStudioDocument,
|
|
35
34
|
} from '../hooks/dashboard/useNavigateToStudioDocument'
|
|
35
|
+
export {useRecordDocumentHistoryEvent} from '../hooks/dashboard/useRecordDocumentHistoryEvent'
|
|
36
36
|
export {useStudioWorkspacesByProjectIdDataset} from '../hooks/dashboard/useStudioWorkspacesByProjectIdDataset'
|
|
37
37
|
export {useDatasets} from '../hooks/datasets/useDatasets'
|
|
38
38
|
export {useApplyDocumentActions} from '../hooks/document/useApplyDocumentActions'
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {type Message, type Node} from '@sanity/comlink'
|
|
2
|
+
import {getNodeState, type NodeState, type StateSource} from '@sanity/sdk'
|
|
3
|
+
import {screen} from '@testing-library/react'
|
|
4
|
+
import {Suspense} from 'react'
|
|
5
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
6
|
+
|
|
7
|
+
import {render, renderHook} from '../../../test/test-utils'
|
|
8
|
+
import {useWindowConnection} from './useWindowConnection'
|
|
9
|
+
|
|
10
|
+
vi.mock('@sanity/sdk', async () => {
|
|
11
|
+
const actual = await vi.importActual('@sanity/sdk')
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
getNodeState: vi.fn(),
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
interface TestMessage {
|
|
19
|
+
type: 'TEST_MESSAGE'
|
|
20
|
+
data: {someData: string}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface AnotherMessage {
|
|
24
|
+
type: 'ANOTHER_MESSAGE'
|
|
25
|
+
data: {otherData: number}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type TestMessages = TestMessage | AnotherMessage
|
|
29
|
+
|
|
30
|
+
describe('useWindowConnection', () => {
|
|
31
|
+
let node: Node<Message, Message>
|
|
32
|
+
let mockStateSource: StateSource<NodeState>
|
|
33
|
+
let stableNodeEntry: NodeState
|
|
34
|
+
|
|
35
|
+
function createMockNode() {
|
|
36
|
+
return {
|
|
37
|
+
on: vi.fn(() => () => {}),
|
|
38
|
+
post: vi.fn(),
|
|
39
|
+
stop: vi.fn(),
|
|
40
|
+
} as unknown as Node<Message, Message>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
node = createMockNode()
|
|
45
|
+
stableNodeEntry = {node, status: 'connected'}
|
|
46
|
+
mockStateSource = {
|
|
47
|
+
subscribe: vi.fn((callback) => {
|
|
48
|
+
callback?.(stableNodeEntry)
|
|
49
|
+
return () => {}
|
|
50
|
+
}),
|
|
51
|
+
getCurrent: vi.fn(() => stableNodeEntry),
|
|
52
|
+
observable: {subscribe: vi.fn(() => ({unsubscribe: () => {}}))},
|
|
53
|
+
} as unknown as StateSource<NodeState>
|
|
54
|
+
vi.mocked(getNodeState).mockReturnValue(mockStateSource)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should register message handlers', () => {
|
|
58
|
+
const mockHandler = vi.fn()
|
|
59
|
+
const mockData = {someData: 'test'}
|
|
60
|
+
|
|
61
|
+
renderHook(() =>
|
|
62
|
+
useWindowConnection<TestMessages, TestMessages>({
|
|
63
|
+
name: 'test',
|
|
64
|
+
connectTo: 'window',
|
|
65
|
+
onMessage: {
|
|
66
|
+
TEST_MESSAGE: mockHandler,
|
|
67
|
+
ANOTHER_MESSAGE: vi.fn(),
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
const onCallback = vi.mocked(node.on).mock.calls[0][1]
|
|
73
|
+
onCallback(mockData)
|
|
74
|
+
|
|
75
|
+
expect(mockHandler).toHaveBeenCalledWith(mockData)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should send messages through the node', () => {
|
|
79
|
+
const {result} = renderHook(() =>
|
|
80
|
+
useWindowConnection<TestMessages, TestMessages>({
|
|
81
|
+
name: 'test',
|
|
82
|
+
connectTo: 'window',
|
|
83
|
+
}),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
result.current.sendMessage('TEST_MESSAGE', {someData: 'test'})
|
|
87
|
+
expect(node.post).toHaveBeenCalledWith('TEST_MESSAGE', {someData: 'test'})
|
|
88
|
+
|
|
89
|
+
result.current.sendMessage('ANOTHER_MESSAGE', {otherData: 123})
|
|
90
|
+
expect(node.post).toHaveBeenCalledWith('ANOTHER_MESSAGE', {otherData: 123})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should suspend and render fallback when node state is undefined', () => {
|
|
94
|
+
const suspenderPromise = Promise.resolve('resolved')
|
|
95
|
+
const mockStateSourceWithUndefined = {
|
|
96
|
+
subscribe: vi.fn(),
|
|
97
|
+
getCurrent: vi.fn(() => undefined),
|
|
98
|
+
observable: {
|
|
99
|
+
pipe: vi.fn(() => ({
|
|
100
|
+
subscribe: vi.fn(() => ({unsubscribe: () => {}})),
|
|
101
|
+
toPromise: () => suspenderPromise,
|
|
102
|
+
})),
|
|
103
|
+
subscribe: vi.fn(() => ({unsubscribe: () => {}})),
|
|
104
|
+
},
|
|
105
|
+
} as unknown as StateSource<NodeState>
|
|
106
|
+
|
|
107
|
+
vi.mocked(getNodeState).mockReturnValue(mockStateSourceWithUndefined)
|
|
108
|
+
|
|
109
|
+
function TestComponent() {
|
|
110
|
+
useWindowConnection<TestMessages, TestMessages>({
|
|
111
|
+
name: 'test',
|
|
112
|
+
connectTo: 'window',
|
|
113
|
+
})
|
|
114
|
+
return <div>Loaded</div>
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
render(
|
|
118
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
119
|
+
<TestComponent />
|
|
120
|
+
</Suspense>,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
expect(screen.getByText('Loading...')).toBeInTheDocument()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('should call node.fetch with correct arguments and return its result', async () => {
|
|
127
|
+
const mockFetch = vi.fn().mockResolvedValue('fetch-result')
|
|
128
|
+
node.fetch = mockFetch
|
|
129
|
+
|
|
130
|
+
const {result} = renderHook(() =>
|
|
131
|
+
useWindowConnection<TestMessages, TestMessages>({
|
|
132
|
+
name: 'test',
|
|
133
|
+
connectTo: 'window',
|
|
134
|
+
}),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
const response = await result.current.fetch('TYPE', {foo: 'bar'}, {responseTimeout: 123})
|
|
138
|
+
expect(mockFetch).toHaveBeenCalledWith('TYPE', {foo: 'bar'}, {responseTimeout: 123})
|
|
139
|
+
expect(response).toBe('fetch-result')
|
|
140
|
+
|
|
141
|
+
const responseNoArgs = await result.current.fetch('TYPE')
|
|
142
|
+
expect(mockFetch).toHaveBeenCalledWith('TYPE', undefined, {})
|
|
143
|
+
expect(responseNoArgs).toBe('fetch-result')
|
|
144
|
+
})
|
|
145
|
+
})
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import {type MessageData, type
|
|
2
|
-
import {
|
|
1
|
+
import {type MessageData, type NodeInput} from '@sanity/comlink'
|
|
2
|
+
import {
|
|
3
|
+
type FrameMessage,
|
|
4
|
+
getNodeState,
|
|
5
|
+
type NodeState,
|
|
6
|
+
type SanityInstance,
|
|
7
|
+
type StateSource,
|
|
8
|
+
type WindowMessage,
|
|
9
|
+
} from '@sanity/sdk'
|
|
3
10
|
import {useCallback, useEffect, useRef} from 'react'
|
|
11
|
+
import {filter, firstValueFrom} from 'rxjs'
|
|
4
12
|
|
|
5
13
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
14
|
+
import {createStateSourceHook} from '../helpers/createStateSourceHook'
|
|
6
15
|
|
|
7
16
|
/**
|
|
8
17
|
* @internal
|
|
@@ -18,7 +27,6 @@ export interface UseWindowConnectionOptions<TMessage extends FrameMessage> {
|
|
|
18
27
|
name: string
|
|
19
28
|
connectTo: string
|
|
20
29
|
onMessage?: Record<TMessage['type'], WindowMessageHandler<TMessage>>
|
|
21
|
-
onStatus?: (status: Status) => void
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
/**
|
|
@@ -40,6 +48,18 @@ export interface WindowConnection<TMessage extends WindowMessage> {
|
|
|
40
48
|
) => Promise<TResponse>
|
|
41
49
|
}
|
|
42
50
|
|
|
51
|
+
const useNodeState = createStateSourceHook({
|
|
52
|
+
getState: getNodeState as (
|
|
53
|
+
instance: SanityInstance,
|
|
54
|
+
nodeInput: NodeInput,
|
|
55
|
+
) => StateSource<NodeState>,
|
|
56
|
+
shouldSuspend: (instance: SanityInstance, nodeInput: NodeInput) =>
|
|
57
|
+
getNodeState(instance, nodeInput).getCurrent() === undefined,
|
|
58
|
+
suspender: (instance: SanityInstance, nodeInput: NodeInput) => {
|
|
59
|
+
return firstValueFrom(getNodeState(instance, nodeInput).observable.pipe(filter(Boolean)))
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
|
|
43
63
|
/**
|
|
44
64
|
* @internal
|
|
45
65
|
* Hook to wrap a Comlink node in a React hook.
|
|
@@ -56,47 +76,32 @@ export function useWindowConnection<
|
|
|
56
76
|
name,
|
|
57
77
|
connectTo,
|
|
58
78
|
onMessage,
|
|
59
|
-
onStatus,
|
|
60
79
|
}: UseWindowConnectionOptions<TFrameMessage>): WindowConnection<TWindowMessage> {
|
|
61
|
-
const
|
|
80
|
+
const {node} = useNodeState({name, connectTo})
|
|
62
81
|
const messageUnsubscribers = useRef<(() => void)[]>([])
|
|
63
82
|
const instance = useSanityInstance()
|
|
64
83
|
|
|
65
84
|
useEffect(() => {
|
|
66
|
-
const node = getOrCreateNode(instance, {
|
|
67
|
-
name,
|
|
68
|
-
connectTo,
|
|
69
|
-
}) as unknown as Node<TWindowMessage, TFrameMessage>
|
|
70
|
-
nodeRef.current = node
|
|
71
|
-
|
|
72
|
-
const statusUnsubscribe = node.onStatus((eventStatus) => {
|
|
73
|
-
onStatus?.(eventStatus)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
85
|
if (onMessage) {
|
|
77
86
|
Object.entries(onMessage).forEach(([type, handler]) => {
|
|
78
87
|
const messageUnsubscribe = node.on(type, handler as WindowMessageHandler<TFrameMessage>)
|
|
79
|
-
|
|
88
|
+
if (messageUnsubscribe) {
|
|
89
|
+
messageUnsubscribers.current.push(messageUnsubscribe)
|
|
90
|
+
}
|
|
80
91
|
})
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
return () => {
|
|
84
|
-
statusUnsubscribe()
|
|
85
95
|
messageUnsubscribers.current.forEach((unsubscribe) => unsubscribe())
|
|
86
96
|
messageUnsubscribers.current = []
|
|
87
|
-
releaseNode(instance, name)
|
|
88
|
-
nodeRef.current = null
|
|
89
97
|
}
|
|
90
|
-
}, [instance, name,
|
|
98
|
+
}, [instance, name, onMessage, node])
|
|
91
99
|
|
|
92
100
|
const sendMessage = useCallback(
|
|
93
101
|
(type: TWindowMessage['type'], data?: Extract<TWindowMessage, {type: typeof type}>['data']) => {
|
|
94
|
-
|
|
95
|
-
throw new Error('Cannot send message before connection is established')
|
|
96
|
-
}
|
|
97
|
-
nodeRef.current.post(type, data)
|
|
102
|
+
node.post(type, data)
|
|
98
103
|
},
|
|
99
|
-
[],
|
|
104
|
+
[node],
|
|
100
105
|
)
|
|
101
106
|
|
|
102
107
|
const fetch = useCallback(
|
|
@@ -109,12 +114,9 @@ export function useWindowConnection<
|
|
|
109
114
|
suppressWarnings?: boolean
|
|
110
115
|
},
|
|
111
116
|
): Promise<TResponse> => {
|
|
112
|
-
|
|
113
|
-
throw new Error('Cannot fetch before connection is established')
|
|
114
|
-
}
|
|
115
|
-
return nodeRef.current?.fetch(type, data, fetchOptions ?? {}) as Promise<TResponse>
|
|
117
|
+
return node.fetch(type, data, fetchOptions ?? {}) as Promise<TResponse>
|
|
116
118
|
},
|
|
117
|
-
[],
|
|
119
|
+
[node],
|
|
118
120
|
)
|
|
119
121
|
return {
|
|
120
122
|
sendMessage,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {type Message
|
|
1
|
+
import {type Message} from '@sanity/comlink'
|
|
2
2
|
import {
|
|
3
3
|
type FavoriteStatusResponse,
|
|
4
4
|
getFavoritesState,
|
|
5
|
-
getOrCreateNode,
|
|
6
5
|
resolveFavoritesState,
|
|
7
6
|
type SanityInstance,
|
|
8
7
|
} from '@sanity/sdk'
|
|
@@ -10,6 +9,7 @@ import {BehaviorSubject} from 'rxjs'
|
|
|
10
9
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
11
10
|
|
|
12
11
|
import {act, renderHook} from '../../../test/test-utils'
|
|
12
|
+
import {useWindowConnection, type WindowConnection} from '../comlink/useWindowConnection'
|
|
13
13
|
import {useSanityInstance} from '../context/useSanityInstance'
|
|
14
14
|
import {useManageFavorite} from './useManageFavorite'
|
|
15
15
|
|
|
@@ -17,8 +17,6 @@ vi.mock(import('@sanity/sdk'), async (importOriginal) => {
|
|
|
17
17
|
const actual = await importOriginal()
|
|
18
18
|
return {
|
|
19
19
|
...actual,
|
|
20
|
-
getOrCreateNode: vi.fn(),
|
|
21
|
-
releaseNode: vi.fn(),
|
|
22
20
|
getFavoritesState: vi.fn(),
|
|
23
21
|
resolveFavoritesState: vi.fn(),
|
|
24
22
|
}
|
|
@@ -26,10 +24,14 @@ vi.mock(import('@sanity/sdk'), async (importOriginal) => {
|
|
|
26
24
|
|
|
27
25
|
vi.mock('../context/useSanityInstance')
|
|
28
26
|
|
|
27
|
+
vi.mock('../comlink/useWindowConnection', () => ({
|
|
28
|
+
useWindowConnection: vi.fn(),
|
|
29
|
+
}))
|
|
30
|
+
|
|
29
31
|
describe('useManageFavorite', () => {
|
|
30
|
-
let node: Node<Message, Message>
|
|
31
|
-
let statusCallback: ((status: Status) => void) | null = null
|
|
32
32
|
let favoriteStatusSubject: BehaviorSubject<FavoriteStatusResponse>
|
|
33
|
+
let mockFetch: ReturnType<typeof vi.fn>
|
|
34
|
+
let mockSendMessage: ReturnType<typeof vi.fn>
|
|
33
35
|
|
|
34
36
|
const mockDocumentHandle = {
|
|
35
37
|
documentId: 'mock-id',
|
|
@@ -37,23 +39,8 @@ describe('useManageFavorite', () => {
|
|
|
37
39
|
resourceType: 'studio' as const,
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
function createMockNode() {
|
|
41
|
-
return {
|
|
42
|
-
on: vi.fn(() => () => {}),
|
|
43
|
-
fetch: vi.fn().mockImplementation(() => Promise.resolve({success: true})),
|
|
44
|
-
stop: vi.fn(),
|
|
45
|
-
onStatus: vi.fn((callback) => {
|
|
46
|
-
statusCallback = callback
|
|
47
|
-
return () => {}
|
|
48
|
-
}),
|
|
49
|
-
} as unknown as Node<Message, Message>
|
|
50
|
-
}
|
|
51
|
-
|
|
52
42
|
beforeEach(() => {
|
|
53
|
-
statusCallback = null
|
|
54
43
|
favoriteStatusSubject = new BehaviorSubject<FavoriteStatusResponse>({isFavorited: false})
|
|
55
|
-
node = createMockNode()
|
|
56
|
-
vi.mocked(getOrCreateNode).mockReturnValue(node)
|
|
57
44
|
|
|
58
45
|
// Mock getFavoritesState
|
|
59
46
|
vi.mocked(getFavoritesState).mockImplementation(() => ({
|
|
@@ -82,6 +69,17 @@ describe('useManageFavorite', () => {
|
|
|
82
69
|
dataset: 'test',
|
|
83
70
|
},
|
|
84
71
|
} as unknown as SanityInstance)
|
|
72
|
+
|
|
73
|
+
// Mock useWindowConnection
|
|
74
|
+
mockFetch = vi.fn().mockResolvedValue({success: true})
|
|
75
|
+
mockSendMessage = vi.fn()
|
|
76
|
+
vi.mocked(useWindowConnection).mockImplementation(() => {
|
|
77
|
+
return {
|
|
78
|
+
fetch: (type: string, data?: unknown, options: unknown = {}) =>
|
|
79
|
+
mockFetch(type, data, options),
|
|
80
|
+
sendMessage: mockSendMessage,
|
|
81
|
+
}
|
|
82
|
+
})
|
|
85
83
|
})
|
|
86
84
|
|
|
87
85
|
afterEach(() => {
|
|
@@ -93,7 +91,6 @@ describe('useManageFavorite', () => {
|
|
|
93
91
|
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
|
|
94
92
|
|
|
95
93
|
expect(result.current.isFavorited).toBe(false)
|
|
96
|
-
expect(result.current.isConnected).toBe(false)
|
|
97
94
|
})
|
|
98
95
|
|
|
99
96
|
it('should handle favorite action and update state', async () => {
|
|
@@ -101,16 +98,11 @@ describe('useManageFavorite', () => {
|
|
|
101
98
|
|
|
102
99
|
expect(result.current.isFavorited).toBe(false)
|
|
103
100
|
|
|
104
|
-
// Simulate connection first
|
|
105
|
-
act(() => {
|
|
106
|
-
statusCallback?.('connected')
|
|
107
|
-
})
|
|
108
|
-
|
|
109
101
|
await act(async () => {
|
|
110
102
|
await result.current.favorite()
|
|
111
103
|
})
|
|
112
104
|
|
|
113
|
-
expect(
|
|
105
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
114
106
|
'dashboard/v1/events/favorite/mutate',
|
|
115
107
|
{
|
|
116
108
|
document: {
|
|
@@ -140,16 +132,11 @@ describe('useManageFavorite', () => {
|
|
|
140
132
|
|
|
141
133
|
expect(result.current.isFavorited).toBe(true)
|
|
142
134
|
|
|
143
|
-
// Simulate connection first
|
|
144
|
-
act(() => {
|
|
145
|
-
statusCallback?.('connected')
|
|
146
|
-
})
|
|
147
|
-
|
|
148
135
|
await act(async () => {
|
|
149
136
|
await result.current.unfavorite()
|
|
150
137
|
})
|
|
151
138
|
|
|
152
|
-
expect(
|
|
139
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
153
140
|
'dashboard/v1/events/favorite/mutate',
|
|
154
141
|
{
|
|
155
142
|
document: {
|
|
@@ -169,7 +156,7 @@ describe('useManageFavorite', () => {
|
|
|
169
156
|
})
|
|
170
157
|
|
|
171
158
|
it('should not update state if favorite action fails', async () => {
|
|
172
|
-
|
|
159
|
+
mockFetch.mockResolvedValueOnce({success: false})
|
|
173
160
|
|
|
174
161
|
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
|
|
175
162
|
|
|
@@ -186,16 +173,12 @@ describe('useManageFavorite', () => {
|
|
|
186
173
|
it('should throw error during favorite/unfavorite actions', async () => {
|
|
187
174
|
const errorMessage = 'Failed to update favorite status'
|
|
188
175
|
|
|
189
|
-
|
|
176
|
+
mockFetch.mockImplementation(() => {
|
|
190
177
|
throw new Error(errorMessage)
|
|
191
178
|
})
|
|
192
179
|
|
|
193
180
|
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
|
|
194
181
|
|
|
195
|
-
await act(async () => {
|
|
196
|
-
statusCallback?.('connected')
|
|
197
|
-
})
|
|
198
|
-
|
|
199
182
|
await act(async () => {
|
|
200
183
|
await expect(result.current.favorite()).rejects.toThrow(errorMessage)
|
|
201
184
|
})
|
|
@@ -210,18 +193,6 @@ describe('useManageFavorite', () => {
|
|
|
210
193
|
expect(resolveFavoritesState).not.toHaveBeenCalled()
|
|
211
194
|
})
|
|
212
195
|
|
|
213
|
-
it('should update connection status', () => {
|
|
214
|
-
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
|
|
215
|
-
|
|
216
|
-
expect(result.current.isConnected).toBe(false)
|
|
217
|
-
|
|
218
|
-
act(() => {
|
|
219
|
-
statusCallback?.('connected')
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
expect(result.current.isConnected).toBe(true)
|
|
223
|
-
})
|
|
224
|
-
|
|
225
196
|
it('should throw error when studio resource is missing projectId or dataset', () => {
|
|
226
197
|
// Mock the Sanity instance to not have projectId or dataset
|
|
227
198
|
vi.mocked(useSanityInstance).mockReturnValue({
|
|
@@ -255,114 +226,93 @@ describe('useManageFavorite', () => {
|
|
|
255
226
|
)
|
|
256
227
|
})
|
|
257
228
|
|
|
258
|
-
it('should
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}))
|
|
265
|
-
|
|
266
|
-
vi.mocked(resolveFavoritesState).mockImplementationOnce(() => {
|
|
267
|
-
throw new Error('Favorites service connection timeout')
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
|
|
271
|
-
|
|
272
|
-
// Should return fallback state instead of suspending
|
|
273
|
-
expect(result.current).toEqual({
|
|
274
|
-
favorite: expect.any(Function),
|
|
275
|
-
unfavorite: expect.any(Function),
|
|
276
|
-
isFavorited: false,
|
|
277
|
-
isConnected: false,
|
|
278
|
-
})
|
|
229
|
+
it('should include schemaName in payload when provided', async () => {
|
|
230
|
+
const mockDocumentHandleWithSchema = {
|
|
231
|
+
...mockDocumentHandle,
|
|
232
|
+
schemaName: 'testSchema',
|
|
233
|
+
}
|
|
234
|
+
const {result} = renderHook(() => useManageFavorite(mockDocumentHandleWithSchema))
|
|
279
235
|
|
|
280
|
-
// Favorite and unfavorite actions should be a no-op
|
|
281
236
|
await act(async () => {
|
|
282
237
|
await result.current.favorite()
|
|
283
238
|
})
|
|
284
239
|
|
|
285
|
-
expect(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
240
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
241
|
+
'dashboard/v1/events/favorite/mutate',
|
|
242
|
+
{
|
|
243
|
+
document: {
|
|
244
|
+
id: 'mock-id',
|
|
245
|
+
type: 'mock-type',
|
|
246
|
+
resource: {
|
|
247
|
+
id: 'test.test',
|
|
248
|
+
type: 'studio',
|
|
249
|
+
schemaName: 'testSchema',
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
eventType: 'added',
|
|
253
|
+
},
|
|
254
|
+
{},
|
|
255
|
+
)
|
|
292
256
|
})
|
|
293
257
|
|
|
294
|
-
it('should
|
|
258
|
+
it('should default isFavorited to false if state is undefined', () => {
|
|
259
|
+
// Mock getFavoritesState to return undefined for getCurrent
|
|
295
260
|
vi.mocked(getFavoritesState).mockImplementation(() => ({
|
|
296
|
-
subscribe: () =>
|
|
297
|
-
|
|
261
|
+
subscribe: (callback?: () => void) => {
|
|
262
|
+
if (!callback) return () => {}
|
|
263
|
+
callback()
|
|
264
|
+
return () => {}
|
|
265
|
+
},
|
|
266
|
+
getCurrent: () => undefined,
|
|
298
267
|
observable: favoriteStatusSubject.asObservable(),
|
|
299
268
|
}))
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const error = new Error('Some other error')
|
|
303
|
-
vi.mocked(resolveFavoritesState).mockImplementation(() => {
|
|
304
|
-
throw error
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
expect(() => {
|
|
308
|
-
renderHook(() => useManageFavorite(mockDocumentHandle))
|
|
309
|
-
}).toThrow(error)
|
|
269
|
+
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
|
|
270
|
+
expect(result.current.isFavorited).toBe(false)
|
|
310
271
|
})
|
|
311
272
|
|
|
312
|
-
it('should
|
|
273
|
+
it('should do nothing if fetch is missing', async () => {
|
|
274
|
+
vi.mocked(useWindowConnection).mockReturnValue({
|
|
275
|
+
fetch: undefined,
|
|
276
|
+
sendMessage: mockSendMessage,
|
|
277
|
+
} as unknown as WindowConnection<Message>)
|
|
313
278
|
const {result} = renderHook(() => useManageFavorite(mockDocumentHandle))
|
|
314
|
-
|
|
315
|
-
// Ensure connection is not established
|
|
316
|
-
expect(result.current.isConnected).toBe(false)
|
|
317
|
-
|
|
318
|
-
// Try to favorite
|
|
319
279
|
await act(async () => {
|
|
320
280
|
await result.current.favorite()
|
|
281
|
+
await result.current.unfavorite()
|
|
321
282
|
})
|
|
283
|
+
expect(mockFetch).not.toHaveBeenCalled()
|
|
284
|
+
})
|
|
322
285
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
286
|
+
it('should do nothing if documentId is missing', async () => {
|
|
287
|
+
const handle = {...mockDocumentHandle, documentId: undefined}
|
|
288
|
+
// @ts-expect-error -- no access to ManageFavorite props type
|
|
289
|
+
const {result} = renderHook(() => useManageFavorite(handle))
|
|
327
290
|
await act(async () => {
|
|
291
|
+
await result.current.favorite()
|
|
328
292
|
await result.current.unfavorite()
|
|
329
293
|
})
|
|
330
|
-
|
|
331
|
-
// Fetch should still not have been called
|
|
332
|
-
expect(node.fetch).not.toHaveBeenCalled()
|
|
294
|
+
expect(mockFetch).not.toHaveBeenCalled()
|
|
333
295
|
})
|
|
334
296
|
|
|
335
|
-
it('should
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
// Simulate connection first
|
|
343
|
-
act(() => {
|
|
344
|
-
statusCallback?.('connected')
|
|
297
|
+
it('should do nothing if documentType is missing', async () => {
|
|
298
|
+
const handle = {...mockDocumentHandle, documentType: undefined}
|
|
299
|
+
// @ts-expect-error -- no access to ManageFavorite props type
|
|
300
|
+
const {result} = renderHook(() => useManageFavorite(handle))
|
|
301
|
+
await act(async () => {
|
|
302
|
+
await result.current.favorite()
|
|
303
|
+
await result.current.unfavorite()
|
|
345
304
|
})
|
|
305
|
+
expect(mockFetch).not.toHaveBeenCalled()
|
|
306
|
+
})
|
|
346
307
|
|
|
308
|
+
it('should do nothing if resourceType is missing', async () => {
|
|
309
|
+
const handle = {...mockDocumentHandle, resourceType: undefined, resourceId: 'studio'}
|
|
310
|
+
// @ts-expect-error -- no access to ManageFavorite props type
|
|
311
|
+
const {result} = renderHook(() => useManageFavorite(handle))
|
|
347
312
|
await act(async () => {
|
|
348
313
|
await result.current.favorite()
|
|
314
|
+
await result.current.unfavorite()
|
|
349
315
|
})
|
|
350
|
-
|
|
351
|
-
expect(node.fetch).toHaveBeenCalledWith(
|
|
352
|
-
'dashboard/v1/events/favorite/mutate',
|
|
353
|
-
{
|
|
354
|
-
document: {
|
|
355
|
-
id: 'mock-id',
|
|
356
|
-
type: 'mock-type',
|
|
357
|
-
resource: {
|
|
358
|
-
id: 'test.test',
|
|
359
|
-
type: 'studio',
|
|
360
|
-
schemaName: 'testSchema', // <-- Expect schemaName here
|
|
361
|
-
},
|
|
362
|
-
},
|
|
363
|
-
eventType: 'added',
|
|
364
|
-
},
|
|
365
|
-
{},
|
|
366
|
-
)
|
|
316
|
+
expect(mockFetch).not.toHaveBeenCalled()
|
|
367
317
|
})
|
|
368
318
|
})
|