From 40fd13fb7ddb01743347a91773fb7ba982cf973d Mon Sep 17 00:00:00 2001 From: Richard Liebmann Date: Sat, 9 Aug 2025 17:35:08 +0200 Subject: [PATCH 1/2] fix #190: make withUndoRedo sync with watchState instead of effect --- libs/ngrx-toolkit/src/lib/with-undo-redo.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/ngrx-toolkit/src/lib/with-undo-redo.ts b/libs/ngrx-toolkit/src/lib/with-undo-redo.ts index cc926a15..35275308 100644 --- a/libs/ngrx-toolkit/src/lib/with-undo-redo.ts +++ b/libs/ngrx-toolkit/src/lib/with-undo-redo.ts @@ -1,10 +1,11 @@ -import { Signal, effect, isSignal, signal, untracked } from '@angular/core'; +import { Signal, isSignal, signal, untracked } from '@angular/core'; import { EmptyFeatureResult, SignalStoreFeature, SignalStoreFeatureResult, patchState, signalStoreFeature, + watchState, withComputed, withHooks, withMethods, @@ -143,7 +144,7 @@ export function withUndoRedo( })), withHooks({ onInit(store) { - effect(() => { + watchState(store, () => { const cand = keys.reduce((acc, key) => { const s = (store as Record)[ key From f6e2c16205c83bfa51f4ddd88c16752a9b3062d1 Mon Sep 17 00:00:00 2001 From: Richard Liebmann Date: Mon, 11 Aug 2025 09:23:25 +0200 Subject: [PATCH 2/2] make tests synchronous and remove fakeAsync and tick --- .../src/lib/with-undo-redo.spec.ts | 49 +++++++------------ 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/libs/ngrx-toolkit/src/lib/with-undo-redo.spec.ts b/libs/ngrx-toolkit/src/lib/with-undo-redo.spec.ts index 1202816f..8edaeb03 100644 --- a/libs/ngrx-toolkit/src/lib/with-undo-redo.spec.ts +++ b/libs/ngrx-toolkit/src/lib/with-undo-redo.spec.ts @@ -1,5 +1,5 @@ import { computed, inject } from '@angular/core'; -import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; import { patchState, signalStore, @@ -73,7 +73,7 @@ describe('withUndoRedo', () => { }); describe('undo and redo', () => { - it('restores previous state for regular store key', fakeAsync(() => { + it('restores previous state for regular store key', () => { TestBed.runInInjectionContext(() => { const Store = signalStore( withState(testState), @@ -85,24 +85,22 @@ describe('withUndoRedo', () => { ); const store = new Store(); - tick(1); store.updateTest(newValue); - tick(1); + expect(store.test()).toEqual(newValue); expect(store.canUndo()).toBe(true); expect(store.canRedo()).toBe(false); store.undo(); - tick(1); expect(store.test()).toEqual(''); expect(store.canUndo()).toBe(false); expect(store.canRedo()).toBe(true); }); - })); + }); - it('restores previous state for regular store key and respects skip', fakeAsync(() => { + it('restores previous state for regular store key and respects skip', () => { TestBed.runInInjectionContext(() => { const Store = signalStore( withState(testState), @@ -114,30 +112,26 @@ describe('withUndoRedo', () => { ); const store = new Store(); - tick(1); store.updateTest(newValue); - tick(1); + expect(store.test()).toEqual(newValue); store.updateTest(newerValue); - tick(1); store.undo(); - tick(1); expect(store.test()).toEqual(newValue); expect(store.canUndo()).toBe(false); store.undo(); - tick(1); // should not change expect(store.test()).toEqual(newValue); }); - })); + }); - it('undoes and redoes previous state for entity', fakeAsync(() => { + it('undoes and redoes previous state for entity', () => { const Store = signalStore( withEntities({ entity: type<{ id: string }>() }), withMethods((store) => ({ @@ -149,19 +143,19 @@ describe('withUndoRedo', () => { TestBed.configureTestingModule({ providers: [Store] }); TestBed.runInInjectionContext(() => { const store = inject(Store); - tick(1); + expect(store.entities()).toEqual([]); expect(store.canUndo()).toBe(false); expect(store.canRedo()).toBe(false); store.addEntity(newValue); - tick(1); + expect(store.entities()).toEqual([{ id: newValue }]); expect(store.canUndo()).toBe(true); expect(store.canRedo()).toBe(false); store.addEntity(newerValue); - tick(1); + expect(store.entities()).toEqual([ { id: newValue }, { id: newerValue }, @@ -182,7 +176,6 @@ describe('withUndoRedo', () => { expect(store.canRedo()).toBe(true); store.redo(); - tick(1); expect(store.entities()).toEqual([{ id: newValue }]); expect(store.canUndo()).toBe(true); @@ -190,13 +183,13 @@ describe('withUndoRedo', () => { // should return canRedo=false after a change store.addEntity('newest'); - tick(1); + expect(store.canUndo()).toBe(true); expect(store.canRedo()).toBe(false); }); - })); + }); - it('restores previous state for named entity', fakeAsync(() => { + it('restores previous state for named entity', () => { TestBed.runInInjectionContext(() => { const Store = signalStore( withEntities({ @@ -215,22 +208,20 @@ describe('withUndoRedo', () => { ); const store = new Store(); - tick(1); store.addEntity(newValue); - tick(1); + expect(store.flightEntities()).toEqual([{ id: newValue }]); expect(store.canUndo()).toBe(true); expect(store.canRedo()).toBe(false); store.undo(); - tick(1); expect(store.flightEntities()).toEqual([]); expect(store.canUndo()).toBe(false); expect(store.canRedo()).toBe(true); }); - })); + }); it('clears undo redo stack', () => { const Store = signalStore( @@ -253,7 +244,7 @@ describe('withUndoRedo', () => { expect(store.canRedo()).toBe(false); }); - it('cannot undo after clearing and setting a new value', fakeAsync(() => { + it('cannot undo after clearing and setting a new value', () => { const Store = signalStore( { providedIn: 'root' }, withState(testState), @@ -266,22 +257,18 @@ describe('withUndoRedo', () => { const store = TestBed.inject(Store); store.update('Alan'); - tick(1); store.update('Gordon'); - tick(1); store.clearStack(); - tick(1); // After clearing the undo/redo stack, there is no previous item anymore. // The following update becomes the first value. // Since there is no other value before, it cannot be undone. store.update('Hugh'); - tick(1); expect(store.canUndo()).toBe(false); expect(store.canRedo()).toBe(false); - })); + }); }); });