UNPKG

graphql-language-service

Version:

The official, runtime independent Language Service for GraphQL

214 lines (200 loc) 5.86 kB
/** * Copyright (c) 2021 GraphQL Contributors * All rights reserved. * * This source code is licensed under the license found in the * LICENSE file in the root directory of this source tree. * * @flow */ import fs from 'node:fs'; import { buildSchema, parse, GraphQLSchema, GraphQLError, ValidationContext, ASTVisitor, FragmentDefinitionNode, version, } from 'graphql'; import path from 'node:path'; import { getDiagnostics, validateQuery, DIAGNOSTIC_SEVERITY, } from '../getDiagnostics'; describe('getDiagnostics', () => { let schema: GraphQLSchema; beforeEach(async () => { const schemaIDL = fs.readFileSync( path.join(__dirname, '__schema__/StarWarsSchema.graphql'), 'utf8', ); schema = buildSchema(schemaIDL); }); it('catches field validation errors', () => { const error = validateQuery(parse('query queryName { title }'), schema)[0]; expect(error.message).toEqual( 'Cannot query field "title" on type "Query".', ); expect(error.severity).toEqual(DIAGNOSTIC_SEVERITY.Error); expect(error.source).toEqual('GraphQL: Validation'); }); it('catches with multiple highlighted nodes', () => { const errors = validateQuery( parse('{ hero(episode: $ep) { name } }'), schema, ); expect(errors).toMatchObject([ { range: { end: { character: 20, line: 0, }, start: { character: 16, line: 0, }, }, }, { range: { end: { character: 32, line: 0, }, start: { character: 0, line: 0, }, }, }, ]); }); it('catches multi root validation errors without breaking (with a custom validation function that always throws errors)', () => { const error = validateQuery(parse('{ hero { name } } { seq }'), schema, [ validationContext => { return { Document(node) { for (const definition of node.definitions) { // add a custom error to every definition validationContext.reportError( new GraphQLError( 'This is a custom error.', // @ts-expect-error parseInt(version, 10) > 16 ? { nodes: definition } : definition, ), ); } return false; }, }; }, ])[0]; expect(error.message).toEqual('This is a custom error.'); expect(error.severity).toEqual(DIAGNOSTIC_SEVERITY.Error); expect(error.source).toEqual('GraphQL: Validation'); }); it('catches field deprecation errors', () => { const error = getDiagnostics( '{ deprecatedField { testField } }', schema, )[0]; expect(error.message).toEqual( 'The field Query.deprecatedField is deprecated. Use test instead.', ); expect(error.severity).toEqual(DIAGNOSTIC_SEVERITY.Warning); expect(error.source).toEqual('GraphQL: Deprecation'); }); it('returns no errors for valid query', () => { const errors = getDiagnostics('query { hero { name } }', schema); expect(errors.length).toEqual(0); }); it('returns no errors for valid query with aliases', () => { const errors = getDiagnostics( 'query { superHero: hero { superName: name } superHero2: hero { superName2: name } }', schema, ); expect(errors.length).toEqual(0); }); it('catches a syntax error in the SDL', () => { const errors = getDiagnostics( ` type Human implements Character { field_without_type_is_a_syntax_error id: String! } `, schema, ); expect(errors.length).toEqual(1); const error = errors[0]; expect(error.message).toEqual( 'Syntax Error: Expected ":", found Name "id".', ); expect(error.severity).toEqual(DIAGNOSTIC_SEVERITY.Error); expect(error.source).toEqual('GraphQL: Syntax'); }); // TODO: change this kitchen sink to depend on the local schema // and then run diagnostics with the schema it('returns no errors after parsing kitchen-sink query', () => { const kitchenSink = fs.readFileSync( path.join(__dirname, '/kitchen-sink.graphql'), 'utf8', ); const errors = getDiagnostics(kitchenSink); expect(errors).toHaveLength(0); }); it('returns a error with a custom validation rule', () => { const noQueryRule = (context: ValidationContext): ASTVisitor => ({ OperationDefinition(node) { if (node.operation === 'query') { context.reportError( new GraphQLError( 'No query allowed.', // @ts-expect-error parseInt(version, 10) > 16 ? { nodes: node } : node, ), ); } }, }); const errors = getDiagnostics('query hero { hero { id } }', schema, [ noQueryRule, ]); expect(errors).toHaveLength(1); expect(errors[0].message).toEqual('No query allowed.'); }); it('validates with external fragments', () => { const errors = getDiagnostics( 'query hero { hero { ...HeroGuy } }', schema, [], false, 'fragment HeroGuy on Human { id }', ); expect(errors).toHaveLength(0); }); it('validates with external fragments as array', () => { const externalFragments = parse(` fragment Person on Human { name } fragment Person2 on Human { name } `).definitions as FragmentDefinitionNode[]; const errors = getDiagnostics( 'query hero { hero { ...Person ...Person2 } }', schema, [], false, externalFragments, ); expect(errors).toHaveLength(0); }); });