import { expect } from 'chai';
import { describe, it } from 'mocha';
import {
GraphQLDeprecatedDirective,
GraphQLIncludeDirective,
GraphQLOneOfDirective,
GraphQLSkipDirective,
GraphQLSpecifiedByDirective,
} from '../../type/directives';
import { GraphQLSchema } from '../../type/schema';
import { buildSchema } from '../buildASTSchema';
import {
BreakingChangeType,
DangerousChangeType,
findBreakingChanges,
findDangerousChanges,
} from '../findBreakingChanges';
describe('findBreakingChanges', () => {
it('should detect if a type was removed or not', () => {
const oldSchema = buildSchema(`
type Type1
type Type2
`);
const newSchema = buildSchema(`
type Type2
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.TYPE_REMOVED,
description: 'Type1 was removed.',
},
]);
expect(findBreakingChanges(oldSchema, oldSchema)).to.deep.equal([]);
});
it('should detect if a standard scalar was removed', () => {
const oldSchema = buildSchema(`
type Query {
foo: Float
}
`);
const newSchema = buildSchema(`
type Query {
foo: String
}
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.TYPE_REMOVED,
description:
'Standard scalar Float was removed because it is not referenced anymore.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Query.foo changed type from Float to String.',
},
]);
expect(findBreakingChanges(oldSchema, oldSchema)).to.deep.equal([]);
});
it('should detect if a type changed its type', () => {
const oldSchema = buildSchema(`
scalar TypeWasScalarBecomesEnum
interface TypeWasInterfaceBecomesUnion
type TypeWasObjectBecomesInputObject
`);
const newSchema = buildSchema(`
enum TypeWasScalarBecomesEnum
union TypeWasInterfaceBecomesUnion
input TypeWasObjectBecomesInputObject
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.TYPE_CHANGED_KIND,
description:
'TypeWasScalarBecomesEnum changed from a Scalar type to an Enum type.',
},
{
type: BreakingChangeType.TYPE_CHANGED_KIND,
description:
'TypeWasInterfaceBecomesUnion changed from an Interface type to a Union type.',
},
{
type: BreakingChangeType.TYPE_CHANGED_KIND,
description:
'TypeWasObjectBecomesInputObject changed from an Object type to an Input type.',
},
]);
});
it('should detect if a field on a type was deleted or changed type', () => {
const oldSchema = buildSchema(`
type TypeA
type TypeB
interface Type1 {
field1: TypeA
field2: String
field3: String
field4: TypeA
field6: String
field7: [String]
field8: Int
field9: Int!
field10: [Int]!
field11: Int
field12: [Int]
field13: [Int!]
field14: [Int]
field15: [[Int]]
field16: Int!
field17: [Int]
field18: [[Int!]!]
}
`);
const newSchema = buildSchema(`
type TypeA
type TypeB
interface Type1 {
field1: TypeA
field3: Boolean
field4: TypeB
field5: String
field6: [String]
field7: String
field8: Int!
field9: Int
field10: [Int]
field11: [Int]!
field12: [Int!]
field13: [Int]
field14: [[Int]]
field15: [Int]
field16: [Int]!
field17: [Int]!
field18: [[Int!]]
}
`);
const changes = findBreakingChanges(oldSchema, newSchema);
expect(changes).to.deep.equal([
{
type: BreakingChangeType.FIELD_REMOVED,
description: 'Type1.field2 was removed.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Type1.field3 changed type from String to Boolean.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Type1.field4 changed type from TypeA to TypeB.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Type1.field6 changed type from String to [String].',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Type1.field7 changed type from [String] to String.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Type1.field9 changed type from Int! to Int.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Type1.field10 changed type from [Int]! to [Int].',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Type1.field11 changed type from Int to [Int]!.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Type1.field13 changed type from [Int!] to [Int].',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Type1.field14 changed type from [Int] to [[Int]].',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Type1.field15 changed type from [[Int]] to [Int].',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Type1.field16 changed type from Int! to [Int]!.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'Type1.field18 changed type from [[Int!]!] to [[Int!]].',
},
]);
});
it('should detect if fields on input types changed kind or were removed', () => {
const oldSchema = buildSchema(`
input InputType1 {
field1: String
field2: Boolean
field3: [String]
field4: String!
field5: String
field6: [Int]
field7: [Int]!
field8: Int
field9: [Int]
field10: [Int!]
field11: [Int]
field12: [[Int]]
field13: Int!
field14: [[Int]!]
field15: [[Int]!]
}
`);
const newSchema = buildSchema(`
input InputType1 {
field1: Int
field3: String
field4: String
field5: String!
field6: [Int]!
field7: [Int]
field8: [Int]!
field9: [Int!]
field10: [Int]
field11: [[Int]]
field12: [Int]
field13: [Int]!
field14: [[Int]]
field15: [[Int!]!]
}
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.FIELD_REMOVED,
description: 'InputType1.field2 was removed.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'InputType1.field1 changed type from String to Int.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'InputType1.field3 changed type from [String] to String.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'InputType1.field5 changed type from String to String!.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'InputType1.field6 changed type from [Int] to [Int]!.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'InputType1.field8 changed type from Int to [Int]!.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'InputType1.field9 changed type from [Int] to [Int!].',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'InputType1.field11 changed type from [Int] to [[Int]].',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'InputType1.field12 changed type from [[Int]] to [Int].',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description: 'InputType1.field13 changed type from Int! to [Int]!.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description:
'InputType1.field15 changed type from [[Int]!] to [[Int!]!].',
},
]);
});
it('should detect if a required field is added to an input type', () => {
const oldSchema = buildSchema(`
input InputType1 {
field1: String
}
`);
const newSchema = buildSchema(`
input InputType1 {
field1: String
requiredField: Int!
optionalField1: Boolean
optionalField2: Boolean! = false
}
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.REQUIRED_INPUT_FIELD_ADDED,
description:
'A required field requiredField on input type InputType1 was added.',
},
]);
});
it('should detect if a type was removed from a union type', () => {
const oldSchema = buildSchema(`
type Type1
type Type2
type Type3
union UnionType1 = Type1 | Type2
`);
const newSchema = buildSchema(`
type Type1
type Type2
type Type3
union UnionType1 = Type1 | Type3
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.TYPE_REMOVED_FROM_UNION,
description: 'Type2 was removed from union type UnionType1.',
},
]);
});
it('should detect if a value was removed from an enum type', () => {
const oldSchema = buildSchema(`
enum EnumType1 {
VALUE0
VALUE1
VALUE2
}
`);
const newSchema = buildSchema(`
enum EnumType1 {
VALUE0
VALUE2
VALUE3
}
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM,
description: 'VALUE1 was removed from enum type EnumType1.',
},
]);
});
it('should detect if a field argument was removed', () => {
const oldSchema = buildSchema(`
interface Interface1 {
field1(arg1: Boolean, objectArg: String): String
}
type Type1 {
field1(name: String): String
}
`);
const newSchema = buildSchema(`
interface Interface1 {
field1: String
}
type Type1 {
field1: String
}
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.ARG_REMOVED,
description: 'Interface1.field1 arg arg1 was removed.',
},
{
type: BreakingChangeType.ARG_REMOVED,
description: 'Interface1.field1 arg objectArg was removed.',
},
{
type: BreakingChangeType.ARG_REMOVED,
description: 'Type1.field1 arg name was removed.',
},
]);
});
it('should detect if a field argument has changed type', () => {
const oldSchema = buildSchema(`
type Type1 {
field1(
arg1: String
arg2: String
arg3: [String]
arg4: String
arg5: String!
arg6: String!
arg7: [Int]!
arg8: Int
arg9: [Int]
arg10: [Int!]
arg11: [Int]
arg12: [[Int]]
arg13: Int!
arg14: [[Int]!]
arg15: [[Int]!]
): String
}
`);
const newSchema = buildSchema(`
type Type1 {
field1(
arg1: Int
arg2: [String]
arg3: String
arg4: String!
arg5: Int
arg6: Int!
arg7: [Int]
arg8: [Int]!
arg9: [Int!]
arg10: [Int]
arg11: [[Int]]
arg12: [Int]
arg13: [Int]!
arg14: [[Int]]
arg15: [[Int!]!]
): String
}
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'Type1.field1 arg arg1 has changed type from String to Int.',
},
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'Type1.field1 arg arg2 has changed type from String to [String].',
},
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'Type1.field1 arg arg3 has changed type from [String] to String.',
},
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'Type1.field1 arg arg4 has changed type from String to String!.',
},
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'Type1.field1 arg arg5 has changed type from String! to Int.',
},
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'Type1.field1 arg arg6 has changed type from String! to Int!.',
},
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'Type1.field1 arg arg8 has changed type from Int to [Int]!.',
},
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'Type1.field1 arg arg9 has changed type from [Int] to [Int!].',
},
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'Type1.field1 arg arg11 has changed type from [Int] to [[Int]].',
},
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'Type1.field1 arg arg12 has changed type from [[Int]] to [Int].',
},
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'Type1.field1 arg arg13 has changed type from Int! to [Int]!.',
},
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'Type1.field1 arg arg15 has changed type from [[Int]!] to [[Int!]!].',
},
]);
});
it('should detect if a required field argument was added', () => {
const oldSchema = buildSchema(`
type Type1 {
field1(arg1: String): String
}
`);
const newSchema = buildSchema(`
type Type1 {
field1(
arg1: String,
newRequiredArg: String!
newOptionalArg1: Int
newOptionalArg2: Int! = 0
): String
}
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.REQUIRED_ARG_ADDED,
description: 'A required arg newRequiredArg on Type1.field1 was added.',
},
]);
});
it('should not flag args with the same type signature as breaking', () => {
const oldSchema = buildSchema(`
input InputType1 {
field1: String
}
type Type1 {
field1(arg1: Int!, arg2: InputType1): Int
}
`);
const newSchema = buildSchema(`
input InputType1 {
field1: String
}
type Type1 {
field1(arg1: Int!, arg2: InputType1): Int
}
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([]);
});
it('should consider args that move away from NonNull as non-breaking', () => {
const oldSchema = buildSchema(`
type Type1 {
field1(name: String!): String
}
`);
const newSchema = buildSchema(`
type Type1 {
field1(name: String): String
}
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([]);
});
it('should detect interfaces removed from types', () => {
const oldSchema = buildSchema(`
interface Interface1
type Type1 implements Interface1
`);
const newSchema = buildSchema(`
interface Interface1
type Type1
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.IMPLEMENTED_INTERFACE_REMOVED,
description: 'Type1 no longer implements interface Interface1.',
},
]);
});
it('should detect interfaces removed from interfaces', () => {
const oldSchema = buildSchema(`
interface Interface1
interface Interface2 implements Interface1
`);
const newSchema = buildSchema(`
interface Interface1
interface Interface2
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.IMPLEMENTED_INTERFACE_REMOVED,
description: 'Interface2 no longer implements interface Interface1.',
},
]);
});
it('should ignore changes in order of interfaces', () => {
const oldSchema = buildSchema(`
interface FirstInterface
interface SecondInterface
type Type1 implements FirstInterface & SecondInterface
`);
const newSchema = buildSchema(`
interface FirstInterface
interface SecondInterface
type Type1 implements SecondInterface & FirstInterface
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([]);
});
it('should detect all breaking changes', () => {
const oldSchema = buildSchema(`
directive @DirectiveThatIsRemoved on FIELD_DEFINITION
directive @DirectiveThatRemovesArg(arg1: String) on FIELD_DEFINITION
directive @NonNullDirectiveAdded on FIELD_DEFINITION
directive @DirectiveThatWasRepeatable repeatable on FIELD_DEFINITION
directive @DirectiveName on FIELD_DEFINITION | QUERY
type ArgThatChanges {
field1(id: Float): String
}
enum EnumTypeThatLosesAValue {
VALUE0
VALUE1
VALUE2
}
interface Interface1
type TypeThatLooseInterface1 implements Interface1
type TypeInUnion1
type TypeInUnion2
union UnionTypeThatLosesAType = TypeInUnion1 | TypeInUnion2
type TypeThatChangesType
type TypeThatGetsRemoved
interface TypeThatHasBreakingFieldChanges {
field1: String
field2: String
}
`);
const newSchema = buildSchema(`
directive @DirectiveThatRemovesArg on FIELD_DEFINITION
directive @NonNullDirectiveAdded(arg1: Boolean!) on FIELD_DEFINITION
directive @DirectiveThatWasRepeatable on FIELD_DEFINITION
directive @DirectiveName on FIELD_DEFINITION
type ArgThatChanges {
field1(id: String): String
}
enum EnumTypeThatLosesAValue {
VALUE1
VALUE2
}
interface Interface1
type TypeThatLooseInterface1
type TypeInUnion1
type TypeInUnion2
union UnionTypeThatLosesAType = TypeInUnion1
interface TypeThatChangesType
interface TypeThatHasBreakingFieldChanges {
field2: Boolean
}
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.TYPE_REMOVED,
description:
'Standard scalar Float was removed because it is not referenced anymore.',
},
{
type: BreakingChangeType.TYPE_REMOVED,
description: 'TypeThatGetsRemoved was removed.',
},
{
type: BreakingChangeType.ARG_CHANGED_KIND,
description:
'ArgThatChanges.field1 arg id has changed type from Float to String.',
},
{
type: BreakingChangeType.VALUE_REMOVED_FROM_ENUM,
description:
'VALUE0 was removed from enum type EnumTypeThatLosesAValue.',
},
{
type: BreakingChangeType.IMPLEMENTED_INTERFACE_REMOVED,
description:
'TypeThatLooseInterface1 no longer implements interface Interface1.',
},
{
type: BreakingChangeType.TYPE_REMOVED_FROM_UNION,
description:
'TypeInUnion2 was removed from union type UnionTypeThatLosesAType.',
},
{
type: BreakingChangeType.TYPE_CHANGED_KIND,
description:
'TypeThatChangesType changed from an Object type to an Interface type.',
},
{
type: BreakingChangeType.FIELD_REMOVED,
description: 'TypeThatHasBreakingFieldChanges.field1 was removed.',
},
{
type: BreakingChangeType.FIELD_CHANGED_KIND,
description:
'TypeThatHasBreakingFieldChanges.field2 changed type from String to Boolean.',
},
{
type: BreakingChangeType.DIRECTIVE_REMOVED,
description: 'DirectiveThatIsRemoved was removed.',
},
{
type: BreakingChangeType.DIRECTIVE_ARG_REMOVED,
description: 'arg1 was removed from DirectiveThatRemovesArg.',
},
{
type: BreakingChangeType.REQUIRED_DIRECTIVE_ARG_ADDED,
description:
'A required arg arg1 on directive NonNullDirectiveAdded was added.',
},
{
type: BreakingChangeType.DIRECTIVE_REPEATABLE_REMOVED,
description:
'Repeatable flag was removed from DirectiveThatWasRepeatable.',
},
{
type: BreakingChangeType.DIRECTIVE_LOCATION_REMOVED,
description: 'QUERY was removed from DirectiveName.',
},
]);
});
it('should detect if a directive was explicitly removed', () => {
const oldSchema = buildSchema(`
directive @DirectiveThatIsRemoved on FIELD_DEFINITION
directive @DirectiveThatStays on FIELD_DEFINITION
`);
const newSchema = buildSchema(`
directive @DirectiveThatStays on FIELD_DEFINITION
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.DIRECTIVE_REMOVED,
description: 'DirectiveThatIsRemoved was removed.',
},
]);
});
it('should detect if a directive was implicitly removed', () => {
const oldSchema = new GraphQLSchema({});
const newSchema = new GraphQLSchema({
directives: [
GraphQLSkipDirective,
GraphQLIncludeDirective,
GraphQLSpecifiedByDirective,
GraphQLOneOfDirective,
],
});
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.DIRECTIVE_REMOVED,
description: `${GraphQLDeprecatedDirective.name} was removed.`,
},
]);
});
it('should detect if a directive argument was removed', () => {
const oldSchema = buildSchema(`
directive @DirectiveWithArg(arg1: String) on FIELD_DEFINITION
`);
const newSchema = buildSchema(`
directive @DirectiveWithArg on FIELD_DEFINITION
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.DIRECTIVE_ARG_REMOVED,
description: 'arg1 was removed from DirectiveWithArg.',
},
]);
});
it('should detect if an optional directive argument was added', () => {
const oldSchema = buildSchema(`
directive @DirectiveName on FIELD_DEFINITION
`);
const newSchema = buildSchema(`
directive @DirectiveName(
newRequiredArg: String!
newOptionalArg1: Int
newOptionalArg2: Int! = 0
) on FIELD_DEFINITION
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.REQUIRED_DIRECTIVE_ARG_ADDED,
description:
'A required arg newRequiredArg on directive DirectiveName was added.',
},
]);
});
it('should detect removal of repeatable flag', () => {
const oldSchema = buildSchema(`
directive @DirectiveName repeatable on OBJECT
`);
const newSchema = buildSchema(`
directive @DirectiveName on OBJECT
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.DIRECTIVE_REPEATABLE_REMOVED,
description: 'Repeatable flag was removed from DirectiveName.',
},
]);
});
it('should detect locations removed from a directive', () => {
const oldSchema = buildSchema(`
directive @DirectiveName on FIELD_DEFINITION | QUERY
`);
const newSchema = buildSchema(`
directive @DirectiveName on FIELD_DEFINITION
`);
expect(findBreakingChanges(oldSchema, newSchema)).to.deep.equal([
{
type: BreakingChangeType.DIRECTIVE_LOCATION_REMOVED,
description: 'QUERY was removed from DirectiveName.',
},
]);
});
});
describe('findDangerousChanges', () => {
it('should detect if a defaultValue changed on an argument', () => {
const oldSDL = `
input Input1 {
innerInputArray: [Input2]
}
input Input2 {
arrayField: [Int]
}
type Type1 {
field1(
withDefaultValue: String = "TO BE DELETED"
stringArg: String = "test"
emptyArray: [Int!] = []
valueArray: [[String]] = [["a", "b"], ["c"]]
complexObject: Input1 = {
innerInputArray: [{ arrayField: [1, 2, 3] }]
}
): String
}
`;
const oldSchema = buildSchema(oldSDL);
const copyOfOldSchema = buildSchema(oldSDL);
expect(findDangerousChanges(oldSchema, copyOfOldSchema)).to.deep.equal([]);
const newSchema = buildSchema(`
input Input1 {
innerInputArray: [Input2]
}
input Input2 {
arrayField: [Int]
}
type Type1 {
field1(
withDefaultValue: String
stringArg: String = "Test"
emptyArray: [Int!] = [7]
valueArray: [[String]] = [["b", "a"], ["d"]]
complexObject: Input1 = {
innerInputArray: [{ arrayField: [3, 2, 1] }]
}
): String
}
`);
expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([
{
type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
description:
'Type1.field1 arg withDefaultValue defaultValue was removed.',
},
{
type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
description:
'Type1.field1 arg stringArg has changed defaultValue from "test" to "Test".',
},
{
type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
description:
'Type1.field1 arg emptyArray has changed defaultValue from [] to [7].',
},
{
type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
description:
'Type1.field1 arg valueArray has changed defaultValue from [["a", "b"], ["c"]] to [["b", "a"], ["d"]].',
},
{
type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
description:
'Type1.field1 arg complexObject has changed defaultValue from {innerInputArray: [{arrayField: [1, 2, 3]}]} to {innerInputArray: [{arrayField: [3, 2, 1]}]}.',
},
]);
});
it('should ignore changes in field order of defaultValue', () => {
const oldSchema = buildSchema(`
input Input1 {
a: String
b: String
c: String
}
type Type1 {
field1(
arg1: Input1 = { a: "a", b: "b", c: "c" }
): String
}
`);
const newSchema = buildSchema(`
input Input1 {
a: String
b: String
c: String
}
type Type1 {
field1(
arg1: Input1 = { c: "c", b: "b", a: "a" }
): String
}
`);
expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([]);
});
it('should ignore changes in field definitions order', () => {
const oldSchema = buildSchema(`
input Input1 {
a: String
b: String
c: String
}
type Type1 {
field1(
arg1: Input1 = { a: "a", b: "b", c: "c" }
): String
}
`);
const newSchema = buildSchema(`
input Input1 {
c: String
b: String
a: String
}
type Type1 {
field1(
arg1: Input1 = { a: "a", b: "b", c: "c" }
): String
}
`);
expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([]);
});
it('should detect if a value was added to an enum type', () => {
const oldSchema = buildSchema(`
enum EnumType1 {
VALUE0
VALUE1
}
`);
const newSchema = buildSchema(`
enum EnumType1 {
VALUE0
VALUE1
VALUE2
}
`);
expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([
{
type: DangerousChangeType.VALUE_ADDED_TO_ENUM,
description: 'VALUE2 was added to enum type EnumType1.',
},
]);
});
it('should detect interfaces added to types', () => {
const oldSchema = buildSchema(`
interface OldInterface
interface NewInterface
type Type1 implements OldInterface
`);
const newSchema = buildSchema(`
interface OldInterface
interface NewInterface
type Type1 implements OldInterface & NewInterface
`);
expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([
{
type: DangerousChangeType.IMPLEMENTED_INTERFACE_ADDED,
description: 'NewInterface added to interfaces implemented by Type1.',
},
]);
});
it('should detect interfaces added to interfaces', () => {
const oldSchema = buildSchema(`
interface OldInterface
interface NewInterface
interface Interface1 implements OldInterface
`);
const newSchema = buildSchema(`
interface OldInterface
interface NewInterface
interface Interface1 implements OldInterface & NewInterface
`);
expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([
{
type: DangerousChangeType.IMPLEMENTED_INTERFACE_ADDED,
description:
'NewInterface added to interfaces implemented by Interface1.',
},
]);
});
it('should detect if a type was added to a union type', () => {
const oldSchema = buildSchema(`
type Type1
type Type2
union UnionType1 = Type1
`);
const newSchema = buildSchema(`
type Type1
type Type2
union UnionType1 = Type1 | Type2
`);
expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([
{
type: DangerousChangeType.TYPE_ADDED_TO_UNION,
description: 'Type2 was added to union type UnionType1.',
},
]);
});
it('should detect if an optional field was added to an input', () => {
const oldSchema = buildSchema(`
input InputType1 {
field1: String
}
`);
const newSchema = buildSchema(`
input InputType1 {
field1: String
field2: Int
}
`);
expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([
{
type: DangerousChangeType.OPTIONAL_INPUT_FIELD_ADDED,
description:
'An optional field field2 on input type InputType1 was added.',
},
]);
});
it('should find all dangerous changes', () => {
const oldSchema = buildSchema(`
enum EnumType1 {
VALUE0
VALUE1
}
type Type1 {
field1(argThatChangesDefaultValue: String = "test"): String
}
interface Interface1
type TypeThatGainsInterface1
type TypeInUnion1
union UnionTypeThatGainsAType = TypeInUnion1
`);
const newSchema = buildSchema(`
enum EnumType1 {
VALUE0
VALUE1
VALUE2
}
type Type1 {
field1(argThatChangesDefaultValue: String = "Test"): String
}
interface Interface1
type TypeThatGainsInterface1 implements Interface1
type TypeInUnion1
type TypeInUnion2
union UnionTypeThatGainsAType = TypeInUnion1 | TypeInUnion2
`);
expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([
{
type: DangerousChangeType.VALUE_ADDED_TO_ENUM,
description: 'VALUE2 was added to enum type EnumType1.',
},
{
type: DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE,
description:
'Type1.field1 arg argThatChangesDefaultValue has changed defaultValue from "test" to "Test".',
},
{
type: DangerousChangeType.IMPLEMENTED_INTERFACE_ADDED,
description:
'Interface1 added to interfaces implemented by TypeThatGainsInterface1.',
},
{
type: DangerousChangeType.TYPE_ADDED_TO_UNION,
description:
'TypeInUnion2 was added to union type UnionTypeThatGainsAType.',
},
]);
});
it('should detect if an optional field argument was added', () => {
const oldSchema = buildSchema(`
type Type1 {
field1(arg1: String): String
}
`);
const newSchema = buildSchema(`
type Type1 {
field1(arg1: String, arg2: String): String
}
`);
expect(findDangerousChanges(oldSchema, newSchema)).to.deep.equal([
{
type: DangerousChangeType.OPTIONAL_ARG_ADDED,
description: 'An optional arg arg2 on Type1.field1 was added.',
},
]);
});
});