From 59a0aebc3dabd7d23ffde576a94bc588e768efbe Mon Sep 17 00:00:00 2001 From: doug-martin Date: Wed, 9 Sep 2020 11:37:40 -0500 Subject: [PATCH] fix(graphql,hooks): Allow getting hooks from parent classes --- packages/core/src/common/reflect.utils.ts | 13 +- .../decorators/hook.decorator.spec.ts | 279 ++++++++++++++++++ .../src/decorators/hook.decorator.ts | 16 +- 3 files changed, 297 insertions(+), 11 deletions(-) create mode 100644 packages/query-graphql/__tests__/decorators/hook.decorator.spec.ts diff --git a/packages/core/src/common/reflect.utils.ts b/packages/core/src/common/reflect.utils.ts index 3d2e3a52f..20d3a5d83 100644 --- a/packages/core/src/common/reflect.utils.ts +++ b/packages/core/src/common/reflect.utils.ts @@ -10,7 +10,14 @@ export const classMetadataDecorator = (key: string): ClassDecoratorDataFun }; }; -export function getClassMetadata(DTOClass: Class, key: string): MetaValue { +export function getClassMetadata( + DTOClass: Class, + key: string, + includeParents: boolean, +): MetaValue { + if (includeParents) { + return Reflect.getMetadata(key, DTOClass) as MetaValue; + } return Reflect.getOwnMetadata(key, DTOClass) as MetaValue; } @@ -54,7 +61,7 @@ export class ValueReflector extends Reflector { export class ArrayReflector extends Reflector { append(DTOClass: Class, data: Data): void { - const metadata = getClassMetadata(DTOClass, this.metaKey) ?? []; + const metadata = getClassMetadata(DTOClass, this.metaKey, false) ?? []; metadata.push(data); this.defineMetadata(metadata, DTOClass); } @@ -66,7 +73,7 @@ export class ArrayReflector extends Reflector { export class MapReflector extends Reflector { set(DTOClass: Class, key: K, value: Data): void { - const metadata = getClassMetadata>(DTOClass, this.metaKey) ?? new Map(); + const metadata = getClassMetadata>(DTOClass, this.metaKey, false) ?? new Map(); metadata.set(key, value); this.defineMetadata(metadata, DTOClass); } diff --git a/packages/query-graphql/__tests__/decorators/hook.decorator.spec.ts b/packages/query-graphql/__tests__/decorators/hook.decorator.spec.ts new file mode 100644 index 000000000..8672ca118 --- /dev/null +++ b/packages/query-graphql/__tests__/decorators/hook.decorator.spec.ts @@ -0,0 +1,279 @@ +// eslint-disable-next-line max-classes-per-file +import { + BeforeCreateMany, + BeforeCreateOne, + BeforeUpdateOne, + BeforeUpdateMany, + BeforeDeleteOne, + BeforeDeleteMany, + BeforeQueryMany, + BeforeFindOne, +} from '../../src'; +import { + getCreateManyHook, + getCreateOneHook, + getUpdateOneHook, + getUpdateManyHook, + getDeleteOneHook, + getDeleteManyHook, + getQueryManyHook, + getFindOneHook, +} from '../../src/decorators'; + +describe('hook decorators', () => { + describe('@BeforeCreateOne', () => { + it('should store the hook', () => { + const hookFn = jest.fn(); + @BeforeCreateOne(hookFn) + class Test {} + + expect(getCreateOneHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the base class', () => { + const hookFn = jest.fn(); + @BeforeCreateOne(hookFn) + class Base {} + + class Test extends Base {} + + expect(getCreateOneHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the child class if there is a hook on both the base and child', () => { + const baseHookFn = jest.fn(); + @BeforeCreateOne(baseHookFn) + class Base {} + + const childHookFn = jest.fn(); + @BeforeCreateOne(childHookFn) + class Test extends Base {} + + expect(getCreateOneHook(Test)).toBe(childHookFn); + }); + }); + + describe('@BeforeCreateMany', () => { + it('should store the hook', () => { + const hookFn = jest.fn(); + @BeforeCreateMany(hookFn) + class Test {} + + expect(getCreateManyHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the base class', () => { + const hookFn = jest.fn(); + @BeforeCreateMany(hookFn) + class Base {} + + class Test extends Base {} + + expect(getCreateManyHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the child class if there is a hook on both the base and child', () => { + const baseHookFn = jest.fn(); + @BeforeCreateMany(baseHookFn) + class Base {} + + const childHookFn = jest.fn(); + @BeforeCreateMany(childHookFn) + class Test extends Base {} + + expect(getCreateManyHook(Test)).toBe(childHookFn); + }); + }); + + describe('@BeforeUpdateOne', () => { + it('should store the hook', () => { + const hookFn = jest.fn(); + @BeforeUpdateOne(hookFn) + class Test {} + + expect(getUpdateOneHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the base class', () => { + const hookFn = jest.fn(); + @BeforeUpdateOne(hookFn) + class Base {} + + class Test extends Base {} + + expect(getUpdateOneHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the child class if there is a hook on both the base and child', () => { + const baseHookFn = jest.fn(); + @BeforeUpdateOne(baseHookFn) + class Base {} + + const childHookFn = jest.fn(); + @BeforeUpdateOne(childHookFn) + class Test extends Base {} + + expect(getUpdateOneHook(Test)).toBe(childHookFn); + }); + }); + + describe('@BeforeUpdateMany', () => { + it('should store the hook', () => { + const hookFn = jest.fn(); + @BeforeUpdateMany(hookFn) + class Test {} + + expect(getUpdateManyHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the base class', () => { + const hookFn = jest.fn(); + @BeforeUpdateMany(hookFn) + class Base {} + + class Test extends Base {} + + expect(getUpdateManyHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the child class if there is a hook on both the base and child', () => { + const baseHookFn = jest.fn(); + @BeforeUpdateMany(baseHookFn) + class Base {} + + const childHookFn = jest.fn(); + @BeforeUpdateMany(childHookFn) + class Test extends Base {} + + expect(getUpdateManyHook(Test)).toBe(childHookFn); + }); + }); + + describe('@BeforeDeleteOne', () => { + it('should store the hook', () => { + const hookFn = jest.fn(); + @BeforeDeleteOne(hookFn) + class Test {} + + expect(getDeleteOneHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the base class', () => { + const hookFn = jest.fn(); + @BeforeDeleteOne(hookFn) + class Base {} + + class Test extends Base {} + + expect(getDeleteOneHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the child class if there is a hook on both the base and child', () => { + const baseHookFn = jest.fn(); + @BeforeDeleteOne(baseHookFn) + class Base {} + + const childHookFn = jest.fn(); + @BeforeDeleteOne(childHookFn) + class Test extends Base {} + + expect(getDeleteOneHook(Test)).toBe(childHookFn); + }); + }); + + describe('@BeforeDeleteMany', () => { + it('should store the hook', () => { + const hookFn = jest.fn(); + @BeforeDeleteMany(hookFn) + class Test {} + + expect(getDeleteManyHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the base class', () => { + const hookFn = jest.fn(); + @BeforeDeleteMany(hookFn) + class Base {} + + class Test extends Base {} + + expect(getDeleteManyHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the child class if there is a hook on both the base and child', () => { + const baseHookFn = jest.fn(); + @BeforeDeleteMany(baseHookFn) + class Base {} + + const childHookFn = jest.fn(); + @BeforeDeleteMany(childHookFn) + class Test extends Base {} + + expect(getDeleteManyHook(Test)).toBe(childHookFn); + }); + }); + + describe('@BeforeQueryMany', () => { + it('should store the hook', () => { + const hookFn = jest.fn(); + @BeforeQueryMany(hookFn) + class Test {} + + expect(getQueryManyHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the base class', () => { + const hookFn = jest.fn(); + @BeforeQueryMany(hookFn) + class Base {} + + class Test extends Base {} + + expect(getQueryManyHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the child class if there is a hook on both the base and child', () => { + const baseHookFn = jest.fn(); + @BeforeQueryMany(baseHookFn) + class Base {} + + const childHookFn = jest.fn(); + @BeforeQueryMany(childHookFn) + class Test extends Base {} + + expect(getQueryManyHook(Test)).toBe(childHookFn); + }); + }); + + describe('@BeforeFindOne', () => { + it('should store the hook', () => { + const hookFn = jest.fn(); + @BeforeFindOne(hookFn) + class Test {} + + expect(getFindOneHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the base class', () => { + const hookFn = jest.fn(); + @BeforeFindOne(hookFn) + class Base {} + + class Test extends Base {} + + expect(getFindOneHook(Test)).toBe(hookFn); + }); + + it('should return a hook from the child class if there is a hook on both the base and child', () => { + const baseHookFn = jest.fn(); + @BeforeFindOne(baseHookFn) + class Base {} + + const childHookFn = jest.fn(); + @BeforeFindOne(childHookFn) + class Test extends Base {} + + expect(getFindOneHook(Test)).toBe(childHookFn); + }); + }); +}); diff --git a/packages/query-graphql/src/decorators/hook.decorator.ts b/packages/query-graphql/src/decorators/hook.decorator.ts index bb8d3a46a..5bfc94379 100644 --- a/packages/query-graphql/src/decorators/hook.decorator.ts +++ b/packages/query-graphql/src/decorators/hook.decorator.ts @@ -26,21 +26,21 @@ export type CreateOneHook = HookFunc>; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const BeforeCreateOne = classMetadataDecorator>(BEFORE_CREATE_ONE_KEY); export function getCreateOneHook(DTOClass: Class): MetaValue> { - return getClassMetadata(DTOClass, BEFORE_CREATE_ONE_KEY); + return getClassMetadata(DTOClass, BEFORE_CREATE_ONE_KEY, true); } export type CreateManyHook = HookFunc>; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const BeforeCreateMany = classMetadataDecorator>(BEFORE_CREATE_MANY_KEY); export function getCreateManyHook(DTOClass: Class): MetaValue> { - return getClassMetadata(DTOClass, BEFORE_CREATE_MANY_KEY); + return getClassMetadata(DTOClass, BEFORE_CREATE_MANY_KEY, true); } export type UpdateOneHook = HookFunc>; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const BeforeUpdateOne = classMetadataDecorator>(BEFORE_UPDATE_ONE_KEY); export function getUpdateOneHook>(DTOClass: Class): MetaValue> { - return getClassMetadata(DTOClass, BEFORE_UPDATE_ONE_KEY); + return getClassMetadata(DTOClass, BEFORE_UPDATE_ONE_KEY, true); } export type UpdateManyHook> = HookFunc>; @@ -49,32 +49,32 @@ export const BeforeUpdateMany = classMetadataDecorator> export function getUpdateManyHook>( DTOClass: Class, ): MetaValue> { - return getClassMetadata(DTOClass, BEFORE_UPDATE_MANY_KEY); + return getClassMetadata(DTOClass, BEFORE_UPDATE_MANY_KEY, true); } export type DeleteOneHook = HookFunc; export const BeforeDeleteOne = classMetadataDecorator(BEFORE_DELETE_ONE_KEY); export function getDeleteOneHook(DTOClass: Class): MetaValue { - return getClassMetadata(DTOClass, BEFORE_DELETE_ONE_KEY); + return getClassMetadata(DTOClass, BEFORE_DELETE_ONE_KEY, true); } export type DeleteManyHook = HookFunc>; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const BeforeDeleteMany = classMetadataDecorator>(BEFORE_DELETE_MANY_KEY); export function getDeleteManyHook(DTOClass: Class): MetaValue> { - return getClassMetadata(DTOClass, BEFORE_DELETE_MANY_KEY); + return getClassMetadata(DTOClass, BEFORE_DELETE_MANY_KEY, true); } export type BeforeQueryManyHook = HookFunc>; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const BeforeQueryMany = classMetadataDecorator>(BEFORE_QUERY_MANY_KEY); export function getQueryManyHook(DTOClass: Class): MetaValue> { - return getClassMetadata(DTOClass, BEFORE_QUERY_MANY_KEY); + return getClassMetadata(DTOClass, BEFORE_QUERY_MANY_KEY, true); } export type BeforeFindOneHook = HookFunc; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const BeforeFindOne = classMetadataDecorator(BEFORE_FIND_ONE_KEY); export function getFindOneHook(DTOClass: Class): MetaValue { - return getClassMetadata(DTOClass, BEFORE_FIND_ONE_KEY); + return getClassMetadata(DTOClass, BEFORE_FIND_ONE_KEY, true); }