import { expect, test, describe, vi } from 'vitest'; import { actionCreator, actionCreatorMulti, createHandler, ActionHandlerMiddleware, ActionMap, createActionMap, DispatchMiddleware, Flowful, ActionHandler, } from '../src/index'; import { skipDevMessages } from '../src/dev'; import { MODE_DEV, setGlobalMode } from '../src/mode'; import { devMessages } from '../src/dev'; import { MODE_PROD } from '../src/mode'; // Assuming MODE_PROD exists and is different from MODE_DEV setGlobalMode(MODE_DEV); skipDevMessages(true); describe('ActionMap', () => { describe('init', () => { test('should initialize with no actions', () => { const actionMap = new ActionMap(); expect(actionMap.size).toBe(0); }); }); describe('delete', () => { test('should throw an error when the handler tries to delete the action', () => { const actions = new ActionMap(); const Action = actionCreator('TEST_ACTION'); actions.add(Action('foo')); expect(actions.size).toBe(1); actions.delete(Action); expect(actions.size).toBe(0); const flowful = new Flowful(); const DAction = actionCreator('DELETE_ACTION'); const testCallback = vi.fn(); flowful.addHandler( createHandler({ inputs: [Action], run(actionMap) { testCallback(); expect(() => actionMap.delete(DAction)).toThrow(); }, }) ); const actions2 = createActionMap(Action('foo'), DAction('bar')); flowful.dispatchActionMap(actions2); expect(testCallback).toHaveBeenCalled(); expect(actions2.size).toBe(2); expect(actions2.has(Action)).toBe(true); expect(actions2.has(DAction)).toBe(true); }); test('should not throw an error when the middleware tries to delete the action', () => { const actions = new ActionMap(); const Action = actionCreator('TEST_ACTION'); actions.add(Action('foo')); expect(actions.size).toBe(1); actions.delete(Action); expect(actions.size).toBe(0); const dispatchMiddleware = new DispatchMiddleware(); const handlerMiddleware = new ActionHandlerMiddleware(); const flowful = new Flowful({ dispatchMiddleware, handlerMiddleware, }); const DMAction = actionCreator('DISPATCH_ACTION'); const HMAction = actionCreator('HANDLER_ACTION'); dispatchMiddleware.add((next) => (actionMap) => { actionMap.delete(DMAction); return next(actionMap); }); handlerMiddleware.add((next) => (actionMap, handler) => { actionMap.delete(HMAction); return next(actionMap, handler); }); const testCallback = vi.fn(); flowful.addHandler( createHandler({ inputs: [Action], run(actionMap) { testCallback(); expect(() => actionMap.delete(HMAction)).toThrow(); }, }) ); const actions2 = createActionMap(Action('foo'), DMAction('bar'), HMAction('baz')); flowful.dispatchActionMap(actions2); expect(testCallback).toHaveBeenCalled(); expect(actions2.size).toBe(1); expect(actions2.has(Action)).toBe(true); expect(actions2.has(DMAction)).toBe(false); expect(actions2.has(HMAction)).toBe(false); }); }); describe('has', () => { test('should return true if action creator exists', () => { const TestAction = actionCreator('TEST'); const map = new ActionMap([TestAction('payload')]); expect(map.has(TestAction)).toBe(true); }); test('should return false if action creator does not exist', () => { const TestAction1 = actionCreator('TEST1'); const TestAction2 = actionCreator('TEST2'); const map = new ActionMap([TestAction1('payload')]); expect(map.has(TestAction2)).toBe(false); }); test('should call _warnAboutHandlerAction in dev mode', () => { const TestAction = actionCreator('TEST'); // Assuming MODE_DEV is set globally from the test setup const map = new ActionMap(); const warnSpy = vi.spyOn(map as any, '_warnAboutHandlerAction'); map.has(TestAction); expect(warnSpy).toHaveBeenCalledWith(TestAction); warnSpy.mockRestore(); }); test('should not call _warnAboutHandlerAction outside dev mode', () => { const TestAction = actionCreator('TEST'); // Create map specifically in non-dev mode setGlobalMode(MODE_PROD); const map = new ActionMap([]); // Use a non-dev mode const warnSpy = vi.spyOn(map as any, '_warnAboutHandlerAction'); map.has(TestAction); expect(warnSpy).not.toHaveBeenCalled(); warnSpy.mockRestore(); setGlobalMode(MODE_DEV); // Reset to dev mode for other tests }); test('should warn via handler when checking for action not in input/output (dev mode)', () => { const warnSpy = vi.spyOn(devMessages, 'warn'); const flowful = new Flowful(); const InputAC = actionCreator('INPUT'); const OutputAC = actionCreator('OUTPUT'); const OtherAC = actionCreator('OTHER'); const handler = createHandler({ inputs: [InputAC], outputs: [OutputAC], run(actionMap) { actionMap.has(OtherAC); // Check for an action not defined in inputs/outputs }, }); flowful.addHandler(handler); flowful.dispatchActionMap(createActionMap(InputAC('test'))); expect(warnSpy).toHaveBeenCalledTimes(1); expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining( `The action handler is trying to obtain action "${OtherAC.type}" that is not defined in either the inputs or outputs.` ), expect.objectContaining({ handler: expect.any(Object), // Check if it's the handler instance actionCreator: OtherAC, }) ); warnSpy.mockRestore(); }); test('should not warn via handler when checking for action in input (dev mode)', () => { const warnSpy = vi.spyOn(devMessages, 'warn'); const flowful = new Flowful(); const InputAC = actionCreator('INPUT'); const OutputAC = actionCreator('OUTPUT'); const handler = createHandler({ inputs: [InputAC], outputs: [OutputAC], run(actionMap) { actionMap.has(InputAC); // Check for an action defined in inputs }, }); flowful.addHandler(handler); flowful.dispatchActionMap(createActionMap(InputAC('test'))); expect(warnSpy).not.toHaveBeenCalled(); warnSpy.mockRestore(); }); test('should not warn via handler when checking for action in output (dev mode)', () => { const warnSpy = vi.spyOn(devMessages, 'warn'); const flowful = new Flowful(); const InputAC = actionCreator('INPUT'); const OutputAC = actionCreator('OUTPUT'); const handler = createHandler({ inputs: [InputAC], outputs: [OutputAC], run(actionMap) { actionMap.has(OutputAC); // Check for an action defined in outputs }, }); flowful.addHandler(handler); flowful.dispatchActionMap(createActionMap(InputAC('test'))); expect(warnSpy).not.toHaveBeenCalled(); warnSpy.mockRestore(); }); test('should warn about handler without action type in dev mode', () => { const warnSpy = vi.spyOn(devMessages, 'warn'); const flowful = new Flowful(); const InputAC = actionCreator('INPUT'); const OutputAC = actionCreator('OUTPUT'); const fakeActionCreator: any = {}; const handler = createHandler({ inputs: [InputAC], outputs: [OutputAC], run(actionMap) { actionMap.has(fakeActionCreator); // Check for an action without a type }, }); flowful.addHandler(handler); flowful.dispatchActionMap(createActionMap(InputAC('test'))); expect(warnSpy).toHaveBeenCalledTimes(1); expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining(`The action handler is trying to obtain action`), expect.objectContaining({ handler: expect.any(ActionHandler), // Check if it's the handler instance actionCreator: fakeActionCreator, }) ); warnSpy.mockRestore(); }); }); describe('set', () => { test('should add a single action', () => { const TestAction = actionCreator('TEST'); const map = new ActionMap(); const action = TestAction('payload'); map.add(action); expect(map.size).toBe(1); expect(map.has(TestAction)).toBe(true); expect(map.get(TestAction)).toBe(action); }); test('should add multiple actions', () => { const TestAction1 = actionCreator('TEST1'); const TestAction2 = actionCreator('TEST2'); const map = new ActionMap(); const action1 = TestAction1('payload1'); const action2 = TestAction2(123); map.add(action1, action2); expect(map.size).toBe(2); expect(map.has(TestAction1)).toBe(true); expect(map.has(TestAction2)).toBe(true); expect(map.get(TestAction1)).toBe(action1); expect(map.get(TestAction2)).toBe(action2); }); test('should overwrite existing single action', () => { const TestAction = actionCreator('TEST'); const map = new ActionMap(); const action1 = TestAction('payload1'); const action2 = TestAction('payload2'); map.add(action1); expect(map.get(TestAction)).toBe(action1); map.add(action2); expect(map.size).toBe(1); expect(map.has(TestAction)).toBe(true); expect(map.get(TestAction)).toBe(action2); // Overwritten }); test('should add to existing multi action', () => { const TestActionMulti = actionCreatorMulti('TEST_MULTI'); const map = new ActionMap(); const action1 = TestActionMulti('payload1'); const action2 = TestActionMulti('payload2'); map.add(action1); expect(map.get(TestActionMulti)).toEqual([action1]); map.add(action2); expect(map.size).toBe(1); expect(map.has(TestActionMulti)).toBe(true); const actions = map.get(TestActionMulti); expect(actions).toHaveLength(2); expect(actions).toEqual([action1, action2]); // Added }); test('should handle mixed single/multi and new/existing actions', () => { const SingleAction = actionCreator('SINGLE'); const MultiAction = actionCreatorMulti('MULTI'); const NewAction = actionCreator('NEW'); const map = new ActionMap(); const single1 = SingleAction('initial_single'); const multi1 = MultiAction('initial_multi'); map.add(single1, multi1); const single2 = SingleAction('updated_single'); const multi2 = MultiAction('second_multi'); const newAction = NewAction(123); map.add(single2, multi2, newAction); expect(map.size).toBe(3); expect(map.has(SingleAction)).toBe(true); expect(map.get(SingleAction)).toBe(single2); // Overwritten expect(map.has(MultiAction)).toBe(true); const multiActions = map.get(MultiAction); expect(multiActions).toHaveLength(2); expect(multiActions).toEqual([multi1, multi2]); // Added expect(map.has(NewAction)).toBe(true); expect(map.get(NewAction)).toBe(newAction); // Added }); test('should throw error if handler sets existing single action', () => { const SingleAction = actionCreator('SINGLE_ACTION'); const TriggerAction = actionCreator('TRIGGER'); const flowful = new Flowful(); const testCallback = vi.fn(); flowful.addHandler( createHandler({ inputs: [TriggerAction], outputs: [SingleAction], run(actionMap) { testCallback(); expect(actionMap.has(SingleAction)).toBe(true); // Verify it exists // Attempting to set the same single action again within the handler expect(() => actionMap.add(SingleAction('second'))).toThrow( /Action "SINGLE_ACTION" already exists in ActionMap/ ); }, }) ); // Dispatch initial single action and the trigger flowful.dispatchActionMap(createActionMap(SingleAction('initial'), TriggerAction(null))); expect(testCallback).toHaveBeenCalled(); }); test('should throw error if handler sets action not in outputs', () => { const InputAC = actionCreator('INPUT'); const OutputAC = actionCreator('OUTPUT'); const ForbiddenAC = actionCreator('FORBIDDEN'); // Not in outputs const flowful = new Flowful(); const testCallback = vi.fn(); flowful.addHandler( createHandler({ inputs: [InputAC], outputs: [OutputAC], // ForbiddenAC is not listed here run(actionMap) { testCallback(); expect(() => actionMap.add(ForbiddenAC('payload'))).toThrow( /should not add actions that are not defined in the action handler outputs/ ); }, }) ); flowful.dispatchActionMap(createActionMap(InputAC('trigger'))); expect(testCallback).toHaveBeenCalled(); }); test('should not throw error if handler sets existing multi action', () => { const MultiAction = actionCreatorMulti('MULTI_ACTION'); const TriggerAction = actionCreator('TRIGGER'); const flowful = new Flowful(); const testCallback = vi.fn(); flowful.addHandler( createHandler({ inputs: [TriggerAction], outputs: [MultiAction], run(actionMap) { testCallback(); expect(actionMap.has(MultiAction)).toBe(true); const action2 = MultiAction('second'); expect(() => actionMap.add(action2)).not.toThrow(); const actions = actionMap.get(MultiAction); expect(actions).toHaveLength(2); // Initial + second expect(actions?.[1]).toBe(action2); }, }) ); // Dispatch initial multi action and the trigger flowful.dispatchActionMap(createActionMap(MultiAction('initial'), TriggerAction(null))); expect(testCallback).toHaveBeenCalled(); }); test('should not throw error if handler sets new action defined in outputs', () => { const InputAC = actionCreator('INPUT'); const OutputAC = actionCreator('OUTPUT'); const flowful = new Flowful(); const testCallback = vi.fn(); flowful.addHandler( createHandler({ inputs: [InputAC], outputs: [OutputAC], // OutputAC is allowed run(actionMap) { testCallback(); const outputAction = OutputAC('payload'); expect(() => actionMap.add(outputAction)).not.toThrow(); expect(actionMap.has(OutputAC)).toBe(true); expect(actionMap.get(OutputAC)).toBe(outputAction); }, }) ); flowful.dispatchActionMap(createActionMap(InputAC('trigger'))); expect(testCallback).toHaveBeenCalled(); }); test('should not throw error when middleware sets an action', () => { const TestAction = actionCreator('TEST_ACTION'); const MiddlewareAction = actionCreator('MIDDLEWARE_ACTION'); // Single action const dispatchMiddleware = new DispatchMiddleware(); const flowful = new Flowful({ dispatchMiddleware }); dispatchMiddleware.add((next) => (actionMap) => { // Middleware sets a new action const mwAction = MiddlewareAction('from_middleware'); expect(() => actionMap.add(mwAction)).not.toThrow(); // Middleware overwrites an existing single action const overwriteAction = TestAction('from_middleware_overwrite'); expect(() => actionMap.add(overwriteAction)).not.toThrow(); return next(actionMap); }); const testCallback = vi.fn(); flowful.addHandler( createHandler({ inputs: [TestAction], // Input to trigger the handler run(actionMap) { testCallback(); // Verify actions set/overwritten by middleware expect(actionMap.has(MiddlewareAction)).toBe(true); expect(actionMap.get(MiddlewareAction)?.payload).toBe('from_middleware'); expect(actionMap.has(TestAction)).toBe(true); expect(actionMap.get(TestAction)?.payload).toBe('from_middleware_overwrite'); }, }) ); // Dispatch initial action that middleware will overwrite flowful.dispatchActionMap(createActionMap(TestAction('initial'))); expect(testCallback).toHaveBeenCalled(); }); test('should throw FlowfulError for invalid action without creator in dev mode', () => { const map = new ActionMap([]); const invalidAction = { type: 'INVALID', payload: 'test' }; // Missing creator expect(() => map.add(invalidAction as any)).toThrow(); expect(() => map.add(invalidAction as any)).toThrow(/action creator is missing/); }); test('should throw FlowfulError for invalid action without creator in prod mode', () => { setGlobalMode(MODE_PROD); // Set to production mode const map = new ActionMap([]); const invalidAction = { type: 'INVALID', payload: 'test' }; // Missing creator // It might throw a different error later (e.g., accessing creator.mode), but not the specific FlowfulError check expect(() => map.add(invalidAction as any)).toThrow(/action creator is missing/); // Let's verify it still adds *something* based on the broken logic path // This behavior isn't ideal, but confirms the check is skipped. // It will likely fail later if .get is called. expect(map.size).toBe(0); // Because creator is undefined, map key is undefined, likely doesn't add setGlobalMode(MODE_DEV); // Reset to dev mode for other tests }); }); describe('get', () => { test('should return the single action for a single action creator', () => { const TestAction = actionCreator('TEST_SINGLE'); const action = TestAction('payload'); const map = new ActionMap([action]); expect(map.get(TestAction)).toBe(action); }); test('should return an array of actions for a multi action creator', () => { const TestActionMulti = actionCreatorMulti('TEST_MULTI'); const action1 = TestActionMulti('payload1'); const action2 = TestActionMulti('payload2'); const map = new ActionMap([action1, action2]); const result = map.get(TestActionMulti); expect(result).toEqual([action1, action2]); expect(Array.isArray(result)).toBe(true); }); test('should return an array with one action for a multi action creator when only one exists', () => { const TestActionMulti = actionCreatorMulti('TEST_MULTI_SINGLE'); const action1 = TestActionMulti('payload1'); const map = new ActionMap([action1]); const result = map.get(TestActionMulti); expect(result).toEqual([action1]); expect(Array.isArray(result)).toBe(true); }); test('should return undefined if the action creator does not exist in the map', () => { const ExistingAction = actionCreator('EXISTING'); const NonExistentSingle = actionCreator('NON_EXISTENT_SINGLE'); const NonExistentMulti = actionCreatorMulti('NON_EXISTENT_MULTI'); const map = new ActionMap([ExistingAction('payload')]); expect(map.get(NonExistentSingle)).toBeUndefined(); expect(map.get(NonExistentMulti)).toBeUndefined(); }); test('should call _warnAboutHandlerAction in dev mode', () => { const TestAction = actionCreator('TEST'); // Assuming MODE_DEV is set globally from the test setup const map = new ActionMap(); const warnSpy = vi.spyOn(map as any, '_warnAboutHandlerAction'); map.get(TestAction); // Call get expect(warnSpy).toHaveBeenCalledWith(TestAction); warnSpy.mockRestore(); }); test('should not call _warnAboutHandlerAction outside dev mode', () => { setGlobalMode(MODE_PROD); // Set to production mode const TestAction = actionCreator('TEST'); // Create map specifically in non-dev mode const map = new ActionMap([]); // Use a non-dev mode const warnSpy = vi.spyOn(map as any, '_warnAboutHandlerAction'); map.get(TestAction); // Call get expect(warnSpy).not.toHaveBeenCalled(); warnSpy.mockRestore(); setGlobalMode(MODE_DEV); // Reset to dev mode for other tests }); test('should warn via handler when getting action not in input/output (dev mode)', () => { const warnSpy = vi.spyOn(devMessages, 'warn'); const flowful = new Flowful(); // Will use global MODE_DEV const InputAC = actionCreator('INPUT_GET'); const OutputAC = actionCreator('OUTPUT_GET'); const OtherAC = actionCreator('OTHER_GET'); const handler = createHandler({ inputs: [InputAC], outputs: [OutputAC], run(actionMap) { actionMap.get(OtherAC); // Get an action not defined in inputs/outputs }, }); flowful.addHandler(handler); // Dispatch actions including the input to trigger the handler, and the 'other' action to be present flowful.dispatchActionMap(createActionMap(InputAC('test'), OtherAC('other_payload'))); expect(warnSpy).toHaveBeenCalledTimes(1); expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining( `The action handler is trying to obtain action "${OtherAC.type}" that is not defined in either the inputs or outputs.` ), expect.objectContaining({ handler: expect.any(Object), // Check if it's the handler instance actionCreator: OtherAC, }) ); warnSpy.mockRestore(); }); test('should not warn via handler when getting action in input (dev mode)', () => { const warnSpy = vi.spyOn(devMessages, 'warn'); const flowful = new Flowful(); const InputAC = actionCreator('INPUT_GET_VALID'); const OutputAC = actionCreator('OUTPUT_GET_VALID'); const handler = createHandler({ inputs: [InputAC], outputs: [OutputAC], run(actionMap) { actionMap.get(InputAC); // Get an action defined in inputs }, }); flowful.addHandler(handler); flowful.dispatchActionMap(createActionMap(InputAC('test'))); expect(warnSpy).not.toHaveBeenCalled(); warnSpy.mockRestore(); }); test('should not warn via handler when getting action in output (dev mode)', () => { const warnSpy = vi.spyOn(devMessages, 'warn'); const flowful = new Flowful(); const InputAC = actionCreator('INPUT_GET_VALID_OUT'); const OutputAC = actionCreator('OUTPUT_GET_VALID_OUT'); const handler = createHandler({ inputs: [InputAC], outputs: [OutputAC], run(actionMap) { // Note: Getting an output action within the handler that *sets* it might be less common, // but the check should still pass if it's defined in outputs. actionMap.get(OutputAC); }, }); flowful.addHandler(handler); // Dispatch input, and potentially the output if it could exist before handler runs (e.g. via middleware) flowful.dispatchActionMap(createActionMap(InputAC('test'), OutputAC('pre_existing_output'))); expect(warnSpy).not.toHaveBeenCalled(); warnSpy.mockRestore(); }); }); describe('getActionCreators', () => { test('should return an iterator over action creators', () => { const Action1 = actionCreator('ACTION1'); const Action2 = actionCreatorMulti('ACTION2'); const map = new ActionMap([Action1('payload1'), Action2('payload2a'), Action2('payload2b')]); const creatorsIterator = map.getActionCreators(); expect(typeof creatorsIterator[Symbol.iterator]).toBe('function'); // Check if it's an iterator const creatorsArray = [...creatorsIterator]; expect(creatorsArray).toHaveLength(2); expect(creatorsArray).toContain(Action1); expect(creatorsArray).toContain(Action2); }); test('should return an empty iterator for an empty map', () => { const map = new ActionMap(); const creatorsIterator = map.getActionCreators(); expect(typeof creatorsIterator[Symbol.iterator]).toBe('function'); const creatorsArray = [...creatorsIterator]; expect(creatorsArray).toHaveLength(0); }); test('should throw an error when called from within an action handler', () => { const TriggerAction = actionCreator('TRIGGER'); const flowful = new Flowful(); const testCallback = vi.fn(); flowful.addHandler( createHandler({ inputs: [TriggerAction], run(actionMap) { testCallback(); // @ts-expect-error not inside basic action map expect(() => actionMap.getActionCreators()).toThrow(); }, }) ); flowful.dispatchActionMap(createActionMap(TriggerAction(null))); expect(testCallback).toHaveBeenCalled(); }); test('should not throw an error when called outside of an action handler execution', () => { const Action1 = actionCreator('ACTION1'); const map = new ActionMap([Action1('payload')]); expect(() => { const creators = [...map.getActionCreators()]; expect(creators).toEqual([Action1]); }).not.toThrow(); }); test('should not throw an error when called from middleware', () => { const TriggerAction = actionCreator('TRIGGER'); const TestAction = actionCreator('TEST'); const dispatchMiddleware = new DispatchMiddleware(); const handlerMiddleware = new ActionHandlerMiddleware(); const flowful = new Flowful({ dispatchMiddleware, handlerMiddleware }); const testCallback = vi.fn(); dispatchMiddleware.add((next) => (actionMap) => { expect(() => [...actionMap.getActionCreators()]).not.toThrow(); return next(actionMap); }); handlerMiddleware.add((next) => (actionMap, handler) => { // Called before handler run, _isHandlerExecuted should be false expect(() => [...actionMap.getActionCreators()]).not.toThrow(); const result = next(actionMap, handler); // Called after handler run, _isHandlerExecuted should be false again expect(() => [...actionMap.getActionCreators()]).not.toThrow(); return result; }); flowful.addHandler( createHandler({ inputs: [TriggerAction], run(actionMap) { testCallback(); // @ts-expect-error not inside basic action map expect(() => actionMap.getActionCreators()).toThrow(); }, }) ); const initialMap = createActionMap(TestAction('initial'), TriggerAction(null)); flowful.dispatchActionMap(initialMap); expect(testCallback).toHaveBeenCalled(); }); }); describe('getActions', () => { test('should return all actions in the map', () => { const Action1 = actionCreator('ACTION1'); const Action2Multi = actionCreatorMulti('ACTION2_MULTI'); const Action3 = actionCreator('ACTION3'); const action1 = Action1('payload1'); const action2a = Action2Multi(100); const action2b = Action2Multi(200); const action3 = Action3(true); const map = new ActionMap([action1, action2a, action2b, action3]); const actions = map.getActions(); const actionArray = [...actions]; // Convert iterable to array expect(actionArray).toHaveLength(4); expect(actionArray).toEqual(expect.arrayContaining([action1, action2a, action2b, action3])); }); test('should return an empty iterable for an empty map', () => { const map = new ActionMap(); const actions = map.getActions(); const actionArray = [...actions]; // Convert iterable to array expect(actionArray).toHaveLength(0); expect(actionArray).toEqual([]); }); test('should throw an error when called from within an action handler', () => { const TriggerAction = actionCreator('TRIGGER'); const flowful = new Flowful(); const testCallback = vi.fn(); flowful.addHandler( createHandler({ inputs: [TriggerAction], run(actionMap) { testCallback(); // @ts-expect-error not inside basic action map expect(() => actionMap.getActions()).toThrow(); }, }) ); flowful.dispatchActionMap(createActionMap(TriggerAction(null))); expect(testCallback).toHaveBeenCalled(); }); test('should not throw an error when called from middleware', () => { const TriggerAction = actionCreator('TRIGGER'); const Action1 = actionCreator('ACTION1'); const Action2 = actionCreator('ACTION2'); const action1Instance = Action1('test'); const action2Instance = Action2(123); const dispatchMiddleware = new DispatchMiddleware(); const flowful = new Flowful({ dispatchMiddleware }); const middlewareCallback = vi.fn(); dispatchMiddleware.add((next) => (actionMap) => { middlewareCallback(); let actions: any[] = []; expect(() => { actions = [...actionMap.getActions()]; }).not.toThrow(); expect(actions).toEqual( expect.arrayContaining([action1Instance, action2Instance, expect.objectContaining({ type: 'TRIGGER' })]) ); // Trigger action is also present return next(actionMap); }); flowful.dispatchActionMap(createActionMap(TriggerAction(null), action1Instance, action2Instance)); expect(middlewareCallback).toHaveBeenCalled(); }); test('should not throw an error when called outside handler/middleware context', () => { const Action1 = actionCreator('ACTION1'); const Action2 = actionCreator('ACTION2'); const action1Instance = Action1('test'); const action2Instance = Action2(123); const map = new ActionMap([action1Instance, action2Instance]); let actions: any[] = []; expect(() => { actions = [...map.getActions()]; }).not.toThrow(); expect(actions).toEqual(expect.arrayContaining([action1Instance, action2Instance])); expect(actions).toHaveLength(2); }); }); });