import { expect, test, describe } from 'vitest'; import { actionCreator, createHandler, DispatchMiddleware, Flowful, ActionHandlerMiddleware, IAction, createActionMap, ActionMap, ActionHandler, buildHandler, } from '../src/index'; import { clone } from './helpers'; import { skipDevMessages } from '../src/dev'; import { MODE_DEV, setGlobalMode } from '../src/mode'; skipDevMessages(true); setGlobalMode(MODE_DEV); class MyActionMap extends ActionMap { constructor(actions: IAction[]) { super(actions); } hello() { return 'hello'; } } describe('advanced', () => { test('#1 handlers order: 3,5,1,2,4 +custom EventsMap implementation +actionHandler builder +EventHandler constructor', () => { const handlerMiddleware = new ActionHandlerMiddleware(); const dispatchMiddleware = new DispatchMiddleware(); const flowful = new Flowful({ mode: MODE_DEV, handlerMiddleware, dispatchMiddleware, }); const A = actionCreator('A'); const B = actionCreator('B'); const C = actionCreator('C'); const D = actionCreator('D'); const E = actionCreator('E'); const F = actionCreator('F'); const G = actionCreator('G'); const H0 = actionCreator('H0'); const aValues: string[] = []; const actionHandler1MayEmit = [B, C] as const; const actionHandler1 = new ActionHandler({ inputs: [A], run(signalMap) { const arr = signalMap.get(A); if (arr) { const aaa = arr.payload; } const a = signalMap.get(A); if (a) { aValues.push(a.payload); } const b = B([]); const c = C([]); for (let i = 0; i < 3; i++) { b.payload.push('b_value' + i); c.payload.push('c_value' + i); } signalMap.add(b); signalMap.add(c); }, outputs: actionHandler1MayEmit, name: 'actionHandler1', }); const bValues: string[] = []; const cValues: string[] = []; const actionHandler2 = createHandler({ name: 'actionHandler2', inputs: [B, C], outputs: [D, E], run(actionMap) { const b = actionMap.get(B); if (b) { for (const value of b.payload) { bValues.push(value); } } const c = actionMap.get(C); if (c) { for (const value of c.payload) { cValues.push(value); } } actionMap.add(D('d_value')); actionMap.add(E('e_value')); }, }); const dValues: string[] = []; const processor3 = buildHandler('processor3') .ins(D) .outs(F, G, H0) .run((actions) => { const d = actions.get(D); if (d) { dValues.push(d.payload); } actions.add(F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value'])); }) .create(); const gValues: string[] = []; const actionHandler4 = new ActionHandler({ name: 'actionHandler4', inputs: [G], inject: { test: true }, run(actions) { const g = actions.get(G); if (g) { gValues.push(...g.payload); } if (actions.has(H0)) { actions.get(H0)?.payload.push('h0_value'); } else { actions.add(H0(['h0_value'])); } }, outputs: [H0], }); const h0Values: string[] = []; const signalHandler5 = buildHandler('signalHandler5') .in(H0) .run((actions) => { const h = actions.get(H0); if (h) { h0Values.push(...h.payload); } }) .create(); flowful.addHandlers([processor3, signalHandler5, actionHandler1, actionHandler2, actionHandler4]); const before: IAction[][] = []; let handlerMiddlewareHasHello = false; handlerMiddleware.add((next) => (signalMap, actionHandler) => { before.push(clone([...signalMap.getActions()])); if (signalMap instanceof MyActionMap && typeof signalMap.hello === 'function' && signalMap.hello() === 'hello') { handlerMiddlewareHasHello = true; } return next(signalMap, actionHandler); }); handlerMiddleware.compose(); const after: IAction[][] = []; let dispatchMiddlewareHasHello = false; dispatchMiddleware.add((next) => (eventMap) => { next(eventMap); if (eventMap instanceof MyActionMap && typeof eventMap.hello === 'function' && eventMap.hello() === 'hello') { dispatchMiddlewareHasHello = true; } after.push(clone([...eventMap.getActions()])); }); dispatchMiddleware.compose(); flowful.dispatchActionMap(new MyActionMap([A('a_value')])); expect(dispatchMiddlewareHasHello).toBe(true); expect(handlerMiddlewareHasHello).toBe(true); expect(before).toEqual([ [...createActionMap(A('a_value')).getActions()], [ ...createActionMap( A('a_value'), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']) ).getActions(), ], [ ...createActionMap( A('a_value'), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), E('e_value') ).getActions(), ], [ ...createActionMap( A('a_value'), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), E('e_value'), F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value']) ).getActions(), ], [ ...createActionMap( A('a_value'), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), E('e_value'), F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value', 'h0_value']) ).getActions(), ], ]); expect(after).toEqual([ [ ...createActionMap( A('a_value'), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), E('e_value'), F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value', 'h0_value']) ).getActions(), ], ]); expect(aValues).toEqual(['a_value']); expect(bValues).toEqual(['b_value0', 'b_value1', 'b_value2']); expect(cValues).toEqual(['c_value0', 'c_value1', 'c_value2']); expect(dValues).toEqual(['d_value']); expect(gValues).toEqual(['g_value', 'g_value-2']); expect(h0Values).toEqual(['h0_value', 'h0_value']); }); test('#2 handlers order: 5,1,3,4,2', () => { const actionHandlerMiddleware = new ActionHandlerMiddleware(); const emitMiddleware = new DispatchMiddleware(); const flowful = new Flowful({ mode: MODE_DEV, handlerMiddleware: actionHandlerMiddleware, dispatchMiddleware: emitMiddleware, }); const A = actionCreator('A'); const B = actionCreator('B'); const C = actionCreator('C'); const D = actionCreator('D'); const E = actionCreator('E'); const F = actionCreator('F'); const G = actionCreator('G'); const H0 = actionCreator('H0'); const aResults: string[] = []; const actionHandler1 = createHandler({ name: 'actionHandler1', inputs: [A], run(actions) { if (actions.has(A)) { aResults.push(...actions.get(A)!.payload); } const b = B([]); const c = C([]); for (let i = 0; i < 3; i++) { b.payload.push('b_value' + i); c.payload.push('c_value' + i); } actions.add(b, c); }, outputs: [B, C], }); const bResults: string[] = []; const cResults: string[] = []; const actionHandler2 = createHandler({ name: 'actionHandler2', inputs: [B, C], run(actions) { if (actions.has(B)) { bResults.push(...actions.get(B)!.payload); } if (actions.has(C)) { cResults.push(...actions.get(C)!.payload); } actions.add(D('d_value')); }, outputs: [D, E], }); const dResults: string[] = []; const actionHandler3 = createHandler({ name: 'actionHandler3', inputs: [D], run(actions) { if (actions.has(D)) { dResults.push(actions.get(D)!.payload); } actions.add(F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value'])); }, outputs: [F, G, H0], }); const gResults: string[] = []; const actionHandler4 = createHandler({ name: 'actionHandler4', inputs: [G], run(actions) { if (actions.has(G)) { gResults.push(...actions.get(G)!.payload); } if (actions.has(H0)) { actions.get(H0)!.payload.push('h0_value'); } else { actions.add(H0(['h0_value'])); } }, outputs: [H0], }); const h0Results: string[] = []; const actionHandler5 = createHandler({ name: 'actionHandler5', inputs: [H0], run(actions) { if (actions.has(H0)) { h0Results.push(...actions.get(H0)!.payload); } return null; }, outputs: [], }); flowful.addHandlers([actionHandler5, actionHandler1, actionHandler3, actionHandler4, actionHandler2]); const before: IAction[][] = []; actionHandlerMiddleware.add((next) => (inputMap, handler) => { before.push(clone([...inputMap.getActions()])); return next(inputMap, handler); }); // actionHandlerMiddleware.compose(); // check if it works without composing const after: IAction[][] = []; emitMiddleware.add((next) => (actionsMap) => { const output = next(actionsMap); after.push(clone([...actionsMap.getActions()])); return output; }); // emitMiddleware.compose(); // check if it works without composing flowful.dispatchActionMap(createActionMap(A(['a_value']))); expect(before).toEqual([ [...createActionMap(A(['a_value'])).getActions()], [ ...createActionMap( A(['a_value']), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']) ).getActions(), ], [ ...createActionMap( A(['a_value']), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value') ).getActions(), ], [ ...createActionMap( A(['a_value']), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value']) ).getActions(), ], [ ...createActionMap( A(['a_value']), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value', 'h0_value']) ).getActions(), ], ]); expect(after).toEqual([ [ ...createActionMap( A(['a_value']), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value', 'h0_value']) ).getActions(), ], ]); expect(aResults).toEqual(['a_value']); expect(bResults).toEqual(['b_value0', 'b_value1', 'b_value2']); expect(cResults).toEqual(['c_value0', 'c_value1', 'c_value2']); expect(dResults).toEqual(['d_value']); expect(gResults).toEqual(['g_value', 'g_value-2']); expect(h0Results).toEqual(['h0_value', 'h0_value']); }); test('#3 handlers order: 1,3,4,2,5 +EventsMap constructor', () => { const actionHandlerMiddleware = new ActionHandlerMiddleware(); const emitMiddleware = new DispatchMiddleware(); const flowful = new Flowful({ mode: MODE_DEV, handlerMiddleware: actionHandlerMiddleware, dispatchMiddleware: emitMiddleware, }); const A = actionCreator('A'); const B = actionCreator('B'); const C = actionCreator('C'); const D = actionCreator('D'); const E = actionCreator('E'); const F = actionCreator('F'); const G = actionCreator('G'); const H0 = actionCreator('H0'); const aResults: string[] = []; const actionHandler1 = createHandler({ name: 'actionHandler1', inputs: [A], run(actions) { if (actions.has(A)) { aResults.push(actions.get(A)!.payload); } const b = B([]); const c = C([]); for (let i = 0; i < 3; i++) { b.payload.push('b_value' + i); c.payload.push('c_value' + i); } actions.add(b, c); }, outputs: [B, C], }); const bResults: string[] = []; const cResults: string[] = []; const actionHandler2 = createHandler({ name: 'actionHandler2', inputs: [B, C], run(actions) { if (actions.has(B)) { bResults.push(...actions.get(B)!.payload); } if (actions.has(C)) { cResults.push(...actions.get(C)!.payload); } actions.add(D('d_value'), E('e_value')); }, outputs: [D, E], }); const dResults: string[] = []; const actionHandler3 = createHandler({ name: 'actionHandler3', inputs: [D], run(actions) { if (actions.has(D)) { dResults.push(actions.get(D)!.payload); } actions.add(F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value'])); }, outputs: [F, G, H0], }); const gResults: string[] = []; const actionHandler4 = createHandler({ name: 'actionHandler4', inputs: [G], run(actions) { if (actions.has(G)) { gResults.push(...actions.get(G)!.payload); } if (!actions.has(H0)) { actions.add(H0(['h0_value'])); } else { actions.get(H0)!.payload.push('h0_value'); } }, outputs: [H0], }); const h0Results: string[] = []; const actionHandler5 = createHandler({ name: 'actionHandler5', inputs: [H0], run(actions) { if (actions.has(H0)) { h0Results.push(...actions.get(H0)!.payload); } }, }); flowful.addHandlers([actionHandler1, actionHandler3, actionHandler4, actionHandler2, actionHandler5]); const before: IAction[][] = []; actionHandlerMiddleware.add((next) => (inputMap, handler) => { before.push(clone([...inputMap.getActions()])); return next(inputMap, handler); }); actionHandlerMiddleware.compose(); const after: IAction[][] = []; emitMiddleware.add((next) => (actionsMap) => { const output = next(actionsMap); after.push(clone([...actionsMap.getActions()])); return output; }); flowful.dispatchActionMap(new ActionMap([A('a_value')])); expect(before).toEqual([ [...createActionMap(A('a_value')).getActions()], [ ...createActionMap( A('a_value'), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']) ).getActions(), ], [ ...createActionMap( A('a_value'), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), E('e_value') ).getActions(), ], [ ...createActionMap( A('a_value'), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), E('e_value'), F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value']) ).getActions(), ], [ ...createActionMap( A('a_value'), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), E('e_value'), F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value', 'h0_value']) ).getActions(), ], ]); expect(after).toEqual([ [ ...createActionMap( A('a_value'), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), E('e_value'), F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value', 'h0_value']) ).getActions(), ], ]); expect(aResults).toEqual(['a_value']); expect(bResults).toEqual(['b_value0', 'b_value1', 'b_value2']); expect(cResults).toEqual(['c_value0', 'c_value1', 'c_value2']); expect(dResults).toEqual(['d_value']); expect(gResults).toEqual(['g_value', 'g_value-2']); expect(h0Results).toEqual(['h0_value', 'h0_value']); }); test('#4 handlers order: 1,2,3,4,5 +empty EventsMap from constructor', () => { const actionHandlerMiddleware = new ActionHandlerMiddleware(); const emitMiddleware = new DispatchMiddleware(); const conductor = new Flowful({ mode: MODE_DEV, handlerMiddleware: actionHandlerMiddleware, dispatchMiddleware: emitMiddleware, }); const A = actionCreator('A'); const B = actionCreator('B'); const C = actionCreator('C'); const D = actionCreator('D'); const E = actionCreator('E'); const F = actionCreator('F'); const G = actionCreator('G'); const H0 = actionCreator('H0'); const aResults: string[] = []; const actionHandler1 = createHandler({ name: 'actionHandler1', inputs: [A], run(actions) { if (actions.has(A)) { aResults.push(...actions.get(A)!.payload); } const b = B([]); const c = C([]); for (let i = 0; i < 3; i++) { b.payload.push('b_value' + i); c.payload.push('c_value' + i); } actions.add(b, c); }, outputs: [B, C], }); const bResults: string[] = []; const cResults: string[] = []; const actionHandler2 = createHandler({ name: 'actionHandler2', inputs: [B, C], run(actions) { if (actions.has(B)) { bResults.push(...actions.get(B)!.payload); } if (actions.has(C)) { cResults.push(...actions.get(C)!.payload); } actions.add(D('d_value'), E('e_value')); }, outputs: [D, E], }); const dResults: string[] = []; const actionHandler3 = createHandler({ name: 'actionHandler3', inputs: [D], run(actions) { if (actions.has(D)) { dResults.push(actions.get(D)!.payload); } actions.add(F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value'])); }, outputs: [F, G, H0], }); const gResults: string[] = []; const actionHandler4 = createHandler({ name: 'actionHandler4', inputs: [G], run(actions) { if (actions.has(G)) { gResults.push(...actions.get(G)!.payload); } actions.get(H0)!.payload.push('h0_value'); }, outputs: [H0], }); const h0Results: string[] = []; const actionHandler5 = createHandler({ name: 'actionHandler5', inputs: [H0], run(actions) { if (actions.has(H0)) { h0Results.push(...actions.get(H0)!.payload); } return null; }, }); conductor.addHandlers([actionHandler1, actionHandler2, actionHandler3, actionHandler4, actionHandler5]); const before: IAction[][] = []; actionHandlerMiddleware.add((next) => (inputMap, handler) => { before.push(clone([...inputMap.getActions()])); return next(inputMap, handler); }); const after: IAction[][] = []; emitMiddleware.add((next) => (e) => { const output = next(e); after.push(clone([...e.getActions()])); // clone to avoid reference to the same object return output; }); const em = new ActionMap(); em.add(A(['a_value'])); conductor.dispatchActionMap(em); expect(before).toEqual([ [...createActionMap(A(['a_value'])).getActions()], [ ...createActionMap( A(['a_value']), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']) ).getActions(), ], [ ...createActionMap( A(['a_value']), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), E('e_value') ).getActions(), ], [ ...createActionMap( A(['a_value']), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), E('e_value'), F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value']) ).getActions(), ], [ ...createActionMap( A(['a_value']), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), E('e_value'), F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value', 'h0_value']) ).getActions(), ], ]); expect(after).toEqual([ [ ...createActionMap( A(['a_value']), B(['b_value0', 'b_value1', 'b_value2']), C(['c_value0', 'c_value1', 'c_value2']), D('d_value'), E('e_value'), F('f_value'), G(['g_value', 'g_value-2']), H0(['h0_value', 'h0_value']) ).getActions(), ], ]); expect(aResults).toEqual(['a_value']); expect(bResults).toEqual(['b_value0', 'b_value1', 'b_value2']); expect(cResults).toEqual(['c_value0', 'c_value1', 'c_value2']); expect(dResults).toEqual(['d_value']); expect(gResults).toEqual(['g_value', 'g_value-2']); expect(h0Results).toEqual(['h0_value', 'h0_value']); }); });