Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix async test #103

Merged
merged 4 commits into from
May 27, 2023
Merged
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
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,6 @@ This is a work in progress. Submit a PR!
## Road Map

- [ ] create nicer API, or a helper hook in react land (useTemporal). or vanilla version of the it
- [ ] create a `present` object that holds the current state? perhaps
- [ ] support history branches rather than clearing the future states
- [ ] store state delta rather than full object
- [ ] track state for multiple stores at once
Expand Down
152 changes: 150 additions & 2 deletions __tests__/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import type {
TemporalState,
Write,
} from '../src/types';
import throttle from '../node_modules/lodash.throttle';
import throttle from 'lodash.throttle';
import { persist } from 'zustand/middleware';

interface MyState {
count: number;
Expand Down Expand Up @@ -402,6 +403,12 @@ describe('Middleware options', () => {
increment();
doNothing();
});
expect(storeWithHandleSet.temporal.getState().pastStates[0]).toContain({
count: 0,
});
expect(storeWithHandleSet.temporal.getState().pastStates[1]).toContain({
count: 1,
});
expect(storeWithHandleSet.temporal.getState().pastStates.length).toBe(2);
expect(console.info).toHaveBeenCalledTimes(2);
act(() => {
Expand All @@ -414,6 +421,43 @@ describe('Middleware options', () => {
expect(console.info).toHaveBeenCalledTimes(2);
});

it('should call function if set (wrapTemporal)', () => {
global.console.info = vi.fn();
const storeWithHandleSet = createVanillaStore({
wrapTemporal: (config) => {
return (_set, get, store) => {
const set: typeof _set = (partial, replace) => {
console.info('handleSet called');
_set(partial, replace);
};
return config(set, get, store);
};
},
});
const { doNothing, increment } = storeWithHandleSet.getState();
act(() => {
increment();
doNothing();
});
expect(storeWithHandleSet.temporal.getState().pastStates[0]).toContain({
count: 0,
});
expect(storeWithHandleSet.temporal.getState().pastStates[1]).toContain({
count: 1,
});
expect(storeWithHandleSet.temporal.getState().pastStates.length).toBe(2);
expect(console.info).toHaveBeenCalledTimes(2);
act(() => {
storeWithHandleSet.temporal.getState().undo(2);
});
expect(storeWithHandleSet.temporal.getState().pastStates.length).toBe(0);
expect(storeWithHandleSet.temporal.getState().futureStates.length).toBe(
2,
);
// Note: in the above test, the handleSet function is called twice, but in this test it is called 3 times because it is also called when undo() and redo() are called.
expect(console.info).toHaveBeenCalledTimes(3);
});

it('should correctly use throttling', () => {
global.console.error = vi.fn();
vi.useFakeTimers();
Expand All @@ -426,6 +470,59 @@ describe('Middleware options', () => {
},
});
const { doNothing, increment } = storeWithHandleSet.getState();
act(() => {
increment();
increment();
increment();
increment();
});
expect(storeWithHandleSet.temporal.getState().pastStates.length).toBe(1);
expect(console.error).toHaveBeenCalledTimes(1);
vi.advanceTimersByTime(1001);
// By default, lodash.throttle includes trailing event
expect(storeWithHandleSet.temporal.getState().pastStates.length).toBe(2);
expect(console.error).toHaveBeenCalledTimes(2);
act(() => {
doNothing();
doNothing();
doNothing();
doNothing();
});
expect(storeWithHandleSet.temporal.getState().pastStates.length).toBe(3);
expect(console.error).toHaveBeenCalledTimes(3);
vi.advanceTimersByTime(1001);
expect(storeWithHandleSet.temporal.getState().pastStates.length).toBe(4);
expect(console.error).toHaveBeenCalledTimes(4);
act(() => {
// Does not call handle set (and is not throttled)
storeWithHandleSet.temporal.getState().undo(4);
storeWithHandleSet.temporal.getState().redo(1);
});
expect(storeWithHandleSet.temporal.getState().pastStates.length).toBe(1);
expect(storeWithHandleSet.temporal.getState().futureStates.length).toBe(
3,
);
expect(console.error).toHaveBeenCalledTimes(4);
vi.useRealTimers();
});
it('should correctly use throttling (wrapTemporal)', () => {
global.console.error = vi.fn();
vi.useFakeTimers();
const storeWithHandleSet = createVanillaStore({
wrapTemporal: (config) => {
return (_set, get, store) => {
const set: typeof _set = throttle<typeof _set>(
(partial, replace) => {
console.error('handleSet called');
_set(partial, replace);
},
1000,
);
return config(set, get, store);
};
},
});
const { doNothing, increment } = storeWithHandleSet.getState();
act(() => {
increment();
});
Expand All @@ -450,6 +547,39 @@ describe('Middleware options', () => {
});
});

describe('wrapTemporal', () => {
describe('should wrap temporal store in given middlewares', () => {
it('persist', () => {
const storeWithTemporalWithPersist = createVanillaStore({
wrapTemporal: (config) => persist(config, { name: '123' }),
});

expect(storeWithTemporalWithPersist.temporal).toHaveProperty('persist');
});

it('temporal', () => {
const storeWithTemporalWithTemporal = createVanillaStore({
wrapTemporal: (store) => temporal(store),
});
expect(storeWithTemporalWithTemporal.temporal).toHaveProperty(
'temporal',
);
});

it('temporal and persist', () => {
const storeWithTemporalWithMiddleware = createVanillaStore({
wrapTemporal: (store) => temporal(persist(store, { name: '123' })),
});
expect(storeWithTemporalWithMiddleware.temporal).toHaveProperty(
'persist',
);
expect(storeWithTemporalWithMiddleware.temporal).toHaveProperty(
'temporal',
);
});
});
});

describe('secret internals', () => {
it('should have a secret internal state', () => {
const { _handleSet, _onSave } =
Expand Down Expand Up @@ -488,7 +618,7 @@ describe('Middleware options', () => {
_onSave(storeWithOnSave.getState(), storeWithOnSave.getState());
});
expect(storeWithOnSave.temporal.getState().pastStates.length).toBe(0);
expect(console.error).toHaveBeenCalledTimes(1);
expect(console.info).toHaveBeenCalledTimes(1);
});
it('should call onSave cb without adding a new state and respond to new setOnSave', () => {
global.console.dir = vi.fn();
Expand Down Expand Up @@ -596,3 +726,21 @@ describe('Middleware options', () => {
});
});
});

describe('setState', () => {
it('it should correctly update the state', () => {
const store = createVanillaStore();
const setState = store.setState;
act(() => {
setState({ count: 100 });
});
expect(store.getState().count).toBe(100);
expect(store.temporal.getState().pastStates.length).toBe(1);
act(() => {
store.temporal.getState().undo();
});
expect(store.getState().count).toBe(0);
expect(store.temporal.getState().pastStates.length).toBe(0);
expect(store.temporal.getState().futureStates.length).toBe(1);
});
});
1 change: 1 addition & 0 deletions examples/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"start": "next start"
},
"dependencies": {
"just-throttle": "4.2.0",
"lodash.merge": "4.6.2",
"lodash.throttle": "4.1.1",
"next": "13.4.3",
Expand Down
120 changes: 120 additions & 0 deletions examples/web/pages/throttle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { temporal } from 'zundo';
import { create } from 'zustand';
import throttle from 'just-throttle';

interface MyState {
bears: number;
bees: number;
increment: () => void;
decrement: () => void;
incrementBees: () => void;
decrementBees: () => void;
}

const useMyStore = create(
temporal<MyState>(
(set) => ({
bears: 0,
bees: 10,
increment: () => set((state) => ({ bears: state.bears + 1 })),
decrement: () => set((state) => ({ bears: state.bears - 1 })),
incrementBees: () => set((state) => ({ bees: state.bees + 1 })),
decrementBees: () => set((state) => ({ bees: state.bees - 1 })),
}),
{
wrapTemporal: (config) => {
const thing: typeof config = (_set, get, store) => {
const set: typeof _set = throttle((partial, replace) => {
console.info('handleSet called');
console.log(
'calling wrapped setter',
JSON.stringify(partial, null, 2),
);
_set(partial, replace);
}, 1000);
return config(set, get, store);
};
return thing;
},
},
),
);
const useTemporalStore = create(useMyStore.temporal);

const UndoBar = () => {
const { undo, redo, futureStates, pastStates } = useTemporalStore();
return (
<div>
past states: {JSON.stringify(pastStates)}
<br />
future states: {JSON.stringify(futureStates)}
<br />
<button onClick={() => undo()} disabled={!pastStates.length}>
undo
</button>
<button onClick={() => redo()} disabled={!futureStates.length}>
redo
</button>
</div>
);
};

const StateBear = () => {
const store = useMyStore((state) => ({
bears: state.bears,
increment: state.increment,
decrement: state.decrement,
}));
const { bears, increment, decrement } = store;
return (
<div>
current state: {JSON.stringify(store)}
<br />
<br />
bears: {bears}
<br />
<button onClick={increment}>increment</button>
<button onClick={decrement}>decrement</button>
</div>
);
};

const StateBee = () => {
const store = useMyStore();
console.log(store);
const { bees, increment, decrement } = store;
return (
<div>
current state: {JSON.stringify(store)}
<br />
<br />
bees: {bees}
<br />
<button onClick={increment}>increment</button>
<button onClick={decrement}>decrement</button>
</div>
);
};

const App = () => {
return (
<div>
<h1>
{' '}
<span role="img" aria-label="bear">
🐻
</span>{' '}
<span role="img" aria-label="recycle">
♻️
</span>{' '}
Zundo!
</h1>
<StateBear />
<StateBee />
<br />
<UndoBar />
</div>
);
};

export default App;
Loading