# coding: utf-8 import time, copy from tests.shared import assertException, getEmptyDeck from anki.utils import stripHTML, intTime from anki.hooks import addHook from anki.consts import * def test_basics(): d = getEmptyDeck() assert not d.sched.getCard() def test_new(): d = getEmptyDeck() assert d.sched.newCount == 0 # add a note f = d.newNote() f['Front'] = u"one"; f['Back'] = u"two" d.addNote(f) d.reset() assert d.sched.newCount == 1 # fetch it c = d.sched.getCard() assert c assert c.queue == 0 assert c.type == 0 # if we answer it, it should become a learn card t = intTime() d.sched.answerCard(c, 1) assert c.queue == 1 assert c.type == 1 assert c.due >= t # the default order should ensure siblings are not seen together, and # should show all cards m = d.models.current(); mm = d.models t = mm.newTemplate("Reverse") t['qfmt'] = "{{Back}}" t['afmt'] = "{{Front}}" mm.addTemplate(m, t) mm.save(m) f = d.newNote() f['Front'] = u"2"; f['Back'] = u"2" d.addNote(f) f = d.newNote() f['Front'] = u"3"; f['Back'] = u"3" d.addNote(f) d.reset() qs = ("2", "3", "2", "3") for n in range(4): c = d.sched.getCard() assert(stripHTML(c.q()) == qs[n]) d.sched.answerCard(c, 2) def test_newLimits(): d = getEmptyDeck() # add some notes g2 = d.decks.id("Default::foo") for i in range(30): f = d.newNote() f['Front'] = str(i) if i > 4: f.did = g2 d.addNote(f) # give the child deck a different configuration c2 = d.decks.confId("new conf") d.decks.setConf(d.decks.get(g2), c2) d.reset() # both confs have defaulted to a limit of 20 assert d.sched.newCount == 20 # first card we get comes from parent c = d.sched.getCard() assert c.did == 1 # limit the parent to 10 cards, meaning we get 10 in total conf1 = d.decks.conf(1) conf1['new']['perDay'] = 10 d.reset() assert d.sched.newCount == 10 # if we limit child to 4, we should get 9 conf2 = d.decks.conf(g2) conf2['new']['perDay'] = 4 d.reset() assert d.sched.newCount == 9 def test_newOrder(): d = getEmptyDeck() m = d.models.current() for i in range(50): t = d.models.newTemplate(m) t['name'] = str(i) t['qfmt'] = "{{Front}}" t['afmt'] = "{{Back}}" t['actv'] = i > 25 d.models.addTemplate(m, t) d.models.save(m) f = d.newNote() f['Front'] = u'1' f['Back'] = u'2' # add first half d.addNote(f) # generate second half d.db.execute("update cards set did = random()") d.conf['newPerDay'] = 100 d.reset() # cards should be sorted by id assert d.sched._newQueue == list(reversed(sorted(d.sched._newQueue))) def test_newBoxes(): d = getEmptyDeck() f = d.newNote() f['Front'] = u"one" d.addNote(f) d.reset() c = d.sched.getCard() d.sched._cardConf(c)['new']['delays'] = [1,2,3,4,5] d.sched.answerCard(c, 2) # should handle gracefully d.sched._cardConf(c)['new']['delays'] = [1] d.sched.answerCard(c, 2) def test_learn(): d = getEmptyDeck() # add a note f = d.newNote() f['Front'] = u"one"; f['Back'] = u"two" f = d.addNote(f) # set as a learn card and rebuild queues d.db.execute("update cards set queue=0, type=0") d.reset() # sched.getCard should return it, since it's due in the past c = d.sched.getCard() assert c d.sched._cardConf(c)['new']['delays'] = [0.5, 3, 10] # fail it d.sched.answerCard(c, 1) # it should have three reps left to graduation assert c.left == 3 # it should by due in 30 seconds t = round(c.due - time.time()) assert t >= 25 and t <= 40 # pass it once d.sched.answerCard(c, 2) # it should by due in 3 minutes assert round(c.due - time.time()) in (179, 180) assert c.left == 2 # check log is accurate log = d.db.first("select * from revlog order by id desc") assert log[3] == 2 assert log[4] == -180 assert log[5] == -30 # pass again d.sched.answerCard(c, 2) # it should by due in 10 minutes assert round(c.due - time.time()) in (599, 600) assert c.left == 1 # the next pass should graduate the card assert c.queue == 1 assert c.type == 1 d.sched.answerCard(c, 2) assert c.queue == 2 assert c.type == 2 # should be due tomorrow, with an interval of 1 assert c.due == d.sched.today+1 assert c.ivl == 1 # or normal removal c.type = 0 c.queue = 1 c.cycles = 1 d.sched.answerCard(c, 3) assert c.type == 2 assert c.queue == 2 assert c.ivl == 4 # revlog should have been updated each time d.db.scalar("select count() from revlog where type = 0") == 6 # now failed card handling c.type = 2 c.queue = 1 c.edue = 123 d.sched.answerCard(c, 3) assert c.due == 123 assert c.type == 2 assert c.queue == 2 # we should be able to remove manually, too c.type = 2 c.queue = 1 c.edue = 321 c.flush() d.sched.removeFailed() c.load() assert c.queue == 2 assert c.due == 321 def test_reviews(): d = getEmptyDeck() # add a note f = d.newNote() f['Front'] = u"one"; f['Back'] = u"two" d.addNote(f) # set the card up as a review card, due 8 days ago c = f.cards()[0] c.type = 2 c.queue = 2 c.due = d.sched.today - 8 c.factor = 2500 c.reps = 3 c.streak = 2 c.lapses = 1 c.ivl = 100 c.startTimer() c.flush() # save it for later use as well cardcopy = copy.copy(c) # failing it should put it in the learn queue with the default options ################################################## # different delay to new d.sched._cardConf(c)['lapse']['delays'] = [2, 20] d.sched.answerCard(c, 1) assert c.queue == 1 # it should be due tomorrow, with an interval of 1 assert c.edue == d.sched.today + 1 assert c.ivl == 1 # but because it's in the learn queue, its current due time should be in # the future assert c.due >= time.time() assert (c.due - time.time()) > 119 # factor should have been decremented assert c.factor == 2300 # check counters assert c.lapses == 2 assert c.reps == 4 # check ests. ni = d.sched.nextIvl assert ni(c, 1) == 120 assert ni(c, 2) == 20*60 # try again with an ease of 2 instead ################################################## c = copy.copy(cardcopy) c.flush() d.sched.answerCard(c, 2) assert c.queue == 2 # the new interval should be (100 + 8/4) * 1.2 = 122 assert c.ivl == 122 assert c.due == d.sched.today + 122 # factor should have been decremented assert c.factor == 2350 # check counters assert c.lapses == 1 assert c.reps == 4 # ease 3 ################################################## c = copy.copy(cardcopy) c.flush() d.sched.answerCard(c, 3) # the new interval should be (100 + 8/2) * 2.5 = 260 assert c.ivl == 260 assert c.due == d.sched.today + 260 # factor should have been left alone assert c.factor == 2500 # ease 4 ################################################## c = copy.copy(cardcopy) c.flush() d.sched.answerCard(c, 4) # the new interval should be (100 + 8) * 2.5 * 1.3 = 351 assert c.ivl == 351 assert c.due == d.sched.today + 351 # factor should have been increased assert c.factor == 2650 # leech handling ################################################## c = copy.copy(cardcopy) c.lapses = 7 c.flush() # steup hook hooked = [] def onLeech(card): hooked.append(1) addHook("leech", onLeech) d.sched.answerCard(c, 1) assert hooked assert c.queue == -1 c.load() assert c.queue == -1 def test_finished(): d = getEmptyDeck() # nothing due assert "Congratulations" in d.sched.finishedMsg() assert "limit" not in d.sched.finishedMsg() f = d.newNote() f['Front'] = u"one"; f['Back'] = u"two" d.addNote(f) # have a new card assert "new cards available" in d.sched.finishedMsg() # turn it into a review c = f.cards()[0] c.startTimer() d.sched.answerCard(c, 3) # nothing should be due tomorrow, as it's due in a week assert "Congratulations" in d.sched.finishedMsg() assert "limit" not in d.sched.finishedMsg() def test_nextIvl(): d = getEmptyDeck() f = d.newNote() f['Front'] = u"one"; f['Back'] = u"two" d.addNote(f) d.reset() c = d.sched.getCard() d.sched._cardConf(c)['new']['delays'] = [0.5, 3, 10] d.sched._cardConf(c)['lapse']['delays'] = [0.5, 3, 10] # new cards ################################################## ni = d.sched.nextIvl assert ni(c, 1) == 30 assert ni(c, 2) == 180 assert ni(c, 3) == 4*86400 d.sched.answerCard(c, 1) # cards in learning ################################################## assert ni(c, 1) == 30 assert ni(c, 2) == 180 assert ni(c, 3) == 4*86400 d.sched.answerCard(c, 2) assert ni(c, 1) == 30 assert ni(c, 2) == 600 assert ni(c, 3) == 4*86400 d.sched.answerCard(c, 2) # normal graduation is tomorrow assert ni(c, 2) == 1*86400 assert ni(c, 3) == 4*86400 # lapsed cards ################################################## c.type = 2 c.ivl = 100 c.factor = 2500 assert ni(c, 1) == 30 assert ni(c, 2) == 100*86400 assert ni(c, 3) == 100*86400 # review cards ################################################## c.queue = 2 c.ivl = 100 c.factor = 2500 # failing it should put it at 30s assert ni(c, 1) == 30 # or 1 day if relearn is false d.sched._cardConf(c)['lapse']['relearn']=False assert ni(c, 1) == 1*86400 # (* 100 1.2 86400)10368000.0 assert ni(c, 2) == 10368000 # (* 100 2.5 86400)21600000.0 assert ni(c, 3) == 21600000 # (* 100 2.5 1.3 86400)28080000.0 assert ni(c, 4) == 28080000 assert d.sched.nextIvlStr(c, 4) == "10.8 months" def test_misc(): d = getEmptyDeck() f = d.newNote() f['Front'] = u"one" d.addNote(f) c = f.cards()[0] # burying d.sched.buryNote(c.nid) d.reset() assert not d.sched.getCard() d.sched.onClose() d.reset() assert d.sched.getCard() def test_suspend(): d = getEmptyDeck() f = d.newNote() f['Front'] = u"one" d.addNote(f) c = f.cards()[0] # suspending d.reset() assert d.sched.getCard() d.sched.suspendCards([c.id]) d.reset() assert not d.sched.getCard() # unsuspending d.sched.unsuspendCards([c.id]) d.reset() assert d.sched.getCard() # should cope with rev cards being relearnt c.due = 0; c.ivl = 100; c.type = 2; c.queue = 2; c.flush() d.reset() c = d.sched.getCard() d.sched.answerCard(c, 1) assert c.due >= time.time() assert c.queue == 1 assert c.type == 2 d.sched.suspendCards([c.id]) d.sched.unsuspendCards([c.id]) c.load() assert c.queue == 2 assert c.type == 2 assert c.due == 1 def test_cram(): print "disabled for now" return d = getEmptyDeck() f = d.newNote() f['Front'] = u"one" d.addNote(f) c = f.cards()[0] c.ivl = 100 c.type = c.queue = 2 # due in 25 days, so it's been waiting 75 days c.due = d.sched.today + 25 c.mod = 1 c.startTimer() c.flush() cardcopy = copy.copy(c) d.conf['decks'] = [1] d.cramDecks() # first, test with initial intervals preserved conf = d.sched._lrnConf(c) conf['reset'] = False conf['resched'] = False assert d.sched.cardCounts() == (1, 0, 0) c = d.sched.getCard() d.sched._cardConf(c)['cram']['delays'] = [0.5, 3, 10] assert d.sched.cardCounts() == (0, 0, 0) # check that estimates work assert d.sched.nextIvl(c, 1) == 30 assert d.sched.nextIvl(c, 2) == 180 assert d.sched.nextIvl(c, 3) == 86400*100 # failing it should not reset ivl assert c.ivl == 100 d.sched.answerCard(c, 1) assert c.ivl == 100 # and should have incremented lrn count assert d.sched.cardCounts()[1] == 1 # reset ivl for exit test, and pass card d.sched.answerCard(c, 2) delta = c.due - time.time() assert delta > 175 and delta <= 180 # another two answers should reschedule it assert c.queue == 1 d.sched.answerCard(c, 2) d.sched.answerCard(c, 2) assert c.queue == -3 assert c.ivl == 100 assert c.due == d.sched.today + c.ivl # and if the queue is reset, it shouldn't appear in the new queue again d.reset() assert d.sched.cardCounts() == (0, 0, 0) # now try again with ivl rescheduling c = copy.copy(cardcopy) c.flush() d.cramDecks() conf = d.sched._lrnConf(c) conf['reset'] = False conf['resched'] = True # failures shouldn't matter d.sched.answerCard(c, 1) # graduating the card will keep the same interval, but shift the card # forward the number of days it had been waiting (75) assert d.sched.nextIvl(c, 3) == 75*86400 d.sched.answerCard(c, 3) assert c.ivl == 100 assert c.due == d.sched.today + 75 # try with ivl reset c = copy.copy(cardcopy) c.flush() d.cramDecks() conf = d.sched._lrnConf(c) conf['reset'] = True conf['resched'] = True d.sched.answerCard(c, 1) assert d.sched.nextIvl(c, 3) == 1*86400 d.sched.answerCard(c, 3) assert c.ivl == 1 assert c.due == d.sched.today + 1 # users should be able to cram entire deck too d.conf['decks'] = [] d.cramDecks() assert d.sched.cardCounts()[0] > 0 def test_cramLimits(): d = getEmptyDeck() # create three cards, due tomorrow, the next, etc for i in range(3): f = d.newNote() f['Front'] = str(i) d.addNote(f) c = f.cards()[0] c.type = c.queue = 2 c.due = d.sched.today + 1 + i c.flush() # the default cram should return all three d.conf['decks'] = [1] d.cramDecks() assert d.sched.cardCounts()[0] == 3 # if we start from the day after tomorrow, it should be 2 d.cramDecks(min=1) assert d.sched.cardCounts()[0] == 2 # or after 2 days d.cramDecks(min=2) assert d.sched.cardCounts()[0] == 1 # we may get nothing d.cramDecks(min=3) assert d.sched.cardCounts()[0] == 0 # tomorrow(0) + dayAfter(1) = 2 d.cramDecks(max=1) assert d.sched.cardCounts()[0] == 2 # if max is tomorrow, we get only one d.cramDecks(max=0) assert d.sched.cardCounts()[0] == 1 # both should work d.cramDecks(min=0, max=0) assert d.sched.cardCounts()[0] == 1 d.cramDecks(min=1, max=1) assert d.sched.cardCounts()[0] == 1 d.cramDecks(min=0, max=1) assert d.sched.cardCounts()[0] == 2 def test_adjIvl(): d = getEmptyDeck() # add two more templates and set second active m = d.models.current(); mm = d.models t = mm.newTemplate("Reverse") t['qfmt'] = "{{Back}}" t['afmt'] = "{{Front}}" mm.addTemplate(m, t) mm.save(m) t = d.models.newTemplate(m) t['name'] = "f2" t['qfmt'] = "{{Front}}" t['afmt'] = "{{Back}}" d.models.addTemplate(m, t) t = d.models.newTemplate(m) t['name'] = "f3" t['qfmt'] = "{{Front}}" t['afmt'] = "{{Back}}" d.models.addTemplate(m, t) d.models.save(m) # create a new note; it should have 4 cards f = d.newNote() f['Front'] = "1"; f['Back'] = "1" d.addNote(f) assert d.cardCount() == 4 d.reset() # immediately remove first; it should get ideal ivl c = d.sched.getCard() d.sched.answerCard(c, 3) assert c.ivl == 4 # with the default settings, second card should be -1 c = d.sched.getCard() d.sched.answerCard(c, 3) assert c.ivl == 3 # and third +1 c = d.sched.getCard() d.sched.answerCard(c, 3) assert c.ivl == 5 # fourth exceeds default settings, so gets ideal again c = d.sched.getCard() d.sched.answerCard(c, 3) assert c.ivl == 4 # try again with another note f = d.newNote() f['Front'] = "2"; f['Back'] = "2" d.addNote(f) d.reset() # set a minSpacing of 0 conf = d.sched._cardConf(c) conf['rev']['minSpace'] = 0 # first card gets ideal c = d.sched.getCard() d.sched.answerCard(c, 3) assert c.ivl == 4 # and second too, because it's below the threshold c = d.sched.getCard() d.sched.answerCard(c, 3) assert c.ivl == 4 # if we increase the ivl minSpace isn't needed conf['new']['ints'][1] = 20 # ideal.. c = d.sched.getCard() d.sched.answerCard(c, 3) assert c.ivl == 20 # adjusted c = d.sched.getCard() d.sched.answerCard(c, 3) assert c.ivl == 19 def test_ordcycle(): d = getEmptyDeck() # add two more templates and set second active m = d.models.current(); mm = d.models t = mm.newTemplate("Reverse") t['qfmt'] = "{{Back}}" t['afmt'] = "{{Front}}" mm.addTemplate(m, t) t = mm.newTemplate("f2") t['qfmt'] = "{{Front}}" t['afmt'] = "{{Back}}" mm.addTemplate(m, t) mm.save(m) # create a new note; it should have 3 cards f = d.newNote() f['Front'] = "1"; f['Back'] = "1" d.addNote(f) assert d.cardCount() == 3 d.reset() # ordinals should arrive in order assert d.sched.getCard().ord == 0 assert d.sched.getCard().ord == 1 assert d.sched.getCard().ord == 2 def test_cardcounts(): d = getEmptyDeck() # add a second deck grp = d.decks.id("Default::new deck") # for each card type for type in range(3): # and each of the decks for did in (1,grp): # create a new note f = d.newNote() f['Front'] = u"one" d.addNote(f) c = f.cards()[0] # set type/did c.type = type c.queue = type c.did = did c.due = 0 c.flush() d.reset() # with the default settings, there's no count limit assert d.sched.cardCounts() == (2,2,2) # check limit to one deck d.decks.select(grp) d.reset() assert d.sched.cardCounts() == (1,1,1) def test_counts_idx(): d = getEmptyDeck() f = d.newNote() f['Front'] = u"one"; f['Back'] = u"two" d.addNote(f) d.reset() assert d.sched.cardCounts() == (1, 0, 0) c = d.sched.getCard() # counter's been decremented but idx indicates 1 assert d.sched.cardCounts() == (0, 0, 0) assert d.sched.countIdx(c) == 0 # answer to move to learn queue d.sched.answerCard(c, 1) assert d.sched.cardCounts() == (0, 1, 0) # fetching again will decrement the count c = d.sched.getCard() assert d.sched.cardCounts() == (0, 0, 0) assert d.sched.countIdx(c) == 1 # answering should add it back again d.sched.answerCard(c, 1) assert d.sched.cardCounts() == (0, 1, 0) def test_repCounts(): d = getEmptyDeck() f = d.newNote() f['Front'] = u"one" d.addNote(f) d.reset() # lrnReps should be accurate on pass/fail assert d.sched.repCounts() == (1, 0, 0) d.sched.answerCard(d.sched.getCard(), 1) assert d.sched.repCounts() == (0, 2, 0) d.sched.answerCard(d.sched.getCard(), 1) assert d.sched.repCounts() == (0, 2, 0) d.sched.answerCard(d.sched.getCard(), 2) assert d.sched.repCounts() == (0, 1, 0) d.sched.answerCard(d.sched.getCard(), 1) assert d.sched.repCounts() == (0, 2, 0) d.sched.answerCard(d.sched.getCard(), 2) assert d.sched.repCounts() == (0, 1, 0) d.sched.answerCard(d.sched.getCard(), 2) assert d.sched.repCounts() == (0, 0, 0) f = d.newNote() f['Front'] = u"two" d.addNote(f) d.reset() # initial pass should be correct too d.sched.answerCard(d.sched.getCard(), 2) assert d.sched.repCounts() == (0, 1, 0) d.sched.answerCard(d.sched.getCard(), 1) assert d.sched.repCounts() == (0, 2, 0) d.sched.answerCard(d.sched.getCard(), 3) assert d.sched.repCounts() == (0, 0, 0) # immediate graduate should work f = d.newNote() f['Front'] = u"three" d.addNote(f) d.reset() d.sched.answerCard(d.sched.getCard(), 3) assert d.sched.repCounts() == (0, 0, 0) # and failing a review should too f = d.newNote() f['Front'] = u"three" d.addNote(f) c = f.cards()[0] c.type = 2 c.queue = 2 c.due = d.sched.today c.flush() d.reset() assert d.sched.repCounts() == (0, 0, 1) d.sched.answerCard(d.sched.getCard(), 1) assert d.sched.repCounts() == (0, 2, 0) def test_timing(): d = getEmptyDeck() # add a few review cards, due today for i in range(5): f = d.newNote() f['Front'] = "num"+str(i) d.addNote(f) c = f.cards()[0] c.type = 2 c.queue = 2 c.due = 0 c.flush() # fail the first one d.reset() c = d.sched.getCard() # set a a fail delay of 1 second so we don't have to wait d.sched._cardConf(c)['lapse']['delays'][0] = 1/60.0 d.sched.answerCard(c, 1) # the next card should be another review c = d.sched.getCard() assert c.queue == 2 # but if we wait for a second, the failed card should come back time.sleep(1) c = d.sched.getCard() assert c.queue == 1 def test_collapse(): d = getEmptyDeck() # add a note f = d.newNote() f['Front'] = u"one" d.addNote(f) d.reset() # test collapsing c = d.sched.getCard() d.sched.answerCard(c, 1) c = d.sched.getCard() d.sched.answerCard(c, 3) assert not d.sched.getCard() def test_deckDue(): d = getEmptyDeck() # add a note with default deck f = d.newNote() f['Front'] = u"one" d.addNote(f) # and one that's a child f = d.newNote() f['Front'] = u"two" default1 = f.did = d.decks.id("Default::1") d.addNote(f) # make it a review card c = f.cards()[0] c.queue = 2 c.due = 0 c.flush() # add one more with a new deck f = d.newNote() f['Front'] = u"two" foobar = f.did = d.decks.id("foo::bar") d.addNote(f) # and one that's a sibling f = d.newNote() f['Front'] = u"three" foobaz = f.did = d.decks.id("foo::baz") d.addNote(f) d.reset() assert len(d.decks.decks) == 5 cnts = d.sched.deckDueList() cnts.sort() assert cnts[0] == ["Default", 1, 0, 1] assert cnts[1] == ["Default::1", default1, 1, 0] assert cnts[2] == ["foo", d.decks.id("foo"), 0, 0] assert cnts[3] == ["foo::bar", foobar, 0, 1] assert cnts[4] == ["foo::baz", foobaz, 0, 1] tree = d.sched.deckDueTree() assert tree[0][0] == "Default" # sum of child and parent assert tree[0][1] == 1 assert tree[0][2] == 1 assert tree[0][3] == 1 # child count is just review assert tree[0][4][0][0] == "1" assert tree[0][4][0][1] == default1 assert tree[0][4][0][2] == 1 assert tree[0][4][0][3] == 0 # code should not fail if a card has an invalid deck c.did = 12345; c.flush() d.sched.deckDueList() d.sched.deckDueTree() def test_deckTree(): d = getEmptyDeck() d.decks.id("new::b::c") d.decks.id("new2") # new should not appear twice in tree names = [x[0] for x in d.sched.deckDueTree()] names.remove("new") assert "new" not in names def test_deckFlow(): d = getEmptyDeck() # add a note with default deck f = d.newNote() f['Front'] = u"one" d.addNote(f) # and one that's a child f = d.newNote() f['Front'] = u"two" default1 = f.did = d.decks.id("Default::2") d.addNote(f) # and another that's higher up f = d.newNote() f['Front'] = u"three" default1 = f.did = d.decks.id("Default::1") d.addNote(f) # should get top level one first, then ::1, then ::2 d.reset() assert d.sched.cardCounts() == (3,0,0) for i in "one", "three", "two": c = d.sched.getCard() assert c.note()['Front'] == i d.sched.answerCard(c, 2) def test_reorder(): d = getEmptyDeck() # add a note with default deck f = d.newNote() f['Front'] = u"one" d.addNote(f) f2 = d.newNote() f2['Front'] = u"two" d.addNote(f2) assert f2.cards()[0].due == 2 found=False # 50/50 chance of being reordered for i in range(20): d.sched.randomizeCards() if f.cards()[0].due != f.id: found=True break assert found d.sched.orderCards() assert f.cards()[0].due == 1 # shifting f3 = d.newNote() f3['Front'] = u"three" d.addNote(f3) f4 = d.newNote() f4['Front'] = u"four" d.addNote(f4) assert f.cards()[0].due == 1 assert f2.cards()[0].due == 2 assert f3.cards()[0].due == 3 assert f4.cards()[0].due == 4 d.sched.sortCards([ f3.cards()[0].id, f4.cards()[0].id], start=1, shift=True) assert f.cards()[0].due == 3 assert f2.cards()[0].due == 4 assert f3.cards()[0].due == 1 assert f4.cards()[0].due == 2 def test_forget(): d = getEmptyDeck() f = d.newNote() f['Front'] = u"one" d.addNote(f) c = f.cards()[0] c.queue = 2; c.type = 2; c.ivl = 100; c.due = 0 c.flush() d.reset() assert d.sched.cardCounts() == (0, 0, 1) d.sched.forgetCards([c.id]) d.reset() assert d.sched.cardCounts() == (1, 0, 0) def test_resched(): d = getEmptyDeck() f = d.newNote() f['Front'] = u"one" d.addNote(f) c = f.cards()[0] d.sched.reschedCards([c.id], 0, 0) c.load() assert c.due == d.sched.today assert c.ivl == 1 assert c.queue == c.type == 2 d.sched.reschedCards([c.id], 1, 1) c.load() assert c.due == d.sched.today+1 assert c.ivl == +1 def test_revlim(): d = getEmptyDeck() for i in range(20): f = d.newNote() f['Front'] = str(i) d.addNote(f) d.db.execute("update cards set due = 0, queue = 2, type = 2") d.reset() assert d.sched.repCounts()[2] == 20 for i in range(5): d.sched.answerCard(d.sched.getCard(), 3) assert d.sched.repCounts()[2] == 15 t = d.decks.top() t['revLim'] = 10 d.reset() assert d.sched.repCounts()[2] == 5