Skip to content
Draft

v3 #157

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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zundo",
"version": "2.2.0",
"version": "3.0.0-beta.0",
"private": false,
"description": "🍜 undo/redo middleware for zustand",
"keywords": [
Expand Down
35 changes: 31 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,38 @@ export const temporal = (<TState>(
temporalStateCreator(set, get, options),
);

const handleSet = (
pastState: TState,
// `replace` will likely be deprecated and removed in the future
replace: boolean | undefined,
currentState: TState,
deltaState?: Partial<TState>,
) => {
if (store.temporal.getState().isTracking) {
// This naively assumes that only one new state can be added at a time
if (
options?.limit &&
store.temporal.getState().pastStates.length >= options?.limit
) {
store.temporal.getState().pastStates.shift();
}

(store.temporal.getState() as _TemporalState<TState>)._onSave?.(
pastState,
currentState,
);
store.temporal.setState({
pastStates: store.temporal
.getState()
.pastStates.concat(deltaState || pastState),
futureStates: [],
});
}
};

const curriedHandleSet =
options?.handleSet?.(
(store.temporal.getState() as _TemporalState<TState>)
._handleSet as StoreApi<TState>['setState'],
) || (store.temporal.getState() as _TemporalState<TState>)._handleSet;
options?.handleSet?.(handleSet as StoreApi<TState>['setState']) ||
handleSet;

const temporalHandleSet = (pastState: TState) => {
if (!store.temporal.getState().isTracking) return;
Expand Down
12 changes: 0 additions & 12 deletions src/temporal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,6 @@ export const temporalStateCreator = <TState>(
setOnSave: (_onSave) => set({ _onSave }),
// Internal properties
_onSave: options?.onSave,
_handleSet: (pastState, replace, currentState, deltaState) => {
// This naively assumes that only one new state can be added at a time
if (options?.limit && get().pastStates.length >= options?.limit) {
get().pastStates.shift();
}

get()._onSave?.(pastState, currentState);
set({
pastStates: get().pastStates.concat(deltaState || pastState),
futureStates: [],
});
},
};
};

Expand Down
12 changes: 1 addition & 11 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ export interface _TemporalState<TState> {

setOnSave: (onSave: onSave<TState>) => void;
_onSave: onSave<TState>;
_handleSet: (
pastState: TState,
// `replace` will likely be deprecated and removed in the future
replace: Parameters<StoreApi<TState>['setState']>[1],
currentState: TState,
deltaState?: Partial<TState> | null,
) => void;
}

export interface ZundoOptions<TState, PartialTState = TState> {
Expand Down Expand Up @@ -60,7 +53,4 @@ export interface ZundoOptions<TState, PartialTState = TState> {

export type Write<T, U> = Omit<T, keyof U> & U;

export type TemporalState<TState> = Omit<
_TemporalState<TState>,
'_onSave' | '_handleSet'
>;
export type TemporalState<TState> = Omit<_TemporalState<TState>, '_onSave'>;
41 changes: 25 additions & 16 deletions tests/__tests__/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ describe('Middleware options', () => {
console.info('handleSet called');
_set(partial, replace);
};
store.setState = set;
return config(set, get, store);
};
},
Expand Down Expand Up @@ -661,6 +662,28 @@ describe('Middleware options', () => {
expect(console.info).toHaveBeenCalledTimes(3);
});

it('should not call function if `store.setState` is not assigned to `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);
};
// intentionally commented out
// store.setState = set;
return config(set, get, store);
};
},
});
const { increment } = storeWithHandleSet.getState();
act(() => {
increment();
});
expect(console.info).toHaveBeenCalledTimes(0);
});

it('should correctly use throttling', () => {
global.console.error = vi.fn();
vi.useFakeTimers();
Expand Down Expand Up @@ -722,6 +745,7 @@ describe('Middleware options', () => {
},
1000,
);
store.setState = set;
return config(set, get, store);
};
},
Expand Down Expand Up @@ -964,9 +988,7 @@ describe('Middleware options', () => {

describe('secret internals', () => {
it('should have a secret internal state', () => {
const { _handleSet, _onSave } =
store.temporal.getState() as _TemporalState<MyState>;
expect(_handleSet).toBeInstanceOf(Function);
const { _onSave } = store.temporal.getState() as _TemporalState<MyState>;
expect(_onSave).toBe(undefined);
});
describe('onSave', () => {
Expand Down Expand Up @@ -1035,19 +1057,6 @@ describe('Middleware options', () => {
expect(console.trace).toHaveBeenCalledTimes(1);
});
});

describe('handleUserSet', () => {
it('should update the temporal store with the pastState when called', () => {
const { _handleSet } =
store.temporal.getState() as _TemporalState<MyState>;
act(() => {
_handleSet(store.getState(), undefined, store.getState(), null);
});
expect(store.temporal.getState().pastStates.length).toBe(1);
});

// TODO: should this check the equality function, limit, and call onSave? These are already tested but indirectly.
});
});

describe('init pastStates', () => {
Expand Down