import { expect, test, describe, vi } from 'vitest'; import { ACTION_MODE_MULTI, actionCreator, actionCreatorMulti, createHandler, createActionMap, Flowful, } from '../src/index'; import { skipDevMessages } from '../src/dev'; import { MODE_DEV, setGlobalMode } from '../src/mode'; setGlobalMode(MODE_DEV); skipDevMessages(true); describe('dispatch', () => { test('dispatch #1 solo', () => { const flowful = new Flowful(); const A = actionCreator('A'); const callback1 = vi.fn(); const callback2 = vi.fn(); const actionHandler1 = createHandler({ name: 'actionHandler1', inputs: [A], run(actions) { callback1(actions); return null; }, }); const actionHandler2 = createHandler({ name: 'actionHandler2', inputs: [A], run(actions) { callback2(actions); return null; }, }); flowful.addHandler(actionHandler1); flowful.addHandler(actionHandler2); const aMap = createActionMap(A('test_value')); flowful.dispatchActionMap(aMap); expect(callback1).toHaveBeenCalledWith(aMap); expect(callback2).toHaveBeenCalledWith(aMap); expect(callback1).toHaveBeenCalledTimes(1); expect(callback2).toHaveBeenCalledTimes(1); const aMap2 = createActionMap(A('test_value_2')); flowful.dispatchActionMap(aMap2); expect(callback1).toHaveBeenCalledWith(aMap2); expect(callback2).toHaveBeenCalledWith(aMap2); expect(callback1).toHaveBeenCalledTimes(2); expect(callback2).toHaveBeenCalledTimes(2); const aMap3 = createActionMap(A('test_value_3')); flowful.dispatchActionMap(aMap3); expect(callback1).toHaveBeenCalledWith(aMap3); expect(callback2).toHaveBeenCalledWith(aMap3); expect(callback1).toHaveBeenCalledTimes(3); expect(callback2).toHaveBeenCalledTimes(3); }); test('dispatch #1 multi', () => { const flowful = new Flowful(); const A = actionCreatorMulti('A'); const callback1 = vi.fn(); const callback2 = vi.fn(); const actionHandler1 = createHandler({ name: 'actionHandler1', inputs: [A], run(actions) { callback1(actions); return null; }, }); const actionHandler2 = createHandler({ name: 'actionHandler2', inputs: [A], run(actions) { callback2(actions); return null; }, }); flowful.addHandler(actionHandler1); flowful.addHandler(actionHandler2); const aMap = createActionMap(A('test_value')); flowful.dispatchActionMap(aMap); expect(callback1).toHaveBeenCalledWith(aMap); expect(callback2).toHaveBeenCalledWith(aMap); expect(callback1).toHaveBeenCalledTimes(1); expect(callback2).toHaveBeenCalledTimes(1); const aMap2 = createActionMap(A('test_value_2')); flowful.dispatchActionMap(aMap2); expect(callback1).toHaveBeenCalledWith(aMap2); expect(callback2).toHaveBeenCalledWith(aMap2); expect(callback1).toHaveBeenCalledTimes(2); expect(callback2).toHaveBeenCalledTimes(2); const aMap3 = createActionMap(A('test_value_3')); flowful.dispatchActionMap(aMap3); expect(callback1).toHaveBeenCalledWith(aMap3); expect(callback2).toHaveBeenCalledWith(aMap3); expect(callback1).toHaveBeenCalledTimes(3); expect(callback2).toHaveBeenCalledTimes(3); }); test('dispatch waterfall cyclic single', () => { const flowful = new Flowful(); const A = actionCreator('A'); const B = actionCreator('B'); const C = actionCreator('C'); const D = actionCreator('D'); const E = actionCreator('E'); const callback1 = vi.fn(); const actionHandler1 = createHandler({ name: 'actionHandler waterfall 1', inputs: [A], run(actions) { callback1(actions); actions.add(B('b_value')); }, outputs: [B], }); const callback2 = vi.fn(); const actionHandler2 = createHandler({ name: 'actionHandler waterfall 2', inputs: [B], run(actions) { callback2(actions); actions.add(C('c_value')); }, outputs: [C], }); const callback3 = vi.fn(); const actionHandler3 = createHandler({ name: 'actionHandler waterfall 3', inputs: [C], run(actions) { callback3(actions); actions.add(D('d_value')); }, outputs: [D], }); const callback4 = vi.fn(); const actionHandler4 = createHandler({ name: 'actionHandler waterfall 4', inputs: [D], run(actions) { callback4(actions); actions.add(E('e_value')); }, outputs: [E], }); let called = false; const callback5 = vi.fn(); const actionHandler5 = createHandler({ name: 'actionHandler waterfall 5', inputs: [E], run(actionMap) { callback5(actionMap); const result = called === false ? A('5_value') : undefined; called = true; if (result) actionMap.add(result); }, outputs: [A], }); flowful.addHandler(actionHandler1); flowful.addHandler(actionHandler2); flowful.addHandler(actionHandler3); flowful.addHandler(actionHandler4); expect(() => { flowful.addHandler(actionHandler5); }).toThrow(); const aMap = createActionMap(A('a_value')); // should throw multiple single action expect(() => { flowful.dispatchActionMap(aMap); }).toThrow(); expect(aMap.has(A)).toBe(true); expect(aMap.has(B)).toBe(true); expect(aMap.has(C)).toBe(true); expect(aMap.has(D)).toBe(true); expect(aMap.has(E)).toBe(true); expect(aMap.get(A)?.payload).toEqual('a_value'); expect(aMap.get(B)?.payload).toEqual('b_value'); expect(aMap.get(C)?.payload).toEqual('c_value'); expect(aMap.get(D)?.payload).toEqual('d_value'); expect(aMap.get(E)?.payload).toEqual('e_value'); expect(callback1).toHaveBeenCalledWith(aMap); expect(callback2).toHaveBeenCalledWith(aMap); expect(callback3).toHaveBeenCalledWith(aMap); expect(callback4).toHaveBeenCalledWith(aMap); expect(callback5).toHaveBeenCalledWith(aMap); expect(callback1).toHaveBeenCalledTimes(1); expect(callback2).toHaveBeenCalledTimes(1); expect(callback3).toHaveBeenCalledTimes(1); expect(callback4).toHaveBeenCalledTimes(1); expect(callback5).toHaveBeenCalledTimes(1); }); test('dispatch waterfall cyclic multi', () => { const flowful = new Flowful(); const A = actionCreatorMulti('A'); const B = actionCreatorMulti('B'); const C = actionCreatorMulti('C'); const D = actionCreatorMulti('D'); const E = actionCreatorMulti('E'); const callback1 = vi.fn(); const actionHandler1 = createHandler({ name: 'actionHandler waterfall 1', inputs: [A], run(actions) { callback1(actions); actions.add(B('b_value')); }, outputs: [B], }); const callback2 = vi.fn(); const actionHandler2 = createHandler({ name: 'actionHandler waterfall 2', inputs: [B], run(actions) { callback2(actions); actions.add(C('c_value')); }, outputs: [C], }); const callback3 = vi.fn(); const actionHandler3 = createHandler({ name: 'actionHandler waterfall 3', inputs: [C], run(actions) { callback3(actions); actions.add(D('d_value')); }, outputs: [D], }); const callback4 = vi.fn(); const actionHandler4 = createHandler({ name: 'actionHandler waterfall 4', inputs: [D], run(actions) { callback4(actions); actions.add(E('e_value')); }, outputs: [E], }); let called = false; const callback5 = vi.fn(); const actionHandler5 = createHandler({ name: 'actionHandler waterfall 5', inputs: [E], run(actions) { callback5(actions); const result = called === false ? A('5_value') : undefined; called = true; if (result) actions.add(result); return null; }, outputs: [A], }); flowful.addHandler(actionHandler1); flowful.addHandler(actionHandler2); flowful.addHandler(actionHandler3); flowful.addHandler(actionHandler4); expect(() => { flowful.addHandler(actionHandler5); }).toThrow(); const aMap = createActionMap(A('a_value')); // should not throw because of multi action flowful.dispatchActionMap(aMap); expect(aMap.has(A)).toBe(true); expect(aMap.has(B)).toBe(true); expect(aMap.has(C)).toBe(true); expect(aMap.has(D)).toBe(true); expect(aMap.has(E)).toBe(true); expect(aMap.get(A)?.map((a) => a.payload)).toEqual(['a_value', '5_value']); expect(aMap.get(B)?.map((b) => b.payload)).toEqual(['b_value']); expect(aMap.get(C)?.map((c) => c.payload)).toEqual(['c_value']); expect(aMap.get(D)?.map((d) => d.payload)).toEqual(['d_value']); expect(aMap.get(E)?.map((e) => e.payload)).toEqual(['e_value']); expect(callback1).toHaveBeenCalledWith(aMap); expect(callback1).toHaveBeenCalledWith(aMap); expect(callback2).toHaveBeenCalledWith(aMap); expect(callback3).toHaveBeenCalledWith(aMap); expect(callback4).toHaveBeenCalledWith(aMap); expect(callback5).toHaveBeenCalledWith(aMap); expect(callback1).toHaveBeenCalledTimes(1); expect(callback2).toHaveBeenCalledTimes(1); expect(callback3).toHaveBeenCalledTimes(1); expect(callback4).toHaveBeenCalledTimes(1); expect(callback5).toHaveBeenCalledTimes(1); }); test('run handler just once', () => { const flowful = new Flowful(); const A = actionCreator('A'); const B = actionCreator('B'); const C = actionCreator('C'); const callback1 = vi.fn(); const handler1 = createHandler({ inputs: [A], run(actionMap) { callback1(); actionMap.add(B('b'), C('c')); }, outputs: [B, C], }); flowful.addHandler(handler1); const callback2 = vi.fn(); const handler2 = createHandler({ inputs: [B, C], run(actionMap) { callback2(); return null; }, outputs: [], }); flowful.addHandler(handler2); flowful.dispatchActionMap(createActionMap(A('a'))); expect(callback1).toHaveBeenCalledTimes(1); expect(callback2).toHaveBeenCalledTimes(1); }); test('run handler just once #2', () => { const flowful = new Flowful(); const A = actionCreator('A'); const B = actionCreator('B', ACTION_MODE_MULTI); const C = actionCreator('C', ACTION_MODE_MULTI); const callback1 = vi.fn(); const handler1 = createHandler({ inputs: [A], run(actionMap) { callback1(); actionMap.add(B('b'), C('c')); }, outputs: [B, C], }); flowful.addHandler(handler1); const callback1_1 = vi.fn(); const handler1_1 = createHandler({ inputs: [A], run(actionMap) { callback1_1(); actionMap.add(B('b'), C('c')); }, outputs: [B, C], }); flowful.addHandler(handler1_1); const callback2 = vi.fn(); const handler2 = createHandler({ inputs: [B, C], run(actionMap) { callback2(); }, outputs: [], }); flowful.addHandler(handler2); flowful.dispatchActionMap(createActionMap(A('a'))); expect(callback1).toHaveBeenCalledTimes(1); expect(callback1_1).toHaveBeenCalledTimes(1); expect(callback2).toHaveBeenCalledTimes(1); }); });