Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .ci/runChecks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
set -e

npm ci
npm run build
npm run lint
npx license-check
npx better-npm-audit audit
228 changes: 228 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
module.exports = {
env: {
es6: true,
node: true
},
ignorePatterns: ['.eslintrc.js', 'jest.config.js'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'prettier'
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: ['tsconfig.json', 'test/tsconfig.json'],
sourceType: 'module'
},
plugins: ['eslint-plugin-jsdoc', 'eslint-plugin-prefer-arrow', '@typescript-eslint'],
root: true,
rules: {
'@typescript-eslint/adjacent-overload-signatures': 'error',
'@typescript-eslint/array-type': [
'error',
{
default: 'generic'
}
],
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/consistent-type-assertions': 'error',
'@typescript-eslint/dot-notation': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/indent': 'off',
'@typescript-eslint/member-delimiter-style': [
'error',
{
multiline: {
delimiter: 'semi',
requireLast: true
},
singleline: {
delimiter: 'semi',
requireLast: false
}
}
],
'@typescript-eslint/naming-convention': [
'off',
{
selector: 'variable',
format: ['camelCase', 'UPPER_CASE', 'PascalCase'],
leadingUnderscore: 'allow',
trailingUnderscore: 'forbid'
}
],
'@typescript-eslint/no-empty-function': 'error',
'@typescript-eslint/no-empty-interface': 'error',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-misused-new': 'error',
'@typescript-eslint/no-namespace': 'error',
'@typescript-eslint/no-parameter-properties': 'off',
'@typescript-eslint/no-shadow': [
'error',
{
hoist: 'all'
}
],
'@typescript-eslint/no-unused-expressions': 'error',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/prefer-for-of': 'error',
'@typescript-eslint/prefer-function-type': 'error',
'@typescript-eslint/prefer-namespace-keyword': 'error',
'@typescript-eslint/quotes': ['error', 'single'],
'@typescript-eslint/semi': ['error', 'always'],
'@typescript-eslint/triple-slash-reference': [
'error',
{
path: 'always',
types: 'prefer-import',
lib: 'always'
}
],
'@typescript-eslint/type-annotation-spacing': 'off',
'@typescript-eslint/typedef': 'off',
'@typescript-eslint/unified-signatures': 'error',
'arrow-parens': ['off', 'always'],
'brace-style': ['off', 'off'],
'comma-dangle': 'off',
complexity: 'off',
'constructor-super': 'error',
'dot-notation': 'off',
'eol-last': 'off',
eqeqeq: ['error', 'smart'],
'guard-for-in': 'error',
'id-denylist': [
'error',
'any',
'Number',
'number',
'String',
'string',
'Boolean',
'boolean',
'Undefined',
'undefined'
],
'id-match': 'error',
indent: 'off',
'jsdoc/check-alignment': 'error',
'jsdoc/check-indentation': 'error',
// "jsdoc/newline-after-description": "error",
'linebreak-style': 'off',
'max-classes-per-file': 'off',
'max-len': 'off',
'new-parens': 'off',
'newline-per-chained-call': 'off',
'no-bitwise': 'error',
'no-caller': 'error',
'no-cond-assign': 'error',
'no-console': [
'error',
{
allow: [
'warn',
'dir',
'time',
'timeEnd',
'timeLog',
'trace',
'assert',
'clear',
'count',
'countReset',
'group',
'groupEnd',
'table',
'debug',
'info',
'dirxml',
'groupCollapsed',
'Console',
'profile',
'profileEnd',
'timeStamp',
'context',
'createTask'
]
}
],
'no-debugger': 'error',
'no-empty': 'error',
'no-empty-function': 'off',
'no-eval': 'error',
'no-extra-semi': 'off',
'no-fallthrough': 'off',
'no-invalid-this': 'off',
'no-irregular-whitespace': 'off',
'no-multiple-empty-lines': 'off',
'no-new-wrappers': 'error',
'no-shadow': 'off',
'no-throw-literal': 'error',
'no-trailing-spaces': 'off',
'no-undef-init': 'error',
'no-underscore-dangle': 'off',
'no-unsafe-finally': 'error',
'no-unused-expressions': 'off',
'no-unused-labels': 'error',
'no-use-before-define': 'off',
'no-var': 'error',
'object-shorthand': ['error', 'never'],
'one-var': ['error', 'never'],
'padded-blocks': [
'off',
{
blocks: 'never'
},
{
allowSingleLineBlocks: true
}
],
'prefer-arrow/prefer-arrow-functions': [
'error',
{
allowStandaloneDeclarations: true
}
],
'prefer-const': 'error',
'quote-props': 'off',
quotes: 'off',
radix: 'error',
'react/jsx-curly-spacing': 'off',
'react/jsx-equals-spacing': 'off',
'react/jsx-tag-spacing': [
'off',
{
afterOpening: 'allow',
closingSlash: 'allow'
}
],
'react/jsx-wrap-multilines': 'off',
semi: 'off',
'space-before-function-paren': 'off',
'space-in-parens': ['off', 'never'],
'spaced-comment': [
'error',
'always',
{
markers: ['/']
}
],
'use-isnan': 'error',
'valid-typeof': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'constructor-super': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/unbound-method': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}
]
}
};
27 changes: 27 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
groups:
update-github-actions-dependencies:
patterns:
- '*'
reviewers:
- 'jkoenig134'
labels:
- 'dependencies'

- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'weekly'
groups:
update-npm-dependencies:
patterns:
- '*'
reviewers:
- 'jkoenig134'
labels:
- 'dependencies'
33 changes: 33 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Publish

on:
push:
branches: [main]

jobs:
run-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: current
- run: bash .ci/runChecks.sh

publish-npm:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: current
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm run build
- run: npx enhanced-publish --if-possible --use-preid-as-tag
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
needs: ['run-checks']
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Test

on:
pull_request:
push:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

