diff --git a/packages/graphql/lib/schema-builder/storages/type-metadata.storage.ts b/packages/graphql/lib/schema-builder/storages/type-metadata.storage.ts index 483a09014..c07d2d195 100644 --- a/packages/graphql/lib/schema-builder/storages/type-metadata.storage.ts +++ b/packages/graphql/lib/schema-builder/storages/type-metadata.storage.ts @@ -142,7 +142,10 @@ export class TypeMetadataStorageHost { addDirectiveMetadata(metadata: ClassDirectiveMetadata) { const classMetadata = this.metadataByTargetCollection.get(metadata.target); - if (!classMetadata.fieldDirectives.sdls.has(metadata.sdl)) { + const isDuplicate = classMetadata.classDirectives + .getAll() + .some((directive) => directive.sdl === metadata.sdl); + if (!isDuplicate) { classMetadata.classDirectives.push(metadata); } } diff --git a/packages/graphql/tests/schema-builder/factories/class-directive-not-shadowed.spec.ts b/packages/graphql/tests/schema-builder/factories/class-directive-not-shadowed.spec.ts new file mode 100644 index 000000000..8ef46b318 --- /dev/null +++ b/packages/graphql/tests/schema-builder/factories/class-directive-not-shadowed.spec.ts @@ -0,0 +1,73 @@ +import { Test } from '@nestjs/testing'; +import { GraphQLObjectType } from 'graphql'; +import { + Directive, + Field, + GraphQLSchemaBuilderModule, + GraphQLSchemaFactory, + ID, + ObjectType, + Query, + Resolver, + TypeMetadataStorage, +} from '../../../lib'; + +@ObjectType() +@Directive('@tag(name: "public")') +class Account { + @Field(() => ID) + id: string; + + @Field() + @Directive('@tag(name: "public")') + email: string; +} + +@Resolver(() => Account) +class AccountResolver { + @Query(() => Account) + me(): Account { + return null; + } +} + +describe('Class-level directive not shadowed by identical field-level directive', () => { + let accountType: GraphQLObjectType; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [GraphQLSchemaBuilderModule], + }).compile(); + + const schemaFactory = moduleRef.get(GraphQLSchemaFactory); + const schema = await schemaFactory.create([AccountResolver]); + accountType = schema.getType('Account') as GraphQLObjectType; + }); + + afterAll(() => { + TypeMetadataStorage.clear(); + }); + + it('should keep the class-level directive even when a field declares the same SDL', () => { + const classDirectives = accountType.astNode?.directives ?? []; + const classTagNames = classDirectives + .filter((d) => d.name.value === 'tag') + .map( + (d) => + (d.arguments?.[0]?.value as { value?: string } | undefined)?.value, + ); + expect(classTagNames).toContain('public'); + }); + + it('should still keep the field-level directive', () => { + const emailField = accountType.getFields().email; + const fieldDirectives = emailField.astNode?.directives ?? []; + const fieldTagNames = fieldDirectives + .filter((d) => d.name.value === 'tag') + .map( + (d) => + (d.arguments?.[0]?.value as { value?: string } | undefined)?.value, + ); + expect(fieldTagNames).toContain('public'); + }); +});