Skip to content

Commit

Permalink
fix async test (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
charkour committed May 27, 2023
1 parent e175c75 commit 6884ed9
Show file tree
Hide file tree
Showing 6 changed files with 397 additions and 3 deletions.
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

0 comments on commit 6884ed9

Please sign in to comment.