jobs:
run-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: current
- run: bash .ci/runChecks.sh

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: current
- run: npm ci
- run: npm run build
- run: npm run test
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ dist
!test/*.js
doc
node_modules
.vscode
reports
*.log
npm-debug.log*
27 changes: 27 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"semi": true,
"tabWidth": 4,
"singleQuote": true,
"printWidth": 120,
"trailingComma": "none",
"overrides": [
{
"files": "*.json",
"options": {
"tabWidth": 2
}
},
{
"files": "*.yml",
"options": {
"tabWidth": 2
}
},
{
"files": ".prettierrc",
"options": {
"tabWidth": 2
}
}
]
}
22 changes: 0 additions & 22 deletions .travis.yml

This file was deleted.

31 changes: 31 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[yaml]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"files.autoSave": "off",
"html.format.wrapAttributes": "preserve-aligned",
"xmlTools.splitAttributesOnFormat": true,
"xmlTools.enforcePrettySelfClosingTagOnFormat": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "always"
},
"files.eol": "\n",
"typescript.tsdk": "node_modules/typescript/lib"
}
385 changes: 195 additions & 190 deletions README.md

Large diffs are not rendered by default.

10,932 changes: 6,050 additions & 4,882 deletions package-lock.json

Large diffs are not rendered by default.

88 changes: 49 additions & 39 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,39 +1,7 @@
{
"name": "typescript-ioc",
"version": "3.2.3",
"name": "@nmshd/typescript-ioc",
"version": "3.2.4",
"description": "A Lightweight annotation-based dependency injection container for typescript.",
"author": "Thiago da Rosa de Bustamante <trbustamante@gmail.com>",
"scripts": {
"start": "tsc -w",
"build": "npm run clean && tsc",
"clean": "rimraf dist",
"prepare": "rimraf dist && tsc",
"lint": "tslint ./src/**/*.ts ./test/**/*.ts",
"lint:fix": "tslint --fix ./src/**/*.ts ./test/**/*.ts -t verbose",
"pretest": "npm run build && npm run lint",
"test": "jest --config ./test/jest.config.js --coverage",
"tsc": "tsc"
},
"dependencies": {
"lodash.get": "^4.4.2",
"lodash.set": "^4.3.2",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {
"@types/jest": "^26.0.0",
"@types/lodash.get": "^4.4.6",
"@types/lodash.set": "^4.3.6",
"@types/node": "^8.10.61",
"codecov": "^3.7.0",
"jest": "^26.1.0",
"rimraf": "^2.7.1",
"source-map-support": "^0.5.19",
"ts-jest": "^26.1.1",
"ts-node": "^8.10.2",
"tslint": "^6.1.2",
"tslint-config-prettier": "^1.18.0",
"typescript": "^3.9.5"
},
"keywords": [
"ioc",
"di",
@@ -45,14 +13,56 @@
"dependency inversion",
"inversion of control container"
],
"bugs": {
"url": "https://github.com/nmshd/typescript-ioc/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/thiagobustamante/typescript-ioc.git"
"url": "https://github.com/nmshd/typescript-ioc.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/thiagobustamante/typescript-ioc/issues"
},
"authors": [
"Thiago da Rosa de Bustamante <trbustamante@gmail.com>",
"js-soft GmbH"
],
"main": "./dist/typescript-ioc.js",
"typings": "./dist/typescript-ioc.d.ts"
"typings": "./dist/typescript-ioc.d.ts",
"scripts": {
"build": "npm run clean && tsc",
"clean": "rimraf dist",
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:tsc",
"lint:eslint": "eslint ./src/**/*.ts ./test/**/*.ts",
"lint:eslint:fix": "eslint --fix ./src/**/*.ts ./test/**/*.ts -t verbose",
"lint:prettier": "prettier --check .",
"lint:tsc": "tsc --noEmit",
"prepare": "rimraf dist && tsc",
"start": "tsc -w",
"pretest": "npm run build && npm run lint",
"test": "jest --config ./test/jest.config.js --coverage",
"tsc": "tsc"
},
"dependencies": {
"lodash": "^4.17.21",
"reflect-metadata": "^0.2.2"
},
"devDependencies": {
"@js-soft/license-check": "^1.0.9",
"@types/jest": "^29.5.13",
"@types/lodash": "^4.17.10",
"@types/node": "^22.7.5",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.20.0",
"codecov": "^3.8.3",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jsdoc": "^48.0.4",
"eslint-plugin-prefer-arrow": "^1.2.3",
"jest": "^29.7.0",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"source-map-support": "^0.5.21",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.6.3"
}
}
14 changes: 6 additions & 8 deletions src/container/container-binding-config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { InjectorHandler } from './injection-handler';
import { Scope, ObjectFactory, Config, BuildContext, ValueConfig } from '../model';
import { get, set } from 'lodash';
import { BuildContext, Config, ObjectFactory, Scope, ValueConfig } from '../model';
import { InstanceFactory, ValueFactory } from './container-types';
import get = require('lodash.get');
import set = require('lodash.set');
import { InjectorHandler } from './injection-handler';

