11import { expect } from 'chai' ;
22import { describe , it } from 'mocha' ;
33
4+ import { identityFunc } from '../../jsutils/identityFunc.js' ;
5+ import { invariant } from '../../jsutils/invariant.js' ;
6+ import type { ObjMap } from '../../jsutils/ObjMap.js' ;
7+
8+ import { parseValue } from '../../language/parser.js' ;
9+ import { print } from '../../language/printer.js' ;
10+
411import type { GraphQLInputType } from '../../type/definition.js' ;
512import {
613 GraphQLEnumType ,
@@ -9,9 +16,15 @@ import {
916 GraphQLNonNull ,
1017 GraphQLScalarType ,
1118} from '../../type/definition.js' ;
12- import { GraphQLInt } from '../../type/scalars.js' ;
19+ import {
20+ GraphQLBoolean ,
21+ GraphQLFloat ,
22+ GraphQLID ,
23+ GraphQLInt ,
24+ GraphQLString ,
25+ } from '../../type/scalars.js' ;
1326
14- import { coerceInputValue } from '../coerceInputValue.js' ;
27+ import { coerceInputLiteral , coerceInputValue } from '../coerceInputValue.js' ;
1528
1629interface CoerceResult {
1730 value : unknown ;
@@ -532,3 +545,264 @@ describe('coerceInputValue', () => {
532545 } ) ;
533546 } ) ;
534547} ) ;
548+
549+ describe ( 'coerceInputLiteral' , ( ) => {
550+ function test (
551+ valueText : string ,
552+ type : GraphQLInputType ,
553+ expected : unknown ,
554+ variables ?: ObjMap < unknown > ,
555+ ) {
556+ const ast = parseValue ( valueText ) ;
557+ const value = coerceInputLiteral ( ast , type , variables ) ;
558+ expect ( value ) . to . deep . equal ( expected ) ;
559+ }
560+
561+ function testWithVariables (
562+ variables : ObjMap < unknown > ,
563+ valueText : string ,
564+ type : GraphQLInputType ,
565+ expected : unknown ,
566+ ) {
567+ test ( valueText , type , expected , variables ) ;
568+ }
569+
570+ it ( 'converts according to input coercion rules' , ( ) => {
571+ test ( 'true' , GraphQLBoolean , true ) ;
572+ test ( 'false' , GraphQLBoolean , false ) ;
573+ test ( '123' , GraphQLInt , 123 ) ;
574+ test ( '123' , GraphQLFloat , 123 ) ;
575+ test ( '123.456' , GraphQLFloat , 123.456 ) ;
576+ test ( '"abc123"' , GraphQLString , 'abc123' ) ;
577+ test ( '123456' , GraphQLID , '123456' ) ;
578+ test ( '"123456"' , GraphQLID , '123456' ) ;
579+ } ) ;
580+
581+ it ( 'does not convert when input coercion rules reject a value' , ( ) => {
582+ test ( '123' , GraphQLBoolean , undefined ) ;
583+ test ( '123.456' , GraphQLInt , undefined ) ;
584+ test ( 'true' , GraphQLInt , undefined ) ;
585+ test ( '"123"' , GraphQLInt , undefined ) ;
586+ test ( '"123"' , GraphQLFloat , undefined ) ;
587+ test ( '123' , GraphQLString , undefined ) ;
588+ test ( 'true' , GraphQLString , undefined ) ;
589+ test ( '123.456' , GraphQLString , undefined ) ;
590+ test ( '123.456' , GraphQLID , undefined ) ;
591+ } ) ;
592+
593+ it ( 'convert using parseLiteral from a custom scalar type' , ( ) => {
594+ const passthroughScalar = new GraphQLScalarType ( {
595+ name : 'PassthroughScalar' ,
596+ parseLiteral ( node ) {
597+ invariant ( node . kind === 'StringValue' ) ;
598+ return node . value ;
599+ } ,
600+ parseValue : identityFunc ,
601+ } ) ;
602+
603+ test ( '"value"' , passthroughScalar , 'value' ) ;
604+
605+ const printScalar = new GraphQLScalarType ( {
606+ name : 'PrintScalar' ,
607+ parseLiteral ( node ) {
608+ return `~~~${ print ( node ) } ~~~` ;
609+ } ,
610+ parseValue : identityFunc ,
611+ } ) ;
612+
613+ test ( '"value"' , printScalar , '~~~"value"~~~' ) ;
614+
615+ const throwScalar = new GraphQLScalarType ( {
616+ name : 'ThrowScalar' ,
617+ parseLiteral ( ) {
618+ throw new Error ( 'Test' ) ;
619+ } ,
620+ parseValue : identityFunc ,
621+ } ) ;
622+
623+ test ( 'value' , throwScalar , undefined ) ;
624+
625+ const returnUndefinedScalar = new GraphQLScalarType ( {
626+ name : 'ReturnUndefinedScalar' ,
627+ parseLiteral ( ) {
628+ return undefined ;
629+ } ,
630+ parseValue : identityFunc ,
631+ } ) ;
632+
633+ test ( 'value' , returnUndefinedScalar , undefined ) ;
634+ } ) ;
635+
636+ it ( 'converts enum values according to input coercion rules' , ( ) => {
637+ const testEnum = new GraphQLEnumType ( {
638+ name : 'TestColor' ,
639+ values : {
640+ RED : { value : 1 } ,
641+ GREEN : { value : 2 } ,
642+ BLUE : { value : 3 } ,
643+ NULL : { value : null } ,
644+ NAN : { value : NaN } ,
645+ NO_CUSTOM_VALUE : { value : undefined } ,
646+ } ,
647+ } ) ;
648+
649+ test ( 'RED' , testEnum , 1 ) ;
650+ test ( 'BLUE' , testEnum , 3 ) ;
651+ test ( '3' , testEnum , undefined ) ;
652+ test ( '"BLUE"' , testEnum , undefined ) ;
653+ test ( 'null' , testEnum , null ) ;
654+ test ( 'NULL' , testEnum , null ) ;
655+ test ( 'NULL' , new GraphQLNonNull ( testEnum ) , null ) ;
656+ test ( 'NAN' , testEnum , NaN ) ;
657+ test ( 'NO_CUSTOM_VALUE' , testEnum , 'NO_CUSTOM_VALUE' ) ;
658+ } ) ;
659+
660+ // Boolean!
661+ const nonNullBool = new GraphQLNonNull ( GraphQLBoolean ) ;
662+ // [Boolean]
663+ const listOfBool = new GraphQLList ( GraphQLBoolean ) ;
664+ // [Boolean!]
665+ const listOfNonNullBool = new GraphQLList ( nonNullBool ) ;
666+ // [Boolean]!
667+ const nonNullListOfBool = new GraphQLNonNull ( listOfBool ) ;
668+ // [Boolean!]!
669+ const nonNullListOfNonNullBool = new GraphQLNonNull ( listOfNonNullBool ) ;
670+
671+ it ( 'coerces to null unless non-null' , ( ) => {
672+ test ( 'null' , GraphQLBoolean , null ) ;
673+ test ( 'null' , nonNullBool , undefined ) ;
674+ } ) ;
675+
676+ it ( 'coerces lists of values' , ( ) => {
677+ test ( 'true' , listOfBool , [ true ] ) ;
678+ test ( '123' , listOfBool , undefined ) ;
679+ test ( 'null' , listOfBool , null ) ;
680+ test ( '[true, false]' , listOfBool , [ true , false ] ) ;
681+ test ( '[true, 123]' , listOfBool , undefined ) ;
682+ test ( '[true, null]' , listOfBool , [ true , null ] ) ;
683+ test ( '{ true: true }' , listOfBool , undefined ) ;
684+ } ) ;
685+
686+ it ( 'coerces non-null lists of values' , ( ) => {
687+ test ( 'true' , nonNullListOfBool , [ true ] ) ;
688+ test ( '123' , nonNullListOfBool , undefined ) ;
689+ test ( 'null' , nonNullListOfBool , undefined ) ;
690+ test ( '[true, false]' , nonNullListOfBool , [ true , false ] ) ;
691+ test ( '[true, 123]' , nonNullListOfBool , undefined ) ;
692+ test ( '[true, null]' , nonNullListOfBool , [ true , null ] ) ;
693+ } ) ;
694+
695+ it ( 'coerces lists of non-null values' , ( ) => {
696+ test ( 'true' , listOfNonNullBool , [ true ] ) ;
697+ test ( '123' , listOfNonNullBool , undefined ) ;
698+ test ( 'null' , listOfNonNullBool , null ) ;
699+ test ( '[true, false]' , listOfNonNullBool , [ true , false ] ) ;
700+ test ( '[true, 123]' , listOfNonNullBool , undefined ) ;
701+ test ( '[true, null]' , listOfNonNullBool , undefined ) ;
702+ } ) ;
703+
704+ it ( 'coerces non-null lists of non-null values' , ( ) => {
705+ test ( 'true' , nonNullListOfNonNullBool , [ true ] ) ;
706+ test ( '123' , nonNullListOfNonNullBool , undefined ) ;
707+ test ( 'null' , nonNullListOfNonNullBool , undefined ) ;
708+ test ( '[true, false]' , nonNullListOfNonNullBool , [ true , false ] ) ;
709+ test ( '[true, 123]' , nonNullListOfNonNullBool , undefined ) ;
710+ test ( '[true, null]' , nonNullListOfNonNullBool , undefined ) ;
711+ } ) ;
712+
713+ it ( 'uses default values for unprovided fields' , ( ) => {
714+ const type = new GraphQLInputObjectType ( {
715+ name : 'TestInput' ,
716+ fields : {
717+ int : { type : GraphQLInt , defaultValue : 42 } ,
718+ } ,
719+ } ) ;
720+
721+ test ( '{}' , type , { int : 42 } ) ;
722+ } ) ;
723+
724+ const testInputObj = new GraphQLInputObjectType ( {
725+ name : 'TestInput' ,
726+ fields : {
727+ int : { type : GraphQLInt , defaultValue : 42 } ,
728+ bool : { type : GraphQLBoolean } ,
729+ requiredBool : { type : nonNullBool } ,
730+ } ,
731+ } ) ;
732+ const testOneOfInputObj = new GraphQLInputObjectType ( {
733+ name : 'TestOneOfInput' ,
734+ fields : {
735+ a : { type : GraphQLString } ,
736+ b : { type : GraphQLString } ,
737+ } ,
738+ isOneOf : true ,
739+ } ) ;
740+
741+ it ( 'coerces input objects according to input coercion rules' , ( ) => {
742+ test ( 'null' , testInputObj , null ) ;
743+ test ( '123' , testInputObj , undefined ) ;
744+ test ( '[]' , testInputObj , undefined ) ;
745+ test ( '{ requiredBool: true }' , testInputObj , {
746+ int : 42 ,
747+ requiredBool : true ,
748+ } ) ;
749+ test ( '{ int: null, requiredBool: true }' , testInputObj , {
750+ int : null ,
751+ requiredBool : true ,
752+ } ) ;
753+ test ( '{ int: 123, requiredBool: false }' , testInputObj , {
754+ int : 123 ,
755+ requiredBool : false ,
756+ } ) ;
757+ test ( '{ bool: true, requiredBool: false }' , testInputObj , {
758+ int : 42 ,
759+ bool : true ,
760+ requiredBool : false ,
761+ } ) ;
762+ test ( '{ int: true, requiredBool: true }' , testInputObj , undefined ) ;
763+ test ( '{ requiredBool: null }' , testInputObj , undefined ) ;
764+ test ( '{ bool: true }' , testInputObj , undefined ) ;
765+ test ( '{ requiredBool: true, unknown: 123 }' , testInputObj , undefined ) ;
766+ test ( '{ a: "abc" }' , testOneOfInputObj , {
767+ a : 'abc' ,
768+ } ) ;
769+ test ( '{ b: "def" }' , testOneOfInputObj , {
770+ b : 'def' ,
771+ } ) ;
772+ test ( '{ a: "abc", b: null }' , testOneOfInputObj , undefined ) ;
773+ test ( '{ a: null }' , testOneOfInputObj , undefined ) ;
774+ test ( '{ a: 1 }' , testOneOfInputObj , undefined ) ;
775+ test ( '{ a: "abc", b: "def" }' , testOneOfInputObj , undefined ) ;
776+ test ( '{}' , testOneOfInputObj , undefined ) ;
777+ test ( '{ c: "abc" }' , testOneOfInputObj , undefined ) ;
778+ } ) ;
779+
780+ it ( 'accepts variable values assuming already coerced' , ( ) => {
781+ test ( '$var' , GraphQLBoolean , undefined ) ;
782+ testWithVariables ( { var : true } , '$var' , GraphQLBoolean , true ) ;
783+ testWithVariables ( { var : null } , '$var' , GraphQLBoolean , null ) ;
784+ testWithVariables ( { var : null } , '$var' , nonNullBool , undefined ) ;
785+ } ) ;
786+
787+ it ( 'asserts variables are provided as items in lists' , ( ) => {
788+ test ( '[ $foo ]' , listOfBool , [ null ] ) ;
789+ test ( '[ $foo ]' , listOfNonNullBool , undefined ) ;
790+ testWithVariables ( { foo : true } , '[ $foo ]' , listOfNonNullBool , [ true ] ) ;
791+ // Note: variables are expected to have already been coerced, so we
792+ // do not expect the singleton wrapping behavior for variables.
793+ testWithVariables ( { foo : true } , '$foo' , listOfNonNullBool , true ) ;
794+ testWithVariables ( { foo : [ true ] } , '$foo' , listOfNonNullBool , [ true ] ) ;
795+ } ) ;
796+
797+ it ( 'omits input object fields for unprovided variables' , ( ) => {
798+ test ( '{ int: $foo, bool: $foo, requiredBool: true }' , testInputObj , {
799+ int : 42 ,
800+ requiredBool : true ,
801+ } ) ;
802+ test ( '{ requiredBool: $foo }' , testInputObj , undefined ) ;
803+ testWithVariables ( { foo : true } , '{ requiredBool: $foo }' , testInputObj , {
804+ int : 42 ,
805+ requiredBool : true ,
806+ } ) ;
807+ } ) ;
808+ } ) ;
0 commit comments