Repository: kamilkisiela/graphql-inspector (ts)

Issue:
[Diff] Cannot compare remote schema
Repo:
https://github.com/spacexland/api/tree/graphql-inspector

Run:
```bash
graphql-inspector diff https://api.spacex.land/graphql schema.graphql
```
Output:
```bash
✖ Cannot read property '__schema' of undefined
```
Definitions in introspected schema are being pretty printed
Hi!

I'm comparing two schemas: one loaded from disk, one introspected from our endpoint. Even if they're both using identical schemas, I'm seeing differences due to newlines being added in type definition comments.

It turns out that the issue is with this process: https://github.com/kamilkisiela/graphql-inspector/blob/master/packages/load/src/schema/index.ts#L18 - the printSchema function is (I think) doing some pretty printing on long comments, which means that they can't be compared to the original.

I'm not sure what the best route to fix it would be, except maybe amending the graphql printSchema function to make pretty printing optional?

Cheers,
Mike

Pull request description:
Treat null and undefined as equal in schema diff
No more quickfix and schema normalization

- [ ] make sure it fixes #73
- [ ] make sure it fixes #90 

Patch:
diff --git a/packages/core/src/diff/argument.ts b/packages/core/src/diff/argument.ts
index acfc8f73..f695249f 100644
--- a/packages/core/src/diff/argument.ts
+++ b/packages/core/src/diff/argument.ts
@@ -5,6 +5,7 @@ import {
   GraphQLInterfaceType,
 } from 'graphql';
 
