Skip to content
Open
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
8 changes: 5 additions & 3 deletions src/helpers/undoRedo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@ export function undoRedo<T>(obs$: ObservablePrimitive<T>, options?: UndoRedoOpti
// We're just going to store a copy of the whole object every time it changes.
const snapshot = internal.clone(obs$.get());

// Always clear any redo history first (items after current position)
history = history.slice(0, historyPointer + 1);

// Then apply the limit if specified
if (options?.limit) {
// limit means the number of undos
// limit means the number of undos, keep the most recent ones
history = history.slice(Math.max(0, history.length - options.limit));
} else {
history = history.slice(0, historyPointer + 1);
}

// we add another history item, which is limit + 1 -- but it's the current one
Expand Down
35 changes: 35 additions & 0 deletions tests/history.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,39 @@ describe('Undo/Redo', () => {
expect(undos$.get()).toBe(2);
expect(redos$.get()).toBe(1);
});

test('Undo/Redo with limit clears redo stack on new change', () => {
// Regression test: when using a limit, making a change after undo should clear the redo stack
const obs$ = observable({ value: 'A' });
const { undo, undos$, redos$, getHistory } = undoRedo(obs$, { limit: 10 });

// Make some changes
obs$.value.set('B');
obs$.value.set('C');

expect(undos$.get()).toBe(2);
expect(redos$.get()).toBe(0);
expect(getHistory()).toEqual([{ value: 'A' }, { value: 'B' }, { value: 'C' }]);

// Undo once (C -> B)
undo();
expect(obs$.get()).toEqual({ value: 'B' });
expect(undos$.get()).toBe(1);
expect(redos$.get()).toBe(1); // Can redo to C

// Make a NEW change - this should clear the redo stack
obs$.value.set('D');
expect(obs$.get()).toEqual({ value: 'D' });
expect(undos$.get()).toBe(2); // A -> B -> D
expect(redos$.get()).toBe(0); // Redo stack should be cleared!

// History should NOT contain C anymore
expect(getHistory()).toEqual([{ value: 'A' }, { value: 'B' }, { value: 'D' }]);

// Undo should go back to B, not C
undo();
expect(obs$.get()).toEqual({ value: 'B' });
expect(undos$.get()).toBe(1);
expect(redos$.get()).toBe(1); // Can redo to D (not C)
});
});