Anki/tests/test_undo.py
Damien Elmes e547b0586a simplify undo
The undo code was using triggers and a temporary table to write out all changed rows before making a change. This made for powerful undo/redo support, but had some problems:
- creating the tables and triggers wasn't cheap, especially on mobile devices
- likewise, every data modification required writing into two separate databases, almost doubling the amount of writes required
- it was possible to leave the DB in an inconsistent state if an undoable operation is followed by a non-undoable operation that references the undoable operation, and the user then rolls back the undoable operation.

To address these issues, we simplify undo by integrating it with the autosave changes:
- .save() can be passed a name to mark a rollback point. If the user undoes the change, any changes since the last save are lost
- autosaves happen every 5 minutes, and are pushed back on a .save(), so the maximum work a user can lose is 5 minutes.
- reviews are handled separately, so we can let the user undo multiple reviews at once
- if necessary, special cases could be added for other operations like marking

This means that if a user does two damaging operations in a row they won't be able to restore the first one, but such an event is both unlikely, and is also covered by the backups made each time a deck is opened.
2011-04-28 09:23:59 +09:00

89 lines
2.2 KiB
Python

# coding: utf-8
import time
from tests.shared import assertException, getEmptyDeck
from anki.stdmodels import BasicModel
def test_op():
d = getEmptyDeck()
# should have no undo by default
assert not d.undoName()
# let's adjust a study option
assert d.qconf['repLim'] == 0
d.save("studyopts")
d.qconf['repLim'] = 10
# it should be listed as undoable
assert d.undoName() == "studyopts"
# with about 5 minutes until it's clobbered
assert time.time() - d._lastSave < 1
# undoing should restore the old value
d.undo()
assert not d.undoName()
assert d.qconf['repLim'] == 0
# an (auto)save will clear the undo
d.save("foo")
assert d.undoName() == "foo"
d.save()
assert not d.undoName()
# and a review will, too
d.save("add")
f = d.newFact()
f['Front'] = u"one"
d.addFact(f)
d.reset()
assert d.undoName() == "add"
c = d.sched.getCard()
d.sched.answerCard(c, 2)
assert d.undoName() == "Review"
def test_review():
d = getEmptyDeck()
f = d.newFact()
f['Front'] = u"one"
d.addFact(f)
d.reset()
assert not d.undoName()
# answer
assert d.sched.counts() == (1, 0, 0)
c = d.sched.getCard()
assert c.queue == 0
assert c.grade == 0
d.sched.answerCard(c, 2)
assert d.sched.counts() == (0, 1, 0)
assert c.queue == 1
assert c.grade == 1
# undo
assert d.undoName()
d.undo()
d.reset()
assert d.sched.counts() == (1, 0, 0)
c.load()
assert c.queue == 0
assert c.grade == 0
assert not d.undoName()
# we should be able to undo multiple answers too
f['Front'] = u"two"
d.addFact(f)
d.reset()
assert d.sched.counts() == (2, 0, 0)
c = d.sched.getCard()
d.sched.answerCard(c, 2)
c = d.sched.getCard()
d.sched.answerCard(c, 2)
assert d.sched.counts() == (0, 2, 0)
d.undo()
d.reset()
assert d.sched.counts() == (1, 1, 0)
d.undo()
d.reset()
assert d.sched.counts() == (2, 0, 0)
# performing a normal op will clear the review queue
c = d.sched.getCard()
d.sched.answerCard(c, 2)
assert d.undoName() == "Review"
d.save("foo")
assert d.undoName() == "foo"
d.undo()
assert not d.undoName()