From 68334fd294b7e17e87814082c6cf2a037156c0fd Mon Sep 17 00:00:00 2001 From: Alexandre Lacheze Date: Tue, 26 Mar 2019 11:36:46 +0100 Subject: [PATCH] fix(resolvers): allow undefined as return value Closes #17 --- src/__tests__/graphqlObservable-test.ts | 23 +++++++++++++++++++++-- src/jstutils/isNullish.ts | 8 ++++++++ src/reactive-graphql.ts | 6 +++++- 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 src/jstutils/isNullish.ts diff --git a/src/__tests__/graphqlObservable-test.ts b/src/__tests__/graphqlObservable-test.ts index 28eb6af..39bf704 100644 --- a/src/__tests__/graphqlObservable-test.ts +++ b/src/__tests__/graphqlObservable-test.ts @@ -88,6 +88,7 @@ const fieldResolverSchema = makeExecutableSchema({ type Plain { noFieldResolver: String! fieldResolver: String! + fieldResolvesUndefined: String! giveMeTheParentFieldResolver: String! giveMeTheArgsFieldResolver(arg: String!): String! giveMeTheContextFieldResolver: String! @@ -126,6 +127,9 @@ const fieldResolverSchema = makeExecutableSchema({ fieldResolver() { return of("I am a field resolver"); }, + fieldResolvesUndefined() { + return of(undefined); + }, giveMeTheParentFieldResolver(parent) { return of(JSON.stringify(parent)); }, @@ -250,7 +254,7 @@ describe("graphqlObservable", function() { } `; - const expectedData = [{ name: "apollo11" }, { name: "challenger" }]; + const expectedData = [{ name: "apollo11", firstFlight: null }, { name: "challenger", firstFlight: null }]; const dataSource = of(expectedData); const expected = m.cold("(a|)", { a: { data: { launched: [expectedData[0]] } } @@ -282,7 +286,7 @@ describe("graphqlObservable", function() { } `; - const expectedData = [{ name: "apollo13" }, { name: "challenger" }]; + const expectedData = [{ name: "apollo13", firstFlight: null }, { name: "challenger", firstFlight: null }]; const dataSource = of(expectedData); const expected = m.cold("(a|)", { a: { data: { launched: [expectedData[0]] } } @@ -400,6 +404,21 @@ describe("graphqlObservable", function() { m.expect(result.pipe(take(1))).toBeObservable(expected); }); + itMarbles("if defined but returns undefined, field is null", function (m) { + const query = gql` + query { + plain { + fieldResolvesUndefined + } + } + `; + const expected = m.cold("(a|)", { + a: { data: { plain: { fieldResolvesUndefined: null } } } + }); + const result = graphql(fieldResolverSchema, query, null, {}); + m.expect(result.pipe(take(1))).toBeObservable(expected); + }); + itMarbles("the field resolvers 1st argument is parent", function(m) { const query = gql` query { diff --git a/src/jstutils/isNullish.ts b/src/jstutils/isNullish.ts new file mode 100644 index 0000000..d123ad7 --- /dev/null +++ b/src/jstutils/isNullish.ts @@ -0,0 +1,8 @@ +// inspired from https://github.com/graphql/graphql-js/blob/926e4d80c558b107c49e9403e943086fa9b043a8/src/jsutils/isNullish.js + +/** + * Returns true if a value is null, undefined, or NaN. + */ +export default function isNullish(value: any) { + return value === null || value === undefined || value !== value; +}; diff --git a/src/reactive-graphql.ts b/src/reactive-graphql.ts index 079c516..e6654ef 100644 --- a/src/reactive-graphql.ts +++ b/src/reactive-graphql.ts @@ -24,6 +24,8 @@ import { parse, } from "graphql"; +import isNullish from './jstutils/isNullish'; + // WARNING: This is NOT a spec complete graphql implementation // https://facebook.github.io/graphql/October2016/ @@ -136,7 +138,9 @@ export default function graphql( context, variables, parent - ); + ) + // If result value is null-ish (null, undefined, or NaN) then return null. + .pipe(map(value => isNullish(value) ? null : value)); // Directly return the leaf nodes if (definition.selectionSet === undefined) {