diff --git a/src/__tests__/inputTypeComposer-test.js b/src/__tests__/inputTypeComposer-test.js index ffb35cf9..b88e1199 100644 --- a/src/__tests__/inputTypeComposer-test.js +++ b/src/__tests__/inputTypeComposer-test.js @@ -49,14 +49,41 @@ describe('InputTypeComposer', () => { expect(fieldNames).to.include('input3'); }); - it('setFields()', () => { - const tc = new InputTypeComposer(objectType); - tc.setFields({ - input3: { type: GraphQLString }, - input4: { type: GraphQLString }, + describe('setFields()', () => { + it('accept regular fields definition', () => { + const tc = new InputTypeComposer(objectType); + tc.setFields({ + input3: { type: GraphQLString }, + input4: { type: GraphQLString }, + }); + expect(tc.getFieldNames()).to.not.have.members(['input1', 'input2']); + expect(tc.getFieldNames()).to.have.members(['input3', 'input4']); + expect(tc.getFieldType('input3')).to.equal(GraphQLString); + expect(tc.getFieldType('input4')).to.equal(GraphQLString); + }); + + it('accept shortand fields definition', () => { + const tc = new InputTypeComposer(objectType); + tc.setFields({ + input3: GraphQLString, + input4: 'String', + }); + expect(tc.getFieldType('input3')).to.equal(GraphQLString); + expect(tc.getFieldType('input4')).to.equal(GraphQLString); + }); + + it('accept types as function', () => { + const tc = new InputTypeComposer(objectType); + const typeAsFn = () => GraphQLString; + tc.setFields({ + input3: { type: typeAsFn }, + }); + expect(tc.getFieldType('input3')).to.equal(typeAsFn); + + // show provide unwrapped/unhoisted type for graphql + expect(tc.getType()._typeConfig.fields().input3.type) + .to.equal(GraphQLString); }); - expect(tc.getFieldNames()).to.not.have.members(['input1', 'input2']); - expect(tc.getFieldNames()).to.have.members(['input3', 'input4']); }); it('addFields()', () => { diff --git a/src/__tests__/typeComposer-test.js b/src/__tests__/typeComposer-test.js index 7d898447..b68b539b 100644 --- a/src/__tests__/typeComposer-test.js +++ b/src/__tests__/typeComposer-test.js @@ -96,6 +96,18 @@ describe('TypeComposer', () => { field5: { field4: true }, }); }); + + it('accept types as function', () => { + const typeAsFn = () => GraphQLString; + tc.setFields({ + input3: { type: typeAsFn }, + }); + expect(tc.getFieldType('input3')).to.equal(typeAsFn); + + // show provide unwrapped/unhoisted type for graphql + expect(tc.getType()._typeConfig.fields().input3.type) + .to.equal(GraphQLString); + }); }); it('addFields()', () => { diff --git a/src/inputTypeComposer.js b/src/inputTypeComposer.js index 5b04a66f..ab0ab2e9 100644 --- a/src/inputTypeComposer.js +++ b/src/inputTypeComposer.js @@ -7,6 +7,7 @@ import { import { resolveMaybeThunk } from './utils/misc'; import { deprecate } from './utils/debug'; import { isObject, isString } from './utils/is'; +import { unwrapFieldsType, wrapFieldsType } from './utils/typeAsFn'; import TypeMapper from './typeMapper'; import { typeByPath } from './typeByPath'; @@ -85,7 +86,8 @@ export default class InputTypeComposer { const fields: Thunk = this.gqType._typeConfig.fields; - const fieldMap:mixed = resolveMaybeThunk(fields); + // $FlowFixMe + const fieldMap:mixed = wrapFieldsType(resolveMaybeThunk(fields)); if (isObject(fieldMap)) { // $FlowFixMe @@ -113,7 +115,8 @@ export default class InputTypeComposer { this.getTypeName() ); - this.gqType._typeConfig.fields = () => prepearedFields; + // $FlowFixMe + this.gqType._typeConfig.fields = () => unwrapFieldsType(prepearedFields); delete this.gqType._fields; // if schema was builded, delete defineFieldMap } diff --git a/src/typeComposer.js b/src/typeComposer.js index 86c5f3f9..89933b63 100644 --- a/src/typeComposer.js +++ b/src/typeComposer.js @@ -7,6 +7,7 @@ import { } from 'graphql'; import { resolveMaybeThunk } from './utils/misc'; import { isObject, isFunction, isString } from './utils/is'; +import { unwrapFieldsType, wrapFieldsType } from './utils/typeAsFn'; import { deprecate } from './utils/debug'; import Resolver from './resolver'; import { toInputObjectType } from './toInputObjectType'; @@ -95,7 +96,8 @@ export default class TypeComposer { const fields: Thunk> = this.gqType._typeConfig.fields; - const fieldMap:mixed = resolveMaybeThunk(fields); + // $FlowFixMe + const fieldMap:mixed = wrapFieldsType(resolveMaybeThunk(fields)); if (isObject(fieldMap)) { // $FlowFixMe @@ -118,7 +120,6 @@ export default class TypeComposer { this.getTypeName() ); - this.gqType._typeConfig.fields = () => prepearedFields; // if field has a projection option, then add it to projection mapper Object.keys(prepearedFields).forEach((name) => { if (prepearedFields[name].projection) { @@ -128,6 +129,8 @@ export default class TypeComposer { } }); + // $FlowFixMe + this.gqType._typeConfig.fields = () => unwrapFieldsType(prepearedFields); delete this.gqType._fields; // clear builded fields in type } diff --git a/src/utils/__tests__/typeAsFn-test.js b/src/utils/__tests__/typeAsFn-test.js new file mode 100644 index 00000000..9f612648 --- /dev/null +++ b/src/utils/__tests__/typeAsFn-test.js @@ -0,0 +1,60 @@ +import { expect } from 'chai'; +import { GraphQLString, GraphQLObjectType } from 'graphql'; +import { unwrapFieldsType, wrapFieldsType } from '../typeAsFn'; + + +describe('typeAsFn', () => { + describe('unwrapFieldsType()', () => { + it('should unwrap types from functions', () => { + const fieldMap = { + f1: { + type: GraphQLString, + }, + f2: { + type: new GraphQLObjectType({ + name: 'MyType', + fields: { + f11: { type: GraphQLString }, + }, + }), + }, + f3: { + type: () => GraphQLString, + }, + }; + const unwrapped = unwrapFieldsType(fieldMap); + expect(unwrapped.f1.type).to.equal(GraphQLString); + expect(unwrapped.f2.type).to.instanceOf(GraphQLObjectType); + expect(unwrapped.f3.type).to.equal(GraphQLString); + expect(unwrapped.f3._typeFn).to.be.ok; + expect(unwrapped.f3._typeFn()).to.equal(GraphQLString); + }); + }); + + describe('wrapFieldsType()', () => { + it('should set _typeFn to type', () => { + const unwrapped = { + f1: { + type: GraphQLString, + }, + f2: { + type: new GraphQLObjectType({ + name: 'MyType', + fields: { + f11: { type: GraphQLString }, + }, + }), + }, + f3: { + type: GraphQLString, + _typeFn: () => GraphQLString, + }, + }; + const wrapped = wrapFieldsType(unwrapped); + expect(wrapped.f1.type).to.equal(GraphQLString); + expect(wrapped.f2.type).to.instanceOf(GraphQLObjectType); + expect(wrapped.f3.type).to.be.ok; + expect(wrapped.f3.type()).to.equal(GraphQLString); + }); + }); +}); diff --git a/src/utils/typeAsFn.js b/src/utils/typeAsFn.js new file mode 100644 index 00000000..a593ffbe --- /dev/null +++ b/src/utils/typeAsFn.js @@ -0,0 +1,36 @@ +/* @flow */ +/* eslint-disable no-param-reassign */ + +import type { + GraphQLInputFieldMap, + GraphQLFieldMap, +} from 'graphql/type/definition'; +import { isFunction } from './is'; + +export type FieldMaps = GraphQLInputFieldMap | GraphQLFieldMap<*, *>; + +export function unwrapFieldsType( + fieldMap: T +): T { + Object.keys(fieldMap).forEach(key => { + if (isFunction(fieldMap[key].type)) { + // $FlowFixMe + fieldMap[key]._typeFn = fieldMap[key].type; + // $FlowFixMe + fieldMap[key].type = fieldMap[key].type(); + } + }); + return fieldMap; +} + +export function wrapFieldsType( + fieldMap: T +): T { + Object.keys(fieldMap).forEach(key => { + if (fieldMap[key]._typeFn) { + // $FlowFixMe + fieldMap[key].type = fieldMap[key]._typeFn; + } + }); + return fieldMap; +}