diff --git a/CHANGELOG.md b/CHANGELOG.md index d9151e21..1f1b14d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fix `Target.isTagged()` to exclude `optional` from tag injections #1190. +- Update `toConstructor`, `toFactory`, `toFunction`, `toAutoFactory`, `toProvider` and `toConstantValue` to have singleton scope #1297. ## [5.0.1] - 2018-10-17 ### Added diff --git a/src/syntax/binding_to_syntax.ts b/src/syntax/binding_to_syntax.ts index 5b093f1f..2b9163ff 100644 --- a/src/syntax/binding_to_syntax.ts +++ b/src/syntax/binding_to_syntax.ts @@ -1,5 +1,5 @@ import * as ERROR_MSGS from "../constants/error_msgs"; -import { BindingTypeEnum } from "../constants/literal_types"; +import { BindingScopeEnum, BindingTypeEnum } from "../constants/literal_types"; import { interfaces } from "../interfaces/interfaces"; import { BindingInWhenOnSyntax } from "./binding_in_when_on_syntax"; import { BindingWhenOnSyntax } from "./binding_when_on_syntax"; @@ -31,6 +31,7 @@ class BindingToSyntax implements interfaces.BindingToSyntax { this._binding.cache = value; this._binding.dynamicValue = null; this._binding.implementationType = null; + this._binding.scope = BindingScopeEnum.Singleton; return new BindingWhenOnSyntax(this._binding); } @@ -45,12 +46,14 @@ class BindingToSyntax implements interfaces.BindingToSyntax { public toConstructor(constructor: interfaces.Newable): interfaces.BindingWhenOnSyntax { this._binding.type = BindingTypeEnum.Constructor; this._binding.implementationType = constructor as any; + this._binding.scope = BindingScopeEnum.Singleton; return new BindingWhenOnSyntax(this._binding); } public toFactory(factory: interfaces.FactoryCreator): interfaces.BindingWhenOnSyntax { this._binding.type = BindingTypeEnum.Factory; this._binding.factory = factory; + this._binding.scope = BindingScopeEnum.Singleton; return new BindingWhenOnSyntax(this._binding); } @@ -59,6 +62,7 @@ class BindingToSyntax implements interfaces.BindingToSyntax { if (typeof func !== "function") { throw new Error(ERROR_MSGS.INVALID_FUNCTION_BINDING); } const bindingWhenOnSyntax = this.toConstantValue(func); this._binding.type = BindingTypeEnum.Function; + this._binding.scope = BindingScopeEnum.Singleton; return bindingWhenOnSyntax; } @@ -68,12 +72,14 @@ class BindingToSyntax implements interfaces.BindingToSyntax { const autofactory = () => context.container.get(serviceIdentifier); return autofactory; }; + this._binding.scope = BindingScopeEnum.Singleton; return new BindingWhenOnSyntax(this._binding); } public toProvider(provider: interfaces.ProviderCreator): interfaces.BindingWhenOnSyntax { this._binding.type = BindingTypeEnum.Provider; this._binding.provider = provider; + this._binding.scope = BindingScopeEnum.Singleton; return new BindingWhenOnSyntax(this._binding); } diff --git a/test/bugs/issue_1297.test.ts b/test/bugs/issue_1297.test.ts new file mode 100644 index 00000000..df90f762 --- /dev/null +++ b/test/bugs/issue_1297.test.ts @@ -0,0 +1,144 @@ +import { expect } from "chai"; +import * as sinon from "sinon"; +import { Container, injectable, interfaces } from "../../src/inversify"; + +describe("Issue 1297", () => { + it('should call onActivation once if the service is a constant value binding', () => { + const container = new Container(); + + const onActivationHandlerSpy = sinon.spy< + (ctx: interfaces.Context, message: string) => string + >((_ctx: interfaces.Context, message: string) => message); + + container.bind("message") + .toConstantValue("Hello world") + .onActivation(onActivationHandlerSpy); + + container.get("message"); + container.get("message"); + + expect(onActivationHandlerSpy.callCount).to.eq(1); + }); + + it('should call onActivation once if the service is a factory binding', () => { + + @injectable() + class Katana { + public hit() { + return "cut!"; + } + } + + const container = new Container(); + + const onActivationHandlerSpy = sinon.spy< + (ctx: interfaces.Context, instance: interfaces.Factory) => interfaces.Factory + >((_ctx: interfaces.Context, instance: interfaces.Factory) => instance); + + container.bind("Katana").to(Katana); + + container.bind>("Factory").toFactory((context) => + () => + context.container.get("Katana")).onActivation(onActivationHandlerSpy); + + container.get("Factory"); + container.get("Factory"); + + expect(onActivationHandlerSpy.callCount).to.eq(1); + }); + + it('should call onActivation once if the service is an auto factory binding', () => { + + @injectable() + class Katana { + public hit() { + return "cut!"; + } + } + + const container = new Container(); + + const onActivationHandlerSpy = sinon.spy< + (ctx: interfaces.Context, instance: interfaces.Factory) => interfaces.Factory + >((_ctx: interfaces.Context, instance: interfaces.Factory) => instance); + + container.bind("Katana").to(Katana); + + container.bind>("Factory") + .toAutoFactory("Katana").onActivation(onActivationHandlerSpy); + + container.get("Factory"); + container.get("Factory"); + + expect(onActivationHandlerSpy.callCount).to.eq(1); + }); + + it('should call onActivation once if the service is a function binding', () => { + + const container = new Container(); + + const onActivationHandlerSpy = sinon.spy< + (ctx: interfaces.Context, messageGenerator: () => string) => () => string + >((_ctx: interfaces.Context, messageGenerator: () => string) => messageGenerator); + + container.bind<() => string>("message") + .toFunction(() => "Hello world") + .onActivation(onActivationHandlerSpy); + + container.get("message"); + container.get("message"); + + expect(onActivationHandlerSpy.callCount).to.eq(1); + }); + + it('should call onActivation once if the service is a constructor binding', () => { + + @injectable() + class Katana { + public hit() { + return "cut!"; + } + } + + const container = new Container(); + + const onActivationHandlerSpy = sinon.spy< + (ctx: interfaces.Context, injectableObj: unknown) => unknown + >((_ctx: interfaces.Context, injectableObj: unknown) => injectableObj); + + container.bind("Katana") + .toConstructor(Katana) + .onActivation(onActivationHandlerSpy); + + container.get("Katana"); + container.get("Katana"); + + expect(onActivationHandlerSpy.callCount).to.eq(1); + }); + + it('should call onActivation once if the service is a provider binding', () => { + + @injectable() + class Katana { + public hit() { + return "cut!"; + } + } + + const container = new Container(); + + const onActivationHandlerSpy = sinon.spy< + (ctx: interfaces.Context, injectableObj: unknown) => unknown + >((_ctx: interfaces.Context, injectableObj: unknown) => injectableObj); + + container.bind("Provider") + .toProvider((context: interfaces.Context) => + () => + Promise.resolve(new Katana())).onActivation(onActivationHandlerSpy); + + container.get("Provider"); + container.get("Provider"); + + expect(onActivationHandlerSpy.callCount).to.eq(1); + }); +});