export class IoCBindConfig implements Config {
public source: Function;
@@ -29,7 +28,7 @@ export class IoCBindConfig implements Config {
this.factory((context) => {
const params = this.getParameters(context);
const constructor = this.decoratedConstructor || target;
return (params ? new constructor(...params) : new constructor());
return params ? new constructor(...params) : new constructor();
});
} else {
this.factory((context) => {
@@ -98,7 +97,7 @@ export class IoCBindConfig implements Config {

private getParameters(context: BuildContext) {
if (this.paramTypes) {
return this.paramTypes.map(paramType => {
return this.paramTypes.map((paramType) => {
if (typeof paramType === 'string' || paramType instanceof String) {
return this.valueFactory(paramType as string);
}
@@ -109,7 +108,6 @@ export class IoCBindConfig implements Config {
}
}


export class IoCBindValueConfig implements ValueConfig {
public name: string;
public path: string;
@@ -165,4 +163,4 @@ export class PropertyPath {
}
return new PropertyPath(value.substring(0, index));
}
}
}
6 changes: 3 additions & 3 deletions src/container/container-namespaces.ts
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ export class ContainerNamespaces {
public removeNamespace(name: string) {
const namespace = this.namespaces.get(name);
if (namespace) {
if (this.currentNamespace && (namespace.name === this.currentNamespace.name)) {
if (this.currentNamespace && namespace.name === this.currentNamespace.name) {
this.currentNamespace = null;
}
namespace.clear();
@@ -60,7 +60,7 @@ export class ContainerNamespaces {
}

public selectedNamespace() {
return (this.currentNamespace ? this.currentNamespace.name : null);
return this.currentNamespace ? this.currentNamespace.name : null;
}
}

@@ -96,4 +96,4 @@ class NamespaceBindings {
this.bindings.clear();
this.values.clear();
}
}
}
6 changes: 2 additions & 4 deletions src/container/container.ts
Original file line number Diff line number Diff line change
@@ -13,8 +13,7 @@ export class IoCContainer {
let config: IoCBindConfig = IoCContainer.namespaces.get(baseSource);
if (!config) {
config = new IoCBindConfig(baseSource, IoCContainer.get, IoCContainer.getValue);
config
.to(source as FunctionConstructor);
config.to(source as FunctionConstructor);
IoCContainer.namespaces.set(baseSource, config);
} else if (!readOnly && config.namespace !== IoCContainer.namespaces.selectedNamespace()) {
config = config.clone();
@@ -30,8 +29,7 @@ export class IoCContainer {
if (!config) {
config = new IoCBindValueConfig(property.name);
IoCContainer.namespaces.setValue(property.name, config);
}
else if (!readOnly && config.namespace !== IoCContainer.namespaces.selectedNamespace()) {
} else if (!readOnly && config.namespace !== IoCContainer.namespaces.selectedNamespace()) {
config = config.clone();
IoCContainer.namespaces.setValue(property.name, config);
}
38 changes: 20 additions & 18 deletions src/container/injection-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { InstanceFactory, ValueFactory } from './container-types';
import { BuildContext } from '../model';
import { InstanceFactory, ValueFactory } from './container-types';

const BUILD_CONTEXT_KEY = '__BuildContext';
const IOC_WRAPPER_CLASS = 'ioc_wrapper';
@@ -11,18 +11,15 @@ export class InjectorHandler {
public static constructorNameRegEx = /function (\w*)/;
private static instantiationsBlocked = true;


public static instrumentConstructor(source: Function) {
let newConstructor: any;
// tslint:disable-next-line:class-name
newConstructor = class ioc_wrapper extends (source as FunctionConstructor) {
public static instrumentConstructor(source: Function): FunctionConstructor {
const newConstructor = class ioc_wrapper extends (source as FunctionConstructor) {
constructor(...args: Array<any>) {
super(...args);
InjectorHandler.assertInstantiable();
}
};
newConstructor['__parent'] = source;
return newConstructor;
return newConstructor as FunctionConstructor;
}

public static blockInstantiation(blocked: boolean) {
@@ -52,15 +49,13 @@ export class InjectorHandler {

public static checkType(source: Object) {
if (!source) {
throw new TypeError('Invalid type requested to IoC ' +
'container. Type is not defined.');
throw new TypeError('Invalid type requested to IoC ' + 'container. Type is not defined.');
}
}

public static checkName(source: string) {
if (!source) {
throw new TypeError('Invalid name requested to IoC ' +
'container. Name is not defined.');
throw new TypeError('Invalid name requested to IoC ' + 'container. Name is not defined.');
}
}

@@ -72,13 +67,18 @@ export class InjectorHandler {
delete target[BUILD_CONTEXT_KEY];
}

public static injectProperty(target: Function, key: string, propertyType: Function, instanceFactory: InstanceFactory) {
public static injectProperty(
target: Function,
key: string,
propertyType: Function,
instanceFactory: InstanceFactory
) {
const propKey = `__${key}`;
Object.defineProperty(target.prototype, key, {
enumerable: true,
get: function () {
const context: BuildContext = this[BUILD_CONTEXT_KEY] || target[BUILD_CONTEXT_KEY];
return this[propKey] ? this[propKey] : this[propKey] = instanceFactory(propertyType, context);
return this[propKey] ? this[propKey] : (this[propKey] = instanceFactory(propertyType, context));
},
set: function (newValue) {
this[propKey] = newValue;
@@ -91,7 +91,7 @@ export class InjectorHandler {
Object.defineProperty(target.prototype, key, {
enumerable: true,
get: function () {
return this[propKey] ? this[propKey] : this[propKey] = valueFactory(name);
return this[propKey] ? this[propKey] : (this[propKey] = valueFactory(name));
},
set: function (newValue) {
this[propKey] = newValue;
@@ -105,7 +105,7 @@ export class InjectorHandler {
} else {
try {
const constructorName = source.prototype.constructor.toString().match(this.constructorNameRegEx)[1];
return (constructorName && constructorName !== IOC_WRAPPER_CLASS);
return constructorName && constructorName !== IOC_WRAPPER_CLASS;
} catch {
// make linter happy
}
@@ -116,8 +116,10 @@ export class InjectorHandler {

private static assertInstantiable() {
if (InjectorHandler.instantiationsBlocked) {
throw new TypeError('Can not instantiate it. The instantiation is blocked for this class. ' +
'Ask Container for it, using Container.get');
throw new TypeError(
'Can not instantiate it. The instantiation is blocked for this class. ' +
'Ask Container for it, using Container.get'
);
}
}
}
}
23 changes: 14 additions & 9 deletions src/decorators.ts
Original file line number Diff line number Diff line change
@@ -57,8 +57,8 @@ export function Singleton(target: Function) {
* }
* ```
*
* You will only be able to create instances of PersonService through the Container.
*
* You will only be able to create instances of PersonService through the Container.
*
* ```
* let PersonService = new PersonService(); // will thrown a TypeError exception
* ```
@@ -183,7 +183,7 @@ export function Inject(...args: Array<any>) {
export function InjectValue(value: string) {
return (...args: Array<any>) => {
if (args.length === 2 || (args.length === 3 && typeof args[2] === 'undefined')) {
const params = [...args, value].filter(v => v ? true : false);
const params = [...args, value].filter((v) => (v ? true : false));
return InjectValuePropertyDecorator.apply(this, params);
} else if (args.length === 3 && typeof args[2] === 'number') {
return InjectValueParamDecorator.apply(this, [...args, value]);
@@ -193,8 +193,6 @@ export function InjectValue(value: string) {
};
}



/**
* Decorator processor for [[Inject]] decorator on properties
*/
@@ -211,7 +209,8 @@ function InjectPropertyDecorator(target: Function, key: string) {
* Decorator processor for [[Inject]] decorator on constructor parameters
*/
function InjectParamDecorator(target: Function, propertyKey: string | symbol, parameterIndex: number) {
if (!propertyKey) { // only intercept constructor parameters
if (!propertyKey) {
// only intercept constructor parameters
const config = IoCContainer.bind(target);
config.paramTypes = config.paramTypes || [];
const paramTypes: Array<any> = Reflect.getMetadata('design:paramtypes', target);
@@ -229,10 +228,16 @@ function InjectValuePropertyDecorator(target: Function, key: string, value: stri
/**
* Decorator processor for [[Inject]] decorator on constructor parameters
*/
function InjectValueParamDecorator(target: Function, propertyKey: string | symbol, _parameterIndex: number, value: string) {
if (!propertyKey) { // only intercept constructor parameters
function InjectValueParamDecorator(
target: Function,
propertyKey: string | symbol,
_parameterIndex: number,
value: string
) {
if (!propertyKey) {
// only intercept constructor parameters
const config = IoCContainer.bind(target);
config.paramTypes = config.paramTypes || [];
config.paramTypes.unshift(value);
}
}
}
12 changes: 8 additions & 4 deletions src/model.ts
Original file line number Diff line number Diff line change
@@ -147,8 +147,12 @@ export interface ConstantConfiguration {
* A set of configurations for namespaces
*/
export interface NamespaceConfiguration {
env?: { [index: string]: Array<ContainerConfiguration | ConstantConfiguration> };
namespace?: { [index: string]: Array<ContainerConfiguration | ConstantConfiguration> };
env?: {
[index: string]: Array<ContainerConfiguration | ConstantConfiguration>;
};
namespace?: {
[index: string]: Array<ContainerConfiguration | ConstantConfiguration>;
};
}

/**
@@ -161,14 +165,14 @@ export interface Snapshot {
*/
restore(): void;
/**
* Makes all snapshot changes effective. All snapshots are already selected when created. If you
* Makes all snapshot changes effective. All snapshots are already selected when created. If you
* select activate other namespaces and want to reactivate the snapshot, you can call select() method.
*/
select(): void;
}

/**
* A Namespace where the IoCContainer work. Types and values bound to a specific namespace are not
* A Namespace where the IoCContainer work. Types and values bound to a specific namespace are not
* available in other namespaces. A Namespace inherit the default namespace.
*/
export interface Namespace {
8 changes: 5 additions & 3 deletions src/scopes.ts
Original file line number Diff line number Diff line change
@@ -46,9 +46,11 @@ export class RequestScope extends Scope {

private ensureContext(context: BuildContext) {
if (!context) {
throw new TypeError('IoC Container can not handle this request. When using @InRequestScope ' +
'in any dependent type, you should be askking to Container to create the instances through Container.get' +
' and not calling the type constructor directly.');
throw new TypeError(
'IoC Container can not handle this request. When using @InRequestScope ' +
'in any dependent type, you should be askking to Container to create the instances through Container.get' +
' and not calling the type constructor directly.'
);
}
}
}
68 changes: 44 additions & 24 deletions src/typescript-ioc.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,43 @@
/**
* This is a lightweight annotation-based dependency injection container for typescript.
*
* Visit the project page on [GitHub] (https://github.com/thiagobustamante/typescript-ioc).
* Visit the project page on [GitHub] (https://github.com/nmshd/typescript-ioc).
*/

import 'reflect-metadata';
import { Config, ValueConfig, ObjectFactory, Scope, ContainerConfiguration, ConstantConfiguration, NamespaceConfiguration, Snapshot, BuildContext } from './model';
import { IoCContainer } from './container/container';
import { LocalScope, SingletonScope, RequestScope } from './scopes';

export { Config };
export { ValueConfig };
export { ObjectFactory };
export { BuildContext };
export { Scope };
export { ContainerConfiguration };
export { ConstantConfiguration };
export { Inject, Factory, Singleton, Scoped, OnlyInstantiableByContainer, InRequestScope, InjectValue } from './decorators';
export { Snapshot };
import {
BuildContext,
Config,
ConstantConfiguration,
ContainerConfiguration,
NamespaceConfiguration,
ObjectFactory,
Scope,
Snapshot,
ValueConfig
} from './model';
import { LocalScope, RequestScope, SingletonScope } from './scopes';

export {
Factory,
Inject,
InjectValue,
InRequestScope,
OnlyInstantiableByContainer,
Scoped,
Singleton
} from './decorators';
export {
BuildContext,
Config,
ConstantConfiguration,
ContainerConfiguration,
ObjectFactory,
Scope,
Snapshot,
ValueConfig
};

Scope.Local = new LocalScope();
Scope.Singleton = new SingletonScope();
@@ -29,7 +49,6 @@ Scope.Request = new RequestScope();
* to configure the dependency directly on the class.
*/
export class Container {

/**
* Add a dependency to the Container. If this type is already present, just return its associated
* configuration object.
@@ -66,8 +85,8 @@ export class Container {
}

/**
*
* @param name
*
* @param name
*/
public static bindName(name: string): ValueConfig {
return IoCContainer.bindName(name);
@@ -109,10 +128,12 @@ export class Container {

/**
* Import an array of configurations to the Container
* @param configurations
* @param configurations
*/
public static configure(...configurations: Array<ContainerConfiguration | ConstantConfiguration | NamespaceConfiguration>) {
configurations.forEach(config => {
public static configure(
...configurations: Array<ContainerConfiguration | ConstantConfiguration | NamespaceConfiguration>
) {
configurations.forEach((config) => {
if ((config as ContainerConfiguration).bind) {
Container.configureType(config as ContainerConfiguration);
} else if ((config as ConstantConfiguration).bindName) {
@@ -126,7 +147,7 @@ export class Container {
private static configureNamespace(config: NamespaceConfiguration) {
const selectedNamespace = IoCContainer.selectedNamespace();
const env = config.env || config.namespace;
Object.keys(env).forEach(namespace => {
Object.keys(env).forEach((namespace) => {
Container.namespace(namespace);
const namespaceConfig = env[namespace];
Container.configure(...namespaceConfig);
@@ -149,8 +170,7 @@ export class Container {
if (bind) {
if (config.to) {
bind.to(config.to);
}
else if (config.factory) {
} else if (config.factory) {
bind.factory(config.factory);
}
if (config.scope) {
@@ -166,7 +186,7 @@ export class Container {
class ContainerBuildContext extends BuildContext {
private context = new Map<Function, any>();

public build<T>(source: Function & { prototype: T; }, factory: ObjectFactory): T {
public build<T>(source: Function & { prototype: T }, factory: ObjectFactory): T {
let instance = this.context.get(source);
if (!instance) {
instance = factory(this);
@@ -178,4 +198,4 @@ class ContainerBuildContext extends BuildContext {
public resolve<T>(source: Function & { prototype: T }): T {
return IoCContainer.get(source, this);
}
}
}
10 changes: 0 additions & 10 deletions test/data/child-type.ts

This file was deleted.

97 changes: 42 additions & 55 deletions test/integration/ioc-container-tests.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@

import { Container, Inject, Scoped, Scope, ObjectFactory, Singleton, Factory } from '../../src/typescript-ioc';
import { OnlyInstantiableByContainer, InRequestScope, InjectValue } from '../../src/decorators';
import { InRequestScope, InjectValue, OnlyInstantiableByContainer } from '../../src/decorators';
import { Container, Factory, Inject, ObjectFactory, Scope, Scoped, Singleton } from '../../src/typescript-ioc';

describe('@Inject annotation on a property', () => {

class SimppleInject {
@Inject public dateProperty: Date;
}
@@ -21,7 +19,7 @@ describe('@Inject annotation on a property', () => {
}

abstract class AbsClass {
constructor(public date: Date) { }
constructor(public date: Date) {}
}

class ConstructorInjected extends AbsClass {
@@ -49,7 +47,6 @@ describe('@Inject annotation on a property', () => {
});

describe('@Inject annotation on Constructor parameter', () => {

const constructorsArgs: Array<any> = new Array<any>();
const constructorsMultipleArgs: Array<any> = new Array<any>();

@@ -78,9 +75,9 @@ describe('@Inject annotation on Constructor parameter', () => {
expect(instance.injectedDate).toEqual(myDate);
});

class Aaaa { }
class Bbbb { }
class Cccc { }
class Aaaa {}
class Bbbb {}
class Cccc {}

class Dddd {
constructor(@Inject a: Aaaa, @Inject b: Bbbb, @Inject c: Cccc) {
@@ -153,7 +150,6 @@ describe('Inheritance on types managed by IoC Container', () => {
}
}


it('should inject all fields from all types and call all constructors', () => {
const instance: Teste2 = new Teste2();
const instance2: Teste2 = new Teste2();
@@ -253,10 +249,13 @@ describe('Request scope for types', () => {
});

it('should handle direct calls to the constructor', () => {
expect(() => new SecondClass().a)
.toThrow(new TypeError('IoC Container can not handle this request. When using @InRequestScope ' +
'in any dependent type, you should be askking to Container to create the instances through Container.get' +
' and not calling the type constructor directly.'));
expect(() => new SecondClass().a).toThrow(
new TypeError(
'IoC Container can not handle this request. When using @InRequestScope ' +
'in any dependent type, you should be askking to Container to create the instances through Container.get' +
' and not calling the type constructor directly.'
)
);
});

it('should support custom providers', () => {
@@ -281,7 +280,6 @@ describe('Request scope for types', () => {
expect(thirdClass.c).toBeDefined();
expect(thirdClass.a.instance).toEqual(thirdClass.c.instance);
});

});

describe('ObjectFactory for types', () => {
@@ -319,7 +317,6 @@ describe('ObjectFactory for types', () => {
});

describe('The IoC Container.bind(source)', () => {

class ContainerInjectTest {
@Inject
public dateProperty: Date;
@@ -339,7 +336,6 @@ describe('The IoC Container.bind(source)', () => {
});

describe('The IoC Container.get(source)', () => {

class ContainerInjectConstructorTest {
public injectedDate: Date;
constructor(@Inject date: Date) {
@@ -356,7 +352,6 @@ describe('The IoC Container.get(source)', () => {
});

describe('The IoC Container.getType(source)', () => {

abstract class ITest {
public abstract testValue: string;
}
@@ -365,7 +360,6 @@ describe('The IoC Container.getType(source)', () => {
public testValue: string = 'success';
}


class TestNoObjectFactory {
public testValue: string = 'success';
}
@@ -388,16 +382,14 @@ describe('The IoC Container.getType(source)', () => {
it('should throw error when the type is not registered in the Container', () => {
try {
Container.getType(TypeNotRegistered);
fail(new Error(`The type TypeNotResistered should not pass the test`));
}
catch (e) {
fail(new Error('The type TypeNotResistered should not pass the test'));
} catch (e) {
expect(e).toBeInstanceOf(TypeError);
}
});
});

describe('The IoC Container.bindName(name)', () => {

interface Config {
dependencyURL: string;
port: number;
@@ -421,8 +413,10 @@ describe('The IoC Container.bindName(name)', () => {
});

class MyService {
constructor(@InjectValue('config') public config: Config,
@InjectValue('config.dependencyURL') public url: string) { }
constructor(
@InjectValue('config') public config: Config,
@InjectValue('config.dependencyURL') public url: string
) {}
}

const myService = Container.get(MyService);
@@ -453,33 +447,29 @@ describe('The IoC Container.bindName(name)', () => {
Container.bindName('config.port').to(1234);

class MyService {
constructor(@InjectValue('config') public config: Config,
@InjectValue('config.dependencyURL') public url: string) { }
constructor(
@InjectValue('config') public config: Config,
@InjectValue('config.dependencyURL') public url: string
) {}
}

const myService = Container.get(MyService);
expect(myService.config.dependencyURL).toEqual('http://localhost:8080');
expect(myService.config.port).toEqual(1234);
expect(myService.url).toEqual('http://localhost:8080');
});

});

describe('The IoC Container.snapshot()', () => {
abstract class IService {}

abstract class IService {
}
class Service implements IService {}

class Service implements IService {
}

class MockService implements IService {
}
class MockService implements IService {}

Container.bind(IService).to(Service);

it('should store the existing service and overwrite with new service without scope', () => {

expect(Container.get(IService)).toBeInstanceOf(Service);

const snapshot = Container.snapshot(IService);
@@ -490,9 +480,7 @@ describe('The IoC Container.snapshot()', () => {
expect(Container.get(IService)).toBeInstanceOf(Service);
});


it('should store the existing service and overwrite with new service with scope', () => {

Container.bind(IService).to(Service).scope(Scope.Local);

expect(Container.get(IService)).toBeInstanceOf(Service);
@@ -506,7 +494,6 @@ describe('The IoC Container.snapshot()', () => {
});

it('should support multiples snapshots', () => {

Container.bindName('configURL').to('myURL');
expect(Container.getValue('configURL')).toEqual('myURL');

@@ -531,7 +518,6 @@ describe('The IoC Container.snapshot()', () => {
});

describe('@OnlyInstantiableByContainer decorator', () => {

@OnlyInstantiableByContainer
@Singleton
class SingletonInstantiation {
@@ -544,14 +530,19 @@ describe('@OnlyInstantiableByContainer decorator', () => {
}

@OnlyInstantiableByContainer
class LocalInstantiation {
}
class LocalInstantiation {}

it('should not allow instantiations of wired classes.', () => {
expect(() => new SingletonInstantiation())
.toThrow(new TypeError('Can not instantiate it. The instantiation is blocked for this class. Ask Container for it, using Container.get'));
expect(() => new LocalInstantiation())
.toThrow(new TypeError('Can not instantiate it. The instantiation is blocked for this class. Ask Container for it, using Container.get'));
expect(() => new SingletonInstantiation()).toThrow(
new TypeError(
'Can not instantiate it. The instantiation is blocked for this class. Ask Container for it, using Container.get'
)
);
expect(() => new LocalInstantiation()).toThrow(
new TypeError(
'Can not instantiate it. The instantiation is blocked for this class. Ask Container for it, using Container.get'
)
);
});

it('should allow Container instantiation of Singleton classes.', () => {
@@ -561,18 +552,18 @@ describe('@OnlyInstantiableByContainer decorator', () => {

it('should allow Container instantiation of Singleton classes with instrumented parent.', () => {
@OnlyInstantiableByContainer
class First { }
class First {}

@OnlyInstantiableByContainer
class Second extends First { }
class Second extends First {}

const instance: Second = Container.get(Second);
expect(instance).toBeDefined();
});

it('should allow Container instantiation of Singleton classes with instrumented properties', () => {
@OnlyInstantiableByContainer
class First { }
class First {}

@OnlyInstantiableByContainer
class Second {
@@ -629,7 +620,6 @@ describe('@OnlyInstantiableByContainer decorator', () => {
});

describe('The IoC Container Config.to()', () => {

abstract class FirstClass {
public abstract getValue(): string;
}
@@ -659,16 +649,13 @@ describe('The IoC Container Config.to()', () => {
});

describe('The IoC Container Config.withParams()', () => {

class WithParamClass {
constructor(public date: Date) {

}
constructor(public date: Date) {}
}
Container.bind(WithParamClass).withParams(Date);

it('should configure the params to be passed to constructor manually', () => {
const instance: WithParamClass = Container.get(WithParamClass);
expect(instance.date).toBeDefined();
});
});
});
16 changes: 3 additions & 13 deletions test/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
module.exports = {
testEnvironment: 'node',
transform: {
"^.+\\.tsx?$": "ts-jest"
'^.+\\.tsx?$': 'ts-jest'
},
moduleFileExtensions: [
"ts",
"tsx",
"js",
"jsx",
"json",
"node",
],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
testRegex: '(/unit/.*|(\\.|/)(test|spec))\\.(ts|js)x?$',
coverageDirectory: 'reports/coverage',
collectCoverageFrom: [
'src/**/*.{ts,tsx,js,jsx}',
'!src/**/*.d.ts',
],
collectCoverageFrom: ['src/**/*.{ts,tsx,js,jsx}', '!src/**/*.d.ts'],
coverageThreshold: {
global: {
branches: 80,
37 changes: 12 additions & 25 deletions test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,13 @@
{
"compilerOptions": {
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"lib": [
"es6",
"dom"
],
"module": "commonjs",
"newLine": "LF",
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": false,
"noUnusedParameters": false,
"noUnusedLocals": true,
"removeComments": true,
"sourceMap": true,
"strictNullChecks": false,
"target": "es6"
},
"include": [
"**/*.spec.ts"
]
}
"compilerOptions": {
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"lib": ["es2017", "dom"],
"module": "commonjs",
"noUnusedLocals": true,
"sourceMap": true,
"target": "es6"
},
"include": ["**/*.spec.ts", "data/*.ts"]
}
23 changes: 10 additions & 13 deletions test/unit/container/container-binding-config.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { InjectorHandler } from '../../../src/container/injection-handler';
import { IoCBindConfig, IoCBindValueConfig, PropertyPath } from '../../../src/container/container-binding-config';
import { BuildContext, ObjectFactory } from '../../../src/model';
@@ -45,7 +44,7 @@ describe('IoCBindConfig', () => {
mockInjectorRemoveContext.mockClear();
});

class MyBaseType { }
class MyBaseType {}

describe('instrumentConstructor()', () => {
it('should instrument the type constructor', () => {
@@ -67,7 +66,6 @@ describe('IoCBindConfig', () => {
const bindConfig = new IoCBindConfig(MyBaseType, mockInstanceFactory, mockValueFactory);
expect(bindConfig.withParams(...paramTypes)).toEqual(bindConfig);
expect(bindConfig.paramTypes).toEqual(paramTypes);

});
});

@@ -153,7 +151,7 @@ describe('IoCBindConfig', () => {

describe('to()', () => {
it('should create providers for type instantiation', () => {
class MyType extends MyBaseType { }
class MyType extends MyBaseType {}

const instance = new MyType();
mockInstanceFactory.mockReturnValue(instance);
@@ -169,7 +167,7 @@ describe('IoCBindConfig', () => {
});

it('should reset scope after change configuration', () => {
class MyType extends MyBaseType { }
class MyType extends MyBaseType {}

const instance = new MyType();
mockInstanceFactory.mockReturnValue(instance);
@@ -188,7 +186,7 @@ describe('IoCBindConfig', () => {
super();
}
}
mockInstanceFactory.mockImplementation((type) => type === Date ? new Date() : null);
mockInstanceFactory.mockImplementation((type) => (type === Date ? new Date() : null));
mockInjectorGetConstructorFromType.mockReturnValue(MyType);
const bindConfig = new IoCBindConfig(MyType, mockInstanceFactory, mockValueFactory);
const buildContext = new TestBuildContext();
@@ -203,7 +201,7 @@ describe('IoCBindConfig', () => {
});

it('should support instrumented constructors', () => {
class ExtendedType extends MyBaseType { }
class ExtendedType extends MyBaseType {}
mockInjectorInstrumentConstructor.mockReturnValue(ExtendedType);
mockInjectorGetConstructorFromType.mockReturnValue(MyBaseType);
const bindConfig = new IoCBindConfig(MyBaseType, mockInstanceFactory, mockValueFactory);
@@ -216,7 +214,6 @@ describe('IoCBindConfig', () => {
expect(mockInjectorInjectContext).toBeCalledWith(ExtendedType, buildContext);
expect(mockInjectorRemoveContext).toBeCalledWith(ExtendedType);
});

});
});

@@ -271,18 +268,18 @@ describe('PropertyPath', () => {
});

it('shoud return null for invalid property', () => {
expect(() => PropertyPath.parse('.mypath'))
.toThrow(new TypeError(`Invalid value [.mypath] passed to Container.bindName`));
expect(() => PropertyPath.parse('.mypath')).toThrow(
new TypeError(`Invalid value [.mypath] passed to Container.bindName`)
);
});
});

});

class TestBuildContext extends BuildContext {
public build<T>(_source: Function & { prototype: T; }, _factory: ObjectFactory): T {
public build<T>(_source: Function & { prototype: T }, _factory: ObjectFactory): T {
return null;
}
public resolve<T>(_source: Function & { prototype: T }): T {
return null;
}
}
}
8 changes: 3 additions & 5 deletions test/unit/container/container-namespaces.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@

import { ContainerNamespaces } from '../../../src/container/container-namespaces';
import { InjectorHandler } from '../../../src/container/injection-handler';
import { IoCBindConfig, IoCBindValueConfig } from '../../../src/container/container-binding-config';

describe('ContainerNamespaces', () => {

let namespaces: ContainerNamespaces;

beforeEach(() => {
@@ -13,15 +11,15 @@ describe('ContainerNamespaces', () => {

describe('get()', () => {
it('should retrieve a bindConfig for the default namespace', () => {
class MyType { }
class MyType {}
const constructor = InjectorHandler.getConstructorFromType(MyType);
const config = new IoCBindConfig(constructor, jest.fn(), jest.fn());
namespaces.set(constructor, config);
expect(namespaces.get(constructor)).toEqual(config);
});

it('should retrieve a bindConfig for the current namespace', () => {
class MyType { }
class MyType {}
const constructor = InjectorHandler.getConstructorFromType(MyType);
const config = new IoCBindConfig(constructor, jest.fn(), jest.fn());
const namespaceName = 'newNamespace';
@@ -33,7 +31,7 @@ describe('ContainerNamespaces', () => {
});

it('should retrieve a bindConfig from default namespace if does not exist in the current namespace', () => {
class MyType { }
class MyType {}
const constructor = InjectorHandler.getConstructorFromType(MyType);
const config = new IoCBindConfig(constructor, jest.fn(), jest.fn());
const namespaceName = 'newNamespace';
28 changes: 12 additions & 16 deletions test/unit/container/container.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { InjectorHandler } from '../../../src/container/injection-handler';
import { IoCContainer } from '../../../src/container/container';
import { IoCBindConfig, IoCBindValueConfig, PropertyPath } from '../../../src/container/container-binding-config';
@@ -78,9 +77,8 @@ describe('Container', () => {
});

describe('bind()', () => {

it('should bind a type to the container', () => {
class MyBaseType { }
class MyBaseType {}
const constructor = { anyObject: 'anyValue' };
mockGetConstructorFromType.mockReturnValue(constructor);

@@ -96,7 +94,6 @@ describe('Container', () => {
});

describe('bindName()', () => {

it('should bind a value to the container', () => {
const valueName = 'myvalue';
const path = 'a';
@@ -122,7 +119,7 @@ describe('Container', () => {

describe('get()', () => {
it('should get an instance for a type bound to the container', () => {
class MyBaseType { }
class MyBaseType {}
const constructor = { anyProp: 'anyValue' };
mockGetConstructorFromType.mockReturnValue(constructor);
mockIoCBindConfig.mockImplementation(() => {
@@ -144,7 +141,7 @@ describe('Container', () => {
});

it('should set a target class before get an instance if no provider is configured', () => {
class MyBaseType { }
class MyBaseType {}
const constructor = { anyProperty: 'anyValue' };
mockGetConstructorFromType.mockReturnValue(constructor);
const instance = { prop: 'instanceProp' };
@@ -178,21 +175,21 @@ describe('Container', () => {
});
});


describe('getType()', () => {
it('should throw an error for a type not bound to the container', () => {
class MyBaseType { }
class MyBaseType {}
const constructor = { prop1: 'propValue' };
mockGetConstructorFromType.mockReturnValue(constructor);
expect(() => IoCContainer.getType(MyBaseType))
.toThrow(TypeError(`The type MyBaseType hasn't been registered with the IOC Container`));
expect(() => IoCContainer.getType(MyBaseType)).toThrow(
TypeError(`The type MyBaseType hasn't been registered with the IOC Container`)
);

expect(mockCheckType).toBeCalledWith(MyBaseType);
expect(mockGetConstructorFromType).toBeCalledWith(MyBaseType);
});

it('should return target type for a type bound to the container', () => {
class MyBaseType { }
class MyBaseType {}
const constructor = { prop1: 'propValue' };
mockGetConstructorFromType.mockReturnValue(constructor);
mockGet.mockReturnValue({
@@ -210,7 +207,7 @@ describe('Container', () => {
});

it('should return source when no targetSource is available', () => {
class MyBaseType { }
class MyBaseType {}
const constructor = { p: 'propValue' };
mockGetConstructorFromType.mockReturnValue(constructor);
mockGet.mockReturnValue({
@@ -230,7 +227,7 @@ describe('Container', () => {

describe('injectProperty()', () => {
it('should call InjectorHandler.injectProperty properly', () => {
class MyBaseType { }
class MyBaseType {}
IoCContainer.injectProperty(MyBaseType, 'prop', Date);

expect(mockInjectProperty).toBeCalledWith(MyBaseType, 'prop', Date, IoCContainer.get);
@@ -255,14 +252,13 @@ describe('Container', () => {
expect(mockSelectedNamespace).toBeCalledTimes(1);
});
});

});

class TestBuildContext extends BuildContext {
public build<T>(_source: Function & { prototype: T; }, _factory: ObjectFactory): T {
public build<T>(_source: Function & { prototype: T }, _factory: ObjectFactory): T {
return null;
}
public resolve<T>(_source: Function & { prototype: T }): T {
return null;
}
}
}
62 changes: 34 additions & 28 deletions test/unit/container/injection-handler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@

import { InjectorHandler } from '../../../src/container/injection-handler';
import { BuildContext, ObjectFactory } from '../../../src/model';

describe('InjectorHandler', () => {
describe('instrumentConstructor()', () => {
it('should decorate the type constructor properly', () => {
class MyBaseType { }
class MyBaseType {}
const newConstructor = InjectorHandler.instrumentConstructor(MyBaseType);
expect(newConstructor.name).toEqual('ioc_wrapper');
expect(newConstructor['__parent']).toEqual(MyBaseType);
});

it('should keep creating valid instances for the baseType', () => {
class MyBaseType { }
class MyBaseType {}
const newConstructor = InjectorHandler.instrumentConstructor(MyBaseType);
InjectorHandler.unblockInstantiation();
expect(new newConstructor()).toBeInstanceOf(MyBaseType);
@@ -22,11 +21,14 @@ describe('InjectorHandler', () => {

describe('blockInstantiation()', () => {
it('should avoid that instrumented constructor create instances', () => {
class MyBaseType { }
class MyBaseType {}
const newConstructor = InjectorHandler.instrumentConstructor(MyBaseType);
InjectorHandler.blockInstantiation(true);
expect(() => new newConstructor())
.toThrow(new TypeError('Can not instantiate it. The instantiation is blocked for this class. Ask Container for it, using Container.get'));
expect(() => new newConstructor()).toThrow(
new TypeError(
'Can not instantiate it. The instantiation is blocked for this class. Ask Container for it, using Container.get'
)
);
});
});

@@ -44,7 +46,7 @@ describe('InjectorHandler', () => {

describe('injectContext()', () => {
it('should inject the context as a hidden property into the target', () => {
class MyBaseType { }
class MyBaseType {}
const context = new TestBuildContext();
InjectorHandler.injectContext(MyBaseType, context);
expect((MyBaseType as any)['__BuildContext']).toEqual(context);
@@ -53,24 +55,23 @@ describe('InjectorHandler', () => {

describe('removeContext()', () => {
it('should remove an injected the context from the target', () => {
class MyBaseType { }
class MyBaseType {}
const context = new TestBuildContext();
InjectorHandler.injectContext(MyBaseType, context);
InjectorHandler.removeContext(MyBaseType);
expect((MyBaseType as any)['__BuildContext']).toBeFalsy();
});

});

describe('getConstructorFromType()', () => {
it('should extract the original constructor from a type', () => {
class MyBaseType { }
class MyBaseType {}
const constructor = InjectorHandler.getConstructorFromType(MyBaseType);
expect(constructor).toEqual(MyBaseType);
});

it('should extract the original constructor from an instrumented type', () => {
class MyBaseType { }
class MyBaseType {}
const newConstructor = InjectorHandler.instrumentConstructor(MyBaseType);
const constructor = InjectorHandler.getConstructorFromType(newConstructor);
expect(constructor).toEqual(MyBaseType);
@@ -85,42 +86,47 @@ describe('InjectorHandler', () => {
});

it('should throw an error with an invalid constructor is informed', () => {
class MyBaseType { }
class MyBaseType {}
const newConstructor = InjectorHandler.instrumentConstructor(MyBaseType);
delete newConstructor['__parent'];
expect(() => InjectorHandler.getConstructorFromType(newConstructor))
.toThrow('Can not identify the base Type for requested target ' + newConstructor.toString());
expect(() => InjectorHandler.getConstructorFromType(newConstructor)).toThrow(
'Can not identify the base Type for requested target ' + newConstructor.toString()
);
});
});

describe('checkType', () => {
it('should thow an error if invalid type is provided', () => {
expect(() => InjectorHandler.checkType(undefined))
.toThrow(new TypeError('Invalid type requested to IoC container. Type is not defined.'));
expect(() => InjectorHandler.checkType(undefined)).toThrow(
new TypeError('Invalid type requested to IoC container. Type is not defined.')
);
});

it('should not thow an error if valid type is provided', () => {
class MyBaseType { }
expect(() => InjectorHandler.checkType(MyBaseType))
.not.toThrow(new TypeError('Invalid type requested to IoC container. Type is not defined.'));
class MyBaseType {}
expect(() => InjectorHandler.checkType(MyBaseType)).not.toThrow(
new TypeError('Invalid type requested to IoC container. Type is not defined.')
);
});
});

describe('checkName', () => {
it('should thow an error if invalid name is provided', () => {
expect(() => InjectorHandler.checkName(undefined))
.toThrow(new TypeError('Invalid name requested to IoC container. Name is not defined.'));
expect(() => InjectorHandler.checkName(undefined)).toThrow(
new TypeError('Invalid name requested to IoC container. Name is not defined.')
);
});

it('should not thow an error if valid name is provided', () => {
expect(() => InjectorHandler.checkName('aName'))
.not.toThrow(new TypeError('Invalid name requested to IoC container. Name is not defined.'));
expect(() => InjectorHandler.checkName('aName')).not.toThrow(
new TypeError('Invalid name requested to IoC container. Name is not defined.')
);
});
});

describe('injectProperty', () => {
it('should create a property to read the injected value from the IoC Container', () => {
class MyBaseType { }
class MyBaseType {}
const propertyInstance = new Date('2019-05-14T11:01:50.135Z');
const secondInstance = new Date('2019-05-14T11:01:55.135Z');
const instanceFactory = jest.fn().mockImplementation(() => {
@@ -138,7 +144,7 @@ describe('InjectorHandler', () => {
});

it('should be able to handle BuildContext in the constructor', () => {
class MyBaseType { }
class MyBaseType {}
const propertyInstance = new Date('2019-05-14T11:01:50.135Z');
const secondInstance = new Date('2019-05-14T11:01:55.135Z');
const instanceFactory = jest.fn().mockImplementation(() => {
@@ -158,7 +164,7 @@ describe('InjectorHandler', () => {

describe('injectValueProperty', () => {
it('should create a property to read the injected value from the IoC Container', () => {
class MyBaseType { }
class MyBaseType {}
const name1 = 'a value';
const name2 = 'another value';
const valueFactory = jest.fn().mockImplementation(() => {
@@ -176,10 +182,10 @@ describe('InjectorHandler', () => {
});

class TestBuildContext extends BuildContext {
public build<T>(_source: Function & { prototype: T; }, _factory: ObjectFactory): T {
public build<T>(_source: Function & { prototype: T }, _factory: ObjectFactory): T {
return null;
}
public resolve<T>(_source: Function & { prototype: T }): T {
return null;
}
}
}
34 changes: 15 additions & 19 deletions test/unit/decorators.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { IoCContainer } from '../../src/container/container';
import { Inject, Config, Singleton, Scope, Scoped, Factory } from '../../src/typescript-ioc';
import { OnlyInstantiableByContainer, InjectValue } from '../../src/decorators';
@@ -8,7 +7,6 @@ const mockInjectProperty = IoCContainer.injectProperty as jest.Mock;
const mockBind = IoCContainer.bind as jest.Mock;

describe('@Inject decorator', () => {

beforeEach(() => {
mockInjectProperty.mockClear();
mockBind.mockClear();
@@ -21,15 +19,15 @@ describe('@Inject decorator', () => {
expect(mockInjectProperty).toBeCalledWith(SimppleInject, 'dateProperty', Date);
});


it('should inject new values on constructor parameters', () => {
const config: any = {};
mockBind.mockReturnValue(config);

class ConstructorInjected {
constructor(@Inject public anotherDate: Date,
@Inject public myProp: String) {
}
constructor(
@Inject public anotherDate: Date,
@Inject public myProp: String
) {}
}
expect(mockBind).toBeCalledWith(ConstructorInjected);
expect(config.paramTypes).toStrictEqual([Date, String]);
@@ -48,7 +46,7 @@ describe('@Inject decorator', () => {
it('can not be used on classes directly', () => {
const testFunction = () => {
@Inject
class ClassInjected { }
class ClassInjected {}
expect(mockBind).not.toBeCalledWith(ClassInjected);
};

@@ -60,7 +58,6 @@ const mockInjectValueProperty = IoCContainer.injectValueProperty as jest.Mock;
const mockBindName = IoCContainer.bindName as jest.Mock;

describe('@InjectValue decorator', () => {

beforeEach(() => {
mockInjectValueProperty.mockClear();
mockBindName.mockClear();
@@ -74,15 +71,15 @@ describe('@InjectValue decorator', () => {
expect(mockInjectValueProperty).toBeCalledWith(SimppleInject, 'dateProperty', 'myDate');
});


it('should inject new values on constructor parameters', () => {
const config: any = {};
mockBind.mockReturnValue(config);

class ConstructorInjected {
constructor(@InjectValue('myDate') public anotherDate: Date,
@Inject public myProp: String) {
}
constructor(
@InjectValue('myDate') public anotherDate: Date,
@Inject public myProp: String
) {}
}
expect(mockBind).toBeCalledWith(ConstructorInjected);
expect(config.paramTypes).toStrictEqual(['myDate', String]);
@@ -101,7 +98,7 @@ describe('@InjectValue decorator', () => {
it('can not be used on classes directly', () => {
const testFunction = () => {
@InjectValue('myDate')
class ClassInjected { }
class ClassInjected {}
expect(mockBind).not.toBeCalledWith(ClassInjected);
};

@@ -118,7 +115,7 @@ const bindResult: Config = {
to: mockTo,
factory: mockFactory,
scope: mockScope,
withParams: mockWithParams,
withParams: mockWithParams
};

describe('@Singleton decorator', () => {
@@ -131,7 +128,7 @@ describe('@Singleton decorator', () => {

it('should configure the class binding into a singleton scope', () => {
@Singleton
class SingletonInject { }
class SingletonInject {}
expect(mockBind).toBeCalledWith(SingletonInject);
expect(mockScope).toBeCalledWith(Scope.Singleton);
});
@@ -147,7 +144,7 @@ describe('@Scoped decorator', () => {

it('should configure the class binding into a custom scope', () => {
@Scoped(Scope.Local)
class ScopedInject { }
class ScopedInject {}
expect(mockBind).toBeCalledWith(ScopedInject);
expect(mockScope).toBeCalledWith(Scope.Local);
});
@@ -164,14 +161,13 @@ describe('@Factory decorator', () => {
it('should configure the class binding to use a custom provider', () => {
const factory = () => new ProvidedInject();
@Factory(factory)
class ProvidedInject { }
class ProvidedInject {}
expect(mockBind).toBeCalledWith(ProvidedInject);
expect(mockFactory).toBeCalledWith(factory);
});
});

describe('@OnlyInstantiableByContainer decorator', () => {

beforeEach(() => {
mockBind.mockClear();
mockInstrumentConstructor.mockReturnThis();
@@ -184,7 +180,7 @@ describe('@OnlyInstantiableByContainer decorator', () => {
decoratedConstructor: constructor
};
mockBind.mockReturnValue(bind);
class WiredInject { }
class WiredInject {}

expect(OnlyInstantiableByContainer(WiredInject)).toEqual(constructor);
expect(mockBind).toBeCalledWith(WiredInject);
14 changes: 5 additions & 9 deletions test/unit/typescript-ioc.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@

import { IoCContainer } from '../../src/container/container';
import { Container, Scope, Config, ObjectFactory } from '../../src/typescript-ioc';
import { BuildContext, ValueConfig } from '../../src/model';
import { Config, Container, ObjectFactory, Scope } from '../../src/typescript-ioc';

jest.mock('../../src/container/container');
const mockBind = IoCContainer.bind as jest.Mock;
@@ -20,7 +19,6 @@ let bindResult: Config;
let bindNameResult: ValueConfig;

describe('Container', () => {

beforeAll(() => {
bindResult = {
to: mockTo,
@@ -31,7 +29,6 @@ describe('Container', () => {
bindNameResult = {
to: mockTo
};

});

beforeEach(() => {
@@ -50,8 +47,8 @@ describe('Container', () => {
mockBindName.mockReturnValue(bindNameResult);
});

class MyBaseType { }
class MyType extends MyBaseType { }
class MyBaseType {}
class MyType extends MyBaseType {}
const MyFactory: ObjectFactory = () => new MyType();

it('should get an instance for a type bound to the container', () => {
@@ -109,7 +106,6 @@ describe('Container', () => {
});

describe('configure()', () => {

it('should configure the IoC Container', () => {
Container.configure({ bind: MyBaseType, to: MyType });

@@ -150,7 +146,7 @@ describe('Container', () => {
it('should apply configurations to specific namespaces', () => {
mockSelectedNamespace.mockReturnValue('otherNamespace');

Container.configure({ namespace: { 'test': [{ bindName: 'myProp', to: 'a value' }] } });
Container.configure({ namespace: { test: [{ bindName: 'myProp', to: 'a value' }] } });

expect(mockSelectedNamespace).toBeCalledTimes(1);
expect(mockNamespace).toBeCalledWith('test');
@@ -162,7 +158,7 @@ describe('Container', () => {
it('should apply configurations to specific environment', () => {
mockSelectedNamespace.mockReturnValue('otherNamespace');

Container.configure({ env: { 'test': [{ bindName: 'myProp', to: 'a value' }] } });
Container.configure({ env: { test: [{ bindName: 'myProp', to: 'a value' }] } });

expect(mockSelectedNamespace).toBeCalledTimes(1);
expect(mockNamespace).toBeCalledWith('test');
38 changes: 16 additions & 22 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
{
"compilerOptions": {
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"lib": [
"es2017"
],
"moduleResolution": "node",
"module": "commonjs",
"noUnusedLocals": true,
"noUnusedParameters": true,
"sourceMap": true,
"target": "es6",
"outDir": "./dist"
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}
"compilerOptions": {
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"lib": ["es2017"],
"moduleResolution": "node",
"module": "commonjs",
"noUnusedLocals": true,
"noUnusedParameters": true,
"sourceMap": true,
"target": "es6",
"outDir": "./dist"
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
34 changes: 0 additions & 34 deletions tslint.json

This file was deleted.