1 | import {
|
2 | buildSchema,
|
3 | parse,
|
4 | GraphQLNonNull,
|
5 | GraphQLString,
|
6 | GraphQLEnumType,
|
7 | GraphQLList,
|
8 | } from "graphql";
|
9 |
|
10 | import { loadSchema } from "apollo-codegen-core/lib/loading";
|
11 | const schema = loadSchema(
|
12 | require.resolve("../../../../__fixtures__/starwars/schema.json")
|
13 | );
|
14 |
|
15 | import {
|
16 | compileToIR,
|
17 | CompilerOptions,
|
18 | CompilerContext,
|
19 | SelectionSet,
|
20 | Field,
|
21 | Argument,
|
22 | } from "apollo-codegen-core/lib/compiler";
|
23 |
|
24 | import { SwiftAPIGenerator } from "../codeGeneration";
|
25 |
|
26 | describe("Swift code generation", () => {
|
27 | let generator: SwiftAPIGenerator;
|
28 |
|
29 | beforeEach(() => {
|
30 | generator = new SwiftAPIGenerator({});
|
31 | });
|
32 |
|
33 | function compile(
|
34 | source: string,
|
35 | options: CompilerOptions = {
|
36 | mergeInFieldsFromFragmentSpreads: true,
|
37 | omitDeprecatedEnumCases: false,
|
38 | }
|
39 | ): CompilerContext {
|
40 | const document = parse(source);
|
41 | const context = compileToIR(schema, document, options);
|
42 | generator.context = context;
|
43 | return context;
|
44 | }
|
45 |
|
46 | describe("#classDeclarationForOperation()", () => {
|
47 | it(`should generate a class declaration for a query with variables`, () => {
|
48 | const { operations } = compile(`
|
49 | query HeroName($episode: Episode) {
|
50 | hero(episode: $episode) {
|
51 | name
|
52 | }
|
53 | }
|
54 | `);
|
55 |
|
56 | generator.classDeclarationForOperation(
|
57 | operations["HeroName"],
|
58 | false,
|
59 | false
|
60 | );
|
61 |
|
62 | expect(generator.output).toMatchSnapshot();
|
63 | });
|
64 |
|
65 | it(`should generate a class declaration for a query with fragment spreads`, () => {
|
66 | const { operations } = compile(`
|
67 | query Hero {
|
68 | hero {
|
69 | ...HeroDetails
|
70 | }
|
71 | }
|
72 |
|
73 | fragment HeroDetails on Character {
|
74 | name
|
75 | }
|
76 | `);
|
77 |
|
78 | generator.classDeclarationForOperation(operations["Hero"], false, false);
|
79 |
|
80 | expect(generator.output).toMatchSnapshot();
|
81 | });
|
82 |
|
83 | it(`should generate a class declaration for a query with conditional fragment spreads`, () => {
|
84 | const { operations } = compile(`
|
85 | query Hero {
|
86 | hero {
|
87 | ...DroidDetails
|
88 | }
|
89 | }
|
90 |
|
91 | fragment DroidDetails on Droid {
|
92 | primaryFunction
|
93 | }
|
94 | `);
|
95 |
|
96 | generator.classDeclarationForOperation(operations["Hero"], false, false);
|
97 |
|
98 | expect(generator.output).toMatchSnapshot();
|
99 | });
|
100 |
|
101 | it("should correctly escape a mutli-line string literal", () => {
|
102 | const { operations } = compile(`
|
103 | mutation CreateReview($episode: Episode) {
|
104 | createReview(episode: $episode, review: {stars: 5, commentary:
|
105 | """
|
106 | Wow!
|
107 | I thought
|
108 | This movie ROCKED!
|
109 | """
|
110 | }) {
|
111 | stars
|
112 | commentary
|
113 | }
|
114 | }
|
115 | `);
|
116 |
|
117 | generator.classDeclarationForOperation(operations["CreateReview"]);
|
118 |
|
119 | expect(generator.output).toMatchSnapshot();
|
120 | });
|
121 |
|
122 | it("should correctly escape a mutli-line string literal with backslashes", () => {
|
123 | const { operations } = compile(`
|
124 | mutation CreateReview($episode: Episode) {
|
125 | createReview(episode: $episode, review: {stars: 5, commentary:
|
126 | """
|
127 | Wow!
|
128 | I thought
|
129 | This movie \\ ROCKED!
|
130 | """
|
131 | }) {
|
132 | stars
|
133 | commentary
|
134 | }
|
135 | }
|
136 | `);
|
137 |
|
138 | generator.classDeclarationForOperation(
|
139 | operations["CreateReview"],
|
140 | false,
|
141 | false
|
142 | );
|
143 |
|
144 | expect(generator.output).toMatchSnapshot();
|
145 | });
|
146 |
|
147 | it(`should generate a class declaration for a query with a fragment spread nested in an inline fragment`, () => {
|
148 | const { operations } = compile(`
|
149 | query Hero {
|
150 | hero {
|
151 | ... on Droid {
|
152 | ...HeroDetails
|
153 | }
|
154 | }
|
155 | }
|
156 |
|
157 | fragment HeroDetails on Character {
|
158 | name
|
159 | }
|
160 | `);
|
161 |
|
162 | generator.classDeclarationForOperation(operations["Hero"], false, false);
|
163 |
|
164 | expect(generator.output).toMatchSnapshot();
|
165 | });
|
166 |
|
167 | it(`should generate a class declaration for a mutation with variables`, () => {
|
168 | const { operations } = compile(`
|
169 | mutation CreateReview($episode: Episode) {
|
170 | createReview(episode: $episode, review: { stars: 5, commentary: "Wow!" }) {
|
171 | stars
|
172 | commentary
|
173 | }
|
174 | }
|
175 | `);
|
176 |
|
177 | generator.classDeclarationForOperation(operations["CreateReview"]);
|
178 |
|
179 | expect(generator.output).toMatchSnapshot();
|
180 | });
|
181 |
|
182 | it(`should generate a class declaration with an operationIdentifier property when generateOperationIds is specified`, () => {
|
183 | const { operations } = compile(
|
184 | `
|
185 | query Hero {
|
186 | hero {
|
187 | ...HeroDetails
|
188 | }
|
189 | }
|
190 | fragment HeroDetails on Character {
|
191 | name
|
192 | }
|
193 | `,
|
194 | {
|
195 | generateOperationIds: true,
|
196 | mergeInFieldsFromFragmentSpreads: true,
|
197 | omitDeprecatedEnumCases: false,
|
198 | }
|
199 | );
|
200 |
|
201 | generator.classDeclarationForOperation(operations["Hero"], false, false);
|
202 |
|
203 | expect(generator.output).toMatchSnapshot();
|
204 | });
|
205 | });
|
206 |
|
207 | describe("#initializerDeclarationForProperties()", () => {
|
208 | it(`should generate initializer for a property`, () => {
|
209 | generator.initializerDeclarationForProperties([
|
210 | { propertyName: "episode", typeName: "Episode" },
|
211 | ]);
|
212 |
|
213 | expect(generator.output).toMatchSnapshot();
|
214 | });
|
215 |
|
216 | it(`should generate initializer for an optional property`, () => {
|
217 | generator.initializerDeclarationForProperties([
|
218 | { propertyName: "episode", typeName: "Episode?", isOptional: true },
|
219 | ]);
|
220 |
|
221 | expect(generator.output).toMatchSnapshot();
|
222 | });
|
223 |
|
224 | it(`should generate initializer for multiple properties`, () => {
|
225 | generator.initializerDeclarationForProperties([
|
226 | { propertyName: "episode", typeName: "Episode?", isOptional: true },
|
227 | { propertyName: "scene", typeName: "String?", isOptional: true },
|
228 | ]);
|
229 |
|
230 | expect(generator.output).toMatchSnapshot();
|
231 | });
|
232 | });
|
233 |
|
234 | describe("#propertyAssignmentForField()", () => {
|
235 | it("should generate expression for nullable scalar", () => {
|
236 | expect(
|
237 | generator.propertyAssignmentForField({
|
238 | responseKey: "response_key",
|
239 | propertyName: "propertyName",
|
240 | type: GraphQLString,
|
241 | }).source
|
242 | ).toBe('"response_key": propertyName');
|
243 | });
|
244 |
|
245 | it("should generate expression for non-null scalar", () => {
|
246 | expect(
|
247 | generator.propertyAssignmentForField({
|
248 | responseKey: "response_key",
|
249 | propertyName: "propertyName",
|
250 | type: new GraphQLNonNull(GraphQLString),
|
251 | }).source
|
252 | ).toBe('"response_key": propertyName');
|
253 | });
|
254 |
|
255 | it("should generate expression for nullable list of nullable scalars", () => {
|
256 | expect(
|
257 | generator.propertyAssignmentForField({
|
258 | responseKey: "response_key",
|
259 | propertyName: "propertyName",
|
260 | type: new GraphQLList(GraphQLString),
|
261 | }).source
|
262 | ).toBe('"response_key": propertyName');
|
263 | });
|
264 |
|
265 | it("should generate expression for nullable list of non-null scalars", () => {
|
266 | expect(
|
267 | generator.propertyAssignmentForField({
|
268 | responseKey: "response_key",
|
269 | propertyName: "propertyName",
|
270 | type: new GraphQLList(new GraphQLNonNull(GraphQLString)),
|
271 | }).source
|
272 | ).toBe('"response_key": propertyName');
|
273 | });
|
274 |
|
275 | it("should generate expression for non-null list of nullable scalars", () => {
|
276 | expect(
|
277 | generator.propertyAssignmentForField({
|
278 | responseKey: "response_key",
|
279 | propertyName: "propertyName",
|
280 | type: new GraphQLNonNull(new GraphQLList(GraphQLString)),
|
281 | }).source
|
282 | ).toBe('"response_key": propertyName');
|
283 | });
|
284 |
|
285 | it("should generate expression for non-null list of non-null scalars", () => {
|
286 | expect(
|
287 | generator.propertyAssignmentForField({
|
288 | responseKey: "response_key",
|
289 | propertyName: "propertyName",
|
290 | type: new GraphQLNonNull(
|
291 | new GraphQLList(new GraphQLNonNull(GraphQLString))
|
292 | ),
|
293 | }).source
|
294 | ).toBe('"response_key": propertyName');
|
295 | });
|
296 |
|
297 | it("should generate expression for nullable composite", () => {
|
298 | expect(
|
299 | generator.propertyAssignmentForField({
|
300 | responseKey: "response_key",
|
301 | propertyName: "propertyName",
|
302 | type: schema.getType("Droid"),
|
303 | }).source
|
304 | ).toBe(
|
305 | '"response_key": propertyName.flatMap { (value: Droid) -> ResultMap in value.resultMap }'
|
306 | );
|
307 | });
|
308 |
|
309 | it("should generate expression for non-null composite", () => {
|
310 | expect(
|
311 | generator.propertyAssignmentForField({
|
312 | responseKey: "response_key",
|
313 | propertyName: "propertyName",
|
314 | type: new GraphQLNonNull(schema.getType("Droid")),
|
315 | }).source
|
316 | ).toBe('"response_key": propertyName.resultMap');
|
317 | });
|
318 |
|
319 | it("should generate expression for nullable list of nullable composites", () => {
|
320 | expect(
|
321 | generator.propertyAssignmentForField({
|
322 | responseKey: "response_key",
|
323 | propertyName: "propertyName",
|
324 | type: new GraphQLList(schema.getType("Droid")),
|
325 | }).source
|
326 | ).toBe(
|
327 | '"response_key": propertyName.flatMap { (value: [Droid?]) -> [ResultMap?] in value.map { (value: Droid?) -> ResultMap? in value.flatMap { (value: Droid) -> ResultMap in value.resultMap } } }'
|
328 | );
|
329 | });
|
330 |
|
331 | it("should generate expression for nullable list of non-null composites", () => {
|
332 | expect(
|
333 | generator.propertyAssignmentForField({
|
334 | responseKey: "response_key",
|
335 | propertyName: "propertyName",
|
336 | type: new GraphQLList(new GraphQLNonNull(schema.getType("Droid"))),
|
337 | }).source
|
338 | ).toBe(
|
339 | '"response_key": propertyName.flatMap { (value: [Droid]) -> [ResultMap] in value.map { (value: Droid) -> ResultMap in value.resultMap } }'
|
340 | );
|
341 | });
|
342 |
|
343 | it("should generate expression for non-null list of nullable composites", () => {
|
344 | expect(
|
345 | generator.propertyAssignmentForField({
|
346 | responseKey: "response_key",
|
347 | propertyName: "propertyName",
|
348 | type: new GraphQLNonNull(new GraphQLList(schema.getType("Droid"))),
|
349 | }).source
|
350 | ).toBe(
|
351 | '"response_key": propertyName.map { (value: Droid?) -> ResultMap? in value.flatMap { (value: Droid) -> ResultMap in value.resultMap } }'
|
352 | );
|
353 | });
|
354 |
|
355 | it("should generate expression for non-null list of non-null composites", () => {
|
356 | expect(
|
357 | generator.propertyAssignmentForField({
|
358 | responseKey: "response_key",
|
359 | propertyName: "propertyName",
|
360 | type: new GraphQLNonNull(
|
361 | new GraphQLList(new GraphQLNonNull(schema.getType("Droid")))
|
362 | ),
|
363 | }).source
|
364 | ).toBe(
|
365 | '"response_key": propertyName.map { (value: Droid) -> ResultMap in value.resultMap }'
|
366 | );
|
367 | });
|
368 | });
|
369 |
|
370 | describe("#propertyDeclarationForField()", () => {
|
371 | it(`should generate structName as testCTum for key testCTA`, () => {
|
372 |
|
373 |
|
374 | const schema = buildSchema(`
|
375 | schema {
|
376 | query: Query
|
377 | }
|
378 | type Query {
|
379 | foo(input: FooInput!): FooOutput
|
380 | }
|
381 | input FooInput {
|
382 | id: ID
|
383 | }
|
384 | type FooOutput {
|
385 | testCTA: Link
|
386 | }
|
387 | union Link = InternalLink | ExternalLink
|
388 | type InternalLink {
|
389 | path: String
|
390 | }
|
391 | type ExternalLink {
|
392 | url: String
|
393 | }
|
394 | `);
|
395 | const document = parse(`
|
396 | query Test {
|
397 | foo(input: {}) {
|
398 | testCTA {
|
399 | ... on InternalLink {
|
400 | path
|
401 | }
|
402 | ... on ExternalLink {
|
403 | url
|
404 | }
|
405 | }
|
406 | }
|
407 | }
|
408 | `);
|
409 | const context = compileToIR(schema, document);
|
410 | generator.context = context;
|
411 |
|
412 | const { operations, typesUsed } = context;
|
413 |
|
414 | const outputField = operations["Test"].selectionSet
|
415 | .selections[0] as Field;
|
416 | generator.propertyDeclarationForField(
|
417 | outputField.selectionSet.selections[0] as Field & Property
|
418 | );
|
419 |
|
420 | expect(generator.output).toMatchSnapshot();
|
421 | });
|
422 | });
|
423 |
|
424 | describe("#structDeclarationForFragment()", () => {
|
425 | it(`should generate a struct declaration for a fragment with an abstract type condition`, () => {
|
426 | const { fragments } = compile(`
|
427 | fragment HeroDetails on Character {
|
428 | name
|
429 | appearsIn
|
430 | }
|
431 | `);
|
432 |
|
433 | generator.structDeclarationForFragment(fragments["HeroDetails"], false);
|
434 |
|
435 | expect(generator.output).toMatchSnapshot();
|
436 | });
|
437 |
|
438 | it(`should generate a struct declaration for a fragment with a concrete type condition`, () => {
|
439 | const { fragments } = compile(`
|
440 | fragment DroidDetails on Droid {
|
441 | name
|
442 | primaryFunction
|
443 | }
|
444 | `);
|
445 |
|
446 | generator.structDeclarationForFragment(
|
447 | fragments["DroidDetails"],
|
448 | false,
|
449 | false
|
450 | );
|
451 |
|
452 | expect(generator.output).toMatchSnapshot();
|
453 | });
|
454 |
|
455 | it(`should generate a struct declaration for a fragment with a subselection`, () => {
|
456 | const { fragments } = compile(`
|
457 | fragment HeroDetails on Character {
|
458 | name
|
459 | friends {
|
460 | name
|
461 | }
|
462 | }
|
463 | `);
|
464 |
|
465 | generator.structDeclarationForFragment(
|
466 | fragments["HeroDetails"],
|
467 | false,
|
468 | false
|
469 | );
|
470 |
|
471 | expect(generator.output).toMatchSnapshot();
|
472 | });
|
473 |
|
474 | it(`should generate a struct declaration for a fragment that includes a fragment spread`, () => {
|
475 | const { fragments } = compile(`
|
476 | fragment HeroDetails on Character {
|
477 | name
|
478 | ...MoreHeroDetails
|
479 | }
|
480 |
|
481 | fragment MoreHeroDetails on Character {
|
482 | appearsIn
|
483 | }
|
484 | `);
|
485 |
|
486 | generator.structDeclarationForFragment(
|
487 | fragments["HeroDetails"],
|
488 | false,
|
489 | false
|
490 | );
|
491 |
|
492 | expect(generator.output).toMatchSnapshot();
|
493 | });
|
494 | });
|
495 |
|
496 | describe("#structDeclarationForSelectionSet()", () => {
|
497 | it(`should generate a struct declaration for a selection set`, () => {
|
498 | const { operations } = compile(`
|
499 | query Hero {
|
500 | hero {
|
501 | name
|
502 | }
|
503 | }
|
504 | `);
|
505 |
|
506 | const selectionSet = (
|
507 | operations["Hero"].selectionSet.selections[0] as Field
|
508 | ).selectionSet as SelectionSet;
|
509 |
|
510 | generator.structDeclarationForSelectionSet(
|
511 | {
|
512 | structName: "Hero",
|
513 | selectionSet,
|
514 | },
|
515 | false
|
516 | );
|
517 |
|
518 | expect(generator.output).toMatchSnapshot();
|
519 | });
|
520 |
|
521 | it(`should preserve leading and trailing underscores on fields`, () => {
|
522 | const { operations } = compile(`
|
523 | query Hero {
|
524 | hero {
|
525 | _name: name
|
526 | _camel_case_id__: id
|
527 | }
|
528 | }
|
529 | `);
|
530 |
|
531 | const selectionSet = (
|
532 | operations["Hero"].selectionSet.selections[0] as Field
|
533 | ).selectionSet as SelectionSet;
|
534 |
|
535 | generator.structDeclarationForSelectionSet(
|
536 | {
|
537 | structName: "Hero",
|
538 | selectionSet,
|
539 | },
|
540 | false
|
541 | );
|
542 |
|
543 | expect(generator.output).toMatchSnapshot();
|
544 | });
|
545 |
|
546 | it(`should escape reserved keywords in a struct declaration for a selection set`, () => {
|
547 | const { operations } = compile(`
|
548 | query Hero {
|
549 | hero {
|
550 | private: name
|
551 | self: friends {
|
552 | id
|
553 | }
|
554 | }
|
555 | }
|
556 | `);
|
557 |
|
558 | const selectionSet = (
|
559 | operations["Hero"].selectionSet.selections[0] as Field
|
560 | ).selectionSet as SelectionSet;
|
561 |
|
562 | generator.structDeclarationForSelectionSet(
|
563 | {
|
564 | structName: "Hero",
|
565 | selectionSet,
|
566 | },
|
567 | false
|
568 | );
|
569 |
|
570 | expect(generator.output).toMatchSnapshot();
|
571 | });
|
572 |
|
573 | it(`should escape init specially in a struct declaration initializer for a selection set`, () => {
|
574 | const { operations } = compile(`
|
575 | query Humans {
|
576 | human(id: 0) {
|
577 | self: friends {
|
578 | id
|
579 | }
|
580 | }
|
581 | human(id: 1) {
|
582 | self: friends {
|
583 | id
|
584 | }
|
585 | _self: name
|
586 | }
|
587 | }
|
588 | `);
|
589 |
|
590 | const human0 = (operations["Humans"].selectionSet.selections[0] as Field)
|
591 | .selectionSet as SelectionSet;
|
592 | const human1 = (operations["Humans"].selectionSet.selections[1] as Field)
|
593 | .selectionSet as SelectionSet;
|
594 |
|
595 | generator.structDeclarationForSelectionSet(
|
596 | {
|
597 | structName: "Human",
|
598 | selectionSet: human0,
|
599 | },
|
600 | false
|
601 | );
|
602 | generator.structDeclarationForSelectionSet(
|
603 | {
|
604 | structName: "Human",
|
605 | selectionSet: human1,
|
606 | },
|
607 | false
|
608 | );
|
609 |
|
610 | expect(generator.output).toMatchSnapshot();
|
611 | });
|
612 |
|
613 | it(`should generate a nested struct declaration for a selection set with subselections`, () => {
|
614 | const { operations } = compile(`
|
615 | query Hero {
|
616 | hero {
|
617 | friends {
|
618 | name
|
619 | }
|
620 | }
|
621 | }
|
622 | `);
|
623 |
|
624 | const selectionSet = (
|
625 | operations["Hero"].selectionSet.selections[0] as Field
|
626 | ).selectionSet as SelectionSet;
|
627 |
|
628 | generator.structDeclarationForSelectionSet(
|
629 | {
|
630 | structName: "Hero",
|
631 | selectionSet,
|
632 | },
|
633 | false
|
634 | );
|
635 |
|
636 | expect(generator.output).toMatchSnapshot();
|
637 | });
|
638 |
|
639 | it(`should generate a struct declaration for a selection set with a fragment spread that matches the parent type`, () => {
|
640 | const { operations } = compile(`
|
641 | query Hero {
|
642 | hero {
|
643 | name
|
644 | ...HeroDetails
|
645 | }
|
646 | }
|
647 |
|
648 | fragment HeroDetails on Character {
|
649 | name
|
650 | }
|
651 | `);
|
652 |
|
653 | const selectionSet = (
|
654 | operations["Hero"].selectionSet.selections[0] as Field
|
655 | ).selectionSet as SelectionSet;
|
656 |
|
657 | generator.structDeclarationForSelectionSet(
|
658 | {
|
659 | structName: "Hero",
|
660 | selectionSet,
|
661 | },
|
662 | false
|
663 | );
|
664 |
|
665 | expect(generator.output).toMatchSnapshot();
|
666 | });
|
667 |
|
668 | it(`should generate a struct declaration for a selection set with a fragment spread with a more specific type condition`, () => {
|
669 | const { operations } = compile(`
|
670 | query Hero {
|
671 | hero {
|
672 | name
|
673 | ...DroidDetails
|
674 | }
|
675 | }
|
676 |
|
677 | fragment DroidDetails on Droid {
|
678 | name
|
679 | }
|
680 | `);
|
681 |
|
682 | const selectionSet = (
|
683 | operations["Hero"].selectionSet.selections[0] as Field
|
684 | ).selectionSet as SelectionSet;
|
685 |
|
686 | generator.structDeclarationForSelectionSet(
|
687 | {
|
688 | structName: "Hero",
|
689 | selectionSet,
|
690 | },
|
691 | false
|
692 | );
|
693 |
|
694 | expect(generator.output).toMatchSnapshot();
|
695 | });
|
696 |
|
697 | it(`should generate a struct declaration for a selection set with an inline fragment`, () => {
|
698 | const { operations } = compile(`
|
699 | query Hero {
|
700 | hero {
|
701 | name
|
702 | ... on Droid {
|
703 | primaryFunction
|
704 | }
|
705 | }
|
706 | }
|
707 | `);
|
708 |
|
709 | const selectionSet = (
|
710 | operations["Hero"].selectionSet.selections[0] as Field
|
711 | ).selectionSet as SelectionSet;
|
712 |
|
713 | generator.structDeclarationForSelectionSet(
|
714 | {
|
715 | structName: "Hero",
|
716 | selectionSet,
|
717 | },
|
718 | false
|
719 | );
|
720 |
|
721 | expect(generator.output).toMatchSnapshot();
|
722 | });
|
723 |
|
724 | it(`should generate a struct declaration for a fragment spread nested in an inline fragment`, () => {
|
725 | const { operations } = compile(`
|
726 | query Hero {
|
727 | hero {
|
728 | name
|
729 | ... on Droid {
|
730 | ...HeroDetails
|
731 | }
|
732 | }
|
733 | }
|
734 |
|
735 | fragment HeroDetails on Character {
|
736 | name
|
737 | }
|
738 | `);
|
739 |
|
740 | const selectionSet = (
|
741 | operations["Hero"].selectionSet.selections[0] as Field
|
742 | ).selectionSet as SelectionSet;
|
743 |
|
744 | generator.structDeclarationForSelectionSet(
|
745 | {
|
746 | structName: "Hero",
|
747 | selectionSet,
|
748 | },
|
749 | false
|
750 | );
|
751 |
|
752 | expect(generator.output).toMatchSnapshot();
|
753 | });
|
754 |
|
755 | it(`should generate a struct declaration for a selection set with a conditional field`, () => {
|
756 | const { operations } = compile(`
|
757 | query Hero($includeName: Boolean!) {
|
758 | hero {
|
759 | name @include(if: $includeName)
|
760 | }
|
761 | }
|
762 | `);
|
763 |
|
764 | const selectionSet = (
|
765 | operations["Hero"].selectionSet.selections[0] as Field
|
766 | ).selectionSet as SelectionSet;
|
767 |
|
768 | generator.structDeclarationForSelectionSet(
|
769 | {
|
770 | structName: "Hero",
|
771 | selectionSet,
|
772 | },
|
773 | false
|
774 | );
|
775 |
|
776 | expect(generator.output).toMatchSnapshot();
|
777 | });
|
778 | });
|
779 |
|
780 | describe("#typeDeclarationForGraphQLType()", () => {
|
781 | it("should generate an enum declaration for a GraphQLEnumType", () => {
|
782 | generator.typeDeclarationForGraphQLType(schema.getType("Episode"), false);
|
783 |
|
784 | expect(generator.output).toMatchSnapshot();
|
785 | });
|
786 |
|
787 | it("should escape identifiers in cases of enum declaration for a GraphQLEnumType", () => {
|
788 | const albumPrivaciesEnum = new GraphQLEnumType({
|
789 | name: "AlbumPrivacies",
|
790 | values: { PUBLIC: { value: "PUBLIC" }, PRIVATE: { value: "PRIVATE" } },
|
791 | });
|
792 |
|
793 | generator.typeDeclarationForGraphQLType(albumPrivaciesEnum, false);
|
794 |
|
795 | expect(generator.output).toMatchSnapshot();
|
796 | });
|
797 |
|
798 | it("should omit deprecated cases from an enum declaration for a GraphQLEnumType", () => {
|
799 | const { operations } = compile(
|
800 | `
|
801 | query Starship {
|
802 | starship(id: 1) {
|
803 | length(unit: METER)
|
804 | }
|
805 | }
|
806 | `,
|
807 | {
|
808 | generateOperationIds: true,
|
809 | mergeInFieldsFromFragmentSpreads: true,
|
810 | omitDeprecatedEnumCases: true,
|
811 | }
|
812 | );
|
813 |
|
814 | let starship = operations["Starship"].selectionSet.selections[0] as Field;
|
815 | let starshipLength = starship.selectionSet.selections[0] as Field;
|
816 | let lengthUnitArg = starshipLength.args[0].type;
|
817 |
|
818 | generator.typeDeclarationForGraphQLType(lengthUnitArg, false);
|
819 |
|
820 | expect(generator.output).toMatchSnapshot();
|
821 | });
|
822 |
|
823 | it("should include deprecated cases in an enum declaration for a GraphQLEnumType", () => {
|
824 | const { operations } = compile(`
|
825 | query Starship {
|
826 | starship(id: 1) {
|
827 | length(unit: METER)
|
828 | }
|
829 | }
|
830 | `);
|
831 |
|
832 | let starship = operations["Starship"].selectionSet.selections[0] as Field;
|
833 | let starshipLength = starship.selectionSet.selections[0] as Field;
|
834 | let lengthUnitArg = starshipLength.args[0].type;
|
835 |
|
836 | generator.typeDeclarationForGraphQLType(lengthUnitArg, false);
|
837 |
|
838 | expect(generator.output).toMatchSnapshot();
|
839 | });
|
840 |
|
841 | it("should generate a struct declaration for a GraphQLInputObjectType", () => {
|
842 | generator.typeDeclarationForGraphQLType(
|
843 | schema.getType("ReviewInput"),
|
844 | false
|
845 | );
|
846 |
|
847 | expect(generator.output).toMatchSnapshot();
|
848 | });
|
849 | });
|
850 |
|
851 | describe("#dictionaryLiteralForFieldArguments()", () => {
|
852 | it("should include expressions for input objects with variables", () => {
|
853 | const { operations } = compile(`
|
854 | mutation FieldArgumentsWithInputObjects($commentary: String!, $red: Int!) {
|
855 | createReview(episode: JEDI, review: { stars: 2, commentary: $commentary, favorite_color: { red: $red, blue: 100, green: 50 } }) {
|
856 | commentary
|
857 | }
|
858 | }
|
859 | `);
|
860 |
|
861 | const fieldArguments = (
|
862 | operations["FieldArgumentsWithInputObjects"].selectionSet
|
863 | .selections[0] as Field
|
864 | ).args as Argument[];
|
865 | const dictionaryLiteral =
|
866 | generator.helpers.dictionaryLiteralForFieldArguments(
|
867 | fieldArguments
|
868 | ).source;
|
869 |
|
870 | expect(dictionaryLiteral).toBe(
|
871 | '["episode": "JEDI", "review": ["stars": 2, "commentary": GraphQLVariable("commentary"), "favorite_color": ["red": GraphQLVariable("red"), "blue": 100, "green": 50]]]'
|
872 | );
|
873 | });
|
874 |
|
875 | it("should handle empty input objects", () => {
|
876 |
|
877 |
|
878 |
|
879 | const schema = buildSchema(`
|
880 | schema {
|
881 | query: Query
|
882 | }
|
883 | type Query {
|
884 | foo(input: FooInput!): Int
|
885 | }
|
886 | input FooInput {
|
887 | id: ID
|
888 | }
|
889 | `);
|
890 | const document = parse(`
|
891 | query FieldArgumentsWithEmptyInputObject {
|
892 | foo(input: {}) {
|
893 | id
|
894 | }
|
895 | }
|
896 | `);
|
897 | const context = compileToIR(schema, document);
|
898 | generator.context = context;
|
899 |
|
900 | const { operations } = context;
|
901 | const fieldArguments = (
|
902 | operations["FieldArgumentsWithEmptyInputObject"].selectionSet
|
903 | .selections[0] as Field
|
904 | ).args as Argument[];
|
905 | const dictionaryLiteral =
|
906 | generator.helpers.dictionaryLiteralForFieldArguments(
|
907 | fieldArguments
|
908 | ).source;
|
909 |
|
910 | expect(dictionaryLiteral).toBe('["input": [:]]');
|
911 | });
|
912 |
|
913 | it("should handle empty input arrays", () => {
|
914 |
|
915 | const schema = buildSchema(`
|
916 | schema {
|
917 | query: Query
|
918 | }
|
919 | type Query {
|
920 | foo(input: [Int!]!): Int
|
921 | }
|
922 | `);
|
923 | const document = parse(`
|
924 | query FieldArgumentsWithEmptyInputArray {
|
925 | foo(input: []) {
|
926 | id
|
927 | }
|
928 | }
|
929 | `);
|
930 | const context = compileToIR(schema, document);
|
931 | generator.context = context;
|
932 |
|
933 | const { operations } = context;
|
934 | const fieldArguments = (
|
935 | operations["FieldArgumentsWithEmptyInputArray"].selectionSet
|
936 | .selections[0] as Field
|
937 | ).args as Argument[];
|
938 | const dictionaryLiteral =
|
939 | generator.helpers.dictionaryLiteralForFieldArguments(
|
940 | fieldArguments
|
941 | ).source;
|
942 |
|
943 | expect(dictionaryLiteral).toBe('["input": []]');
|
944 | });
|
945 |
|
946 | it("should handle input fields of various scalar types including null", () => {
|
947 |
|
948 | const schema = buildSchema(`
|
949 | schema {
|
950 | query: Query
|
951 | }
|
952 | type Query {
|
953 | foo(input: FooInput!): Int
|
954 | }
|
955 | input FooInput {
|
956 | id: ID
|
957 | id2: ID
|
958 | name: String
|
959 | age: Int
|
960 | rating: Float
|
961 | bool: Boolean
|
962 | }
|
963 | `);
|
964 | const document = parse(`
|
965 | query FieldArgumentsWithVariousScalars {
|
966 | foo(input: { id: null, id2: "4", name: "Anne", age: 27, rating: 4.7, bool: true }) {
|
967 | id
|
968 | }
|
969 | }
|
970 | `);
|
971 | const context = compileToIR(schema, document);
|
972 | generator.context = context;
|
973 |
|
974 | const { operations } = context;
|
975 | const fieldArguments = (
|
976 | operations["FieldArgumentsWithVariousScalars"].selectionSet
|
977 | .selections[0] as Field
|
978 | ).args as Argument[];
|
979 | const dictionaryLiteral =
|
980 | generator.helpers.dictionaryLiteralForFieldArguments(
|
981 | fieldArguments
|
982 | ).source;
|
983 |
|
984 | expect(dictionaryLiteral).toBe(
|
985 | '["input": ["id": nil, "id2": "4", "name": "Anne", "age": 27, "rating": 4.7, "bool": true]]'
|
986 | );
|
987 | });
|
988 | });
|
989 | });
|