import { test } from 'tap'; import sinon from 'sinon'; import { difference, omit } from 'lodash-es'; import { Updux } from './Updux.js'; test('initial', async (t) => { const dux = new Updux({ initial: {}, subduxes: { '*': { initial: { a: 1 }, }, }, }); t.same(dux.initial, {}); }); test('actions', async (t) => { const dux = new Updux({ initial: {}, subduxes: { '*': { initial: { a: 1 }, actions: { foo: null }, }, }, }); t.type(dux.actions.foo, 'function'); }); test('selectors', async (t) => { const dux = new Updux({ initial: {}, subduxes: { '*': { initial: { a: 1 }, selectors: { getA: ({ a }) => a, }, }, }, }); t.same(dux.selectors, {}); const getInner = (state) => (index) => state[index]; dux.setSplatSelector('getInner', getInner); t.type(dux.selectors.getInner, 'function'); t.same( dux.selectors.getInner({ one: { a: 1 }, two: { a: 2 }, })('one')(), { a: 1 } ); t.same( dux.selectors .getInner({ one: { a: 1 }, two: { a: 2 }, })('one') .getA(), 1 ); // and now with the store const store = dux.createStore({ one: { a: 1 }, two: { a: 2 }, }); t.same(store.getState.getInner('two')(), { a: 2 }); t.same(store.getState.getInner('two').getA(), 2); }); test('splat middleware', async (t) => { const snitch = sinon.fake(() => true); const dux = new Updux({ initial: {}, subduxes: { '*': { initial: { a: 1 }, actions: { foo: null }, effects: { foo: (api) => (next) => (action) => { snitch(); next(action); }, }, }, }, }); const store = dux.createStore({ one: { a: 1 }, two: { a: 2 } }); store.dispatch.foo(); t.notOk(snitch.called); }); test('splat subscriptions', async (t) => { const snitch = sinon.fake(() => true); const dux = new Updux({ initial: {}, subduxes: { '*': { initial: { a: 1 }, actions: { foo: null }, mutations: { foo: (id) => (state) => state.a === i ? { ...state, b: 1 } : state, }, subscriptions: [ (store) => (state, previous, unsub) => { snitch(state); }, ], }, }, }); const store = dux.createStore({ one: { a: 1 }, two: { a: 2 } }); store.dispatch({ type: 'noop' }); t.notOk(snitch.called); }); test('splat subscriptions, more', async (t) => { const snitch = sinon.fake(() => true); const inner = new Updux({ initial: { a: 1 }, actions: { foo: null, incAll: null }, mutations: { foo: (id) => (state) => state.a === id ? { ...state, b: 1 } : state, incAll: () => (state) => ({ ...state, a: state.a + 1 }), }, subscriptions: [() => snitch], }); const dux = new Updux({ initial: {}, actions: { delete: null, newEntry: null, }, mutations: { delete: (id) => (state) => omit(state, id), newEntry: (name) => (state) => ({ ...state, [name]: { a: 10 } }), }, splatReaction: 'whatev', subduxes: { '*': inner, }, }); const store = dux.createStore({ one: { a: 1 }, two: { a: 2 } }); store.dispatch({ type: 'noop' }); t.equal(snitch.callCount, 2); t.same(snitch.firstCall.args[0], { a: 1 }); t.same(snitch.secondCall.args[0], { a: 2 }); snitch.resetHistory(); store.dispatch.foo(2); t.equal(snitch.callCount, 1); t.same(snitch.firstCall.args[0], { a: 2, b: 1 }); snitch.resetHistory(); store.dispatch.delete('one'); t.same(store.getState(), { two: { a: 2, b: 1 }, }); t.equal(snitch.callCount, 1); t.ok( snitch.calledWithMatch( undefined, { a: 1 }, sinon.match.typeOf('function') ) ); await t.test('only one subscriber left', async (t) => { snitch.resetHistory(); store.dispatch.incAll(); t.equal(snitch.callCount, 1); t.ok( snitch.calledWithMatch( { a: 3, b: 1 }, { a: 2, b: 1 }, sinon.match.typeOf('function') ) ); }); await t.test('new entry gets subscribed', async (t) => { snitch.resetHistory(); store.dispatch.newEntry('newbie'); t.equal(snitch.callCount, 1); t.ok( snitch.calledWithMatch( { a: 10 }, undefined, sinon.match.typeOf('function') ) ); }); }); test('many levels down', { only: false }, async (t) => { const snitch = sinon.fake(() => true); const dux = new Updux({ splatReaction: 'potato', actions: { remove: null }, mutations: { remove: () => (state) => ({}), }, subduxes: { '*': { subduxes: { a: { initial: 1, actions: { add: null, }, mutations: { add: () => (x) => x + 1, }, subscriptions: [ (store) => (state) => snitch(state, store), ], }, }, }, }, }); const store = dux.createStore({ one: { a: 1 } }); store.dispatch({ type: 'foo' }); t.ok(snitch.calledOnce); t.same(snitch.firstCall.firstArg, 1); snitch.resetHistory(); store.dispatch.remove(); t.ok(snitch.calledOnce); t.same(snitch.firstCall.firstArg, undefined); }); test('inherit info via the store', async (t) => { const snitch = sinon.fake(() => true); const splatSnitch = sinon.fake(() => true); const dux = new Updux({ splatReaction: (store, key) => { store.itemId = key; splatSnitch(); return { itemId: key }; }, subduxes: { '*': { subduxes: { a: { initial: 1, actions: { add: null, }, mutations: { add: () => (x) => x + 1, }, subscriptions: [ (store) => (state) => snitch(state, store.itemId), ], }, }, }, }, }); const store = dux.createStore({ one: { a: 1 }, two: { a: 2 }, }); store.dispatch({ type: 'noop' }); t.ok(splatSnitch.calledTwice); t.ok(snitch.calledTwice); t.ok(snitch.calledWithMatch(1, 'one')); t.ok(snitch.calledWithMatch(2, 'two')); });