Пример #1
0
func TestPrinter_CorrectlyPrintsNonQueryOperationsWithoutName(t *testing.T) {

	// Test #1
	queryAstShorthanded := `query { id, name }`
	expected := `{
  id
  name
}
`
	astDoc := parse(t, queryAstShorthanded)
	results := printer.Print(astDoc)

	if !reflect.DeepEqual(expected, results) {
		t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(results, expected))
	}

	// Test #2
	mutationAst := `mutation { id, name }`
	expected = `mutation {
  id
  name
}
`
	astDoc = parse(t, mutationAst)
	results = printer.Print(astDoc)

	if !reflect.DeepEqual(expected, results) {
		t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(results, expected))
	}

	// Test #3
	queryAstWithArtifacts := `query ($foo: TestType) @testDirective { id, name }`
	expected = `query ($foo: TestType) @testDirective {
  id
  name
}
`
	astDoc = parse(t, queryAstWithArtifacts)
	results = printer.Print(astDoc)

	if !reflect.DeepEqual(expected, results) {
		t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(results, expected))
	}

	// Test #4
	mutationAstWithArtifacts := `mutation ($foo: TestType) @testDirective { id, name }`
	expected = `mutation ($foo: TestType) @testDirective {
  id
  name
}
`
	astDoc = parse(t, mutationAstWithArtifacts)
	results = printer.Print(astDoc)

	if !reflect.DeepEqual(expected, results) {
		t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(results, expected))
	}
}
func TestSchemaPrinter_PrintsKitchenSink(t *testing.T) {
	b, err := ioutil.ReadFile("../../schema-kitchen-sink.graphql")
	if err != nil {
		t.Fatalf("unable to load schema-kitchen-sink.graphql")
	}

	query := string(b)
	astDoc := parse(t, query)
	expected := `schema {
  query: QueryType
  mutation: MutationType
}

type Foo implements Bar {
  one: Type
  two(argument: InputType!): Type
  three(argument: InputType, other: String): Int
  four(argument: String = "string"): String
  five(argument: [String] = ["string", "string"]): String
  six(argument: InputType = {key: "value"}): Type
}

interface Bar {
  one: Type
  four(argument: String = "string"): String
}

union Feed = Story | Article | Advert

scalar CustomScalar

enum Site {
  DESKTOP
  MOBILE
}

input InputType {
  key: String!
  answer: Int = 42
}

extend type Foo {
  seven(argument: [String]): Type
}

directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
`
	results := printer.Print(astDoc)
	if !reflect.DeepEqual(expected, results) {
		t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(results, expected))
	}
}
Пример #3
0
func TestPrinter_PrintsMinimalAST(t *testing.T) {
	astDoc := ast.NewField(&ast.Field{
		Name: ast.NewName(&ast.Name{
			Value: "foo",
		}),
	})
	results := printer.Print(astDoc)
	expected := "foo"
	if !reflect.DeepEqual(results, expected) {
		t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results))
	}
}
func TestSchemaPrinter_PrintsMinimalAST(t *testing.T) {
	astDoc := ast.NewScalarDefinition(&ast.ScalarDefinition{
		Name: ast.NewName(&ast.Name{
			Value: "foo",
		}),
	})
	results := printer.Print(astDoc)
	expected := "scalar foo"
	if !reflect.DeepEqual(results, expected) {
		t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results))
	}
}
Пример #5
0
func TestPrinter_DoesNotAlterAST(t *testing.T) {
	b, err := ioutil.ReadFile("../../kitchen-sink.graphql")
	if err != nil {
		t.Fatalf("unable to load kitchen-sink.graphql")
	}

	query := string(b)
	astDoc := parse(t, query)

	astDocBefore := testutil.ASTToJSON(t, astDoc)

	_ = printer.Print(astDoc)

	astDocAfter := testutil.ASTToJSON(t, astDoc)

	_ = testutil.ASTToJSON(t, astDoc)

	if !reflect.DeepEqual(astDocAfter, astDocBefore) {
		t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(astDocAfter, astDocBefore))
	}
}
Пример #6
0
// Given a variable definition, and any value of input, return a value which
// adheres to the variable definition, or throw an error.
func getVariableValue(schema Schema, definitionAST *ast.VariableDefinition, input interface{}) (interface{}, error) {
	ttype, err := typeFromAST(schema, definitionAST.Type)
	if err != nil {
		return nil, err
	}
	variable := definitionAST.Variable

	if ttype == nil || !IsInputType(ttype) {
		return "", gqlerrors.NewError(
			fmt.Sprintf(`Variable "$%v" expected value of type `+
				`"%v" which cannot be used as an input type.`, variable.Name.Value, printer.Print(definitionAST.Type)),
			[]ast.Node{definitionAST},
			"",
			nil,
			[]int{},
			nil,
		)
	}

	isValid, messages := isValidInputValue(input, ttype)
	if isValid {
		if isNullish(input) {
			defaultValue := definitionAST.DefaultValue
			if defaultValue != nil {
				variables := map[string]interface{}{}
				val := valueFromAST(defaultValue, ttype, variables)
				return val, nil
			}
		}
		return coerceValue(ttype, input), nil
	}
	if isNullish(input) {
		return "", gqlerrors.NewError(
			fmt.Sprintf(`Variable "$%v" of required type `+
				`"%v" was not provided.`, variable.Name.Value, printer.Print(definitionAST.Type)),
			[]ast.Node{definitionAST},
			"",
			nil,
			[]int{},
			nil,
		)
	}
	// convert input interface into string for error message
	inputStr := ""
	b, err := json.Marshal(input)
	if err == nil {
		inputStr = string(b)
	}
	messagesStr := ""
	if len(messages) > 0 {
		messagesStr = "\n" + strings.Join(messages, "\n")
	}

	return "", gqlerrors.NewError(
		fmt.Sprintf(`Variable "$%v" got invalid value `+
			`%v.%v`, variable.Name.Value, inputStr, messagesStr),
		[]ast.Node{definitionAST},
		"",
		nil,
		[]int{},
		nil,
	)
}
Пример #7
0
func init() {

	typeKindEnum = NewEnum(EnumConfig{
		Name:        "__TypeKind",
		Description: "An enum describing what kind of type a given `__Type` is",
		Values: EnumValueConfigMap{
			"SCALAR": &EnumValueConfig{
				Value:       TypeKindScalar,
				Description: "Indicates this type is a scalar.",
			},
			"OBJECT": &EnumValueConfig{
				Value: TypeKindObject,
				Description: "Indicates this type is an object. " +
					"`fields` and `interfaces` are valid fields.",
			},
			"INTERFACE": &EnumValueConfig{
				Value: TypeKindInterface,
				Description: "Indicates this type is an interface. " +
					"`fields` and `possibleTypes` are valid fields.",
			},
			"UNION": &EnumValueConfig{
				Value: TypeKindUnion,
				Description: "Indicates this type is a union. " +
					"`possibleTypes` is a valid field.",
			},
			"ENUM": &EnumValueConfig{
				Value: TypeKindEnum,
				Description: "Indicates this type is an enum. " +
					"`enumValues` is a valid field.",
			},
			"INPUT_OBJECT": &EnumValueConfig{
				Value: TypeKindInputObject,
				Description: "Indicates this type is an input object. " +
					"`inputFields` is a valid field.",
			},
			"LIST": &EnumValueConfig{
				Value: TypeKindList,
				Description: "Indicates this type is a list. " +
					"`ofType` is a valid field.",
			},
			"NON_NULL": &EnumValueConfig{
				Value: TypeKindNonNull,
				Description: "Indicates this type is a non-null. " +
					"`ofType` is a valid field.",
			},
		},
	})

	directiveLocationEnum = NewEnum(EnumConfig{
		Name: "__DirectiveLocation",
		Description: "A Directive can be adjacent to many parts of the GraphQL language, a " +
			"__DirectiveLocation describes one such possible adjacencies.",
		Values: EnumValueConfigMap{
			"QUERY": &EnumValueConfig{
				Value:       DirectiveLocationQuery,
				Description: "Location adjacent to a query operation.",
			},
			"MUTATION": &EnumValueConfig{
				Value:       DirectiveLocationMutation,
				Description: "Location adjacent to a mutation operation.",
			},
			"SUBSCRIPTION": &EnumValueConfig{
				Value:       DirectiveLocationSubscription,
				Description: "Location adjacent to a subscription operation.",
			},
			"FIELD": &EnumValueConfig{
				Value:       DirectiveLocationField,
				Description: "Location adjacent to a field.",
			},
			"FRAGMENT_DEFINITION": &EnumValueConfig{
				Value:       DirectiveLocationFragmentDefinition,
				Description: "Location adjacent to a fragment definition.",
			},
			"FRAGMENT_SPREAD": &EnumValueConfig{
				Value:       DirectiveLocationFragmentSpread,
				Description: "Location adjacent to a fragment spread.",
			},
			"INLINE_FRAGMENT": &EnumValueConfig{
				Value:       DirectiveLocationInlineFragment,
				Description: "Location adjacent to an inline fragment.",
			},
		},
	})

	// Note: some fields (for e.g "fields", "interfaces") are defined later due to cyclic reference
	typeType = NewObject(ObjectConfig{
		Name: "__Type",
		Description: "The fundamental unit of any GraphQL Schema is the type. There are " +
			"many kinds of types in GraphQL as represented by the `__TypeKind` enum." +
			"\n\nDepending on the kind of a type, certain fields describe " +
			"information about that type. Scalar types provide no information " +
			"beyond a name and description, while Enum types provide their values. " +
			"Object and Interface types provide the fields they describe. Abstract " +
			"types, Union and Interface, provide the Object types possible " +
			"at runtime. List and NonNull types compose other types.",

		Fields: Fields{
			"kind": &Field{
				Type: NewNonNull(typeKindEnum),
				Resolve: func(p ResolveParams) (interface{}, error) {
					switch p.Source.(type) {
					case *Scalar:
						return TypeKindScalar, nil
					case *Object:
						return TypeKindObject, nil
					case *Interface:
						return TypeKindInterface, nil
					case *Union:
						return TypeKindUnion, nil
					case *Enum:
						return TypeKindEnum, nil
					case *InputObject:
						return TypeKindInputObject, nil
					case *List:
						return TypeKindList, nil
					case *NonNull:
						return TypeKindNonNull, nil
					}
					return nil, fmt.Errorf("Unknown kind of type: %v", p.Source)
				},
			},
			"name": &Field{
				Type: String,
			},
			"description": &Field{
				Type: String,
			},
			"fields":        &Field{},
			"interfaces":    &Field{},
			"possibleTypes": &Field{},
			"enumValues":    &Field{},
			"inputFields":   &Field{},
			"ofType":        &Field{},
		},
	})

	inputValueType = NewObject(ObjectConfig{
		Name: "__InputValue",
		Description: "Arguments provided to Fields or Directives and the input fields of an " +
			"InputObject are represented as Input Values which describe their type " +
			"and optionally a default value.",
		Fields: Fields{
			"name": &Field{
				Type: NewNonNull(String),
			},
			"description": &Field{
				Type: String,
			},
			"type": &Field{
				Type: NewNonNull(typeType),
			},
			"defaultValue": &Field{
				Type: String,
				Description: "A GraphQL-formatted string representing the default value for this " +
					"input value.",
				Resolve: func(p ResolveParams) (interface{}, error) {
					if inputVal, ok := p.Source.(*Argument); ok {
						if inputVal.DefaultValue == nil {
							return nil, nil
						}
						if isNullish(inputVal.DefaultValue) {
							return nil, nil
						}
						astVal := astFromValue(inputVal.DefaultValue, inputVal)
						return printer.Print(astVal), nil
					}
					if inputVal, ok := p.Source.(*InputObjectField); ok {
						if inputVal.DefaultValue == nil {
							return nil, nil
						}
						astVal := astFromValue(inputVal.DefaultValue, inputVal)
						return printer.Print(astVal), nil
					}
					return nil, nil
				},
			},
		},
	})

	fieldType = NewObject(ObjectConfig{
		Name: "__Field",
		Description: "Object and Interface types are described by a list of Fields, each of " +
			"which has a name, potentially a list of arguments, and a return type.",
		Fields: Fields{
			"name": &Field{
				Type: NewNonNull(String),
			},
			"description": &Field{
				Type: String,
			},
			"args": &Field{
				Type: NewNonNull(NewList(NewNonNull(inputValueType))),
				Resolve: func(p ResolveParams) (interface{}, error) {
					if field, ok := p.Source.(*FieldDefinition); ok {
						return field.Args, nil
					}
					return []interface{}{}, nil
				},
			},
			"type": &Field{
				Type: NewNonNull(typeType),
			},
			"isDeprecated": &Field{
				Type: NewNonNull(Boolean),
				Resolve: func(p ResolveParams) (interface{}, error) {
					if field, ok := p.Source.(*FieldDefinition); ok {
						return (field.DeprecationReason != ""), nil
					}
					return false, nil
				},
			},
			"deprecationReason": &Field{
				Type: String,
			},
		},
	})

	directiveType = NewObject(ObjectConfig{
		Name: "__Directive",
		Description: "A Directive provides a way to describe alternate runtime execution and " +
			"type validation behavior in a GraphQL document. " +
			"\n\nIn some cases, you need to provide options to alter GraphQL's " +
			"execution behavior in ways field arguments will not suffice, such as " +
			"conditionally including or skipping a field. Directives provide this by " +
			"describing additional information to the executor.",
		Fields: Fields{
			"name": &Field{
				Type: NewNonNull(String),
			},
			"description": &Field{
				Type: String,
			},
			"locations": &Field{
				Type: NewNonNull(NewList(
					NewNonNull(directiveLocationEnum),
				)),
			},
			"args": &Field{
				Type: NewNonNull(NewList(
					NewNonNull(inputValueType),
				)),
			},
			// NOTE: the following three fields are deprecated and are no longer part
			// of the GraphQL specification.
			"onOperation": &Field{
				DeprecationReason: "Use `locations`.",
				Type:              NewNonNull(Boolean),
				Resolve: func(p ResolveParams) (interface{}, error) {
					if dir, ok := p.Source.(*Directive); ok {
						res := false
						for _, loc := range dir.Locations {
							if loc == DirectiveLocationQuery ||
								loc == DirectiveLocationMutation ||
								loc == DirectiveLocationSubscription {
								res = true
								break
							}
						}
						return res, nil
					}
					return false, nil
				},
			},
			"onFragment": &Field{
				DeprecationReason: "Use `locations`.",
				Type:              NewNonNull(Boolean),
				Resolve: func(p ResolveParams) (interface{}, error) {
					if dir, ok := p.Source.(*Directive); ok {
						res := false
						for _, loc := range dir.Locations {
							if loc == DirectiveLocationFragmentSpread ||
								loc == DirectiveLocationInlineFragment ||
								loc == DirectiveLocationFragmentDefinition {
								res = true
								break
							}
						}
						return res, nil
					}
					return false, nil
				},
			},
			"onField": &Field{
				DeprecationReason: "Use `locations`.",
				Type:              NewNonNull(Boolean),
				Resolve: func(p ResolveParams) (interface{}, error) {
					if dir, ok := p.Source.(*Directive); ok {
						res := false
						for _, loc := range dir.Locations {
							if loc == DirectiveLocationField {
								res = true
								break
							}
						}
						return res, nil
					}
					return false, nil
				},
			},
		},
	})

	schemaType = NewObject(ObjectConfig{
		Name: "__Schema",
		Description: `A GraphQL Schema defines the capabilities of a GraphQL server. ` +
			`It exposes all available types and directives on the server, as well as ` +
			`the entry points for query, mutation, and subscription operations.`,
		Fields: Fields{
			"types": &Field{
				Description: "A list of all types supported by this server.",
				Type: NewNonNull(NewList(
					NewNonNull(typeType),
				)),
				Resolve: func(p ResolveParams) (interface{}, error) {
					if schema, ok := p.Source.(Schema); ok {
						results := []Type{}
						for _, ttype := range schema.TypeMap() {
							results = append(results, ttype)
						}
						return results, nil
					}
					return []Type{}, nil
				},
			},
			"queryType": &Field{
				Description: "The type that query operations will be rooted at.",
				Type:        NewNonNull(typeType),
				Resolve: func(p ResolveParams) (interface{}, error) {
					if schema, ok := p.Source.(Schema); ok {
						return schema.QueryType(), nil
					}
					return nil, nil
				},
			},
			"mutationType": &Field{
				Description: `If this server supports mutation, the type that ` +
					`mutation operations will be rooted at.`,
				Type: typeType,
				Resolve: func(p ResolveParams) (interface{}, error) {
					if schema, ok := p.Source.(Schema); ok {
						if schema.MutationType() != nil {
							return schema.MutationType(), nil
						}
					}
					return nil, nil
				},
			},
			"subscriptionType": &Field{
				Description: `If this server supports subscription, the type that ` +
					`subscription operations will be rooted at.`,
				Type: typeType,
				Resolve: func(p ResolveParams) (interface{}, error) {
					if schema, ok := p.Source.(Schema); ok {
						if schema.SubscriptionType() != nil {
							return schema.SubscriptionType(), nil
						}
					}
					return nil, nil
				},
			},
			"directives": &Field{
				Description: `A list of all directives supported by this server.`,
				Type: NewNonNull(NewList(
					NewNonNull(directiveType),
				)),
				Resolve: func(p ResolveParams) (interface{}, error) {
					if schema, ok := p.Source.(Schema); ok {
						return schema.Directives(), nil
					}
					return nil, nil
				},
			},
		},
	})

	enumValueType = NewObject(ObjectConfig{
		Name: "__EnumValue",
		Description: "One possible value for a given Enum. Enum values are unique values, not " +
			"a placeholder for a string or numeric value. However an Enum value is " +
			"returned in a JSON response as a string.",
		Fields: Fields{
			"name": &Field{
				Type: NewNonNull(String),
			},
			"description": &Field{
				Type: String,
			},
			"isDeprecated": &Field{
				Type: NewNonNull(Boolean),
				Resolve: func(p ResolveParams) (interface{}, error) {
					if field, ok := p.Source.(*EnumValueDefinition); ok {
						return (field.DeprecationReason != ""), nil
					}
					return false, nil
				},
			},
			"deprecationReason": &Field{
				Type: String,
			},
		},
	})

	// Again, adding field configs to __Type that have cyclic reference here
	// because golang don't like them too much during init/compile-time
	typeType.AddFieldConfig("fields", &Field{
		Type: NewList(NewNonNull(fieldType)),
		Args: FieldConfigArgument{
			"includeDeprecated": &ArgumentConfig{
				Type:         Boolean,
				DefaultValue: false,
			},
		},
		Resolve: func(p ResolveParams) (interface{}, error) {
			includeDeprecated, _ := p.Args["includeDeprecated"].(bool)
			switch ttype := p.Source.(type) {
			case *Object:
				if ttype == nil {
					return nil, nil
				}
				fields := []*FieldDefinition{}
				for _, field := range ttype.Fields() {
					if !includeDeprecated && field.DeprecationReason != "" {
						continue
					}
					fields = append(fields, field)
				}
				return fields, nil
			case *Interface:
				if ttype == nil {
					return nil, nil
				}
				fields := []*FieldDefinition{}
				for _, field := range ttype.Fields() {
					if !includeDeprecated && field.DeprecationReason != "" {
						continue
					}
					fields = append(fields, field)
				}
				return fields, nil
			}
			return nil, nil
		},
	})
	typeType.AddFieldConfig("interfaces", &Field{
		Type: NewList(NewNonNull(typeType)),
		Resolve: func(p ResolveParams) (interface{}, error) {
			switch ttype := p.Source.(type) {
			case *Object:
				return ttype.Interfaces(), nil
			}
			return nil, nil
		},
	})
	typeType.AddFieldConfig("possibleTypes", &Field{
		Type: NewList(NewNonNull(typeType)),
		Resolve: func(p ResolveParams) (interface{}, error) {
			switch ttype := p.Source.(type) {
			case *Interface:
				return p.Info.Schema.PossibleTypes(ttype), nil
			case *Union:
				return p.Info.Schema.PossibleTypes(ttype), nil
			}
			return nil, nil
		},
	})
	typeType.AddFieldConfig("enumValues", &Field{
		Type: NewList(NewNonNull(enumValueType)),
		Args: FieldConfigArgument{
			"includeDeprecated": &ArgumentConfig{
				Type:         Boolean,
				DefaultValue: false,
			},
		},
		Resolve: func(p ResolveParams) (interface{}, error) {
			includeDeprecated, _ := p.Args["includeDeprecated"].(bool)
			switch ttype := p.Source.(type) {
			case *Enum:
				if includeDeprecated {
					return ttype.Values(), nil
				}
				values := []*EnumValueDefinition{}
				for _, value := range ttype.Values() {
					if value.DeprecationReason != "" {
						continue
					}
					values = append(values, value)
				}
				return values, nil
			}
			return nil, nil
		},
	})
	typeType.AddFieldConfig("inputFields", &Field{
		Type: NewList(NewNonNull(inputValueType)),
		Resolve: func(p ResolveParams) (interface{}, error) {
			switch ttype := p.Source.(type) {
			case *InputObject:
				fields := []*InputObjectField{}
				for _, field := range ttype.Fields() {
					fields = append(fields, field)
				}
				return fields, nil
			}
			return nil, nil
		},
	})
	typeType.AddFieldConfig("ofType", &Field{
		Type: typeType,
	})

	// Note that these are FieldDefinition and not FieldConfig,
	// so the format for args is different.   d
	SchemaMetaFieldDef = &FieldDefinition{
		Name:        "__schema",
		Type:        NewNonNull(schemaType),
		Description: "Access the current type schema of this server.",
		Args:        []*Argument{},
		Resolve: func(p ResolveParams) (interface{}, error) {
			return p.Info.Schema, nil
		},
	}
	TypeMetaFieldDef = &FieldDefinition{
		Name:        "__type",
		Type:        typeType,
		Description: "Request the type information of a single type.",
		Args: []*Argument{
			{
				PrivateName: "name",
				Type:        NewNonNull(String),
			},
		},
		Resolve: func(p ResolveParams) (interface{}, error) {
			name, ok := p.Args["name"].(string)
			if !ok {
				return nil, nil
			}
			return p.Info.Schema.Type(name), nil
		},
	}

	TypeNameMetaFieldDef = &FieldDefinition{
		Name:        "__typename",
		Type:        NewNonNull(String),
		Description: "The name of the current Object type at runtime.",
		Args:        []*Argument{},
		Resolve: func(p ResolveParams) (interface{}, error) {
			return p.Info.ParentType.Name(), nil
		},
	}

}
Пример #8
0
func TestPrinter_PrintsKitchenSink(t *testing.T) {
	b, err := ioutil.ReadFile("../../kitchen-sink.graphql")
	if err != nil {
		t.Fatalf("unable to load kitchen-sink.graphql")
	}

	query := string(b)
	astDoc := parse(t, query)
	expected := `query namedQuery($foo: ComplexFooType, $bar: Bar = DefaultBarValue) {
  customUser: user(id: [987, 654]) {
    id
    ... on User @defer {
      field2 {
        id
        alias: field1(first: 10, after: $foo) @include(if: $foo) {
          id
          ...frag
        }
      }
    }
    ... @skip(unless: $foo) {
      id
    }
    ... {
      id
    }
  }
}

mutation favPost {
  fav(post: 123) @defer {
    post {
      id
    }
  }
}

subscription PostFavSubscription($input: StoryLikeSubscribeInput) {
  postFavSubscribe(input: $input) {
    post {
      favers {
        count
      }
      favSentence {
        text
      }
    }
  }
}

fragment frag on Follower {
  foo(size: $size, bar: $b, obj: {key: "value"})
}

{
  unnamed(truthyVal: true, falseyVal: false)
  query
}
`
	results := printer.Print(astDoc)

	if !reflect.DeepEqual(expected, results) {
		t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(results, expected))
	}
}
Пример #9
0
func TestParsesMultiByteCharacters_UnicodeText(t *testing.T) {

	doc := `
        # This comment has a фы世界 multi-byte character.
        { field(arg: "Has a фы世界 multi-byte character.") }
	`
	astDoc := parse(t, doc)

	expectedASTDoc := ast.NewDocument(&ast.Document{
		Loc: ast.NewLocation(&ast.Location{
			Start: 67,
			End:   121,
		}),
		Definitions: []ast.Node{
			ast.NewOperationDefinition(&ast.OperationDefinition{
				Loc: ast.NewLocation(&ast.Location{
					Start: 67,
					End:   119,
				}),
				Operation: "query",
				SelectionSet: ast.NewSelectionSet(&ast.SelectionSet{
					Loc: ast.NewLocation(&ast.Location{
						Start: 67,
						End:   119,
					}),
					Selections: []ast.Selection{
						ast.NewField(&ast.Field{
							Loc: ast.NewLocation(&ast.Location{
								Start: 67,
								End:   117,
							}),
							Name: ast.NewName(&ast.Name{
								Loc: ast.NewLocation(&ast.Location{
									Start: 69,
									End:   74,
								}),
								Value: "field",
							}),
							Arguments: []*ast.Argument{
								ast.NewArgument(&ast.Argument{
									Loc: ast.NewLocation(&ast.Location{
										Start: 75,
										End:   116,
									}),
									Name: ast.NewName(&ast.Name{

										Loc: ast.NewLocation(&ast.Location{
											Start: 75,
											End:   78,
										}),
										Value: "arg",
									}),
									Value: ast.NewStringValue(&ast.StringValue{

										Loc: ast.NewLocation(&ast.Location{
											Start: 80,
											End:   116,
										}),
										Value: "Has a фы世界 multi-byte character.",
									}),
								}),
							},
						}),
					},
				}),
			}),
		},
	})

	astDocQuery := printer.Print(astDoc)
	expectedASTDocQuery := printer.Print(expectedASTDoc)

	if !reflect.DeepEqual(astDocQuery, expectedASTDocQuery) {
		t.Fatalf("unexpected document, expected: %v, got: %v", astDocQuery, expectedASTDocQuery)
	}
}