mutations
This commit is contained in:
parent
1a653fac5b
commit
1759ce16c6
3
.taprc
Normal file
3
.taprc
Normal file
@ -0,0 +1,3 @@
|
||||
coverage: false
|
||||
browser: false
|
||||
test-regex: src/.*\.test.js
|
11
package.json
11
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@yanick/updeep": "../updeep",
|
||||
"@yanick/updeep": "link:../updeep",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash-es": "^4.17.21",
|
||||
"moize": "^6.1.0",
|
||||
@ -12,15 +12,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.8.4",
|
||||
"@babel/core": "^7.8.7",
|
||||
"@babel/core": "^7.15.5",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.15.4",
|
||||
"@babel/preset-env": "^7.8.7",
|
||||
"@types/jest": "^25.1.4",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/sinon": "^7.5.2",
|
||||
"@typescript-eslint/eslint-plugin": "^2.23.0",
|
||||
"@typescript-eslint/parser": "^2.23.0",
|
||||
"babel-jest": "^25.1.0",
|
||||
"chai": "^4.3.4",
|
||||
"docsify": "^4.11.2",
|
||||
"docsify-cli": "^4.4.0",
|
||||
"docsify-tools": "^1.0.20",
|
||||
@ -30,12 +28,11 @@
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"glob": "^7.1.6",
|
||||
"jest": "^25.1.0",
|
||||
"prettier": "^2.4.1",
|
||||
"promake": "^3.1.3",
|
||||
"sinon": "^9.0.1",
|
||||
"standard-version": "^8.0.0",
|
||||
"tap": "15",
|
||||
"ts-jest": "^25.2.1",
|
||||
"tsd": "^0.11.0",
|
||||
"typedoc": "0.17.7",
|
||||
"typedoc-plugin-markdown": "^2.2.17",
|
||||
|
51
src/Updux.js
51
src/Updux.js
@ -5,7 +5,7 @@ import { buildInitial } from './buildInitial/index.js';
|
||||
import { buildActions } from './buildActions/index.js';
|
||||
import { buildSelectors } from './buildSelectors/index.js';
|
||||
import { action } from './actions.js';
|
||||
|
||||
import { buildUpreducer } from './buildUpreducer.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -17,36 +17,64 @@ import { action } from './actions.js';
|
||||
export class Updux {
|
||||
#initial = {};
|
||||
#subduxes = {};
|
||||
|
||||
/** @type Record<string,Function> */
|
||||
#actions = {};
|
||||
#selectors = {};
|
||||
#mutations = {};
|
||||
|
||||
constructor(config) {
|
||||
this.#initial = config.initial ?? {};
|
||||
this.#subduxes = config.subduxes ?? {};
|
||||
this.#actions = config.actions ?? {};
|
||||
this.#selectors = config.selectors ?? {};
|
||||
|
||||
this.#mutations = config.mutations ?? {};
|
||||
|
||||
Object.keys(this.#mutations)
|
||||
.filter((action) => action !== '*')
|
||||
.filter((action) => !this.actions.hasOwnProperty(action))
|
||||
.forEach((action) => {
|
||||
throw new Error(`action '${action}' is not defined`);
|
||||
});
|
||||
}
|
||||
|
||||
#memoInitial = moize( buildInitial );
|
||||
#memoInitial = moize(buildInitial);
|
||||
#memoActions = moize(buildActions);
|
||||
#memoSelectors = moize(buildSelectors);
|
||||
#memoUpreducer = moize(buildUpreducer);
|
||||
|
||||
get initial() {
|
||||
return this.#memoInitial(this.#initial,this.#subduxes);
|
||||
return this.#memoInitial(this.#initial, this.#subduxes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Record<string,Function>}
|
||||
*/
|
||||
get actions() {
|
||||
return this.#memoActions(this.#actions, this.#subduxes);
|
||||
}
|
||||
|
||||
get selectors() {
|
||||
return this.#memoSelectors(this.#selectors,this.#subduxes);
|
||||
return this.#memoSelectors(this.#selectors, this.#subduxes);
|
||||
}
|
||||
|
||||
get upreducer() {
|
||||
return this.#memoUpreducer(
|
||||
this.initial,
|
||||
this.#mutations,
|
||||
this.#subduxes
|
||||
);
|
||||
}
|
||||
|
||||
get reducer() {
|
||||
return (state, action) => this.upreducer(action)(state);
|
||||
}
|
||||
|
||||
addAction(type, payloadFunc) {
|
||||
const theAction = action(type,payloadFunc);
|
||||
const theAction = action(type, payloadFunc);
|
||||
|
||||
this.#actions = u( { [type]: theAction }, this.#actions );
|
||||
this.#actions = u({ [type]: theAction }, this.#actions);
|
||||
|
||||
return theAction;
|
||||
}
|
||||
@ -55,4 +83,15 @@ export class Updux {
|
||||
this.#selectors[name] = func;
|
||||
return func;
|
||||
}
|
||||
|
||||
addMutation(name, mutation) {
|
||||
if (typeof name === 'function') name = name.type;
|
||||
|
||||
this.#mutations = {
|
||||
...this.#mutations,
|
||||
[name]: mutation,
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,12 @@ test('basic state', async (t) => {
|
||||
subduxes: { alpha, beta },
|
||||
});
|
||||
|
||||
t.same(dux.initial,{
|
||||
t.same(dux.initial, {
|
||||
a: 1,
|
||||
b: 'two',
|
||||
alpha: { sub: 1 },
|
||||
beta: { foo: 1 },
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('basic actions', async (t) => {
|
||||
@ -38,14 +37,14 @@ test('basic actions', async (t) => {
|
||||
const dux = new Updux({
|
||||
subduxes: { alpha, beta },
|
||||
actions: {
|
||||
bar: action('bar' ),
|
||||
bar: action('bar'),
|
||||
},
|
||||
});
|
||||
|
||||
t.same(Object.keys(dux.actions).sort(),['bar', 'foo']);
|
||||
t.same(Object.keys(dux.actions).sort(), ['bar', 'foo']);
|
||||
});
|
||||
|
||||
test('addAction', async(t) => {
|
||||
test('addAction', async (t) => {
|
||||
const dux = new Updux({
|
||||
actions: {
|
||||
bar: action('bar'),
|
||||
@ -53,15 +52,15 @@ test('addAction', async(t) => {
|
||||
});
|
||||
dux.addAction('foo');
|
||||
|
||||
t.same(Object.keys(dux.actions).sort(),['bar', 'foo']);
|
||||
t.same(Object.keys(dux.actions).sort(), ['bar', 'foo']);
|
||||
});
|
||||
|
||||
test('basic selectors', async(t) => {
|
||||
test('basic selectors', { todo: true }, async (t) => {
|
||||
const alpha = new Updux({
|
||||
initial: { quux: 3 },
|
||||
selectors: {
|
||||
getQuux: ({quux}) => quux
|
||||
}
|
||||
getQuux: ({ quux }) => quux,
|
||||
},
|
||||
});
|
||||
|
||||
const dux = new Updux({
|
||||
@ -74,30 +73,36 @@ test('basic selectors', async(t) => {
|
||||
getBar: ({ bar }) => bar,
|
||||
},
|
||||
});
|
||||
dux.addSelector('getFoo', (state) => state.foo)
|
||||
dux.addSelector('getAdd', ({ foo }) => (add) => add + foo)
|
||||
dux.addSelector('getFoo', (state) => state.foo);
|
||||
dux.addSelector(
|
||||
'getAdd',
|
||||
({ foo }) =>
|
||||
(add) =>
|
||||
add + foo
|
||||
);
|
||||
dux.addAction('stuff');
|
||||
|
||||
t.equal(dux.selectors.getBar({ bar: 3 }), 3);
|
||||
t.equal(dux.selectors.getFoo({ foo: 3 }) , 3);
|
||||
t.equal(dux.selectors.getFoo({ foo: 3 }), 3);
|
||||
|
||||
t.equal(alpha.selectors.getQuux({ quux: 1 }),1);
|
||||
t.equal(dux.selectors.getQuux({ alpha: { quux: 1 } }) ,1);
|
||||
t.equal(alpha.selectors.getQuux({ quux: 1 }), 1);
|
||||
t.equal(dux.selectors.getQuux({ alpha: { quux: 1 } }), 1);
|
||||
|
||||
const store = dux.createStore();
|
||||
|
||||
t.equal(store.selectors.getFoo(),1);
|
||||
t.equal(store.selectors.getQuux(),3);
|
||||
t.equal(store.selectors.getAdd(7),8);
|
||||
t.equal(store.selectors.getFoo(), 1);
|
||||
t.equal(store.selectors.getQuux(), 3);
|
||||
t.equal(store.selectors.getAdd(7), 8);
|
||||
});
|
||||
|
||||
test('mutations', async () => {
|
||||
|
||||
test('mutations', { todo: true }, async () => {
|
||||
const alpha = new Updux({
|
||||
initial: { quux: 3 },
|
||||
})
|
||||
.addAction( 'add' )
|
||||
.addMutation( 'add', ( toAdd ) => (state) => ({ quux: state.quux + toAdd }) );
|
||||
});
|
||||
alpha.addAction('add');
|
||||
alpha.addMutation('add', (toAdd) => (state) => ({
|
||||
quux: state.quux + toAdd,
|
||||
}));
|
||||
|
||||
const dux = new Updux({
|
||||
initial: {
|
||||
@ -105,31 +110,38 @@ test('mutations', async () => {
|
||||
bar: 4,
|
||||
},
|
||||
subduxes: { alpha },
|
||||
})
|
||||
.addAction( 'subtract' )
|
||||
.addMutation( 'add', toAdd => state => ({ ...state, foo: state.foo + toAdd }) )
|
||||
.addMutation( 'subtract', toSubtract => state => ({ ...state, bar: state.bar - toSubtract }) );
|
||||
|
||||
});
|
||||
dux.addAction('subtract');
|
||||
dux.addMutation('add', (toAdd) => (state) => ({
|
||||
...state,
|
||||
foo: state.foo + toAdd,
|
||||
}));
|
||||
dux.addMutation('subtract', (toSubtract) => (state) => ({
|
||||
...state,
|
||||
bar: state.bar - toSubtract,
|
||||
}));
|
||||
|
||||
const store = dux.createStore();
|
||||
|
||||
t.same(store.getState(),{
|
||||
foo: 1, bar: 4, alpha: { quux: 3 },
|
||||
})
|
||||
t.same(store.getState(), {
|
||||
foo: 1,
|
||||
bar: 4,
|
||||
alpha: { quux: 3 },
|
||||
});
|
||||
|
||||
store.dispatch.add(10);
|
||||
|
||||
t.same(store.getState(), {
|
||||
foo: 11, bar: 4, alpha: { quux: 13 },
|
||||
})
|
||||
|
||||
foo: 11,
|
||||
bar: 4,
|
||||
alpha: { quux: 13 },
|
||||
});
|
||||
|
||||
store.dispatch.subtract(20);
|
||||
|
||||
t.same(store.getState(), {
|
||||
foo: 11, bar: -16, alpha: { quux: 13 },
|
||||
})
|
||||
|
||||
|
||||
|
||||
foo: 11,
|
||||
bar: -16,
|
||||
alpha: { quux: 13 },
|
||||
});
|
||||
});
|
||||
|
@ -1,17 +1,11 @@
|
||||
|
||||
|
||||
export function action( type, payloadFunction = null ) {
|
||||
|
||||
const generator = function(payloadArg) {
|
||||
|
||||
export function action(type, payloadFunction = null) {
|
||||
const generator = function (payloadArg) {
|
||||
const result = { type };
|
||||
|
||||
if( payloadFunction )
|
||||
result.payload = payloadFunction(payloadArg);
|
||||
if (payloadFunction) result.payload = payloadFunction(payloadArg);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
generator.type = type;
|
||||
|
||||
|
@ -2,16 +2,14 @@ import { test } from 'tap';
|
||||
|
||||
import { action } from './actions.js';
|
||||
|
||||
test( 'action generators', async (t) => {
|
||||
|
||||
test('action generators', async (t) => {
|
||||
const foo = action('foo');
|
||||
|
||||
t.equal( foo.type, 'foo' );
|
||||
t.same( foo(), { type: 'foo' } );
|
||||
t.equal(foo.type, 'foo');
|
||||
t.same(foo(), { type: 'foo' });
|
||||
|
||||
const bar = action('bar');
|
||||
|
||||
t.equal( bar.type, 'bar' );
|
||||
t.same( bar(), { type: 'bar' } );
|
||||
|
||||
} )
|
||||
t.equal(bar.type, 'bar');
|
||||
t.same(bar(), { type: 'bar' });
|
||||
});
|
||||
|
@ -1,10 +1,10 @@
|
||||
export function buildActions(actions={}, subduxes={}) {
|
||||
export function buildActions(actions = {}, subduxes = {}) {
|
||||
// priority => generics => generic subs => craft subs => creators
|
||||
|
||||
const merged = { ...actions };
|
||||
|
||||
Object.values(subduxes).forEach(({ actions }) => {
|
||||
if(!actions) return;
|
||||
if (!actions) return;
|
||||
Object.entries(actions).forEach(([type, func]) => {
|
||||
if (merged[type]) {
|
||||
if (merged[type] === func) return;
|
||||
@ -15,7 +15,6 @@ export function buildActions(actions={}, subduxes={}) {
|
||||
|
||||
merged[type] = func;
|
||||
});
|
||||
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
|
@ -3,9 +3,7 @@ import { test } from 'tap';
|
||||
import { buildInitial } from './index.js';
|
||||
|
||||
test('basic', async (t) => {
|
||||
t.same(
|
||||
buildInitial({ a: 1 }, { b: { initial: { c: 2 } } })
|
||||
,{
|
||||
t.same(buildInitial({ a: 1 }, { b: { initial: { c: 2 } } }), {
|
||||
a: 1,
|
||||
b: { c: 3 },
|
||||
});
|
||||
|
27
src/buildUpreducer.js
Normal file
27
src/buildUpreducer.js
Normal file
@ -0,0 +1,27 @@
|
||||
import u from '@yanick/updeep';
|
||||
import { mapValues } from 'lodash-es';
|
||||
|
||||
export function buildUpreducer(initial, mutations, subduxes = {}) {
|
||||
const subReducers =
|
||||
Object.keys(subduxes).length > 0
|
||||
? mapValues(subduxes, ({ upreducer }) => upreducer)
|
||||
: null;
|
||||
|
||||
return (action) => (state) => {
|
||||
let newState = state ?? initial;
|
||||
|
||||
if (subReducers) {
|
||||
const update = mapValues(subReducers, (upReducer) =>
|
||||
upReducer(action)
|
||||
);
|
||||
|
||||
newState = u(update, newState);
|
||||
}
|
||||
|
||||
const a = mutations[action.type] || mutations['*'];
|
||||
|
||||
if (!a) return newState;
|
||||
|
||||
return a(action.payload, action)(newState);
|
||||
};
|
||||
}
|
103
src/mutations.test.js
Normal file
103
src/mutations.test.js
Normal file
@ -0,0 +1,103 @@
|
||||
import { test } from 'tap';
|
||||
|
||||
import { Updux } from './Updux.js';
|
||||
import { action } from './actions.js';
|
||||
|
||||
test('basic', async (t) => {
|
||||
const doIt = action('doIt');
|
||||
const thisToo = action('thisToo');
|
||||
|
||||
const dux = new Updux({
|
||||
initial: '',
|
||||
actions: { doIt, thisToo },
|
||||
mutations: {
|
||||
doIt: () => () => 'bingo',
|
||||
thisToo: () => () => 'straight type',
|
||||
},
|
||||
});
|
||||
|
||||
t.equal(dux.reducer(undefined, dux.actions.doIt()), 'bingo');
|
||||
|
||||
t.equal(dux.reducer(undefined, dux.actions.thisToo()), 'straight type');
|
||||
});
|
||||
|
||||
test('override', async (t) => {
|
||||
const foo = action('foo');
|
||||
|
||||
const dux = new Updux({
|
||||
initial: { alpha: [] },
|
||||
mutations: {
|
||||
'*': (payload, action) => (state) => ({
|
||||
...state,
|
||||
alpha: [...state.alpha, action.type],
|
||||
}),
|
||||
},
|
||||
subduxes: {
|
||||
subbie: new Updux({
|
||||
initial: 0,
|
||||
actions: {
|
||||
foo,
|
||||
},
|
||||
mutations: {
|
||||
foo: () => (state) => state + 1,
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
let state = [foo(), { type: 'bar' }].reduce(
|
||||
(state, action) => dux.upreducer(action)(state),
|
||||
undefined
|
||||
);
|
||||
|
||||
t.match(state, {
|
||||
alpha: ['foo', 'bar'],
|
||||
subbie: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test('order of processing', async (t) => {
|
||||
const foo = action('foo');
|
||||
|
||||
const dux = new Updux({
|
||||
initial: {
|
||||
x: [],
|
||||
},
|
||||
mutations: {
|
||||
foo:
|
||||
() =>
|
||||
({ x }) => ({ x: [...x, 'main'] }),
|
||||
},
|
||||
subduxes: {
|
||||
x: new Updux({
|
||||
actions: { foo },
|
||||
mutations: {
|
||||
foo: () => (state) => [...state, 'subdux'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
t.same(dux.reducer(undefined, foo()), { x: ['subdux', 'main'] });
|
||||
});
|
||||
|
||||
test('addMutation', async (t) => {
|
||||
const foo = action('foo');
|
||||
|
||||
const dux = new Updux({
|
||||
initial: '',
|
||||
});
|
||||
|
||||
t.equal(dux.reducer(undefined, foo()), '', 'noop');
|
||||
|
||||
dux.addMutation('foo', () => () => 'foo');
|
||||
|
||||
t.equal(dux.reducer(undefined, foo()), 'foo', 'foo was added');
|
||||
|
||||
await t.test('name as function', async (t) => {
|
||||
const bar = action('bar');
|
||||
dux.addMutation(bar, () => () => 'bar');
|
||||
|
||||
t.equal(dux.reducer(undefined, bar()), 'bar', 'bar was added');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user