import { expect } from 'chai';
import { describe, it } from 'mocha';

import dedent from '../../__testUtils__/dedent';
import kitchenSinkSDL from '../../__testUtils__/kitchenSinkSDL';

import { parse } from '../parser';

import toJSONDeep from './toJSONDeep';

function expectSyntaxError(text: string) {
  return expect(() => parse(text)).to.throw();
}

function typeNode(name: mixed, loc: mixed) {
  return {
    kind: 'NamedType',
    name: nameNode(name, loc),
    loc,
  };
}

function nameNode(name: mixed, loc: mixed) {
  return {
    kind: 'Name',
    value: name,
    loc,
  };
}

function fieldNode(name: mixed, type: mixed, loc: mixed) {
  return fieldNodeWithArgs(name, type, [], loc);
}

function fieldNodeWithArgs(name: mixed, type: mixed, args: mixed, loc: mixed) {
  return {
    kind: 'FieldDefinition',
    description: undefined,
    name,
    arguments: args,
    type,
    directives: [],
    loc,
  };
}

function enumValueNode(name: mixed, loc: mixed) {
  return {
    kind: 'EnumValueDefinition',
    name: nameNode(name, loc),
    description: undefined,
    directives: [],
    loc,
  };
}

function inputValueNode(
  name: mixed,
  type: mixed,
  defaultValue: mixed,
  loc: mixed,
) {
  return {
    kind: 'InputValueDefinition',
    name,
    description: undefined,
    type,
    defaultValue,
    directives: [],
    loc,
  };
}

