@joist/di
Version:
Dependency Injection for Vanilla JS classes
310 lines • 12.1 kB
JavaScript
import { __esDecorate, __runInitializers } from "tslib";
import { assert } from "chai";
import { inject } from "./inject.js";
import { injectable } from "./injectable.js";
import { Injector } from "./injector.js";
import { StaticToken } from "./provider.js";
it("should create a new instance of a single provider", () => {
class A {
}
const app = new Injector();
assert(app.inject(A) instanceof A);
assert.equal(app.inject(A), app.inject(A));
});
it("should inject providers in the correct order", () => {
class A {
}
class B {
}
let MyService = (() => {
let _classDecorators = [injectable()];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var MyService = class {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
MyService = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
}
a = inject(A);
b = inject(B);
};
return MyService = _classThis;
})();
const app = new Injector();
const instance = app.inject(MyService);
assert(instance.a() instanceof A);
assert(instance.b() instanceof B);
});
it("should create a new instance of a provider that has a full dep tree", () => {
class A {
}
let B = (() => {
let _classDecorators = [injectable()];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var B = class {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
B = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
}
a = inject(A);
};
return B = _classThis;
})();
let C = (() => {
let _classDecorators = [injectable()];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var C = class {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
C = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
}
b = inject(B);
};
return C = _classThis;
})();
let D = (() => {
let _classDecorators = [injectable()];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var D = class {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
D = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
}
c = inject(C);
};
return D = _classThis;
})();
let E = (() => {
let _classDecorators = [injectable()];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var E = class {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
E = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
}
d = inject(D);
};
return E = _classThis;
})();
const app = new Injector();
const instance = app.inject(E);
assert(instance.d().c().b().a() instanceof A);
});
it("should override a provider if explicitly instructed", () => {
class A {
}
let B = (() => {
let _classDecorators = [injectable()];
let _classDescriptor;
let _classExtraInitializers = [];
let _classThis;
var B = class {
static { _classThis = this; }
static {
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
B = _classThis = _classDescriptor.value;
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
__runInitializers(_classThis, _classExtraInitializers);
}
a = inject(A);
};
return B = _classThis;
})();
class AltA extends A {
}
const app = new Injector({
providers: [[A, { use: AltA }]],
});
assert(app.inject(B).a() instanceof AltA);
});
it("should return an existing instance from a parent injector", () => {
class A {
}
const parent = new Injector();
const app = new Injector({
parent,
});
assert.equal(parent.inject(A), app.inject(A));
});
it("should use a factory if provided", () => {
class Service {
hello() {
return "world";
}
}
const injector = new Injector({
providers: [
[
Service,
{
factory() {
return {
hello() {
return "world";
},
};
},
},
],
],
});
assert.equal(injector.inject(Service).hello(), "world");
});
it("should throw an error if provider is missing both factory and use", () => {
class Service {
hello() {
return "world";
}
}
const injector = new Injector({
providers: [[Service, {}]],
});
assert.throws(() => injector.inject(Service), "Provider for Service found but is missing either 'use' or 'factory'");
});
it("should pass factories and instance of the injector", async () => {
class Service {
hello() {
return "world";
}
}
let factoryInjector = null;
const injector = new Injector({
providers: [
[
Service,
{
factory(i) {
factoryInjector = i;
},
},
],
],
});
injector.inject(Service);
assert.equal(factoryInjector, injector);
});
it("should create an instance from a StaticToken factory", () => {
const TOKEN = new StaticToken("test", () => "Hello World");
const injector = new Injector();
const res = injector.inject(TOKEN);
assert.equal(res, "Hello World");
});
it("should create an instance from an async StaticToken factory", async () => {
const TOKEN = new StaticToken("test", async () => "Hello World");
const injector = new Injector();
const res = await injector.inject(TOKEN);
assert.equal(res, "Hello World");
});
it("should allow static token to be overridden", () => {
const TOKEN = new StaticToken("test");
const injector = new Injector({
providers: [
[
TOKEN,
{
factory() {
return "Hello World";
},
},
],
],
});
const res = injector.inject(TOKEN);
assert.equal(res, "Hello World");
});
it("should allow you to get ALL available instances in a particular injector chain", () => {
const TOKEN = new StaticToken("TOKEN");
const injector = new Injector({
providers: [[TOKEN, { factory: () => "first" }]],
parent: new Injector({
providers: [[TOKEN, { factory: () => "second" }]],
parent: new Injector({
providers: [[TOKEN, { factory: () => "third" }]],
parent: new Injector({
providers: [[TOKEN, { factory: () => "fourth" }]],
}),
}),
}),
});
const res = injector.injectAll(TOKEN);
assert.deepEqual(res, ["first", "second", "third", "fourth"]);
});
it("should respect skipParent option when injecting", () => {
class Service {
value = "child";
}
const parent = new Injector({
providers: [
[
Service,
{
use: class extends Service {
value = "parent";
},
},
],
],
});
const child = new Injector({ parent });
assert.equal(child.inject(Service).value, "parent");
assert.equal(child.inject(Service, { ignoreParent: true }).value, "child");
});
it("should handle skipParent with static tokens", () => {
const TOKEN = new StaticToken("test", () => "child-value");
const parent = new Injector({
providers: [[TOKEN, { factory: () => "parent-value" }]],
});
const child = new Injector({ parent });
assert.equal(child.inject(TOKEN), "parent-value");
assert.equal(child.inject(TOKEN, { ignoreParent: true }), "child-value");
});
it("should handle StaticToken with null/undefined factory", () => {
const TOKEN = new StaticToken("test");
const injector = new Injector();
assert.throws(() => injector.inject(TOKEN), 'Provider not found for "test"');
});
it("should handle StaticToken factory throwing errors", () => {
const TOKEN = new StaticToken("test", () => {
throw new Error("Factory error");
});
const injector = new Injector();
assert.throws(() => injector.inject(TOKEN), "Factory error");
});
it("should create a non singleton instance", () => {
class A {
}
const app = new Injector();
assert(app.inject(A) instanceof A);
assert.notEqual(app.inject(A, { singleton: false }), app.inject(A, { singleton: false }));
});
//# sourceMappingURL=injector.test.js.map