import { test, expect } from 'vitest'; import Updux from './Updux.js'; import { buildEffectsMiddleware } from './effects.js'; import { createAction, withPayload } from './index.js'; test('addEffect signatures', () => { const someAction = createAction('someAction', withPayload()); const dux = new Updux({ actions: { someAction, } }); dux.addEffect((api) => (next) => (action) => { expectTypeOf(action).toMatchTypeOf(); expectTypeOf(next).toMatchTypeOf(); expectTypeOf(api).toMatchTypeOf(); }); dux.addEffect('someAction', (api) => (next) => (action) => { expectTypeOf(action).toMatchTypeOf(); expectTypeOf(next).toMatchTypeOf(); expectTypeOf(api).toMatchTypeOf(); }); dux.addEffect(someAction, (api) => (next) => (action) => { expectTypeOf(action).toMatchTypeOf(); expectTypeOf(next).toMatchTypeOf(); expectTypeOf(api).toMatchTypeOf(); }); dux.addEffect((action) => (action === null || action === void 0 ? void 0 : action.payload) === 3, (api) => (next) => (action) => { expectTypeOf(action).toMatchTypeOf(); expectTypeOf(next).toMatchTypeOf(); expectTypeOf(api).toMatchTypeOf(); }); }); test('buildEffectsMiddleware', () => { let seen = 0; const mw = buildEffectsMiddleware([ (api) => (next) => (action) => { seen++; expect(api).toHaveProperty('getState'); expect(api.getState).toBeTypeOf('function'); expect(api.getState()).toEqual('the state'); expect(action).toHaveProperty('type'); expect(next).toBeTypeOf('function'); expect(api).toHaveProperty('actions'); expect(api.actions.action1()).toHaveProperty('type', 'action1'); api.dispatch.action1(); expect(api.selectors.getFoo(2)).toBe(2); expect(api.getState.getFoo()).toBe('the state'); expect(api.getState.getBar(2)).toBe('the state2'); next(action); }, ], { action1: createAction('action1'), }, { getFoo: (state) => state, getBar: (state) => (i) => state + i, }); expect(seen).toEqual(0); const dispatch = vi.fn(); mw({ getState: () => 'the state', dispatch })(() => { })({ type: 'noop', }); expect(seen).toEqual(1); expect(dispatch).toHaveBeenCalledWith({ type: 'action1' }); }); test('basic', () => { const dux = new Updux({ initialState: { loaded: true, }, actions: { foo: null, }, }); let seen = 0; dux.addEffect((api) => (next) => (action) => { seen++; expect(api).toHaveProperty('getState'); expect(api.getState()).toHaveProperty('loaded'); expect(action).toHaveProperty('type'); expect(next).toBeTypeOf('function'); next(action); }); const store = dux.createStore(); expect(seen).toEqual(0); store.dispatch.foo(); expect(seen).toEqual(1); }); test('subdux', () => { const bar = new Updux({ initialState: 'bar state', actions: { foo: null }, }); let seen = 0; bar.addEffect((api) => (next) => (action) => { seen++; expect(api.getState()).toBe('bar state'); next(action); }); const dux = new Updux({ initialState: { loaded: true, }, subduxes: { bar, }, }); const store = dux.createStore(); expect(seen).toEqual(0); store.dispatch.foo(); expect(seen).toEqual(1); }); test('addEffect with actionCreator', () => { const dux = new Updux({ actions: { foo: null, bar: null, }, }); const next = vi.fn(); const spy = vi.fn(); const [mw] = dux.addEffect(dux.actions.foo, (api) => (next) => (action) => next(spy(action))).effects; mw({})(next)(dux.actions.bar()); expect(next).toHaveBeenCalled(); expect(spy).not.toHaveBeenCalled(); next.mockReset(); mw({})(next)(dux.actions.foo()); expect(next).toHaveBeenCalled(); expect(spy).toHaveBeenCalled(); }); test('addEffect with function', () => { const dux = new Updux({ actions: { foo: () => { }, bar: () => { }, }, }); const next = vi.fn(); const spy = vi.fn(); const [mw] = dux.addEffect((action) => action.type[0] === 'f', (api) => (next) => (action) => next(spy(action))).effects; mw({})(next)(dux.actions.bar()); expect(next).toHaveBeenCalled(); expect(spy).not.toHaveBeenCalled(); next.mockReset(); mw({})(next)(dux.actions.foo()); expect(next).toHaveBeenCalled(); expect(spy).toHaveBeenCalled(); }); test('catchall addEffect', () => { const dux = new Updux({ initialState: { a: 1, }, }); const spy = vi.fn(); dux.addEffect((api) => (next) => (action) => { expectTypeOf(api.getState()).toMatchTypeOf(); spy(); next(action); }); const store = dux.createStore(); expect(spy).not.toHaveBeenCalled(); store.dispatch({ type: 'noop' }); expect(spy).toHaveBeenCalled(); }); test('addEffect with unknown actionCreator adds it', () => { const foo = createAction('foo'); const dux = new Updux({}).addEffect(foo, () => () => () => { }); expectTypeOf(dux.actions.foo).toMatchTypeOf(); expect(dux.actions.foo()).toMatchObject({ type: 'foo' }); }); test('effects of subduxes', () => { const foo = new Updux({ initialState: 12, actions: { bar: null, }, selectors: { addHundred: (x) => x + 100 } }) .addMutation(createAction('setFoo', withPayload()), (state) => () => state) .addEffect(({ type: t }) => t === 'doit', (api) => next => action => { api.dispatch.setFoo(api.getState.addHundred()); }); const dux = new Updux({ subduxes: { foo } }); const store = dux.createStore(); store.dispatch({ type: "doit" }); expect(store.getState()).toMatchObject({ foo: 112 }); }); // TODO subdux effects // TODO allow to subscribe / unsubscribe effects?