1/* eslint-disable @typescript-eslint/no-var-requires */
2
3import { gql } from '@urql/core';
4import { minifyIntrospectionQuery } from '@urql/introspection';
5import { describe, it, beforeEach, beforeAll, expect } from 'vitest';
6
7import { Store } from '../store/store';
8import { __initAnd_write as write } from './write';
9import { __initAnd_query as query } from './query';
10
11const TODO_QUERY = gql`
12 query Todos {
13 todos {
14 id
15 text
16 complete
17 author {
18 id
19 name
20 known
21 __typename
22 }
23 __typename
24 }
25 }
26`;
27
28describe('Query', () => {
29 let schema, store, alteredRoot;
30
31 beforeAll(() => {
32 schema = minifyIntrospectionQuery(
33 require('../test-utils/simple_schema.json')
34 );
35 alteredRoot = minifyIntrospectionQuery(
36 require('../test-utils/altered_root_schema.json')
37 );
38 });
39
40 beforeEach(() => {
41 store = new Store({ schema });
42 write(
43 store,
44 { query: TODO_QUERY },
45 {
46 __typename: 'Query',
47 todos: [
48 { id: '0', text: 'Teach', __typename: 'Todo' },
49 { id: '1', text: 'Learn', __typename: 'Todo' },
50 ],
51 }
52 );
53 });
54
55 it('test partial results', () => {
56 const result = query(store, { query: TODO_QUERY });
57 expect(result.partial).toBe(true);
58 expect(result.data).toEqual({
59 todos: [
60 {
61 id: '0',
62 text: 'Teach',
63 __typename: 'Todo',
64 author: null,
65 complete: null,
66 },
67 {
68 id: '1',
69 text: 'Learn',
70 __typename: 'Todo',
71 author: null,
72 complete: null,
73 },
74 ],
75 });
76 });
77
78 it('should warn once for invalid fields on an entity', () => {
79 const INVALID_TODO_QUERY = gql`
80 query InvalidTodo {
81 todos {
82 id
83 text
84 incomplete
85 }
86 }
87 `;
88
89 query(store, { query: INVALID_TODO_QUERY });
90 expect(console.warn).toHaveBeenCalledTimes(1);
91 expect((console.warn as any).mock.calls[0][0]).toMatch(
92 /Caused At: "InvalidTodo" query/
93 );
94
95 query(store, { query: INVALID_TODO_QUERY });
96 expect(console.warn).toHaveBeenCalledTimes(1);
97
98 expect((console.warn as any).mock.calls[0][0]).toMatch(/incomplete/);
99 });
100
101 it('should warn once for invalid sub-entities on an entity at the right stack', () => {
102 const INVALID_TODO_QUERY = gql`
103 query InvalidTodo {
104 todos {
105 ...ValidTodo
106 ...InvalidFields
107 }
108 }
109
110 fragment ValidTodo on Todo {
111 id
112 text
113 }
114
115 fragment InvalidFields on Todo {
116 id
117 writer {
118 id
119 }
120 }
121 `;
122
123 query(store, { query: INVALID_TODO_QUERY });
124 expect(console.warn).toHaveBeenCalledTimes(1);
125 expect((console.warn as any).mock.calls[0][0]).toMatch(
126 /Caused At: "InvalidTodo" query, "InvalidFields" Fragment/
127 );
128
129 query(store, { query: INVALID_TODO_QUERY });
130 expect(console.warn).toHaveBeenCalledTimes(1);
131 expect((console.warn as any).mock.calls[0][0]).toMatch(/writer/);
132 });
133
134 // Issue#64
135 it('should not crash for valid queries', () => {
136 const VALID_QUERY = gql`
137 query getTodos {
138 __typename
139 todos {
140 __typename
141 id
142 text
143 }
144 }
145 `;
146 // Use new store to ensure bug reproduction
147 const store = new Store({ schema });
148
149 let { data } = query(store, { query: VALID_QUERY });
150 expect(data).toEqual(null);
151
152 write(
153 store,
154 { query: VALID_QUERY },
155 // @ts-ignore
156 {
157 // Removing typename here would formerly crash this.
158 todos: [{ __typename: 'Todo', id: '0', text: 'Solve bug' }],
159 }
160 );
161 ({ data } = query(store, { query: VALID_QUERY }));
162 expect(data).toEqual({
163 __typename: 'Query',
164 todos: [{ __typename: 'Todo', id: '0', text: 'Solve bug' }],
165 });
166
167 expect(console.error).not.toHaveBeenCalled();
168 });
169
170 it('should respect altered root types', () => {
171 const QUERY = gql`
172 query getTodos {
173 __typename
174 todos {
175 __typename
176 id
177 text
178 }
179 }
180 `;
181
182 const store = new Store({ schema: alteredRoot });
183
184 let { data } = query(store, { query: QUERY });
185 expect(data).toEqual(null);
186
187 write(
188 store,
189 { query: QUERY },
190 {
191 todos: [{ __typename: 'Todo', id: '0', text: 'Solve bug' }],
192 __typename: 'query_root',
193 }
194 );
195
196 ({ data } = query(store, { query: QUERY }));
197 expect(data).toEqual({
198 __typename: 'query_root',
199 todos: [{ __typename: 'Todo', id: '0', text: 'Solve bug' }],
200 });
201
202 expect(console.warn).not.toHaveBeenCalled();
203 expect(console.error).not.toHaveBeenCalled();
204 });
205
206 it('should not allow subsequent reads when first result was null', () => {
207 const QUERY_WRITE = gql`
208 query writeTodos {
209 __typename
210 todos {
211 __typename
212 ...ValidRead
213 }
214 }
215
216 fragment ValidRead on Todo {
217 id
218 }
219 `;
220
221 const QUERY_READ = gql`
222 query getTodos {
223 __typename
224 todos {
225 __typename
226 ...MissingRead
227 }
228 todos {
229 __typename
230 id
231 }
232 }
233
234 fragment MissingRead on Todo {
235 id
236 text
237 }
238 `;
239
240 const store = new Store({
241 schema: alteredRoot,
242 });
243
244 let { data } = query(store, { query: QUERY_READ });
245 expect(data).toEqual(null);
246
247 write(
248 store,
249 { query: QUERY_WRITE },
250 {
251 todos: [
252 {
253 __typename: 'Todo',
254 id: '0',
255 },
256 ],
257 __typename: 'Query',
258 }
259 );
260
261 ({ data } = query(store, { query: QUERY_READ }));
262 expect(data).toEqual({
263 __typename: 'query_root',
264 todos: [null],
265 });
266
267 expect(console.warn).not.toHaveBeenCalled();
268 expect(console.error).not.toHaveBeenCalled();
269 });
270
271 it('should not allow subsequent reads when first result was null (with resolvers)', () => {
272 const QUERY_WRITE = gql`
273 query writeTodos {
274 __typename
275 todos {
276 __typename
277 ...ValidRead
278 }
279 }
280
281 fragment ValidRead on Todo {
282 id
283 }
284 `;
285
286 const QUERY_READ = gql`
287 query getTodos {
288 __typename
289 todos {
290 __typename
291 ...MissingRead
292 }
293 todos {
294 __typename
295 id
296 }
297 }
298
299 fragment MissingRead on Todo {
300 id
301 text
302 }
303 `;
304
305 const store = new Store({
306 schema,
307 resolvers: {
308 Query: {
309 todos: (_parent, _args, cache) => cache.resolve('Query', 'todos'),
310 },
311 },
312 });
313
314 let { data } = query(store, { query: QUERY_READ });
315 expect(data).toEqual(null);
316
317 write(
318 store,
319 { query: QUERY_WRITE },
320 {
321 todos: [
322 {
323 __typename: 'Todo',
324 id: '0',
325 },
326 ],
327 __typename: 'Query',
328 }
329 );
330
331 ({ data } = query(store, { query: QUERY_READ }));
332 expect(data).toEqual({
333 __typename: 'Query',
334 todos: [null],
335 });
336
337 expect(console.warn).not.toHaveBeenCalled();
338 expect(console.error).not.toHaveBeenCalled();
339 });
340
341 it('should not mix references', () => {
342 const QUERY_WRITE = gql`
343 query writeTodos {
344 __typename
345 todos {
346 __typename
347 id
348 textA
349 textB
350 }
351 }
352 `;
353
354 const QUERY_READ = gql`
355 query getTodos {
356 __typename
357 todos {
358 __typename
359 id
360 textA
361 }
362 todos {
363 __typename
364 textB
365 }
366 }
367 `;
368
369 const store = new Store({
370 schema: alteredRoot,
371 });
372
373 write(
374 store,
375 { query: QUERY_WRITE },
376 {
377 todos: [
378 {
379 __typename: 'Todo',
380 id: '0',
381 textA: 'a',
382 textB: 'b',
383 },
384 ],
385 __typename: 'Query',
386 }
387 );
388
389 let data: any;
390 data = query(store, { query: QUERY_READ }).data;
391
392 expect(data).toEqual({
393 __typename: 'query_root',
394 todos: [
395 {
396 __typename: 'Todo',
397 id: '0',
398 textA: 'a',
399 textB: 'b',
400 },
401 ],
402 });
403
404 const previousData = {
405 __typename: 'query_root',
406 todos: [
407 {
408 __typename: 'Todo',
409 id: '0',
410 textA: 'a',
411 textB: 'old',
412 },
413 ],
414 };
415
416 data = query(store, { query: QUERY_READ }, previousData).data;
417
418 expect(data).toEqual({
419 __typename: 'query_root',
420 todos: [
421 {
422 __typename: 'Todo',
423 id: '0',
424 textA: 'a',
425 textB: 'b',
426 },
427 ],
428 });
429
430 expect(previousData).toHaveProperty('todos.0.textA', 'a');
431 expect(previousData).toHaveProperty('todos.0.textB', 'old');
432 });
433
434 it('should keep references stable (1)', () => {
435 const QUERY = gql`
436 query todos {
437 __typename
438 todos {
439 __typename
440 test
441 }
442 todos {
443 __typename
444 id
445 }
446 }
447 `;
448
449 const store = new Store({
450 schema: alteredRoot,
451 });
452
453 const expected = {
454 todos: [
455 {
456 __typename: 'Todo',
457 id: '0',
458 test: '0',
459 },
460 {
461 __typename: 'Todo',
462 id: '1',
463 test: '1',
464 },
465 {
466 __typename: 'Todo',
467 id: '2',
468 test: '2',
469 },
470 ],
471 __typename: 'query_root',
472 };
473
474 write(store, { query: QUERY }, expected);
475
476 const prevData = query(
477 store,
478 { query: QUERY },
479 {
480 todos: [
481 {
482 __typename: 'Todo',
483 id: '0',
484 test: 'prev-0',
485 },
486 {
487 __typename: 'Todo',
488 id: '1',
489 test: '1',
490 },
491 {
492 __typename: 'Todo',
493 id: '2',
494 test: '2',
495 },
496 ],
497 __typename: 'query_root',
498 }
499 ).data as any;
500
501 const data = query(store, { query: QUERY }, prevData).data as any;
502 expect(data).toEqual(expected);
503
504 expect(prevData.todos[0]).toBe(data.todos[0]);
505 expect(prevData.todos[1]).toBe(data.todos[1]);
506 expect(prevData.todos[2]).toBe(data.todos[2]);
507 expect(prevData.todos).toBe(data.todos);
508 expect(prevData).toBe(data);
509 });
510
511 it('should keep references stable (negative test)', () => {
512 const QUERY = gql`
513 query todos {
514 __typename
515 todos {
516 __typename
517 id
518 }
519 todos {
520 __typename
521 test
522 }
523 }
524 `;
525
526 const store = new Store({
527 schema: alteredRoot,
528 });
529
530 const expected = {
531 todos: [
532 {
533 __typename: 'Todo',
534 id: '0',
535 test: '0',
536 },
537 {
538 __typename: 'Todo',
539 id: '1',
540 test: '1',
541 },
542 {
543 __typename: 'Todo',
544 id: '2',
545 test: '2',
546 },
547 ],
548 __typename: 'query_root',
549 };
550
551 write(store, { query: QUERY }, expected);
552
553 const prevData = query(
554 store,
555 { query: QUERY },
556 {
557 todos: [
558 {
559 __typename: 'Todo',
560 id: '0',
561 test: 'prev-0',
562 },
563 {
564 __typename: 'Todo',
565 id: '1',
566 test: '1',
567 },
568 {
569 __typename: 'Todo',
570 id: '2',
571 test: '2',
572 },
573 ],
574 __typename: 'query_root',
575 }
576 ).data as any;
577
578 expected.todos[0].test = 'x';
579 write(store, { query: QUERY }, expected);
580
581 const data = query(store, { query: QUERY }, prevData).data as any;
582 expect(data).toEqual(expected);
583
584 expect(prevData.todos[1]).toBe(data.todos[1]);
585 expect(prevData.todos[2]).toBe(data.todos[2]);
586 expect(prevData.todos[0]).not.toBe(data.todos[0]);
587 expect(prevData.todos).not.toBe(data.todos);
588 expect(prevData).not.toBe(data);
589 });
590});