Skip to content

Commit 0fff196

Browse files
committed
test(views): add wider test covered for expected views behavior
1 parent 73bde14 commit 0fff196

File tree

1 file changed

+331
-13
lines changed

1 file changed

+331
-13
lines changed

test/views/client.test.ts

Lines changed: 331 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,62 @@
1-
import {describe, expect, test} from 'vitest'
1+
import {describe, expect, test, afterEach} from 'vitest'
22
import {createViewClient, ObservableViewClient, type ViewClientConfig} from '../../src/views'
3-
import nock from 'nock'
43

54
const apiHost = 'api.sanity.url'
65
const apicdnHost = 'apicdn.sanity.url'
76

87
describe('view client', async () => {
8+
const isEdge = typeof EdgeRuntime === 'string'
9+
let nock: typeof import('nock') = (() => {
10+
throw new Error('Not supported in EdgeRuntime')
11+
}) as any
12+
if (!isEdge) {
13+
const _nock = await import('nock')
14+
nock = _nock.default
15+
}
16+
17+
afterEach(() => {
18+
if (!isEdge) {
19+
nock.cleanAll()
20+
}
21+
})
22+
923
const defaultConfig: ViewClientConfig = {
1024
apiHost: `https://${apiHost}`,
1125
apiCdnHost: `https://${apicdnHost}`,
1226
apiVersion: '2025-01-01',
1327
}
1428

29+
describe('createViewClient', () => {
30+
test('can create a view client', () => {
31+
const client = createViewClient(defaultConfig)
32+
expect(client).toBeDefined()
33+
expect(client.observable).toBeInstanceOf(ObservableViewClient)
34+
})
35+
36+
test('can create a view client with additional config options', () => {
37+
const config: ViewClientConfig = {
38+
...defaultConfig,
39+
maxRetries: 5,
40+
retryDelay: () => 1000, // retryDelay should be a function
41+
timeout: 30000,
42+
}
43+
const client = createViewClient(config)
44+
expect(client).toBeDefined()
45+
expect(client.observable).toBeInstanceOf(ObservableViewClient)
46+
})
47+
})
48+
1549
describe('promise client', () => {
16-
test('uses the correct url for a view resource', async () => {
50+
test.skipIf(isEdge)('uses the correct url for a view resource', async () => {
1751
const client = createViewClient(defaultConfig)
1852
const result = [{_id: 'njgNkngskjg'}]
1953

20-
nock(`https://${apicdnHost}`)
54+
// Mock both hosts to see which one gets called
55+
const apiMock = nock(`https://${apiHost}`)
56+
.get(/.*/)
57+
.reply(200, {ms: 123, result})
58+
59+
const apicdnMock = nock(`https://${apicdnHost}`)
2160
.get(
2261
`/v2025-01-01/views/vw123/query?query=*%5B_id+%3D%3D+%22view%22%5D%7B_id%7D&returnQuery=false`,
2362
)
@@ -28,6 +67,132 @@ describe('view client', async () => {
2867

2968
const res = await client.fetch('vw123', '*[_id == "view"]{_id}', {}, {})
3069
expect(res).toEqual(result)
70+
71+
expect(apiMock.isDone()).toBe(false)
72+
expect(apicdnMock.isDone()).toBe(true)
73+
})
74+
75+
test.skipIf(isEdge)('can fetch without params', async () => {
76+
const client = createViewClient(defaultConfig)
77+
const result = [{_id: 'doc1', title: 'Test Document'}]
78+
79+
nock(`https://${apicdnHost}`).get(/.*/).reply(200, {ms: 150, result})
80+
81+
const res = await client.fetch('vw456', '*[_type == "document"]')
82+
expect(res).toEqual(result)
83+
})
84+
85+
test.skipIf(isEdge)('can fetch with params', async () => {
86+
const client = createViewClient(defaultConfig)
87+
const result = [{_id: 'doc1', title: 'Specific Document'}]
88+
const params = {docType: 'article'}
89+
90+
nock(`https://${apicdnHost}`).get(/.*/).reply(200, {ms: 200, result})
91+
92+
const res = await client.fetch('vw789', '*[_type == $docType]', params)
93+
expect(res).toEqual(result)
94+
})
95+
96+
test.skipIf(isEdge)('can fetch with ViewQueryOptions', async () => {
97+
const client = createViewClient(defaultConfig)
98+
const result = [{_id: 'doc1', title: 'Published Document'}]
99+
100+
nock(`https://${apicdnHost}`).get(/.*/).reply(200, {ms: 180, result})
101+
102+
const res = await client.fetch('vw101', '*[_type == "page"]', {}, {perspective: 'published'})
103+
expect(res).toEqual(result)
104+
})
105+
106+
test.skipIf(isEdge)('can fetch with resultSourceMap option', async () => {
107+
const client = createViewClient(defaultConfig)
108+
const result = [{_id: 'doc1', title: 'Document with source map'}]
109+
const resultSourceMap = {
110+
documents: [
111+
{
112+
_id: 'doc1',
113+
_type: 'document',
114+
},
115+
],
116+
paths: ['$[0]'],
117+
mappings: {
118+
'$[0]': {
119+
source: {
120+
document: 0,
121+
path: '',
122+
type: 'documentValue',
123+
},
124+
},
125+
},
126+
}
127+
128+
nock(`https://${apicdnHost}`).get(/.*/).reply(200, {ms: 220, result, resultSourceMap})
129+
130+
const res = await client.fetch('vw202', '*[_type == "page"]', {}, {resultSourceMap: true})
131+
// By default, the client returns just the result, even with resultSourceMap: true
132+
// To get the full response including resultSourceMap, use filterResponse: false
133+
expect(res).toEqual(result)
134+
})
135+
136+
test.skipIf(isEdge)('can fetch with filterResponse: false to get full response', async () => {
137+
const client = createViewClient(defaultConfig)
138+
const result = [{_id: 'doc1', title: 'Document with full response'}]
139+
const resultSourceMap = {
140+
documents: [
141+
{
142+
_id: 'doc1',
143+
_type: 'document',
144+
},
145+
],
146+
paths: ['$[0]'],
147+
mappings: {
148+
'$[0]': {
149+
source: {
150+
document: 0,
151+
path: '',
152+
type: 'documentValue',
153+
},
154+
},
155+
},
156+
}
157+
158+
nock(`https://${apicdnHost}`).get(/.*/).reply(200, {ms: 250, result, resultSourceMap})
159+
160+
const res = await client.fetch('vw203', '*[_type == "page"]', {}, {resultSourceMap: true, filterResponse: false})
161+
// With filterResponse: false, the client returns the full response object
162+
expect(res).toEqual({result, resultSourceMap, ms: 250})
163+
})
164+
165+
test('can clone client with withConfig', () => {
166+
const client = createViewClient(defaultConfig)
167+
const newClient = client.withConfig({apiVersion: '2024-12-01'})
168+
169+
expect(client).not.toBe(newClient)
170+
expect(newClient).toBeDefined()
171+
expect(newClient.observable).toBeInstanceOf(ObservableViewClient)
172+
})
173+
174+
test.skipIf(isEdge)('withConfig preserves existing configuration', async () => {
175+
const client = createViewClient({
176+
...defaultConfig,
177+
timeout: 5000,
178+
})
179+
const newClient = client.withConfig({apiVersion: '2024-12-01'})
180+
const result = [{_id: 'test'}]
181+
182+
nock(`https://${apicdnHost}`).get(/.*/).reply(200, {ms: 100, result})
183+
184+
const res = await newClient.fetch('vw303', '*')
185+
expect(res).toEqual(result)
186+
})
187+
188+
test.skipIf(isEdge)('always uses CDN for view queries', async () => {
189+
const client = createViewClient(defaultConfig)
190+
const result = [{_id: 'cdn-test'}]
191+
192+
nock(`https://${apicdnHost}`).get(/.*/).reply(200, {ms: 50, result})
193+
194+
const res = await client.fetch('vw404', '*')
195+
expect(res).toEqual(result)
31196
})
32197
})
33198

@@ -37,18 +202,11 @@ describe('view client', async () => {
37202
expect(client.observable).toBeInstanceOf(ObservableViewClient)
38203
})
39204

40-
test('uses the correct url for a view resource', async () => {
205+
test.skipIf(isEdge)('uses the correct url for a view resource', async () => {
41206
const client = createViewClient(defaultConfig)
42207
const result = [{_id: 'njgNkngskjg'}]
43208

44-
nock(`https://${apicdnHost}`)
45-
.get(
46-
`/v2025-01-01/views/vw123/query?query=*%5B_id+%3D%3D+%22view%22%5D%7B_id%7D&returnQuery=false`,
47-
)
48-
.reply(200, {
49-
ms: 123,
50-
result,
51-
})
209+
nock(`https://${apicdnHost}`).get(/.*/).reply(200, {ms: 123, result})
52210

53211
const req = client.observable.fetch('vw123', '*[_id == "view"]{_id}', {}, {})
54212
await new Promise((resolve) => setTimeout(resolve, 1))
@@ -63,5 +221,165 @@ describe('view client', async () => {
63221
})
64222
})
65223
})
224+
225+
test.skipIf(isEdge)('observable requests are lazy', async () => {
226+
const client = createViewClient(defaultConfig)
227+
let didRequest = false
228+
229+
nock(`https://${apicdnHost}`).get(/.*/).reply(() => {
230+
didRequest = true
231+
return [200, {ms: 100, result: []}]
232+
})
233+
234+
const req = client.observable.fetch('vw505', '*')
235+
await new Promise((resolve) => setTimeout(resolve, 1))
236+
237+
expect(didRequest).toBe(false)
238+
239+
await new Promise<void>((resolve, reject) => {
240+
req.subscribe({
241+
next: () => {
242+
expect(didRequest).toBe(true)
243+
},
244+
error: reject,
245+
complete: resolve,
246+
})
247+
})
248+
})
249+
250+
test.skipIf(isEdge)('observable requests are cold', async () => {
251+
const client = createViewClient(defaultConfig)
252+
let requestCount = 0
253+
254+
// Mock CDN host
255+
nock(`https://${apicdnHost}`).get(/.*/).twice().reply(() => {
256+
requestCount++
257+
return [200, {ms: 100, result: [{_id: `doc${requestCount}`}]}]
258+
})
259+
260+
const req = client.observable.fetch('vw606', '*')
261+
262+
await new Promise<void>((resolve, reject) => {
263+
expect(requestCount).toBe(0)
264+
req.subscribe({
265+
next: () => {
266+
expect(requestCount).toBe(1)
267+
req.subscribe({
268+
next: () => {
269+
expect(requestCount).toBe(2)
270+
},
271+
error: reject,
272+
complete: resolve,
273+
})
274+
},
275+
error: reject,
276+
})
277+
})
278+
})
279+
280+
test.skipIf(isEdge)('can fetch without params', async () => {
281+
const client = createViewClient(defaultConfig)
282+
const result = [{_id: 'obs-doc1', title: 'Observable Document'}]
283+
284+
// Mock CDN host
285+
nock(`https://${apicdnHost}`).get(/.*/).reply(200, {ms: 160, result})
286+
287+
await new Promise<void>((resolve, reject) => {
288+
client.observable.fetch('vw707', '*[_type == "post"]').subscribe({
289+
next: (res) => {
290+
expect(res).toEqual(result)
291+
},
292+
error: reject,
293+
complete: resolve,
294+
})
295+
})
296+
})
297+
298+
test.skipIf(isEdge)('can fetch with params', async () => {
299+
const client = createViewClient(defaultConfig)
300+
const result = [{_id: 'obs-doc2', category: 'tech'}]
301+
const params = {cat: 'tech'}
302+
303+
// Mock CDN host
304+
nock(`https://${apicdnHost}`).get(/.*/).reply(200, {ms: 140, result})
305+
306+
await new Promise<void>((resolve, reject) => {
307+
client.observable.fetch('vw808', '*[category == $cat]', params).subscribe({
308+
next: (res) => {
309+
expect(res).toEqual(result)
310+
},
311+
error: reject,
312+
complete: resolve,
313+
})
314+
})
315+
})
316+
317+
test.skipIf(isEdge)('can fetch with ViewQueryOptions', async () => {
318+
const client = createViewClient(defaultConfig)
319+
const result = [{_id: 'obs-doc3', status: 'draft'}]
320+
321+
// Mock CDN host
322+
nock(`https://${apicdnHost}`)
323+
.get(/.*/)
324+
.reply(200, {ms: 190, result})
325+
326+
await new Promise<void>((resolve, reject) => {
327+
client.observable.fetch('vw909', '*[_type == "article"]', {}, {perspective: 'previewDrafts'}).subscribe({
328+
next: (res) => {
329+
expect(res).toEqual(result)
330+
},
331+
error: reject,
332+
complete: resolve,
333+
})
334+
})
335+
})
336+
337+
test('can clone observable client with withConfig', () => {
338+
const client = createViewClient(defaultConfig)
339+
const newObservableClient = client.observable.withConfig({apiVersion: '2024-11-01'})
340+
341+
expect(client.observable).not.toBe(newObservableClient)
342+
expect(newObservableClient).toBeInstanceOf(ObservableViewClient)
343+
})
344+
345+
test.skipIf(isEdge)('withConfig on observable client preserves existing configuration', async () => {
346+
const client = createViewClient({
347+
...defaultConfig,
348+
timeout: 8000,
349+
})
350+
const newObservableClient = client.observable.withConfig({apiVersion: '2024-11-01'})
351+
const result = [{_id: 'config-test'}]
352+
353+
// Mock CDN host
354+
nock(`https://${apicdnHost}`).get(/.*/).reply(200, {ms: 80, result})
355+
356+
await new Promise<void>((resolve, reject) => {
357+
newObservableClient.fetch('vw1010', '*').subscribe({
358+
next: (res) => {
359+
expect(res).toEqual(result)
360+
},
361+
error: reject,
362+
complete: resolve,
363+
})
364+
})
365+
})
366+
367+
test.skipIf(isEdge)('always uses CDN for observable view queries', async () => {
368+
const client = createViewClient(defaultConfig)
369+
const result = [{_id: 'obs-cdn-test'}]
370+
371+
// Mock CDN host
372+
nock(`https://${apicdnHost}`).get(/.*/).reply(200, {ms: 60, result})
373+
374+
await new Promise<void>((resolve, reject) => {
375+
client.observable.fetch('vw1111', '*').subscribe({
376+
next: (res) => {
377+
expect(res).toEqual(result)
378+
},
379+
error: reject,
380+
complete: resolve,
381+
})
382+
})
383+
})
66384
})
67385
})

0 commit comments

Comments
 (0)