# frozen_string_literal: true module GraphQL module Compatibility # This asserts that a given parse function turns a string into # the proper tree of {{GraphQL::Language::Nodes}}. module SchemaParserSpecification # @yieldparam query_string [String] A query string to parse # @yieldreturn [GraphQL::Language::Nodes::Document] # @return [Class] A test suite for this parse function def self.build_suite(&block) Class.new(Minitest::Test) do @@parse_fn = block def parse(query_string) @@parse_fn.call(query_string) end def test_it_parses_object_types document = parse(' # This is what # somebody said about something type Comment implements Node @deprecated(reason: "No longer supported") { id: ID! } ') type = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeDefinition, type.class assert_equal 'Comment', type.name assert_equal "This is what\nsomebody said about something", type.description assert_equal ['Node'], type.interfaces.map(&:name) assert_equal ['id'], type.fields.map(&:name) assert_equal [], type.fields[0].arguments assert_equal 'ID', type.fields[0].type.of_type.name assert_equal 1, type.directives.length deprecated_directive = type.directives[0] assert_equal 'deprecated', deprecated_directive.name assert_equal 'reason', deprecated_directive.arguments[0].name assert_equal 'No longer supported', deprecated_directive.arguments[0].value end def test_it_parses_scalars document = parse('scalar DateTime') type = document.definitions.first assert_equal GraphQL::Language::Nodes::ScalarTypeDefinition, type.class assert_equal 'DateTime', type.name end def test_it_parses_enum_types document = parse(' enum DogCommand { # Good dog SIT DOWN @deprecated(reason: "No longer supported") HEEL } ') type = document.definitions.first assert_equal GraphQL::Language::Nodes::EnumTypeDefinition, type.class assert_equal 'DogCommand', type.name assert_equal 3, type.values.length assert_equal 'SIT', type.values[0].name assert_equal [], type.values[0].directives assert_equal "Good dog", type.values[0].description assert_equal 'DOWN', type.values[1].name assert_equal 1, type.values[1].directives.length deprecated_directive = type.values[1].directives[0] assert_equal 'deprecated', deprecated_directive.name assert_equal 'reason', deprecated_directive.arguments[0].name assert_equal 'No longer supported', deprecated_directive.arguments[0].value assert_equal 'HEEL', type.values[2].name assert_equal [], type.values[2].directives end def test_it_parses_union_types document = parse( "union BagOfThings = \n" \ "A |\n" \ "B |\n" \ "C" ) union = document.definitions.first assert_equal GraphQL::Language::Nodes::UnionTypeDefinition, union.class assert_equal 'BagOfThings', union.name assert_equal 3, union.types.length assert_equal [1, 1], union.position assert_equal GraphQL::Language::Nodes::TypeName, union.types[0].class assert_equal 'A', union.types[0].name assert_equal [2, 1], union.types[0].position assert_equal GraphQL::Language::Nodes::TypeName, union.types[1].class assert_equal 'B', union.types[1].name assert_equal [3, 1], union.types[1].position assert_equal GraphQL::Language::Nodes::TypeName, union.types[2].class assert_equal 'C', union.types[2].name assert_equal [4, 1], union.types[2].position end def test_it_parses_input_types document = parse(' input EmptyMutationInput { clientMutationId: String } ') type = document.definitions.first assert_equal GraphQL::Language::Nodes::InputObjectTypeDefinition, type.class assert_equal 'EmptyMutationInput', type.name assert_equal ['clientMutationId'], type.fields.map(&:name) assert_equal 'String', type.fields[0].type.name assert_equal nil, type.fields[0].default_value end def test_it_parses_directives document = parse(' directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT ') type = document.definitions.first assert_equal GraphQL::Language::Nodes::DirectiveDefinition, type.class assert_equal 'include', type.name assert_equal 1, type.arguments.length assert_equal 'if', type.arguments[0].name assert_equal 'Boolean', type.arguments[0].type.of_type.name assert_equal 3, type.locations.length assert_instance_of GraphQL::Language::Nodes::DirectiveLocation, type.locations[0] assert_equal 'FIELD', type.locations[0].name assert_equal [3, 20], type.locations[0].position assert_instance_of GraphQL::Language::Nodes::DirectiveLocation, type.locations[1] assert_equal 'FRAGMENT_SPREAD', type.locations[1].name assert_equal [4, 19], type.locations[1].position assert_instance_of GraphQL::Language::Nodes::DirectiveLocation, type.locations[2] assert_equal 'INLINE_FRAGMENT', type.locations[2].name assert_equal [5, 19], type.locations[2].position end def test_it_parses_field_arguments document = parse(' type Mutation { post( id: ID! @deprecated(reason: "Not used"), # This is what goes in the post data: String ): Post } ') field = document.definitions.first.fields.first assert_equal ['id', 'data'], field.arguments.map(&:name) id_arg = field.arguments[0] deprecated_directive = id_arg.directives[0] assert_equal 'deprecated', deprecated_directive.name assert_equal 'reason', deprecated_directive.arguments[0].name assert_equal 'Not used', deprecated_directive.arguments[0].value data_arg = field.arguments[1] assert_equal "data", data_arg.name assert_equal "This is what goes in the post", data_arg.description end def test_it_parses_schema_definition document = parse(' schema { query: QueryRoot mutation: MutationRoot subscription: SubscriptionRoot } ') schema = document.definitions.first assert_equal 'QueryRoot', schema.query assert_equal 'MutationRoot', schema.mutation assert_equal 'SubscriptionRoot', schema.subscription end def test_it_parses_schema_extensions document = parse(' extend schema { query: QueryRoot mutation: MutationRoot subscription: SubscriptionRoot } ') schema_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::SchemaExtension, schema_extension.class assert_equal [2, 15], schema_extension.position assert_equal 'QueryRoot', schema_extension.query assert_equal 'MutationRoot', schema_extension.mutation assert_equal 'SubscriptionRoot', schema_extension.subscription end def test_it_parses_schema_extensions_with_directives document = parse(' extend schema @something { query: QueryRoot } ') schema_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::SchemaExtension, schema_extension.class assert_equal 1, schema_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, schema_extension.directives.first.class assert_equal 'something', schema_extension.directives.first.name assert_equal 'QueryRoot', schema_extension.query assert_equal nil, schema_extension.mutation assert_equal nil, schema_extension.subscription end def test_it_parses_schema_extensions_with_only_directives document = parse(' extend schema @something ') schema_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::SchemaExtension, schema_extension.class assert_equal 1, schema_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, schema_extension.directives.first.class assert_equal 'something', schema_extension.directives.first.name assert_equal nil, schema_extension.query assert_equal nil, schema_extension.mutation assert_equal nil, schema_extension.subscription end def test_it_parses_scalar_extensions document = parse(' extend scalar Date @something @somethingElse ') scalar_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ScalarTypeExtension, scalar_extension.class assert_equal 'Date', scalar_extension.name assert_equal [2, 15], scalar_extension.position assert_equal 2, scalar_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, scalar_extension.directives.first.class assert_equal 'something', scalar_extension.directives.first.name assert_equal GraphQL::Language::Nodes::Directive, scalar_extension.directives.last.class assert_equal 'somethingElse', scalar_extension.directives.last.name end def test_it_parses_object_type_extensions_with_field_definitions document = parse(' extend type User { login: String! } ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, object_type_extension.fields.first.class end def test_it_parses_object_type_extensions_with_field_definitions_and_directives document = parse(' extend type User @deprecated { login: String! } ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, object_type_extension.fields.first.class assert_equal 1, object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, object_type_extension.directives.first.class end def test_it_parses_object_type_extensions_with_field_definitions_and_implements document = parse(' extend type User implements Node { login: String! } ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, object_type_extension.fields.first.class assert_equal 1, object_type_extension.interfaces.length assert_equal GraphQL::Language::Nodes::TypeName, object_type_extension.interfaces.first.class end def test_it_parses_object_type_extensions_with_only_directives document = parse(' extend type User @deprecated ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, object_type_extension.directives.first.class assert_equal 'deprecated', object_type_extension.directives.first.name end def test_it_parses_object_type_extensions_with_implements_and_directives document = parse(' extend type User implements Node @deprecated ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, object_type_extension.directives.first.class assert_equal 'deprecated', object_type_extension.directives.first.name assert_equal 1, object_type_extension.interfaces.length assert_equal GraphQL::Language::Nodes::TypeName, object_type_extension.interfaces.first.class assert_equal 'Node', object_type_extension.interfaces.first.name end def test_it_parses_object_type_extensions_with_only_implements document = parse(' extend type User implements Node ') object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::ObjectTypeExtension, object_type_extension.class assert_equal 'User', object_type_extension.name assert_equal [2, 15], object_type_extension.position assert_equal 1, object_type_extension.interfaces.length assert_equal GraphQL::Language::Nodes::TypeName, object_type_extension.interfaces.first.class assert_equal 'Node', object_type_extension.interfaces.first.name end def test_it_parses_interface_type_extensions_with_directives_and_fields document = parse(' extend interface Node @directive { field: String } ') interface_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InterfaceTypeExtension, interface_type_extension.class assert_equal 'Node', interface_type_extension.name assert_equal [2, 15], interface_type_extension.position assert_equal 1, interface_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, interface_type_extension.directives.first.class assert_equal 'directive', interface_type_extension.directives.first.name assert_equal 1, interface_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, interface_type_extension.fields.first.class assert_equal 'field', interface_type_extension.fields.first.name end def test_it_parses_interface_type_extensions_with_fields document = parse(' extend interface Node { field: String } ') interface_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InterfaceTypeExtension, interface_type_extension.class assert_equal 'Node', interface_type_extension.name assert_equal [2, 15], interface_type_extension.position assert_equal 0, interface_type_extension.directives.length assert_equal 1, interface_type_extension.fields.length assert_equal GraphQL::Language::Nodes::FieldDefinition, interface_type_extension.fields.first.class assert_equal 'field', interface_type_extension.fields.first.name end def test_it_parses_interface_type_extensions_with_directives document = parse(' extend interface Node @directive ') interface_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InterfaceTypeExtension, interface_type_extension.class assert_equal 'Node', interface_type_extension.name assert_equal [2, 15], interface_type_extension.position assert_equal 1, interface_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, interface_type_extension.directives.first.class assert_equal 'directive', interface_type_extension.directives.first.name end def test_it_parses_union_type_extension_with_union_members document = parse(' extend union BagOfThings = A | B ') union_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::UnionTypeExtension, union_type_extension.class assert_equal 'BagOfThings', union_type_extension.name assert_equal [2, 15], union_type_extension.position assert_equal 0, union_type_extension.directives.length assert_equal 2, union_type_extension.types.length assert_equal GraphQL::Language::Nodes::TypeName, union_type_extension.types.first.class assert_equal 'A', union_type_extension.types.first.name end def test_it_parses_union_type_extension_with_directives_and_union_members document = parse(' extend union BagOfThings @directive = A | B ') union_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::UnionTypeExtension, union_type_extension.class assert_equal 'BagOfThings', union_type_extension.name assert_equal [2, 15], union_type_extension.position assert_equal 1, union_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, union_type_extension.directives.first.class assert_equal 'directive', union_type_extension.directives.first.name assert_equal 2, union_type_extension.types.length assert_equal GraphQL::Language::Nodes::TypeName, union_type_extension.types.first.class assert_equal 'A', union_type_extension.types.first.name end def test_it_parses_union_type_extension_with_directives document = parse(' extend union BagOfThings @directive ') union_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::UnionTypeExtension, union_type_extension.class assert_equal 'BagOfThings', union_type_extension.name assert_equal [2, 15], union_type_extension.position assert_equal 1, union_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, union_type_extension.directives.first.class assert_equal 'directive', union_type_extension.directives.first.name assert_equal 0, union_type_extension.types.length end def test_it_parses_enum_type_extension_with_values document = parse(' extend enum Status { DRAFT PUBLISHED } ') enum_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::EnumTypeExtension, enum_type_extension.class assert_equal 'Status', enum_type_extension.name assert_equal [2, 15], enum_type_extension.position assert_equal 0, enum_type_extension.directives.length assert_equal 2, enum_type_extension.values.length assert_equal GraphQL::Language::Nodes::EnumValueDefinition, enum_type_extension.values.first.class assert_equal 'DRAFT', enum_type_extension.values.first.name end def test_it_parses_enum_type_extension_with_directives_and_values document = parse(' extend enum Status @directive { DRAFT PUBLISHED } ') enum_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::EnumTypeExtension, enum_type_extension.class assert_equal 'Status', enum_type_extension.name assert_equal [2, 15], enum_type_extension.position assert_equal 1, enum_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, enum_type_extension.directives.first.class assert_equal 'directive', enum_type_extension.directives.first.name assert_equal 2, enum_type_extension.values.length assert_equal GraphQL::Language::Nodes::EnumValueDefinition, enum_type_extension.values.first.class assert_equal 'DRAFT', enum_type_extension.values.first.name end def test_it_parses_enum_type_extension_with_directives document = parse(' extend enum Status @directive ') enum_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::EnumTypeExtension, enum_type_extension.class assert_equal 'Status', enum_type_extension.name assert_equal [2, 15], enum_type_extension.position assert_equal 1, enum_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, enum_type_extension.directives.first.class assert_equal 'directive', enum_type_extension.directives.first.name assert_equal 0, enum_type_extension.values.length end def test_it_parses_input_object_type_extension_with_fields document = parse(' extend input UserInput { login: String! } ') input_object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InputObjectTypeExtension, input_object_type_extension.class assert_equal 'UserInput', input_object_type_extension.name assert_equal [2, 15], input_object_type_extension.position assert_equal 1, input_object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::InputValueDefinition, input_object_type_extension.fields.first.class assert_equal 'login', input_object_type_extension.fields.first.name assert_equal 0, input_object_type_extension.directives.length end def test_it_parses_input_object_type_extension_with_directives_and_fields document = parse(' extend input UserInput @deprecated { login: String! } ') input_object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InputObjectTypeExtension, input_object_type_extension.class assert_equal 'UserInput', input_object_type_extension.name assert_equal [2, 15], input_object_type_extension.position assert_equal 1, input_object_type_extension.fields.length assert_equal GraphQL::Language::Nodes::InputValueDefinition, input_object_type_extension.fields.first.class assert_equal 'login', input_object_type_extension.fields.first.name assert_equal 1, input_object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, input_object_type_extension.directives.first.class assert_equal 'deprecated', input_object_type_extension.directives.first.name end def test_it_parses_input_object_type_extension_with_directives document = parse(' extend input UserInput @deprecated ') input_object_type_extension = document.definitions.first assert_equal GraphQL::Language::Nodes::InputObjectTypeExtension, input_object_type_extension.class assert_equal 'UserInput', input_object_type_extension.name assert_equal [2, 15], input_object_type_extension.position assert_equal 0, input_object_type_extension.fields.length assert_equal 1, input_object_type_extension.directives.length assert_equal GraphQL::Language::Nodes::Directive, input_object_type_extension.directives.first.class assert_equal 'deprecated', input_object_type_extension.directives.first.name end def test_it_parses_whole_definition_with_descriptions document = parse(SCHEMA_DEFINITION_STRING) assert_equal 6, document.definitions.size schema_definition, directive_definition, enum_type_definition, object_type_definition, input_object_type_definition, interface_type_definition = document.definitions assert_equal GraphQL::Language::Nodes::SchemaDefinition, schema_definition.class assert_equal GraphQL::Language::Nodes::DirectiveDefinition, directive_definition.class assert_equal 'This is a directive', directive_definition.description assert_equal GraphQL::Language::Nodes::EnumTypeDefinition, enum_type_definition.class assert_equal "Multiline comment\n\nWith an enum", enum_type_definition.description assert_nil enum_type_definition.values[0].description assert_equal 'Not a creative color', enum_type_definition.values[1].description assert_equal GraphQL::Language::Nodes::ObjectTypeDefinition, object_type_definition.class assert_equal 'Comment without preceding space', object_type_definition.description assert_equal 'And a field to boot', object_type_definition.fields[0].description assert_equal GraphQL::Language::Nodes::InputObjectTypeDefinition, input_object_type_definition.class assert_equal 'Comment for input object types', input_object_type_definition.description assert_equal 'Color of the car', input_object_type_definition.fields[0].description assert_equal GraphQL::Language::Nodes::InterfaceTypeDefinition, interface_type_definition.class assert_equal 'Comment for interface definitions', interface_type_definition.description assert_equal 'Amount of wheels', interface_type_definition.fields[0].description brand_field = interface_type_definition.fields[1] assert_equal 1, brand_field.arguments.length assert_equal 'argument', brand_field.arguments[0].name assert_instance_of GraphQL::Language::Nodes::NullValue, brand_field.arguments[0].default_value end end end SCHEMA_DEFINITION_STRING = %| # Schema at beginning of file schema { query: Hello } # Comment between two definitions are omitted # This is a directive directive @foo( # It has an argument arg: Int ) on FIELD # Multiline comment # # With an enum enum Color { RED # Not a creative color GREEN BLUE } #Comment without preceding space type Hello { # And a field to boot str: String } # Comment for input object types input Car { # Color of the car color: String! } # Comment for interface definitions interface Vehicle { # Amount of wheels wheels: Int! brand(argument: String = null): String! } # Comment at the end of schema | end end end