diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index cc3c3486d..9c7be5b38 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -18,6 +18,8 @@ export * from './lib/mapping-configurations/type-converters'; export * from './lib/mapping-configurations/construct-using'; export * from './lib/mapping-configurations/before-map'; export * from './lib/mapping-configurations/after-map'; +export * from './lib/mapping-configurations/before-map-array'; +export * from './lib/mapping-configurations/after-map-array'; export * from './lib/mapping-configurations/extend'; export * from './lib/mapping-configurations/naming-conventions'; export * from './lib/mapping-configurations/auto-map'; diff --git a/packages/core/src/lib/core.ts b/packages/core/src/lib/core.ts index 041d55f19..e92ae72f7 100644 --- a/packages/core/src/lib/core.ts +++ b/packages/core/src/lib/core.ts @@ -26,6 +26,7 @@ import type { ModelIdentifier, NamingConventionInput, } from './types'; +import { getBeforeAndAfterMap } from './utils/get-callbacks'; import { getMapping } from './utils/get-mapping'; import { AutoMapperLogger } from './utils/logger'; @@ -314,13 +315,18 @@ Mapper {} is an empty Object as a Proxy. The following methods are available to ); const { beforeMap, afterMap, extraArgs } = - (mapOptions || {}) as MapOptions< - TSource[], - TDestination[] - >; + getBeforeAndAfterMap( + mapping, + (mapOptions || {}) as MapOptions< + TSource[], + TDestination[] + > + ); + + const extraArguments = extraArgs?.(mapping, []); if (beforeMap) { - beforeMap(sourceArray, []); + beforeMap(sourceArray, [], extraArguments); } const destinationArray: TDestination[] = []; @@ -361,7 +367,7 @@ Mapper {} is an empty Object as a Proxy. The following methods are available to } if (afterMap) { - afterMap(sourceArray, destinationArray); + afterMap(sourceArray, destinationArray, extraArguments); } return destinationArray; @@ -492,13 +498,18 @@ Mapper {} is an empty Object as a Proxy. The following methods are available to ); const { beforeMap, afterMap, extraArgs } = - (mapOptions || {}) as MapOptions< - TSource[], - TDestination[] - >; + getBeforeAndAfterMap( + mapping, + (mapOptions || {}) as MapOptions< + TSource[], + TDestination[] + > + ); + + const extraArguments = extraArgs?.(mapping, destinationArray); if (beforeMap) { - beforeMap(sourceArray, destinationArray); + beforeMap(sourceArray, destinationArray, extraArguments); } for ( @@ -534,7 +545,7 @@ Mapper {} is an empty Object as a Proxy. The following methods are available to } if (afterMap) { - afterMap(sourceArray, destinationArray); + afterMap(sourceArray, destinationArray, extraArguments); } }; } diff --git a/packages/core/src/lib/mapping-configurations/after-map-array.ts b/packages/core/src/lib/mapping-configurations/after-map-array.ts new file mode 100644 index 000000000..868340fb8 --- /dev/null +++ b/packages/core/src/lib/mapping-configurations/after-map-array.ts @@ -0,0 +1,17 @@ +import type { Dictionary, MapCallback, MappingConfiguration } from '../types'; +import { MappingCallbacksClassId, MappingClassId } from '../types'; + +export function afterMapArray< + TSource extends Dictionary, + TDestination extends Dictionary +>( + cb: MapCallback +): MappingConfiguration { + return (mapping) => { + if (!mapping[MappingClassId.callbacks]) { + mapping[MappingClassId.callbacks] = []; + } + mapping[MappingClassId.callbacks][MappingCallbacksClassId.afterMapArray] = + cb; + }; +} diff --git a/packages/core/src/lib/mapping-configurations/before-map-array.ts b/packages/core/src/lib/mapping-configurations/before-map-array.ts new file mode 100644 index 000000000..85d8b64b8 --- /dev/null +++ b/packages/core/src/lib/mapping-configurations/before-map-array.ts @@ -0,0 +1,17 @@ +import type { Dictionary, MapCallback, MappingConfiguration } from '../types'; +import { MappingCallbacksClassId, MappingClassId } from '../types'; + +export function beforeMapArray< + TSource extends Dictionary, + TDestination extends Dictionary +>( + cb: MapCallback +): MappingConfiguration { + return (mapping) => { + if (!mapping[MappingClassId.callbacks]) { + mapping[MappingClassId.callbacks] = []; + } + mapping[MappingClassId.callbacks][MappingCallbacksClassId.beforeMapArray] = + cb; + }; +} diff --git a/packages/core/src/lib/mapping-configurations/specs/after-map-array.spec.ts b/packages/core/src/lib/mapping-configurations/specs/after-map-array.spec.ts new file mode 100644 index 000000000..7ee25ef32 --- /dev/null +++ b/packages/core/src/lib/mapping-configurations/specs/after-map-array.spec.ts @@ -0,0 +1,14 @@ +import type { Mapping } from '../../types'; +import { MappingCallbacksClassId, MappingClassId } from '../../types'; +import { afterMapArray } from '../after-map-array'; + +describe(afterMapArray.name, () => { + it('should update mapping configuration with afterMapArray', () => { + const mapping = [] as unknown as Mapping; + const cb = jest.fn(); + afterMapArray(cb)(mapping); + expect( + mapping[MappingClassId.callbacks]![MappingCallbacksClassId.afterMapArray] + ).toBe(cb); + }); +}); diff --git a/packages/core/src/lib/mapping-configurations/specs/before-map-array.spec.ts b/packages/core/src/lib/mapping-configurations/specs/before-map-array.spec.ts new file mode 100644 index 000000000..b8f1e5972 --- /dev/null +++ b/packages/core/src/lib/mapping-configurations/specs/before-map-array.spec.ts @@ -0,0 +1,14 @@ +import type { Mapping } from '../../types'; +import { MappingCallbacksClassId, MappingClassId } from '../../types'; +import { beforeMapArray } from '../before-map-array'; + +describe(beforeMapArray.name, () => { + it('should update mapping configuration with beforeMapArray', () => { + const mapping = [] as unknown as Mapping; + const cb = jest.fn(); + beforeMapArray(cb)(mapping); + expect( + mapping[MappingClassId.callbacks]![MappingCallbacksClassId.beforeMapArray] + ).toBe(cb); + }); +}); diff --git a/packages/core/src/lib/mappings/map.ts b/packages/core/src/lib/mappings/map.ts index be55049cd..cc50bf043 100644 --- a/packages/core/src/lib/mappings/map.ts +++ b/packages/core/src/lib/mappings/map.ts @@ -10,6 +10,7 @@ import type { import { MapFnClassId, MetadataClassId, TransformationType } from '../types'; import { assertUnmappedProperties } from '../utils/assert-unmapped-properties'; import { get } from '../utils/get'; +import { getBeforeAndAfterMap } from '../utils/get-callbacks'; import { getMapping } from '../utils/get-mapping'; import { isDateConstructor } from '../utils/is-date-constructor'; import { isEmpty } from '../utils/is-empty'; @@ -116,17 +117,12 @@ export function map< , mapper, destinationConstructor, - , - [mappingBeforeCallback, mappingAfterCallback] = [], ] = mapping; // deconstruct MapOptions const { - beforeMap: mapBeforeCallback, - afterMap: mapAfterCallback, destinationConstructor: mapDestinationConstructor = destinationConstructor, - extraArgs, } = options ?? {}; const errorHandler = getErrorHandler(mapper); @@ -137,6 +133,12 @@ export function map< destinationIdentifier ); + const { beforeMap, afterMap, extraArgs } = + getBeforeAndAfterMap( + mapping, + options || {} + ); + // get extraArguments const extraArguments = extraArgs?.(mapping, destination); @@ -144,7 +146,6 @@ export function map< const configuredKeys: string[] = []; if (!isMapArray) { - const beforeMap = mapBeforeCallback ?? mappingBeforeCallback; if (beforeMap) { beforeMap(sourceObject, destination, extraArguments); } @@ -354,7 +355,6 @@ Original error: ${originalError}`; } if (!isMapArray) { - const afterMap = mapAfterCallback ?? mappingAfterCallback; if (afterMap) { afterMap(sourceObject, destination, extraArguments); } diff --git a/packages/core/src/lib/types.ts b/packages/core/src/lib/types.ts index 8fcbe8ebe..7358dcd1c 100644 --- a/packages/core/src/lib/types.ts +++ b/packages/core/src/lib/types.ts @@ -486,6 +486,8 @@ export const enum MappingPropertiesClassId { export const enum MappingCallbacksClassId { beforeMap, afterMap, + beforeMapArray, + afterMapArray, } export const enum NestedMappingPairClassId { @@ -564,7 +566,9 @@ export type Mapping< >, callbacks?: [ beforeMap?: MapCallback, - afterMap?: MapCallback + afterMap?: MapCallback, + beforeMapArray?: MapCallback, + afterMapArray?: MapCallback ], namingConventions?: [ source: NamingConvention, diff --git a/packages/core/src/lib/utils/get-callbacks.ts b/packages/core/src/lib/utils/get-callbacks.ts new file mode 100644 index 000000000..80931ef87 --- /dev/null +++ b/packages/core/src/lib/utils/get-callbacks.ts @@ -0,0 +1,22 @@ +import { Dictionary, MapOptions, Mapping, MappingCallbacksClassId, MappingClassId } from "../types"; + +export function getBeforeAndAfterMap< + TSource extends Dictionary, + TDestination extends Dictionary +>( + mapping: Mapping, + mapOptions?: MapOptions +): Pick, 'beforeMap' | 'afterMap' | 'extraArgs'> { + const callbacks = mapping?.[MappingClassId.callbacks]; + const beforeMapCfg = callbacks?.[MappingCallbacksClassId.beforeMap]; + const afterMapCfg = callbacks?.[MappingCallbacksClassId.afterMap]; + + const { beforeMap: beforeMapCb, afterMap: afterMapCb, extraArgs } = + mapOptions || {}; + + return { + beforeMap: beforeMapCb || beforeMapCfg, + afterMap: afterMapCb || afterMapCfg, + extraArgs, + }; +} diff --git a/packages/documentations/docs/api/core/enums/MappingCallbacksClassId.md b/packages/documentations/docs/api/core/enums/MappingCallbacksClassId.md index 12fa1cc74..0c6e9fcc6 100644 --- a/packages/documentations/docs/api/core/enums/MappingCallbacksClassId.md +++ b/packages/documentations/docs/api/core/enums/MappingCallbacksClassId.md @@ -8,20 +8,40 @@ custom_edit_url: null ## Enumeration members +### beforeMap + +• **beforeMap** = `0` + +#### Defined in + +[lib/types.ts:487](https://github.com/nartc/mapper/blob/9d18866/packages/core/src/lib/types.ts#L487) + +___ + ### afterMap • **afterMap** = `1` #### Defined in -[lib/types.ts:483](https://github.com/nartc/mapper/blob/efc4cb9d/packages/core/src/lib/types.ts#L483) +[lib/types.ts:488](https://github.com/nartc/mapper/blob/9d18866/packages/core/src/lib/types.ts#L488) ___ -### beforeMap +### beforeMapArray -• **beforeMap** = `0` +• **beforeMapArray** = `2` + +#### Defined in + +[lib/types.ts:489](https://github.com/nartc/mapper/blob/9d18866/packages/core/src/lib/types.ts#L489) + +___ + +### afterMapArray + +• **afterMapArray** = `3` #### Defined in -[lib/types.ts:482](https://github.com/nartc/mapper/blob/efc4cb9d/packages/core/src/lib/types.ts#L482) +[lib/types.ts:490](https://github.com/nartc/mapper/blob/9d18866/packages/core/src/lib/types.ts#L490) diff --git a/packages/documentations/docs/mapping-configuration/after-map-array.mdx b/packages/documentations/docs/mapping-configuration/after-map-array.mdx new file mode 100644 index 000000000..1d7d48c85 --- /dev/null +++ b/packages/documentations/docs/mapping-configuration/after-map-array.mdx @@ -0,0 +1,80 @@ +--- +id: after-map-array +title: AfterMapArray +sidebar_label: AfterMapArray +sidebar_position: 3 +--- + +As the name suggests, `afterMapArray()` sets up a `MapCallback` to be called **after** the mapArray operation. + +## Configure on `Mapping` + +Pass `afterMapArray()` in `createMap()` to sets up the `MapCallback` + +```ts +createMap( + mapper, + User, + UserDto, + afterMapArray((sources, destinations) => { + // destinations.map((destination, index) => Object.assign(destination, { prop: sources[index].prop })); + }) +); +``` + +## Configure on `mapArray()` + +Pass `afterMap` in `MapOptions` when calling `mapArray()` to sets up the `MapCallback` + +```ts +mapper.mapArray(user, User, UserDto, { + afterMap: (sources, destinations) => {}, +}); +``` + +:::info + +- `afterMap()` on `mapArray()` has precedence over `afterMapArray` on `Mapping` +- both `afterMap()` on `mapArray()` and `afterMapArray()` on `Mapping` will be invoked with `(sourceArray, destinationArray)` parameters + +::: + +## Async Mapping + +One of the common use-cases of `afterMapArray` is to execute some asynchronous operation. Let's assume our `Destinations` have some property whose value can only be computed from an asynchronous operation, we can leverage `mapArrayAsync()` and `afterMapArray()` for it. + +```ts +createMap( + mapper, + User, + UserDto, + // 👇 We are fetching the "fullName" manually + // 👇 👇 so we need to ignore it + forMember((d) => d.fullName, ignore()), + afterMapArray(async (sources, destinations) => { + await Promise.all( + sources.map(async (source, index) => { + const fullName = await fetchFullName(source); + Object.assign(destinations[index], { fullName }); + }) + ); + }) +); + +// 👇 mapArrayAsync is needed if we use the above "trick" with afterMapArray +const dto = await mapper.mapArrayAsync([user], User, UserDto); +``` + +:::caution + +Simple asynchronous operations should be fine with this approach. However due to [Fake Async](../misc/fake-async), we should **NOT** use AutoMapper for a particular pair of models if those models require some heavy and complex asynchronous operations. + +::: + +## What about `postMap`? + +When create the `Mapper`, we can customize the `postMap` function on the `MappingStrategy`. The differences between `postMap` and `afterMapArray` are: + +- `postMap` runs after every **map** operation +- There is only one `postMap` per `Mapper` +- `postMap` runs **BEFORE** `afterMapArray` diff --git a/packages/documentations/docs/mapping-configuration/after-map.mdx b/packages/documentations/docs/mapping-configuration/after-map.mdx index 07490bbcd..11331252d 100644 --- a/packages/documentations/docs/mapping-configuration/after-map.mdx +++ b/packages/documentations/docs/mapping-configuration/after-map.mdx @@ -33,7 +33,7 @@ mapper.map(user, User, UserDto, { :::info - `afterMap()` on `map()` has precedence over `Mapping` -- For `mapArray` (and its variants), `afterMap()` on `Mapping` is **ignored** because it would be bad for performance if we run `afterMap` for each and every item of the array. `afterMap()` on `mapArray()` will be invoked with `(sourceArray, destinationArray)` instead +- For `mapArray` (and its variants), `afterMap()` on `Mapping` is **ignored** because it would be bad for performance if we run `afterMap` for each and every item of the array. Use `afterMapArray()` on `Mapping` instead and it will be invoked with `(sourceArray, destinationArray)` params ::: diff --git a/packages/documentations/docs/mapping-configuration/auto-map.mdx b/packages/documentations/docs/mapping-configuration/auto-map.mdx index c0abdbfbb..d2a89a822 100644 --- a/packages/documentations/docs/mapping-configuration/auto-map.mdx +++ b/packages/documentations/docs/mapping-configuration/auto-map.mdx @@ -2,7 +2,7 @@ id: auto-map title: AutoMap sidebar_label: AutoMap -sidebar_position: 3 +sidebar_position: 4 --- `autoMap()` is an alternative to the the `@AutoMap()` decorator. It trivially maps a property with the **same name and type** on the `Source` and `Destination` objects. diff --git a/packages/documentations/docs/mapping-configuration/before-map-array.mdx b/packages/documentations/docs/mapping-configuration/before-map-array.mdx new file mode 100644 index 000000000..ebe63ab2a --- /dev/null +++ b/packages/documentations/docs/mapping-configuration/before-map-array.mdx @@ -0,0 +1,46 @@ +--- +id: before-map-array +title: BeforeMapArray +sidebar_label: BeforeMapArray +sidebar_position: 6 +--- + +As the name suggests, `beforeMapArray()` sets up a `MapCallback` to be called **before** the map array operation. + +## Configure on `Mapping` + +Pass `beforeMapArray()` in `createMap()` to sets up the `MapCallback` + +```ts +createMap( + mapper, + User, + UserDto, + beforeMapArray((sources, destinations) => {}) +); +``` + +## Configure on `mapArray()` + +Pass `beforeMap` in `MapOptions` when calling `mapArray()` to sets up the `MapCallback` + +```ts +mapper.mapArray([user], User, UserDto, { + beforeMap: (sources, destinations) => {}, +}); +``` + +:::info + +- `beforeMapArray()` on `mapArray()` has precedence over `Mapping` +- `beforeMapArray()` on `mapArray()` will be invoked with `(sourceArray, [])` parameters + +::: + +## What about `preMap`? + +When create the `Mapper`, we can customize the `preMap` function on the `MappingStrategy`. The differences between `preMap` and `beforeMapArray` are: + +- `preMap` runs before every **map** operation +- There is only one `preMap` per `Mapper` +- `preMap` runs **AFTER** `beforeMapArray` diff --git a/packages/documentations/docs/mapping-configuration/before-map.mdx b/packages/documentations/docs/mapping-configuration/before-map.mdx index 86cd2981b..7580399b7 100644 --- a/packages/documentations/docs/mapping-configuration/before-map.mdx +++ b/packages/documentations/docs/mapping-configuration/before-map.mdx @@ -2,7 +2,7 @@ id: before-map title: BeforeMap sidebar_label: BeforeMap -sidebar_position: 4 +sidebar_position: 5 --- As the name suggests, `beforeMap()` sets up a `MapCallback` to be called **before** the map operation. @@ -33,7 +33,7 @@ mapper.map(user, User, UserDto, { :::info - `beforeMap()` on `map()` has precedence over `Mapping` -- For `mapArray` (and its variants), `beforeMap()` on `Mapping` is **ignored** because it would be bad for performance if we run `beforeMap` for each and every item of the array. `beforeMap()` on `mapArray()` will be invoked with `(sourceArray, [])` instead +- For `mapArray` (and its variants), `beforeMap()` on `Mapping` is **ignored** because it would be bad for performance if we run `beforeMap` for each and every item of the array. Use `beforeMapArray()` on `Mapping` instead and it will be invoked with `(sourceArray, [])` params ::: diff --git a/packages/documentations/docs/mapping-configuration/construct-using.mdx b/packages/documentations/docs/mapping-configuration/construct-using.mdx index 727e09ea2..51e4a0c6e 100644 --- a/packages/documentations/docs/mapping-configuration/construct-using.mdx +++ b/packages/documentations/docs/mapping-configuration/construct-using.mdx @@ -2,7 +2,7 @@ id: construct-using title: ConstructUsing sidebar_label: ConstructUsing -sidebar_position: 5 +sidebar_position: 7 --- Call `constructUsing()` and pass in a `DestinationConstructor` to customize how AutoMapper should construct the `Destination` before every map operation against that `Destination`. diff --git a/packages/documentations/docs/mapping-configuration/extend.mdx b/packages/documentations/docs/mapping-configuration/extend.mdx index 4d823efd5..33847d323 100644 --- a/packages/documentations/docs/mapping-configuration/extend.mdx +++ b/packages/documentations/docs/mapping-configuration/extend.mdx @@ -2,7 +2,7 @@ id: extend title: Extend sidebar_label: Extend -sidebar_position: 6 +sidebar_position: 8 --- Call `extend()` and pass in either a `Mapping` or a pair of models to tell AutoMapper to extend the `MappingProperties` to the `Mapping` we are creating diff --git a/packages/documentations/docs/mapping-configuration/for-self.mdx b/packages/documentations/docs/mapping-configuration/for-self.mdx index b43d05d1e..728a441d9 100644 --- a/packages/documentations/docs/mapping-configuration/for-self.mdx +++ b/packages/documentations/docs/mapping-configuration/for-self.mdx @@ -2,7 +2,7 @@ id: for-self title: ForSelf sidebar_label: ForSelf -sidebar_position: 8 +sidebar_position: 10 --- In previous sections, we've learned that we can have [Auto Flattening](../fundamentals/auto-flattening) with [Naming Conventions](../fundamentals/naming-convention). diff --git a/packages/documentations/docs/mapping-configuration/naming-conventions.mdx b/packages/documentations/docs/mapping-configuration/naming-conventions.mdx index 72d340ee7..8dc5f947d 100644 --- a/packages/documentations/docs/mapping-configuration/naming-conventions.mdx +++ b/packages/documentations/docs/mapping-configuration/naming-conventions.mdx @@ -2,7 +2,7 @@ id: naming-conventions title: NamingConventions sidebar_label: NamingConventions -sidebar_position: 9 +sidebar_position: 11 --- Call `namingConventions()` and pass in a `NamingConventionInput` to customize the Mapping's [NamingConvention](../fundamentals/naming-convention) diff --git a/packages/documentations/docs/mapping-configuration/type-converters.mdx b/packages/documentations/docs/mapping-configuration/type-converters.mdx index 1a14063e0..e07ad22a4 100644 --- a/packages/documentations/docs/mapping-configuration/type-converters.mdx +++ b/packages/documentations/docs/mapping-configuration/type-converters.mdx @@ -2,7 +2,7 @@ id: type-converters title: TypeConverters sidebar_label: TypeConverters -sidebar_position: 10 +sidebar_position: 12 --- ## What is Type Converter? diff --git a/packages/documentations/sidebars.js b/packages/documentations/sidebars.js index 584ec6ba5..3defa82ca 100644 --- a/packages/documentations/sidebars.js +++ b/packages/documentations/sidebars.js @@ -42,8 +42,10 @@ const sidebars = { items: [ 'mapping-configuration/overview', 'mapping-configuration/after-map', + 'mapping-configuration/after-map-array', 'mapping-configuration/auto-map', 'mapping-configuration/before-map', + 'mapping-configuration/before-map-array', 'mapping-configuration/construct-using', 'mapping-configuration/extend', { diff --git a/packages/integration-test/src/classes/map-array.spec.ts b/packages/integration-test/src/classes/map-array.spec.ts new file mode 100644 index 000000000..dd4338079 --- /dev/null +++ b/packages/integration-test/src/classes/map-array.spec.ts @@ -0,0 +1,100 @@ +import { classes } from '@automapper/classes'; +import { afterMap, afterMapArray, createMap, createMapper, forMember, ignore, mapFrom } from '@automapper/core'; +import { SimpleUserDto } from './dtos/simple-user.dto'; +import { SimpleUser } from './models/simple-user'; + +describe('Map Array Classes', () => { + const mapper = createMapper({ strategyInitializer: classes() }); + + afterEach(() => { + mapper.dispose(); + }); + + it('should map', () => { + createMap( + mapper, + SimpleUser, + SimpleUserDto, + forMember( + (d) => d.fullName, + mapFrom((s) => s.firstName + ' ' + s.lastName) + ) + ); + + const user = new SimpleUser('Chau', 'Tran'); + + const dto = mapper.map(user, SimpleUser, SimpleUserDto); + expect(dto.fullName).toEqual('Chau Tran'); + }); + + // afterMap + + it('should map with afterMap', () => { + createMap( + mapper, + SimpleUser, + SimpleUserDto, + forMember( + (d) => d.fullName, + ignore() + ), + afterMap((s, d) => { + d.fullName = s.firstName + ' ' + s.lastName; + }) + ); + + const user = new SimpleUser('Chau', 'Tran'); + + const dto = mapper.map(user, SimpleUser, SimpleUserDto); + expect(dto.fullName).toEqual('Chau Tran'); + }); + + it('should not map array async with afterMap', async () => { + createMap( + mapper, + SimpleUser, + SimpleUserDto, + forMember( + (d) => d.fullName, + ignore() + ), + afterMap(async (s, d) => { + d.fullName = s.firstName + ' ' + s.lastName; + }) + ); + + const user = new SimpleUser('Chau', 'Tran'); + + const dtos = await mapper.mapArrayAsync([user], SimpleUser, SimpleUserDto); + + expect(dtos.length).toEqual(1); + expect(dtos[0].fullName).toEqual(undefined); // not mapped on mapArrayAsync + }); + + // afterMapArray + + it('should map with afterMapArray', () => { + createMap( + mapper, + SimpleUser, + SimpleUserDto, + forMember( + (d) => d.fullName, + ignore() + ), + afterMapArray((sources, destinations) => { + destinations.forEach((destination, index) => { + const source = sources[index]; + destination.fullName = source.firstName + ' ' + source.lastName; + }); + }) + ); + + const user = new SimpleUser('Chau', 'Tran'); + + const dtos = mapper.mapArray([user], SimpleUser, SimpleUserDto); + + expect(dtos.length).toEqual(1); + expect(dtos[0].fullName).toEqual('Chau Tran'); + }); +});