Skip to content

Commit

Permalink
fix(SchemaComposer): add helper methods getOrCreateTC(), `getOrCrea…
Browse files Browse the repository at this point in the history
…teITC()`, `getOrCreateETC()`, fix flowtype autocompletion
  • Loading branch information
nodkz committed Feb 14, 2018
1 parent 359c236 commit 0edb902
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 65 deletions.
73 changes: 37 additions & 36 deletions src/SchemaComposer.js
@@ -1,24 +1,23 @@
/* @flow strict */
/* eslint-disable class-methods-use-this */

import { GraphQLObjectType, GraphQLSchema, type GraphQLNamedType } from './graphql';
import { GraphQLObjectType, GraphQLSchema } from './graphql';
import { TypeStorage } from './TypeStorage';
import { TypeMapper } from './TypeMapper';
import { TypeComposer as _TypeComposer } from './TypeComposer';
import { InputTypeComposer as _InputTypeComposer } from './InputTypeComposer';
import { EnumTypeComposer as _EnumTypeComposer } from './EnumTypeComposer';
import { Resolver as _Resolver } from './Resolver';
import { isFunction } from './utils/is';

export class SchemaComposer<TContext> extends TypeStorage<
_TypeComposer<TContext> | _InputTypeComposer | GraphQLNamedType
> {
export class SchemaComposer<TContext> extends TypeStorage<TContext> {
typeMapper: TypeMapper<TContext>;
TypeComposer: Class<_TypeComposer<TContext>>;
InputTypeComposer: typeof _InputTypeComposer;
EnumTypeComposer: typeof _EnumTypeComposer;
Resolver: Class<_Resolver<any, TContext>>;

constructor() {
constructor(): SchemaComposer<TContext> {
super();
const schema = this;

Expand All @@ -44,32 +43,8 @@ export class SchemaComposer<TContext> extends TypeStorage<

this.typeMapper = new TypeMapper(schema);

getOrCreateTC(typeName: string): _TypeComposer<TContext> {
if (!this.hasInstance(typeName, this.TypeComposer)) {
this.set(typeName, this.TypeComposer.create(typeName));
}
return (this.get(typeName): any);
}

getOrCreateITC(typeName: string): _InputTypeComposer {
if (!this.hasInstance(typeName, this.InputTypeComposer)) {
this.set(typeName, this.InputTypeComposer.create(typeName));
}
return (this.get(typeName): any);
}

getTC(typeName: string): _TypeComposer<TContext> {
if (!this.hasInstance(typeName, this.TypeComposer)) {
throw new Error(`GQC does not have TypeComposer with name ${typeName}`);
}
return (this.get(typeName): any);
}

getITC(typeName: string): _InputTypeComposer {
if (!this.hasInstance(typeName, this.InputTypeComposer)) {
throw new Error(`GQC does not have InputTypeComposer with name ${typeName}`);
}
return (this.get(typeName): any);
// alive proper Flow type casting in autosuggestions
/* :: return this; */
}

rootQuery(): _TypeComposer<TContext> {
Expand Down Expand Up @@ -140,18 +115,44 @@ export class SchemaComposer<TContext> extends TypeStorage<
typeComposer.setFields(fields);
}

getOrCreateTC(
typeName: string,
onCreate?: (_TypeComposer<TContext>) => any
): _TypeComposer<TContext> {
if (!this.hasInstance(typeName, _TypeComposer)) {
const tc = this.TypeComposer.create(typeName);
this.set(typeName, tc);
if (onCreate && isFunction(onCreate)) onCreate(tc);
}
return (this.get(typeName): any);
}

getOrCreateITC(typeName: string, onCreate?: _InputTypeComposer => any): _InputTypeComposer {
if (!this.hasInstance(typeName, _InputTypeComposer)) {
const itc = this.InputTypeComposer.create(typeName);
this.set(typeName, itc);
if (onCreate && isFunction(onCreate)) onCreate(itc);
}
return (this.get(typeName): any);
}

getOrCreateETC(typeName: string, onCreate?: _EnumTypeComposer => any): _EnumTypeComposer {
if (!this.hasInstance(typeName, _EnumTypeComposer)) {
const etc = this.EnumTypeComposer.create(typeName);
this.set(typeName, etc);
if (onCreate && isFunction(onCreate)) onCreate(etc);
}
return (this.get(typeName): any);
}

// disable redundant noise in console.logs
toString(): string {
// disable redundant noise in console.logs
return 'SchemaComposer';
}

toJSON() {
// disable redundant noise in console.logs
return 'SchemaComposer';
}

inspect() {
// disable redundant noise in console.logs
return 'SchemaComposer';
}
}
154 changes: 125 additions & 29 deletions src/__tests__/SchemaComposer-test.js
@@ -1,74 +1,170 @@
/* @flow strict */

import { SchemaComposer } from '../';
import { TypeComposer } from '../TypeComposer';
import { InputTypeComposer } from '../InputTypeComposer';
import { EnumTypeComposer } from '../EnumTypeComposer';

describe('Storage [Class]', () => {
describe('SchemaComposer', () => {
it('should implements `add` method', () => {
const storage = new SchemaComposer();
const someTC = storage.TypeComposer.create({ name: 'validType' });
storage.add(someTC);
expect(storage.get('validType')).toBe(someTC);
const sc = new SchemaComposer();
const SomeTC = sc.TypeComposer.create({ name: 'validType' });
sc.add(SomeTC);
expect(sc.get('validType')).toBe(SomeTC);
});

it('should implements `get` method', () => {
const storage = new SchemaComposer();
const someTC = storage.TypeComposer.create({ name: 'validType' });
storage.add(someTC);
expect(storage.get('validType')).toBe(someTC);
const sc = new SchemaComposer();
const SomeTC = sc.TypeComposer.create({ name: 'validType' });
sc.add(SomeTC);
expect(sc.get('validType')).toBe(SomeTC);
});

it('should implements `has` method`', () => {
const storage = new SchemaComposer();
const someTC = storage.TypeComposer.create({ name: 'validType' });
storage.add(someTC);
expect(storage.has('validType')).toBe(true);
expect(storage.has('unexistedType')).toBe(false);
const sc = new SchemaComposer();
const SomeTC = sc.TypeComposer.create({ name: 'validType' });
sc.add(SomeTC);
expect(sc.has('validType')).toBe(true);
expect(sc.has('unexistedType')).toBe(false);
});

describe('getOrCreateTC()', () => {
it('should create TC if not exists', () => {
const sc = new SchemaComposer();
const UserTC = sc.getOrCreateTC('User');
expect(UserTC).toBeInstanceOf(TypeComposer);
expect(sc.has('User')).toBeTruthy();
expect(sc.hasInstance('User', TypeComposer)).toBeTruthy();
expect(sc.getTC('User')).toBe(UserTC);
});

it('should create TC if not exists with onCreate', () => {
const sc = new SchemaComposer();
const UserTC = sc.getOrCreateTC('User', tc => {
tc.setDescription('User model');
});
expect(UserTC.getDescription()).toBe('User model');
});

it('should return already created TC without onCreate', () => {
const sc = new SchemaComposer();
const UserTC = sc.getOrCreateTC('User', tc => {
tc.setDescription('User model');
});
const UserTC2 = sc.getOrCreateTC('User', tc => {
tc.setDescription('updated description');
});
expect(UserTC).toBe(UserTC2);
expect(UserTC.getDescription()).toBe('User model');
});
});

describe('getOrCreateITC()', () => {
it('should create ITC if not exists', () => {
const sc = new SchemaComposer();
const UserITC = sc.getOrCreateITC('UserInput');
expect(UserITC).toBeInstanceOf(InputTypeComposer);
expect(sc.has('UserInput')).toBeTruthy();
expect(sc.hasInstance('UserInput', InputTypeComposer)).toBeTruthy();
expect(sc.getITC('UserInput')).toBe(UserITC);
});

it('should create ITC if not exists with onCreate', () => {
const sc = new SchemaComposer();
const UserITC = sc.getOrCreateITC('UserInput', tc => {
tc.setDescription('User input');
});
expect(UserITC.getDescription()).toBe('User input');
});

it('should return already created ITC without onCreate', () => {
const sc = new SchemaComposer();
const UserITC = sc.getOrCreateITC('UserInput', tc => {
tc.setDescription('User input');
});
const UserITC2 = sc.getOrCreateITC('UserInput', tc => {
tc.setDescription('updated description');
});
expect(UserITC).toBe(UserITC2);
expect(UserITC.getDescription()).toBe('User input');
});
});

describe('getOrCreateETC()', () => {
it('should create ETC if not exists', () => {
const sc = new SchemaComposer();
const UserETC = sc.getOrCreateETC('UserEnum');
expect(UserETC).toBeInstanceOf(EnumTypeComposer);
expect(sc.has('UserEnum')).toBeTruthy();
expect(sc.hasInstance('UserEnum', EnumTypeComposer)).toBeTruthy();
expect(sc.getETC('UserEnum')).toBe(UserETC);
});

it('should create ETC if not exists with onCreate', () => {
const sc = new SchemaComposer();
const UserETC = sc.getOrCreateETC('UserEnum', tc => {
tc.setDescription('User enum');
});
expect(UserETC.getDescription()).toBe('User enum');
});

it('should return already created ETC without onCreate', () => {
const sc = new SchemaComposer();
const UserETC = sc.getOrCreateETC('UserEnum', tc => {
tc.setDescription('User enum');
});
const UserETC2 = sc.getOrCreateETC('UserEnum', tc => {
tc.setDescription('updated description');
});
expect(UserETC).toBe(UserETC2);
expect(UserETC.getDescription()).toBe('User enum');
});
});

describe('buildSchema()', () => {
it('should throw error, if root fields not defined', () => {
const storage = new SchemaComposer();
storage.clear();
const sc = new SchemaComposer();
sc.clear();

expect(() => {
storage.buildSchema();
sc.buildSchema();
}).toThrowError();
});
});

describe('removeEmptyTypes()', () => {
it('should remove fields with Types which have no fields', () => {
const storage = new SchemaComposer();
const typeWithoutFieldsTC = storage.getOrCreateTC('Stub');
typeWithoutFieldsTC.setFields({});
const sc = new SchemaComposer();
const TypeWithoutFieldsTC = sc.getOrCreateTC('Stub');
TypeWithoutFieldsTC.setFields({});

const viewerTC = storage.getOrCreateTC('Viewer');
viewerTC.setFields({
const ViewerTC = sc.getOrCreateTC('Viewer');
ViewerTC.setFields({
name: 'String',
stub: typeWithoutFieldsTC,
stub: TypeWithoutFieldsTC,
});

/* eslint-disable */
const oldConsoleLog = console.log;
global.console.log = jest.fn();

storage.removeEmptyTypes(viewerTC);
sc.removeEmptyTypes(ViewerTC);

expect(console.log).lastCalledWith(
"GQC: Delete field 'Viewer.stub' with type 'Stub', cause it does not have fields.",
);
global.console.log = oldConsoleLog;
/* eslint-enable */

expect(viewerTC.hasField('stub')).toBe(false);
expect(ViewerTC.hasField('stub')).toBe(false);
});

it('should not produce Maximum call stack size exceeded', () => {
const storage = new SchemaComposer();
const userTC = storage.getOrCreateTC('User');
userTC.setField('friend', userTC);
const sc = new SchemaComposer();
const UserTC = sc.getOrCreateTC('User');
UserTC.setField('friend', UserTC);

storage.removeEmptyTypes(userTC);
sc.removeEmptyTypes(UserTC);
});
});
});

0 comments on commit 0edb902

Please sign in to comment.