Clear undo/redo history a library referenced in the stack is being removed

This commit is contained in:
Tom Najdek 2026-06-08 20:01:51 +02:00
parent 39bcb64fc0
commit 189170f968
No known key found for this signature in database
GPG key ID: EEC61A7B4C667D77
3 changed files with 70 additions and 0 deletions

View file

@ -683,6 +683,13 @@ Zotero.Library.prototype._eraseData = async function (env) {
await Zotero.DB.queryAsync("DELETE FROM libraries WHERE libraryID=?", this.libraryID);
// TODO: Emit event so this doesn't have to be here
await Zotero.Fulltext.clearLibraryVersion(this.libraryID);
// Discard undo/redo history that references this library
if (Zotero.UndoHistory) {
Zotero.DB.addCurrentCallback('commit', function () {
Zotero.UndoHistory.clearForLibrary(this.libraryID);
}.bind(this));
}
};
Zotero.Library.prototype._finalizeErase = async function (env) {

View file

@ -58,6 +58,17 @@ Zotero.UndoHistory = {
this._pendingEntry = null;
},
/**
* Discard both stacks if any entry references an object in the given library.
* @param {Integer} libraryID
*/
clearForLibrary(libraryID) {
let affectsLibrary = entry => entry.changes.some(change => change.libraryID === libraryID);
if (this._undoStack.some(affectsLibrary) || this._redoStack.some(affectsLibrary)) {
this.clear();
}
},
/**
* Return a window controller for cmd_undo/cmd_redo that defers to
* native text-editing controllers when they are active.

View file

@ -281,6 +281,58 @@ describe("Zotero.UndoHistory", function () {
});
});
describe("library erasure", function () {
it("should clear undo history when a related library is erased", async function () {
let group = await createGroup();
let collection = await createDataObject(
'collection', { libraryID: group.libraryID, name: 'Group Collection' }
);
Zotero.UndoHistory.clear();
collection.name = 'Renamed';
await collection.saveTx({ undoAction: 'undo-action-rename-collection' });
assert.isTrue(Zotero.UndoHistory.canUndo());
// The group's objects are cascade-deleted without per-object events, so
// the related undo entry must be discarded
await group.eraseTx();
assert.isFalse(Zotero.UndoHistory.canUndo());
assert.isFalse(Zotero.UndoHistory.canRedo());
});
it("should clear undo history when the erased library is referenced only in the redo stack", async function () {
let group = await createGroup();
let collection = await createDataObject(
'collection', { libraryID: group.libraryID, name: 'Group Collection' }
);
Zotero.UndoHistory.clear();
collection.name = 'Renamed';
await collection.saveTx({ undoAction: 'undo-action-rename-collection' });
// Move the entry onto the redo stack
await Zotero.UndoHistory.undo();
assert.isTrue(Zotero.UndoHistory.canRedo());
await group.eraseTx();
assert.isFalse(Zotero.UndoHistory.canRedo());
assert.isFalse(Zotero.UndoHistory.canUndo());
});
it("should preserve undo history when an unrelated library is erased", async function () {
// Record an undo entry in the user library
let collection = await createDataObject('collection', { name: 'My Library Collection' });
Zotero.UndoHistory.clear();
collection.name = 'Renamed';
await collection.saveTx({ undoAction: 'undo-action-rename-collection' });
assert.isTrue(Zotero.UndoHistory.canUndo());
// Erasing an unrelated group must not touch the user-library history
let group = await createGroup();
await group.eraseTx();
assert.isTrue(Zotero.UndoHistory.canUndo());
});
});
describe("canUndo/canRedo", function () {
it("should return false when stacks are empty", function () {
assert.isFalse(Zotero.UndoHistory.canUndo());