import gql from 'graphql-tag';
import { cloneDeep } from 'lodash';

import { shouldInclude, hasDirectives } from '../directives';
import { getQueryDefinition } from '../getFromAST';

describe('hasDirective', () => {
  it('should allow searching the ast for a directive', () => {
    const query = gql`
      query Simple {
        field @live
      }
    `;
    expect(hasDirectives(['live'], query)).toBe(true);
    expect(hasDirectives(['defer'], query)).toBe(false);
  });
  it('works for all operation types', () => {
    const query = gql`
      {
        field @live {
          subField {
            hello @live
          }
        }
      }
    `;

    const mutation = gql`
      mutation Directive {
        mutate {
          field {
            subField {
              hello @live
            }
          }
        }
      }
    `;

    const subscription = gql`
      subscription LiveDirective {
        sub {
          field {
            subField {
              hello @live
            }
          }
        }
      }
    `;

    [query, mutation, subscription].forEach(x => {
      expect(hasDirectives(['live'], x)).toBe(true);
      expect(hasDirectives(['defer'], x)).toBe(false);
    });
  });
  it('works for simple fragments', () => {
    const query = gql`
      query Simple {
        ...fieldFragment
      }

      fragment fieldFragment on Field {
        foo @live
      }
    `;
    expect(hasDirectives(['live'], query)).toBe(true);
    expect(hasDirectives(['defer'], query)).toBe(false);
  });
  it('works for nested fragments', () => {
    const query = gql`
      query Simple {
        ...fieldFragment1
      }

      fragment fieldFragment1 on Field {
        bar {
          baz {
            ...nestedFragment
          }
        }
      }

      fragment nestedFragment on Field {
        foo @live
      }
    `;
    expect(hasDirectives(['live'], query)).toBe(true);
    expect(hasDirectives(['defer'], query)).toBe(false);
  });
});

describe('shouldInclude', () => {
  it('should should not include a skipped field', () => {
    const query = gql`
      query {
        fortuneCookie @skip(if: true)
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];
    expect(!shouldInclude(field, {})).toBe(true);
  });

  it('should include an included field', () => {
    const query = gql`
      query {
        fortuneCookie @include(if: true)
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];
    expect(shouldInclude(field, {})).toBe(true);
  });

  it('should not include a not include: false field', () => {
    const query = gql`
      query {
        fortuneCookie @include(if: false)
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];
    expect(!shouldInclude(field, {})).toBe(true);
  });

  it('should include a skip: false field', () => {
    const query = gql`
      query {
        fortuneCookie @skip(if: false)
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];
    expect(shouldInclude(field, {})).toBe(true);
  });

  it('should not include a field if skip: true and include: true', () => {
    const query = gql`
      query {
        fortuneCookie @skip(if: true) @include(if: true)
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];
    expect(!shouldInclude(field, {})).toBe(true);
  });

  it('should not include a field if skip: true and include: false', () => {
    const query = gql`
      query {
        fortuneCookie @skip(if: true) @include(if: false)
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];
    expect(!shouldInclude(field, {})).toBe(true);
  });

  it('should include a field if skip: false and include: true', () => {
    const query = gql`
      query {
        fortuneCookie @skip(if: false) @include(if: true)
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];
    expect(shouldInclude(field, {})).toBe(true);
  });

  it('should not include a field if skip: false and include: false', () => {
    const query = gql`
      query {
        fortuneCookie @skip(if: false) @include(if: false)
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];
    expect(!shouldInclude(field, {})).toBe(true);
  });

  it('should leave the original query unmodified', () => {
    const query = gql`
      query {
        fortuneCookie @skip(if: false) @include(if: false)
      }
    `;
    const queryClone = cloneDeep(query);
    const field = getQueryDefinition(query).selectionSet.selections[0];
    shouldInclude(field, {});
    expect(query).toEqual(queryClone);
  });

  it('does not throw an error on an unsupported directive', () => {
    const query = gql`
      query {
        fortuneCookie @dosomething(if: true)
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];

    expect(() => {
      shouldInclude(field, {});
    }).not.toThrow();
  });

  it('throws an error on an invalid argument for the skip directive', () => {
    const query = gql`
      query {
        fortuneCookie @skip(nothing: true)
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];

    expect(() => {
      shouldInclude(field, {});
    }).toThrow();
  });

  it('throws an error on an invalid argument for the include directive', () => {
    const query = gql`
      query {
        fortuneCookie @include(nothing: true)
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];

    expect(() => {
      shouldInclude(field, {});
    }).toThrow();
  });

  it('throws an error on an invalid variable name within a directive argument', () => {
    const query = gql`
      query {
        fortuneCookie @include(if: $neverDefined)
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];
    expect(() => {
      shouldInclude(field, {});
    }).toThrow();
  });

  it('evaluates variables on skip fields', () => {
    const query = gql`
      query($shouldSkip: Boolean) {
        fortuneCookie @skip(if: $shouldSkip)
      }
    `;
    const variables = {
      shouldSkip: true,
    };
    const field = getQueryDefinition(query).selectionSet.selections[0];
    expect(!shouldInclude(field, variables)).toBe(true);
  });

  it('evaluates variables on include fields', () => {
    const query = gql`
      query($shouldSkip: Boolean) {
        fortuneCookie @include(if: $shouldInclude)
      }
    `;
    const variables = {
      shouldInclude: false,
    };
    const field = getQueryDefinition(query).selectionSet.selections[0];
    expect(!shouldInclude(field, variables)).toBe(true);
  });

  it('throws an error if the value of the argument is not a variable or boolean', () => {
    const query = gql`
      query {
        fortuneCookie @include(if: "string")
      }
    `;
    const field = getQueryDefinition(query).selectionSet.selections[0];
    expect(() => {
      shouldInclude(field, {});
    }).toThrow();
  });
});