diff --git a/packages/core/src/effects/effects.combiner.spec.ts b/packages/core/src/effects/effects.combiner.spec.ts index f0150a02..863ee2c5 100644 --- a/packages/core/src/effects/effects.combiner.spec.ts +++ b/packages/core/src/effects/effects.combiner.spec.ts @@ -1,4 +1,4 @@ -import { mapTo, tap } from 'rxjs/operators'; +import { map, mapTo, tap } from 'rxjs/operators'; import { Marbles } from '../../../util/marbles.spec-util'; import { HttpRequest, HttpResponse } from '../http.interface'; import { combineEffects, combineMiddlewareEffects, combineRoutes } from './effects.combiner'; @@ -50,12 +50,11 @@ describe('Effects combiner', () => { const c$: Effect = request$ => request$.pipe(mapTo({ status: 203 })); const d$: Effect = request$ => request$.pipe(mapTo({ status: 204 })); - const group1$: GroupedEffects = { path: '/test', effects: [c$, d$] }; - const group2$: GroupedEffects = { path: '/test/foo', effects: [c$, d$] }; + const group1$: GroupedEffects = { path: '/test', effects: [c$, d$], middlewares: [] }; const req = createMockReq('/test'); const res = createMockRes(); - const combinedEffects = combineEffects([ a$, b$, group1$, group2$ ]); + const combinedEffects = combineEffects([ a$, b$, group1$ ]); const http$ = combinedEffects(res)(req); Marbles.assertCombinedEffects(http$, [ @@ -68,6 +67,36 @@ describe('Effects combiner', () => { ]); }); + it('#combineEffects combines effects for grouped effects with middlewares', () => { + // given + const a$: Effect = request$ => request$.pipe(map(req => ({ status: 201, mid: req.mid }))); + const b$: Effect = request$ => request$.pipe(map(req => ({ status: 202, mid: req.mid }))); + const c$: Effect = request$ => request$.pipe(map(req => ({ status: 203, mid: req.mid }))); + const d$: Effect = request$ => request$.pipe(map(req => ({ status: 204, mid: req.mid }))); + + const m$: Effect = request$ => request$.pipe(tap(req => req.mid = (req.mid || 0) + 1)); + + const group1$: GroupedEffects = { path: '/test', effects: [c$, d$], middlewares: [m$, m$] }; + + // when + const req = createMockReq('/test/foo'); + const res = createMockRes(); + const combinedEffects = combineEffects([ a$, b$, group1$, c$, d$ ]); + const http$ = combinedEffects(res)(req); + + // then + Marbles.assertCombinedEffects(http$, [ + '(abcdef|)', { + a: { status: 201, mid: undefined }, + b: { status: 202, mid: undefined }, + c: { status: 203, mid: 2 }, + d: { status: 204, mid: 2 }, + e: { status: 203, mid: 2 }, + f: { status: 204, mid: 2 }, + } + ]); + }); + it('#combineMiddlewareEffects combines chained middlewares', () => { const a$: Effect = request$ => request$.pipe(tap(req => { req.test = 1; })); const b$: Effect = request$ => request$.pipe(tap(req => { req.test = req.test + 1; })); @@ -85,13 +114,59 @@ describe('Effects combiner', () => { ]); }); - it('#combineRoutes factorizes route combiner', () => { + it('#combineRoutes factorizes combined routes for effects only', () => { + // given const a$: Effect = request$ => request$.pipe(mapTo({})); const b$: Effect = request$ => request$.pipe(mapTo({})); - expect(combineRoutes('/test', [a$, b$])).toEqual({ + // when + const combiner = combineRoutes('/test', [a$, b$]); + + // then + expect(combiner).toEqual({ + path: '/test', + effects: [a$, b$], + middlewares: [], + }); + }); + + it('#combineRoutes factorizes combined routes for effects and middlewares', () => { + // given + const a$: Effect = request$ => request$.pipe(mapTo({})); + const b$: Effect = request$ => request$.pipe(mapTo({})); + + const m1$: Effect = request$ => request$; + const m2$: Effect = request$ => request$; + + // when + const combiner = combineRoutes('/test', { + effects: [a$, b$], + middlewares: [m1$, m2$], + }); + + // then + expect(combiner).toEqual({ + path: '/test', + effects: [a$, b$], + middlewares: [m1$, m2$], + }); + }); + + it('#combineRoutes factorizes combined routes for effects and empty middlewares', () => { + // given + const a$: Effect = request$ => request$.pipe(mapTo({})); + const b$: Effect = request$ => request$.pipe(mapTo({})); + + // when + const combiner = combineRoutes('/test', { + effects: [a$, b$], + }); + + // then + expect(combiner).toEqual({ path: '/test', effects: [a$, b$], + middlewares: [], }); }); diff --git a/packages/core/src/effects/effects.combiner.ts b/packages/core/src/effects/effects.combiner.ts index 5b35b221..6ad52455 100644 --- a/packages/core/src/effects/effects.combiner.ts +++ b/packages/core/src/effects/effects.combiner.ts @@ -1,8 +1,8 @@ import { concat, of } from 'rxjs'; -import { concatMap, switchMap } from 'rxjs/operators'; +import { concatMap, mergeMap, switchMap } from 'rxjs/operators'; import { HttpRequest } from '../http.interface'; import { matchPath } from '../operators'; -import { isGroup } from './effects.helpers'; +import { isGroup, isRouteCombinerConfig } from './effects.helpers'; import { EffectCombiner, MiddlewareCombiner, RouteCombiner } from './effects.interface'; export const combineEffects: EffectCombiner = effects => res => req => { @@ -10,6 +10,7 @@ export const combineEffects: EffectCombiner = effects => res => req => { const mappedEffects = effects.map(effect => isGroup(effect) ? req$.pipe( matchPath(effect.path, { suffix: '/:foo*', combiner: true }), + mergeMap(combineMiddlewareEffects(effect.middlewares)(res)), concatMap(combineEffects(effect.effects)(res)) ) : effect(req$, res, undefined) @@ -26,5 +27,8 @@ export const combineMiddlewareEffects: MiddlewareCombiner = effects => res => re return req$.pipe(...mappedEffects); }; -export const combineRoutes: RouteCombiner = (path, effects) => - ({ path, effects }); +export const combineRoutes: RouteCombiner = (path, configOrEffects) => ({ + path, + effects: isRouteCombinerConfig(configOrEffects) ? configOrEffects.effects : configOrEffects, + middlewares: isRouteCombinerConfig(configOrEffects) ? (configOrEffects.middlewares || []) : [], +}); diff --git a/packages/core/src/effects/effects.helpers.spec.ts b/packages/core/src/effects/effects.helpers.spec.ts index 1d04112d..26013bf7 100644 --- a/packages/core/src/effects/effects.helpers.spec.ts +++ b/packages/core/src/effects/effects.helpers.spec.ts @@ -1,21 +1,55 @@ import { mapTo } from 'rxjs/operators'; -import { isEffect, isGroup } from './effects.helpers'; -import { Effect, GroupedEffects } from './effects.interface'; +import { isEffect, isGroup, isRouteCombinerConfig, isRouteCombinerEffects } from './effects.helpers'; +import { Effect, GroupedEffects, RouteCombinerConfig } from './effects.interface'; describe('Effects helpers', () => { - it('#isGroup checks if parameters is GroupedEffects type', () => { - expect(isGroup({ path: '/test', effects: [] })).toBe(true); - expect(isGroup({ path: '/test', effects: {} } as GroupedEffects)).toBe(false); + test('#isGroup checks if parameters is GroupedEffects type', () => { + expect(isGroup({ path: '/test', effects: [] } as GroupedEffects)).toBe(true); expect(isGroup({ path: '/test', effects: {} } as GroupedEffects)).toBe(false); expect(isGroup({ effects: [] } as any as GroupedEffects)).toBe(false); }); - it('#isEffect checks if parameters is GroupedEffects type', () => { + test('#isEffect checks if parameters is GroupedEffects type', () => { + // given const effect$: Effect = request$ => request$.pipe(mapTo({})); - const groupedEffects = { path: '/test', effects: [effect$] }; + const groupedEffects = { path: '/test', effects: [effect$], middlewares: [] }; + + // when + const effect = isEffect(effect$); + const group = isEffect(groupedEffects); + + // then + expect(effect).toBe(true); + expect(group).toBe(false); + }); + + test('#isRouteCombinerConfig checks if parameter is a configuration object', () => { + // given + const effects = []; + const combinedRoutes: RouteCombinerConfig = { middlewares: [], effects: [] }; + + // when + const combinedRoutesEffects = isRouteCombinerConfig(effects); + const combinedRoutesConfig = isRouteCombinerConfig(combinedRoutes); - expect(isEffect(effect$)).toBe(true); - expect(isEffect(groupedEffects)).toBe(false); + // then + expect(combinedRoutesConfig).toBe(true); + expect(combinedRoutesEffects).toBe(false); }); + + test('#isRouteCombinerEffects checks if parameters is a collection of effects', () => { + // given + const effects = []; + const combinedRoutes: RouteCombinerConfig = { middlewares: [], effects: [] }; + + // when + const combinedRoutesEffects = isRouteCombinerEffects(effects); + const combinedRoutesConfig = isRouteCombinerEffects(combinedRoutes); + + // then + expect(combinedRoutesConfig).toBe(false); + expect(combinedRoutesEffects).toBe(true); + }); + }); diff --git a/packages/core/src/effects/effects.helpers.ts b/packages/core/src/effects/effects.helpers.ts index e12417ca..74ef92ce 100644 --- a/packages/core/src/effects/effects.helpers.ts +++ b/packages/core/src/effects/effects.helpers.ts @@ -1,4 +1,4 @@ -import { Effect, EffectResponse, GroupedEffects } from './effects.interface'; +import { Effect, EffectResponse, Effects, GroupedEffects, RouteCombinerConfig } from './effects.interface'; export const isGroup = (item: Effect | GroupedEffects): item is GroupedEffects => typeof item === 'object' @@ -10,3 +10,9 @@ export const isGroup = (item: Effect | GroupedEffects): item is GroupedEffects = export const isEffect = (item: Effect | GroupedEffects): item is Effect => !isGroup(item) && typeof item === 'function'; + +export const isRouteCombinerConfig = (item: RouteCombinerConfig | Effects): item is RouteCombinerConfig => + !Array.isArray(item); + +export const isRouteCombinerEffects = (item: RouteCombinerConfig | Effects): item is Effects => + !isRouteCombinerConfig(item); diff --git a/packages/core/src/effects/effects.interface.ts b/packages/core/src/effects/effects.interface.ts index 3c569bcf..19647ce6 100644 --- a/packages/core/src/effects/effects.interface.ts +++ b/packages/core/src/effects/effects.interface.ts @@ -10,6 +10,7 @@ export interface EffectResponse { export interface GroupedEffects { path: string; effects: Effects; + middlewares: Effect[]; } export interface EffectCombiner { @@ -21,7 +22,12 @@ export interface MiddlewareCombiner { } export interface RouteCombiner { - (path: string, effects: Effects): GroupedEffects; + (path: string, config: RouteCombinerConfig | Effects): GroupedEffects; +} + +export interface RouteCombinerConfig { + middlewares?: Effect[]; + effects: Effects; } export type Effect = (