+import {notEqual} from './common/compare';
 import {Change} from './changes/change';
 import {
   fieldArgumentDescriptionChanged,
@@ -21,11 +22,11 @@ export function changesInArgument(
 ): Change[] {
   const changes: Change[] = [];
 
-  if (oldArg.description !== newArg.description) {
+  if (notEqual(oldArg.description, newArg.description)) {
     changes.push(fieldArgumentDescriptionChanged(type, field, oldArg, newArg));
   }
 
-  if (oldArg.defaultValue !== newArg.defaultValue) {
+  if (notEqual(oldArg.defaultValue, newArg.defaultValue)) {
     if (
       Array.isArray(oldArg.defaultValue) &&
       Array.isArray(newArg.defaultValue)
@@ -42,7 +43,7 @@ export function changesInArgument(
     }
   }
 
-  if (oldArg.type.toString() !== newArg.type.toString()) {
+  if (notEqual(oldArg.type.toString(), newArg.type.toString())) {
     changes.push(fieldArgumentTypeChanged(type, field, oldArg, newArg));
   }
 
diff --git a/packages/core/src/diff/common/compare.ts b/packages/core/src/diff/common/compare.ts
new file mode 100644
index 00000000..fc791018
--- /dev/null
+++ b/packages/core/src/diff/common/compare.ts
@@ -0,0 +1,7 @@
+export function equal<T>(a: T, b: T): boolean {
+  return a === b || (!a && !b);
+}
+
+export function notEqual<T>(a: T, b: T): boolean {
+  return !equal(a, b);
+}
diff --git a/packages/core/src/diff/directive.ts b/packages/core/src/diff/directive.ts
index 20f21975..88e4882c 100644
--- a/packages/core/src/diff/directive.ts
+++ b/packages/core/src/diff/directive.ts
@@ -4,6 +4,7 @@ import {
   GraphQLArgument,
 } from 'graphql';
 
+import {notEqual} from './common/compare';
 import {Change} from './changes/change';
 import {
   directiveDescriptionChanged,
@@ -23,7 +24,7 @@ export function changesInDirective(
 ): Change[] {
   const changes: Change[] = [];
 
-  if (oldDirective.description !== newDirective.description) {
+  if (notEqual(oldDirective.description, newDirective.description)) {
     changes.push(directiveDescriptionChanged(oldDirective, newDirective));
   }
 
@@ -89,19 +90,19 @@ function changesInDirectiveArgument(
 ): Change[] {
   const changes: Change[] = [];
 
-  if (oldArg.description !== newArg.description) {
+  if (notEqual(oldArg.description, newArg.description)) {
     changes.push(
       directiveArgumentDescriptionChanged(directive, oldArg, newArg),
     );
   }
 
-  if (oldArg.defaultValue !== newArg.defaultValue) {
+  if (notEqual(oldArg.defaultValue, newArg.defaultValue)) {
     changes.push(
       directiveArgumentDefaultValueChanged(directive, oldArg, newArg),
     );
   }
 
-  if (oldArg.type.toString() !== newArg.type.toString()) {
+  if (notEqual(oldArg.type.toString(), newArg.type.toString())) {
     changes.push(directiveArgumentTypeChanged(directive, oldArg, newArg));
   }
 
diff --git a/packages/core/src/diff/enum.ts b/packages/core/src/diff/enum.ts
index 833aad4f..e0ec7059 100644
--- a/packages/core/src/diff/enum.ts
+++ b/packages/core/src/diff/enum.ts
@@ -1,5 +1,6 @@
 import {GraphQLEnumType, GraphQLEnumValue} from 'graphql';
 
+import {notEqual} from './common/compare';
 import {
   enumValueRemoved,
   enumValueAdded,
@@ -32,11 +33,11 @@ export function changesInEnum(
   changes.push(...removed.map(v => enumValueRemoved(oldEnum, v)));
 
   common.forEach(({oldValue, newValue}) => {
-    if (oldValue.description !== newValue.description) {
+    if (notEqual(oldValue.description, newValue.description)) {
       changes.push(enumValueDescriptionChanged(newEnum, oldValue, newValue));
     }
 
-    if (oldValue.deprecationReason !== newValue.deprecationReason) {
+    if (notEqual(oldValue.deprecationReason, newValue.deprecationReason)) {
       changes.push(
         enumValueDeprecationReasonChanged(newEnum, oldValue, newValue),
       );
diff --git a/packages/core/src/diff/field.ts b/packages/core/src/diff/field.ts
index 7380ed23..5147dbbd 100644
--- a/packages/core/src/diff/field.ts
+++ b/packages/core/src/diff/field.ts
@@ -5,6 +5,7 @@ import {
   GraphQLInterfaceType,
 } from 'graphql';
 
+import {notEqual} from './common/compare';
 import {Change} from './changes/change';
 import {
   fieldDescriptionChanged,
@@ -23,15 +24,15 @@ export function changesInField(
 ): Change[] {
   const changes: Change[] = [];
 
-  if (oldField.description !== newField.description) {
+  if (notEqual(oldField.description, newField.description)) {
     changes.push(fieldDescriptionChanged(type, oldField, newField));
   }
 
-  if (oldField.deprecationReason !== newField.deprecationReason) {
+  if (notEqual(oldField.deprecationReason, newField.deprecationReason)) {
     changes.push(fieldDeprecationReasonChanged(type, oldField, newField));
   }
 
-  if (oldField.type.toString() !== newField.type.toString()) {
+  if (notEqual(oldField.type.toString(), newField.type.toString())) {
     changes.push(fieldTypeChanged(type, oldField, newField));
   }
 
diff --git a/packages/core/src/diff/input.ts b/packages/core/src/diff/input.ts
index a921647d..961aaf06 100644
--- a/packages/core/src/diff/input.ts
+++ b/packages/core/src/diff/input.ts
@@ -1,4 +1,6 @@
 import {GraphQLInputObjectType, GraphQLInputField} from 'graphql';
+
+import {notEqual} from './common/compare';
 import {Change} from './changes/change';
 import {diffArrays, unionArrays} from '../utils/arrays';
 import {
@@ -43,11 +45,11 @@ function changesInInputField(
 ): Change[] {
   const changes: Change[] = [];
 
-  if (oldField.description !== newField.description) {
+  if (notEqual(oldField.description, newField.description)) {
     changes.push(inputFieldDescriptionChanged(input, oldField, newField));
   }
 
-  if (oldField.defaultValue !== newField.defaultValue) {
+  if (notEqual(oldField.defaultValue, newField.defaultValue)) {
     if (
       Array.isArray(oldField.defaultValue) &&
       Array.isArray(newField.defaultValue)
@@ -63,7 +65,7 @@ function changesInInputField(
     }
   }
 
-  if (oldField.type.toString() !== newField.type.toString()) {
+  if (notEqual(oldField.type.toString(), newField.type.toString())) {
     changes.push(inputFieldTypeChanged(input, oldField, newField));
   }
 
diff --git a/packages/core/src/diff/schema.ts b/packages/core/src/diff/schema.ts
index 9032b5d3..a84e1f70 100644
--- a/packages/core/src/diff/schema.ts
+++ b/packages/core/src/diff/schema.ts
@@ -11,6 +11,7 @@ import {
   isScalarType,
 } from 'graphql';
 
+import {notEqual} from './common/compare';
 import {unionArrays, diffArrays} from '../utils/arrays';
 import {isPrimitive} from '../utils/graphql';
 import {Change} from './changes/change';
@@ -152,15 +153,15 @@ function changesInSchema(
       .name,
   };
 
-  if (oldRoot.query !== newRoot.query) {
+  if (notEqual(oldRoot.query, newRoot.query)) {
     changes.push(schemaQueryTypeChanged(oldSchema, newSchema));
   }
 
-  if (oldRoot.mutation !== newRoot.mutation) {
+  if (notEqual(oldRoot.mutation, newRoot.mutation)) {
     changes.push(schemaMutationTypeChanged(oldSchema, newSchema));
   }
 
-  if (oldRoot.subscription !== newRoot.subscription) {
+  if (notEqual(oldRoot.subscription, newRoot.subscription)) {
     changes.push(schemaSubscriptionTypeChanged(oldSchema, newSchema));
   }
 
@@ -189,7 +190,7 @@ function changesInType(
     changes = [typeKindChanged(oldType, newType)];
   }
 
-  if (oldType.description !== newType.description) {
+  if (notEqual(oldType.description, newType.description)) {
     changes.push(typeDescriptionChanged(oldType, newType));
   }
 
diff --git a/packages/load/src/schema/index.ts b/packages/load/src/schema/index.ts
index 93dc3bfd..21629def 100644
--- a/packages/load/src/schema/index.ts
+++ b/packages/load/src/schema/index.ts
@@ -1,23 +1,8 @@
-import {
-  GraphQLSchema,
-  isSchema,
-  DocumentNode,
-  buildASTSchema,
-  printSchema,
-  buildSchema,
-} from 'graphql';
+import {GraphQLSchema, isSchema, DocumentNode, buildASTSchema} from 'graphql';
 import {loadSchema as useSchema} from 'graphql-toolkit';
 
 import {fromGithub} from './from-github';
 
-function isUri(uri: string): boolean {
-  return uri.startsWith('http');
-}
-
-function normalizeSchema(schema: GraphQLSchema): GraphQLSchema {
-  return buildSchema(printSchema(schema));
-}
-
 export async function loadSchema(
   pointer: string,
   extra?: any,
@@ -25,18 +10,12 @@ export async function loadSchema(
   const useGithub = fromGithub(pointer, extra);
 
   if (useGithub) {
-    // quickfix of #73
-    return normalizeSchema(await useGithub());
+    return useGithub();
   }
 
   const resolved = await useSchema(pointer, {});
 
   if (isSchema(resolved)) {
-    // quickfix of #73
-    if (isUri(pointer)) {
-      return normalizeSchema(resolved);
-    }
-
     return resolved;
   }
 


Test patch:
diff --git a/packages/core/__tests__/diff/schema.ts b/packages/core/__tests__/diff/schema.ts
index e76cca6e..22acc9d5 100644
--- a/packages/core/__tests__/diff/schema.ts
+++ b/packages/core/__tests__/diff/schema.ts
@@ -1,4 +1,9 @@
-import {buildASTSchema} from 'graphql';
+import {
+  buildASTSchema,
+  buildSchema,
+  introspectionFromSchema,
+  buildClientSchema,
+} from 'graphql';
 import gql from 'graphql-tag';
 
 import {diff} from '../../src/index';
@@ -80,7 +85,7 @@ test('renamed query', () => {
   );
 });
 
-test('new field and field changed in Query', () => {
+test('new field and field changed', () => {
   const schemaA = buildASTSchema(gql`
     type Query {
       fieldA: String!
@@ -110,7 +115,22 @@ test('new field and field changed in Query', () => {
   );
 });
 
-test('', () => {
+test('schema from an introspection result should be the same', () => {
+  const typeDefsA = /* GraphQL */ `
+    type Query {
+      fieldA: String!
+      fieldB: String
+    }
+  `;
+  const schemaA = buildSchema(typeDefsA);
+  const schemaB = buildClientSchema(introspectionFromSchema(schemaA));
+
+  const changes = diff(schemaA, schemaB);
+
+  expect(changes.length).toEqual(0);
+});
+
+test('huge test', () => {
   const schemaA = buildASTSchema(gql`
     schema {
       query: Query