diff --git a/src/schema/definition-node.ts b/src/schema/definition-node.ts index 5f3066541..f75e5dc2e 100644 --- a/src/schema/definition-node.ts +++ b/src/schema/definition-node.ts @@ -18,7 +18,9 @@ import { type DirectiveMetadata } from "@/metadata/definitions"; import { type SetRequired } from "@/typings"; export function getDirectiveNode(directive: DirectiveMetadata): ConstDirectiveNode { - const { nameOrDefinition, args } = directive; + // Inline and trim start + const nameOrDefinition = directive.nameOrDefinition.replaceAll("\n", " ").trimStart(); + const { args } = directive; if (nameOrDefinition === "") { throw new InvalidDirectiveError( diff --git a/tests/functional/directives.ts b/tests/functional/directives.ts index 8e6a1b25d..1b6e536b2 100644 --- a/tests/functional/directives.ts +++ b/tests/functional/directives.ts @@ -582,6 +582,101 @@ describe("Directives", () => { }); }); + describe("multiline and leading white spaces", () => { + let schema: GraphQLSchema; + beforeAll(async () => { + @Resolver() + class SampleResolver { + @Directive("\n@test") + @Query() + multiline(): boolean { + return true; + } + + @Directive(" @test") + @Query() + leadingWhiteSpaces(): boolean { + return true; + } + + @Directive("\n @test") + @Query() + multilineAndLeadingWhiteSpaces(): boolean { + return true; + } + + @Directive(` + @test( + argNonNullDefault: "argNonNullDefault", + argNullDefault: "argNullDefault", + argNull: "argNull" + ) + `) + @Query() + rawMultilineAndLeadingWhiteSpaces(): boolean { + return true; + } + } + + schema = await buildSchema({ + resolvers: [SampleResolver], + directives: [testDirective], + validate: false, + }); + schema = testDirectiveTransformer(schema); + }); + + it("should properly emit directive in AST", () => { + const multilineInfo = schema.getRootType(OperationTypeNode.QUERY)!.getFields().multiline; + const leadingWhiteSpacesInfo = schema + .getRootType(OperationTypeNode.QUERY)! + .getFields().leadingWhiteSpaces; + const multilineAndLeadingWhiteSpacesInfo = schema + .getRootType(OperationTypeNode.QUERY)! + .getFields().multilineAndLeadingWhiteSpaces; + const rawMultilineAndLeadingWhiteSpacesInfo = schema + .getRootType(OperationTypeNode.QUERY)! + .getFields().rawMultilineAndLeadingWhiteSpaces; + + expect(() => { + assertValidDirective(multilineInfo.astNode, "test"); + assertValidDirective(leadingWhiteSpacesInfo.astNode, "test"); + assertValidDirective(multilineAndLeadingWhiteSpacesInfo.astNode, "test"); + assertValidDirective(rawMultilineAndLeadingWhiteSpacesInfo.astNode, "test", { + argNonNullDefault: `"argNonNullDefault"`, + argNullDefault: `"argNullDefault"`, + argNull: `"argNull"`, + }); + }).not.toThrow(); + }); + + it("should properly apply directive mapper", async () => { + const multilineInfo = schema.getRootType(OperationTypeNode.QUERY)!.getFields().multiline; + const leadingWhiteSpacesInfo = schema + .getRootType(OperationTypeNode.QUERY)! + .getFields().leadingWhiteSpaces; + const multilineAndLeadingWhiteSpacesInfo = schema + .getRootType(OperationTypeNode.QUERY)! + .getFields().multilineAndLeadingWhiteSpaces; + const rawMultilineAndLeadingWhiteSpacesInfo = schema + .getRootType(OperationTypeNode.QUERY)! + .getFields().rawMultilineAndLeadingWhiteSpaces; + + expect(multilineInfo.extensions).toMatchObject({ + TypeGraphQL: { isMappedByDirective: true }, + }); + expect(leadingWhiteSpacesInfo.extensions).toMatchObject({ + TypeGraphQL: { isMappedByDirective: true }, + }); + expect(multilineAndLeadingWhiteSpacesInfo.extensions).toMatchObject({ + TypeGraphQL: { isMappedByDirective: true }, + }); + expect(rawMultilineAndLeadingWhiteSpacesInfo.extensions).toMatchObject({ + TypeGraphQL: { isMappedByDirective: true }, + }); + }); + }); + describe("errors", () => { beforeEach(async () => { getMetadataStorage().clear(); diff --git a/tests/helpers/directives/TestDirective.ts b/tests/helpers/directives/TestDirective.ts index bfbd5534e..6e8dbe92e 100644 --- a/tests/helpers/directives/TestDirective.ts +++ b/tests/helpers/directives/TestDirective.ts @@ -9,9 +9,11 @@ import { type GraphQLInputObjectTypeConfig, GraphQLInterfaceType, type GraphQLInterfaceTypeConfig, + GraphQLNonNull, GraphQLObjectType, type GraphQLObjectTypeConfig, type GraphQLSchema, + GraphQLString, } from "graphql"; function mapConfig< @@ -36,6 +38,19 @@ function mapConfig< export const testDirective = new GraphQLDirective({ name: "test", + args: { + argNonNullDefault: { + type: new GraphQLNonNull(GraphQLString), + defaultValue: "argNonNullDefault", + }, + argNullDefault: { + type: GraphQLString, + defaultValue: "argNullDefault", + }, + argNull: { + type: GraphQLString, + }, + }, locations: [ DirectiveLocation.OBJECT, DirectiveLocation.FIELD_DEFINITION, diff --git a/tests/helpers/directives/assertValidDirective.ts b/tests/helpers/directives/assertValidDirective.ts index d11cb64da..1fb87ab93 100644 --- a/tests/helpers/directives/assertValidDirective.ts +++ b/tests/helpers/directives/assertValidDirective.ts @@ -38,12 +38,17 @@ export function assertValidDirective( expect(directive.arguments).toBeFalsy(); } } else { + expect(directive.arguments).toHaveLength(Object.keys(args).length); expect(directive.arguments).toEqual( - Object.keys(args).map(arg => ({ - kind: "Argument", - name: { kind: "Name", value: arg }, - value: parseValue(args[arg]), - })), + expect.arrayContaining( + Object.keys(args).map(arg => + expect.objectContaining({ + kind: "Argument", + name: expect.objectContaining({ kind: "Name", value: arg }), + value: expect.objectContaining(parseValue(args[arg], { noLocation: true })), + }), + ), + ), ); } }