diff --git a/.changeset/tough-suits-repeat.md b/.changeset/tough-suits-repeat.md new file mode 100644 index 0000000..15bdd84 --- /dev/null +++ b/.changeset/tough-suits-repeat.md @@ -0,0 +1,5 @@ +--- +"@inversifyjs/core": minor +--- + +Added `getClassMetadataFromMetadataReader`. diff --git a/packages/container/libraries/core/src/index.ts b/packages/container/libraries/core/src/index.ts index 80c6721..5dad9ca 100644 --- a/packages/container/libraries/core/src/index.ts +++ b/packages/container/libraries/core/src/index.ts @@ -1,4 +1,5 @@ import { getClassMetadata } from './metadata/calculations/getClassMetadata'; +import { getClassMetadataFromMetadataReader } from './metadata/calculations/getClassMetadataFromMetadataReader'; import { ClassElementMetadata } from './metadata/models/ClassElementMetadata'; import { ClassElementMetadataKind } from './metadata/models/ClassElementMetadataKind'; import { ClassMetadata } from './metadata/models/ClassMetadata'; @@ -20,4 +21,8 @@ export type { UnmanagedClassElementMetadata, }; -export { ClassElementMetadataKind, getClassMetadata }; +export { + ClassElementMetadataKind, + getClassMetadata, + getClassMetadataFromMetadataReader, +}; diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassMetadata.spec.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadata.spec.ts index 2dee690..0e4be92 100644 --- a/packages/container/libraries/core/src/metadata/calculations/getClassMetadata.spec.ts +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadata.spec.ts @@ -7,6 +7,9 @@ import { getReflectMetadata } from '@inversifyjs/reflect-metadata-utils'; jest.mock('./getClassMetadataConstructorArguments'); jest.mock('./getClassMetadataProperties'); +import { Newable } from '@inversifyjs/common'; + +import { POST_CONSTRUCT, PRE_DESTROY } from '../../reflectMetadata/data/keys'; import { ClassElementMetadata } from '../models/ClassElementMetadata'; import { ClassElementMetadataKind } from '../models/ClassElementMetadataKind'; import { ClassMetadata } from '../models/ClassMetadata'; @@ -22,6 +25,8 @@ describe(getClassMetadata.name, () => { let postConstructMetadataFixture: LegacyMetadata; let preDestroyMetadataFixture: LegacyMetadata; + let typeFixture: Newable; + let result: unknown; beforeAll(() => { @@ -55,6 +60,8 @@ describe(getClassMetadata.name, () => { value: 'pre-destroy-value-fixture', }; + typeFixture = class {}; + ( getClassMetadataConstructorArguments as jest.Mock< typeof getClassMetadataConstructorArguments @@ -71,13 +78,39 @@ describe(getClassMetadata.name, () => { .mockReturnValueOnce(postConstructMetadataFixture) .mockReturnValueOnce(preDestroyMetadataFixture); - result = getClassMetadata(class {}); + result = getClassMetadata(typeFixture); }); afterAll(() => { jest.clearAllMocks(); }); + it('should call getReflectMetadata()', () => { + expect(getReflectMetadata).toHaveBeenCalledTimes(2); + expect(getReflectMetadata).toHaveBeenNthCalledWith( + 1, + typeFixture, + POST_CONSTRUCT, + ); + expect(getReflectMetadata).toHaveBeenNthCalledWith( + 2, + typeFixture, + PRE_DESTROY, + ); + }); + + it('should call getClassMetadataConstructorArguments()', () => { + expect(getClassMetadataConstructorArguments).toHaveBeenCalledTimes(1); + expect(getClassMetadataConstructorArguments).toHaveBeenCalledWith( + typeFixture, + ); + }); + + it('should call getClassMetadataProperties()', () => { + expect(getClassMetadataProperties).toHaveBeenCalledTimes(1); + expect(getClassMetadataProperties).toHaveBeenCalledWith(typeFixture); + }); + it('should return ClassMetadata', () => { const expected: ClassMetadata = { constructorArguments: constructorArgumentsMetadataFixture, diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassMetadataConstructorArgumentsFromMetadataReader.spec.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataConstructorArgumentsFromMetadataReader.spec.ts new file mode 100644 index 0000000..669ecbf --- /dev/null +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataConstructorArgumentsFromMetadataReader.spec.ts @@ -0,0 +1,246 @@ +import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; + +import { Newable } from '@inversifyjs/common'; + +jest.mock('./getClassElementMetadataFromLegacyMetadata'); +jest.mock('./getClassElementMetadataFromNewable'); + +import { ClassElementMetadata } from '../models/ClassElementMetadata'; +import { ClassElementMetadataKind } from '../models/ClassElementMetadataKind'; +import { LegacyMetadata } from '../models/LegacyMetadata'; +import { LegacyMetadataMap } from '../models/LegacyMetadataMap'; +import { LegacyMetadataReader } from '../models/LegacyMetadataReader'; +import { getClassElementMetadataFromLegacyMetadata } from './getClassElementMetadataFromLegacyMetadata'; +import { getClassElementMetadataFromNewable } from './getClassElementMetadataFromNewable'; +import { getClassMetadataConstructorArgumentsFromMetadataReader } from './getClassMetadataConstructorArgumentsFromMetadataReader'; + +describe(getClassMetadataConstructorArgumentsFromMetadataReader.name, () => { + describe('when called, and getReflectMetadata() provides typescript metadata', () => { + let typescriptTypeFixture: Newable; + + let typeFixture: Newable; + let metadataReaderMock: jest.Mocked; + + let classElementMetadataFixture: ClassElementMetadata; + + let result: unknown; + + beforeAll(() => { + typescriptTypeFixture = class {}; + + typeFixture = class {}; + metadataReaderMock = { + getConstructorMetadata: jest.fn(), + getPropertiesMetadata: jest.fn(), + }; + + classElementMetadataFixture = { + kind: ClassElementMetadataKind.unmanaged, + }; + + metadataReaderMock.getConstructorMetadata.mockReturnValueOnce({ + compilerGeneratedMetadata: [typescriptTypeFixture], + userGeneratedMetadata: {}, + }); + + ( + getClassElementMetadataFromNewable as jest.Mock< + typeof getClassElementMetadataFromNewable + > + ).mockReturnValueOnce(classElementMetadataFixture); + + result = getClassMetadataConstructorArgumentsFromMetadataReader( + typeFixture, + metadataReaderMock, + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call metadataReader.getConstructorMetadata()', () => { + expect(metadataReaderMock.getConstructorMetadata).toHaveBeenCalledTimes( + 1, + ); + expect(metadataReaderMock.getConstructorMetadata).toHaveBeenCalledWith( + typeFixture, + ); + }); + + 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 metadataReaderMock: jest.Mocked; + + let classElementMetadataFixture: ClassElementMetadata; + + let result: unknown; + + beforeAll(() => { + legacyMetadataMapPropertyFixture = '0'; + legacyMetadataListFixture = [ + { + key: 'key-fixture', + value: 'value-fixture', + }, + ]; + + const legacyMetadataMap: LegacyMetadataMap = { + [legacyMetadataMapPropertyFixture]: legacyMetadataListFixture, + }; + + typeFixture = class {}; + metadataReaderMock = { + getConstructorMetadata: jest.fn(), + getPropertiesMetadata: jest.fn(), + }; + + classElementMetadataFixture = { + kind: ClassElementMetadataKind.unmanaged, + }; + + metadataReaderMock.getConstructorMetadata.mockReturnValueOnce({ + compilerGeneratedMetadata: undefined, + userGeneratedMetadata: legacyMetadataMap, + }); + + ( + getClassElementMetadataFromLegacyMetadata as jest.Mock< + typeof getClassElementMetadataFromLegacyMetadata + > + ).mockReturnValueOnce(classElementMetadataFixture); + + result = getClassMetadataConstructorArgumentsFromMetadataReader( + typeFixture, + metadataReaderMock, + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call metadataReader.getConstructorMetadata()', () => { + expect(metadataReaderMock.getConstructorMetadata).toHaveBeenCalledTimes( + 1, + ); + expect(metadataReaderMock.getConstructorMetadata).toHaveBeenCalledWith( + typeFixture, + ); + }); + + 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 metadataReaderMock: jest.Mocked; + + 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 {}; + metadataReaderMock = { + getConstructorMetadata: jest.fn(), + getPropertiesMetadata: jest.fn(), + }; + + classElementMetadataFixture = { + kind: ClassElementMetadataKind.unmanaged, + }; + + metadataReaderMock.getConstructorMetadata.mockReturnValueOnce({ + compilerGeneratedMetadata: [typescriptTypeFixture], + userGeneratedMetadata: legacyMetadataMap, + }); + + ( + getClassElementMetadataFromLegacyMetadata as jest.Mock< + typeof getClassElementMetadataFromLegacyMetadata + > + ).mockReturnValueOnce(classElementMetadataFixture); + + result = getClassMetadataConstructorArgumentsFromMetadataReader( + typeFixture, + metadataReaderMock, + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call metadataReader.getConstructorMetadata()', () => { + expect(metadataReaderMock.getConstructorMetadata).toHaveBeenCalledTimes( + 1, + ); + expect(metadataReaderMock.getConstructorMetadata).toHaveBeenCalledWith( + typeFixture, + ); + }); + + 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/getClassMetadataConstructorArgumentsFromMetadataReader.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataConstructorArgumentsFromMetadataReader.ts new file mode 100644 index 0000000..7cbe683 --- /dev/null +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataConstructorArgumentsFromMetadataReader.ts @@ -0,0 +1,47 @@ +import { Newable } from '@inversifyjs/common'; + +import { ClassElementMetadata } from '../models/ClassElementMetadata'; +import { LegacyConstructorMetadata } from '../models/LegacyConstructorMetadata'; +import { LegacyMetadataReader } from '../models/LegacyMetadataReader'; +import { getClassElementMetadataFromLegacyMetadata } from './getClassElementMetadataFromLegacyMetadata'; +import { getClassElementMetadataFromNewable } from './getClassElementMetadataFromNewable'; + +export function getClassMetadataConstructorArgumentsFromMetadataReader< + TInstance, + TArgs extends unknown[], +>( + type: Newable, + metadataReader: LegacyMetadataReader, +): ClassElementMetadata[] { + const legacyConstructorMetadata: LegacyConstructorMetadata = + metadataReader.getConstructorMetadata(type); + + const constructorArgumentsMetadata: ClassElementMetadata[] = []; + + for (const [stringifiedIndex, metadataList] of Object.entries( + legacyConstructorMetadata.userGeneratedMetadata, + )) { + const index: number = parseInt(stringifiedIndex); + + constructorArgumentsMetadata[index] = + getClassElementMetadataFromLegacyMetadata(metadataList); + } + + if (legacyConstructorMetadata.compilerGeneratedMetadata !== undefined) { + for ( + let i: number = 0; + i < legacyConstructorMetadata.compilerGeneratedMetadata.length; + ++i + ) { + if (constructorArgumentsMetadata[i] === undefined) { + const typescriptMetadata: Newable = legacyConstructorMetadata + .compilerGeneratedMetadata[i] as Newable; + + constructorArgumentsMetadata[i] = + getClassElementMetadataFromNewable(typescriptMetadata); + } + } + } + + return constructorArgumentsMetadata; +} diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassMetadataFromMetadataReader.spec.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataFromMetadataReader.spec.ts new file mode 100644 index 0000000..7f27994 --- /dev/null +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataFromMetadataReader.spec.ts @@ -0,0 +1,140 @@ +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('./getClassMetadataConstructorArgumentsFromMetadataReader'); +jest.mock('./getClassMetadataPropertiesFromMetadataReader'); + +import { Newable } from '@inversifyjs/common'; + +import { POST_CONSTRUCT, PRE_DESTROY } from '../../reflectMetadata/data/keys'; +import { ClassElementMetadata } from '../models/ClassElementMetadata'; +import { ClassElementMetadataKind } from '../models/ClassElementMetadataKind'; +import { ClassMetadata } from '../models/ClassMetadata'; +import { LegacyMetadata } from '../models/LegacyMetadata'; +import { LegacyMetadataReader } from '../models/LegacyMetadataReader'; +import { getClassMetadataConstructorArgumentsFromMetadataReader } from './getClassMetadataConstructorArgumentsFromMetadataReader'; +import { getClassMetadataFromMetadataReader } from './getClassMetadataFromMetadataReader'; +import { getClassMetadataPropertiesFromMetadataReader } from './getClassMetadataPropertiesFromMetadataReader'; + +describe(getClassMetadataFromMetadataReader.name, () => { + describe('when called, and getReflectMetadata() returns LegacyMetadata', () => { + let constructorArgumentsMetadataFixture: ClassElementMetadata[]; + let propertiesMetadataFixture: Map; + let postConstructMetadataFixture: LegacyMetadata; + let preDestroyMetadataFixture: LegacyMetadata; + + let typeFixture: Newable; + let metadataReaderFixture: LegacyMetadataReader; + + 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', + }; + + typeFixture = class {}; + metadataReaderFixture = Symbol() as unknown as LegacyMetadataReader; + + ( + getClassMetadataConstructorArgumentsFromMetadataReader as jest.Mock< + typeof getClassMetadataConstructorArgumentsFromMetadataReader + > + ).mockReturnValueOnce(constructorArgumentsMetadataFixture); + + ( + getClassMetadataPropertiesFromMetadataReader as jest.Mock< + typeof getClassMetadataPropertiesFromMetadataReader + > + ).mockReturnValueOnce(propertiesMetadataFixture); + + (getReflectMetadata as jest.Mock) + .mockReturnValueOnce(postConstructMetadataFixture) + .mockReturnValueOnce(preDestroyMetadataFixture); + + result = getClassMetadataFromMetadataReader( + typeFixture, + metadataReaderFixture, + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call getReflectMetadata()', () => { + expect(getReflectMetadata).toHaveBeenCalledTimes(2); + expect(getReflectMetadata).toHaveBeenNthCalledWith( + 1, + typeFixture, + POST_CONSTRUCT, + ); + expect(getReflectMetadata).toHaveBeenNthCalledWith( + 2, + typeFixture, + PRE_DESTROY, + ); + }); + + it('should call getClassMetadataConstructorArgumentsFromMetadataReader()', () => { + expect( + getClassMetadataConstructorArgumentsFromMetadataReader, + ).toHaveBeenCalledTimes(1); + expect( + getClassMetadataConstructorArgumentsFromMetadataReader, + ).toHaveBeenCalledWith(typeFixture, metadataReaderFixture); + }); + + it('should call getClassMetadataPropertiesFromMetadataReader()', () => { + expect( + getClassMetadataPropertiesFromMetadataReader, + ).toHaveBeenCalledTimes(1); + expect(getClassMetadataPropertiesFromMetadataReader).toHaveBeenCalledWith( + typeFixture, + metadataReaderFixture, + ); + }); + + 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/getClassMetadataFromMetadataReader.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataFromMetadataReader.ts new file mode 100644 index 0000000..8b3b69f --- /dev/null +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataFromMetadataReader.ts @@ -0,0 +1,40 @@ +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 { LegacyMetadataReader } from '../models/LegacyMetadataReader'; +import { getClassMetadataConstructorArgumentsFromMetadataReader } from './getClassMetadataConstructorArgumentsFromMetadataReader'; +import { getClassMetadataPropertiesFromMetadataReader } from './getClassMetadataPropertiesFromMetadataReader'; + +export function getClassMetadataFromMetadataReader< + TInstance, + TArgs extends unknown[], +>( + type: Newable, + metadataReader: LegacyMetadataReader, +): ClassMetadata { + const postConstructMetadata: LegacyMetadata | undefined = + getReflectMetadata(type, POST_CONSTRUCT); + const preDestroyMetadata: LegacyMetadata | undefined = + getReflectMetadata(type, PRE_DESTROY); + + const classMetadata: ClassMetadata = { + constructorArguments: + getClassMetadataConstructorArgumentsFromMetadataReader( + type, + metadataReader, + ), + lifecycle: { + postConstructMethodName: postConstructMetadata?.value as string, + preDestroyMethodName: preDestroyMetadata?.value as string, + }, + properties: getClassMetadataPropertiesFromMetadataReader( + type, + metadataReader, + ), + }; + + return classMetadata; +} diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassMetadataProperties.spec.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataProperties.spec.ts index 4cf4140..938e41e 100644 --- a/packages/container/libraries/core/src/metadata/calculations/getClassMetadataProperties.spec.ts +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataProperties.spec.ts @@ -111,7 +111,7 @@ describe(getClassMetadataProperties.name, () => { ); }); - it('should return an empty Map', () => { + it('should return a Map', () => { const expected: Map = new Map([ [legacyMetadataMapPropertyFixture, classElementMetadataFixture], ]); @@ -185,7 +185,7 @@ describe(getClassMetadataProperties.name, () => { ); }); - it('should return an empty Map', () => { + it('should return a Map', () => { const expected: Map = new Map([ [legacyMetadataMapPropertyFixture, classElementMetadataFixture], ]); diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassMetadataPropertiesFromMetadataReader.spec.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataPropertiesFromMetadataReader.spec.ts new file mode 100644 index 0000000..0c3bcff --- /dev/null +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataPropertiesFromMetadataReader.spec.ts @@ -0,0 +1,184 @@ +import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; + +jest.mock('./getClassElementMetadataFromLegacyMetadata'); + +import { Newable } from '@inversifyjs/common'; + +import { ClassElementMetadata } from '../models/ClassElementMetadata'; +import { ClassElementMetadataKind } from '../models/ClassElementMetadataKind'; +import { LegacyMetadata } from '../models/LegacyMetadata'; +import { LegacyMetadataMap } from '../models/LegacyMetadataMap'; +import { LegacyMetadataReader } from '../models/LegacyMetadataReader'; +import { getClassElementMetadataFromLegacyMetadata } from './getClassElementMetadataFromLegacyMetadata'; +import { getClassMetadataPropertiesFromMetadataReader } from './getClassMetadataPropertiesFromMetadataReader'; + +describe(getClassMetadataPropertiesFromMetadataReader.name, () => { + describe('when called, and metadataReader.getPropertiesMetadata() returns LegacyMetadataMap with a symbol property', () => { + let legacyMetadataMapPropertyFixture: string | symbol; + let legacyMetadataListFixture: LegacyMetadata[]; + + let classElementMetadataFixture: ClassElementMetadata; + + let typeFixture: Newable; + let metadataReaderMock: jest.Mocked; + + 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 {}; + + metadataReaderMock = { + getConstructorMetadata: jest.fn(), + getPropertiesMetadata: jest.fn(), + }; + + const legacyMetadataMap: LegacyMetadataMap = { + [legacyMetadataMapPropertyFixture]: legacyMetadataListFixture, + }; + + metadataReaderMock.getPropertiesMetadata.mockReturnValueOnce( + legacyMetadataMap, + ); + + ( + getClassElementMetadataFromLegacyMetadata as jest.Mock< + typeof getClassElementMetadataFromLegacyMetadata + > + ).mockReturnValueOnce(classElementMetadataFixture); + + result = getClassMetadataPropertiesFromMetadataReader( + typeFixture, + metadataReaderMock, + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call metadataReader.getPropertiesMetadata()', () => { + expect(metadataReaderMock.getPropertiesMetadata).toHaveBeenCalledTimes(1); + expect(metadataReaderMock.getPropertiesMetadata).toHaveBeenCalledWith( + typeFixture, + ); + }); + + it('should call getClassElementMetadataFromLegacyMetadata()', () => { + expect(getClassElementMetadataFromLegacyMetadata).toHaveBeenCalledTimes( + 1, + ); + expect(getClassElementMetadataFromLegacyMetadata).toHaveBeenCalledWith( + legacyMetadataListFixture, + ); + }); + + it('should return a Map', () => { + const expected: Map = new Map([ + [legacyMetadataMapPropertyFixture, classElementMetadataFixture], + ]); + + expect(result).toStrictEqual(expected); + }); + }); + + describe('when called, and metadataReader.getPropertiesMetadata() returns LegacyMetadataMap with a string property', () => { + let legacyMetadataMapPropertyFixture: string | symbol; + let legacyMetadataListFixture: LegacyMetadata[]; + + let classElementMetadataFixture: ClassElementMetadata; + + let typeFixture: Newable; + let metadataReaderMock: jest.Mocked; + + 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 {}; + metadataReaderMock = { + getConstructorMetadata: jest.fn(), + getPropertiesMetadata: jest.fn(), + }; + + const legacyMetadataMap: LegacyMetadataMap = { + [legacyMetadataMapPropertyFixture]: legacyMetadataListFixture, + }; + + metadataReaderMock.getPropertiesMetadata.mockReturnValueOnce( + legacyMetadataMap, + ); + + ( + getClassElementMetadataFromLegacyMetadata as jest.Mock< + typeof getClassElementMetadataFromLegacyMetadata + > + ).mockReturnValueOnce(classElementMetadataFixture); + + result = getClassMetadataPropertiesFromMetadataReader( + typeFixture, + metadataReaderMock, + ); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call metadataReader.getPropertiesMetadata()', () => { + expect(metadataReaderMock.getPropertiesMetadata).toHaveBeenCalledTimes(1); + expect(metadataReaderMock.getPropertiesMetadata).toHaveBeenCalledWith( + typeFixture, + ); + }); + + it('should call getClassElementMetadataFromLegacyMetadata()', () => { + expect(getClassElementMetadataFromLegacyMetadata).toHaveBeenCalledTimes( + 1, + ); + expect(getClassElementMetadataFromLegacyMetadata).toHaveBeenCalledWith( + legacyMetadataListFixture, + ); + }); + + it('should return a Map', () => { + const expected: Map = new Map([ + [legacyMetadataMapPropertyFixture, classElementMetadataFixture], + ]); + + expect(result).toStrictEqual(expected); + }); + }); +}); diff --git a/packages/container/libraries/core/src/metadata/calculations/getClassMetadataPropertiesFromMetadataReader.ts b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataPropertiesFromMetadataReader.ts new file mode 100644 index 0000000..e348554 --- /dev/null +++ b/packages/container/libraries/core/src/metadata/calculations/getClassMetadataPropertiesFromMetadataReader.ts @@ -0,0 +1,33 @@ +import { Newable } from '@inversifyjs/common'; + +import { ClassElementMetadata } from '../models/ClassElementMetadata'; +import { LegacyMetadata } from '../models/LegacyMetadata'; +import { LegacyMetadataMap } from '../models/LegacyMetadataMap'; +import { LegacyMetadataReader } from '../models/LegacyMetadataReader'; +import { getClassElementMetadataFromLegacyMetadata } from './getClassElementMetadataFromLegacyMetadata'; + +export function getClassMetadataPropertiesFromMetadataReader< + TInstance, + TArgs extends unknown[], +>( + type: Newable, + metadataReader: LegacyMetadataReader, +): Map { + const propertiesLegacyMetadata: LegacyMetadataMap = + metadataReader.getPropertiesMetadata(type); + + const propertiesMetadata: Map = + new Map(); + + for (const property of Reflect.ownKeys(propertiesLegacyMetadata)) { + const legacyMetadata: LegacyMetadata[] = propertiesLegacyMetadata[ + property + ] as LegacyMetadata[]; + propertiesMetadata.set( + property, + getClassElementMetadataFromLegacyMetadata(legacyMetadata), + ); + } + + return propertiesMetadata; +} diff --git a/packages/container/libraries/core/src/metadata/models/LegacyConstructorMetadata.ts b/packages/container/libraries/core/src/metadata/models/LegacyConstructorMetadata.ts new file mode 100644 index 0000000..d1ee562 --- /dev/null +++ b/packages/container/libraries/core/src/metadata/models/LegacyConstructorMetadata.ts @@ -0,0 +1,6 @@ +import { LegacyMetadataMap } from './LegacyMetadataMap'; + +export interface LegacyConstructorMetadata { + compilerGeneratedMetadata: NewableFunction[] | undefined; + userGeneratedMetadata: LegacyMetadataMap; +} diff --git a/packages/container/libraries/core/src/metadata/models/LegacyMetadataReader.ts b/packages/container/libraries/core/src/metadata/models/LegacyMetadataReader.ts new file mode 100644 index 0000000..811f16f --- /dev/null +++ b/packages/container/libraries/core/src/metadata/models/LegacyMetadataReader.ts @@ -0,0 +1,9 @@ +import { LegacyConstructorMetadata } from './LegacyConstructorMetadata'; +import { LegacyMetadataMap } from './LegacyMetadataMap'; + +export interface LegacyMetadataReader { + getConstructorMetadata( + constructorFunc: NewableFunction, + ): LegacyConstructorMetadata; + getPropertiesMetadata(constructorFunc: NewableFunction): LegacyMetadataMap; +}