describe('Schema Parser', () => {
  it('Simple type', () => {
    const doc = parse(dedent`
      type Hello {
        world: String
      }
    `);

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ObjectTypeDefinition',
          name: nameNode('Hello', { start: 5, end: 10 }),
          description: undefined,
          interfaces: [],
          directives: [],
          fields: [
            fieldNode(
              nameNode('world', { start: 15, end: 20 }),
              typeNode('String', { start: 22, end: 28 }),
              { start: 15, end: 28 },
            ),
          ],
          loc: { start: 0, end: 30 },
        },
      ],
      loc: { start: 0, end: 31 },
    });
  });

  it('parses type with description string', () => {
    const doc = parse(dedent`
      "Description"
      type Hello {
        world: String
      }
    `);

    expect(toJSONDeep(doc)).to.deep.nested.property(
      'definitions[0].description',
      {
        kind: 'StringValue',
        value: 'Description',
        block: false,
        loc: { start: 0, end: 13 },
      },
    );
  });

  it('parses type with description multi-line string', () => {
    const doc = parse(dedent`
      """
      Description
      """
      # Even with comments between them
      type Hello {
        world: String
      }
    `);

    expect(toJSONDeep(doc)).to.deep.nested.property(
      'definitions[0].description',
      {
        kind: 'StringValue',
        value: 'Description',
        block: true,
        loc: { start: 0, end: 19 },
      },
    );
  });

  it('parses schema with description string', () => {
    const doc = parse(dedent`
      "Description"
      schema {
        query: Foo
      }
    `);

    expect(toJSONDeep(doc)).to.deep.nested.property(
      'definitions[0].description',
      {
        kind: 'StringValue',
        value: 'Description',
        block: false,
        loc: { start: 0, end: 13 },
      },
    );
  });

  it('Description followed by something other than type system definition throws', () => {
    expectSyntaxError('"Description" 1').to.deep.equal({
      message: 'Syntax Error: Unexpected Int "1".',
      locations: [{ line: 1, column: 15 }],
    });
  });

  it('Simple extension', () => {
    const doc = parse(dedent`
      extend type Hello {
        world: String
      }
    `);

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ObjectTypeExtension',
          name: nameNode('Hello', { start: 12, end: 17 }),
          interfaces: [],
          directives: [],
          fields: [
            fieldNode(
              nameNode('world', { start: 22, end: 27 }),
              typeNode('String', { start: 29, end: 35 }),
              { start: 22, end: 35 },
            ),
          ],
          loc: { start: 0, end: 37 },
        },
      ],
      loc: { start: 0, end: 38 },
    });
  });

  it('Object extension without fields', () => {
    const doc = parse('extend type Hello implements Greeting');

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ObjectTypeExtension',
          name: nameNode('Hello', { start: 12, end: 17 }),
          interfaces: [typeNode('Greeting', { start: 29, end: 37 })],
          directives: [],
          fields: [],
          loc: { start: 0, end: 37 },
        },
      ],
      loc: { start: 0, end: 37 },
    });
  });

  it('Interface extension without fields', () => {
    const doc = parse('extend interface Hello implements Greeting');
    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'InterfaceTypeExtension',
          name: nameNode('Hello', { start: 17, end: 22 }),
          interfaces: [typeNode('Greeting', { start: 34, end: 42 })],
          directives: [],
          fields: [],
          loc: { start: 0, end: 42 },
        },
      ],
      loc: { start: 0, end: 42 },
    });
  });

  it('Object extension without fields followed by extension', () => {
    const doc = parse(`
      extend type Hello implements Greeting

      extend type Hello implements SecondGreeting
    `);

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ObjectTypeExtension',
          name: nameNode('Hello', { start: 19, end: 24 }),
          interfaces: [typeNode('Greeting', { start: 36, end: 44 })],
          directives: [],
          fields: [],
          loc: { start: 7, end: 44 },
        },
        {
          kind: 'ObjectTypeExtension',
          name: nameNode('Hello', { start: 64, end: 69 }),
          interfaces: [typeNode('SecondGreeting', { start: 81, end: 95 })],
          directives: [],
          fields: [],
          loc: { start: 52, end: 95 },
        },
      ],
      loc: { start: 0, end: 100 },
    });
  });

  it('Extension without anything throws', () => {
    expectSyntaxError('extend scalar Hello').to.deep.equal({
      message: 'Syntax Error: Unexpected <EOF>.',
      locations: [{ line: 1, column: 20 }],
    });

    expectSyntaxError('extend type Hello').to.deep.equal({
      message: 'Syntax Error: Unexpected <EOF>.',
      locations: [{ line: 1, column: 18 }],
    });

    expectSyntaxError('extend interface Hello').to.deep.equal({
      message: 'Syntax Error: Unexpected <EOF>.',
      locations: [{ line: 1, column: 23 }],
    });

    expectSyntaxError('extend union Hello').to.deep.equal({
      message: 'Syntax Error: Unexpected <EOF>.',
      locations: [{ line: 1, column: 19 }],
    });

    expectSyntaxError('extend enum Hello').to.deep.equal({
      message: 'Syntax Error: Unexpected <EOF>.',
      locations: [{ line: 1, column: 18 }],
    });

    expectSyntaxError('extend input Hello').to.deep.equal({
      message: 'Syntax Error: Unexpected <EOF>.',
      locations: [{ line: 1, column: 19 }],
    });
  });

  it('Interface extension without fields followed by extension', () => {
    const doc = parse(`
      extend interface Hello implements Greeting

      extend interface Hello implements SecondGreeting
    `);
    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'InterfaceTypeExtension',
          name: nameNode('Hello', { start: 24, end: 29 }),
          interfaces: [typeNode('Greeting', { start: 41, end: 49 })],
          directives: [],
          fields: [],
          loc: { start: 7, end: 49 },
        },
        {
          kind: 'InterfaceTypeExtension',
          name: nameNode('Hello', { start: 74, end: 79 }),
          interfaces: [typeNode('SecondGreeting', { start: 91, end: 105 })],
          directives: [],
          fields: [],
          loc: { start: 57, end: 105 },
        },
      ],
      loc: { start: 0, end: 110 },
    });
  });

  it('Object extension do not include descriptions', () => {
    expectSyntaxError(`
      "Description"
      extend type Hello {
        world: String
      }
    `).to.deep.equal({
      message: 'Syntax Error: Unexpected Name "extend".',
      locations: [{ line: 3, column: 7 }],
    });

    expectSyntaxError(`
      extend "Description" type Hello {
        world: String
      }
    `).to.deep.equal({
      message: 'Syntax Error: Unexpected String "Description".',
      locations: [{ line: 2, column: 14 }],
    });
  });

  it('Interface extension do not include descriptions', () => {
    expectSyntaxError(`
      "Description"
      extend interface Hello {
        world: String
      }
    `).to.deep.equal({
      message: 'Syntax Error: Unexpected Name "extend".',
      locations: [{ line: 3, column: 7 }],
    });

    expectSyntaxError(`
      extend "Description" interface Hello {
        world: String
      }
    `).to.deep.equal({
      message: 'Syntax Error: Unexpected String "Description".',
      locations: [{ line: 2, column: 14 }],
    });
  });

  it('Schema extension', () => {
    const body = `
      extend schema {
        mutation: Mutation
      }`;
    const doc = parse(body);
    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'SchemaExtension',
          directives: [],
          operationTypes: [
            {
              kind: 'OperationTypeDefinition',
              operation: 'mutation',
              type: typeNode('Mutation', { start: 41, end: 49 }),
              loc: { start: 31, end: 49 },
            },
          ],
          loc: { start: 7, end: 57 },
        },
      ],
      loc: { start: 0, end: 57 },
    });
  });

  it('Schema extension with only directives', () => {
    const body = 'extend schema @directive';
    const doc = parse(body);
    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'SchemaExtension',
          directives: [
            {
              kind: 'Directive',
              name: nameNode('directive', { start: 15, end: 24 }),
              arguments: [],
              loc: { start: 14, end: 24 },
            },
          ],
          operationTypes: [],
          loc: { start: 0, end: 24 },
        },
      ],
      loc: { start: 0, end: 24 },
    });
  });

  it('Schema extension without anything throws', () => {
    expectSyntaxError('extend schema').to.deep.equal({
      message: 'Syntax Error: Unexpected <EOF>.',
      locations: [{ line: 1, column: 14 }],
    });
  });

  it('Schema extension with invalid operation type throws', () => {
    expectSyntaxError('extend schema { unknown: SomeType }').to.deep.equal({
      message: 'Syntax Error: Unexpected Name "unknown".',
      locations: [{ line: 1, column: 17 }],
    });
  });

  it('Simple non-null type', () => {
    const doc = parse(dedent`
      type Hello {
        world: String!
      }
    `);

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ObjectTypeDefinition',
          name: nameNode('Hello', { start: 5, end: 10 }),
          description: undefined,
          interfaces: [],
          directives: [],
          fields: [
            fieldNode(
              nameNode('world', { start: 15, end: 20 }),
              {
                kind: 'NonNullType',
                type: typeNode('String', { start: 22, end: 28 }),
                loc: { start: 22, end: 29 },
              },
              { start: 15, end: 29 },
            ),
          ],
          loc: { start: 0, end: 31 },
        },
      ],
      loc: { start: 0, end: 32 },
    });
  });

  it('Simple interface inheriting interface', () => {
    const doc = parse('interface Hello implements World { field: String }');
    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'InterfaceTypeDefinition',
          name: nameNode('Hello', { start: 10, end: 15 }),
          description: undefined,
          interfaces: [typeNode('World', { start: 27, end: 32 })],
          directives: [],
          fields: [
            fieldNode(
              nameNode('field', { start: 35, end: 40 }),
              typeNode('String', { start: 42, end: 48 }),
              { start: 35, end: 48 },
            ),
          ],
          loc: { start: 0, end: 50 },
        },
      ],
      loc: { start: 0, end: 50 },
    });
  });

  it('Simple type inheriting interface', () => {
    const doc = parse('type Hello implements World { field: String }');

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ObjectTypeDefinition',
          name: nameNode('Hello', { start: 5, end: 10 }),
          description: undefined,
          interfaces: [typeNode('World', { start: 22, end: 27 })],
          directives: [],
          fields: [
            fieldNode(
              nameNode('field', { start: 30, end: 35 }),
              typeNode('String', { start: 37, end: 43 }),
              { start: 30, end: 43 },
            ),
          ],
          loc: { start: 0, end: 45 },
        },
      ],
      loc: { start: 0, end: 45 },
    });
  });

  it('Simple type inheriting multiple interfaces', () => {
    const doc = parse('type Hello implements Wo & rld { field: String }');

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ObjectTypeDefinition',
          name: nameNode('Hello', { start: 5, end: 10 }),
          description: undefined,
          interfaces: [
            typeNode('Wo', { start: 22, end: 24 }),
            typeNode('rld', { start: 27, end: 30 }),
          ],
          directives: [],
          fields: [
            fieldNode(
              nameNode('field', { start: 33, end: 38 }),
              typeNode('String', { start: 40, end: 46 }),
              { start: 33, end: 46 },
            ),
          ],
          loc: { start: 0, end: 48 },
        },
      ],
      loc: { start: 0, end: 48 },
    });
  });

  it('Simple interface inheriting multiple interfaces', () => {
    const doc = parse('interface Hello implements Wo & rld { field: String }');
    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'InterfaceTypeDefinition',
          name: nameNode('Hello', { start: 10, end: 15 }),
          description: undefined,
          interfaces: [
            typeNode('Wo', { start: 27, end: 29 }),
            typeNode('rld', { start: 32, end: 35 }),
          ],
          directives: [],
          fields: [
            fieldNode(
              nameNode('field', { start: 38, end: 43 }),
              typeNode('String', { start: 45, end: 51 }),
              { start: 38, end: 51 },
            ),
          ],
          loc: { start: 0, end: 53 },
        },
      ],
      loc: { start: 0, end: 53 },
    });
  });

  it('Simple type inheriting multiple interfaces with leading ampersand', () => {
    const doc = parse('type Hello implements & Wo & rld { field: String }');

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ObjectTypeDefinition',
          name: nameNode('Hello', { start: 5, end: 10 }),
          description: undefined,
          interfaces: [
            typeNode('Wo', { start: 24, end: 26 }),
            typeNode('rld', { start: 29, end: 32 }),
          ],
          directives: [],
          fields: [
            fieldNode(
              nameNode('field', { start: 35, end: 40 }),
              typeNode('String', { start: 42, end: 48 }),
              { start: 35, end: 48 },
            ),
          ],
          loc: { start: 0, end: 50 },
        },
      ],
      loc: { start: 0, end: 50 },
    });
  });

  it('Simple interface inheriting multiple interfaces with leading ampersand', () => {
    const doc = parse(
      'interface Hello implements & Wo & rld { field: String }',
    );
    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'InterfaceTypeDefinition',
          name: nameNode('Hello', { start: 10, end: 15 }),
          description: undefined,
          interfaces: [
            typeNode('Wo', { start: 29, end: 31 }),
            typeNode('rld', { start: 34, end: 37 }),
          ],
          directives: [],
          fields: [
            fieldNode(
              nameNode('field', { start: 40, end: 45 }),
              typeNode('String', { start: 47, end: 53 }),
              { start: 40, end: 53 },
            ),
          ],
          loc: { start: 0, end: 55 },
        },
      ],
      loc: { start: 0, end: 55 },
    });
  });

  it('Single value enum', () => {
    const doc = parse('enum Hello { WORLD }');

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'EnumTypeDefinition',
          name: nameNode('Hello', { start: 5, end: 10 }),
          description: undefined,
          directives: [],
          values: [enumValueNode('WORLD', { start: 13, end: 18 })],
          loc: { start: 0, end: 20 },
        },
      ],
      loc: { start: 0, end: 20 },
    });
  });

  it('Double value enum', () => {
    const doc = parse('enum Hello { WO, RLD }');

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'EnumTypeDefinition',
          name: nameNode('Hello', { start: 5, end: 10 }),
          description: undefined,
          directives: [],
          values: [
            enumValueNode('WO', { start: 13, end: 15 }),
            enumValueNode('RLD', { start: 17, end: 20 }),
          ],
          loc: { start: 0, end: 22 },
        },
      ],
      loc: { start: 0, end: 22 },
    });
  });

  it('Simple interface', () => {
    const doc = parse(dedent`
      interface Hello {
        world: String
      }
    `);

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'InterfaceTypeDefinition',
          name: nameNode('Hello', { start: 10, end: 15 }),
          description: undefined,
          interfaces: [],
          directives: [],
          fields: [
            fieldNode(
              nameNode('world', { start: 20, end: 25 }),
              typeNode('String', { start: 27, end: 33 }),
              { start: 20, end: 33 },
            ),
          ],
          loc: { start: 0, end: 35 },
        },
      ],
      loc: { start: 0, end: 36 },
    });
  });

  it('Simple field with arg', () => {
    const doc = parse(dedent`
      type Hello {
        world(flag: Boolean): String
      }
    `);

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ObjectTypeDefinition',
          name: nameNode('Hello', { start: 5, end: 10 }),
          description: undefined,
          interfaces: [],
          directives: [],
          fields: [
            fieldNodeWithArgs(
              nameNode('world', { start: 15, end: 20 }),
              typeNode('String', { start: 37, end: 43 }),
              [
                inputValueNode(
                  nameNode('flag', { start: 21, end: 25 }),
                  typeNode('Boolean', { start: 27, end: 34 }),
                  undefined,
                  { start: 21, end: 34 },
                ),
              ],
              { start: 15, end: 43 },
            ),
          ],
          loc: { start: 0, end: 45 },
        },
      ],
      loc: { start: 0, end: 46 },
    });
  });

  it('Simple field with arg with default value', () => {
    const doc = parse(dedent`
      type Hello {
        world(flag: Boolean = true): String
      }
    `);

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ObjectTypeDefinition',
          name: nameNode('Hello', { start: 5, end: 10 }),
          description: undefined,
          interfaces: [],
          directives: [],
          fields: [
            fieldNodeWithArgs(
              nameNode('world', { start: 15, end: 20 }),
              typeNode('String', { start: 44, end: 50 }),
              [
                inputValueNode(
                  nameNode('flag', { start: 21, end: 25 }),
                  typeNode('Boolean', { start: 27, end: 34 }),
                  {
                    kind: 'BooleanValue',
                    value: true,
                    loc: { start: 37, end: 41 },
                  },
                  { start: 21, end: 41 },
                ),
              ],
              { start: 15, end: 50 },
            ),
          ],
          loc: { start: 0, end: 52 },
        },
      ],
      loc: { start: 0, end: 53 },
    });
  });

  it('Simple field with list arg', () => {
    const doc = parse(dedent`
      type Hello {
        world(things: [String]): String
      }
    `);

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ObjectTypeDefinition',
          name: nameNode('Hello', { start: 5, end: 10 }),
          description: undefined,
          interfaces: [],
          directives: [],
          fields: [
            fieldNodeWithArgs(
              nameNode('world', { start: 15, end: 20 }),
              typeNode('String', { start: 40, end: 46 }),
              [
                inputValueNode(
                  nameNode('things', { start: 21, end: 27 }),
                  {
                    kind: 'ListType',
                    type: typeNode('String', { start: 30, end: 36 }),
                    loc: { start: 29, end: 37 },
                  },
                  undefined,
                  { start: 21, end: 37 },
                ),
              ],
              { start: 15, end: 46 },
            ),
          ],
          loc: { start: 0, end: 48 },
        },
      ],
      loc: { start: 0, end: 49 },
    });
  });

  it('Simple field with two args', () => {
    const doc = parse(dedent`
      type Hello {
        world(argOne: Boolean, argTwo: Int): String
      }
    `);

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ObjectTypeDefinition',
          name: nameNode('Hello', { start: 5, end: 10 }),
          description: undefined,
          interfaces: [],
          directives: [],
          fields: [
            fieldNodeWithArgs(
              nameNode('world', { start: 15, end: 20 }),
              typeNode('String', { start: 52, end: 58 }),
              [
                inputValueNode(
                  nameNode('argOne', { start: 21, end: 27 }),
                  typeNode('Boolean', { start: 29, end: 36 }),
                  undefined,
                  { start: 21, end: 36 },
                ),
                inputValueNode(
                  nameNode('argTwo', { start: 38, end: 44 }),
                  typeNode('Int', { start: 46, end: 49 }),
                  undefined,
                  { start: 38, end: 49 },
                ),
              ],
              { start: 15, end: 58 },
            ),
          ],
          loc: { start: 0, end: 60 },
        },
      ],
      loc: { start: 0, end: 61 },
    });
  });

  it('Simple union', () => {
    const doc = parse('union Hello = World');

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'UnionTypeDefinition',
          name: nameNode('Hello', { start: 6, end: 11 }),
          description: undefined,
          directives: [],
          types: [typeNode('World', { start: 14, end: 19 })],
          loc: { start: 0, end: 19 },
        },
      ],
      loc: { start: 0, end: 19 },
    });
  });

  it('Union with two types', () => {
    const doc = parse('union Hello = Wo | Rld');

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'UnionTypeDefinition',
          name: nameNode('Hello', { start: 6, end: 11 }),
          description: undefined,
          directives: [],
          types: [
            typeNode('Wo', { start: 14, end: 16 }),
            typeNode('Rld', { start: 19, end: 22 }),
          ],
          loc: { start: 0, end: 22 },
        },
      ],
      loc: { start: 0, end: 22 },
    });
  });

  it('Union with two types and leading pipe', () => {
    const doc = parse('union Hello = | Wo | Rld');

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'UnionTypeDefinition',
          name: nameNode('Hello', { start: 6, end: 11 }),
          description: undefined,
          directives: [],
          types: [
            typeNode('Wo', { start: 16, end: 18 }),
            typeNode('Rld', { start: 21, end: 24 }),
          ],
          loc: { start: 0, end: 24 },
        },
      ],
      loc: { start: 0, end: 24 },
    });
  });

  it('Union fails with no types', () => {
    expectSyntaxError('union Hello = |').to.deep.equal({
      message: 'Syntax Error: Expected Name, found <EOF>.',
      locations: [{ line: 1, column: 16 }],
    });
  });

  it('Union fails with leading double pipe', () => {
    expectSyntaxError('union Hello = || Wo | Rld').to.deep.equal({
      message: 'Syntax Error: Expected Name, found "|".',
      locations: [{ line: 1, column: 16 }],
    });
  });

  it('Union fails with double pipe', () => {
    expectSyntaxError('union Hello = Wo || Rld').to.deep.equal({
      message: 'Syntax Error: Expected Name, found "|".',
      locations: [{ line: 1, column: 19 }],
    });
  });

  it('Union fails with trailing pipe', () => {
    expectSyntaxError('union Hello = | Wo | Rld |').to.deep.equal({
      message: 'Syntax Error: Expected Name, found <EOF>.',
      locations: [{ line: 1, column: 27 }],
    });
  });

  it('Scalar', () => {
    const doc = parse('scalar Hello');

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'ScalarTypeDefinition',
          name: nameNode('Hello', { start: 7, end: 12 }),
          description: undefined,
          directives: [],
          loc: { start: 0, end: 12 },
        },
      ],
      loc: { start: 0, end: 12 },
    });
  });

  it('Simple input object', () => {
    const doc = parse(`
input Hello {
  world: String
}`);

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'InputObjectTypeDefinition',
          name: nameNode('Hello', { start: 7, end: 12 }),
          description: undefined,
          directives: [],
          fields: [
            inputValueNode(
              nameNode('world', { start: 17, end: 22 }),
              typeNode('String', { start: 24, end: 30 }),
              undefined,
              { start: 17, end: 30 },
            ),
          ],
          loc: { start: 1, end: 32 },
        },
      ],
      loc: { start: 0, end: 32 },
    });
  });

  it('Simple input object with args should fail', () => {
    expectSyntaxError(`
      input Hello {
        world(foo: Int): String
      }
    `).to.deep.equal({
      message: 'Syntax Error: Expected ":", found "(".',
      locations: [{ line: 3, column: 14 }],
    });
  });

  it('Directive definition', () => {
    const body = 'directive @foo on OBJECT | INTERFACE';
    const doc = parse(body);

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'DirectiveDefinition',
          description: undefined,
          name: {
            kind: 'Name',
            value: 'foo',
            loc: { start: 11, end: 14 },
          },
          arguments: [],
          repeatable: false,
          locations: [
            {
              kind: 'Name',
              value: 'OBJECT',
              loc: { start: 18, end: 24 },
            },
            {
              kind: 'Name',
              value: 'INTERFACE',
              loc: { start: 27, end: 36 },
            },
          ],
          loc: { start: 0, end: 36 },
        },
      ],
      loc: { start: 0, end: 36 },
    });
  });

  it('Repeatable directive definition', () => {
    const body = 'directive @foo repeatable on OBJECT | INTERFACE';
    const doc = parse(body);

    expect(toJSONDeep(doc)).to.deep.equal({
      kind: 'Document',
      definitions: [
        {
          kind: 'DirectiveDefinition',
          description: undefined,
          name: {
            kind: 'Name',
            value: 'foo',
            loc: { start: 11, end: 14 },
          },
          arguments: [],
          repeatable: true,
          locations: [
            {
              kind: 'Name',
              value: 'OBJECT',
              loc: { start: 29, end: 35 },
            },
            {
              kind: 'Name',
              value: 'INTERFACE',
              loc: { start: 38, end: 47 },
            },
          ],
          loc: { start: 0, end: 47 },
        },
      ],
      loc: { start: 0, end: 47 },
    });
  });

  it('Directive with incorrect locations', () => {
    expectSyntaxError(
      'directive @foo on FIELD | INCORRECT_LOCATION',
    ).to.deep.equal({
      message: 'Syntax Error: Unexpected Name "INCORRECT_LOCATION".',
      locations: [{ line: 1, column: 27 }],
    });
  });

  it('parses kitchen sink schema', () => {
    expect(() => parse(kitchenSinkSDL)).to.not.throw();
  });

  it('Option: allowLegacySDLEmptyFields supports type with empty fields', () => {
    const body = 'type Hello { }';
    expectSyntaxError(body).to.include({
      message: 'Syntax Error: Expected Name, found "}".',
    });

    const doc = parse(body, { allowLegacySDLEmptyFields: true });
    expect(doc).to.have.deep.nested.property('definitions[0].fields', []);
  });

  it('Option: allowLegacySDLImplementsInterfaces', () => {
    const body = 'type Hello implements Wo rld { field: String }';
    expectSyntaxError(body).to.include({
      message: 'Syntax Error: Unexpected Name "rld".',
    });

    const doc = parse(body, { allowLegacySDLImplementsInterfaces: true });
    expect(
      toJSONDeep(doc),
    ).to.have.deep.nested.property('definitions[0].interfaces', [
      typeNode('Wo', { start: 22, end: 24 }),
      typeNode('rld', { start: 25, end: 28 }),
    ]);
  });
});
