Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/inversify/InversifyJS int…
Browse files Browse the repository at this point in the history
…o fix/issue-928-constructor-injection-broken
  • Loading branch information
notaphplover committed Apr 16, 2021
2 parents 3b8a99c + 784fec7 commit e02a2fa
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 14 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Upgrade information for v4.x to v5.x

### Fixed
- Fix `Target.isTagged()` to exclude `optional` from tag injections #1190.
- Update `toConstructor`, `toFactory`, `toFunction`, `toAutoFactory`, `toProvider` and `toConstantValue` to have singleton scope #1297.
- Fix injection on optional properties when targeting ES6 #928

## [5.0.1] - 2018-10-17
Expand Down
13 changes: 13 additions & 0 deletions src/constants/metadata_keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,16 @@ export const DESIGN_PARAM_TYPES = "design:paramtypes";

// used to identify postConstruct functions
export const POST_CONSTRUCT = "post_construct";

function getNonCustomTagKeys(): string[] {
return [
INJECT_TAG,
MULTI_INJECT_TAG,
NAME_TAG,
UNMANAGED_TAG,
NAMED_TAG,
OPTIONAL_TAG,
];
}

export const NON_CUSTOM_TAG_KEYS: string[] = getNonCustomTagKeys();
21 changes: 8 additions & 13 deletions src/planning/target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,9 @@ class Target implements interfaces.Target {
}

public isTagged(): boolean {
return this.metadata.some((m) =>
(m.key !== METADATA_KEY.INJECT_TAG) &&
(m.key !== METADATA_KEY.MULTI_INJECT_TAG) &&
(m.key !== METADATA_KEY.NAME_TAG) &&
(m.key !== METADATA_KEY.UNMANAGED_TAG) &&
(m.key !== METADATA_KEY.NAMED_TAG));
return this.metadata.some(
(metadata) => METADATA_KEY.NON_CUSTOM_TAG_KEYS.every((key) => metadata.key !== key),
);
}

