Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions examples/advanced-shared/components/color.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions examples/advanced-shared/components/width.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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({
Expand Down
12 changes: 8 additions & 4 deletions examples/advanced-shared/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}>
Expand All @@ -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>
);
};
Expand Down
156 changes: 153 additions & 3 deletions src/__tests__/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
});

Expand Down Expand Up @@ -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
Expand Up @@ -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),
});
});
});

Expand All @@ -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();
});

Expand All @@ -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();
});

Expand All @@ -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();
});

Expand Down Expand Up @@ -229,6 +239,7 @@ describe('Container', () => {
setState: expect.any(Function),
actions: expect.any(Object),
dispatch: expect.any(Function),
dispatchTo: expect.any(Function),
},
{ defaultCount: 5 }
);
Expand Down Expand Up @@ -256,6 +267,7 @@ describe('Container', () => {
setState: expect.any(Function),
actions: expect.any(Object),
dispatch: expect.any(Function),
dispatchTo: expect.any(Function),
},
{ defaultCount: 6 }
);
Expand Down
6 changes: 5 additions & 1 deletion src/components/__tests__/hook.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Loading