Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: complete edge cases #2

Merged
merged 3 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/containers/Container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export class Container implements IContainer {
}

// if this token itself is a constructor, use itself as class provider
if (isConstructor(token)) {
if (isConstructor(token) && context.rootToken === token) {
return this.resolveFromRegistrations(
token,
[
Expand Down Expand Up @@ -260,7 +260,9 @@ export class Container implements IContainer {
const cachedInstance = this.instanceMap.get(registration);
const shouldUseCache =
cachedInstance &&
(context.useCache ||
context.useCache &&
(getProviderType(registration.provider) !==
ProviderTypeEnum.ClassProvider ||
isGlobalSingleton(registration) ||
(isScopedSingleton(registration) && context.container === this));

Expand Down
4 changes: 0 additions & 4 deletions src/decorators/injectable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ export function injectable(
Reflect.getMetadata('design:paramtypes', target) || [];

constructorParams.forEach((paramType: unknown, index: number) => {
if (typeof paramType !== 'function') {
return;
}

defineConstructorDependencyDescriptor(target, {
token: paramType as Constructor<unknown>,
index,
Expand Down
38 changes: 18 additions & 20 deletions src/decorators/lazy.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { defineOptionInDescriptor } from '@/utils';

export function lazy() {
return function (
target: any,
propertyKey?: string | symbol,
parameterIndex?: number,
) {
// constructor params decorator
if (parameterIndex !== undefined) {
defineOptionInDescriptor(target, parameterIndex, {
lazy: true,
});
return;
}
export function lazy(
target: any,
propertyKey?: string | symbol,
parameterIndex?: number,
) {
// constructor params decorator
if (parameterIndex !== undefined) {
defineOptionInDescriptor(target, parameterIndex, {
lazy: true,
});
return;
}

// property decorator
if (propertyKey !== undefined) {
defineOptionInDescriptor(target.constructor, propertyKey, {
lazy: true,
});
}
};
// property decorator
if (propertyKey !== undefined) {
defineOptionInDescriptor(target.constructor, propertyKey, {
lazy: true,
});
}
}
38 changes: 18 additions & 20 deletions src/decorators/multiple.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { defineOptionInDescriptor } from '@/utils';

export function multiple() {
return function (
target: any,
propertyKey?: string | symbol,
parameterIndex?: number,
) {
// constructor params decorator
if (parameterIndex !== undefined) {
defineOptionInDescriptor(target, parameterIndex, {
multiple: true,
});
return;
}
export function multiple(
target: any,
propertyKey?: string | symbol,
parameterIndex?: number,
) {
// constructor params decorator
if (parameterIndex !== undefined) {
defineOptionInDescriptor(target, parameterIndex, {
multiple: true,
});
return;
}

// property decorator
if (propertyKey !== undefined) {
defineOptionInDescriptor(target.constructor, propertyKey, {
multiple: true,
});
}
};
// property decorator
if (propertyKey !== undefined) {
defineOptionInDescriptor(target.constructor, propertyKey, {
multiple: true,
});
}
}
38 changes: 18 additions & 20 deletions src/decorators/optional.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { defineOptionInDescriptor } from '@/utils';

export function optional() {
return function (
target: any,
propertyKey?: string | symbol,
parameterIndex?: number,
) {
// constructor params decorator
if (parameterIndex !== undefined) {
defineOptionInDescriptor(target, parameterIndex, {
optional: true,
});
return;
}
export function optional(
target: any,
propertyKey?: string | symbol,
parameterIndex?: number,
) {
// constructor params decorator
if (parameterIndex !== undefined) {
defineOptionInDescriptor(target, parameterIndex, {
optional: true,
});
return;
}

// property decorator
if (propertyKey !== undefined) {
defineOptionInDescriptor(target.constructor, propertyKey, {
optional: true,
});
}
};
// property decorator
if (propertyKey !== undefined) {
defineOptionInDescriptor(target.constructor, propertyKey, {
optional: true,
});
}
}
5 changes: 0 additions & 5 deletions src/modules/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,5 @@ export function createAsyncModuleLoader<T>(
get(_, key: string | symbol, receiver) {
return loadModule().then(module => Reflect.get(module, key, receiver));
},

set(_, key: string | symbol, value: any, receiver) {
loadModule().then(module => Reflect.set(module, key, value, receiver));
return true;
},
});
}
1 change: 1 addition & 0 deletions src/utils/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export function isDisposable(

export function isGlobalSingleton(registration: ProviderRegistration<unknown>) {
return (
registration.options?.singleton === undefined ||
registration.options?.singleton === true ||
registration.options?.singleton === SingletonScope.Global
);
Expand Down
50 changes: 50 additions & 0 deletions tests/child-container.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { NoProviderFoundError } from '@/common';
import { SingletonScope, inject, injectable, rootContainer } from '@/index';
import { beforeEach, describe, expect, it } from 'vitest';

describe('[Child Container] test hierarchical containers', () => {
beforeEach(() => rootContainer.dispose(true));

it('should resolve dependencies from parent by default', () => {
class A {}
class B {
constructor(@inject('A') public a: A) {}
}

rootContainer.register('A').toClass(A);
const childContainer = rootContainer.fork('CHILDREN');

const b = childContainer.resolve(B);
expect(b).toBeInstanceOf(B);
expect(b.a).toBeInstanceOf(A);
});

it('should throw NoProviderFoundError when resolveParent is disabled', () => {
class A {}
class B {
constructor(@inject('A') public a: A) {}
}

rootContainer.register('A').toClass(A);
const childContainer = rootContainer.fork('CHILDREN');

expect(() =>
childContainer.resolve(B, { resolveParent: false }),
).toThrowError(NoProviderFoundError);
});

it('should not use scoped singleton instance from parent container even resolveParent is enabled', () => {
@injectable({ token: 'A', singleton: SingletonScope.Scoped }) class A {}

class B {
constructor(@inject('A') public a: A) {}
}

const childContainer = rootContainer.fork('CHILDREN');

const a = rootContainer.resolve('A');
const b = childContainer.resolve(B);

expect(a === b.a).toBeFalsy();
})
});
16 changes: 8 additions & 8 deletions tests/decorators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ describe('[Decorator] test util decorators', () => {

class B {
constructor(
@inject('C') @optional() public c: unknown,
@optional() @inject('D') public d: unknown,
@inject('C') @optional public c: unknown,
@optional @inject('D') public d: unknown,
) {}

@inject('C') @optional() public c2: unknown;
@optional() @inject('D') public d2: unknown;
@inject('C') @optional public c2: unknown;
@optional @inject('D') public d2: unknown;
}

const b = rootContainer.resolve(B);
Expand All @@ -65,12 +65,12 @@ describe('[Decorator] test util decorators', () => {

class C {
constructor(
@inject('Foo') @multiple() public foo1: unknown[],
@multiple() @inject('Foo') public foo2: unknown[],
@inject('Foo') @multiple public foo1: unknown[],
@multiple @inject('Foo') public foo2: unknown[],
) {}

@multiple() @inject('Foo') public foo3!: unknown[];
@inject('Foo') @multiple() public foo4!: unknown[];
@multiple @inject('Foo') public foo3!: unknown[];
@inject('Foo') @multiple public foo4!: unknown[];
}

const c = rootContainer.resolve(C);
Expand Down
6 changes: 3 additions & 3 deletions tests/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe('[Errors] test scenes that should throw error', () => {

@injectable({ token: 'B' })
class B {
constructor(@lazy() @inject('A') public a: A) {}
constructor(@lazy @inject('A') public a: A) {}
}

const b = rootContainer.resolve(B);
Expand All @@ -90,12 +90,12 @@ describe('[Errors] test scenes that should throw error', () => {
echo() {
console.log('A');
}
@inject('B') @lazy() public b2: unknown;
@inject('B') @lazy public b2: unknown;
}

@injectable({ token: 'B' })
class B {
constructor(@inject('A') @lazy() public a: A) {
constructor(@inject('A') @lazy public a: A) {
a.echo();
}
}
Expand Down
29 changes: 26 additions & 3 deletions tests/metadata.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { beforeEach, describe, expect, it } from 'vitest';

import 'reflect-metadata';
import { inject, injectable } from '@/decorators';
import { inject, injectable, optional } from '@/decorators';
import { rootContainer } from '@/containers';
import { InjectionTokenInvalidError } from '@/common';

describe('[Metadata] test scenes that requires reflect-metadata polyfill', () => {
beforeEach(() => {
Expand All @@ -17,7 +18,7 @@ describe('[Metadata] test scenes that requires reflect-metadata polyfill', () =>

@injectable()
class C {
constructor(public a: A, public b: B) {}
constructor(public a: A, public b: B, @optional public c?: Number) {}
}

const c = rootContainer.resolve(C);
Expand All @@ -28,7 +29,9 @@ describe('[Metadata] test scenes that requires reflect-metadata polyfill', () =>

it('@inject(): should automatically infer dependency constructor when used in consturctor parameters', () => {
@injectable()
class A {}
class A {
constructor() {}
}

@injectable()
class B {}
Expand Down Expand Up @@ -59,4 +62,24 @@ describe('[Metadata] test scenes that requires reflect-metadata polyfill', () =>
expect(c.a).toBeInstanceOf(A);
expect(c.b).toBeInstanceOf(B);
});

it('@inject(): should throw error if inferred dependency is invalid', () => {
@injectable()
class A {}

@injectable()
class B {}

expect(() => {
class C {
constructor(@inject() public a: undefined) {}
}
}).toThrowError(InjectionTokenInvalidError);

expect(() => {
class D {
@inject() public a: undefined;
}
}).toThrowError(InjectionTokenInvalidError);
});
});