Skip to content

Commit 7434d41

Browse files
committed
test(undo-redo): ensure no undo after clearing the undo-redo stacks
1 parent 5331846 commit 7434d41

File tree

1 file changed

+101
-27
lines changed

1 file changed

+101
-27
lines changed

libs/ngrx-toolkit/src/lib/with-undo-redo.spec.ts

Lines changed: 101 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
import { patchState, signalStore, type, withComputed, withMethods, withState } from '@ngrx/signals';
1+
import { computed, inject } from '@angular/core';
22
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
3-
import { withUndoRedo } from './with-undo-redo';
3+
import {
4+
patchState,
5+
signalStore,
6+
type,
7+
withComputed,
8+
withMethods,
9+
withState,
10+
} from '@ngrx/signals';
411
import { addEntity, withEntities } from '@ngrx/signals/entities';
5-
import { computed, inject } from '@angular/core';
612
import { withCallState } from './with-call-state';
13+
import { withUndoRedo } from './with-undo-redo';
714

815
const testState = { test: '' };
916
const testKeys = ['test' as const];
@@ -13,7 +20,10 @@ const newerValue = 'newer value';
1320
describe('withUndoRedo', () => {
1421
it('adds methods for undo, redo, canUndo, canRedo', () => {
1522
TestBed.runInInjectionContext(() => {
16-
const Store = signalStore(withState(testState), withUndoRedo({ keys: testKeys }));
23+
const Store = signalStore(
24+
withState(testState),
25+
withUndoRedo({ keys: testKeys })
26+
);
1727
const store = new Store();
1828

1929
expect(Object.keys(store)).toEqual([
@@ -22,39 +32,55 @@ describe('withUndoRedo', () => {
2232
'canRedo',
2333
'undo',
2434
'redo',
25-
'clearStack'
35+
'clearStack',
2636
]);
2737
});
2838
});
2939

3040
it('should check keys and collection types', () => {
31-
signalStore(withState(testState),
41+
signalStore(
42+
withState(testState),
3243
// @ts-expect-error - should not allow invalid keys
33-
withUndoRedo({ keys: ['tes'] }));
34-
signalStore(withState(testState),
44+
withUndoRedo({ keys: ['tes'] })
45+
);
46+
signalStore(
47+
withState(testState),
3548
withEntities({ entity: type(), collection: 'flight' }),
3649
// @ts-expect-error - should not allow invalid keys when entities are present
37-
withUndoRedo({ keys: ['flightIdsTest'] }));
38-
signalStore(withState(testState),
50+
withUndoRedo({ keys: ['flightIdsTest'] })
51+
);
52+
signalStore(
53+
withState(testState),
3954
// @ts-expect-error - should not allow collections without named entities
40-
withUndoRedo({ collections: ['tee'] }));
41-
signalStore(withState(testState), withComputed(store => ({ testComputed: computed(() => store.test()) })),
55+
withUndoRedo({ collections: ['tee'] })
56+
);
57+
signalStore(
58+
withState(testState),
59+
withComputed((store) => ({ testComputed: computed(() => store.test()) })),
4260
// @ts-expect-error - should not allow collections without named entities with other computed
43-
withUndoRedo({ collections: ['tested'] }));
44-
signalStore(withEntities({ entity: type() }),
61+
withUndoRedo({ collections: ['tested'] })
62+
);
63+
signalStore(
64+
withEntities({ entity: type() }),
4565
// @ts-expect-error - should not allow collections without named entities
46-
withUndoRedo({ collections: ['test'] }));
47-
signalStore(withEntities({ entity: type(), collection: 'flight' }),
66+
withUndoRedo({ collections: ['test'] })
67+
);
68+
signalStore(
69+
withEntities({ entity: type(), collection: 'flight' }),
4870
// @ts-expect-error - should not allow invalid collections
49-
withUndoRedo({ collections: ['test'] }));
71+
withUndoRedo({ collections: ['test'] })
72+
);
5073
});
5174

5275
describe('undo and redo', () => {
5376
it('restores previous state for regular store key', fakeAsync(() => {
5477
TestBed.runInInjectionContext(() => {
5578
const Store = signalStore(
5679
withState(testState),
57-
withMethods(store => ({ updateTest: (newTest: string) => patchState(store, { test: newTest }) })),
80+
withMethods((store) => ({
81+
updateTest: (newTest: string) =>
82+
patchState(store, { test: newTest }),
83+
})),
5884
withUndoRedo({ keys: testKeys })
5985
);
6086

@@ -80,7 +106,10 @@ describe('withUndoRedo', () => {
80106
TestBed.runInInjectionContext(() => {
81107
const Store = signalStore(
82108
withState(testState),
83-
withMethods(store => ({ updateTest: (newTest: string) => patchState(store, { test: newTest }) })),
109+
withMethods((store) => ({
110+
updateTest: (newTest: string) =>
111+
patchState(store, { test: newTest }),
112+
})),
84113
withUndoRedo({ keys: testKeys, skip: 1 })
85114
);
86115

@@ -111,8 +140,9 @@ describe('withUndoRedo', () => {
111140
it('undoes and redoes previous state for entity', fakeAsync(() => {
112141
const Store = signalStore(
113142
withEntities({ entity: type<{ id: string }>() }),
114-
withMethods(store => ({
115-
addEntity: (newTest: string) => patchState(store, addEntity({ id: newTest }))
143+
withMethods((store) => ({
144+
addEntity: (newTest: string) =>
145+
patchState(store, addEntity({ id: newTest })),
116146
})),
117147
withUndoRedo()
118148
);
@@ -132,7 +162,10 @@ describe('withUndoRedo', () => {
132162

133163
store.addEntity(newerValue);
134164
tick(1);
135-
expect(store.entities()).toEqual([{ id: newValue }, { id: newerValue }]);
165+
expect(store.entities()).toEqual([
166+
{ id: newValue },
167+
{ id: newerValue },
168+
]);
136169
expect(store.canUndo()).toBe(true);
137170
expect(store.canRedo()).toBe(false);
138171

@@ -166,9 +199,16 @@ describe('withUndoRedo', () => {
166199
it('restores previous state for named entity', fakeAsync(() => {
167200
TestBed.runInInjectionContext(() => {
168201
const Store = signalStore(
169-
withEntities({ entity: type<{ id: string }>(), collection: 'flight' }),
170-
withMethods(store => ({
171-
addEntity: (newTest: string) => patchState(store, addEntity({ id: newTest }, { collection: 'flight' }))
202+
withEntities({
203+
entity: type<{ id: string }>(),
204+
collection: 'flight',
205+
}),
206+
withMethods((store) => ({
207+
addEntity: (newTest: string) =>
208+
patchState(
209+
store,
210+
addEntity({ id: newTest }, { collection: 'flight' })
211+
),
172212
})),
173213
withCallState({ collection: 'flight' }),
174214
withUndoRedo({ collections: ['flight'] })
@@ -196,7 +236,9 @@ describe('withUndoRedo', () => {
196236
const Store = signalStore(
197237
{ providedIn: 'root' },
198238
withState(testState),
199-
withMethods(store => ({ update: (value: string) => patchState(store, { test: value }) })),
239+
withMethods((store) => ({
240+
update: (value: string) => patchState(store, { test: value }),
241+
})),
200242
withUndoRedo({ keys: testKeys })
201243
);
202244

@@ -209,6 +251,38 @@ describe('withUndoRedo', () => {
209251

210252
expect(store.canUndo()).toBe(false);
211253
expect(store.canRedo()).toBe(false);
212-
})
254+
});
255+
256+
it('can undo after clearing and setting the same value again', fakeAsync(() => {
257+
const recordState = { customer: { firstname: 'Santa' } };
258+
259+
const Store = signalStore(
260+
{ providedIn: 'root' },
261+
withState(recordState),
262+
withMethods((store) => ({
263+
update: (value: typeof recordState) => patchState(store, value),
264+
})),
265+
withUndoRedo({ keys: ['customer'] })
266+
);
267+
268+
const store = TestBed.inject(Store);
269+
270+
store.update({ customer: { firstname: 'Alan' } });
271+
tick(1);
272+
273+
store.update({ customer: { firstname: 'Gordon' } });
274+
tick(1);
275+
276+
store.clearStack();
277+
tick(1);
278+
279+
// After clearing the undo/redo stack, there is no previous item any more
280+
// The following update is the very first value.
281+
store.update({ customer: { firstname: 'Hugh' } });
282+
tick(1);
283+
284+
expect(store.canUndo()).toBe(false);
285+
expect(store.canRedo()).toBe(false);
286+
}));
213287
});
214288
});

0 commit comments

Comments
 (0)