Skip to content

Commit

Permalink
Merge pull request #105 from inversify/refactor/add-named-decorator
Browse files Browse the repository at this point in the history
Add named decorator
  • Loading branch information
notaphplover authored Nov 16, 2024
2 parents 7eb68e2 + 4e6978d commit 607d37f
Show file tree
Hide file tree
Showing 7 changed files with 692 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { beforeAll, describe, expect, it } from '@jest/globals';

import { MaybeClassElementMetadataKind } from '../models/MaybeClassElementMetadataKind';
import { MaybeManagedClassElementMetadata } from '../models/MaybeManagedClassElementMetadata';
import { buildDefaultMaybeClassElementMetadata } from './buildDefaultMaybeClassElementMetadata';

describe(buildDefaultMaybeClassElementMetadata.name, () => {
describe('when called', () => {
let result: unknown;

beforeAll(() => {
result = buildDefaultMaybeClassElementMetadata();
});

it('should return MaybeManagedClassElementMetadata', () => {
const expected: MaybeManagedClassElementMetadata = {
kind: MaybeClassElementMetadataKind.unknown,
name: undefined,
optional: false,
tags: new Map(),
targetName: undefined,
};

expect(result).toStrictEqual(expected);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { MaybeClassElementMetadataKind } from '../models/MaybeClassElementMetadataKind';
import { MaybeManagedClassElementMetadata } from '../models/MaybeManagedClassElementMetadata';

export function buildDefaultMaybeClassElementMetadata(): MaybeManagedClassElementMetadata {
return {
kind: MaybeClassElementMetadataKind.unknown,
name: undefined,
optional: false,
tags: new Map(),
targetName: undefined,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { beforeAll, describe, expect, it } from '@jest/globals';

import { InversifyCoreError } from '../../error/models/InversifyCoreError';
import { InversifyCoreErrorKind } from '../../error/models/InversifyCoreErrorKind';
import { ClassElementMetadataKind } from '../models/ClassElementMetadataKind';
import { ManagedClassElementMetadata } from '../models/ManagedClassElementMetadata';
import { MaybeManagedClassElementMetadata } from '../models/MaybeManagedClassElementMetadata';
import { MetadataTag } from '../models/MetadataTag';
import { UnmanagedClassElementMetadata } from '../models/UnmanagedClassElementMetadata';
import { buildMaybeClassElementMetadataFromMaybeClassElementMetadata } from './buildMaybeClassElementMetadataFromMaybeClassElementMetadata';

describe(
buildMaybeClassElementMetadataFromMaybeClassElementMetadata.name,
() => {
describe('having unmanaged metadata', () => {
let metadataPartialFixture: Partial<MaybeManagedClassElementMetadata>;
let metadataFixture: UnmanagedClassElementMetadata;

beforeAll(() => {
metadataPartialFixture = {};
metadataFixture = {
kind: ClassElementMetadataKind.unmanaged,
};
});

describe('when called', () => {
let result: unknown;

beforeAll(() => {
try {
buildMaybeClassElementMetadataFromMaybeClassElementMetadata(
metadataPartialFixture,
)(metadataFixture);
} catch (error: unknown) {
result = error;
}
});

it('should throw an InversifyCoreError', () => {
const expectedErrorProperties: Partial<InversifyCoreError> = {
kind: InversifyCoreErrorKind.injectionDecoratorConflict,
message:
'Unexpected injection found. Found @unmanaged injection with additional @named, @optional, @tagged or @targetName injections',
};

expect(result).toBeInstanceOf(InversifyCoreError);
expect(result).toStrictEqual(
expect.objectContaining(expectedErrorProperties),
);
});
});
});

describe('having non unmanaged metadata', () => {
let metadataPartialFixture: Partial<MaybeManagedClassElementMetadata>;
let metadataFixture: ManagedClassElementMetadata;

beforeAll(() => {
metadataPartialFixture = {
name: 'name-fixture',
optional: true,
targetName: 'target-name-fixture',
};
metadataFixture = {
kind: ClassElementMetadataKind.singleInjection,
name: undefined,
optional: false,
tags: new Map([['foo', 'bar']]),
targetName: undefined,
value: 'service-identifier',
};
});

describe('when called', () => {
let result: unknown;

beforeAll(() => {
result = buildMaybeClassElementMetadataFromMaybeClassElementMetadata(
metadataPartialFixture,
)(metadataFixture);
});

it('should return ManagedClassElementMetadata', () => {
const expected:
| ManagedClassElementMetadata
| MaybeManagedClassElementMetadata = {
...metadataFixture,
...metadataPartialFixture,
};

expect(result).toStrictEqual(expected);
});
});
});

describe('having non unmanaged metadata and partial metadata with tags', () => {
let metadataPartialFixture: Partial<MaybeManagedClassElementMetadata>;
let metadataFixture: ManagedClassElementMetadata;

beforeAll(() => {
metadataPartialFixture = {
name: 'name-fixture',
optional: true,
tags: new Map([['bar', 'baz']]),
targetName: 'target-name-fixture',
};
metadataFixture = {
kind: ClassElementMetadataKind.singleInjection,
name: undefined,
optional: false,
tags: new Map([['foo', 'bar']]),
targetName: undefined,
value: 'service-identifier',
};
});

describe('when called', () => {
let result: unknown;

beforeAll(() => {
result = buildMaybeClassElementMetadataFromMaybeClassElementMetadata(
metadataPartialFixture,
)(metadataFixture);
});

it('should return ManagedClassElementMetadata', () => {
const expected:
| ManagedClassElementMetadata
| MaybeManagedClassElementMetadata = {
...metadataFixture,
...metadataPartialFixture,
tags: new Map([
...metadataFixture.tags,
...(metadataPartialFixture.tags as Map<MetadataTag, unknown>),
]),
};

expect(result).toStrictEqual(expected);
});
});
});
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { InversifyCoreError } from '../../error/models/InversifyCoreError';
import { InversifyCoreErrorKind } from '../../error/models/InversifyCoreErrorKind';
import { ClassElementMetadataKind } from '../models/ClassElementMetadataKind';
import { ManagedClassElementMetadata } from '../models/ManagedClassElementMetadata';
import { MaybeClassElementMetadata } from '../models/MaybeClassElementMetadata';
import { MaybeManagedClassElementMetadata } from '../models/MaybeManagedClassElementMetadata';
import { buildDefaultMaybeClassElementMetadata } from './buildDefaultMaybeClassElementMetadata';

export function buildMaybeClassElementMetadataFromMaybeClassElementMetadata(
metadataPartial: Partial<MaybeManagedClassElementMetadata>,
): (
metadata: MaybeClassElementMetadata | undefined,
) => ManagedClassElementMetadata | MaybeManagedClassElementMetadata {
return (
metadata: MaybeClassElementMetadata | undefined,
): ManagedClassElementMetadata | MaybeManagedClassElementMetadata => {
const definedMetadata: MaybeClassElementMetadata =
metadata ?? buildDefaultMaybeClassElementMetadata();

switch (definedMetadata.kind) {
case ClassElementMetadataKind.unmanaged:
throw new InversifyCoreError(
InversifyCoreErrorKind.injectionDecoratorConflict,
'Unexpected injection found. Found @unmanaged injection with additional @named, @optional, @tagged or @targetName injections',
);
default:
return buildMergedMetadata(definedMetadata, metadataPartial);
}
};
}

function buildMergedMetadata(
metadata: ManagedClassElementMetadata | MaybeManagedClassElementMetadata,
metadataPartial: Partial<MaybeManagedClassElementMetadata>,
): ManagedClassElementMetadata | MaybeManagedClassElementMetadata {
const mergedMetadata:
| ManagedClassElementMetadata
| MaybeManagedClassElementMetadata = {
...metadata,
...metadataPartial,
};

if (metadataPartial.tags !== undefined) {
mergedMetadata.tags = new Map([...metadata.tags, ...metadataPartial.tags]);
}

return mergedMetadata;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { beforeAll, describe, expect, it } from '@jest/globals';

import 'reflect-metadata';

import { getReflectMetadata } from '@inversifyjs/reflect-metadata-utils';

import { classMetadataReflectKey } from '../../reflectMetadata/data/classMetadataReflectKey';
import { MaybeClassElementMetadataKind } from '../models/MaybeClassElementMetadataKind';
import { MaybeClassMetadata } from '../models/MaybeClassMetadata';
import { named } from './named';

describe(named.name, () => {
describe('when called', () => {
let result: unknown;

beforeAll(() => {
class Foo {
@named('bar')
public readonly bar!: string;

@named('baz')
public readonly baz!: string;

constructor(
@named('firstParam')
public firstParam: number,
@named('secondParam')
public secondParam: number,
) {}
}

result = getReflectMetadata(Foo, classMetadataReflectKey);
});

it('should return expected metadata', () => {
const expected: MaybeClassMetadata = {
constructorArguments: [
{
kind: MaybeClassElementMetadataKind.unknown,
name: 'firstParam',
optional: false,
tags: new Map(),
targetName: undefined,
},
{
kind: MaybeClassElementMetadataKind.unknown,
name: 'secondParam',
optional: false,
tags: new Map(),
targetName: undefined,
},
],
lifecycle: {
postConstructMethodName: undefined,
preDestroyMethodName: undefined,
},
properties: new Map([
[
'bar',
{
kind: MaybeClassElementMetadataKind.unknown,
name: 'bar',
optional: false,
tags: new Map(),
targetName: undefined,
},
],
[
'baz',
{
kind: MaybeClassElementMetadataKind.unknown,
name: 'baz',
optional: false,
tags: new Map(),
targetName: undefined,
},
],
]),
};

expect(result).toStrictEqual(expected);
});
});
});
Loading

0 comments on commit 607d37f

Please sign in to comment.