-
Notifications
You must be signed in to change notification settings - Fork 45
fix #190: make withUndoRedo sync with watchState instead of effect #207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix #190: make withUndoRedo sync with watchState instead of effect #207
Conversation
…stead of effect
|
@richardliebmann I have a problem in understanding the way how @manfredsteyer & @GregOnNet, you both worked on this. Maybe you could help here. Here's an example of a failing test: it('should define clearStack behavior', () => {
const Store = signalStore(
{ providedIn: 'root' },
withState({ number: 0 }),
withMethods((store) => ({
updateNumber: (newNumber: number) =>
patchState(store, { number: newNumber }),
})),
withUndoRedo({ keys: ['number'] }),
);
const store = TestBed.inject(Store);
store.updateNumber(1);
expect(store.number()).toBe(1);
expect(store.canUndo()).toBe(true);
store.undo();
expect(store.number()).toBe(0);
expect(store.canUndo()).toBe(false);
store.updateNumber(10);
store.clearStack();
expect(store.canUndo()).toBe(false);
store.updateNumber(11);
expect(store.number()).toBe(11);
/**
* The following assertions fails, although it should be possible
* to undo the last change to 10.
*/
expect(store.canUndo()).toBe(true);
}); |
|
@rainerhahnekamp You can add something like this: withMethods((store) => ({
undo(): void {
const item = undoStack.pop();
if (item && previous) {
redoStack.push(previous);
}
if (item) {
skipOnce = true;
patchState(store, item);
previous = item;
}
updateInternal();
},
redo(): void {
const item = redoStack.pop();
if (item && previous) {
undoStack.push(previous);
}
if (item) {
skipOnce = true;
patchState(store, item);
previous = item;
}
updateInternal();
},
clearStack(): void {
undoStack.splice(0);
redoStack.splice(0);
previous = null;
updateInternal();
},
softClearStack(): void {
undoStack.splice(0);
redoStack.splice(0);
// Capture current state as new baseline
const cand = keys.reduce((acc, key) => {
const s = (store as Record<string | keyof Input['state'], unknown>)[key];
if (s && isSignal(s)) {
acc[key] = s();
}
return acc;
}, {} as StackItem);
previous = cand;
updateInternal();
},
}))to match the |
|
Hi @rainerhahnekamp, the test you brought up is worth discussing it. The behaviour of For my use case, Here is a table that describes the flow of your test, Rainer.
Use CaseImagine you have a form-editor that can load and edit different forms. |
| withHooks({ | ||
| onInit(store) { | ||
| effect(() => { | ||
| watchState(store, () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is great and allows us to make all tests in with-und-redo.spec.ts synchronous.
Can we omit all fakeAsync- and tick-usages there?
|
Hi @GregOnNet and @rainerhahnekamp, I have removed the asynchronous features from the test, including 'fakeAsync' and 'tick'. In my opinion, @GregOnNet is right. We also use the clearStack feature to reset to the initial value. In our case, we call clearStack after selecting or loading a new file in an editor and don't want to go back to the previous file. I currently cannot imagine a requirement where softClearStack would make sense, but it might be a useful extension. |
|
@GregOnNet, @richardliebmann, sorry but I still don't get it. The core issue is that during initialization the stack always gets the initial state but not on As a result, the first change after clearing (e.g. setting Using your editors analogy: If you move from one file to another, the second file also has an initial state, and that’s exactly what the stack should adopt as its baseline. Pseudo code for Richard’s case: store.selectNewFile('graz.jpg');
// store's state is cleared and becomes the initial state for Graz
store.clearStack(); // Graz file's state is now the baseline
store.setTitle('Wien');
store.undo(); // restores title back to Graz@jdegand This is not about the actual watchState or other implementation details - it’s purely about defining the intended behavior of the feature. |
|
Hi @rainerhahnekamp, @richardliebmann please correct me, if I am wrong. If you write… patchState(...);
store.clearStack();… it is possible that |
|
@GregOnNet Yes, this is addressing an issue unrelated to Richard’s original intention. I came across it while writing a test for this particular change. Should we merge this as is and open a separate issue for the new problem? |
|
Yes, this PR can be merged. Concerning the new problem, we might want to jump on a short call. |
This pull request fixes bug #190 and causes the undo/redo feature to synchronise instead of operating asynchronously.