public isOptional(): boolean {
Expand All @@ -85,14 +82,12 @@ class Target implements interfaces.Target {

public getCustomTags(): interfaces.Metadata[] | null {
if (this.isTagged()) {
return this.metadata.filter((m) =>
(m.key !== METADATA_KEY.INJECT_TAG) &&
(m.key !== METADATA_KEY.MULTI_INJECT_TAG) &&
(m.key !== METADATA_KEY.NAME_TAG) &&
(m.key !== METADATA_KEY.UNMANAGED_TAG) &&
(m.key !== METADATA_KEY.NAMED_TAG));
return this.metadata.filter(
(metadata) => METADATA_KEY.NON_CUSTOM_TAG_KEYS.every((key) => metadata.key !== key),
);
} else {
return null;
}
return null;
}

public matchesNamedTag(name: string): boolean {
Expand Down
8 changes: 7 additions & 1 deletion src/syntax/binding_to_syntax.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -31,6 +31,7 @@ class BindingToSyntax<T> implements interfaces.BindingToSyntax<T> {
this._binding.cache = value;
this._binding.dynamicValue = null;
this._binding.implementationType = null;
this._binding.scope = BindingScopeEnum.Singleton;
return new BindingWhenOnSyntax<T>(this._binding);
}

Expand All @@ -45,12 +46,14 @@ class BindingToSyntax<T> implements interfaces.BindingToSyntax<T> {
public toConstructor<T2>(constructor: interfaces.Newable<T2>): interfaces.BindingWhenOnSyntax<T> {
this._binding.type = BindingTypeEnum.Constructor;
this._binding.implementationType = constructor as any;
this._binding.scope = BindingScopeEnum.Singleton;
return new BindingWhenOnSyntax<T>(this._binding);
}

public toFactory<T2>(factory: interfaces.FactoryCreator<T2>): interfaces.BindingWhenOnSyntax<T> {
this._binding.type = BindingTypeEnum.Factory;
this._binding.factory = factory;
this._binding.scope = BindingScopeEnum.Singleton;
return new BindingWhenOnSyntax<T>(this._binding);
}

Expand All @@ -59,6 +62,7 @@ class BindingToSyntax<T> implements interfaces.BindingToSyntax<T> {
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;
}

Expand All @@ -68,12 +72,14 @@ class BindingToSyntax<T> implements interfaces.BindingToSyntax<T> {
const autofactory = () => context.container.get<T2>(serviceIdentifier);
return autofactory;
};
this._binding.scope = BindingScopeEnum.Singleton;
return new BindingWhenOnSyntax<T>(this._binding);
}

public toProvider<T2>(provider: interfaces.ProviderCreator<T2>): interfaces.BindingWhenOnSyntax<T> {
this._binding.type = BindingTypeEnum.Provider;
this._binding.provider = provider;
this._binding.scope = BindingScopeEnum.Singleton;
return new BindingWhenOnSyntax<T>(this._binding);
}

Expand Down
61 changes: 61 additions & 0 deletions test/bugs/issue_1190.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { expect } from "chai";
import { injectable, inject, optional, Container, named } from "../../src/inversify";

describe("Issue 1190", () => {

it('should inject a katana as default weapon to ninja', () => {
const TYPES = {
Weapon: "Weapon"
};

const TAG = {
throwable: "throwable"
};

interface Weapon {
name: string;
}

@injectable()
class Katana implements Weapon {
public name: string;
public constructor() {
this.name = "Katana";
}
}

@injectable()
class Shuriken implements Weapon {
public name: string;
public constructor() {
this.name = "Shuriken";
}
}

@injectable()
class Ninja {
public name: string;
public katana: Katana;
public shuriken: Shuriken;
public constructor(
@inject(TYPES.Weapon) @optional() katana: Weapon,
@inject(TYPES.Weapon) @named(TAG.throwable) shuriken: Weapon
) {
this.name = "Ninja";
this.katana = katana;
this.shuriken = shuriken;
}
}

const container = new Container();

container.bind<Weapon>(TYPES.Weapon).to(Katana).whenTargetIsDefault();
container.bind<Weapon>(TYPES.Weapon).to(Shuriken).whenTargetNamed(TAG.throwable);

container.bind<Ninja>("Ninja").to(Ninja);

const ninja = container.get<Ninja>("Ninja");

expect(ninja.katana).to.deep.eq(new Katana());
});
});
144 changes: 144 additions & 0 deletions test/bugs/issue_1297.test.ts
Original file line number Diff line number Diff line change
@@ -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<Katana>) => interfaces.Factory<Katana>
>((_ctx: interfaces.Context, instance: interfaces.Factory<Katana>) => instance);

container.bind<Katana>("Katana").to(Katana);

container.bind<interfaces.Factory<Katana>>("Factory<Katana>").toFactory<Katana>((context) =>
() =>
context.container.get<Katana>("Katana")).onActivation(onActivationHandlerSpy);

container.get("Factory<Katana>");
container.get("Factory<Katana>");

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<Katana>) => interfaces.Factory<Katana>
>((_ctx: interfaces.Context, instance: interfaces.Factory<Katana>) => instance);

container.bind<Katana>("Katana").to(Katana);

container.bind<interfaces.Factory<Katana>>("Factory<Katana>")
.toAutoFactory<Katana>("Katana").onActivation(onActivationHandlerSpy);

container.get("Factory<Katana>");
container.get("Factory<Katana>");

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>(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<Katana>")
.toProvider<Katana>((context: interfaces.Context) =>
() =>
Promise.resolve(new Katana())).onActivation(onActivationHandlerSpy);

container.get("Provider<Katana>");
container.get("Provider<Katana>");

expect(onActivationHandlerSpy.callCount).to.eq(1);
});
});
23 changes: 23 additions & 0 deletions test/planning/target.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,29 @@ describe("Target", () => {
target3.metadata.push(new Metadata("power", 5), new Metadata("speed", 5));
expect(target3.isTagged()).to.be.eql(true);

const target4 = new Target(TargetTypeEnum.Variable, "", "Katana");
target4.metadata.push(new Metadata(METADATA_KEY.INJECT_TAG, "Katana"))
expect(target4.isTagged()).to.be.eql(false);

const target5 = new Target(TargetTypeEnum.Variable, "", "Katana");
target5.metadata.push(new Metadata(METADATA_KEY.MULTI_INJECT_TAG, "Katana"))
expect(target5.isTagged()).to.be.eql(false);

const target6 = new Target(TargetTypeEnum.Variable, "katanaName", "Katana");
target6.metadata.push(new Metadata(METADATA_KEY.NAME_TAG, "katanaName"))
expect(target6.isTagged()).to.be.eql(false);

const target7 = new Target(TargetTypeEnum.Variable, "", "Katana");
target7.metadata.push(new Metadata(METADATA_KEY.UNMANAGED_TAG, true))
expect(target7.isTagged()).to.be.eql(false);

const target8 = new Target(TargetTypeEnum.Variable, "katanaName", "Katana");
target8.metadata.push(new Metadata(METADATA_KEY.NAMED_TAG, "katanaName"))
expect(target8.isTagged()).to.be.eql(false);

const target9 = new Target(TargetTypeEnum.Variable, "", "Katana");
target9.metadata.push(new Metadata(METADATA_KEY.OPTIONAL_TAG, true))
expect(target9.isTagged()).to.be.eql(false);
});

it("Should be able to match tagged metadata", () => {
Expand Down

0 comments on commit e02a2fa

Please sign in to comment.