diff --git a/src/execution/__tests__/variables-test.js b/src/execution/__tests__/variables-test.js index 01b649766a..2011c86ea5 100644 --- a/src/execution/__tests__/variables-test.js +++ b/src/execution/__tests__/variables-test.js @@ -159,6 +159,36 @@ describe('Execute: Handles inputs', () => { }); }); + it('properly parses null value to null', async () => { + const doc = ` + { + fieldWithObjectInput(input: {a: null, b: null, c: "C", d: null}) + } + `; + const ast = parse(doc); + + return expect(await execute(schema, ast)).to.deep.equal({ + data: { + fieldWithObjectInput: '{"a":null,"b":null,"c":"C","d":null}' + } + }); + }); + + it('properly parses null value in list', async () => { + const doc = ` + { + fieldWithObjectInput(input: {b: ["A",null,"C"], c: "C"}) + } + `; + const ast = parse(doc); + + return expect(await execute(schema, ast)).to.deep.equal({ + data: { + fieldWithObjectInput: '{"b":["A",null,"C"],"c":"C"}' + } + }); + }); + it('does not use incorrect value', async () => { const doc = ` { diff --git a/src/execution/values.js b/src/execution/values.js index ac678c7e5d..9f3abdf13e 100644 --- a/src/execution/values.js +++ b/src/execution/values.js @@ -13,6 +13,7 @@ import { forEach, isCollection } from 'iterall'; import { GraphQLError } from '../error'; import invariant from '../jsutils/invariant'; import isNullish from '../jsutils/isNullish'; +import isInvalid from '../jsutils/isInvalid'; import keyMap from '../jsutils/keyMap'; import { typeFromAST } from '../utilities/typeFromAST'; import { valueFromAST } from '../utilities/valueFromAST'; @@ -66,10 +67,10 @@ export function getArgumentValues( const name = argDef.name; const valueAST = argASTMap[name] ? argASTMap[name].value : null; let value = valueFromAST(valueAST, argDef.type, variableValues); - if (isNullish(value)) { + if (isInvalid(value)) { value = argDef.defaultValue; } - if (!isNullish(value)) { + if (!isInvalid(value)) { result[name] = value; } return result; @@ -98,7 +99,7 @@ function getVariableValue( const inputType = ((type: any): GraphQLInputType); const errors = isValidJSValue(input, inputType); if (!errors.length) { - if (isNullish(input)) { + if (isInvalid(input)) { const defaultValue = definitionAST.defaultValue; if (defaultValue) { return valueFromAST(defaultValue, inputType); @@ -134,10 +135,14 @@ function coerceValue(type: GraphQLInputType, value: mixed): mixed { return coerceValue(type.ofType, _value); } - if (isNullish(_value)) { + if (_value === null) { return null; } + if (isInvalid(_value)) { + return undefined; + } + if (type instanceof GraphQLList) { const itemType = type.ofType; if (isCollection(_value)) { @@ -158,10 +163,10 @@ function coerceValue(type: GraphQLInputType, value: mixed): mixed { return Object.keys(fields).reduce((obj, fieldName) => { const field = fields[fieldName]; let fieldValue = coerceValue(field.type, _value[fieldName]); - if (isNullish(fieldValue)) { + if (isInvalid(fieldValue)) { fieldValue = field.defaultValue; } - if (!isNullish(fieldValue)) { + if (!isInvalid(fieldValue)) { obj[fieldName] = fieldValue; } return obj; diff --git a/src/jsutils/isInvalid.js b/src/jsutils/isInvalid.js new file mode 100644 index 0000000000..e226abb6e0 --- /dev/null +++ b/src/jsutils/isInvalid.js @@ -0,0 +1,16 @@ +/* @flow */ +/** + * Copyright (c) 2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/** + * Returns true if a value is undefined, or NaN. + */ +export default function isInvalid(value: mixed): boolean { + return value === undefined || value !== value; +} diff --git a/src/language/__tests__/kitchen-sink.graphql b/src/language/__tests__/kitchen-sink.graphql index 0e04e2e42d..993de9ad02 100644 --- a/src/language/__tests__/kitchen-sink.graphql +++ b/src/language/__tests__/kitchen-sink.graphql @@ -52,6 +52,6 @@ fragment frag on Friend { } { - unnamed(truthy: true, falsey: false), + unnamed(truthy: true, falsey: false, nullish: null), query } diff --git a/src/language/__tests__/parser-test.js b/src/language/__tests__/parser-test.js index a26e7b9033..bb425812b8 100644 --- a/src/language/__tests__/parser-test.js +++ b/src/language/__tests__/parser-test.js @@ -91,12 +91,6 @@ fragment MissingOn Type ).to.throw('Syntax Error GraphQL (1:9) Expected Name, found }'); }); - it('does not allow null as value', async () => { - expect( - () => parse('{ fieldWithNullableStringInput(input: null) }') - ).to.throw('Syntax Error GraphQL (1:39) Unexpected Name "null"'); - }); - it('parses multi-byte characters', async () => { // Note: \u0A0A could be naively interpretted as two line-feed chars. expect( @@ -296,6 +290,13 @@ fragment ${fragmentName} on Type { describe('parseValue', () => { + it('parses null value', () => { + expect(parseValue('null')).to.containSubset({ + kind: Kind.NULL, + loc: { start: 0, end: 4 } + }); + }); + it('parses list values', () => { expect(parseValue('[123 "abc"]')).to.containSubset({ kind: Kind.LIST, diff --git a/src/language/__tests__/printer-test.js b/src/language/__tests__/printer-test.js index 51b93ab2aa..25b9920739 100644 --- a/src/language/__tests__/printer-test.js +++ b/src/language/__tests__/printer-test.js @@ -132,7 +132,7 @@ fragment frag on Friend { } { - unnamed(truthy: true, falsey: false) + unnamed(truthy: true, falsey: false, nullish: null) query } `); diff --git a/src/language/__tests__/schema-kitchen-sink.graphql b/src/language/__tests__/schema-kitchen-sink.graphql index d148276253..a56c0e4cc6 100644 --- a/src/language/__tests__/schema-kitchen-sink.graphql +++ b/src/language/__tests__/schema-kitchen-sink.graphql @@ -17,6 +17,7 @@ type Foo implements Bar { four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: InputType = {key: "value"}): Type + seven(argument: Int = null): Type } type AnnotatedObject @onObject(arg: "value") { diff --git a/src/language/__tests__/schema-printer-test.js b/src/language/__tests__/schema-printer-test.js index 29376f32c9..4fa56275bb 100644 --- a/src/language/__tests__/schema-printer-test.js +++ b/src/language/__tests__/schema-printer-test.js @@ -63,6 +63,7 @@ type Foo implements Bar { four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: InputType = {key: "value"}): Type + seven(argument: Int = null): Type } type AnnotatedObject @onObject(arg: "value") { diff --git a/src/language/__tests__/visitor-test.js b/src/language/__tests__/visitor-test.js index 3c3b6ca0ad..3a43936db0 100644 --- a/src/language/__tests__/visitor-test.js +++ b/src/language/__tests__/visitor-test.js @@ -614,6 +614,12 @@ describe('Visitor', () => { [ 'enter', 'BooleanValue', 'value', 'Argument' ], [ 'leave', 'BooleanValue', 'value', 'Argument' ], [ 'leave', 'Argument', 1, undefined ], + [ 'enter', 'Argument', 2, undefined ], + [ 'enter', 'Name', 'name', 'Argument' ], + [ 'leave', 'Name', 'name', 'Argument' ], + [ 'enter', 'NullValue', 'value', 'Argument' ], + [ 'leave', 'NullValue', 'value', 'Argument' ], + [ 'leave', 'Argument', 2, undefined ], [ 'leave', 'Field', 0, undefined ], [ 'enter', 'Field', 1, undefined ], [ 'enter', 'Name', 'name', 'Field' ], diff --git a/src/language/ast.js b/src/language/ast.js index 7c3f4b3050..81d52ae87a 100644 --- a/src/language/ast.js +++ b/src/language/ast.js @@ -126,6 +126,7 @@ export type Node = | FloatValue | StringValue | BooleanValue + | NullValue | EnumValue | ListValue | ObjectValue @@ -260,6 +261,7 @@ export type Value = | FloatValue | StringValue | BooleanValue + | NullValue | EnumValue | ListValue | ObjectValue; @@ -288,6 +290,11 @@ export type BooleanValue = { value: boolean; }; +export type NullValue = { + kind: 'NullValue'; + loc?: Location; +}; + export type EnumValue = { kind: 'EnumValue'; loc?: Location; diff --git a/src/language/kinds.js b/src/language/kinds.js index 3b69f086de..774fc1606d 100644 --- a/src/language/kinds.js +++ b/src/language/kinds.js @@ -34,6 +34,7 @@ export const INT = 'IntValue'; export const FLOAT = 'FloatValue'; export const STRING = 'StringValue'; export const BOOLEAN = 'BooleanValue'; +export const NULL = 'NullValue'; export const ENUM = 'EnumValue'; export const LIST = 'ListValue'; export const OBJECT = 'ObjectValue'; diff --git a/src/language/parser.js b/src/language/parser.js index 431529dcc6..af4ee1c715 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -89,6 +89,7 @@ import { FLOAT, STRING, BOOLEAN, + NULL, ENUM, LIST, OBJECT, @@ -503,12 +504,15 @@ function parseFragmentName(lexer: Lexer<*>): Name { * - FloatValue * - StringValue * - BooleanValue + * - NullValue * - EnumValue * - ListValue[?Const] * - ObjectValue[?Const] * * BooleanValue : one of `true` `false` * + * NullValue : `null` + * * EnumValue : Name but not `true`, `false` or `null` */ function parseValueLiteral(lexer: Lexer<*>, isConst: boolean): Value { @@ -547,15 +551,19 @@ function parseValueLiteral(lexer: Lexer<*>, isConst: boolean): Value { value: token.value === 'true', loc: loc(lexer, token) }; - } else if (token.value !== 'null') { + } else if (token.value === 'null') { lexer.advance(); return { - kind: (ENUM: 'EnumValue'), - value: ((token.value: any): string), + kind: (NULL: 'NullValue'), loc: loc(lexer, token) }; } - break; + lexer.advance(); + return { + kind: (ENUM: 'EnumValue'), + value: ((token.value: any): string), + loc: loc(lexer, token) + }; case TokenKind.DOLLAR: if (!isConst) { return parseVariable(lexer); diff --git a/src/language/printer.js b/src/language/printer.js index b908162f1f..e056ce0e9f 100644 --- a/src/language/printer.js +++ b/src/language/printer.js @@ -76,6 +76,7 @@ const printDocASTReducer = { FloatValue: ({ value }) => value, StringValue: ({ value }) => JSON.stringify(value), BooleanValue: ({ value }) => JSON.stringify(value), + NullValue: () => 'null', EnumValue: ({ value }) => value, ListValue: ({ values }) => '[' + join(values, ', ') + ']', ObjectValue: ({ fields }) => '{' + join(fields, ', ') + '}', diff --git a/src/language/visitor.js b/src/language/visitor.js index 480c48a9c0..a8422170dd 100644 --- a/src/language/visitor.js +++ b/src/language/visitor.js @@ -27,6 +27,7 @@ export const QueryDocumentKeys = { FloatValue: [], StringValue: [], BooleanValue: [], + NullValue: [], EnumValue: [], ListValue: [ 'values' ], ObjectValue: [ 'fields' ], diff --git a/src/type/definition.js b/src/type/definition.js index dd62e7133a..6f77f6ea08 100644 --- a/src/type/definition.js +++ b/src/type/definition.js @@ -446,7 +446,7 @@ function defineFieldMap( name: argName, description: arg.description === undefined ? null : arg.description, type: arg.type, - defaultValue: arg.defaultValue === undefined ? null : arg.defaultValue + defaultValue: arg.defaultValue }; }); } diff --git a/src/type/directives.js b/src/type/directives.js index 0b1c30194d..cd8a39a2c8 100644 --- a/src/type/directives.js +++ b/src/type/directives.js @@ -84,7 +84,7 @@ export class GraphQLDirective { name: argName, description: arg.description === undefined ? null : arg.description, type: arg.type, - defaultValue: arg.defaultValue === undefined ? null : arg.defaultValue + defaultValue: arg.defaultValue }; }); } diff --git a/src/utilities/__tests__/astFromValue-test.js b/src/utilities/__tests__/astFromValue-test.js index a1491a7af9..6e1543b202 100644 --- a/src/utilities/__tests__/astFromValue-test.js +++ b/src/utilities/__tests__/astFromValue-test.js @@ -19,6 +19,7 @@ import { GraphQLString, GraphQLBoolean, GraphQLID, + GraphQLNonNull, } from '../../type'; @@ -33,10 +34,14 @@ describe('astFromValue', () => { { kind: 'BooleanValue', value: false } ); - expect(astFromValue(null, GraphQLBoolean)).to.deep.equal( + expect(astFromValue(undefined, GraphQLBoolean)).to.deep.equal( null ); + expect(astFromValue(null, GraphQLBoolean)).to.deep.equal( + { kind: 'NullValue' } + ); + expect(astFromValue(0, GraphQLBoolean)).to.deep.equal( { kind: 'BooleanValue', value: false } ); @@ -44,6 +49,11 @@ describe('astFromValue', () => { expect(astFromValue(1, GraphQLBoolean)).to.deep.equal( { kind: 'BooleanValue', value: true } ); + + const NonNullBoolean = new GraphQLNonNull(GraphQLBoolean); + expect(astFromValue(0, NonNullBoolean)).to.deep.equal( + { kind: 'BooleanValue', value: false } + ); }); it('converts Int values to Int ASTs', () => { @@ -105,6 +115,10 @@ describe('astFromValue', () => { ); expect(astFromValue(null, GraphQLString)).to.deep.equal( + { kind: 'NullValue' } + ); + + expect(astFromValue(undefined, GraphQLString)).to.deep.equal( null ); }); @@ -133,6 +147,17 @@ describe('astFromValue', () => { ); expect(astFromValue(null, GraphQLID)).to.deep.equal( + { kind: 'NullValue' } + ); + + expect(astFromValue(undefined, GraphQLID)).to.deep.equal( + null + ); + }); + + it('does not converts NonNull values to NullValue', () => { + const NonNullBoolean = new GraphQLNonNull(GraphQLBoolean); + expect(astFromValue(null, NonNullBoolean)).to.deep.equal( null ); }); @@ -220,4 +245,26 @@ describe('astFromValue', () => { value: { kind: 'EnumValue', value: 'HELLO' } } ] } ); }); + + it('converts input objects with explicit nulls', () => { + const inputObj = new GraphQLInputObjectType({ + name: 'MyInputObj', + fields: { + foo: { type: GraphQLFloat }, + bar: { type: myEnum }, + } + }); + + expect(astFromValue( + { foo: null }, + inputObj + )).to.deep.equal( + { kind: 'ObjectValue', + fields: [ + { kind: 'ObjectField', + name: { kind: 'Name', value: 'foo' }, + value: { kind: 'NullValue' } } ] } + ); + }); + }); diff --git a/src/utilities/__tests__/schemaPrinter-test.js b/src/utilities/__tests__/schemaPrinter-test.js index 721f212bca..d25580c645 100644 --- a/src/utilities/__tests__/schemaPrinter-test.js +++ b/src/utilities/__tests__/schemaPrinter-test.js @@ -213,6 +213,25 @@ type Root { ); }); + it('Prints String Field With Int Arg With Default Null', () => { + const output = printSingleFieldSchema( + { + type: GraphQLString, + args: { argOne: { type: GraphQLInt, defaultValue: null } }, + } + ); + expect(output).to.equal(` +schema { + query: Root +} + +type Root { + singleField(argOne: Int = null): String +} +` + ); + }); + it('Prints String Field With Int! Arg', () => { const output = printSingleFieldSchema( { diff --git a/src/utilities/astFromValue.js b/src/utilities/astFromValue.js index 0b7fae2fd9..b3aa2d98d1 100644 --- a/src/utilities/astFromValue.js +++ b/src/utilities/astFromValue.js @@ -12,12 +12,14 @@ import { forEach, isCollection } from 'iterall'; import invariant from '../jsutils/invariant'; import isNullish from '../jsutils/isNullish'; +import isInvalid from '../jsutils/isInvalid'; import type { Value, IntValue, FloatValue, StringValue, BooleanValue, + NullValue, EnumValue, ListValue, ObjectValue, @@ -28,6 +30,7 @@ import { FLOAT, STRING, BOOLEAN, + NULL, ENUM, LIST, OBJECT, @@ -58,6 +61,7 @@ import { GraphQLID } from '../type/scalars'; * | String | String / Enum Value | * | Number | Int / Float | * | Mixed | Enum Value | + * | null | NullValue | * */ export function astFromValue( @@ -68,12 +72,20 @@ export function astFromValue( const _value = value; if (type instanceof GraphQLNonNull) { - // Note: we're not checking that the result is non-null. - // This function is not responsible for validating the input value. - return astFromValue(_value, type.ofType); + const astValue = astFromValue(_value, type.ofType); + if (astValue && astValue.kind === NULL) { + return null; + } + return astValue; + } + + // only explicit null, not undefined, NaN + if (_value === null) { + return ({ kind: NULL }: NullValue); } - if (isNullish(_value)) { + // undefined, NaN + if (isInvalid(_value)) { return null; } diff --git a/src/utilities/isValidLiteralValue.js b/src/utilities/isValidLiteralValue.js index 694872efee..96a74baaec 100644 --- a/src/utilities/isValidLiteralValue.js +++ b/src/utilities/isValidLiteralValue.js @@ -11,6 +11,7 @@ import { print } from '../language/printer'; import type { Value, ListValue, ObjectValue } from '../language/ast'; import { + NULL, VARIABLE, LIST, OBJECT @@ -41,7 +42,7 @@ export function isValidLiteralValue( ): Array { // A value must be provided if the type is non-null. if (type instanceof GraphQLNonNull) { - if (!valueAST) { + if (!valueAST || (valueAST.kind === NULL)) { if (type.ofType.name) { return [ `Expected "${String(type.ofType.name)}!", found null.` ]; } @@ -50,7 +51,7 @@ export function isValidLiteralValue( return isValidLiteralValue(type.ofType, valueAST); } - if (!valueAST) { + if (!valueAST || (valueAST.kind === NULL)) { return []; } diff --git a/src/utilities/schemaPrinter.js b/src/utilities/schemaPrinter.js index fc0976603d..b01df5529c 100644 --- a/src/utilities/schemaPrinter.js +++ b/src/utilities/schemaPrinter.js @@ -10,6 +10,7 @@ import invariant from '../jsutils/invariant'; import isNullish from '../jsutils/isNullish'; +import isInvalid from '../jsutils/isInvalid'; import { astFromValue } from '../utilities/astFromValue'; import { print } from '../language/printer'; import type { GraphQLSchema } from '../type/schema'; @@ -231,7 +232,7 @@ function printArgs(args, indentation = '') { function printInputValue(arg) { let argDecl = arg.name + ': ' + String(arg.type); - if (!isNullish(arg.defaultValue)) { + if (!isInvalid(arg.defaultValue)) { argDecl += ` = ${print(astFromValue(arg.defaultValue, arg.type))}`; } return argDecl; diff --git a/src/utilities/valueFromAST.js b/src/utilities/valueFromAST.js index 884d773af9..3d216a30da 100644 --- a/src/utilities/valueFromAST.js +++ b/src/utilities/valueFromAST.js @@ -11,6 +11,7 @@ import keyMap from '../jsutils/keyMap'; import invariant from '../jsutils/invariant'; import isNullish from '../jsutils/isNullish'; +import isInvalid from '../jsutils/isInvalid'; import * as Kind from '../language/kinds'; import { GraphQLScalarType, @@ -42,6 +43,7 @@ import type { * | String | String | * | Int / Float | Number | * | Enum Value | Mixed | + * | NullValue | null | * */ export function valueFromAST( @@ -57,13 +59,21 @@ export function valueFromAST( } if (!valueAST) { + // When there is no AST, then there is also no value. + // Importantly, this is different from returning the value null. + return; + } + + if (valueAST.kind === Kind.NULL) { + // This is explicitly returning the value null. return null; } if (valueAST.kind === Kind.VARIABLE) { const variableName = (valueAST: Variable).name.value; if (!variables || !variables.hasOwnProperty(variableName)) { - return null; + // No valid return value. + return; } // Note: we're not doing any checking that this variable is correct. We're // assuming that this query has been validated and the variable usage here @@ -83,7 +93,8 @@ export function valueFromAST( if (type instanceof GraphQLInputObjectType) { if (valueAST.kind !== Kind.OBJECT) { - return null; + // No valid return value. + return; } const fields = type.getFields(); const fieldASTs = keyMap( @@ -93,14 +104,11 @@ export function valueFromAST( return Object.keys(fields).reduce((obj, fieldName) => { const field = fields[fieldName]; const fieldAST = fieldASTs[fieldName]; - let fieldValue = + const fieldValue = valueFromAST(fieldAST && fieldAST.value, field.type, variables); - if (isNullish(fieldValue)) { - fieldValue = field.defaultValue; - } - if (!isNullish(fieldValue)) { - obj[fieldName] = fieldValue; - } + + // If no valid field value was provided, use the default value + obj[fieldName] = isInvalid(fieldValue) ? field.defaultValue : fieldValue; return obj; }, {}); } diff --git a/src/validation/__tests__/ArgumentsOfCorrectType-test.js b/src/validation/__tests__/ArgumentsOfCorrectType-test.js index 036dc8d921..3636c63683 100644 --- a/src/validation/__tests__/ArgumentsOfCorrectType-test.js +++ b/src/validation/__tests__/ArgumentsOfCorrectType-test.js @@ -115,6 +115,24 @@ describe('Validate: Argument values of correct type', () => { `); }); + it('null into nullable type', () => { + expectPassesRule(ArgumentsOfCorrectType, ` + { + complicatedArgs { + intArgField(intArg: null) + } + } + `); + + expectPassesRule(ArgumentsOfCorrectType, ` + { + dog(a: null, b: null, c:{ requiredField: true, intField: null }) { + name + } + } + `); + }); + }); @@ -454,7 +472,7 @@ describe('Validate: Argument values of correct type', () => { expectPassesRule(ArgumentsOfCorrectType, ` { complicatedArgs { - stringListArgField(stringListArg: ["one", "two"]) + stringListArgField(stringListArg: ["one", null, "two"]) } } `); @@ -470,6 +488,16 @@ describe('Validate: Argument values of correct type', () => { `); }); + it('Null value', () => { + expectPassesRule(ArgumentsOfCorrectType, ` + { + complicatedArgs { + stringListArgField(stringListArg: null) + } + } + `); + }); + it('Single value into List', () => { expectPassesRule(ArgumentsOfCorrectType, ` { @@ -646,6 +674,20 @@ describe('Validate: Argument values of correct type', () => { ]); }); + it('Null value', () => { + expectFailsRule(ArgumentsOfCorrectType, ` + { + complicatedArgs { + multipleReqs(req1: null) + } + } + `, [ + badValue('req1', 'Int!', 'null', 4, 32, [ + 'Expected "Int!", found null.' + ]), + ]); + }); + }); diff --git a/src/validation/__tests__/DefaultValuesOfCorrectType-test.js b/src/validation/__tests__/DefaultValuesOfCorrectType-test.js index ecbe313ae6..e9872d172d 100644 --- a/src/validation/__tests__/DefaultValuesOfCorrectType-test.js +++ b/src/validation/__tests__/DefaultValuesOfCorrectType-test.js @@ -68,6 +68,44 @@ describe('Validate: Variable default values of correct type', () => { `); }); + it('variables with valid default null values', () => { + expectPassesRule(DefaultValuesOfCorrectType, ` + query WithDefaultValues( + $a: Int = null, + $b: String = null, + $c: ComplexInput = { requiredField: true, intField: null } + ) { + dog { name } + } + `); + }); + + it('variables with invalid default null values', () => { + expectFailsRule(DefaultValuesOfCorrectType, ` + query WithDefaultValues( + $a: Int! = null, + $b: String! = null, + $c: ComplexInput = { requiredField: null, intField: null } + ) { + dog { name } + } + `, [ + defaultForNonNullArg('a', 'Int!', 'Int', 3, 20), + badValue('a', 'Int!', 'null', 3, 20, [ + 'Expected "Int!", found null.' + ]), + defaultForNonNullArg('b', 'String!', 'String', 4, 23), + badValue('b', 'String!', 'null', 4, 23, [ + 'Expected "String!", found null.' + ]), + badValue('c', 'ComplexInput', '{requiredField: null, intField: null}', + 5, 28, [ + 'In field "requiredField": Expected "Boolean!", found null.' + ] + ), + ]); + }); + it('no required variables with default values', () => { expectFailsRule(DefaultValuesOfCorrectType, ` query UnreachableDefaultValues($a: Int! = 3, $b: String! = "default") {