packages/web-components/fast-element/src/di/di.get.spec.ts
import { expect } from "chai";
import {
all,
DI,
Container,
inject,
lazy,
optional,
Registration,
singleton,
} from "./di.js";
describe("DI.get", function () {
let container: Container;
// eslint-disable-next-line mocha/no-hooks
beforeEach(function () {
container = DI.createContainer();
});
describe("@lazy", function () {
class Bar {}
class Foo {
public constructor(@lazy(Bar) public readonly provider: () => Bar) {}
}
it("@singleton", function () {
const bar0 = container.get(Foo).provider();
const bar1 = container.get(Foo).provider();
expect(bar0).to.equal(bar1);
});
it("@transient", function () {
container.register(Registration.transient(Bar, Bar));
const bar0 = container.get(Foo).provider();
const bar1 = container.get(Foo).provider();
expect(bar0).to.not.equal(bar1);
});
});
describe("@scoped", function () {
describe("true", function () {
@singleton({ scoped: true })
class ScopedFoo {}
describe("Foo", function () {
const constructor = ScopedFoo;
it("children", function () {
const root = DI.createContainer();
const child1 = root.createChild();
const child2 = root.createChild();
const a = child1.get(constructor);
const b = child2.get(constructor);
const c = child1.get(constructor);
expect(a).to.equal(c, "a and c are the same");
expect(a).to.not.equal(b, "a and b are not the same");
expect(root.has(constructor, false)).to.equal(
false,
"root has class"
);
expect(child1.has(constructor, false)).to.equal(
true,
"child1 has class"
);
expect(child2.has(constructor, false)).to.equal(
true,
"child2 has class"
);
});
it("root", function () {
const root = DI.createContainer();
const child1 = root.createChild();
const child2 = root.createChild();
const a = root.get(constructor);
const b = child2.get(constructor);
const c = child1.get(constructor);
expect(a).to.equal(c, "a and c are the same");
expect(a).to.equal(b, "a and b are the same");
expect(root.has(constructor, false)).to.equal(true, "root has class");
expect(child1.has(constructor, false)).to.equal(
false,
"child1 does not have class"
);
expect(child2.has(constructor, false)).to.equal(
false,
"child2 does not have class"
);
});
});
});
describe("false", function () {
@singleton({ scoped: false })
class ScopedFoo {}
describe("Foo", function () {
const constructor = ScopedFoo;
it("children", function () {
const root = DI.createContainer();
const child1 = root.createChild();
const child2 = root.createChild();
const a = child1.get(constructor);
const b = child2.get(constructor);
const c = child1.get(constructor);
expect(a).to.equal(c, "a and c are the same");
expect(a).to.equal(b, "a and b are the same");
expect(root.has(constructor, false)).to.equal(true, "root has class");
expect(child1.has(constructor, false)).to.equal(
false,
"child1 has class"
);
expect(child2.has(constructor, false)).to.equal(
false,
"child2 has class"
);
});
});
describe("default", function () {
@singleton
class DefaultFoo {}
const constructor = DefaultFoo;
it("children", function () {
const root = DI.createContainer();
const child1 = root.createChild();
const child2 = root.createChild();
const a = child1.get(constructor);
const b = child2.get(constructor);
const c = child1.get(constructor);
expect(a).to.equal(c, "a and c are the same");
expect(a).to.equal(b, "a and b are the same");
expect(root.has(constructor, false)).to.equal(true, "root has class");
expect(child1.has(constructor, false)).to.equal(
false,
"child1 has class"
);
expect(child2.has(constructor, false)).to.equal(
false,
"child2 has class"
);
});
});
});
});
describe("@optional", function () {
it("with default", function () {
class Foo {
public constructor(
@optional("key") public readonly test: string = "hello"
) {}
}
expect(container.get(Foo).test).to.equal("hello");
});
it("no default, but param allows undefined", function () {
class Foo {
public constructor(@optional("key") public readonly test?: string) {}
}
expect(container.get(Foo).test).to.equal(undefined);
});
it("no default, param does not allow undefind", function () {
class Foo {
public constructor(@optional("key") public readonly test: string) {}
}
expect(container.get(Foo).test).to.equal(undefined);
});
it("interface with default", function () {
const Strings = DI.createContext<string[]>(x => x.instance([]));
class Foo {
public constructor(@optional(Strings) public readonly test: string[]) {}
}
expect(container.get(Foo).test).to.equal(undefined);
});
it("interface with default and default in constructor", function () {
const MyStr = DI.createContext<string>(x => x.instance("hello"));
class Foo {
public constructor(
@optional(MyStr) public readonly test: string = "test"
) {}
}
expect(container.get(Foo).test).to.equal("test");
});
it("interface with default registered and default in constructor", function () {
const MyStr = DI.createContext<string>(x => x.instance("hello"));
container.register(MyStr);
class Foo {
public constructor(
@optional(MyStr) public readonly test: string = "test"
) {}
}
expect(container.get(Foo).test).to.equal("hello");
});
});
describe("intrinsic", function () {
describe("bad", function () {
it("Array", function () {
@singleton
class Foo {
public constructor(@inject(Array) private readonly test: string[]) {}
}
expect(() => container.get(Foo)).throws();
});
it("ArrayBuffer", function () {
@singleton
class Foo {
public constructor(
@inject(ArrayBuffer) private readonly test: ArrayBuffer
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Boolean", function () {
@singleton
class Foo {
// eslint-disable-next-line @typescript-eslint/ban-types
public constructor(@inject(Boolean) private readonly test: Boolean) {}
}
expect(() => container.get(Foo)).throws();
});
it("DataView", function () {
@singleton
class Foo {
public constructor(
@inject(DataView) private readonly test: DataView
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Date", function () {
@singleton
class Foo {
public constructor(@inject(Date) private readonly test: Date) {}
}
expect(() => container.get(Foo)).throws();
});
it("Error", function () {
@singleton
class Foo {
public constructor(@inject(Error) private readonly test: Error) {}
}
expect(() => container.get(Foo)).throws();
});
it("EvalError", function () {
@singleton
class Foo {
public constructor(
@inject(EvalError) private readonly test: EvalError
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Float32Array", function () {
@singleton
class Foo {
public constructor(
@inject(Float32Array) private readonly test: Float32Array
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Float64Array", function () {
@singleton
class Foo {
public constructor(
@inject(Float64Array) private readonly test: Float64Array
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Function", function () {
@singleton
class Foo {
// eslint-disable-next-line @typescript-eslint/ban-types
public constructor(
@inject(Function) private readonly test: Function
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Int8Array", function () {
@singleton
class Foo {
public constructor(
@inject(Int8Array) private readonly test: Int8Array
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Int16Array", function () {
@singleton
class Foo {
public constructor(
@inject(Int16Array) private readonly test: Int16Array
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Int32Array", function () {
@singleton
class Foo {
public constructor(
@inject(Int32Array) private readonly test: Int16Array
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Map", function () {
@singleton
class Foo {
public constructor(
@inject(Map) private readonly test: Map<unknown, unknown>
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Number", function () {
@singleton
class Foo {
// eslint-disable-next-line @typescript-eslint/ban-types
public constructor(@inject(Number) private readonly test: Number) {}
}
expect(() => container.get(Foo)).throws();
});
it("Object", function () {
@singleton
class Foo {
// eslint-disable-next-line @typescript-eslint/ban-types
public constructor(@inject(Object) private readonly test: Object) {}
}
expect(() => container.get(Foo)).throws();
});
it("Promise", function () {
@singleton
class Foo {
public constructor(
@inject(Promise) private readonly test: Promise<unknown>
) {}
}
expect(() => container.get(Foo)).throws();
});
it("RangeError", function () {
@singleton
class Foo {
public constructor(
@inject(RangeError) private readonly test: RangeError
) {}
}
expect(() => container.get(Foo)).throws();
});
it("ReferenceError", function () {
@singleton
class Foo {
public constructor(
@inject(ReferenceError) private readonly test: ReferenceError
) {}
}
expect(() => container.get(Foo)).throws();
});
it("RegExp", function () {
@singleton
class Foo {
public constructor(@inject(RegExp) private readonly test: RegExp) {}
}
expect(() => container.get(Foo)).throws();
});
it("Set", function () {
@singleton
class Foo {
public constructor(
@inject(Set) private readonly test: Set<unknown>
) {}
}
expect(() => container.get(Foo)).throws();
});
// if (typeof SharedArrayBuffer !== 'undefined') {
// it('SharedArrayBuffer', function () {
// @singleton
// class Foo {
// public constructor(private readonly test: SharedArrayBuffer) {
// }
// }
// assert.throws(() => container.get(Foo));
// });
// }
it("String", function () {
@singleton
class Foo {
// eslint-disable-next-line @typescript-eslint/ban-types
public constructor(@inject(String) private readonly test: String) {}
}
expect(() => container.get(Foo)).throws();
});
it("SyntaxError", function () {
@singleton
class Foo {
public constructor(
@inject(SyntaxError) private readonly test: SyntaxError
) {}
}
expect(() => container.get(Foo)).throws();
});
it("TypeError", function () {
@singleton
class Foo {
public constructor(
@inject(TypeError) private readonly test: TypeError
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Uint8Array", function () {
@singleton
class Foo {
public constructor(
@inject(Uint8Array) private readonly test: Uint8Array
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Uint8ClampedArray", function () {
@singleton
class Foo {
public constructor(
@inject(Uint8ClampedArray)
private readonly test: Uint8ClampedArray
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Uint16Array", function () {
@singleton
class Foo {
public constructor(
@inject(Uint16Array) private readonly test: Uint16Array
) {}
}
expect(() => container.get(Foo)).throws();
});
it("Uint32Array", function () {
@singleton
class Foo {
public constructor(
@inject(Uint32Array) private readonly test: Uint32Array
) {}
}
expect(() => container.get(Foo)).throws();
});
it("UriError", function () {
@singleton
class Foo {
public constructor(
@inject(URIError) private readonly test: URIError
) {}
}
expect(() => container.get(Foo)).throws();
});
it("WeakMap", function () {
@singleton
class Foo {
public constructor(
@inject(WeakMap) private readonly test: WeakMap<any, unknown>
) {}
}
expect(() => container.get(Foo)).throws();
});
it("WeakSet", function () {
@singleton
class Foo {
public constructor(
@inject(WeakSet) private readonly test: WeakSet<any>
) {}
}
expect(() => container.get(Foo)).throws();
});
});
describe("good", function () {
it("@all()", function () {
class Foo {
public constructor(@all("test") public readonly test: string[]) {}
}
expect(container.get(Foo).test).to.eql([]);
});
it("@optional()", function () {
class Foo {
public constructor(
@optional("test") public readonly test: string | null = null
) {}
}
expect(container.get(Foo).test).to.equal(null);
});
it("undef instance, with constructor default", function () {
container.register(Registration.instance("test", undefined));
class Foo {
public constructor(
@inject("test") public readonly test: string[] = []
) {}
}
expect(container.get(Foo).test).to.eql([]);
});
it("can inject if registered", function () {
container.register(Registration.instance(String, "test"));
@singleton
class Foo {
public constructor(@inject(String) public readonly test: string) {}
}
expect(container.get(Foo).test).to.equal("test");
});
});
});
});
describe("DI.getAsync", () => {
it("calls the registration locator for unknown keys", async () => {
const key = "key";
const instance = {};
const asyncRegistrationLocator = async key => {
return Registration.instance(key, instance);
};
const container = DI.createContainer({
asyncRegistrationLocator
});
const found = await container.getAsync(key);
expect(found).equals(instance);
const foundAgain = container.get(key);
expect(foundAgain).equals(instance);
});
it("calls the registration locator for unknown dependencies", async () => {
const key1 = "key";
const instance1 = {};
const key2 = "key2";
const instance2 = {};
const key3 = "key3";
const instance3 = {};
const asyncRegistrationLocator = async key => {
switch(key) {
case key1:
return Registration.instance(key1, instance1);
case key2:
return Registration.instance(key2, instance2);
case key3:
return Registration.instance(key3, instance3);
}
throw new Error();
};
const container = DI.createContainer({
asyncRegistrationLocator
});
class Test {
constructor(
@inject(key1) public one,
@inject(key2) public two,
@inject(key3) public three
){}
}
container.register(
Registration.singleton(Test, Test)
);
const found = await container.getAsync(Test);
expect(found.one).equals(instance1);
expect(found.two).equals(instance2);
expect(found.three).equals(instance3);
const foundAgain = container.get(Test);
expect(foundAgain).equals(found);
});
it("calls the registration locator for a hierarchy of unknowns", async () => {
const key1 = "key";
const instance1 = {};
const key2 = "key2";
const instance2 = {};
const key3 = "key3";
const instance3 = {};
class Test {
constructor(
@inject(key1) public one,
@inject(key2) public two,
@inject(key3) public three
){}
}
class Test2 {
constructor(
@inject(Test) public test: Test
) {}
}
const asyncRegistrationLocator = async key => {
switch(key) {
case key1:
return Registration.instance(key1, instance1);
case key2:
return Registration.instance(key2, instance2);
case key3:
return Registration.instance(key3, instance3);
case Test:
return Registration.singleton(key, Test);
case Test2:
return Registration.transient(key, Test2);
}
throw new Error();
};
const container = DI.createContainer({
asyncRegistrationLocator
});
const found = await container.getAsync(Test2);
expect(found.test.one).equals(instance1);
expect(found.test.two).equals(instance2);
expect(found.test.three).equals(instance3);
const foundTest = container.get(Test);
expect(foundTest).equals(found.test);
const foundTransient = container.get(Test2);
expect(foundTransient).not.equals(found);
expect(foundTransient).instanceOf(Test2);
expect(foundTransient.test.one).equals(instance1);
expect(foundTransient.test.two).equals(instance2);
expect(foundTransient.test.three).equals(instance3);
expect(foundTransient.test).equals(foundTest);
});
});