diff --git a/src/Utils/FindBreakingChanges.php b/src/Utils/FindBreakingChanges.php index 631fd69..ecad9f8 100644 --- a/src/Utils/FindBreakingChanges.php +++ b/src/Utils/FindBreakingChanges.php @@ -21,14 +21,14 @@ use GraphQL\Type\Schema; class FindBreakingChanges { - const BREAKING_CHANGE_FIELD_CHANGED = 'FIELD_CHANGED_KIND'; + const BREAKING_CHANGE_FIELD_CHANGED_KIND = 'FIELD_CHANGED_KIND'; const BREAKING_CHANGE_FIELD_REMOVED = 'FIELD_REMOVED'; - const BREAKING_CHANGE_TYPE_CHANGED = 'TYPE_CHANGED_KIND'; + const BREAKING_CHANGE_TYPE_CHANGED_KIND = 'TYPE_CHANGED_KIND'; const BREAKING_CHANGE_TYPE_REMOVED = 'TYPE_REMOVED'; const BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION = 'TYPE_REMOVED_FROM_UNION'; const BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM = 'VALUE_REMOVED_FROM_ENUM'; const BREAKING_CHANGE_ARG_REMOVED = 'ARG_REMOVED'; - const BREAKING_CHANGE_ARG_CHANGED = 'ARG_CHANGED_KIND'; + const BREAKING_CHANGE_ARG_CHANGED_KIND = 'ARG_CHANGED_KIND'; const BREAKING_CHANGE_NON_NULL_ARG_ADDED = 'NON_NULL_ARG_ADDED'; const BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED = 'NON_NULL_INPUT_FIELD_ADDED'; const BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT = 'INTERFACE_REMOVED_FROM_OBJECT'; @@ -37,8 +37,9 @@ class FindBreakingChanges const BREAKING_CHANGE_DIRECTIVE_LOCATION_REMOVED = 'DIRECTIVE_LOCATION_REMOVED'; const BREAKING_CHANGE_NON_NULL_DIRECTIVE_ARG_ADDED = 'NON_NULL_DIRECTIVE_ARG_ADDED'; - const DANGEROUS_CHANGE_ARG_DEFAULT_VALUE = 'ARG_DEFAULT_VALUE_CHANGE'; + const DANGEROUS_CHANGE_ARG_DEFAULT_VALUE_CHANGED = 'ARG_DEFAULT_VALUE_CHANGE'; const DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM = 'VALUE_ADDED_TO_ENUM'; + const DANGEROUS_CHANGE_INTERFACE_ADDED_TO_OBJECT = 'INTERFACE_ADDED_TO_OBJECT'; const DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION = 'TYPE_ADDED_TO_UNION'; const DANGEROUS_CHANGE_NULLABLE_INPUT_FIELD_ADDED = 'NULLABLE_INPUT_FIELD_ADDED'; const DANGEROUS_CHANGE_NULLABLE_ARG_ADDED = 'NULLABLE_ARG_ADDED'; @@ -78,6 +79,7 @@ class FindBreakingChanges return array_merge( self::findArgChanges($oldSchema, $newSchema)['dangerousChanges'], self::findValuesAddedToEnums($oldSchema, $newSchema), + self::findInterfacesAddedToObjectTypes($oldSchema, $newSchema), self::findTypesAddedToUnions($oldSchema, $newSchema), self::findFieldsThatChangedTypeOnInputObjectTypes($oldSchema, $newSchema)['dangerousChanges'] ); @@ -90,20 +92,21 @@ class FindBreakingChanges * @return array */ public static function findRemovedTypes( - Schema $oldSchema, Schema $newSchema - ) - { + Schema $oldSchema, + Schema $newSchema + ) { $oldTypeMap = $oldSchema->getTypeMap(); $newTypeMap = $newSchema->getTypeMap(); $breakingChanges = []; - foreach ($oldTypeMap as $typeName => $typeDefinition) { + foreach (array_keys($oldTypeMap) as $typeName) { if (!isset($newTypeMap[$typeName])) { - $breakingChanges[] = - ['type' => self::BREAKING_CHANGE_TYPE_REMOVED, 'description' => "${typeName} was removed."]; + $breakingChanges[] = [ + 'type' => self::BREAKING_CHANGE_TYPE_REMOVED, + 'description' => "${typeName} was removed." + ]; } } - return $breakingChanges; } @@ -114,28 +117,27 @@ class FindBreakingChanges * @return array */ public static function findTypesThatChangedKind( - Schema $oldSchema, Schema $newSchema - ) - { + Schema $oldSchema, + Schema $newSchema + ) { $oldTypeMap = $oldSchema->getTypeMap(); $newTypeMap = $newSchema->getTypeMap(); $breakingChanges = []; - foreach ($oldTypeMap as $typeName => $typeDefinition) { + foreach ($oldTypeMap as $typeName => $oldType) { if (!isset($newTypeMap[$typeName])) { continue; } - $newTypeDefinition = $newTypeMap[$typeName]; - if (!($typeDefinition instanceof $newTypeDefinition)) { - $oldTypeKindName = self::typeKindName($typeDefinition); - $newTypeKindName = self::typeKindName($newTypeDefinition); + $newType = $newTypeMap[$typeName]; + if (!($oldType instanceof $newType)) { + $oldTypeKindName = self::typeKindName($oldType); + $newTypeKindName = self::typeKindName($newType); $breakingChanges[] = [ - 'type' => self::BREAKING_CHANGE_TYPE_CHANGED, + 'type' => self::BREAKING_CHANGE_TYPE_CHANGED_KIND, 'description' => "${typeName} changed from ${oldTypeKindName} to ${newTypeKindName}." ]; } } - return $breakingChanges; } @@ -148,59 +150,63 @@ class FindBreakingChanges * @return array */ public static function findArgChanges( - Schema $oldSchema, Schema $newSchema - ) - { + Schema $oldSchema, + Schema $newSchema + ) { $oldTypeMap = $oldSchema->getTypeMap(); $newTypeMap = $newSchema->getTypeMap(); $breakingChanges = []; $dangerousChanges = []; - foreach ($oldTypeMap as $oldTypeName => $oldTypeDefinition) { - $newTypeDefinition = isset($newTypeMap[$oldTypeName]) ? $newTypeMap[$oldTypeName] : null; + + foreach ($oldTypeMap as $typeName => $oldType) { + $newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null; if ( - !($oldTypeDefinition instanceof ObjectType || $oldTypeDefinition instanceof InterfaceType) || - !($newTypeDefinition instanceof ObjectType || $newTypeDefinition instanceof InterfaceType) || - !($newTypeDefinition instanceof $oldTypeDefinition) + !($oldType instanceof ObjectType || $oldType instanceof InterfaceType) || + !($newType instanceof ObjectType || $newType instanceof InterfaceType) || + !($newType instanceof $oldType) ) { continue; } - $oldTypeFields = $oldTypeDefinition->getFields(); - $newTypeFields = $newTypeDefinition->getFields(); + $oldTypeFields = $oldType->getFields(); + $newTypeFields = $newType->getFields(); - foreach ($oldTypeFields as $fieldName => $fieldDefinition) { + foreach ($oldTypeFields as $fieldName => $oldField) { if (!isset($newTypeFields[$fieldName])) { continue; } - foreach ($fieldDefinition->args as $oldArgDef) { + foreach ($oldField->args as $oldArgDef) { $newArgs = $newTypeFields[$fieldName]->args; $newArgDef = Utils::find( - $newArgs, function ($arg) use ($oldArgDef) { - return $arg->name === $oldArgDef->name; - } + $newArgs, + function ($arg) use ($oldArgDef) { + return $arg->name === $oldArgDef->name; + } ); if (!$newArgDef) { - $argName = $oldArgDef->name; $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_ARG_REMOVED, - 'description' => "${oldTypeName}->${fieldName} arg ${argName} was removed" + 'description' => "${typeName}.${fieldName} arg {$oldArgDef->name} was removed" ]; } else { - $isSafe = self::isChangeSafeForInputObjectFieldOrFieldArg($oldArgDef->getType(), $newArgDef->getType()); + $isSafe = self::isChangeSafeForInputObjectFieldOrFieldArg( + $oldArgDef->getType(), + $newArgDef->getType() + ); $oldArgType = $oldArgDef->getType(); $oldArgName = $oldArgDef->name; if (!$isSafe) { $newArgType = $newArgDef->getType(); $breakingChanges[] = [ - 'type' => self::BREAKING_CHANGE_ARG_CHANGED, - 'description' => "${oldTypeName}->${fieldName} arg ${oldArgName} has changed type from ${oldArgType} to ${newArgType}." + 'type' => self::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => "${typeName}.${fieldName} arg ${oldArgName} has changed type from ${oldArgType} to ${newArgType}" ]; } elseif ($oldArgDef->defaultValueExists() && $oldArgDef->defaultValue !== $newArgDef->defaultValue) { $dangerousChanges[] = [ - 'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE, - 'description' => "${oldTypeName}->${fieldName} arg ${oldArgName} has changed defaultValue" + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE_CHANGED, + 'description' => "${typeName}.${fieldName} arg ${oldArgName} has changed defaultValue" ]; } } @@ -208,23 +214,24 @@ class FindBreakingChanges foreach ($newTypeFields[$fieldName]->args as $newArgDef) { $oldArgs = $oldTypeFields[$fieldName]->args; $oldArgDef = Utils::find( - $oldArgs, function ($arg) use ($newArgDef) { + $oldArgs, + function ($arg) use ($newArgDef) { return $arg->name === $newArgDef->name; } ); if (!$oldArgDef) { - $newTypeName = $newTypeDefinition->name; + $newTypeName = $newType->name; $newArgName = $newArgDef->name; if ($newArgDef->getType() instanceof NonNull) { $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_NON_NULL_ARG_ADDED, - 'description' => "A non-null arg ${newArgName} on ${newTypeName}->${fieldName} was added." + 'description' => "A non-null arg ${newArgName} on ${newTypeName}.${fieldName} was added" ]; } else { $dangerousChanges[] = [ 'type' => self::DANGEROUS_CHANGE_NULLABLE_ARG_ADDED, - 'description' => "A nullable arg ${newArgName} on ${newTypeName}->${fieldName} was added." + 'description' => "A nullable arg ${newArgName} on ${newTypeName}.${fieldName} was added" ]; } } @@ -233,7 +240,10 @@ class FindBreakingChanges } } - return ['breakingChanges' => $breakingChanges, 'dangerousChanges' => $dangerousChanges]; + return [ + 'breakingChanges' => $breakingChanges, + 'dangerousChanges' => $dangerousChanges, + ]; } /** @@ -261,14 +271,10 @@ class FindBreakingChanges throw new \TypeError('unknown type ' . $type->name); } - /** - * @param Schema $oldSchema - * @param Schema $newSchema - * - * @return array - */ - public static function findFieldsThatChangedTypeOnObjectOrInterfaceTypes(Schema $oldSchema, Schema $newSchema) - { + public static function findFieldsThatChangedTypeOnObjectOrInterfaceTypes( + Schema $oldSchema, + Schema $newSchema + ) { $oldTypeMap = $oldSchema->getTypeMap(); $newTypeMap = $newSchema->getTypeMap(); @@ -282,20 +288,34 @@ class FindBreakingChanges ) { continue; } + $oldTypeFieldsDef = $oldType->getFields(); $newTypeFieldsDef = $newType->getFields(); foreach ($oldTypeFieldsDef as $fieldName => $fieldDefinition) { + // Check if the field is missing on the type in the new schema. if (!isset($newTypeFieldsDef[$fieldName])) { - $breakingChanges[] = ['type' => self::BREAKING_CHANGE_FIELD_REMOVED, 'description' => "${typeName}->${fieldName} was removed."]; + $breakingChanges[] = [ + 'type' => self::BREAKING_CHANGE_FIELD_REMOVED, + 'description' => "${typeName}.${fieldName} was removed." + ]; } else { $oldFieldType = $oldTypeFieldsDef[$fieldName]->getType(); - $newfieldType = $newTypeFieldsDef[$fieldName]->getType(); - $isSafe = self::isChangeSafeForObjectOrInterfaceField($oldFieldType, $newfieldType); + $newFieldType = $newTypeFieldsDef[$fieldName]->getType(); + $isSafe = self::isChangeSafeForObjectOrInterfaceField( + $oldFieldType, + $newFieldType + ); if (!$isSafe) { - - $oldFieldTypeString = $oldFieldType instanceof NamedType ? $oldFieldType->name : $oldFieldType; - $newFieldTypeString = $newfieldType instanceof NamedType ? $newfieldType->name : $newfieldType; - $breakingChanges[] = ['type' => self::BREAKING_CHANGE_FIELD_CHANGED, 'description' => "${typeName}->${fieldName} changed type from ${oldFieldTypeString} to ${newFieldTypeString}."]; + $oldFieldTypeString = $oldFieldType instanceof NamedType + ? $oldFieldType->name + : $oldFieldType; + $newFieldTypeString = $newFieldType instanceof NamedType + ? $newFieldType->name + : $newFieldType; + $breakingChanges[] = [ + 'type' => self::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => "${typeName}.${fieldName} changed type from ${oldFieldTypeString} to ${newFieldTypeString}." + ]; } } } @@ -303,16 +323,10 @@ class FindBreakingChanges return $breakingChanges; } - /** - * @param Schema $oldSchema - * @param Schema $newSchema - * - * @return array - */ public static function findFieldsThatChangedTypeOnInputObjectTypes( - Schema $oldSchema, Schema $newSchema - ) - { + Schema $oldSchema, + Schema $newSchema + ) { $oldTypeMap = $oldSchema->getTypeMap(); $newTypeMap = $newSchema->getTypeMap(); @@ -323,24 +337,33 @@ class FindBreakingChanges if (!($oldType instanceof InputObjectType) || !($newType instanceof InputObjectType)) { continue; } + $oldTypeFieldsDef = $oldType->getFields(); $newTypeFieldsDef = $newType->getFields(); - foreach ($oldTypeFieldsDef as $fieldName => $fieldDefinition) { + foreach (array_keys($oldTypeFieldsDef) as $fieldName) { if (!isset($newTypeFieldsDef[$fieldName])) { $breakingChanges[] = [ 'type' => self::BREAKING_CHANGE_FIELD_REMOVED, - 'description' => "${typeName}->${fieldName} was removed." + 'description' => "${typeName}.${fieldName} was removed." ]; } else { $oldFieldType = $oldTypeFieldsDef[$fieldName]->getType(); $newFieldType = $newTypeFieldsDef[$fieldName]->getType(); - $isSafe = self::isChangeSafeForInputObjectFieldOrFieldArg($oldFieldType, $newFieldType); + + $isSafe = self::isChangeSafeForInputObjectFieldOrFieldArg( + $oldFieldType, + $newFieldType + ); if (!$isSafe) { - $oldFieldTypeString = $oldFieldType instanceof NamedType ? $oldFieldType->name : $oldFieldType; - $newFieldTypeString = $newFieldType instanceof NamedType ? $newFieldType->name : $newFieldType; + $oldFieldTypeString = $oldFieldType instanceof NamedType + ? $oldFieldType->name + : $oldFieldType; + $newFieldTypeString = $newFieldType instanceof NamedType + ? $newFieldType->name + : $newFieldType; $breakingChanges[] = [ - 'type' => self::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => "${typeName}->${fieldName} changed type from ${oldFieldTypeString} to ${newFieldTypeString}."]; + 'type' => self::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => "${typeName}.${fieldName} changed type from ${oldFieldTypeString} to ${newFieldTypeString}."]; } } } @@ -363,35 +386,42 @@ class FindBreakingChanges } } - return ['breakingChanges' => $breakingChanges, 'dangerousChanges' => $dangerousChanges]; + return [ + 'breakingChanges' => $breakingChanges, + 'dangerousChanges' => $dangerousChanges, + ]; } private static function isChangeSafeForObjectOrInterfaceField( - Type $oldType, Type $newType - ) - { + Type $oldType, + Type $newType + ) { if ($oldType instanceof NamedType) { - // if they're both named types, see if their names are equivalent - return ($newType instanceof NamedType && $oldType->name === $newType->name) + return ( + // if they're both named types, see if their names are equivalent + ($newType instanceof NamedType && $oldType->name === $newType->name) || // moving from nullable to non-null of the same underlying type is safe - || ($newType instanceof NonNull - && self::isChangeSafeForObjectOrInterfaceField( - $oldType, $newType->getWrappedType() - )); + ($newType instanceof NonNull && + self::isChangeSafeForObjectOrInterfaceField($oldType, $newType->getWrappedType()) + ) + ); } elseif ($oldType instanceof ListOfType) { - // if they're both lists, make sure the underlying types are compatible - return ($newType instanceof ListOfType && + return ( + // if they're both lists, make sure the underlying types are compatible + ($newType instanceof ListOfType && self::isChangeSafeForObjectOrInterfaceField($oldType->getWrappedType(), $newType->getWrappedType())) || // moving from nullable to non-null of the same underlying type is safe ($newType instanceof NonNull && - self::isChangeSafeForObjectOrInterfaceField($oldType, $newType->getWrappedType())); + self::isChangeSafeForObjectOrInterfaceField($oldType, $newType->getWrappedType())) + ); } elseif ($oldType instanceof NonNull) { // if they're both non-null, make sure the underlying types are compatible - return $newType instanceof NonNull && - self::isChangeSafeForObjectOrInterfaceField($oldType->getWrappedType(), $newType->getWrappedType()); + return ( + $newType instanceof NonNull && + self::isChangeSafeForObjectOrInterfaceField($oldType->getWrappedType(), $newType->getWrappedType()) + ); } - return false; } @@ -406,15 +436,24 @@ class FindBreakingChanges Type $newType ) { if ($oldType instanceof NamedType) { + // if they're both named types, see if their names are equivalent return $newType instanceof NamedType && $oldType->name === $newType->name; } elseif ($oldType instanceof ListOfType) { + // if they're both lists, make sure the underlying types are compatible return $newType instanceof ListOfType && self::isChangeSafeForInputObjectFieldOrFieldArg($oldType->getWrappedType(), $newType->getWrappedType()); } elseif ($oldType instanceof NonNull) { return ( - $newType instanceof NonNull && self::isChangeSafeForInputObjectFieldOrFieldArg($oldType->getWrappedType(), $newType->getWrappedType()) - ) || ( - !($newType instanceof NonNull) && self::isChangeSafeForInputObjectFieldOrFieldArg($oldType->getWrappedType(), $newType) - ); + // if they're both non-null, make sure the underlying types are + // compatible + ($newType instanceof NonNull && + self::isChangeSafeForInputObjectFieldOrFieldArg( + $oldType->getWrappedType(), + $newType->getWrappedType() + )) || + // moving from non-null to nullable of the same underlying type is safe + (!($newType instanceof NonNull) && + self::isChangeSafeForInputObjectFieldOrFieldArg($oldType->getWrappedType(), $newType)) + ); } return false; } @@ -426,9 +465,9 @@ class FindBreakingChanges * @return array */ public static function findTypesRemovedFromUnions( - Schema $oldSchema, Schema $newSchema - ) - { + Schema $oldSchema, + Schema $newSchema + ) { $oldTypeMap = $oldSchema->getTypeMap(); $newTypeMap = $newSchema->getTypeMap(); @@ -444,8 +483,10 @@ class FindBreakingChanges } foreach ($oldType->getTypes() as $type) { if (!isset($typeNamesInNewUnion[$type->name])) { - $missingTypeName = $type->name; - $typesRemovedFromUnion[] = ['type' => self::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION, 'description' => "${missingTypeName} was removed from union type ${typeName}."]; + $typesRemovedFromUnion[] = [ + 'type' => self::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION, + 'description' => "{$type->name} was removed from union type ${typeName}.", + ]; } } } @@ -459,14 +500,13 @@ class FindBreakingChanges * @return array */ public static function findTypesAddedToUnions( - Schema $oldSchema, Schema $newSchema - ) - { + Schema $oldSchema, + Schema $newSchema + ) { $oldTypeMap = $oldSchema->getTypeMap(); $newTypeMap = $newSchema->getTypeMap(); $typesAddedToUnion = []; - foreach ($newTypeMap as $typeName => $newType) { $oldType = isset($oldTypeMap[$typeName]) ? $oldTypeMap[$typeName] : null; if (!($oldType instanceof UnionType) || !($newType instanceof UnionType)) { @@ -479,12 +519,13 @@ class FindBreakingChanges } foreach ($newType->getTypes() as $type) { if (!isset($typeNamesInOldUnion[$type->name])) { - $addedTypeName = $type->name; - $typesAddedToUnion[] = ['type' => self::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, 'description' => "${addedTypeName} was added to union type ${typeName}"]; + $typesAddedToUnion[] = [ + 'type' => self::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, + 'description' => "{$type->name} was added to union type ${typeName}.", + ]; } } } - return $typesAddedToUnion; } @@ -495,14 +536,13 @@ class FindBreakingChanges * @return array */ public static function findValuesRemovedFromEnums( - Schema $oldSchema, Schema $newSchema - ) - { + Schema $oldSchema, + Schema $newSchema + ) { $oldTypeMap = $oldSchema->getTypeMap(); $newTypeMap = $newSchema->getTypeMap(); $valuesRemovedFromEnums = []; - foreach ($oldTypeMap as $typeName => $oldType) { $newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null; if (!($oldType instanceof EnumType) || !($newType instanceof EnumType)) { @@ -514,12 +554,13 @@ class FindBreakingChanges } foreach ($oldType->getValues() as $value) { if (!isset($valuesInNewEnum[$value->name])) { - $valueName = $value->name; - $valuesRemovedFromEnums[] = ['type' => self::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM, 'description' => "${valueName} was removed from enum type ${typeName}."]; + $valuesRemovedFromEnums[] = [ + 'type' => self::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM, + 'description' => "{$value->name} was removed from enum type ${typeName}.", + ]; } } } - return $valuesRemovedFromEnums; } @@ -530,9 +571,9 @@ class FindBreakingChanges * @return array */ public static function findValuesAddedToEnums( - Schema $oldSchema, Schema $newSchema - ) - { + Schema $oldSchema, + Schema $newSchema + ) { $oldTypeMap = $oldSchema->getTypeMap(); $newTypeMap = $newSchema->getTypeMap(); @@ -548,12 +589,13 @@ class FindBreakingChanges } foreach ($newType->getValues() as $value) { if (!isset($valuesInOldEnum[$value->name])) { - $valueName = $value->name; - $valuesAddedToEnums[] = ['type' => self::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM, 'description' => "${valueName} was added to enum type ${typeName}"]; + $valuesAddedToEnums[] = [ + 'type' => self::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM, + 'description' => "{$value->name} was added to enum type ${typeName}.", + ]; } } } - return $valuesAddedToEnums; } @@ -564,13 +606,13 @@ class FindBreakingChanges * @return array */ public static function findInterfacesRemovedFromObjectTypes( - Schema $oldSchema, Schema $newSchema - ) - { + Schema $oldSchema, + Schema $newSchema + ) { $oldTypeMap = $oldSchema->getTypeMap(); $newTypeMap = $newSchema->getTypeMap(); - $breakingChanges = []; + foreach ($oldTypeMap as $typeName => $oldType) { $newType = isset($newTypeMap[$typeName]) ? $newTypeMap[$typeName] : null; if (!($oldType instanceof ObjectType) || !($newType instanceof ObjectType)) { @@ -583,9 +625,9 @@ class FindBreakingChanges if (!Utils::find($newInterfaces, function (InterfaceType $interface) use ($oldInterface) { return $interface->name === $oldInterface->name; })) { - $oldInterfaceName = $oldInterface->name; - $breakingChanges[] = ['type' => self::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT, - 'description' => "${typeName} no longer implements interface ${oldInterfaceName}." + $breakingChanges[] = [ + 'type' => self::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT, + 'description' => "${typeName} no longer implements interface {$oldInterface->name}." ]; } } @@ -593,6 +635,42 @@ class FindBreakingChanges return $breakingChanges; } + /** + * @param Schema $oldSchema + * @param Schema $newSchema + * + * @return array + */ + public static function findInterfacesAddedToObjectTypes( + Schema $oldSchema, + Schema $newSchema + ) { + $oldTypeMap = $oldSchema->getTypeMap(); + $newTypeMap = $newSchema->getTypeMap(); + $interfacesAddedToObjectTypes = []; + + foreach ($newTypeMap as $typeName => $newType) { + $oldType = isset($oldTypeMap[$typeName]) ? $oldTypeMap[$typeName] : null; + if (!($oldType instanceof ObjectType) || !($newType instanceof ObjectType)) { + continue; + } + + $oldInterfaces = $oldType->getInterfaces(); + $newInterfaces = $newType->getInterfaces(); + foreach ($newInterfaces as $newInterface) { + if (!Utils::find($oldInterfaces, function (InterfaceType $interface) use ($newInterface) { + return $interface->name === $newInterface->name; + })) { + $interfacesAddedToObjectTypes[] = [ + 'type' => self::DANGEROUS_CHANGE_INTERFACE_ADDED_TO_OBJECT, + 'description' => "{$newInterface->name} added to interfaces implemented by {$typeName}.", + ]; + } + } + } + return $interfacesAddedToObjectTypes; + } + public static function findRemovedDirectives(Schema $oldSchema, Schema $newSchema) { $removedDirectives = []; diff --git a/tests/Utils/FindBreakingChangesTest.php b/tests/Utils/FindBreakingChangesTest.php index b60301d..60134a0 100644 --- a/tests/Utils/FindBreakingChangesTest.php +++ b/tests/Utils/FindBreakingChangesTest.php @@ -29,7 +29,12 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ]); } - public function testShouldDetectIfTypeWasRemoved() + //DESCRIBE: findBreakingChanges + + /** + * @it should detect if a type was removed or not + */ + public function testShouldDetectIfTypeWasRemovedOrNot() { $type1 = new ObjectType([ 'name' => 'Type1', @@ -44,31 +49,33 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ] ]); $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $type1, - 'type2' => $type2 - ] - ]) + 'query' => $this->queryType, + 'types' => [$type1, $type2] ]); $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type2' => $type2 - ] - ]) + 'query' => $this->queryType, + 'types' => [$type2] ]); - $this->assertEquals(['type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, 'description' => 'Type1 was removed.'], - FindBreakingChanges::findRemovedTypes($oldSchema, $newSchema)[0] + $expected = [ + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, + 'description' => 'Type1 was removed.' + ] + ]; + + $this->assertEquals( + $expected, + FindBreakingChanges::findRemovedTypes($oldSchema, $newSchema) ); $this->assertEquals([], FindBreakingChanges::findRemovedTypes($oldSchema, $oldSchema)); } - public function testShouldDetectTypeChanges() + /** + * @it should detect if a type changed its type + */ + public function testShouldDetectIfATypeChangedItsType() { $objectType = new ObjectType([ 'name' => 'ObjectType', @@ -90,37 +97,41 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ]); $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $interfaceType - ] - ]) + 'query' => $this->queryType, + 'types' => [$interfaceType] ]); $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $unionType - ] - ]) + 'query' => $this->queryType, + 'types' => [$unionType] ]); + $expected = [ + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_CHANGED_KIND, + 'description' => 'Type1 changed from an Interface type to a Union type.' + ] + ]; + $this->assertEquals( - ['type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_CHANGED, 'description' => 'Type1 changed from an Interface type to a Union type.'], - FindBreakingChanges::findTypesThatChangedKind($oldSchema, $newSchema)[0] + $expected, + FindBreakingChanges::findTypesThatChangedKind($oldSchema, $newSchema) ); } - public function testShouldDetectFieldChangesAndDeletions() + /** + * @it should detect if a field on a type was deleted or changed type + */ + public function testShouldDetectIfAFieldOnATypeWasDeletedOrChangedType() { - $typeA1 = new ObjectType([ + $typeA = new ObjectType([ 'name' => 'TypeA', 'fields' => [ 'field1' => ['type' => Type::string()], ] ]); + // logically equivalent to TypeA; findBreakingFieldChanges shouldn't + // treat this as different than TypeA $typeA2 = new ObjectType([ 'name' => 'TypeA', 'fields' => [ @@ -136,10 +147,10 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $oldType1 = new InterfaceType([ 'name' => 'Type1', 'fields' => [ - 'field1' => ['type' => $typeA1], + 'field1' => ['type' => $typeA], 'field2' => ['type' => Type::string()], 'field3' => ['type' => Type::string()], - 'field4' => ['type' => $typeA1], + 'field4' => ['type' => $typeA], 'field6' => ['type' => Type::string()], 'field7' => ['type' => Type::listOf(Type::string())], 'field8' => ['type' => Type::int()], @@ -184,84 +195,78 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ] ]); - $expectedFieldChanges = [ - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_REMOVED, - 'description' => 'Type1->field2 was removed.', - ], - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'Type1->field3 changed type from String to Boolean.', - ], - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'Type1->field4 changed type from TypeA to TypeB.', - ], - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'Type1->field6 changed type from String to [String].', - ], - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'Type1->field7 changed type from [String] to String.', - ], - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'Type1->field9 changed type from Int! to Int.', - ], - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'Type1->field10 changed type from [Int]! to [Int].', - ], - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'Type1->field11 changed type from Int to [Int]!.', - ], - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'Type1->field13 changed type from [Int!] to [Int].', - ], - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'Type1->field14 changed type from [Int] to [[Int]].', - ], - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'Type1->field15 changed type from [[Int]] to [Int].', - ], - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'Type1->field16 changed type from Int! to [Int]!.', - ], - [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'Type1->field18 changed type from [[Int!]!] to [[Int!]].', - ], - ]; - $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'Type1' => $oldType1 - ] - ]) + 'query' => $this->queryType, + 'types' => [$oldType1], ]); $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'Type1' => $newType1 - ] - ]) + 'query' => $this->queryType, + 'types' => [$newType1], ]); + $expectedFieldChanges = [ + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_REMOVED, + 'description' => 'Type1.field2 was removed.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'Type1.field3 changed type from String to Boolean.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'Type1.field4 changed type from TypeA to TypeB.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'Type1.field6 changed type from String to [String].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'Type1.field7 changed type from [String] to String.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'Type1.field9 changed type from Int! to Int.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'Type1.field10 changed type from [Int]! to [Int].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'Type1.field11 changed type from Int to [Int]!.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'Type1.field13 changed type from [Int!] to [Int].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'Type1.field14 changed type from [Int] to [[Int]].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'Type1.field15 changed type from [[Int]] to [Int].', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'Type1.field16 changed type from Int! to [Int]!.', + ], + [ + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'Type1.field18 changed type from [[Int!]!] to [[Int!]].', + ], + ]; + $this->assertEquals($expectedFieldChanges, FindBreakingChanges::findFieldsThatChangedTypeOnObjectOrInterfaceTypes($oldSchema, $newSchema)); } - - public function testShouldDetectInputFieldChanges() + /** + * @it should detect if fields on input types changed kind or were removed + */ + public function testShouldDetectIfFieldsOnInputTypesChangedKindOrWereRemoved() { $oldInputType = new InputObjectType([ 'name' => 'InputType1', @@ -363,74 +368,69 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ]); $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $oldInputType - ] - ]) + 'query' => $this->queryType, + 'types' => [$oldInputType] ]); $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $newInputType - ] - ]) + 'query' => $this->queryType, + 'types' => [$newInputType] ]); $expectedFieldChanges = [ [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'InputType1->field1 changed type from String to Int.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'InputType1.field1 changed type from String to Int.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_REMOVED, - 'description' => 'InputType1->field2 was removed.', + 'description' => 'InputType1.field2 was removed.', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'InputType1->field3 changed type from [String] to String.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'InputType1.field3 changed type from [String] to String.', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'InputType1->field5 changed type from String to String!.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'InputType1.field5 changed type from String to String!.', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'InputType1->field6 changed type from [Int] to [Int]!.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'InputType1.field6 changed type from [Int] to [Int]!.', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'InputType1->field8 changed type from Int to [Int]!.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'InputType1.field8 changed type from Int to [Int]!.', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'InputType1->field9 changed type from [Int] to [Int!].', + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'InputType1.field9 changed type from [Int] to [Int!].', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'InputType1->field11 changed type from [Int] to [[Int]].', + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'InputType1.field11 changed type from [Int] to [[Int]].', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'InputType1->field12 changed type from [[Int]] to [Int].', + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'InputType1.field12 changed type from [[Int]] to [Int].', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'InputType1->field13 changed type from Int! to [Int]!.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'InputType1.field13 changed type from Int! to [Int]!.', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'InputType1->field15 changed type from [[Int]!] to [[Int!]!].', + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'InputType1.field15 changed type from [[Int]!] to [[Int!]!].', ], ]; $this->assertEquals($expectedFieldChanges, FindBreakingChanges::findFieldsThatChangedTypeOnInputObjectTypes($oldSchema, $newSchema)['breakingChanges']); } - public function testDetectsNonNullFieldAddedToInputType() + /** + * @it should detect if a non-null field is added to an input type + */ + public function testShouldDetectIfANonNullFieldIsAddedToAnInputType() { $oldInputType = new InputObjectType([ 'name' => 'InputType1', @@ -449,33 +449,32 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ]); $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $oldInputType - ] - ]) + 'query' => $this->queryType, + 'types' => [$oldInputType], ]); $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $newInputType - ] - ]) + 'query' => $this->queryType, + 'types' => [$newInputType], ]); - $this->assertEquals( + $expected = [ [ 'type' => FindBreakingChanges::BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED, 'description' => 'A non-null field requiredField on input type InputType1 was added.' ], - FindBreakingChanges::findFieldsThatChangedTypeOnInputObjectTypes($oldSchema, $newSchema)['breakingChanges'][0] + ]; + + $this->assertEquals( + $expected, + FindBreakingChanges::findFieldsThatChangedTypeOnInputObjectTypes($oldSchema, $newSchema)['breakingChanges'] ); } - public function testDetectsIfTypeWasRemovedFromUnion() + /** + * @it should detect if a type was removed from a union type + */ + public function testShouldRetectIfATypeWasRemovedFromAUnionType() { $type1 = new ObjectType([ 'name' => 'Type1', @@ -483,21 +482,20 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase 'field1' => Type::string() ] ]); - + // logially equivalent to type1; findTypesRemovedFromUnions should not + // treat this as different than type1 $type1a = new ObjectType([ 'name' => 'Type1', 'fields' => [ 'field1' => Type::string() ] ]); - $type2 = new ObjectType([ 'name' => 'Type2', 'fields' => [ 'field1' => Type::string() ] ]); - $type3 = new ObjectType([ 'name' => 'Type3', 'fields' => [ @@ -509,41 +507,37 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase 'name' => 'UnionType1', 'types' => [$type1, $type2], ]); - - $newUnionType = new UnionType([ 'name' => 'UnionType1', 'types' => [$type1a, $type3], ]); $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $oldUnionType - ] - ]) + 'query' => $this->queryType, + 'types' => [$oldUnionType], ]); - $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $newUnionType - ] - ]) + 'query' => $this->queryType, + 'types' => [$newUnionType], ]); - $this->assertEquals( + $expected = [ [ 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION, 'description' => 'Type2 was removed from union type UnionType1.' - ], - FindBreakingChanges::findTypesRemovedFromUnions($oldSchema, $newSchema)[0] + ] + ]; + + $this->assertEquals( + $expected, + FindBreakingChanges::findTypesRemovedFromUnions($oldSchema, $newSchema) ); } - public function testDetectsValuesRemovedFromEnum() + /** + * @it should detect if a value was removed from an enum type + */ + public function testShouldDetectIfAValueWasRemovedFromAnEnumType() { $oldEnumType = new EnumType([ 'name' => 'EnumType1', @@ -563,35 +557,33 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ]); $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $oldEnumType - ] - ]) + 'query' => $this->queryType, + 'types' => [$oldEnumType] ]); $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $newEnumType - ] - ]) + 'query' => $this->queryType, + 'types' => [$newEnumType] ]); - $this->assertEquals( + $expected = [ [ 'type' => FindBreakingChanges::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM, 'description' => 'VALUE1 was removed from enum type EnumType1.' - ], - FindBreakingChanges::findValuesRemovedFromEnums($oldSchema, $newSchema)[0] + ] + ]; + + $this->assertEquals( + $expected, + FindBreakingChanges::findValuesRemovedFromEnums($oldSchema, $newSchema) ); } - public function testDetectsRemovalOfFieldArgument() + /** + * @it should detect if a field argument was removed + */ + public function testShouldDetectIfAFieldArgumentWasRemoved() { - $oldType = new ObjectType([ 'name' => 'Type1', 'fields' => [ @@ -604,7 +596,6 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ] ]); - $inputType = new InputObjectType([ 'name' => 'InputType1', 'fields' => [ @@ -643,48 +634,38 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ]); $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $oldType, - 'type2' => $oldInterfaceType - ], - 'types' => [$oldType, $oldInterfaceType] - ]) + 'query' => $this->queryType, + 'types' => [$oldType, $oldInterfaceType], ]); $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $newType, - 'type2' => $newInterfaceType - ], - 'types' => [$newType, $newInterfaceType] - ]) + 'query' => $this->queryType, + 'types' => [$newType, $newInterfaceType], ]); $expectedChanges = [ [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_REMOVED, - 'description' => 'Type1->field1 arg name was removed', + 'description' => 'Type1.field1 arg name was removed', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_REMOVED, - 'description' => 'Interface1->field1 arg arg1 was removed', + 'description' => 'Interface1.field1 arg arg1 was removed', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_REMOVED, - 'description' => 'Interface1->field1 arg objectArg was removed', + 'description' => 'Interface1.field1 arg objectArg was removed', ] ]; $this->assertEquals($expectedChanges, FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } - public function testDetectsFieldArgumentTypeChange() + /** + * @it should detect if a field argument has changed type + */ + public function testShouldDetectIfAFieldArgumentHasChangedType() { - $oldType = new ObjectType([ 'name' => 'Type1', 'fields' => [ @@ -738,78 +719,73 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ]); $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $oldType - ] - ]) + 'query' => $this->queryType, + 'types' => [$oldType] ]); $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $newType - ] - ]) + 'query' => $this->queryType, + 'types' => [$newType] ]); $expectedChanges = [ [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'Type1->field1 arg arg1 has changed type from String to Int.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'Type1.field1 arg arg1 has changed type from String to Int', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'Type1->field1 arg arg2 has changed type from String to [String].' + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'Type1.field1 arg arg2 has changed type from String to [String]' ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'Type1->field1 arg arg3 has changed type from [String] to String.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'Type1.field1 arg arg3 has changed type from [String] to String', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'Type1->field1 arg arg4 has changed type from String to String!.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'Type1.field1 arg arg4 has changed type from String to String!', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'Type1->field1 arg arg5 has changed type from String! to Int.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'Type1.field1 arg arg5 has changed type from String! to Int', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'Type1->field1 arg arg6 has changed type from String! to Int!.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'Type1.field1 arg arg6 has changed type from String! to Int!', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'Type1->field1 arg arg8 has changed type from Int to [Int]!.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'Type1.field1 arg arg8 has changed type from Int to [Int]!', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'Type1->field1 arg arg9 has changed type from [Int] to [Int!].', + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'Type1.field1 arg arg9 has changed type from [Int] to [Int!]', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'Type1->field1 arg arg11 has changed type from [Int] to [[Int]].', + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'Type1.field1 arg arg11 has changed type from [Int] to [[Int]]', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'Type1->field1 arg arg12 has changed type from [[Int]] to [Int].', + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'Type1.field1 arg arg12 has changed type from [[Int]] to [Int]', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'Type1->field1 arg arg13 has changed type from Int! to [Int]!.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'Type1.field1 arg arg13 has changed type from Int! to [Int]!', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'Type1->field1 arg arg15 has changed type from [[Int]!] to [[Int!]!].', + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'Type1.field1 arg arg15 has changed type from [[Int]!] to [[Int!]!]', ], ]; $this->assertEquals($expectedChanges, FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } - public function testDetectsAdditionOfFieldArg() + /** + * @it should detect if a non-null field argument was added + */ + public function testShouldDetectIfANonNullFieldArgumentWasAdded() { $oldType = new ObjectType([ 'name' => 'Type1', @@ -834,31 +810,30 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ] ]); $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $oldType, - ] - ]) + 'query' => $this->queryType, + 'types' => [$oldType] ]); $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $newType - ] - ]) + 'query' => $this->queryType, + 'types' => [$newType] ]); - $this->assertEquals( + $expected = [ [ 'type' => FindBreakingChanges::BREAKING_CHANGE_NON_NULL_ARG_ADDED, - 'description' => 'A non-null arg newRequiredArg on Type1->field1 was added.' - ], - FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges'][0]); + 'description' => 'A non-null arg newRequiredArg on Type1.field1 was added' + ] + ]; + + $this->assertEquals( + $expected, + FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } - public function testDoesNotFlagArgsWithSameTypeSignature() + /** + * @it should not flag args with the same type signature as breaking + */ + public function testShouldNotFlagArgsWithTheSameTypeSignatureAsBreaking() { $inputType1a = new InputObjectType([ 'name' => 'InputType1', @@ -901,26 +876,21 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ]); $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $oldType, - ] - ]) + 'query' => $this->queryType, + 'types' => [$oldType], ]); $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $newType - ] - ]) + 'query' => $this->queryType, + 'types' => [$newType], ]); $this->assertEquals([], FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } - public function testArgsThatMoveAwayFromNonNull() + /** + * @it should consider args that move away from NonNull as non-breaking + */ + public function testShouldConsiderArgsThatMoveAwayFromNonNullAsNonBreaking() { $oldType = new ObjectType([ 'name' => 'Type1', @@ -946,26 +916,21 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ]); $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $oldType, - ] - ]) + 'query' => $this->queryType, + 'types' => [$oldType], ]); $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $newType - ] - ]) + 'query' => $this->queryType, + 'types' => [$newType], ]); $this->assertEquals([], FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } - public function testDetectsRemovalOfInterfaces() + /** + * @it should detect interfaces removed from types + */ + public function testShouldDetectInterfacesRemovedFromTypes() { $interface1 = new InterfaceType([ 'name' => 'Interface1', @@ -988,31 +953,30 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ]); $oldSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $oldType, - ] - ]) + 'query' => $this->queryType, + 'types' => [$oldType], ]); $newSchema = new Schema([ - 'query' => new ObjectType([ - 'name' => 'root', - 'fields' => [ - 'type1' => $newType - ] - ]) + 'query' => $this->queryType, + 'types' => [$newType], ]); - $this->assertEquals( + $expected = [ [ 'type' => FindBreakingChanges::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT, 'description' => 'Type1 no longer implements interface Interface1.' ], - FindBreakingChanges::findInterfacesRemovedFromObjectTypes($oldSchema, $newSchema)[0]); + ]; + + $this->assertEquals( + $expected, + FindBreakingChanges::findInterfacesRemovedFromObjectTypes($oldSchema, $newSchema)); } - public function testDetectsAllBreakingChanges() + /** + * @it should detect all breaking changes + */ + public function testShouldDetectAllBreakingChanges() { $typeThatGetsRemoved = new ObjectType([ 'name' => 'TypeThatGetsRemoved', @@ -1177,13 +1141,13 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $oldSchema = new Schema([ 'query' => $this->queryType, 'types' => [ - 'TypeThatGetsRemoved' => $typeThatGetsRemoved, - 'TypeThatChangesType' => $typeThatChangesTypeOld, - 'TypeThatHasBreakingFieldChanges' => $typeThatHasBreakingFieldChangesOld, - 'UnionTypeThatLosesAType' => $unionTypeThatLosesATypeOld, - 'EnumTypeThatLosesAValue' => $enumTypeThatLosesAValueOld, - 'ArgThatChanges' => $argThatChanges, - 'TypeThatLosesInterface' => $typeThatLosesInterfaceOld + $typeThatGetsRemoved, + $typeThatChangesTypeOld, + $typeThatHasBreakingFieldChangesOld, + $unionTypeThatLosesATypeOld, + $enumTypeThatLosesAValueOld, + $argThatChanges, + $typeThatLosesInterfaceOld ], 'directives' => [ $directiveThatIsRemoved, @@ -1196,13 +1160,13 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $newSchema = new Schema([ 'query' => $this->queryType, 'types' => [ - 'TypeThatChangesType' => $typeThatChangesTypeNew, - 'TypeThatHasBreakingFieldChanges' => $typeThatHasBreakingFieldChangesNew, - 'UnionTypeThatLosesAType' => $unionTypeThatLosesATypeNew, - 'EnumTypeThatLosesAValue' => $enumTypeThatLosesAValueNew, - 'ArgThatChanges' => $argChanged, - 'TypeThatLosesInterface' => $typeThatLosesInterfaceNew, - 'Interface1' => $interface1 + $typeThatChangesTypeNew, + $typeThatHasBreakingFieldChangesNew, + $unionTypeThatLosesATypeNew, + $enumTypeThatLosesAValueNew, + $argChanged, + $typeThatLosesInterfaceNew, + $interface1 ], 'directives' => [ $directiveThatRemovesArgNew, @@ -1220,25 +1184,23 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, 'description' => 'TypeInUnion2 was removed.', ], - /* - // NB the below assertion is included in the graphql-js tests, but it makes no sense. - // Seriously, look for what `int` type was supposed to be removed between the two schemas. There is none. - // I honestly think it's a bug in the js implementation and was put into the test just to make it pass. + /* This is reported in the js version because builtin sclar types are added on demand + and not like here always [ 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, 'description' => 'Int was removed.' ],*/ [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_CHANGED, + 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_CHANGED_KIND, 'description' => 'TypeThatChangesType changed from an Object type to an Interface type.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_REMOVED, - 'description' => 'TypeThatHasBreakingFieldChanges->field1 was removed.', + 'description' => 'TypeThatHasBreakingFieldChanges.field1 was removed.', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, - 'description' => 'TypeThatHasBreakingFieldChanges->field2 changed type from String to Boolean.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED_KIND, + 'description' => 'TypeThatHasBreakingFieldChanges.field2 changed type from String to Boolean.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION, @@ -1249,8 +1211,8 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase 'description' => 'VALUE0 was removed from enum type EnumTypeThatLosesAValue.', ], [ - 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, - 'description' => 'ArgThatChanges->field1 arg id has changed type from Int to String.', + 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED_KIND, + 'description' => 'ArgThatChanges.field1 arg id has changed type from Int to String', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT, @@ -1457,8 +1419,12 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase } // DESCRIBE: findDangerousChanges + // DESCRIBE: findArgChanges - public function testFindDangerousArgChanges() + /** + * @it should detect if an argument's defaultValue has changed + */ + public function testShouldDetectIfAnArgumentsDefaultValueHasChanged() { $oldType = new ObjectType([ 'name' => 'Type1', @@ -1483,7 +1449,7 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase 'args' => [ 'name' => [ 'type' => Type::string(), - 'defaultValue' => 'Testertest' + 'defaultValue' => 'Test' ] ] ] @@ -1492,28 +1458,31 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $oldSchema = new Schema([ 'query' => $this->queryType, - 'types' => [ - $oldType - ] + 'types' => [$oldType], ]); $newSchema = new Schema([ 'query' => $this->queryType, - 'types' => [ - $newType - ] + 'types' => [$newType], ]); - $this->assertEquals( + $expected = [ [ - 'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE, - 'description' => 'Type1->field1 arg name has changed defaultValue' - ], - FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['dangerousChanges'][0] + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE_CHANGED, + 'description' => 'Type1.field1 arg name has changed defaultValue' + ] + ]; + + $this->assertEquals( + $expected, + FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['dangerousChanges'] ); } - public function testDetectsEnumValueAdditions() + /** + * @it should detect if a value was added to an enum type + */ + public function testShouldDetectIfAValueWasAddedToAnEnumType() { $oldEnumType = new EnumType([ 'name' => 'EnumType1', @@ -1533,28 +1502,80 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $oldSchema = new Schema([ 'query' => $this->queryType, - 'types' => [ - $oldEnumType - ] + 'types' => [$oldEnumType], ]); $newSchema = new Schema([ 'query' => $this->queryType, - 'types' => [ - $newEnumType - ] + 'types' => [$newEnumType], ]); - $this->assertEquals( + $expected = [ [ 'type' => FindBreakingChanges::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM, - 'description' => 'VALUE2 was added to enum type EnumType1' - ], - FindBreakingChanges::findValuesAddedToEnums($oldSchema, $newSchema)[0] + 'description' => 'VALUE2 was added to enum type EnumType1.' + ] + ]; + + $this->assertEquals( + $expected, + FindBreakingChanges::findValuesAddedToEnums($oldSchema, $newSchema) ); } - public function testDetectsAdditionsToUnionType() + /** + * @it should detect interfaces added to types + */ + public function testShouldDetectInterfacesAddedToTypes() + { + $interface1 = new InterfaceType([ + 'name' => 'Interface1', + 'fields' => [ + 'field1' => Type::string(), + ], + ]); + $oldType = new ObjectType([ + 'name' => 'Type1', + 'fields' => [ + 'field1' => Type::string(), + ], + ]); + + $newType = new ObjectType([ + 'name' => 'Type1', + 'interfaces' => [$interface1], + 'fields' => [ + 'field1' => Type::string(), + ], + ]); + + $oldSchema = new Schema([ + 'query' => $this->queryType, + 'types' => [$oldType], + ]); + + $newSchema = new Schema([ + 'query' => $this->queryType, + 'types' => [$newType], + ]); + + $expected = [ + [ + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_INTERFACE_ADDED_TO_OBJECT, + 'description' => 'Interface1 added to interfaces implemented by Type1.' + ] + ]; + + $this->assertEquals( + $expected, + FindBreakingChanges::findInterfacesAddedToObjectTypes($oldSchema, $newSchema) + ); + } + + /** + * @it should detect if a type was added to a union type + */ + public function testShouldDetectIfATypeWasAddedToAUnionType() { $type1 = new ObjectType([ 'name' => 'Type1', @@ -1562,14 +1583,14 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase 'field1' => Type::string() ] ]); - + // logially equivalent to type1; findTypesRemovedFromUnions should not + //treat this as different than type1 $type1a = new ObjectType([ 'name' => 'Type1', 'fields' => [ 'field1' => Type::string() ] ]); - $type2 = new ObjectType([ 'name' => 'Type2', 'fields' => [ @@ -1581,7 +1602,6 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase 'name' => 'UnionType1', 'types' => [$type1], ]); - $newUnionType = new UnionType([ 'name' => 'UnionType1', 'types' => [$type1a, $type2], @@ -1589,24 +1609,24 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $oldSchema = new Schema([ 'query' => $this->queryType, - 'types' => [ - $oldUnionType - ] + 'types' => [$oldUnionType], ]); $newSchema = new Schema([ 'query' => $this->queryType, - 'types' => [ - $newUnionType - ] + 'types' => [$newUnionType], ]); - $this->assertEquals( + $expected = [ [ 'type' => FindBreakingChanges::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, - 'description' => 'Type2 was added to union type UnionType1' - ], - FindBreakingChanges::findTypesAddedToUnions($oldSchema, $newSchema)[0] + 'description' => 'Type2 was added to union type UnionType1.' + ] + ]; + + $this->assertEquals( + $expected, + FindBreakingChanges::findTypesAddedToUnions($oldSchema, $newSchema) ); } @@ -1659,7 +1679,10 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedFieldChanges, FindBreakingChanges::findFieldsThatChangedTypeOnInputObjectTypes($oldSchema, $newSchema)['dangerousChanges']); } - public function testFindsAllDangerousChanges() + /** + * @it should find all dangerous changes + */ + public function testShouldFindAllDangerousChanges() { $enumThatGainsAValueOld = new EnumType([ 'name' => 'EnumType1', @@ -1692,6 +1715,27 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase ] ]); + $typeInUnion1 = new ObjectType([ + 'name' => 'TypeInUnion1', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + $typeInUnion2 = new ObjectType([ + 'name' => 'TypeInUnion2', + 'fields' => [ + 'field1' => Type::string() + ] + ]); + $unionTypeThatGainsATypeOld = new UnionType([ + 'name' => 'UnionTypeThatGainsAType', + 'types' => [$typeInUnion1], + ]); + $unionTypeThatGainsATypeNew = new UnionType([ + 'name' => 'UnionTypeThatGainsAType', + 'types' => [$typeInUnion1, $typeInUnion2], + ]); + $newType = new ObjectType([ 'name' => 'Type1', 'fields' => [ @@ -1700,35 +1744,33 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase 'args' => [ 'name' => [ 'type' => Type::string(), - 'defaultValue' => 'Testertest' + 'defaultValue' => 'Test' ] ] ] ] ]); - $typeInUnion1 = new ObjectType([ - 'name' => 'TypeInUnion1', + $interface1 = new InterfaceType([ + 'name' => 'Interface1', 'fields' => [ - 'field1' => Type::string() - ] + 'field1' => Type::string(), + ], ]); - $typeInUnion2 = new ObjectType([ - 'name' => 'TypeInUnion2', + $typeThatGainsInterfaceOld = new ObjectType([ + 'name' => 'TypeThatGainsInterface1', 'fields' => [ - 'field1' => Type::string() - ] + 'field1' => Type::string(), + ], ]); - $unionTypeThatGainsATypeOld = new UnionType([ - 'name' => 'UnionType1', - 'types' => [$typeInUnion1], - ]); - - $unionTypeThatGainsATypeNew = new UnionType([ - 'name' => 'UnionType1', - 'types' => [$typeInUnion1, $typeInUnion2], + $typeThatGainsInterfaceNew = new ObjectType([ + 'name' => 'TypeThatGainsInterface1', + 'interfaces' => [$interface1], + 'fields' => [ + 'field1' => Type::string(), + ], ]); $oldSchema = new Schema([ @@ -1736,6 +1778,7 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase 'types' => [ $oldType, $enumThatGainsAValueOld, + $typeThatGainsInterfaceOld, $unionTypeThatGainsATypeOld ] ]); @@ -1745,22 +1788,27 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase 'types' => [ $newType, $enumThatGainsAValueNew, + $typeThatGainsInterfaceNew, $unionTypeThatGainsATypeNew ] ]); $expectedDangerousChanges = [ [ - 'description' => 'Type1->field1 arg name has changed defaultValue', - 'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE + 'description' => 'Type1.field1 arg name has changed defaultValue', + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE_CHANGED ], [ - 'description' => 'VALUE2 was added to enum type EnumType1', + 'description' => 'VALUE2 was added to enum type EnumType1.', 'type' => FindBreakingChanges::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM ], + [ + 'type' => FindBreakingChanges::DANGEROUS_CHANGE_INTERFACE_ADDED_TO_OBJECT, + 'description' => 'Interface1 added to interfaces implemented by TypeThatGainsInterface1.', + ], [ 'type' => FindBreakingChanges::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION, - 'description' => 'TypeInUnion2 was added to union type UnionType1', + 'description' => 'TypeInUnion2 was added to union type UnionTypeThatGainsAType.', ] ]; @@ -1805,21 +1853,17 @@ class FindBreakingChangesTest extends \PHPUnit_Framework_TestCase $oldSchema = new Schema([ 'query' => $this->queryType, - 'types' => [ - $oldType, - ] + 'types' => [$oldType], ]); $newSchema = new Schema([ 'query' => $this->queryType, - 'types' => [ - $newType, - ] + 'types' => [$newType], ]); $expectedFieldChanges = [ [ - 'description' => 'A nullable arg arg2 on Type1->field1 was added.', + 'description' => 'A nullable arg arg2 on Type1.field1 was added', 'type' => FindBreakingChanges::DANGEROUS_CHANGE_NULLABLE_ARG_ADDED ], ];