From 094654a64f7f59e5d5e832150d51fa164125b56a Mon Sep 17 00:00:00 2001 From: Gabriele Date: Fri, 20 Aug 2021 22:11:32 +0200 Subject: [PATCH] feat(typedefs-resolvers): add sync function version (#803) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: MichaƂ Lytek --- CHANGELOG.md | 1 + docs/bootstrap.md | 10 +- src/utils/buildTypeDefsAndResolvers.ts | 13 +- src/utils/index.ts | 5 +- tests/functional/typedefs-resolvers.ts | 986 ++++++++++++++----------- 5 files changed, 560 insertions(+), 455 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 214fc0c98..048c2c377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - allow deprecating input fields and args (#794) - support disabling inferring default values (#793) - support readonly arrays for roles of `@Authorized` decorator (#935) +- add sync version of `buildTypeDefsAndResolvers` function (#803) ### Fixes - **Breaking Change**: properly emit types nullability when `defaultValue` is provided and remove `ConflictingDefaultWithNullableError` error (#751) - allow defining extension on field resolver level for fields also defined as a property of the class (#776) diff --git a/docs/bootstrap.md b/docs/bootstrap.md index 90f09faa7..433bc003e 100644 --- a/docs/bootstrap.md +++ b/docs/bootstrap.md @@ -139,4 +139,12 @@ const stateLink = withClientState({ // ...the rest of `ApolloClient` initialization code ``` -Be aware that some of the TypeGraphQL features (i.a. [query complexity](complexity.md)) might not work with the `buildTypeDefsAndResolvers` approach because they use some low-level `graphql-js` features. +There's also a sync version of it - `buildTypeDefsAndResolversSync`: + +```typescript +const { typeDefs, resolvers } = buildTypeDefsAndResolvers({ + resolvers: [FirstResolver, SecondResolver], +}); +``` + +However, be aware that some of the TypeGraphQL features (i.a. [query complexity](complexity.md)) might not work with the `buildTypeDefsAndResolvers` approach because they use some low-level `graphql-js` features. diff --git a/src/utils/buildTypeDefsAndResolvers.ts b/src/utils/buildTypeDefsAndResolvers.ts index abafb39df..5c0ada7ab 100644 --- a/src/utils/buildTypeDefsAndResolvers.ts +++ b/src/utils/buildTypeDefsAndResolvers.ts @@ -1,10 +1,19 @@ -import { printSchema } from "graphql"; +import { GraphQLSchema, printSchema } from "graphql"; -import { BuildSchemaOptions, buildSchema } from "./buildSchema"; +import { BuildSchemaOptions, buildSchema, buildSchemaSync } from "./buildSchema"; import { createResolversMap } from "./createResolversMap"; export async function buildTypeDefsAndResolvers(options: BuildSchemaOptions) { const schema = await buildSchema(options); + return createTypeDefsAndResolversMap(schema); +} + +export function buildTypeDefsAndResolversSync(options: BuildSchemaOptions) { + const schema = buildSchemaSync(options); + return createTypeDefsAndResolversMap(schema); +} + +function createTypeDefsAndResolversMap(schema: GraphQLSchema) { const typeDefs = printSchema(schema); const resolvers = createResolversMap(schema); return { typeDefs, resolvers }; diff --git a/src/utils/index.ts b/src/utils/index.ts index 20f8f4660..fae4db30f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,5 +1,8 @@ export { buildSchema, buildSchemaSync, BuildSchemaOptions } from "./buildSchema"; -export { buildTypeDefsAndResolvers } from "./buildTypeDefsAndResolvers"; +export { + buildTypeDefsAndResolvers, + buildTypeDefsAndResolversSync, +} from "./buildTypeDefsAndResolvers"; export { createResolversMap } from "./createResolversMap"; export { emitSchemaDefinitionFile, diff --git a/tests/functional/typedefs-resolvers.ts b/tests/functional/typedefs-resolvers.ts index e2dfcfdd0..685c68d1b 100644 --- a/tests/functional/typedefs-resolvers.ts +++ b/tests/functional/typedefs-resolvers.ts @@ -8,7 +8,6 @@ import { TypeKind, IntrospectionObjectType, IntrospectionInputObjectType, - Kind, IntrospectionNamedTypeRef, IntrospectionEnumType, IntrospectionUnionType, @@ -29,6 +28,7 @@ import { Resolver, Query, buildTypeDefsAndResolvers, + buildTypeDefsAndResolversSync, InterfaceType, ObjectType, Field, @@ -47,557 +47,641 @@ import { ResolverOptions, } from "../../src"; -describe("buildTypeDefsAndResolvers", () => { - const timestamp = 1547398942902; - let typeDefs: string; - let resolvers: ResolversMap; - let schemaIntrospection: IntrospectionSchema; - let schema: GraphQLSchema; - let pubSub: PubSubEngine; - let inputValue: any; - let enumValue: any; - let middlewareLogs: string[]; - - beforeEach(async () => { - middlewareLogs = []; - enumValue = undefined; - }); - - beforeAll(async () => { - getMetadataStorage().clear(); - - @Service() - class SampleService { - getSampleString() { - return "SampleString"; - } - } - - @InterfaceType() - abstract class SampleInterface { - @Field() - sampleInterfaceStringField: string; - } - - @ObjectType({ implements: SampleInterface }) - class SampleType1 implements SampleInterface { - @Field() - sampleInterfaceStringField: string; - @Field({ description: "sampleType1StringFieldDescription" }) - sampleType1StringField: string; - } - - @ObjectType({ implements: SampleInterface }) - class SampleType2 implements SampleInterface { - @Field() - sampleInterfaceStringField: string; - @Field({ deprecationReason: "sampleType2StringFieldDeprecation" }) - sampleType2StringField: string; - } - - @ObjectType() - class SampleType3 { - @Field() - sampleInterfaceStringField: string; - @Field() - sampleType3StringField: string; - } - - @InputType() - class SampleInput { - @Field() - @MinLength(10) - sampleInputStringField: string; - @Field() - sampleInputDefaultStringField: string = "sampleInputDefaultStringField"; - } - - enum SampleNumberEnum { - OptionOne, - OptionTwo, - } - registerEnumType(SampleNumberEnum, { name: "SampleNumberEnum" }); - - enum SampleStringEnum { - OptionOne = "OptionOneString", - OptionTwo = "OptionTwoString", - } - registerEnumType(SampleStringEnum, { name: "SampleStringEnum" }); - - const SampleUnion = createUnionType({ - types: () => [SampleType2, SampleType3], - name: "SampleUnion", - description: "SampleUnion description", +describe("typeDefs and resolvers", () => { + describe("buildTypeDefsAndResolvers", () => { + const timestamp = 1547398942902; + let typeDefs: string; + let resolvers: ResolversMap; + let schemaIntrospection: IntrospectionSchema; + let schema: GraphQLSchema; + let pubSub: PubSubEngine; + let inputValue: any; + let enumValue: any; + let middlewareLogs: string[]; + + beforeEach(async () => { + middlewareLogs = []; + enumValue = undefined; }); - const SampleResolveUnion = createUnionType({ - types: () => [SampleType2, SampleType3], - name: "SampleResolveUnion", - resolveType: value => { - if ("sampleType2StringField" in value) { - return "SampleType2"; - } - if ("sampleType3StringField" in value) { - return "SampleType3"; + beforeAll(async () => { + getMetadataStorage().clear(); + + @Service() + class SampleService { + getSampleString() { + return "SampleString"; } - return; - }, - }); + } - @Service() - @Resolver() - class SampleResolver { - constructor(private readonly sampleService: SampleService) {} + @InterfaceType() + abstract class SampleInterface { + @Field() + sampleInterfaceStringField: string; + } - @Query({ description: "sampleDateQueryDescription" }) - sampleDateQuery(): Date { - return new Date(timestamp); + @ObjectType({ implements: SampleInterface }) + class SampleType1 implements SampleInterface { + @Field() + sampleInterfaceStringField: string; + @Field({ description: "sampleType1StringFieldDescription" }) + sampleType1StringField: string; } - @Query() - sampleServiceQuery(): string { - return this.sampleService.getSampleString(); + @ObjectType({ implements: SampleInterface }) + class SampleType2 implements SampleInterface { + @Field() + sampleInterfaceStringField: string; + @Field({ deprecationReason: "sampleType2StringFieldDeprecation" }) + sampleType2StringField: string; } - @Query() - @UseMiddleware(async (_, next) => { - middlewareLogs.push("sampleMiddlewareBooleanQuery"); - return next(); - }) - sampleMiddlewareBooleanQuery(): boolean { - return true; + @ObjectType() + class SampleType3 { + @Field() + sampleInterfaceStringField: string; + @Field() + sampleType3StringField: string; } - @Mutation() - sampleBooleanMutation(): boolean { - return true; + @InputType() + class SampleInput { + @Field() + @MinLength(10) + sampleInputStringField: string; + @Field() + sampleInputDefaultStringField: string = "sampleInputDefaultStringField"; } - @Mutation() - sampleMutationWithInput(@Arg("input") input: SampleInput): boolean { - inputValue = input; - return true; + enum SampleNumberEnum { + OptionOne, + OptionTwo, } + registerEnumType(SampleNumberEnum, { name: "SampleNumberEnum" }); - @Mutation() - @Authorized() - sampleAuthorizedMutation(): boolean { - return true; + enum SampleStringEnum { + OptionOne = "OptionOneString", + OptionTwo = "OptionTwoString", } + registerEnumType(SampleStringEnum, { name: "SampleStringEnum" }); - @Query() - sampleInterfaceQuery(): SampleInterface { - const type1 = new SampleType1(); - type1.sampleInterfaceStringField = "sampleInterfaceStringField"; - type1.sampleType1StringField = "sampleType1StringField"; + const SampleUnion = createUnionType({ + types: () => [SampleType2, SampleType3], + name: "SampleUnion", + description: "SampleUnion description", + }); - return type1; - } + const SampleResolveUnion = createUnionType({ + types: () => [SampleType2, SampleType3], + name: "SampleResolveUnion", + resolveType: value => { + if ("sampleType2StringField" in value) { + return "SampleType2"; + } + if ("sampleType3StringField" in value) { + return "SampleType3"; + } + return; + }, + }); - @Query(returns => SampleUnion) - sampleUnionQuery(): typeof SampleUnion { - const type3 = new SampleType3(); - type3.sampleInterfaceStringField = "sampleInterfaceStringField"; - type3.sampleType3StringField = "sampleType3StringField"; + @Service() + @Resolver() + class SampleResolver { + constructor(private readonly sampleService: SampleService) {} - return type3; - } + @Query({ description: "sampleDateQueryDescription" }) + sampleDateQuery(): Date { + return new Date(timestamp); + } - @Query(returns => SampleResolveUnion) - sampleResolveUnionQuery(): typeof SampleResolveUnion { - return { - sampleInterfaceStringField: "sampleInterfaceStringField", - sampleType3StringField: "sampleType3StringField", - }; - } + @Query() + sampleServiceQuery(): string { + return this.sampleService.getSampleString(); + } - @Query(returns => SampleNumberEnum) - sampleNumberEnumQuery( - @Arg("numberEnum", type => SampleNumberEnum) numberEnum: SampleNumberEnum, - ): SampleNumberEnum { - enumValue = numberEnum; - return numberEnum; - } + @Query() + @UseMiddleware(async (_, next) => { + middlewareLogs.push("sampleMiddlewareBooleanQuery"); + return next(); + }) + sampleMiddlewareBooleanQuery(): boolean { + return true; + } - @Query(returns => SampleStringEnum) - sampleStringEnumQuery( - @Arg("stringEnum", type => SampleStringEnum) stringEnum: SampleStringEnum, - ): SampleStringEnum { - enumValue = stringEnum; - return stringEnum; - } + @Mutation() + sampleBooleanMutation(): boolean { + return true; + } - @Subscription({ - topics: "SAMPLE", - }) - sampleSubscription(@Root() payload: number): number { - return payload; - } - } - - pubSub = new PubSub(); - ({ typeDefs, resolvers } = await buildTypeDefsAndResolvers({ - resolvers: [SampleResolver], - authChecker: () => false, - pubSub, - container: Container, - orphanedTypes: [SampleType1], - })); - schema = makeExecutableSchema({ - typeDefs, - resolvers, - }); - const introspectionResult = await graphql(schema, getIntrospectionQuery()); - schemaIntrospection = (introspectionResult.data as IntrospectionQuery).__schema; - }); + @Mutation() + sampleMutationWithInput(@Arg("input") input: SampleInput): boolean { + inputValue = input; + return true; + } - it("should generate schema without errors", () => { - expect(schemaIntrospection).toBeDefined(); - }); + @Mutation() + @Authorized() + sampleAuthorizedMutation(): boolean { + return true; + } - describe("typeDefs", () => { - it("should generate typeDefs correctly", async () => { - expect(typeDefs).toBeDefined(); - }); + @Query() + sampleInterfaceQuery(): SampleInterface { + const type1 = new SampleType1(); + type1.sampleInterfaceStringField = "sampleInterfaceStringField"; + type1.sampleType1StringField = "sampleType1StringField"; - it("should generate interface type", async () => { - const sampleInterface = schemaIntrospection.types.find( - it => it.name === "SampleInterface", - ) as IntrospectionInterfaceType; - - expect(sampleInterface.kind).toBe(TypeKind.INTERFACE); - expect(sampleInterface.fields).toHaveLength(1); - expect(sampleInterface.fields[0].name).toBe("sampleInterfaceStringField"); - expect(sampleInterface.possibleTypes).toHaveLength(2); - expect(sampleInterface.possibleTypes.map(it => it.name)).toContain("SampleType1"); - expect(sampleInterface.possibleTypes.map(it => it.name)).toContain("SampleType2"); - }); + return type1; + } - it("should generate object types", async () => { - const sampleType1 = schemaIntrospection.types.find( - it => it.name === "SampleType1", - ) as IntrospectionObjectType; - const sampleType2 = schemaIntrospection.types.find( - it => it.name === "SampleType2", - ) as IntrospectionObjectType; - const sampleType1StringField = sampleType1.fields.find( - it => it.name === "sampleType1StringField", - )!; - const sampleType2StringField = sampleType2.fields.find( - it => it.name === "sampleType2StringField", - )!; - - expect(sampleType1.kind).toBe(TypeKind.OBJECT); - expect(sampleType1.fields).toHaveLength(2); - expect(sampleType1StringField.description).toEqual("sampleType1StringFieldDescription"); - expect(sampleType1.interfaces).toHaveLength(1); - expect(sampleType1.interfaces[0].name).toBe("SampleInterface"); - expect(sampleType2StringField.deprecationReason).toBe("sampleType2StringFieldDeprecation"); - }); + @Query(returns => SampleUnion) + sampleUnionQuery(): typeof SampleUnion { + const type3 = new SampleType3(); + type3.sampleInterfaceStringField = "sampleInterfaceStringField"; + type3.sampleType3StringField = "sampleType3StringField"; - it("should generate input type", async () => { - const sampleInput = schemaIntrospection.types.find( - it => it.name === "SampleInput", - ) as IntrospectionInputObjectType; - const sampleInputDefaultStringField = sampleInput.inputFields.find( - it => it.name === "sampleInputDefaultStringField", - )!; - const sampleInputDefaultStringFieldType = sampleInputDefaultStringField.type as IntrospectionNamedTypeRef; - - expect(sampleInput.kind).toBe(TypeKind.INPUT_OBJECT); - expect(sampleInput.inputFields).toHaveLength(2); - expect(sampleInputDefaultStringFieldType).toEqual({ - kind: "NON_NULL", - name: null, - ofType: { - kind: "SCALAR", - name: "String", - ofType: null, - }, + return type3; + } + + @Query(returns => SampleResolveUnion) + sampleResolveUnionQuery(): typeof SampleResolveUnion { + return { + sampleInterfaceStringField: "sampleInterfaceStringField", + sampleType3StringField: "sampleType3StringField", + }; + } + + @Query(returns => SampleNumberEnum) + sampleNumberEnumQuery( + @Arg("numberEnum", type => SampleNumberEnum) numberEnum: SampleNumberEnum, + ): SampleNumberEnum { + enumValue = numberEnum; + return numberEnum; + } + + @Query(returns => SampleStringEnum) + sampleStringEnumQuery( + @Arg("stringEnum", type => SampleStringEnum) stringEnum: SampleStringEnum, + ): SampleStringEnum { + enumValue = stringEnum; + return stringEnum; + } + + @Subscription({ + topics: "SAMPLE", + }) + sampleSubscription(@Root() payload: number): number { + return payload; + } + } + + pubSub = new PubSub(); + ({ typeDefs, resolvers } = await buildTypeDefsAndResolvers({ + resolvers: [SampleResolver], + authChecker: () => false, + pubSub, + container: Container, + orphanedTypes: [SampleType1], + })); + schema = makeExecutableSchema({ + typeDefs, + resolvers, }); - expect(sampleInputDefaultStringField.defaultValue).toBe('"sampleInputDefaultStringField"'); + const introspectionResult = await graphql(schema, getIntrospectionQuery()); + schemaIntrospection = (introspectionResult.data as IntrospectionQuery).__schema; }); - it("should generate enum types", async () => { - const sampleNumberEnum = schemaIntrospection.types.find( - it => it.name === "SampleNumberEnum", - ) as IntrospectionEnumType; - const sampleStringEnum = schemaIntrospection.types.find( - it => it.name === "SampleStringEnum", - ) as IntrospectionEnumType; - - expect(sampleNumberEnum.kind).toBe(TypeKind.ENUM); - expect(sampleNumberEnum).toBeDefined(); - expect(sampleNumberEnum.enumValues).toHaveLength(2); - expect(sampleStringEnum.enumValues).toHaveLength(2); + it("should generate schema without errors", () => { + expect(schemaIntrospection).toBeDefined(); }); - it("should generate union type", async () => { - const sampleUnion = schemaIntrospection.types.find( - it => it.name === "SampleUnion", - ) as IntrospectionUnionType; + describe("typeDefs", () => { + it("should generate typeDefs correctly", async () => { + expect(typeDefs).toBeDefined(); + }); - expect(sampleUnion.kind).toBe(TypeKind.UNION); - expect(sampleUnion.description).toBe("SampleUnion description"); - expect(sampleUnion.possibleTypes).toHaveLength(2); - expect(sampleUnion.possibleTypes.map(it => it.name)).toContain("SampleType2"); - expect(sampleUnion.possibleTypes.map(it => it.name)).toContain("SampleType3"); - }); + it("should generate interface type", async () => { + const sampleInterface = schemaIntrospection.types.find( + it => it.name === "SampleInterface", + ) as IntrospectionInterfaceType; + + expect(sampleInterface.kind).toBe(TypeKind.INTERFACE); + expect(sampleInterface.fields).toHaveLength(1); + expect(sampleInterface.fields[0].name).toBe("sampleInterfaceStringField"); + expect(sampleInterface.possibleTypes).toHaveLength(2); + expect(sampleInterface.possibleTypes.map(it => it.name)).toContain("SampleType1"); + expect(sampleInterface.possibleTypes.map(it => it.name)).toContain("SampleType2"); + }); - it("should generate queries", async () => { - const queryType = schemaIntrospection.types.find( - it => it.name === schemaIntrospection.queryType.name, - ) as IntrospectionObjectType; + it("should generate object types", async () => { + const sampleType1 = schemaIntrospection.types.find( + it => it.name === "SampleType1", + ) as IntrospectionObjectType; + const sampleType2 = schemaIntrospection.types.find( + it => it.name === "SampleType2", + ) as IntrospectionObjectType; + const sampleType1StringField = sampleType1.fields.find( + it => it.name === "sampleType1StringField", + )!; + const sampleType2StringField = sampleType2.fields.find( + it => it.name === "sampleType2StringField", + )!; + + expect(sampleType1.kind).toBe(TypeKind.OBJECT); + expect(sampleType1.fields).toHaveLength(2); + expect(sampleType1StringField.description).toEqual("sampleType1StringFieldDescription"); + expect(sampleType1.interfaces).toHaveLength(1); + expect(sampleType1.interfaces[0].name).toBe("SampleInterface"); + expect(sampleType2StringField.deprecationReason).toBe("sampleType2StringFieldDeprecation"); + }); - expect(queryType.fields).toHaveLength(8); - }); + it("should generate input type", async () => { + const sampleInput = schemaIntrospection.types.find( + it => it.name === "SampleInput", + ) as IntrospectionInputObjectType; + const sampleInputDefaultStringField = sampleInput.inputFields.find( + it => it.name === "sampleInputDefaultStringField", + )!; + const sampleInputDefaultStringFieldType = sampleInputDefaultStringField.type as IntrospectionNamedTypeRef; + + expect(sampleInput.kind).toBe(TypeKind.INPUT_OBJECT); + expect(sampleInput.inputFields).toHaveLength(2); + expect(sampleInputDefaultStringFieldType).toEqual({ + kind: "NON_NULL", + name: null, + ofType: { + kind: "SCALAR", + name: "String", + ofType: null, + }, + }); + expect(sampleInputDefaultStringField.defaultValue).toBe('"sampleInputDefaultStringField"'); + }); - it("should generate mutations", async () => { - const mutationType = schemaIntrospection.types.find( - it => it.name === schemaIntrospection.mutationType!.name, - ) as IntrospectionObjectType; + it("should generate enum types", async () => { + const sampleNumberEnum = schemaIntrospection.types.find( + it => it.name === "SampleNumberEnum", + ) as IntrospectionEnumType; + const sampleStringEnum = schemaIntrospection.types.find( + it => it.name === "SampleStringEnum", + ) as IntrospectionEnumType; + + expect(sampleNumberEnum.kind).toBe(TypeKind.ENUM); + expect(sampleNumberEnum).toBeDefined(); + expect(sampleNumberEnum.enumValues).toHaveLength(2); + expect(sampleStringEnum.enumValues).toHaveLength(2); + }); - expect(mutationType.fields).toHaveLength(3); - }); + it("should generate union type", async () => { + const sampleUnion = schemaIntrospection.types.find( + it => it.name === "SampleUnion", + ) as IntrospectionUnionType; - it("should generate subscription", async () => { - const subscriptionType = schemaIntrospection.types.find( - it => it.name === schemaIntrospection.subscriptionType!.name, - ) as IntrospectionObjectType; + expect(sampleUnion.kind).toBe(TypeKind.UNION); + expect(sampleUnion.description).toBe("SampleUnion description"); + expect(sampleUnion.possibleTypes).toHaveLength(2); + expect(sampleUnion.possibleTypes.map(it => it.name)).toContain("SampleType2"); + expect(sampleUnion.possibleTypes.map(it => it.name)).toContain("SampleType3"); + }); - expect(subscriptionType.fields).toHaveLength(1); - }); + it("should generate queries", async () => { + const queryType = schemaIntrospection.types.find( + it => it.name === schemaIntrospection.queryType.name, + ) as IntrospectionObjectType; - it("should emit Date scalar", async () => { - const dateScalar = schemaIntrospection.types.find( - it => it.name === "DateTime", - ) as IntrospectionScalarType; + expect(queryType.fields).toHaveLength(8); + }); - expect(dateScalar.kind).toBe(TypeKind.SCALAR); - }); - }); + it("should generate mutations", async () => { + const mutationType = schemaIntrospection.types.find( + it => it.name === schemaIntrospection.mutationType!.name, + ) as IntrospectionObjectType; - describe("resolvers", () => { - it("should generate resolversMap without errors", async () => { - expect(resolvers).toBeDefined(); - }); + expect(mutationType.fields).toHaveLength(3); + }); - it("should not emit `__isTypeOf` for root objects", async () => { - expect(resolvers.Query).not.toHaveProperty("__isTypeOf"); - expect(resolvers.Mutation).not.toHaveProperty("__isTypeOf"); - expect(resolvers.Subscription).not.toHaveProperty("__isTypeOf"); - }); + it("should generate subscription", async () => { + const subscriptionType = schemaIntrospection.types.find( + it => it.name === schemaIntrospection.subscriptionType!.name, + ) as IntrospectionObjectType; - it("should properly serialize Date scalar", async () => { - const document = gql` - query { - sampleDateQuery - } - `; + expect(subscriptionType.fields).toHaveLength(1); + }); - const { data } = await execute(schema, document); - const parsedDate = new Date(data!.sampleDateQuery); + it("should emit Date scalar", async () => { + const dateScalar = schemaIntrospection.types.find( + it => it.name === "DateTime", + ) as IntrospectionScalarType; - expect(typeof data!.sampleDateQuery).toBe("string"); - expect(parsedDate.getTime()).toEqual(timestamp); + expect(dateScalar.kind).toBe(TypeKind.SCALAR); + }); }); - it("should use container to resolve dependency", async () => { - const document = gql` - query { - sampleServiceQuery - } - `; + describe("resolvers", () => { + it("should generate resolversMap without errors", async () => { + expect(resolvers).toBeDefined(); + }); - const { data } = await execute(schema, document); + it("should not emit `__isTypeOf` for root objects", async () => { + expect(resolvers.Query).not.toHaveProperty("__isTypeOf"); + expect(resolvers.Mutation).not.toHaveProperty("__isTypeOf"); + expect(resolvers.Subscription).not.toHaveProperty("__isTypeOf"); + }); - expect(data!.sampleServiceQuery).toEqual("SampleString"); - }); + it("should properly serialize Date scalar", async () => { + const document = gql` + query { + sampleDateQuery + } + `; - it("should run resolver method middleware", async () => { - const document = gql` - query { - sampleMiddlewareBooleanQuery - } - `; + const { data } = await execute(schema, document); + const parsedDate = new Date(data!.sampleDateQuery); - const { data } = await execute(schema, document); + expect(typeof data!.sampleDateQuery).toBe("string"); + expect(parsedDate.getTime()).toEqual(timestamp); + }); - expect(data!.sampleMiddlewareBooleanQuery).toEqual(true); - expect(middlewareLogs).toHaveLength(1); - expect(middlewareLogs[0]).toEqual("sampleMiddlewareBooleanQuery"); - }); + it("should use container to resolve dependency", async () => { + const document = gql` + query { + sampleServiceQuery + } + `; - it("should allow for simple boolean mutation", async () => { - const document = gql` - mutation { - sampleBooleanMutation - } - `; + const { data } = await execute(schema, document); - const { data } = await execute(schema, document); + expect(data!.sampleServiceQuery).toEqual("SampleString"); + }); - expect(data!.sampleBooleanMutation).toBe(true); - }); + it("should run resolver method middleware", async () => { + const document = gql` + query { + sampleMiddlewareBooleanQuery + } + `; - it("should properly transform input argument", async () => { - const document = gql` - mutation { - sampleMutationWithInput(input: { sampleInputStringField: "sampleInputStringField" }) - } - `; + const { data } = await execute(schema, document); - const { data } = await execute(schema, document); + expect(data!.sampleMiddlewareBooleanQuery).toEqual(true); + expect(middlewareLogs).toHaveLength(1); + expect(middlewareLogs[0]).toEqual("sampleMiddlewareBooleanQuery"); + }); - expect(data!.sampleMutationWithInput).toBe(true); - expect(inputValue.constructor.name).toBe("SampleInput"); - expect(inputValue.sampleInputStringField).toBe("sampleInputStringField"); - expect(inputValue.sampleInputDefaultStringField).toBe("sampleInputDefaultStringField"); - }); + it("should allow for simple boolean mutation", async () => { + const document = gql` + mutation { + sampleBooleanMutation + } + `; - it("should validate the input", async () => { - const document = gql` - mutation { - sampleMutationWithInput(input: { sampleInputStringField: "short" }) - } - `; + const { data } = await execute(schema, document); - const { errors } = await execute(schema, document); + expect(data!.sampleBooleanMutation).toBe(true); + }); - expect(errors).toHaveLength(1); - expect(errors![0].message).toContain("Argument Validation Error"); - }); + it("should properly transform input argument", async () => { + const document = gql` + mutation { + sampleMutationWithInput(input: { sampleInputStringField: "sampleInputStringField" }) + } + `; - it("should properly guard authorized resolver method", async () => { - const document = gql` - mutation { - sampleAuthorizedMutation - } - `; + const { data } = await execute(schema, document); - const { errors } = await execute(schema, document); + expect(data!.sampleMutationWithInput).toBe(true); + expect(inputValue.constructor.name).toBe("SampleInput"); + expect(inputValue.sampleInputStringField).toBe("sampleInputStringField"); + expect(inputValue.sampleInputDefaultStringField).toBe("sampleInputDefaultStringField"); + }); - expect(errors).toHaveLength(1); - expect(errors![0].message).toContain("Access denied"); - }); + it("should validate the input", async () => { + const document = gql` + mutation { + sampleMutationWithInput(input: { sampleInputStringField: "short" }) + } + `; - it("should detect returned object type from interface", async () => { - const document = gql` - query { - sampleInterfaceQuery { - sampleInterfaceStringField - ... on SampleType1 { - sampleType1StringField - } + const { errors } = await execute(schema, document); + + expect(errors).toHaveLength(1); + expect(errors![0].message).toContain("Argument Validation Error"); + }); + + it("should properly guard authorized resolver method", async () => { + const document = gql` + mutation { + sampleAuthorizedMutation } - } - `; + `; - const { data } = await execute(schema, document); + const { errors } = await execute(schema, document); - expect(data!.sampleInterfaceQuery).toEqual({ - sampleInterfaceStringField: "sampleInterfaceStringField", - sampleType1StringField: "sampleType1StringField", + expect(errors).toHaveLength(1); + expect(errors![0].message).toContain("Access denied"); }); - }); - it("should detect returned object type from union", async () => { - const document = gql` - query { - sampleUnionQuery { - ... on SampleType3 { + it("should detect returned object type from interface", async () => { + const document = gql` + query { + sampleInterfaceQuery { sampleInterfaceStringField - sampleType3StringField + ... on SampleType1 { + sampleType1StringField + } } } - } - `; + `; - const { data } = await execute(schema, document); + const { data } = await execute(schema, document); - expect(data!.sampleUnionQuery).toEqual({ - sampleInterfaceStringField: "sampleInterfaceStringField", - sampleType3StringField: "sampleType3StringField", + expect(data!.sampleInterfaceQuery).toEqual({ + sampleInterfaceStringField: "sampleInterfaceStringField", + sampleType1StringField: "sampleType1StringField", + }); }); - }); - it("should detect returned object type using resolveType from union", async () => { - const document = gql` - query { - sampleResolveUnionQuery { - ... on SampleType3 { - sampleInterfaceStringField - sampleType3StringField + it("should detect returned object type from union", async () => { + const document = gql` + query { + sampleUnionQuery { + ... on SampleType3 { + sampleInterfaceStringField + sampleType3StringField + } } } - } - `; + `; - const { data } = await execute(schema, document); + const { data } = await execute(schema, document); - expect(data!.sampleResolveUnionQuery).toEqual({ - sampleInterfaceStringField: "sampleInterfaceStringField", - sampleType3StringField: "sampleType3StringField", + expect(data!.sampleUnionQuery).toEqual({ + sampleInterfaceStringField: "sampleInterfaceStringField", + sampleType3StringField: "sampleType3StringField", + }); }); - }); - it("should properly transform number enum argument", async () => { - const document = gql` - query { - sampleNumberEnumQuery(numberEnum: OptionOne) - } - `; + it("should detect returned object type using resolveType from union", async () => { + const document = gql` + query { + sampleResolveUnionQuery { + ... on SampleType3 { + sampleInterfaceStringField + sampleType3StringField + } + } + } + `; - const { data } = await execute(schema, document); + const { data } = await execute(schema, document); - expect(data!.sampleNumberEnumQuery).toBe("OptionOne"); - expect(enumValue).toBe(0); - }); + expect(data!.sampleResolveUnionQuery).toEqual({ + sampleInterfaceStringField: "sampleInterfaceStringField", + sampleType3StringField: "sampleType3StringField", + }); + }); - it("should properly transform string enum argument", async () => { - const document = gql` - query { - sampleStringEnumQuery(stringEnum: OptionTwo) - } - `; + it("should properly transform number enum argument", async () => { + const document = gql` + query { + sampleNumberEnumQuery(numberEnum: OptionOne) + } + `; + + const { data } = await execute(schema, document); + + expect(data!.sampleNumberEnumQuery).toBe("OptionOne"); + expect(enumValue).toBe(0); + }); + + it("should properly transform string enum argument", async () => { + const document = gql` + query { + sampleStringEnumQuery(stringEnum: OptionTwo) + } + `; + + const { data } = await execute(schema, document); + + expect(data!.sampleStringEnumQuery).toBe("OptionTwo"); + expect(enumValue).toBe("OptionTwoString"); + }); + + it("should properly run subscriptions", async () => { + const document = gql` + subscription { + sampleSubscription + } + `; + const payload = 5.4321; + + const iterator = (await subscribe(schema, document)) as AsyncIterator; + const firstValuePromise = iterator.next(); + pubSub.publish("SAMPLE", payload); + const data = await firstValuePromise; + + expect(data.value.data!.sampleSubscription).toBe(payload); + }); + + it("should generate simple resolvers function for queries and mutations", async () => { + expect((resolvers.Query as ResolverObject).sampleDateQuery).toBeInstanceOf( + Function, + ); + expect( + (resolvers.Mutation as ResolverObject).sampleBooleanMutation, + ).toBeInstanceOf(Function); + }); - const { data } = await execute(schema, document); + it("should generate resolvers object for subscriptions", async () => { + const sampleSubscription = (resolvers.Subscription as ResolverObject) + .sampleSubscription as ResolverOptions; - expect(data!.sampleStringEnumQuery).toBe("OptionTwo"); - expect(enumValue).toBe("OptionTwoString"); + expect(sampleSubscription.resolve).toBeInstanceOf(Function); + expect(sampleSubscription.subscribe).toBeInstanceOf(Function); + }); }); + }); - it("should properly run subscriptions", async () => { - const document = gql` - subscription { - sampleSubscription + describe("buildTypeDefsAndResolversSync", () => { + let typeDefs: string; + let resolvers: ResolversMap; + let schemaIntrospection: IntrospectionSchema; + let schema: GraphQLSchema; + + beforeAll(async () => { + getMetadataStorage().clear(); + + @ObjectType() + class SampleType { + @Field() + sampleInterfaceStringField: string; + @Field({ description: "sampleTypeStringFieldDescription" }) + sampleTypeStringField: string; + } + + @Resolver() + class SampleResolver { + @Query() + sampleBooleanQuery(): boolean { + return true; } - `; - const payload = 5.4321; + } - const iterator = (await subscribe(schema, document)) as AsyncIterator; - const firstValuePromise = iterator.next(); - pubSub.publish("SAMPLE", payload); - const data = await firstValuePromise; + ({ typeDefs, resolvers } = buildTypeDefsAndResolversSync({ + resolvers: [SampleResolver], + authChecker: () => false, + orphanedTypes: [SampleType], + })); + schema = makeExecutableSchema({ + typeDefs, + resolvers, + }); + const introspectionResult = await graphql(schema, getIntrospectionQuery()); + schemaIntrospection = (introspectionResult.data as IntrospectionQuery).__schema; + }); - expect(data.value.data!.sampleSubscription).toBe(payload); + it("should generate schema without errors", () => { + expect(schemaIntrospection).toBeDefined(); }); - it("should generate simple resolvers function for queries and mutations", async () => { - expect((resolvers.Query as ResolverObject).sampleDateQuery).toBeInstanceOf( - Function, - ); - expect((resolvers.Mutation as ResolverObject).sampleBooleanMutation).toBeInstanceOf( - Function, - ); + describe("typeDefs", () => { + it("should generate typeDefs correctly", async () => { + expect(typeDefs).toBeDefined(); + }); + + it("should generate object types", async () => { + const sampleType = schemaIntrospection.types.find( + it => it.name === "SampleType", + ) as IntrospectionObjectType; + const sampleTypeStringField = sampleType.fields.find( + it => it.name === "sampleTypeStringField", + )!; + + expect(sampleType.kind).toBe(TypeKind.OBJECT); + expect(sampleType.fields).toHaveLength(2); + expect(sampleTypeStringField.description).toEqual("sampleTypeStringFieldDescription"); + expect(sampleType.interfaces).toHaveLength(0); + }); }); - it("should generate resolvers object for subscriptions", async () => { - const sampleSubscription = (resolvers.Subscription as ResolverObject) - .sampleSubscription as ResolverOptions; + describe("resolvers", () => { + it("should generate resolversMap without errors", async () => { + expect(resolvers).toBeDefined(); + }); - expect(sampleSubscription.resolve).toBeInstanceOf(Function); - expect(sampleSubscription.subscribe).toBeInstanceOf(Function); + it("should allow for simple boolean query", async () => { + const document = gql` + query { + sampleBooleanQuery + } + `; + + const { data, errors } = await execute(schema, document); + + expect(errors).toBeUndefined(); + expect(data!.sampleBooleanQuery).toBe(true); + }); }); }); });