From bdab0723ef9f7092f172833b84a223e2baaa50c6 Mon Sep 17 00:00:00 2001 From: Chau Tran Date: Tue, 24 Mar 2020 16:44:19 -0500 Subject: [PATCH] fix(automapper.ts): add more tests and fix get bug --- src/member-functions/specs/condition.spec.ts | 60 +++++++++++++++++++ .../specs/convert-using.spec.ts | 42 +++++++++++++ src/member-functions/specs/from-value.spec.ts | 25 ++++++++ src/member-functions/specs/ignore.spec.ts | 24 ++++++++ src/member-functions/specs/map-from.spec.ts | 52 ++++++++++++++++ .../specs/map-initialize.spec.ts | 31 ++++++++++ src/member-functions/specs/map-with.spec.ts | 50 ++++++++++++++++ .../specs/null-substitution.spec.ts | 39 ++++++++++++ .../specs/pre-condition.spec.ts | 25 ++++++++ src/types.ts | 20 +++++-- src/utils/get.ts | 2 +- src/utils/isResolver.ts | 4 +- 12 files changed, 367 insertions(+), 7 deletions(-) create mode 100644 src/member-functions/specs/condition.spec.ts create mode 100644 src/member-functions/specs/convert-using.spec.ts create mode 100644 src/member-functions/specs/from-value.spec.ts create mode 100644 src/member-functions/specs/ignore.spec.ts create mode 100644 src/member-functions/specs/map-from.spec.ts create mode 100644 src/member-functions/specs/map-initialize.spec.ts create mode 100644 src/member-functions/specs/map-with.spec.ts create mode 100644 src/member-functions/specs/null-substitution.spec.ts create mode 100644 src/member-functions/specs/pre-condition.spec.ts diff --git a/src/member-functions/specs/condition.spec.ts b/src/member-functions/specs/condition.spec.ts new file mode 100644 index 000000000..2cb91041e --- /dev/null +++ b/src/member-functions/specs/condition.spec.ts @@ -0,0 +1,60 @@ +import { + MemberMapFunctionReturnClassId, + TransformationType, +} from '../../types'; +import { condition } from '../condition'; + +describe('ConditionFunction', () => { + const source = { + truthy: true, + falsy: false, + }; + + it('should return correctly', () => { + const conditionFn = condition(s => s.truthy); + expect(conditionFn).toBeTruthy(); + expect(conditionFn[MemberMapFunctionReturnClassId.type]).toBe( + TransformationType.Condition + ); + expect(conditionFn[MemberMapFunctionReturnClassId.misc]).toBe(null); + expect(conditionFn[MemberMapFunctionReturnClassId.fn]).toBeInstanceOf( + Function + ); + }); + + it('should map to truthy when true', () => { + const conditionFn = condition(s => s.truthy); + const result = conditionFn[MemberMapFunctionReturnClassId.fn]( + source, + 'truthy' + ); + expect(result).toBe(true); + }); + + it('should still map to truthy when true and defaultValue is provided', () => { + const conditionFn = condition(s => s.truthy, 'defaultValue'); + const result = conditionFn[MemberMapFunctionReturnClassId.fn]( + source, + 'truthy' + ); + expect(result).toBe(true); + }); + + it('should map to null when false', () => { + const conditionFn = condition(s => s.falsy); + const result = conditionFn[MemberMapFunctionReturnClassId.fn]( + source, + 'falsy' + ); + expect(result).toBe(null); + }); + + it('should map to defaultValue when false and provided', () => { + const conditionFn = condition(s => s.falsy, 'defaultValue'); + const result = conditionFn[MemberMapFunctionReturnClassId.fn]( + source, + 'falsy' + ); + expect(result).toBe('defaultValue'); + }); +}); diff --git a/src/member-functions/specs/convert-using.spec.ts b/src/member-functions/specs/convert-using.spec.ts new file mode 100644 index 000000000..957371553 --- /dev/null +++ b/src/member-functions/specs/convert-using.spec.ts @@ -0,0 +1,42 @@ +import { + Converter, + MemberMapFunctionReturnClassId, + TransformationType, +} from '../../types'; +import { convertUsing } from '../convert-using'; + +describe('ConvertUsingFunction', () => { + class DateTimeConverter implements Converter { + convert(source: Date): string { + return source.toDateString(); + } + } + + const source = { + birthday: new Date('10/14/1991'), + }; + + it('should return correctly', () => { + const convertUsingFn = convertUsing( + new DateTimeConverter(), + s => s.birthday + ); + expect(convertUsingFn).toBeTruthy(); + expect(convertUsingFn[MemberMapFunctionReturnClassId.type]).toBe( + TransformationType.ConvertUsing + ); + expect(convertUsingFn[MemberMapFunctionReturnClassId.misc]).toBe(null); + expect(convertUsingFn[MemberMapFunctionReturnClassId.fn]).toBeInstanceOf( + Function + ); + }); + + it('should map correctly', () => { + const convertUsingFn = convertUsing( + new DateTimeConverter(), + s => s.birthday + ); + const result = convertUsingFn[MemberMapFunctionReturnClassId.fn](source); + expect(result).toBe(new Date('10/14/1991').toDateString()); + }); +}); diff --git a/src/member-functions/specs/from-value.spec.ts b/src/member-functions/specs/from-value.spec.ts new file mode 100644 index 000000000..efd9d1620 --- /dev/null +++ b/src/member-functions/specs/from-value.spec.ts @@ -0,0 +1,25 @@ +import { + MemberMapFunctionReturnClassId, + TransformationType, +} from '../../types'; +import { fromValue } from '../from-value'; + +describe('FromValueFunction', () => { + it('should return correctly', () => { + const fromValueFunction = fromValue(10); + expect(fromValueFunction).toBeTruthy(); + expect(fromValueFunction[MemberMapFunctionReturnClassId.type]).toBe( + TransformationType.FromValue + ); + expect(fromValueFunction[MemberMapFunctionReturnClassId.misc]).toBe(null); + expect(fromValueFunction[MemberMapFunctionReturnClassId.fn]).toBeInstanceOf( + Function + ); + }); + + it('should map correctly', () => { + const fromValueFunction = fromValue(10); + const result = fromValueFunction[MemberMapFunctionReturnClassId.fn](); + expect(result).toBe(10); + }); +}); diff --git a/src/member-functions/specs/ignore.spec.ts b/src/member-functions/specs/ignore.spec.ts new file mode 100644 index 000000000..0e63dffba --- /dev/null +++ b/src/member-functions/specs/ignore.spec.ts @@ -0,0 +1,24 @@ +import { + MemberMapFunctionReturnClassId, + TransformationType, +} from '../../types'; +import { ignore } from '../ignore'; + +describe('IgnoreFunction', () => { + it('should return correctly', () => { + const ignoreFn = ignore(); + expect(ignoreFn).toBeTruthy(); + expect(ignoreFn[MemberMapFunctionReturnClassId.type]).toBe( + TransformationType.Ignore + ); + expect(ignoreFn[MemberMapFunctionReturnClassId.misc]).toBe(null); + expect(ignoreFn[MemberMapFunctionReturnClassId.fn]).toBeInstanceOf( + Function + ); + }); + + it('should return undefined when called', () => { + const ignoreFn = ignore(); + expect(ignoreFn[MemberMapFunctionReturnClassId.fn]()).toBe(undefined); + }); +}); diff --git a/src/member-functions/specs/map-from.spec.ts b/src/member-functions/specs/map-from.spec.ts new file mode 100644 index 000000000..6eb64fc24 --- /dev/null +++ b/src/member-functions/specs/map-from.spec.ts @@ -0,0 +1,52 @@ +import { + MemberMapFunctionReturnClassId, + Resolver, + TransformationType, +} from '../../types'; +import { mapFrom } from '../map-from'; + +describe('MapFromFunction', () => { + const source = { + foo: 'bar', + }; + + const sourceSelector = (s: typeof source) => s.foo; + + it('should return correctly', () => { + const mapFromFn = mapFrom(sourceSelector); + expect(mapFromFn).toBeTruthy(); + expect(mapFromFn[MemberMapFunctionReturnClassId.type]).toBe( + TransformationType.MapFrom + ); + expect(mapFromFn[MemberMapFunctionReturnClassId.misc]).toBe(sourceSelector); + expect(mapFromFn[MemberMapFunctionReturnClassId.fn]).toBeInstanceOf( + Function + ); + }); + + it('should map to foo correctly', () => { + const mapFromFn = mapFrom(sourceSelector); + const result = mapFromFn[MemberMapFunctionReturnClassId.fn]( + source, + null, + null + ); + expect(result).toBe('bar'); + }); + + class FooResolver implements Resolver { + resolve(source: any) { + return source.foo; + } + } + + it('should use resolver correctly', () => { + const mapFromFn = mapFrom(new FooResolver()); + const result = mapFromFn[MemberMapFunctionReturnClassId.fn]( + source, + null, + null + ); + expect(result).toBe('bar'); + }); +}); diff --git a/src/member-functions/specs/map-initialize.spec.ts b/src/member-functions/specs/map-initialize.spec.ts new file mode 100644 index 000000000..6ae0fa623 --- /dev/null +++ b/src/member-functions/specs/map-initialize.spec.ts @@ -0,0 +1,31 @@ +import { + MemberMapFunctionReturnClassId, + TransformationType, +} from '../../types'; +import { mapInitialize } from '../map-initialize'; + +describe('MapInitializeFunction', () => { + const source = { + foo: { + bar: 'foo', + }, + }; + + it('should return correctly', () => { + const mapInitFn = mapInitialize(); + expect(mapInitFn).toBeTruthy(); + expect(mapInitFn[MemberMapFunctionReturnClassId.type]).toBe( + TransformationType.MapInitialize + ); + expect(mapInitFn[MemberMapFunctionReturnClassId.misc]).toBe(null); + expect(mapInitFn[MemberMapFunctionReturnClassId.fn]).toBeInstanceOf( + Function + ); + }); + + it('should map correctly', () => { + const mapInitFn = mapInitialize('foo.bar'); + const result = mapInitFn[MemberMapFunctionReturnClassId.fn](source); + expect(result).toBe('foo'); + }); +}); diff --git a/src/member-functions/specs/map-with.spec.ts b/src/member-functions/specs/map-with.spec.ts new file mode 100644 index 000000000..2b520d840 --- /dev/null +++ b/src/member-functions/specs/map-with.spec.ts @@ -0,0 +1,50 @@ +import { Mapper } from '../../automapper'; +import { + MemberMapFunctionReturnClassId, + TransformationType, +} from '../../types'; +import { mapFrom } from '../map-from'; +import { mapWith } from '../map-with'; + +describe('MapWithFunction', () => { + class Bar { + bar!: string; + } + + const sourceSelector = (s: any) => s.foo; + + it('should return correctly', () => { + const mapWithFn = mapWith(Bar, sourceSelector); + expect(mapWithFn).toBeTruthy(); + expect(mapWithFn[MemberMapFunctionReturnClassId.type]).toBe( + TransformationType.MapWith + ); + expect(mapWithFn[MemberMapFunctionReturnClassId.misc]).toBe(sourceSelector); + expect(mapWithFn[MemberMapFunctionReturnClassId.fn]).toBeInstanceOf( + Function + ); + }); + + class Foo { + foo!: string; + } + + it('should map correctly', () => { + Mapper.createMap(Foo, Bar).forMember( + d => d.bar, + mapFrom(s => s.foo) + ); + + const mapWithFn = mapWith(Bar, sourceSelector); + const foo = new Foo(); + foo.foo = 'bar'; + const result = mapWithFn[MemberMapFunctionReturnClassId.fn]({ foo }); + expect(result).toBeTruthy(); + expect(result).toBeInstanceOf(Bar); + expect(result?.bar).toBe(foo.foo); + }); + + afterAll(() => { + Mapper.dispose(); + }); +}); diff --git a/src/member-functions/specs/null-substitution.spec.ts b/src/member-functions/specs/null-substitution.spec.ts new file mode 100644 index 000000000..085d38833 --- /dev/null +++ b/src/member-functions/specs/null-substitution.spec.ts @@ -0,0 +1,39 @@ +import { + MemberMapFunctionReturnClassId, + TransformationType, +} from '../../types'; +import { nullSubstitution } from '../null-substitution'; + +describe('NullSubstitutionFunction', () => { + it('should return correctly', () => { + const nullSubFn = nullSubstitution(''); + expect(nullSubFn).toBeTruthy(); + expect(nullSubFn[MemberMapFunctionReturnClassId.type]).toBe( + TransformationType.NullSubstitution + ); + expect(nullSubFn[MemberMapFunctionReturnClassId.misc]).toBe(null); + expect(nullSubFn[MemberMapFunctionReturnClassId.fn]).toBeInstanceOf( + Function + ); + }); + + it('should return source if source is not null', () => { + const nullSubFn = nullSubstitution('subbed'); + const result = nullSubFn[MemberMapFunctionReturnClassId.fn]( + { foo: 'bar' }, + 'foo' + ); + expect(result).toBe('bar'); + expect(result).not.toBe('subbed'); + }); + + it('should return subbed if source is null', () => { + const nullSubFn = nullSubstitution('subbed'); + const result = nullSubFn[MemberMapFunctionReturnClassId.fn]( + { foo: null }, + 'foo' + ); + expect(result).toBe('subbed'); + expect(result).not.toBe(null); + }); +}); diff --git a/src/member-functions/specs/pre-condition.spec.ts b/src/member-functions/specs/pre-condition.spec.ts new file mode 100644 index 000000000..bb5b24810 --- /dev/null +++ b/src/member-functions/specs/pre-condition.spec.ts @@ -0,0 +1,25 @@ +import { preCondition } from '../pre-condition'; + +describe('PreConditionFunction', () => { + it('should return correctly', () => { + let preCondFn = preCondition(s => s.truthy); + expect(preCondFn).toBeTruthy(); + expect(preCondFn[0]).toBeInstanceOf(Function); + expect(preCondFn[1]).toBe(undefined); + + preCondFn = preCondition(s => s.truthy, 'default'); + expect(preCondFn[1]).toBe('default'); + }); + + it('should return truthy when true', () => { + const preCond = preCondition(s => s.truthy); + const result = preCond[0]({ truthy: true }); + expect(result).toBe(true); + }); + + it('should return falsy when false', () => { + const preCond = preCondition(s => s.falsy); + const result = preCond[0]({ falsy: false }); + expect(result).toBe(false); + }); +}); diff --git a/src/types.ts b/src/types.ts index 8bf60a578..395d15557 100644 --- a/src/types.ts +++ b/src/types.ts @@ -81,8 +81,8 @@ export interface NamingConvention { } export interface Resolver< - TSource extends Dict = any, - TDestination extends Dict = any, + TSource extends Dict, + TDestination extends Dict, TReturnType = SelectorReturn > { resolve( @@ -246,8 +246,18 @@ export interface MapFromFunction< ValueSelector, ( source: TSource, - destination: typeof from extends Resolver ? TDestination : any, - transformation: typeof from extends Resolver + destination: typeof from extends Resolver< + TSource, + TDestination, + TSelectorReturn + > + ? TDestination + : any, + transformation: typeof from extends Resolver< + TSource, + TDestination, + TSelectorReturn + > ? MappingTransformation : any ) => TSelectorReturn @@ -289,7 +299,7 @@ export interface FromValueFunction< (rawValue: TSelectorReturn): [ TransformationType.FromValue, null, - (source: TSource) => TSelectorReturn + () => TSelectorReturn ]; } diff --git a/src/utils/get.ts b/src/utils/get.ts index bc987f7f4..a822bd7d0 100644 --- a/src/utils/get.ts +++ b/src/utils/get.ts @@ -6,7 +6,7 @@ export function get( function _getInternal(object: T, path: string) { const _path = path.split('.').filter(Boolean); const _val = _path.reduce((obj: any, key) => obj && obj[key], object); - return _val === undefined ? defaultVal : _val; + return _val == undefined ? defaultVal : _val; } let val = _getInternal(object, paths[0]); diff --git a/src/utils/isResolver.ts b/src/utils/isResolver.ts index 6b5df1b0d..1299599c9 100644 --- a/src/utils/isResolver.ts +++ b/src/utils/isResolver.ts @@ -1,5 +1,7 @@ import { Resolver, ValueSelector } from '../types'; -export function isResolver(fn: ValueSelector | Resolver): fn is Resolver { +export function isResolver( + fn: ValueSelector | Resolver +): fn is Resolver { return 'resolve' in fn; }