1import { DocumentNode } from 'graphql';
2import { gql } from '@urql/core';
3import { it, expect } from 'vitest';
4import { __initAnd_query as query } from '../operations/query';
5import { __initAnd_write as write } from '../operations/write';
6import { Store } from '../store/store';
7
8interface TestCase {
9 query: DocumentNode;
10 variables?: any;
11 data: any;
12}
13
14const expectCacheIntegrity = (testcase: TestCase) => {
15 const store = new Store();
16 const request = { query: testcase.query, variables: testcase.variables };
17 const writeRes = write(store, request, testcase.data);
18 const queryRes = query(store, request);
19 expect(queryRes.data).not.toBe(null);
20 expect(queryRes.data).toEqual(testcase.data);
21 expect(queryRes.partial).toBe(false);
22 expect(queryRes.dependencies).toEqual(writeRes.dependencies);
23};
24
25it('int on query', () => {
26 expectCacheIntegrity({
27 query: gql`
28 {
29 __typename
30 int
31 }
32 `,
33 data: { __typename: 'Query', int: 42 },
34 });
35});
36
37it('aliased field on query', () => {
38 expectCacheIntegrity({
39 query: gql`
40 {
41 __typename
42 anotherName: int
43 }
44 `,
45 data: { __typename: 'Query', anotherName: 42 },
46 });
47});
48
49it('@skip directive on field on query', () => {
50 expectCacheIntegrity({
51 query: gql`
52 {
53 __typename
54 intA @skip(if: true)
55 intB @skip(if: false)
56 }
57 `,
58 data: { __typename: 'Query', intB: 2 },
59 });
60});
61
62it('@include directive on field on query', () => {
63 expectCacheIntegrity({
64 query: gql`
65 {
66 __typename
67 intA @include(if: true)
68 intB @include(if: false)
69 }
70 `,
71 data: { __typename: 'Query', intA: 2 },
72 });
73});
74
75it('random directive on field on query', () => {
76 expectCacheIntegrity({
77 query: gql`
78 {
79 __typename
80 int @shouldntMatter
81 }
82 `,
83 data: { __typename: 'Query', int: 1 },
84 });
85});
86
87it('json on query', () => {
88 expectCacheIntegrity({
89 query: gql`
90 {
91 __typename
92 json
93 }
94 `,
95 // The `__typename` field should not mislead the cache
96 data: {
97 __typename: 'Query',
98 json: { __typename: 'Misleading', test: true },
99 },
100 });
101});
102
103it('nullable field on query', () => {
104 expectCacheIntegrity({
105 query: gql`
106 {
107 __typename
108 missing
109 }
110 `,
111 data: { __typename: 'Query', missing: null },
112 });
113});
114
115it('int field with arguments on query', () => {
116 expectCacheIntegrity({
117 query: gql`
118 {
119 __typename
120 int(test: true)
121 }
122 `,
123 data: { __typename: 'Query', int: 42 },
124 });
125});
126
127it('non-keyable entity on query', () => {
128 expectCacheIntegrity({
129 query: gql`
130 {
131 __typename
132 item {
133 __typename
134 name
135 }
136 }
137 `,
138 // This entity has no `id` or `_id` field
139 data: { __typename: 'Query', item: { __typename: 'Item', name: 'Test' } },
140 });
141});
142
143it('non-IDable entity on query', () => {
144 expectCacheIntegrity({
145 query: gql`
146 {
147 __typename
148 item {
149 __typename
150 name
151 }
152 }
153 `,
154 // This entity has a `__typename` but no ID fields
155 data: { __typename: 'Query', item: { __typename: 'Item', name: 'Test' } },
156 });
157});
158
159it('entity on query', () => {
160 expectCacheIntegrity({
161 query: gql`
162 {
163 __typename
164 item {
165 __typename
166 id
167 name
168 }
169 }
170 `,
171 data: {
172 __typename: 'Query',
173 item: { __typename: 'Item', id: '1', name: 'Test' },
174 },
175 });
176});
177
178it('entity on aliased field on query', () => {
179 expectCacheIntegrity({
180 query: gql`
181 {
182 __typename
183 anotherName: item {
184 __typename
185 id
186 name
187 }
188 }
189 `,
190 data: {
191 __typename: 'Query',
192 anotherName: { __typename: 'Item', id: '1', name: 'Test' },
193 },
194 });
195});
196
197it('entity with arguments on query', () => {
198 expectCacheIntegrity({
199 query: gql`
200 {
201 __typename
202 item(test: true) {
203 __typename
204 id
205 name
206 }
207 }
208 `,
209 data: {
210 __typename: 'Query',
211 item: { __typename: 'Item', id: '1', name: 'Test' },
212 },
213 });
214});
215
216it('entity with Int-like ID on query', () => {
217 expectCacheIntegrity({
218 query: gql`
219 {
220 __typename
221 item {
222 __typename
223 id
224 name
225 }
226 }
227 `,
228 // This is the same as above, but with a number on `id`
229 data: {
230 __typename: 'Query',
231 item: { __typename: 'Item', id: 1, name: 'Test' },
232 },
233 });
234});
235
236it('entity list on query', () => {
237 expectCacheIntegrity({
238 query: gql`
239 {
240 __typename
241 items {
242 __typename
243 id
244 }
245 }
246 `,
247 data: {
248 __typename: 'Query',
249 items: [
250 { __typename: 'Item', id: 1 },
251 { __typename: 'Item', id: 2 },
252 ],
253 },
254 });
255});
256
257it('nested entity list on query', () => {
258 expectCacheIntegrity({
259 query: gql`
260 {
261 __typename
262 items {
263 __typename
264 id
265 }
266 }
267 `,
268 data: {
269 // This is the same as above, but with a nested array and added null values
270 __typename: 'Query',
271 items: [
272 { __typename: 'Item', id: 1 },
273 [{ __typename: 'Item', id: 2 }, null],
274 null,
275 ],
276 },
277 });
278});
279
280it('entity list on query and inline fragment', () => {
281 expectCacheIntegrity({
282 query: gql`
283 {
284 __typename
285 items {
286 __typename
287 id
288 }
289 ... on Query {
290 items {
291 test
292 }
293 }
294 }
295 `,
296 data: {
297 __typename: 'Query',
298 items: [{ __typename: 'Item', id: 1, test: true }, null],
299 },
300 });
301});
302
303it('conditionless inline fragment', () => {
304 expectCacheIntegrity({
305 query: gql`
306 {
307 __typename
308 ... {
309 test
310 }
311 }
312 `,
313 data: {
314 __typename: 'Query',
315 test: true,
316 },
317 });
318});
319
320it('skipped conditionless inline fragment', () => {
321 expectCacheIntegrity({
322 query: gql`
323 {
324 __typename
325 ... @skip(if: true) {
326 test
327 }
328 }
329 `,
330 data: {
331 __typename: 'Query',
332 },
333 });
334});
335
336it('entity list on query and spread fragment', () => {
337 expectCacheIntegrity({
338 query: gql`
339 query Test {
340 __typename
341 items {
342 __typename
343 id
344 }
345 ...TestFragment
346 }
347
348 fragment TestFragment on Query {
349 items {
350 test
351 }
352 }
353 `,
354 data: {
355 __typename: 'Query',
356 items: [{ __typename: 'Item', id: 1, test: true }, null],
357 },
358 });
359});
360
361it('skipped spread fragment', () => {
362 expectCacheIntegrity({
363 query: gql`
364 query Test {
365 __typename
366 ...TestFragment @skip(if: true)
367 }
368
369 fragment TestFragment on Query {
370 test
371 }
372 `,
373 data: {
374 __typename: 'Query',
375 },
376 });
377});
378
379it('embedded objects on entities', () => {
380 expectCacheIntegrity({
381 query: gql`
382 {
383 __typename
384 user {
385 __typename
386 id
387 posts {
388 __typename
389 edges {
390 __typename
391 node {
392 __typename
393 id
394 }
395 }
396 }
397 }
398 }
399 `,
400 data: {
401 __typename: 'Query',
402 user: {
403 __typename: 'User',
404 id: 1,
405 posts: {
406 __typename: 'PostsConnection',
407 edges: [
408 {
409 __typename: 'PostsEdge',
410 node: {
411 __typename: 'Post',
412 id: 1,
413 },
414 },
415 ],
416 },
417 },
418 },
419 });
420});
421
422it('nested viewer selections', () => {
423 expectCacheIntegrity({
424 query: gql`
425 {
426 __typename
427 int
428 viewer {
429 __typename
430 int
431 }
432 }
433 `,
434 data: {
435 __typename: 'Query',
436 int: 42,
437 viewer: {
438 __typename: 'Query',
439 int: 42,
440 },
441 },
442 });
443});