Skip to content

Commit

Permalink
Cross store actions
Browse files Browse the repository at this point in the history
albertogasparin committed May 30, 2023
1 parent 17183b6 commit 1f0cb01
Showing 13 changed files with 327 additions and 209 deletions.
9 changes: 7 additions & 2 deletions examples/advanced-shared/components/color.tsx
Original file line number Diff line number Diff line change
@@ -13,15 +13,20 @@ const initialState: State = {
color: 'white',
};

const actions = {
export const actions = {
set:
(color: string) =>
({ setState }: StoreActionApi<State>) => {
setState({ color });
},
reset:
() =>
({ setState }: StoreActionApi<State>) => {
setState(initialState);
},
};

const Store = createStore({
export const Store = createStore({
initialState,
actions,
containedBy: ThemingContainer,
13 changes: 13 additions & 0 deletions examples/advanced-shared/components/width.tsx
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import {
type StoreActionApi,
} from 'react-sweet-state';
import { ThemingContainer } from './theming';
import { Store as ColorStore, actions as colorActions } from './color';

type State = {
width: number;
@@ -19,6 +20,18 @@ const actions = {
({ setState }: StoreActionApi<State>) => {
setState({ width });
},
reset:
() =>
({ setState }: StoreActionApi<State>) => {
setState(initialState);
},

resetAll:
() =>
({ dispatch, dispatchTo }: StoreActionApi<State>) => {
dispatch(actions.reset());
dispatchTo(ColorStore, colorActions.reset());
},
};

const Store = createStore({
12 changes: 8 additions & 4 deletions examples/advanced-shared/index.tsx
Original file line number Diff line number Diff line change
@@ -4,18 +4,21 @@ import ReactDOM from 'react-dom/client';
import { useColor } from './components/color';
import { useWidth } from './components/width';
import { ThemingContainer } from './components/theming';
import { defaults } from 'react-sweet-state';

const colors = ['white', 'aliceblue', 'beige', 'gainsboro', 'honeydew'];
const widths = [200, 220, 240, 260, 280];
defaults.devtools = true;

const colors = ['aliceblue', 'beige', 'gainsboro', 'honeydew'];
const widths = [220, 240, 260, 280];
const rand = () => Math.floor(Math.random() * colors.length);
const initialData = { color: colors[rand()], width: widths[rand()] };
const initialData = { color: 'white', width: 200 };

/**
* Components
*/
const ThemeHook = ({ title }: { title: string }) => {
const [{ color }, { set: setColor }] = useColor();
const [{ width }, { set: setWidth }] = useWidth();
const [{ width }, { set: setWidth, resetAll }] = useWidth();

return (
<div style={{ background: color, width }}>
@@ -24,6 +27,7 @@ const ThemeHook = ({ title }: { title: string }) => {
<p>Width: {width}</p>
<button onClick={() => setColor(colors[rand()])}>Change color</button>
<button onClick={() => setWidth(widths[rand()])}>Change width</button>
<button onClick={() => resetAll()}>Reset all</button>
</div>
);
};
156 changes: 153 additions & 3 deletions src/__tests__/integration.test.js
Original file line number Diff line number Diff line change
@@ -181,9 +181,7 @@ describe('Integration', () => {

const state3 = { loading: false, todos: ['todoB'] };
const call3 = 3;
// its 3+1 because on scope change we do NOT use context and force notify
// causing ones that have naturally re-rendered already to re-render once more.
expect(children1.mock.calls[call3 + 1]).toEqual([state3, expectActions]);
expect(children1.mock.calls[call3]).toEqual([state3, expectActions]);
expect(children2.mock.calls[call3]).toEqual([state3, expectActions]);
});

@@ -487,4 +485,156 @@ describe('Integration', () => {
}).toThrow(/should be contained/);
errorSpy.mockRestore();
});

describe('dispatchTo', () => {
const createTestElements = ({ mainContainer, otherContainer }) => {
const actionOther =
(n) =>
({ setState }, { plus }) =>
setState({ count: n, plus });
const StoreOther = createStore({
name: 'store-other',
containedBy: otherContainer,
initialState: {},
actions: { set: actionOther },
});
const StoreMain = createStore({
name: 'store-main',
containedBy: mainContainer,
initialState: {},
actions: {
setOther:
(n) =>
({ dispatchTo }) =>
dispatchTo(StoreOther, actionOther(n)),
},
});

const MainSubscriber = createSubscriber(StoreMain);
const OtherSubscriber = createSubscriber(StoreOther);
const mainSpy = jest.fn().mockReturnValue(null);
const otherSpy = jest.fn().mockReturnValue(null);

const Content = () => (
<>
<MainSubscriber>{mainSpy}</MainSubscriber>
<OtherSubscriber>{otherSpy}</OtherSubscriber>
</>
);
return {
Content,
StoreMain,
mainReturn: (n = 0) => mainSpy.mock.calls[n],
otherReturn: (n = 0) => otherSpy.mock.calls[n],
};
};

it('should allow dispatchTo global -> global', () => {
const { Content, mainReturn, otherReturn } = createTestElements({
mainContainer: null,
otherContainer: null,
});

render(<Content />);
const [, mainActions] = mainReturn(0);
act(() => mainActions.setOther(1));

expect(otherReturn(1)).toEqual([{ count: 1 }, expect.any(Object)]);
});

it('should allow dispatchTo contained -> contained', () => {
const SharedContainer = createContainer();
const { Content, mainReturn, otherReturn } = createTestElements({
mainContainer: SharedContainer,
otherContainer: SharedContainer,
});

render(
<SharedContainer>
<Content />
</SharedContainer>
);
const [, mainActions] = mainReturn(0);
act(() => mainActions.setOther(1));

expect(otherReturn(1)).toEqual([{ count: 1 }, expect.any(Object)]);
});

it('should allow dispatchTo contained -> global', () => {
const MainContainer = createContainer();
const { Content, mainReturn, otherReturn } = createTestElements({
mainContainer: MainContainer,
otherContainer: null,
});

render(
<MainContainer>
<Content />
</MainContainer>
);
const [, mainActions] = mainReturn(0);
act(() => mainActions.setOther(1));

expect(otherReturn(1)).toEqual([{ count: 1 }, expect.any(Object)]);
});

it('should allow dispatchTo global -> contained if properly contained', () => {
const OtherContainer = createContainer({ displayName: 'OtherContainer' });
const { Content, mainReturn, otherReturn } = createTestElements({
mainContainer: null,
otherContainer: OtherContainer,
});

render(
<OtherContainer>
<Content />
</OtherContainer>
);
const [, mainActions] = mainReturn(0);
act(() => mainActions.setOther(1));

expect(otherReturn(1)).toEqual([{ count: 1 }, expect.any(Object)]);
});

it('should allow dispatchTo contained -> other contained', async () => {
const MainContainer = createContainer();
const OtherContainer = createContainer();

const { Content, mainReturn, otherReturn } = createTestElements({
mainContainer: MainContainer,
otherContainer: OtherContainer,
});

render(
<OtherContainer>
<MainContainer>
<Content />
</MainContainer>
</OtherContainer>
);
const [, mainActions] = mainReturn(0);
act(() => mainActions.setOther(1));

expect(otherReturn(1)).toEqual([{ count: 1 }, expect.any(Object)]);
});

it('should allow dispatchTo override -> contained', async () => {
const { Content, StoreMain, mainReturn, otherReturn } =
createTestElements({
mainContainer: null,
otherContainer: null,
});
const OverrideContainer = createContainer(StoreMain);

render(
<OverrideContainer>
<Content />
</OverrideContainer>
);
const [, mainActions] = mainReturn(0);
act(() => mainActions.setOther(1));

expect(otherReturn(1)).toEqual([{ count: 1 }, expect.any(Object)]);
});
});
});
32 changes: 22 additions & 10 deletions src/components/__tests__/container.test.js
Original file line number Diff line number Diff line change
@@ -60,12 +60,6 @@ describe('Container', () => {
describe('createContainer', () => {
it('should return a Container component', () => {
expect(Container.displayName).toEqual('Container(test)');
expect(Container.storeType).toEqual(Store);
expect(Container.hooks).toEqual({
onInit: expect.any(Function),
onUpdate: expect.any(Function),
onCleanup: expect.any(Function),
});
});
});

@@ -88,7 +82,11 @@ describe('Container', () => {
const children = <Subscriber>{() => null}</Subscriber>;
render(<Container scope="s1">{children}</Container>);

expect(defaultRegistry.getStore).toHaveBeenCalledWith(Store, 's1');
expect(defaultRegistry.getStore).toHaveBeenCalledWith(
Store,
's1',
expect.any(Function)
);
expect(mockLocalRegistry.getStore).not.toHaveBeenCalled();
});

@@ -110,15 +108,23 @@ describe('Container', () => {
</Container>
);

expect(defaultRegistry.getStore).toHaveBeenCalledWith(Store, 's2');
expect(defaultRegistry.getStore).toHaveBeenCalledWith(
Store,
's2',
expect.any(Function)
);
});

it('should get local storeState if local matching', () => {
const Subscriber = createSubscriber(Store);
const children = <Subscriber>{() => null}</Subscriber>;
render(<Container>{children}</Container>);

expect(mockLocalRegistry.getStore).toHaveBeenCalledWith(Store, undefined);
expect(mockLocalRegistry.getStore).toHaveBeenCalledWith(
Store,
undefined,
expect.any(Function)
);
expect(defaultRegistry.getStore).not.toHaveBeenCalled();
});

@@ -127,7 +133,11 @@ describe('Container', () => {
const children = <Subscriber>{() => null}</Subscriber>;
render(<Container isGlobal>{children}</Container>);

expect(defaultRegistry.getStore).toHaveBeenCalledWith(Store, undefined);
expect(defaultRegistry.getStore).toHaveBeenCalledWith(
Store,
undefined,
expect.any(Function)
);
expect(mockLocalRegistry.getStore).not.toHaveBeenCalled();
});

@@ -229,6 +239,7 @@ describe('Container', () => {
setState: expect.any(Function),
actions: expect.any(Object),
dispatch: expect.any(Function),
dispatchTo: expect.any(Function),
},
{ defaultCount: 5 }
);
@@ -256,6 +267,7 @@ describe('Container', () => {
setState: expect.any(Function),
actions: expect.any(Object),
dispatch: expect.any(Function),
dispatchTo: expect.any(Function),
},
{ defaultCount: 6 }
);
6 changes: 5 additions & 1 deletion src/components/__tests__/hook.test.js
Original file line number Diff line number Diff line change
@@ -61,7 +61,11 @@ describe('Hook', () => {
it('should get the storeState from registry', () => {
const { getRender } = setup();
getRender();
expect(defaultRegistry.getStore).toHaveBeenCalledWith(StoreMock);
expect(defaultRegistry.getStore).toHaveBeenCalledWith(
StoreMock,
undefined,
expect.any(Function)
);
});

it('should render children with store data and actions', () => {
Loading

0 comments on commit 1f0cb01

Please sign in to comment.