From d1ed23de2c4358c905a4672fffa33acacdee612e Mon Sep 17 00:00:00 2001 From: Yanick Champoux <yanick@babyl.ca> Date: Mon, 6 Mar 2023 13:04:51 -0500 Subject: [PATCH 1/3] test updux takes in actions --- src/actions.test.todo | 18 ------------------ src/actions.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/actions.test.todo b/src/actions.test.todo index d1e45ee..174cfa2 100644 --- a/src/actions.test.todo +++ b/src/actions.test.todo @@ -5,24 +5,6 @@ import { action } from './actions.js'; import { Updux } from './Updux.js'; -test('Updux config accepts actions', () => { - const foo = new Updux({ - actions: { - one: action('one', (x) => ({ x })), - two: action('two', (x) => x), - }, - }); - - expect(Object.keys(foo.actions)).toHaveLength(2); - - expect(foo.actions.one).toBeTypeOf('function'); - expect(foo.actions.one('potato')).toEqual({ - type: 'one', - payload: { - x: 'potato', - }, - }); -}); test('throw if double action', () => { diff --git a/src/actions.test.ts b/src/actions.test.ts index c051266..99fdeb9 100644 --- a/src/actions.test.ts +++ b/src/actions.test.ts @@ -39,3 +39,28 @@ test('subduxes actions', () => { expect(foo.actions.bar(2)).toHaveProperty('type', 'bar'); expect(foo.actions.baz()).toHaveProperty('type', 'baz'); }); + +test('Updux config accepts actions', () => { + const foo = new Updux({ + actions: { + one: createAction( + 'one', + withPayload((x) => ({ x })), + ), + two: createAction( + 'two', + withPayload((x) => x), + ), + }, + }); + + expect(Object.keys(foo.actions)).toHaveLength(2); + + expect(foo.actions.one).toBeTypeOf('function'); + expect(foo.actions.one('potato')).toEqual({ + type: 'one', + payload: { + x: 'potato', + }, + }); +}); From 000ca9871ab2bc132c3f8800b5872d379f98a5c5 Mon Sep 17 00:00:00 2001 From: Yanick Champoux <yanick@babyl.ca> Date: Mon, 6 Mar 2023 15:07:24 -0500 Subject: [PATCH 2/3] duplicate actions --- src/Updux.ts | 14 ++++++++------ src/actions.test.todo | 18 ------------------ src/actions.test.ts | 36 ++++++++++++++++++++++++++++++++++++ src/actions.ts | 4 ++-- src/buildActions.ts | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 src/buildActions.ts diff --git a/src/Updux.ts b/src/Updux.ts index 7aad5e3..9fd7513 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -8,6 +8,7 @@ import { import { configureStore, Reducer, createAction } from '@reduxjs/toolkit'; import { withPayload } from './actions.js'; import { AggregateActions, Dux, UnionToIntersection } from './types.js'; +import { buildActions } from './buildActions.js'; type ActionCreator = ReturnType<typeof createAction>; @@ -32,7 +33,9 @@ export default class Updux< > = {}; #subduxes: SUBDUXES; - #actions: Record<string, ActionCreator>; + #name: string; + + #actions: AggregateActions<T_LocalActions, SUBDUXES>; constructor( config: Partial<{ @@ -45,13 +48,12 @@ export default class Updux< this.#localInitial = config.initial ?? ({} as T_LocalState); this.#localActions = config.actions ?? ({} as T_LocalActions); this.#subduxes = config.subduxes ?? ({} as SUBDUXES); + + this.#actions = buildActions(this.#localActions, this.#subduxes); } - get actions(): AggregateActions<T_LocalActions, SUBDUXES> { - return R.mergeAll([ - this.#localActions, - ...Object.values(this.#subduxes).map(R.pathOr(['actions'], {})), - ]) as any; + get actions() { + return this.#actions; } // TODO memoize? diff --git a/src/actions.test.todo b/src/actions.test.todo index 174cfa2..e17edf4 100644 --- a/src/actions.test.todo +++ b/src/actions.test.todo @@ -7,24 +7,6 @@ import { Updux } from './Updux.js'; -test('throw if double action', () => { - expect( - () => - new Updux({ - actions: { - foo: action('foo'), - }, - subduxes: { - beta: { - actions: { - foo: action('foo'), - }, - }, - }, - }), - ).toThrow(/action 'foo' already defined/); -}); - test('action definition shortcut', () => { const foo = new Updux({ actions: { diff --git a/src/actions.test.ts b/src/actions.test.ts index 99fdeb9..c39b8be 100644 --- a/src/actions.test.ts +++ b/src/actions.test.ts @@ -64,3 +64,39 @@ test('Updux config accepts actions', () => { }, }); }); + +test('throw if double action', () => { + expect( + () => + new Updux({ + actions: { + foo: createAction('foo'), + }, + subduxes: { + beta: { + actions: { + foo: createAction('foo'), + }, + }, + }, + }), + ).toThrow(/action 'foo' defined both locally and in subdux 'beta'/); + + expect( + () => + new Updux({ + subduxes: { + gamma: { + actions: { + foo: createAction('foo'), + }, + }, + beta: { + actions: { + foo: createAction('foo'), + }, + }, + }, + }), + ).toThrow(/action 'foo' defined both in subduxes 'gamma' and 'beta'/); +}); diff --git a/src/actions.ts b/src/actions.ts index 9bd5453..f8d7dcf 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -3,7 +3,7 @@ import { createAction } from '@reduxjs/toolkit'; export { createAction } from '@reduxjs/toolkit'; interface WithPayload { - (): <P>(input: P) => { payload: P }; + <P>(): (input: P) => { payload: P }; <P, A extends any[]>(prepare: (...args: A) => P): (...input: A) => { payload: P; }; @@ -11,5 +11,5 @@ interface WithPayload { export const withPayload: WithPayload = ((prepare) => (...input) => ({ - payload: prepare ? prepare(...input) : input, + payload: prepare ? prepare(...input) : input[0], })) as any; diff --git a/src/buildActions.ts b/src/buildActions.ts new file mode 100644 index 0000000..21f0cef --- /dev/null +++ b/src/buildActions.ts @@ -0,0 +1,33 @@ +import * as R from 'remeda'; + +export function buildActions(localActions, subduxes) { + let actions: Record<string, string> = {}; + + for (const slice in subduxes) { + const subdux = subduxes[slice].actions; + + if (!subdux) continue; + + for (const a in subdux) { + if (actions[a]) { + throw new Error( + `action '${a}' defined both in subduxes '${actions[a]}' and '${slice}'`, + ); + } + actions[a] = slice; + } + } + + for (const a in localActions) { + if (actions[a]) { + throw new Error( + `action '${a}' defined both locally and in subdux '${actions[a]}'`, + ); + } + } + + return R.mergeAll([ + localActions, + ...Object.values(subduxes).map(R.pathOr<any, any>(['actions'], {})), + ]) as any; +} From 88808507ad9c9652e05143163e864e435b45b444 Mon Sep 17 00:00:00 2001 From: Yanick Champoux <yanick@babyl.ca> Date: Mon, 6 Mar 2023 16:07:22 -0500 Subject: [PATCH 3/3] all action tests are done --- src/Updux.ts | 7 ++++++- src/actions.test.todo | 23 ----------------------- src/actions.test.ts | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 24 deletions(-) delete mode 100644 src/actions.test.todo diff --git a/src/Updux.ts b/src/Updux.ts index 9fd7513..3932ec8 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -5,7 +5,12 @@ import { DeepPartial, Action, } from 'redux'; -import { configureStore, Reducer, createAction } from '@reduxjs/toolkit'; +import { + configureStore, + Reducer, + createAction, + PrepareAction, +} from '@reduxjs/toolkit'; import { withPayload } from './actions.js'; import { AggregateActions, Dux, UnionToIntersection } from './types.js'; import { buildActions } from './buildActions.js'; diff --git a/src/actions.test.todo b/src/actions.test.todo deleted file mode 100644 index e17edf4..0000000 --- a/src/actions.test.todo +++ /dev/null @@ -1,23 +0,0 @@ -import { test, expect } from 'vitest'; - -import { action } from './actions.js'; - -import { Updux } from './Updux.js'; - - - - -test('action definition shortcut', () => { - const foo = new Updux({ - actions: { - foo: null, - bar: (x) => ({ x }), - }, - }); - - expect(foo.actions.foo('hello')).toEqual({ type: 'foo', payload: 'hello' }); - expect(foo.actions.bar('hello')).toEqual({ - type: 'bar', - payload: { x: 'hello' }, - }); -}); diff --git a/src/actions.test.ts b/src/actions.test.ts index c39b8be..b74eb21 100644 --- a/src/actions.test.ts +++ b/src/actions.test.ts @@ -100,3 +100,18 @@ test('throw if double action', () => { }), ).toThrow(/action 'foo' defined both in subduxes 'gamma' and 'beta'/); }); + +test.todo('action definition shortcut', () => { + const foo = new Updux({ + actions: { + foo: undefined, + bar: (x) => ({ x }), + }, + }); + + expect(foo.actions.foo('hello')).toEqual({ type: 'foo', payload: 'hello' }); + expect(foo.actions.bar('hello')).toEqual({ + type: 'bar', + payload: { x: 'hello' }, + }); +});