diff --git a/packages/container/libraries/core/src/index.ts b/packages/container/libraries/core/src/index.ts index 802f8542..80c67210 100644 --- a/packages/container/libraries/core/src/index.ts +++ b/packages/container/libraries/core/src/index.ts @@ -2,6 +2,7 @@ import { getClassMetadata } from './metadata/calculations/getClassMetadata'; import { ClassElementMetadata } from './metadata/models/ClassElementMetadata'; import { ClassElementMetadataKind } from './metadata/models/ClassElementMetadataKind'; import { ClassMetadata } from './metadata/models/ClassMetadata'; +import { ClassMetadataLifecycle } from './metadata/models/ClassMetadataLifecycle'; import { ManagedClassElementMetadata } from './metadata/models/ManagedClassElementMetadata'; import { MetadataName } from './metadata/models/MetadataName'; import { MetadataTag } from './metadata/models/MetadataTag'; @@ -11,6 +12,7 @@ import { UnmanagedClassElementMetadata } from './metadata/models/UnmanagedClassE export type { ClassElementMetadata, ClassMetadata, + ClassMetadataLifecycle, ManagedClassElementMetadata, MetadataName, MetadataTag, diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassElementMetadataFromLegacyMetadata.spec.ts b/packages/container/libraries/core/src/metadata/calculations/getClassElementMetadataFromLegacyMetadata.spec.ts new file mode 100644 index 00000000..44e3005c --- /dev/null +++ b/packages/container/libraries/core/src/metadata/calculations/getClassElementMetadataFromLegacyMetadata.spec.ts @@ -0,0 +1,276 @@ +import { beforeAll, describe, expect, it } from '@jest/globals'; + +import { ServiceIdentifier } from '@inversifyjs/common'; + +import { + INJECT_TAG, + MULTI_INJECT_TAG, + NAME_TAG, + NAMED_TAG, + OPTIONAL_TAG, + UNMANAGED_TAG, +} from '../../reflectMetadata/data/keys'; +import { ClassElementMetadata } from '../models/ClassElementMetadata'; +import { ClassElementMetadataKind } from '../models/ClassElementMetadataKind'; +import { LegacyMetadata } from '../models/LegacyMetadata'; +import { MetadataName } from '../models/MetadataName'; +import { MetadataTag } from '../models/MetadataTag'; +import { MetadataTargetName } from '../models/MetadataTargetName'; +import { getClassElementMetadataFromLegacyMetadata } from './getClassElementMetadataFromLegacyMetadata'; + +describe(getClassElementMetadataFromLegacyMetadata.name, () => { + describe('having an empty metadata list', () => { + let metadataListFixture: LegacyMetadata[]; + + beforeAll(() => { + metadataListFixture = []; + }); + + describe('when called', () => { + let result: unknown; + + beforeAll(() => { + try { + getClassElementMetadataFromLegacyMetadata(metadataListFixture); + } catch (error: unknown) { + result = error; + } + }); + + it('should throw an Error', () => { + const expectedErrorProperties: Partial = { + message: 'Expected @inject, @multiInject or @unmanaged metadata', + }; + + expect(result).toBeInstanceOf(Error); + expect(result).toStrictEqual( + expect.objectContaining(expectedErrorProperties), + ); + }); + }); + }); + + describe('having a metadata list with unmanaged metadata', () => { + let metadataListFixture: LegacyMetadata[]; + + beforeAll(() => { + metadataListFixture = [ + { + key: UNMANAGED_TAG, + value: true, + }, + ]; + }); + + describe('when called', () => { + let result: unknown; + + beforeAll(() => { + result = getClassElementMetadataFromLegacyMetadata(metadataListFixture); + }); + + it('should return ClassElementMetadata', () => { + const expectedClassElementMetadata: ClassElementMetadata = { + kind: ClassElementMetadataKind.unmanaged, + }; + + expect(result).toStrictEqual(expectedClassElementMetadata); + }); + }); + }); + + describe.each<[string, LegacyMetadata]>([ + [ + 'inject', + { + key: INJECT_TAG, + value: Symbol(), + }, + ], + [ + 'multi inject', + { + key: MULTI_INJECT_TAG, + value: Symbol(), + }, + ], + ])( + 'having a metadata list with both unmanaged and %s metadata', + (_: string, metadata: LegacyMetadata) => { + let metadataListFixture: LegacyMetadata[]; + + beforeAll(() => { + metadataListFixture = [ + { + key: UNMANAGED_TAG, + value: true, + }, + metadata, + ]; + }); + + describe('when called', () => { + let result: unknown; + + beforeAll(() => { + try { + getClassElementMetadataFromLegacyMetadata(metadataListFixture); + } catch (error: unknown) { + result = error; + } + }); + + it('should throw an Error', () => { + const expectedErrorProperties: Partial = { + message: + 'Expected a single @inject, @multiInject or @unmanaged metadata', + }; + + expect(result).toBeInstanceOf(Error); + expect(result).toStrictEqual( + expect.objectContaining(expectedErrorProperties), + ); + }); + }); + }, + ); + + describe.each<[string, ClassElementMetadataKind, LegacyMetadata]>([ + [ + 'inject', + ClassElementMetadataKind.singleInjection, + { + key: INJECT_TAG, + value: Symbol(), + }, + ], + [ + 'multi inject', + ClassElementMetadataKind.multipleInjection, + { + key: MULTI_INJECT_TAG, + value: Symbol(), + }, + ], + ])( + 'having a metadata list with % metadata', + ( + _: string, + classElementMetadataKind: ClassElementMetadataKind, + metadata: LegacyMetadata, + ) => { + let metadataListFixture: LegacyMetadata[]; + + beforeAll(() => { + metadataListFixture = [metadata]; + }); + + describe('when called', () => { + let result: unknown; + + beforeAll(() => { + result = + getClassElementMetadataFromLegacyMetadata(metadataListFixture); + }); + + it('should return ClassElementMetadata', () => { + const expectedClassElementMetadata: ClassElementMetadata = { + kind: classElementMetadataKind, + name: undefined, + optional: false, + tags: new Map(), + targetName: undefined, + value: metadata.value as ServiceIdentifier, + }; + + expect(result).toStrictEqual(expectedClassElementMetadata); + }); + }); + }, + ); + + describe.each<[string, ClassElementMetadataKind, LegacyMetadata]>([ + [ + 'inject', + ClassElementMetadataKind.singleInjection, + { + key: INJECT_TAG, + value: Symbol(), + }, + ], + [ + 'multi inject', + ClassElementMetadataKind.multipleInjection, + { + key: MULTI_INJECT_TAG, + value: Symbol(), + }, + ], + ])( + 'having a metadata list with % metadata', + ( + _: string, + classElementMetadataKind: ClassElementMetadataKind, + metadata: LegacyMetadata, + ) => { + let customTagMetadataFixture: LegacyMetadata; + let nameMetadataFixture: LegacyMetadata; + let optionalMetadataFixture: LegacyMetadata; + let targetNameMetadataFixture: LegacyMetadata; + let metadataListFixture: LegacyMetadata[]; + + beforeAll(() => { + customTagMetadataFixture = { + key: 'customTag', + value: 'customTagValue', + }; + nameMetadataFixture = { + key: NAME_TAG, + value: 'name-fixture', + }; + optionalMetadataFixture = { + key: OPTIONAL_TAG, + value: true, + }; + targetNameMetadataFixture = { + key: NAMED_TAG, + value: 'target-name-fixture', + }; + metadataListFixture = [ + metadata, + customTagMetadataFixture, + nameMetadataFixture, + optionalMetadataFixture, + targetNameMetadataFixture, + ]; + }); + + describe('when called', () => { + let result: unknown; + + beforeAll(() => { + result = + getClassElementMetadataFromLegacyMetadata(metadataListFixture); + }); + + it('should return ClassElementMetadata', () => { + const expectedClassElementMetadata: ClassElementMetadata = { + kind: classElementMetadataKind, + name: nameMetadataFixture.value as MetadataName, + optional: true, + tags: new Map([ + [ + customTagMetadataFixture.key as MetadataTag, + customTagMetadataFixture.value, + ], + ]), + targetName: targetNameMetadataFixture.value as MetadataTargetName, + value: metadata.value as ServiceIdentifier, + }; + + expect(result).toStrictEqual(expectedClassElementMetadata); + }); + }); + }, + ); +}); diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassElementMetadataFromLegacyMetadata.ts b/packages/container/libraries/core/src/metadata/calculations/getClassElementMetadataFromLegacyMetadata.ts index 18a7b123..23de23c6 100644 --- a/packages/container/libraries/core/src/metadata/calculations/getClassElementMetadataFromLegacyMetadata.ts +++ b/packages/container/libraries/core/src/metadata/calculations/getClassElementMetadataFromLegacyMetadata.ts @@ -1,4 +1,4 @@ -import { ServiceIdentifier } from '@inversifyjs/common'; +import { LazyServiceIdentifier, ServiceIdentifier } from '@inversifyjs/common'; import { INJECT_TAG, @@ -16,6 +16,7 @@ import { ManagedClassElementMetadata } from '../models/ManagedClassElementMetada import { MetadataName } from '../models/MetadataName'; import { MetadataTag } from '../models/MetadataTag'; import { MetadataTargetName } from '../models/MetadataTargetName'; +import { UnmanagedClassElementMetadata } from '../models/UnmanagedClassElementMetadata'; export function getClassElementMetadataFromLegacyMetadata( metadataList: LegacyMetadata[], @@ -31,13 +32,14 @@ export function getClassElementMetadataFromLegacyMetadata( ); if (unmanagedMetadata !== undefined) { - return { - kind: ClassElementMetadataKind.unmanaged, - }; + return getUnmanagedClassElementMetadata( + injectMetadata, + multiInjectMetadata, + ); } if (multiInjectMetadata === undefined && injectMetadata === undefined) { - throw new Error(); + throw new Error('Expected @inject, @multiInject or @unmanaged metadata'); } const nameMetadata: LegacyMetadata | undefined = metadataList.find( @@ -74,9 +76,26 @@ export function getClassElementMetadataFromLegacyMetadata( targetName: targetNameMetadata?.value as MetadataTargetName | undefined, value: injectMetadata === undefined - ? (multiInjectMetadata?.value as ServiceIdentifier) - : (injectMetadata.value as ServiceIdentifier), + ? (multiInjectMetadata?.value as + | ServiceIdentifier + | LazyServiceIdentifier) + : (injectMetadata.value as ServiceIdentifier | LazyServiceIdentifier), }; return managedClassElementMetadata; } + +function getUnmanagedClassElementMetadata( + injectMetadata: LegacyMetadata | undefined, + multiInjectMetadata: LegacyMetadata | undefined, +): UnmanagedClassElementMetadata { + if (multiInjectMetadata !== undefined || injectMetadata !== undefined) { + throw new Error( + 'Expected a single @inject, @multiInject or @unmanaged metadata', + ); + } + + return { + kind: ClassElementMetadataKind.unmanaged, + }; +} diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassElementMetadataFromNewable.spec.ts b/packages/container/libraries/core/src/metadata/calculations/getClassElementMetadataFromNewable.spec.ts new file mode 100644 index 00000000..3112d634 --- /dev/null +++ b/packages/container/libraries/core/src/metadata/calculations/getClassElementMetadataFromNewable.spec.ts @@ -0,0 +1,34 @@ +import { beforeAll, describe, expect, it } from '@jest/globals'; + +import { Newable } from '@inversifyjs/common'; + +import { ClassElementMetadata } from '../models/ClassElementMetadata'; +import { ClassElementMetadataKind } from '../models/ClassElementMetadataKind'; +import { getClassElementMetadataFromNewable } from './getClassElementMetadataFromNewable'; + +describe(getClassElementMetadataFromNewable.name, () => { + describe('when called', () => { + let typeFixture: Newable; + + let result: unknown; + + beforeAll(() => { + typeFixture = class {}; + + result = getClassElementMetadataFromNewable(typeFixture); + }); + + it('should return ClassElementMetadata', () => { + const expected: ClassElementMetadata = { + kind: ClassElementMetadataKind.singleInjection, + name: undefined, + optional: false, + tags: new Map(), + targetName: undefined, + value: typeFixture, + }; + + expect(result).toStrictEqual(expected); + }); + }); +}); diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassMetadata.spec.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadata.spec.ts new file mode 100644 index 00000000..2dee6907 --- /dev/null +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadata.spec.ts @@ -0,0 +1,94 @@ +import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; + +jest.mock('@inversifyjs/reflect-metadata-utils'); + +import { getReflectMetadata } from '@inversifyjs/reflect-metadata-utils'; + +jest.mock('./getClassMetadataConstructorArguments'); +jest.mock('./getClassMetadataProperties'); + +import { ClassElementMetadata } from '../models/ClassElementMetadata'; +import { ClassElementMetadataKind } from '../models/ClassElementMetadataKind'; +import { ClassMetadata } from '../models/ClassMetadata'; +import { LegacyMetadata } from '../models/LegacyMetadata'; +import { getClassMetadata } from './getClassMetadata'; +import { getClassMetadataConstructorArguments } from './getClassMetadataConstructorArguments'; +import { getClassMetadataProperties } from './getClassMetadataProperties'; + +describe(getClassMetadata.name, () => { + describe('when called, and getReflectMetadata() returns LegacyMetadata', () => { + let constructorArgumentsMetadataFixture: ClassElementMetadata[]; + let propertiesMetadataFixture: Map; + let postConstructMetadataFixture: LegacyMetadata; + let preDestroyMetadataFixture: LegacyMetadata; + + let result: unknown; + + beforeAll(() => { + constructorArgumentsMetadataFixture = [ + { + kind: ClassElementMetadataKind.unmanaged, + }, + ]; + + propertiesMetadataFixture = new Map([ + [ + 'property-fixture', + { + kind: ClassElementMetadataKind.singleInjection, + name: undefined, + optional: false, + tags: new Map(), + targetName: undefined, + value: Symbol(), + }, + ], + ]); + + postConstructMetadataFixture = { + key: 'post-construct-key-fixture', + value: 'post-construct-value-fixture', + }; + + preDestroyMetadataFixture = { + key: 'pre-destroy-key-fixture', + value: 'pre-destroy-value-fixture', + }; + + ( + getClassMetadataConstructorArguments as jest.Mock< + typeof getClassMetadataConstructorArguments + > + ).mockReturnValueOnce(constructorArgumentsMetadataFixture); + + ( + getClassMetadataProperties as jest.Mock< + typeof getClassMetadataProperties + > + ).mockReturnValueOnce(propertiesMetadataFixture); + + (getReflectMetadata as jest.Mock) + .mockReturnValueOnce(postConstructMetadataFixture) + .mockReturnValueOnce(preDestroyMetadataFixture); + + result = getClassMetadata(class {}); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should return ClassMetadata', () => { + const expected: ClassMetadata = { + constructorArguments: constructorArgumentsMetadataFixture, + lifecycle: { + postConstructMethodName: postConstructMetadataFixture.value as string, + preDestroyMethodName: preDestroyMetadataFixture.value as string, + }, + properties: propertiesMetadataFixture, + }; + + expect(result).toStrictEqual(expected); + }); + }); +}); diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassMetadata.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadata.ts index 968d09c7..f6216a4c 100644 --- a/packages/container/libraries/core/src/metadata/calculations/getClassMetadata.ts +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadata.ts @@ -1,14 +1,26 @@ import { Newable } from '@inversifyjs/common'; +import { getReflectMetadata } from '@inversifyjs/reflect-metadata-utils'; +import { POST_CONSTRUCT, PRE_DESTROY } from '../../reflectMetadata/data/keys'; import { ClassMetadata } from '../models/ClassMetadata'; +import { LegacyMetadata } from '../models/LegacyMetadata'; import { getClassMetadataConstructorArguments } from './getClassMetadataConstructorArguments'; import { getClassMetadataProperties } from './getClassMetadataProperties'; export function getClassMetadata( type: Newable, ): ClassMetadata { + const postConstructMetadata: LegacyMetadata | undefined = + getReflectMetadata(type, POST_CONSTRUCT); + const preDestroyMetadata: LegacyMetadata | undefined = + getReflectMetadata(type, PRE_DESTROY); + const classMetadata: ClassMetadata = { constructorArguments: getClassMetadataConstructorArguments(type), + lifecycle: { + postConstructMethodName: postConstructMetadata?.value as string, + preDestroyMethodName: preDestroyMetadata?.value as string, + }, properties: getClassMetadataProperties(type), }; diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassMetadataConstructorArguments.spec.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataConstructorArguments.spec.ts new file mode 100644 index 00000000..7dbcfeb5 --- /dev/null +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataConstructorArguments.spec.ts @@ -0,0 +1,236 @@ +import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; + +jest.mock('@inversifyjs/reflect-metadata-utils'); + +import { Newable } from '@inversifyjs/common'; +import { getReflectMetadata } from '@inversifyjs/reflect-metadata-utils'; + +jest.mock('./getClassElementMetadataFromLegacyMetadata'); +jest.mock('./getClassElementMetadataFromNewable'); + +import { DESIGN_PARAM_TYPES, TAGGED } from '../../reflectMetadata/data/keys'; +import { ClassElementMetadata } from '../models/ClassElementMetadata'; +import { ClassElementMetadataKind } from '../models/ClassElementMetadataKind'; +import { LegacyMetadata } from '../models/LegacyMetadata'; +import { LegacyMetadataMap } from '../models/LegacyMetadataMap'; +import { getClassElementMetadataFromLegacyMetadata } from './getClassElementMetadataFromLegacyMetadata'; +import { getClassElementMetadataFromNewable } from './getClassElementMetadataFromNewable'; +import { getClassMetadataConstructorArguments } from './getClassMetadataConstructorArguments'; + +describe(getClassMetadataConstructorArguments.name, () => { + describe('when called, and getReflectMetadata() provides typescript metadata', () => { + let typescriptTypeFixture: Newable; + let typeFixture: Newable; + + let classElementMetadataFixture: ClassElementMetadata; + + let result: unknown; + + beforeAll(() => { + typescriptTypeFixture = class {}; + + typeFixture = class {}; + + classElementMetadataFixture = { + kind: ClassElementMetadataKind.unmanaged, + }; + + (getReflectMetadata as jest.Mock) + .mockReturnValueOnce([typescriptTypeFixture]) + .mockReturnValueOnce(undefined); + + ( + getClassElementMetadataFromNewable as jest.Mock< + typeof getClassElementMetadataFromNewable + > + ).mockReturnValueOnce(classElementMetadataFixture); + + result = getClassMetadataConstructorArguments(typeFixture); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call getReflectMetadata()', () => { + expect(getReflectMetadata).toHaveBeenCalledTimes(2); + expect(getReflectMetadata).toHaveBeenNthCalledWith( + 1, + typeFixture, + DESIGN_PARAM_TYPES, + ); + expect(getReflectMetadata).toHaveBeenNthCalledWith( + 2, + typeFixture, + TAGGED, + ); + }); + + it('should call getClassElementMetadataFromNewable()', () => { + expect(getClassElementMetadataFromNewable).toHaveBeenCalledTimes(1); + expect(getClassElementMetadataFromNewable).toHaveBeenCalledWith( + typescriptTypeFixture, + ); + }); + + it('should return ClassElementMetadata[]', () => { + expect(result).toStrictEqual([classElementMetadataFixture]); + }); + }); + + describe('when called, and getReflectMetadata() provides tag metadata', () => { + let legacyMetadataMapPropertyFixture: string | symbol; + let legacyMetadataListFixture: LegacyMetadata[]; + + let typeFixture: Newable; + + let classElementMetadataFixture: ClassElementMetadata; + + let result: unknown; + + beforeAll(() => { + legacyMetadataMapPropertyFixture = '0'; + legacyMetadataListFixture = [ + { + key: 'key-fixture', + value: 'value-fixture', + }, + ]; + + const legacyMetadataMap: LegacyMetadataMap = { + [legacyMetadataMapPropertyFixture]: legacyMetadataListFixture, + }; + + typeFixture = class {}; + + classElementMetadataFixture = { + kind: ClassElementMetadataKind.unmanaged, + }; + + (getReflectMetadata as jest.Mock) + .mockReturnValueOnce(undefined) + .mockReturnValueOnce(legacyMetadataMap); + + ( + getClassElementMetadataFromLegacyMetadata as jest.Mock< + typeof getClassElementMetadataFromLegacyMetadata + > + ).mockReturnValueOnce(classElementMetadataFixture); + + result = getClassMetadataConstructorArguments(typeFixture); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call getReflectMetadata()', () => { + expect(getReflectMetadata).toHaveBeenCalledTimes(2); + expect(getReflectMetadata).toHaveBeenNthCalledWith( + 1, + typeFixture, + DESIGN_PARAM_TYPES, + ); + expect(getReflectMetadata).toHaveBeenNthCalledWith( + 2, + typeFixture, + TAGGED, + ); + }); + + it('should call getClassElementMetadataFromLegacyMetadata()', () => { + expect(getClassElementMetadataFromLegacyMetadata).toHaveBeenCalledTimes( + 1, + ); + expect(getClassElementMetadataFromLegacyMetadata).toHaveBeenCalledWith( + legacyMetadataListFixture, + ); + }); + + it('should return ClassElementMetadata[]', () => { + expect(result).toStrictEqual([classElementMetadataFixture]); + }); + }); + + describe('when called, and getReflectMetadata() provides both typescript and tag metadata', () => { + let legacyMetadataMapPropertyFixture: string | symbol; + let legacyMetadataListFixture: LegacyMetadata[]; + + let typescriptTypeFixture: Newable; + + let typeFixture: Newable; + + let classElementMetadataFixture: ClassElementMetadata; + + let result: unknown; + + beforeAll(() => { + legacyMetadataMapPropertyFixture = '0'; + legacyMetadataListFixture = [ + { + key: 'key-fixture', + value: 'value-fixture', + }, + ]; + + const legacyMetadataMap: LegacyMetadataMap = { + [legacyMetadataMapPropertyFixture]: legacyMetadataListFixture, + }; + + typescriptTypeFixture = class {}; + + typeFixture = class {}; + + classElementMetadataFixture = { + kind: ClassElementMetadataKind.unmanaged, + }; + + (getReflectMetadata as jest.Mock) + .mockReturnValueOnce([typescriptTypeFixture]) + .mockReturnValueOnce(legacyMetadataMap); + + ( + getClassElementMetadataFromLegacyMetadata as jest.Mock< + typeof getClassElementMetadataFromLegacyMetadata + > + ).mockReturnValueOnce(classElementMetadataFixture); + + result = getClassMetadataConstructorArguments(typeFixture); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call getReflectMetadata()', () => { + expect(getReflectMetadata).toHaveBeenCalledTimes(2); + expect(getReflectMetadata).toHaveBeenNthCalledWith( + 1, + typeFixture, + DESIGN_PARAM_TYPES, + ); + expect(getReflectMetadata).toHaveBeenNthCalledWith( + 2, + typeFixture, + TAGGED, + ); + }); + + it('should call getClassElementMetadataFromLegacyMetadata()', () => { + expect(getClassElementMetadataFromLegacyMetadata).toHaveBeenCalledTimes( + 1, + ); + expect(getClassElementMetadataFromLegacyMetadata).toHaveBeenCalledWith( + legacyMetadataListFixture, + ); + }); + + it('should not call getClassElementMetadataFromNewable()', () => { + expect(getClassElementMetadataFromNewable).not.toHaveBeenCalled(); + }); + + it('should return ClassElementMetadata[]', () => { + expect(result).toStrictEqual([classElementMetadataFixture]); + }); + }); +}); diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassMetadataProperties.spec.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataProperties.spec.ts new file mode 100644 index 00000000..4cf4140a --- /dev/null +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataProperties.spec.ts @@ -0,0 +1,196 @@ +import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; + +jest.mock('@inversifyjs/reflect-metadata-utils'); + +import { getReflectMetadata } from '@inversifyjs/reflect-metadata-utils'; + +jest.mock('./getClassElementMetadataFromLegacyMetadata'); + +import { Newable } from '@inversifyjs/common'; + +import { TAGGED_PROP } from '../../reflectMetadata/data/keys'; +import { ClassElementMetadata } from '../models/ClassElementMetadata'; +import { ClassElementMetadataKind } from '../models/ClassElementMetadataKind'; +import { LegacyMetadata } from '../models/LegacyMetadata'; +import { LegacyMetadataMap } from '../models/LegacyMetadataMap'; +import { getClassElementMetadataFromLegacyMetadata } from './getClassElementMetadataFromLegacyMetadata'; +import { getClassMetadataProperties } from './getClassMetadataProperties'; + +describe(getClassMetadataProperties.name, () => { + describe('when called, and getReflectMetadata returns undefined', () => { + let typeFixture: Newable; + + let result: unknown; + + beforeAll(() => { + typeFixture = class {}; + + ( + getReflectMetadata as jest.Mock + ).mockReturnValueOnce(undefined); + + result = getClassMetadataProperties(typeFixture); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call getReflectMetadata()', () => { + expect(getReflectMetadata).toHaveBeenCalledTimes(1); + expect(getReflectMetadata).toHaveBeenCalledWith(typeFixture, TAGGED_PROP); + }); + + it('should return an empty Map', () => { + expect(result).toStrictEqual(new Map()); + }); + }); + + describe('when called, and getReflectMetadata returns LegacyMetadataMap with a symbol property', () => { + let legacyMetadataMapPropertyFixture: string | symbol; + let legacyMetadataListFixture: LegacyMetadata[]; + + let classElementMetadataFixture: ClassElementMetadata; + + let typeFixture: Newable; + + let result: unknown; + + beforeAll(() => { + legacyMetadataMapPropertyFixture = Symbol(); + legacyMetadataListFixture = [ + { + key: 'key-fixture', + value: 'value-fixture', + }, + ]; + + classElementMetadataFixture = { + kind: ClassElementMetadataKind.singleInjection, + name: undefined, + optional: false, + tags: new Map(), + targetName: undefined, + value: Symbol(), + }; + + typeFixture = class {}; + + const legacyMetadataMap: LegacyMetadataMap = { + [legacyMetadataMapPropertyFixture]: legacyMetadataListFixture, + }; + + ( + getReflectMetadata as jest.Mock + ).mockReturnValueOnce(legacyMetadataMap); + + ( + getClassElementMetadataFromLegacyMetadata as jest.Mock< + typeof getClassElementMetadataFromLegacyMetadata + > + ).mockReturnValueOnce(classElementMetadataFixture); + + result = getClassMetadataProperties(typeFixture); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call getReflectMetadata()', () => { + expect(getReflectMetadata).toHaveBeenCalledTimes(1); + expect(getReflectMetadata).toHaveBeenCalledWith(typeFixture, TAGGED_PROP); + }); + + it('should call getClassElementMetadataFromLegacyMetadata()', () => { + expect(getClassElementMetadataFromLegacyMetadata).toHaveBeenCalledTimes( + 1, + ); + expect(getClassElementMetadataFromLegacyMetadata).toHaveBeenCalledWith( + legacyMetadataListFixture, + ); + }); + + it('should return an empty Map', () => { + const expected: Map = new Map([ + [legacyMetadataMapPropertyFixture, classElementMetadataFixture], + ]); + + expect(result).toStrictEqual(expected); + }); + }); + + describe('when called, and getReflectMetadata returns LegacyMetadataMap with a string property', () => { + let legacyMetadataMapPropertyFixture: string | symbol; + let legacyMetadataListFixture: LegacyMetadata[]; + + let classElementMetadataFixture: ClassElementMetadata; + + let typeFixture: Newable; + + let result: unknown; + + beforeAll(() => { + legacyMetadataMapPropertyFixture = 'property-fixture'; + legacyMetadataListFixture = [ + { + key: 'key-fixture', + value: 'value-fixture', + }, + ]; + + classElementMetadataFixture = { + kind: ClassElementMetadataKind.singleInjection, + name: undefined, + optional: false, + tags: new Map(), + targetName: undefined, + value: Symbol(), + }; + + typeFixture = class {}; + + const legacyMetadataMap: LegacyMetadataMap = { + [legacyMetadataMapPropertyFixture]: legacyMetadataListFixture, + }; + + ( + getReflectMetadata as jest.Mock + ).mockReturnValueOnce(legacyMetadataMap); + + ( + getClassElementMetadataFromLegacyMetadata as jest.Mock< + typeof getClassElementMetadataFromLegacyMetadata + > + ).mockReturnValueOnce(classElementMetadataFixture); + + result = getClassMetadataProperties(typeFixture); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call getReflectMetadata()', () => { + expect(getReflectMetadata).toHaveBeenCalledTimes(1); + expect(getReflectMetadata).toHaveBeenCalledWith(typeFixture, TAGGED_PROP); + }); + + it('should call getClassElementMetadataFromLegacyMetadata()', () => { + expect(getClassElementMetadataFromLegacyMetadata).toHaveBeenCalledTimes( + 1, + ); + expect(getClassElementMetadataFromLegacyMetadata).toHaveBeenCalledWith( + legacyMetadataListFixture, + ); + }); + + it('should return an empty Map', () => { + const expected: Map = new Map([ + [legacyMetadataMapPropertyFixture, classElementMetadataFixture], + ]); + + expect(result).toStrictEqual(expected); + }); + }); +}); diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassMetadataProperties.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataProperties.ts index 650d0002..97afa502 100644 --- a/packages/container/libraries/core/src/metadata/calculations/getClassMetadataProperties.ts +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataProperties.ts @@ -3,6 +3,7 @@ import { getReflectMetadata } from '@inversifyjs/reflect-metadata-utils'; import { TAGGED_PROP } from '../../reflectMetadata/data/keys'; import { ClassElementMetadata } from '../models/ClassElementMetadata'; +import { LegacyMetadata } from '../models/LegacyMetadata'; import { LegacyMetadataMap } from '../models/LegacyMetadataMap'; import { getClassElementMetadataFromLegacyMetadata } from './getClassElementMetadataFromLegacyMetadata'; @@ -16,9 +17,10 @@ export function getClassMetadataProperties( new Map(); if (propertiesLegacyMetadata !== undefined) { - for (const [property, legacyMetadata] of Object.entries( - propertiesLegacyMetadata, - )) { + for (const property of Reflect.ownKeys(propertiesLegacyMetadata)) { + const legacyMetadata: LegacyMetadata[] = propertiesLegacyMetadata[ + property + ] as LegacyMetadata[]; propertiesMetadata.set( property, getClassElementMetadataFromLegacyMetadata(legacyMetadata), diff --git a/packages/container/libraries/core/src/metadata/models/ClassMetadata.ts b/packages/container/libraries/core/src/metadata/models/ClassMetadata.ts index 9580aeca..03bc3d09 100644 --- a/packages/container/libraries/core/src/metadata/models/ClassMetadata.ts +++ b/packages/container/libraries/core/src/metadata/models/ClassMetadata.ts @@ -1,6 +1,8 @@ import { ClassElementMetadata } from './ClassElementMetadata'; +import { ClassMetadataLifecycle } from './ClassMetadataLifecycle'; export interface ClassMetadata { constructorArguments: ClassElementMetadata[]; + lifecycle: ClassMetadataLifecycle; properties: Map; } diff --git a/packages/container/libraries/core/src/metadata/models/ClassMetadataLifecycle.ts b/packages/container/libraries/core/src/metadata/models/ClassMetadataLifecycle.ts new file mode 100644 index 00000000..f1a09374 --- /dev/null +++ b/packages/container/libraries/core/src/metadata/models/ClassMetadataLifecycle.ts @@ -0,0 +1,4 @@ +export interface ClassMetadataLifecycle { + postConstructMethodName: string | undefined; + preDestroyMethodName: string | undefined; +}