@@ -12,6 +12,10 @@ import { parse, parseConstValue, parseType, parseValue } from '../parser';
1212import { Source } from '../source' ;
1313import { TokenKind } from '../tokenKind' ;
1414
15+ function parseCCN ( source : string ) {
16+ return parse ( source , { experimentalClientControlledNullability : true } ) ;
17+ }
18+
1519function expectSyntaxError ( text : string ) {
1620 return expectToThrowJSON ( ( ) => parse ( text ) ) ;
1721}
@@ -153,7 +157,7 @@ describe('Parser', () => {
153157 } ) ;
154158
155159 it ( 'parses kitchen sink' , ( ) => {
156- expect ( ( ) => parse ( kitchenSinkQuery ) ) . to . not . throw ( ) ;
160+ expect ( ( ) => parseCCN ( kitchenSinkQuery ) ) . to . not . throw ( ) ;
157161 } ) ;
158162
159163 it ( 'allows non-keywords anywhere a Name is allowed' , ( ) => {
@@ -224,6 +228,206 @@ describe('Parser', () => {
224228 ) . to . not . throw ( ) ;
225229 } ) ;
226230
231+ it ( 'parses required field' , ( ) => {
232+ const result = parseCCN ( '{ requiredField! }' ) ;
233+
234+ expectJSON ( result ) . toDeepNestedProperty (
235+ 'definitions[0].selectionSet.selections[0].nullabilityAssertion' ,
236+ {
237+ kind : Kind . NON_NULL_ASSERTION ,
238+ loc : { start : 15 , end : 16 } ,
239+ nullabilityAssertion : undefined ,
240+ } ,
241+ ) ;
242+ } ) ;
243+
244+ it ( 'parses optional field' , ( ) => {
245+ expect ( ( ) => parseCCN ( '{ optionalField? }' ) ) . to . not . throw ( ) ;
246+ } ) ;
247+
248+ it ( 'does not parse field with multiple designators' , ( ) => {
249+ expect ( ( ) => parseCCN ( '{ optionalField?! }' ) ) . to . throw (
250+ 'Syntax Error: Expected Name, found "!".' ,
251+ ) ;
252+
253+ expect ( ( ) => parseCCN ( '{ optionalField!? }' ) ) . to . throw (
254+ 'Syntax Error: Expected Name, found "?".' ,
255+ ) ;
256+ } ) ;
257+
258+ it ( 'parses required with alias' , ( ) => {
259+ expect ( ( ) => parseCCN ( '{ requiredField: field! }' ) ) . to . not . throw ( ) ;
260+ } ) ;
261+
262+ it ( 'parses optional with alias' , ( ) => {
263+ expect ( ( ) => parseCCN ( '{ requiredField: field? }' ) ) . to . not . throw ( ) ;
264+ } ) ;
265+
266+ it ( 'does not parse aliased field with bang on left of colon' , ( ) => {
267+ expect ( ( ) => parseCCN ( '{ requiredField!: field }' ) ) . to . throw ( ) ;
268+ } ) ;
269+
270+ it ( 'does not parse aliased field with question mark on left of colon' , ( ) => {
271+ expect ( ( ) => parseCCN ( '{ requiredField?: field }' ) ) . to . throw ( ) ;
272+ } ) ;
273+
274+ it ( 'does not parse aliased field with bang on left and right of colon' , ( ) => {
275+ expect ( ( ) => parseCCN ( '{ requiredField!: field! }' ) ) . to . throw ( ) ;
276+ } ) ;
277+
278+ it ( 'does not parse aliased field with question mark on left and right of colon' , ( ) => {
279+ expect ( ( ) => parseCCN ( '{ requiredField?: field? }' ) ) . to . throw ( ) ;
280+ } ) ;
281+
282+ it ( 'does not parse designator on query' , ( ) => {
283+ expect ( ( ) => parseCCN ( 'query? { field }' ) ) . to . throw ( ) ;
284+ } ) ;
285+
286+ it ( 'parses required within fragment' , ( ) => {
287+ expect ( ( ) =>
288+ parseCCN ( 'fragment MyFragment on Query { field! }' ) ,
289+ ) . to . not . throw ( ) ;
290+ } ) ;
291+
292+ it ( 'parses optional within fragment' , ( ) => {
293+ expect ( ( ) =>
294+ parseCCN ( 'fragment MyFragment on Query { field? }' ) ,
295+ ) . to . not . throw ( ) ;
296+ } ) ;
297+
298+ it ( 'parses field with required list elements' , ( ) => {
299+ const result = parseCCN ( '{ field[!] }' ) ;
300+
301+ expectJSON ( result ) . toDeepNestedProperty (
302+ 'definitions[0].selectionSet.selections[0].nullabilityAssertion' ,
303+ {
304+ kind : Kind . LIST_NULLABILITY_OPERATOR ,
305+ loc : { start : 7 , end : 10 } ,
306+ nullabilityAssertion : {
307+ kind : Kind . NON_NULL_ASSERTION ,
308+ loc : { start : 8 , end : 9 } ,
309+ nullabilityAssertion : undefined ,
310+ } ,
311+ } ,
312+ ) ;
313+ } ) ;
314+
315+ it ( 'parses field with optional list elements' , ( ) => {
316+ const result = parseCCN ( '{ field[?] }' ) ;
317+
318+ expectJSON ( result ) . toDeepNestedProperty (
319+ 'definitions[0].selectionSet.selections[0].nullabilityAssertion' ,
320+ {
321+ kind : Kind . LIST_NULLABILITY_OPERATOR ,
322+ loc : { start : 7 , end : 10 } ,
323+ nullabilityAssertion : {
324+ kind : Kind . ERROR_BOUNDARY ,
325+ loc : { start : 8 , end : 9 } ,
326+ nullabilityAssertion : undefined ,
327+ } ,
328+ } ,
329+ ) ;
330+ } ) ;
331+
332+ it ( 'parses field with required list' , ( ) => {
333+ const result = parseCCN ( '{ field[]! }' ) ;
334+
335+ expectJSON ( result ) . toDeepNestedProperty (
336+ 'definitions[0].selectionSet.selections[0].nullabilityAssertion' ,
337+ {
338+ kind : Kind . NON_NULL_ASSERTION ,
339+ loc : { start : 7 , end : 10 } ,
340+ nullabilityAssertion : {
341+ kind : Kind . LIST_NULLABILITY_OPERATOR ,
342+ loc : { start : 7 , end : 9 } ,
343+ nullabilityAssertion : undefined ,
344+ } ,
345+ } ,
346+ ) ;
347+ } ) ;
348+
349+ it ( 'parses field with optional list' , ( ) => {
350+ const result = parseCCN ( '{ field[]? }' ) ;
351+
352+ expectJSON ( result ) . toDeepNestedProperty (
353+ 'definitions[0].selectionSet.selections[0].nullabilityAssertion' ,
354+ {
355+ kind : Kind . ERROR_BOUNDARY ,
356+ loc : { start : 7 , end : 10 } ,
357+ nullabilityAssertion : {
358+ kind : Kind . LIST_NULLABILITY_OPERATOR ,
359+ loc : { start : 7 , end : 9 } ,
360+ nullabilityAssertion : undefined ,
361+ } ,
362+ } ,
363+ ) ;
364+ } ) ;
365+
366+ it ( 'parses multidimensional field with mixed list elements' , ( ) => {
367+ const result = parseCCN ( '{ field[[[?]!]]! }' ) ;
368+
369+ expectJSON ( result ) . toDeepNestedProperty (
370+ 'definitions[0].selectionSet.selections[0].nullabilityAssertion' ,
371+ {
372+ kind : Kind . NON_NULL_ASSERTION ,
373+ loc : { start : 7 , end : 16 } ,
374+ nullabilityAssertion : {
375+ kind : Kind . LIST_NULLABILITY_OPERATOR ,
376+ loc : { start : 7 , end : 15 } ,
377+ nullabilityAssertion : {
378+ kind : Kind . LIST_NULLABILITY_OPERATOR ,
379+ loc : { start : 8 , end : 14 } ,
380+ nullabilityAssertion : {
381+ kind : Kind . NON_NULL_ASSERTION ,
382+ loc : { start : 9 , end : 13 } ,
383+ nullabilityAssertion : {
384+ kind : Kind . LIST_NULLABILITY_OPERATOR ,
385+ loc : { start : 9 , end : 12 } ,
386+ nullabilityAssertion : {
387+ kind : Kind . ERROR_BOUNDARY ,
388+ loc : { start : 10 , end : 11 } ,
389+ nullabilityAssertion : undefined ,
390+ } ,
391+ } ,
392+ } ,
393+ } ,
394+ } ,
395+ } ,
396+ ) ;
397+ } ) ;
398+
399+ it ( 'does not parse field with unbalanced brackets' , ( ) => {
400+ expect ( ( ) => parseCCN ( '{ field[[] }' ) ) . to . throw (
401+ 'Syntax Error: Expected "]", found "}".' ,
402+ ) ;
403+
404+ expect ( ( ) => parseCCN ( '{ field[]] }' ) ) . to . throw (
405+ 'Syntax Error: Expected Name, found "]".' ,
406+ ) ;
407+
408+ expect ( ( ) => parse ( '{ field] }' ) ) . to . throw (
409+ 'Syntax Error: Expected Name, found "]".' ,
410+ ) ;
411+
412+ expect ( ( ) => parseCCN ( '{ field[ }' ) ) . to . throw (
413+ 'Syntax Error: Expected "]", found "}".' ,
414+ ) ;
415+ } ) ;
416+
417+ it ( 'does not parse field with assorted invalid nullability designators' , ( ) => {
418+ expect ( ( ) => parseCCN ( '{ field[][] }' ) ) . to . throw (
419+ 'Syntax Error: Expected Name, found "[".' ,
420+ ) ;
421+
422+ expect ( ( ) => parseCCN ( '{ field[!!] }' ) ) . to . throw (
423+ 'Syntax Error: Expected "]", found "!".' ,
424+ ) ;
425+
426+ expect ( ( ) => parseCCN ( '{ field[]?! }' ) ) . to . throw (
427+ 'Syntax Error: Expected Name, found "!".' ,
428+ ) ;
429+ } ) ;
430+
227431 it ( 'creates ast' , ( ) => {
228432 const result = parse ( dedent `
229433 {
@@ -274,6 +478,7 @@ describe('Parser', () => {
274478 loc : { start : 9 , end : 14 } ,
275479 } ,
276480 ] ,
481+ nullabilityAssertion : undefined ,
277482 directives : [ ] ,
278483 selectionSet : {
279484 kind : Kind . SELECTION_SET ,
@@ -289,6 +494,7 @@ describe('Parser', () => {
289494 value : 'id' ,
290495 } ,
291496 arguments : [ ] ,
497+ nullabilityAssertion : undefined ,
292498 directives : [ ] ,
293499 selectionSet : undefined ,
294500 } ,
@@ -302,6 +508,7 @@ describe('Parser', () => {
302508 value : 'name' ,
303509 } ,
304510 arguments : [ ] ,
511+ nullabilityAssertion : undefined ,
305512 directives : [ ] ,
306513 selectionSet : undefined ,
307514 } ,
@@ -349,6 +556,7 @@ describe('Parser', () => {
349556 value : 'node' ,
350557 } ,
351558 arguments : [ ] ,
559+ nullabilityAssertion : undefined ,
352560 directives : [ ] ,
353561 selectionSet : {
354562 kind : Kind . SELECTION_SET ,
@@ -364,6 +572,7 @@ describe('Parser', () => {
364572 value : 'id' ,
365573 } ,
366574 arguments : [ ] ,
575+ nullabilityAssertion : undefined ,
367576 directives : [ ] ,
368577 selectionSet : undefined ,
369578 } ,
0 commit comments