From 9257c9ac5cec333f9b20cda9ae354da07e575b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Pintos=20L=C3=B3pez?= Date: Fri, 10 Jan 2025 20:00:43 +0100 Subject: [PATCH] fix(core): update BindToFluentSyntaxImplementation.to update method to rely in class scope if found in metadata --- .changeset/thirty-nails-smile.md | 5 + .../BindingFluentSyntaxImplementation.spec.ts | 98 ++++++++++++++----- .../BindingFluentSyntaxImplementation.ts | 26 ++--- .../fixtures/ClassMetadataFixtures.ts | 35 +++++++ 4 files changed, 117 insertions(+), 47 deletions(-) create mode 100644 .changeset/thirty-nails-smile.md create mode 100644 packages/container/libraries/container/src/metadata/fixtures/ClassMetadataFixtures.ts diff --git a/.changeset/thirty-nails-smile.md b/.changeset/thirty-nails-smile.md new file mode 100644 index 00000000..c1a2ecf3 --- /dev/null +++ b/.changeset/thirty-nails-smile.md @@ -0,0 +1,5 @@ +--- +"@inversifyjs/core": patch +--- + +Updated `BindToFluentSyntaxImplementation.to` to set binding scope if found in class metadata diff --git a/packages/container/libraries/container/src/binding/models/BindingFluentSyntaxImplementation.spec.ts b/packages/container/libraries/container/src/binding/models/BindingFluentSyntaxImplementation.spec.ts index 319e221f..895a8aa5 100644 --- a/packages/container/libraries/container/src/binding/models/BindingFluentSyntaxImplementation.spec.ts +++ b/packages/container/libraries/container/src/binding/models/BindingFluentSyntaxImplementation.spec.ts @@ -1,5 +1,7 @@ import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals'; +jest.mock('@inversifyjs/core'); + jest.mock('../actions/getBindingId'); jest.mock('../calculations/isAnyAncestorBindingMetadata'); jest.mock('../calculations/isAnyAncestorBindingMetadataWithName'); @@ -22,9 +24,11 @@ import { bindingScopeValues, BindingType, bindingTypeValues, + ClassMetadata, ConstantValueBinding, DynamicValueBuilder, Factory, + getClassMetadata, InstanceBinding, MetadataName, MetadataTag, @@ -35,6 +39,7 @@ import { } from '@inversifyjs/core'; import { Writable } from '../../common/models/Writable'; +import { ClassMetadataFixtures } from '../../metadata/fixtures/ClassMetadataFixtures'; import { getBindingId } from '../actions/getBindingId'; import { isAnyAncestorBindingMetadata } from '../calculations/isAnyAncestorBindingMetadata'; import { isAnyAncestorBindingMetadataWithName } from '../calculations/isAnyAncestorBindingMetadataWithName'; @@ -152,8 +157,6 @@ describe(BindInFluentSyntaxImplementation.name, () => { }); describe(BindToFluentSyntaxImplementation.name, () => { - class Foo {} - let bindingIdFixture: number; let dynamicValueBuilderfixture: DynamicValueBuilder; @@ -204,30 +207,6 @@ describe(BindToFluentSyntaxImplementation.name, () => { NewableFunction, ] >([ - [ - '.to()', - ( - bindToFluentSyntaxImplementation: BindToFluentSyntaxImplementation, - ): unknown => bindToFluentSyntaxImplementation.to(Foo), - (): Binding => ({ - cache: { - isRight: false, - value: undefined, - }, - id: bindingIdFixture, - implementationType: Foo, - isSatisfiedBy: expect.any(Function) as unknown as ( - metadata: BindingMetadata, - ) => boolean, - moduleId: containerModuleIdFixture, - onActivation: undefined, - onDeactivation: undefined, - scope: defaultScopeFixture, - serviceIdentifier: serviceIdentifierFixture, - type: bindingTypeValues.Instance, - }), - BindWhenOnFluentSyntaxImplementation, - ], [ '.toConstantValue()', ( @@ -452,10 +431,14 @@ describe(BindToFluentSyntaxImplementation.name, () => { ); }); - describe('when called', () => { + describe('when called, and getClassMetadata() returns ClassMetadata with undefined scope', () => { let result: unknown; beforeAll(() => { + ( + getClassMetadata as jest.Mock + ).mockReturnValueOnce(ClassMetadataFixtures.withScopeUndefined); + result = bindToFluentSyntaxImplementation.toSelf(); }); @@ -463,6 +446,13 @@ describe(BindToFluentSyntaxImplementation.name, () => { jest.clearAllMocks(); }); + it('should call getClassMetadata()', () => { + expect(getClassMetadata).toHaveBeenCalledTimes(1); + expect(getClassMetadata).toHaveBeenCalledWith( + serviceIdentifierFixture, + ); + }); + it('should call callback()', () => { const expectedBinding: InstanceBinding = { cache: { @@ -490,6 +480,60 @@ describe(BindToFluentSyntaxImplementation.name, () => { expect(result).toBeInstanceOf(BindInWhenOnFluentSyntaxImplementation); }); }); + + describe('when called, and getClassMetadata() returns ClassMetadata with scope', () => { + let classMetadataFixture: ClassMetadata; + + let result: unknown; + + beforeAll(() => { + classMetadataFixture = ClassMetadataFixtures.withScopeRequest; + + ( + getClassMetadata as jest.Mock + ).mockReturnValueOnce(classMetadataFixture); + + result = bindToFluentSyntaxImplementation.toSelf(); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('should call getClassMetadata()', () => { + expect(getClassMetadata).toHaveBeenCalledTimes(1); + expect(getClassMetadata).toHaveBeenCalledWith( + serviceIdentifierFixture, + ); + }); + + it('should call callback()', () => { + const expectedBinding: InstanceBinding = { + cache: { + isRight: false, + value: undefined, + }, + id: getBindingId(), + implementationType: Foo, + isSatisfiedBy: expect.any(Function) as unknown as ( + metadata: BindingMetadata, + ) => boolean, + moduleId: containerModuleIdFixture, + onActivation: undefined, + onDeactivation: undefined, + scope: classMetadataFixture.scope as BindingScope, + serviceIdentifier: serviceIdentifierFixture, + type: bindingTypeValues.Instance, + }; + + expect(callbackMock).toHaveBeenCalledTimes(1); + expect(callbackMock).toHaveBeenCalledWith(expectedBinding); + }); + + it('should return expected result', () => { + expect(result).toBeInstanceOf(BindInWhenOnFluentSyntaxImplementation); + }); + }); }); }); diff --git a/packages/container/libraries/container/src/binding/models/BindingFluentSyntaxImplementation.ts b/packages/container/libraries/container/src/binding/models/BindingFluentSyntaxImplementation.ts index 3cbbaae6..0a6c8e49 100644 --- a/packages/container/libraries/container/src/binding/models/BindingFluentSyntaxImplementation.ts +++ b/packages/container/libraries/container/src/binding/models/BindingFluentSyntaxImplementation.ts @@ -8,11 +8,13 @@ import { bindingScopeValues, BindingType, bindingTypeValues, + ClassMetadata, ConstantValueBinding, DynamicValueBinding, DynamicValueBuilder, Factory, FactoryBinding, + getClassMetadata, InstanceBinding, MetadataName, MetadataTag, @@ -96,6 +98,8 @@ export class BindToFluentSyntaxImplementation } public to(type: Newable): BindInWhenOnFluentSyntax { + const classMetadata: ClassMetadata = getClassMetadata(type); + const binding: InstanceBinding = { cache: { isRight: false, @@ -107,7 +111,7 @@ export class BindToFluentSyntaxImplementation moduleId: this.#containerModuleId, onActivation: undefined, onDeactivation: undefined, - scope: this.#defaultScope, + scope: classMetadata.scope ?? this.#defaultScope, serviceIdentifier: this.#serviceIdentifier, type: bindingTypeValues.Instance, }; @@ -124,25 +128,7 @@ export class BindToFluentSyntaxImplementation ); } - const binding: InstanceBinding = { - cache: { - isRight: false, - value: undefined, - }, - id: getBindingId(), - implementationType: this.#serviceIdentifier as Newable, - isSatisfiedBy: BindingConstraintUtils.always, - moduleId: this.#containerModuleId, - onActivation: undefined, - onDeactivation: undefined, - scope: this.#defaultScope, - serviceIdentifier: this.#serviceIdentifier, - type: bindingTypeValues.Instance, - }; - - this.#callback(binding); - - return new BindInWhenOnFluentSyntaxImplementation(binding); + return this.to(this.#serviceIdentifier as Newable); } public toConstantValue(value: T): BindWhenOnFluentSyntax { diff --git a/packages/container/libraries/container/src/metadata/fixtures/ClassMetadataFixtures.ts b/packages/container/libraries/container/src/metadata/fixtures/ClassMetadataFixtures.ts new file mode 100644 index 00000000..c65539c1 --- /dev/null +++ b/packages/container/libraries/container/src/metadata/fixtures/ClassMetadataFixtures.ts @@ -0,0 +1,35 @@ +import { bindingScopeValues, ClassMetadata } from '@inversifyjs/core'; + +export class ClassMetadataFixtures { + public static get any(): ClassMetadata { + const fixture: ClassMetadata = { + constructorArguments: [], + lifecycle: { + postConstructMethodName: undefined, + preDestroyMethodName: undefined, + }, + properties: new Map(), + scope: undefined, + }; + + return fixture; + } + + public static get withScopeRequest(): ClassMetadata { + const fixture: ClassMetadata = { + ...ClassMetadataFixtures.any, + scope: bindingScopeValues.Request, + }; + + return fixture; + } + + public static get withScopeUndefined(): ClassMetadata { + const fixture: ClassMetadata = { + ...ClassMetadataFixtures.any, + scope: undefined, + }; + + return fixture; + } +}