mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 14:32:22 -04:00
format tests
This commit is contained in:
parent
9791bcb36b
commit
e5c4618a9a
19 changed files with 801 additions and 585 deletions
2
Makefile
2
Makefile
|
@ -7,7 +7,7 @@ MAKEFLAGS += --warn-undefined-variables
|
|||
MAKEFLAGS += --no-builtin-rules
|
||||
RUNARGS :=
|
||||
.SUFFIXES:
|
||||
BLACKARGS := -t py36 anki aqt
|
||||
BLACKARGS := -t py36 anki aqt tests
|
||||
RUSTARGS := --release --strip
|
||||
|
||||
$(shell mkdir -p .build)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import tempfile, os, shutil
|
||||
from anki import Collection as aopen
|
||||
|
||||
|
||||
def assertException(exception, func):
|
||||
found = False
|
||||
try:
|
||||
|
@ -25,6 +26,7 @@ def getEmptyCol():
|
|||
col = aopen(nam)
|
||||
return col
|
||||
|
||||
|
||||
getEmptyCol.master = ""
|
||||
|
||||
# Fallback for when the DB needs options passed in.
|
||||
|
@ -34,10 +36,12 @@ def getEmptyDeckWith(**kwargs):
|
|||
os.unlink(nam)
|
||||
return aopen(nam, **kwargs)
|
||||
|
||||
|
||||
def getUpgradeDeckPath(name="anki12.anki"):
|
||||
src = os.path.join(testDir, "support", name)
|
||||
(fd, dst) = tempfile.mkstemp(suffix=".anki2")
|
||||
shutil.copy(src, dst)
|
||||
return dst
|
||||
|
||||
|
||||
testDir = os.path.dirname(__file__)
|
||||
|
|
|
@ -9,65 +9,48 @@ from aqt.addons import AddonManager
|
|||
|
||||
def test_readMinimalManifest():
|
||||
assertReadManifest(
|
||||
'{"package": "yes", "name": "no"}',
|
||||
{"package": "yes", "name": "no"}
|
||||
'{"package": "yes", "name": "no"}', {"package": "yes", "name": "no"}
|
||||
)
|
||||
|
||||
|
||||
def test_readExtraKeys():
|
||||
assertReadManifest(
|
||||
'{"package": "a", "name": "b", "mod": 3, "conflicts": ["d", "e"]}',
|
||||
{"package": "a", "name": "b", "mod": 3, "conflicts": ["d", "e"]}
|
||||
{"package": "a", "name": "b", "mod": 3, "conflicts": ["d", "e"]},
|
||||
)
|
||||
|
||||
|
||||
def test_invalidManifest():
|
||||
assertReadManifest(
|
||||
'{"one": 1}',
|
||||
{}
|
||||
)
|
||||
assertReadManifest('{"one": 1}', {})
|
||||
|
||||
|
||||
def test_mustHaveName():
|
||||
assertReadManifest(
|
||||
'{"package": "something"}',
|
||||
{}
|
||||
)
|
||||
assertReadManifest('{"package": "something"}', {})
|
||||
|
||||
|
||||
def test_mustHavePackage():
|
||||
assertReadManifest(
|
||||
'{"name": "something"}',
|
||||
{}
|
||||
)
|
||||
assertReadManifest('{"name": "something"}', {})
|
||||
|
||||
|
||||
def test_invalidJson():
|
||||
assertReadManifest(
|
||||
'this is not a JSON dictionary',
|
||||
{}
|
||||
)
|
||||
assertReadManifest("this is not a JSON dictionary", {})
|
||||
|
||||
|
||||
def test_missingManifest():
|
||||
assertReadManifest(
|
||||
'{"package": "what", "name": "ever"}',
|
||||
{},
|
||||
nameInZip="not-manifest.bin"
|
||||
'{"package": "what", "name": "ever"}', {}, nameInZip="not-manifest.bin"
|
||||
)
|
||||
|
||||
|
||||
def test_ignoreExtraKeys():
|
||||
assertReadManifest(
|
||||
'{"package": "a", "name": "b", "game": "c"}',
|
||||
{"package": "a", "name": "b"}
|
||||
'{"package": "a", "name": "b", "game": "c"}', {"package": "a", "name": "b"}
|
||||
)
|
||||
|
||||
|
||||
def test_conflictsMustBeStrings():
|
||||
assertReadManifest(
|
||||
'{"package": "a", "name": "b", "conflicts": ["c", 4, {"d": "e"}]}',
|
||||
{}
|
||||
'{"package": "a", "name": "b", "conflicts": ["c", 4, {"d": "e"}]}', {}
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
|
||||
from tests.shared import getEmptyCol
|
||||
|
||||
|
||||
def test_previewCards():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = '1'
|
||||
f['Back'] = '2'
|
||||
f["Front"] = "1"
|
||||
f["Back"] = "2"
|
||||
# non-empty and active
|
||||
cards = deck.previewCards(f, 0)
|
||||
assert len(cards) == 1
|
||||
|
@ -22,11 +23,12 @@ def test_previewCards():
|
|||
# make sure we haven't accidentally added cards to the db
|
||||
assert deck.cardCount() == 1
|
||||
|
||||
|
||||
def test_delete():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = '1'
|
||||
f['Back'] = '2'
|
||||
f["Front"] = "1"
|
||||
f["Back"] = "2"
|
||||
deck.addNote(f)
|
||||
cid = f.cards()[0].id
|
||||
deck.reset()
|
||||
|
@ -38,62 +40,65 @@ def test_delete():
|
|||
assert deck.db.scalar("select count() from cards") == 0
|
||||
assert deck.db.scalar("select count() from graves") == 2
|
||||
|
||||
|
||||
def test_misc():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = '1'
|
||||
f['Back'] = '2'
|
||||
f["Front"] = "1"
|
||||
f["Back"] = "2"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
id = d.models.current()['id']
|
||||
assert c.template()['ord'] == 0
|
||||
id = d.models.current()["id"]
|
||||
assert c.template()["ord"] == 0
|
||||
|
||||
|
||||
def test_genrem():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = '1'
|
||||
f['Back'] = ''
|
||||
f["Front"] = "1"
|
||||
f["Back"] = ""
|
||||
d.addNote(f)
|
||||
assert len(f.cards()) == 1
|
||||
m = d.models.current()
|
||||
mm = d.models
|
||||
# adding a new template should automatically create cards
|
||||
t = mm.newTemplate("rev")
|
||||
t['qfmt'] = '{{Front}}'
|
||||
t['afmt'] = ""
|
||||
t["qfmt"] = "{{Front}}"
|
||||
t["afmt"] = ""
|
||||
mm.addTemplate(m, t)
|
||||
mm.save(m, templates=True)
|
||||
assert len(f.cards()) == 2
|
||||
# if the template is changed to remove cards, they'll be removed
|
||||
t['qfmt'] = "{{Back}}"
|
||||
t["qfmt"] = "{{Back}}"
|
||||
mm.save(m, templates=True)
|
||||
d.remCards(d.emptyCids())
|
||||
assert len(f.cards()) == 1
|
||||
# if we add to the note, a card should be automatically generated
|
||||
f.load()
|
||||
f['Back'] = "1"
|
||||
f["Back"] = "1"
|
||||
f.flush()
|
||||
assert len(f.cards()) == 2
|
||||
|
||||
|
||||
def test_gendeck():
|
||||
d = getEmptyCol()
|
||||
cloze = d.models.byName("Cloze")
|
||||
d.models.setCurrent(cloze)
|
||||
f = d.newNote()
|
||||
f['Text'] = '{{c1::one}}'
|
||||
f["Text"] = "{{c1::one}}"
|
||||
d.addNote(f)
|
||||
assert d.cardCount() == 1
|
||||
assert f.cards()[0].did == 1
|
||||
# set the model to a new default deck
|
||||
newId = d.decks.id("new")
|
||||
cloze['did'] = newId
|
||||
cloze["did"] = newId
|
||||
d.models.save(cloze, updateReqs=False)
|
||||
# a newly generated card should share the first card's deck
|
||||
f['Text'] += '{{c2::two}}'
|
||||
f["Text"] += "{{c2::two}}"
|
||||
f.flush()
|
||||
assert f.cards()[1].did == 1
|
||||
# and same with multiple cards
|
||||
f['Text'] += '{{c3::three}}'
|
||||
f["Text"] += "{{c3::three}}"
|
||||
f.flush()
|
||||
assert f.cards()[2].did == 1
|
||||
# if one of the cards is in a different deck, it should revert to the
|
||||
|
@ -101,9 +106,6 @@ def test_gendeck():
|
|||
c = f.cards()[1]
|
||||
c.did = newId
|
||||
c.flush()
|
||||
f['Text'] += '{{c4::four}}'
|
||||
f["Text"] += "{{c4::four}}"
|
||||
f.flush()
|
||||
assert f.cards()[3].did == newId
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ from anki.stdmodels import addBasicModel, models
|
|||
|
||||
from anki import Collection as aopen
|
||||
|
||||
|
||||
def test_create_open():
|
||||
(fd, path) = tempfile.mkstemp(suffix=".anki2", prefix="test_attachNew")
|
||||
try:
|
||||
|
@ -32,27 +33,28 @@ def test_create_open():
|
|||
dir = "c:\root.anki2"
|
||||
else:
|
||||
dir = "/attachroot.anki2"
|
||||
assertException(Exception,
|
||||
lambda: aopen(dir))
|
||||
assertException(Exception, lambda: aopen(dir))
|
||||
# reuse tmp file from before, test non-writeable file
|
||||
os.chmod(newPath, 0)
|
||||
assertException(Exception,
|
||||
lambda: aopen(newPath))
|
||||
assertException(Exception, lambda: aopen(newPath))
|
||||
os.chmod(newPath, 0o666)
|
||||
os.unlink(newPath)
|
||||
|
||||
|
||||
def test_noteAddDelete():
|
||||
deck = getEmptyCol()
|
||||
# add a note
|
||||
f = deck.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
n = deck.addNote(f)
|
||||
assert n == 1
|
||||
# test multiple cards - add another template
|
||||
m = deck.models.current(); mm = deck.models
|
||||
m = deck.models.current()
|
||||
mm = deck.models
|
||||
t = mm.newTemplate("Reverse")
|
||||
t['qfmt'] = "{{Back}}"
|
||||
t['afmt'] = "{{Front}}"
|
||||
t["qfmt"] = "{{Back}}"
|
||||
t["afmt"] = "{{Front}}"
|
||||
mm.addTemplate(m, t)
|
||||
mm.save(m)
|
||||
# the default save doesn't generate cards
|
||||
|
@ -63,7 +65,8 @@ def test_noteAddDelete():
|
|||
assert deck.cardCount() == 2
|
||||
# creating new notes should use both cards
|
||||
f = deck.newNote()
|
||||
f['Front'] = "three"; f['Back'] = "four"
|
||||
f["Front"] = "three"
|
||||
f["Back"] = "four"
|
||||
n = deck.addNote(f)
|
||||
assert n == 2
|
||||
assert deck.cardCount() == 4
|
||||
|
@ -74,36 +77,39 @@ def test_noteAddDelete():
|
|||
assert not f.dupeOrEmpty()
|
||||
# now let's make a duplicate
|
||||
f2 = deck.newNote()
|
||||
f2['Front'] = "one"; f2['Back'] = ""
|
||||
f2["Front"] = "one"
|
||||
f2["Back"] = ""
|
||||
assert f2.dupeOrEmpty()
|
||||
# empty first field should not be permitted either
|
||||
f2['Front'] = " "
|
||||
f2["Front"] = " "
|
||||
assert f2.dupeOrEmpty()
|
||||
|
||||
|
||||
def test_fieldChecksum():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = "new"; f['Back'] = "new2"
|
||||
f["Front"] = "new"
|
||||
f["Back"] = "new2"
|
||||
deck.addNote(f)
|
||||
assert deck.db.scalar(
|
||||
"select csum from notes") == int("c2a6b03f", 16)
|
||||
assert deck.db.scalar("select csum from notes") == int("c2a6b03f", 16)
|
||||
# changing the val should change the checksum
|
||||
f['Front'] = "newx"
|
||||
f["Front"] = "newx"
|
||||
f.flush()
|
||||
assert deck.db.scalar(
|
||||
"select csum from notes") == int("302811ae", 16)
|
||||
assert deck.db.scalar("select csum from notes") == int("302811ae", 16)
|
||||
|
||||
|
||||
def test_addDelTags():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = "1"
|
||||
f["Front"] = "1"
|
||||
deck.addNote(f)
|
||||
f2 = deck.newNote()
|
||||
f2['Front'] = "2"
|
||||
f2["Front"] = "2"
|
||||
deck.addNote(f2)
|
||||
# adding for a given id
|
||||
deck.tags.bulkAdd([f.id], "foo")
|
||||
f.load(); f2.load()
|
||||
f.load()
|
||||
f2.load()
|
||||
assert "foo" in f.tags
|
||||
assert "foo" not in f2.tags
|
||||
# should be canonified
|
||||
|
@ -112,6 +118,7 @@ def test_addDelTags():
|
|||
assert f.tags[0] == "aaa"
|
||||
assert len(f.tags) == 2
|
||||
|
||||
|
||||
def test_timestamps():
|
||||
deck = getEmptyCol()
|
||||
assert len(deck.models.models) == len(models)
|
||||
|
@ -119,23 +126,24 @@ def test_timestamps():
|
|||
addBasicModel(deck)
|
||||
assert len(deck.models.models) == 100 + len(models)
|
||||
|
||||
|
||||
def test_furigana():
|
||||
deck = getEmptyCol()
|
||||
mm = deck.models
|
||||
m = mm.current()
|
||||
# filter should work
|
||||
m['tmpls'][0]['qfmt'] = '{{kana:Front}}'
|
||||
m["tmpls"][0]["qfmt"] = "{{kana:Front}}"
|
||||
mm.save(m)
|
||||
n = deck.newNote()
|
||||
n['Front'] = 'foo[abc]'
|
||||
n["Front"] = "foo[abc]"
|
||||
deck.addNote(n)
|
||||
c = n.cards()[0]
|
||||
assert c.q().endswith("abc")
|
||||
# and should avoid sound
|
||||
n['Front'] = 'foo[sound:abc.mp3]'
|
||||
n["Front"] = "foo[sound:abc.mp3]"
|
||||
n.flush()
|
||||
assert "sound:" in c.q(reload=True)
|
||||
# it shouldn't throw an error while people are editing
|
||||
m['tmpls'][0]['qfmt'] = '{{kana:}}'
|
||||
m["tmpls"][0]["qfmt"] = "{{kana:}}"
|
||||
mm.save(m)
|
||||
c.q(reload=True)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
from anki.errors import DeckRenameError
|
||||
from tests.shared import assertException, getEmptyCol
|
||||
|
||||
|
||||
def test_basic():
|
||||
deck = getEmptyCol()
|
||||
# we start with a standard deck
|
||||
|
@ -34,21 +35,22 @@ def test_basic():
|
|||
# parents with a different case should be handled correctly
|
||||
deck.decks.id("ONE")
|
||||
m = deck.models.current()
|
||||
m['did'] = deck.decks.id("one::two")
|
||||
m["did"] = deck.decks.id("one::two")
|
||||
deck.models.save(m, updateReqs=False)
|
||||
n = deck.newNote()
|
||||
n['Front'] = "abc"
|
||||
n["Front"] = "abc"
|
||||
deck.addNote(n)
|
||||
# this will error if child and parent case don't match
|
||||
deck.sched.deckDueList()
|
||||
|
||||
|
||||
def test_remove():
|
||||
deck = getEmptyCol()
|
||||
# create a new deck, and add a note/card to it
|
||||
g1 = deck.decks.id("g1")
|
||||
f = deck.newNote()
|
||||
f['Front'] = "1"
|
||||
f.model()['did'] = g1
|
||||
f["Front"] = "1"
|
||||
f.model()["did"] = g1
|
||||
deck.addNote(f)
|
||||
c = f.cards()[0]
|
||||
assert c.did == g1
|
||||
|
@ -62,12 +64,14 @@ def test_remove():
|
|||
assert deck.decks.name(c.did) == "[no deck]"
|
||||
# let's create another deck and explicitly set the card to it
|
||||
g2 = deck.decks.id("g2")
|
||||
c.did = g2; c.flush()
|
||||
c.did = g2
|
||||
c.flush()
|
||||
# this time we'll delete the card/note too
|
||||
deck.decks.rem(g2, cardsToo=True)
|
||||
assert deck.cardCount() == 0
|
||||
assert deck.noteCount() == 0
|
||||
|
||||
|
||||
def test_rename():
|
||||
d = getEmptyCol()
|
||||
id = d.decks.id("hello::world")
|
||||
|
@ -80,8 +84,7 @@ def test_rename():
|
|||
# create another deck
|
||||
id = d.decks.id("tmp")
|
||||
# we can't rename it if it conflicts
|
||||
assertException(
|
||||
Exception, lambda: d.decks.rename(d.decks.get(id), "foo"))
|
||||
assertException(Exception, lambda: d.decks.rename(d.decks.get(id), "foo"))
|
||||
# when renaming, the children should be renamed too
|
||||
d.decks.id("one::two::three")
|
||||
id = d.decks.id("one")
|
||||
|
@ -102,62 +105,66 @@ def test_rename():
|
|||
assertException(DeckRenameError, lambda: d.decks.rename(child, "PARENT::child"))
|
||||
|
||||
|
||||
|
||||
def test_renameForDragAndDrop():
|
||||
d = getEmptyCol()
|
||||
|
||||
def deckNames():
|
||||
return [ name for name in sorted(d.decks.allNames()) if name != 'Default' ]
|
||||
return [name for name in sorted(d.decks.allNames()) if name != "Default"]
|
||||
|
||||
languages_did = d.decks.id('Languages')
|
||||
chinese_did = d.decks.id('Chinese')
|
||||
hsk_did = d.decks.id('Chinese::HSK')
|
||||
languages_did = d.decks.id("Languages")
|
||||
chinese_did = d.decks.id("Chinese")
|
||||
hsk_did = d.decks.id("Chinese::HSK")
|
||||
|
||||
# Renaming also renames children
|
||||
d.decks.renameForDragAndDrop(chinese_did, languages_did)
|
||||
assert deckNames() == [ 'Languages', 'Languages::Chinese', 'Languages::Chinese::HSK' ]
|
||||
assert deckNames() == ["Languages", "Languages::Chinese", "Languages::Chinese::HSK"]
|
||||
|
||||
# Dragging a deck onto itself is a no-op
|
||||
d.decks.renameForDragAndDrop(languages_did, languages_did)
|
||||
assert deckNames() == [ 'Languages', 'Languages::Chinese', 'Languages::Chinese::HSK' ]
|
||||
assert deckNames() == ["Languages", "Languages::Chinese", "Languages::Chinese::HSK"]
|
||||
|
||||
# Dragging a deck onto its parent is a no-op
|
||||
d.decks.renameForDragAndDrop(hsk_did, chinese_did)
|
||||
assert deckNames() == [ 'Languages', 'Languages::Chinese', 'Languages::Chinese::HSK' ]
|
||||
assert deckNames() == ["Languages", "Languages::Chinese", "Languages::Chinese::HSK"]
|
||||
|
||||
# Dragging a deck onto a descendant is a no-op
|
||||
d.decks.renameForDragAndDrop(languages_did, hsk_did)
|
||||
assert deckNames() == [ 'Languages', 'Languages::Chinese', 'Languages::Chinese::HSK' ]
|
||||
assert deckNames() == ["Languages", "Languages::Chinese", "Languages::Chinese::HSK"]
|
||||
|
||||
# Can drag a grandchild onto its grandparent. It becomes a child
|
||||
d.decks.renameForDragAndDrop(hsk_did, languages_did)
|
||||
assert deckNames() == [ 'Languages', 'Languages::Chinese', 'Languages::HSK' ]
|
||||
assert deckNames() == ["Languages", "Languages::Chinese", "Languages::HSK"]
|
||||
|
||||
# Can drag a deck onto its sibling
|
||||
d.decks.renameForDragAndDrop(hsk_did, chinese_did)
|
||||
assert deckNames() == [ 'Languages', 'Languages::Chinese', 'Languages::Chinese::HSK' ]
|
||||
assert deckNames() == ["Languages", "Languages::Chinese", "Languages::Chinese::HSK"]
|
||||
|
||||
# Can drag a deck back to the top level
|
||||
d.decks.renameForDragAndDrop(chinese_did, None)
|
||||
assert deckNames() == [ 'Chinese', 'Chinese::HSK', 'Languages' ]
|
||||
assert deckNames() == ["Chinese", "Chinese::HSK", "Languages"]
|
||||
|
||||
# Dragging a top level deck to the top level is a no-op
|
||||
d.decks.renameForDragAndDrop(chinese_did, None)
|
||||
assert deckNames() == [ 'Chinese', 'Chinese::HSK', 'Languages' ]
|
||||
assert deckNames() == ["Chinese", "Chinese::HSK", "Languages"]
|
||||
|
||||
# can't drack a deck where sibling have same name
|
||||
new_hsk_did = d.decks.id("HSK")
|
||||
assertException(DeckRenameError, lambda: d.decks.renameForDragAndDrop(new_hsk_did, chinese_did))
|
||||
assertException(
|
||||
DeckRenameError, lambda: d.decks.renameForDragAndDrop(new_hsk_did, chinese_did)
|
||||
)
|
||||
d.decks.rem(new_hsk_did)
|
||||
|
||||
# can't drack a deck where sibling have same name different case
|
||||
new_hsk_did = d.decks.id("hsk")
|
||||
assertException(DeckRenameError, lambda: d.decks.renameForDragAndDrop(new_hsk_did, chinese_did))
|
||||
assertException(
|
||||
DeckRenameError, lambda: d.decks.renameForDragAndDrop(new_hsk_did, chinese_did)
|
||||
)
|
||||
d.decks.rem(new_hsk_did)
|
||||
|
||||
# '' is a convenient alias for the top level DID
|
||||
d.decks.renameForDragAndDrop(hsk_did, '')
|
||||
assert deckNames() == [ 'Chinese', 'HSK', 'Languages' ]
|
||||
d.decks.renameForDragAndDrop(hsk_did, "")
|
||||
assert deckNames() == ["Chinese", "HSK", "Languages"]
|
||||
|
||||
|
||||
def test_check():
|
||||
d = getEmptyCol()
|
||||
|
|
|
@ -13,20 +13,26 @@ deck = None
|
|||
ds = None
|
||||
testDir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
def setup1():
|
||||
global deck
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = "foo"; f['Back'] = "bar<br>"; f.tags = ["tag", "tag2"]
|
||||
f["Front"] = "foo"
|
||||
f["Back"] = "bar<br>"
|
||||
f.tags = ["tag", "tag2"]
|
||||
deck.addNote(f)
|
||||
# with a different deck
|
||||
f = deck.newNote()
|
||||
f['Front'] = "baz"; f['Back'] = "qux"
|
||||
f.model()['did'] = deck.decks.id("new deck")
|
||||
f["Front"] = "baz"
|
||||
f["Back"] = "qux"
|
||||
f.model()["did"] = deck.decks.id("new deck")
|
||||
deck.addNote(f)
|
||||
|
||||
|
||||
##########################################################################
|
||||
|
||||
|
||||
@with_setup(setup1)
|
||||
def test_export_anki():
|
||||
# create a new deck with its own conf to test conf copying
|
||||
|
@ -34,7 +40,7 @@ def test_export_anki():
|
|||
dobj = deck.decks.get(did)
|
||||
confId = deck.decks.confId("newconf")
|
||||
conf = deck.decks.getConf(confId)
|
||||
conf['new']['perDay'] = 5
|
||||
conf["new"]["perDay"] = 5
|
||||
deck.decks.save(conf)
|
||||
deck.decks.setConf(dobj, confId)
|
||||
# export
|
||||
|
@ -46,7 +52,7 @@ def test_export_anki():
|
|||
e.exportInto(newname)
|
||||
# exporting should not have changed conf for original deck
|
||||
conf = deck.decks.confForDid(did)
|
||||
assert conf['id'] != 1
|
||||
assert conf["id"] != 1
|
||||
# connect to new deck
|
||||
d2 = aopen(newname)
|
||||
assert d2.cardCount() == 2
|
||||
|
@ -54,10 +60,10 @@ def test_export_anki():
|
|||
did = d2.decks.id("test", create=False)
|
||||
assert did
|
||||
conf2 = d2.decks.confForDid(did)
|
||||
assert conf2['new']['perDay'] == 20
|
||||
assert conf2["new"]["perDay"] == 20
|
||||
dobj = d2.decks.get(did)
|
||||
# conf should be 1
|
||||
assert dobj['conf'] == 1
|
||||
assert dobj["conf"] == 1
|
||||
# try again, limited to a deck
|
||||
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2")
|
||||
newname = str(newname)
|
||||
|
@ -68,13 +74,14 @@ def test_export_anki():
|
|||
d2 = aopen(newname)
|
||||
assert d2.cardCount() == 1
|
||||
|
||||
|
||||
@with_setup(setup1)
|
||||
def test_export_ankipkg():
|
||||
# add a test file to the media folder
|
||||
with open(os.path.join(deck.media.dir(), "今日.mp3"), "w") as f:
|
||||
f.write("test")
|
||||
n = deck.newNote()
|
||||
n['Front'] = '[sound:今日.mp3]'
|
||||
n["Front"] = "[sound:今日.mp3]"
|
||||
deck.addNote(n)
|
||||
e = AnkiPackageExporter(deck)
|
||||
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".apkg")
|
||||
|
@ -83,13 +90,14 @@ def test_export_ankipkg():
|
|||
os.unlink(newname)
|
||||
e.exportInto(newname)
|
||||
|
||||
|
||||
@with_setup(setup1)
|
||||
def test_export_anki_due():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = "foo"
|
||||
f["Front"] = "foo"
|
||||
deck.addNote(f)
|
||||
deck.crt -= 86400*10
|
||||
deck.crt -= 86400 * 10
|
||||
deck.sched.reset()
|
||||
c = deck.sched.getCard()
|
||||
deck.sched.answerCard(c, 3)
|
||||
|
@ -115,6 +123,7 @@ def test_export_anki_due():
|
|||
deck2.sched.reset()
|
||||
assert c.due - deck2.sched.today == 1
|
||||
|
||||
|
||||
# @with_setup(setup1)
|
||||
# def test_export_textcard():
|
||||
# e = TextCardExporter(deck)
|
||||
|
@ -124,6 +133,7 @@ def test_export_anki_due():
|
|||
# e.includeTags = True
|
||||
# e.exportInto(f)
|
||||
|
||||
|
||||
@with_setup(setup1)
|
||||
def test_export_textnote():
|
||||
e = TextNoteExporter(deck)
|
||||
|
@ -138,5 +148,6 @@ def test_export_textnote():
|
|||
e.exportInto(f)
|
||||
assert open(f).readline() == "foo\tbar\n"
|
||||
|
||||
|
||||
def test_exporters():
|
||||
assert "*.apkg" in str(exporters())
|
||||
|
|
|
@ -4,6 +4,7 @@ from nose2.tools.such import helper
|
|||
from anki.find import Finder
|
||||
from tests.shared import getEmptyCol
|
||||
|
||||
|
||||
def test_parse():
|
||||
f = Finder(None)
|
||||
assert f._tokenize("hello world") == ["hello", "world"]
|
||||
|
@ -12,43 +13,54 @@ def test_parse():
|
|||
assert f._tokenize("one --two") == ["one", "-", "two"]
|
||||
assert f._tokenize("one - two") == ["one", "-", "two"]
|
||||
assert f._tokenize("one or -two") == ["one", "or", "-", "two"]
|
||||
assert f._tokenize("'hello \"world\"'") == ["hello \"world\""]
|
||||
assert f._tokenize("'hello \"world\"'") == ['hello "world"']
|
||||
assert f._tokenize('"hello world"') == ["hello world"]
|
||||
assert f._tokenize("one (two or ( three or four))") == [
|
||||
"one", "(", "two", "or", "(", "three", "or", "four",
|
||||
")", ")"]
|
||||
"one",
|
||||
"(",
|
||||
"two",
|
||||
"or",
|
||||
"(",
|
||||
"three",
|
||||
"or",
|
||||
"four",
|
||||
")",
|
||||
")",
|
||||
]
|
||||
assert f._tokenize("embedded'string") == ["embedded'string"]
|
||||
assert f._tokenize("deck:'two words'") == ["deck:two words"]
|
||||
|
||||
|
||||
def test_findCards():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = 'dog'
|
||||
f['Back'] = 'cat'
|
||||
f["Front"] = "dog"
|
||||
f["Back"] = "cat"
|
||||
f.tags.append("monkey animal_1 * %")
|
||||
f1id = f.id
|
||||
deck.addNote(f)
|
||||
firstCardId = f.cards()[0].id
|
||||
f = deck.newNote()
|
||||
f['Front'] = 'goats are fun'
|
||||
f['Back'] = 'sheep'
|
||||
f["Front"] = "goats are fun"
|
||||
f["Back"] = "sheep"
|
||||
f.tags.append("sheep goat horse animal11")
|
||||
deck.addNote(f)
|
||||
f2id = f.id
|
||||
f = deck.newNote()
|
||||
f['Front'] = 'cat'
|
||||
f['Back'] = 'sheep'
|
||||
f["Front"] = "cat"
|
||||
f["Back"] = "sheep"
|
||||
deck.addNote(f)
|
||||
catCard = f.cards()[0]
|
||||
m = deck.models.current(); mm = deck.models
|
||||
m = deck.models.current()
|
||||
mm = deck.models
|
||||
t = mm.newTemplate("Reverse")
|
||||
t['qfmt'] = "{{Back}}"
|
||||
t['afmt'] = "{{Front}}"
|
||||
t["qfmt"] = "{{Back}}"
|
||||
t["afmt"] = "{{Front}}"
|
||||
mm.addTemplate(m, t)
|
||||
mm.save(m)
|
||||
f = deck.newNote()
|
||||
f['Front'] = 'test'
|
||||
f['Back'] = 'foo bar'
|
||||
f["Front"] = "test"
|
||||
f["Back"] = "foo bar"
|
||||
deck.addNote(f)
|
||||
latestCardIds = [c.id for c in f.cards()]
|
||||
# tag searches
|
||||
|
@ -66,9 +78,7 @@ def test_findCards():
|
|||
assert len(deck.findCards("tag:sheep -tag:monkey")) == 1
|
||||
assert len(deck.findCards("-tag:sheep")) == 4
|
||||
deck.tags.bulkAdd(deck.db.list("select id from notes"), "foo bar")
|
||||
assert (len(deck.findCards("tag:foo")) ==
|
||||
len(deck.findCards("tag:bar")) ==
|
||||
5)
|
||||
assert len(deck.findCards("tag:foo")) == len(deck.findCards("tag:bar")) == 5
|
||||
deck.tags.bulkRem(deck.db.list("select id from notes"), "foo")
|
||||
assert len(deck.findCards("tag:foo")) == 0
|
||||
assert len(deck.findCards("tag:bar")) == 5
|
||||
|
@ -86,7 +96,8 @@ def test_findCards():
|
|||
c.flush()
|
||||
assert deck.findCards("is:review") == [c.id]
|
||||
assert deck.findCards("is:due") == []
|
||||
c.due = 0; c.queue = 2
|
||||
c.due = 0
|
||||
c.queue = 2
|
||||
c.flush()
|
||||
assert deck.findCards("is:due") == [c.id]
|
||||
assert len(deck.findCards("-is:due")) == 4
|
||||
|
@ -97,7 +108,7 @@ def test_findCards():
|
|||
assert deck.findCards("is:suspended") == [c.id]
|
||||
# nids
|
||||
assert deck.findCards("nid:54321") == []
|
||||
assert len(deck.findCards("nid:%d"%f.id)) == 2
|
||||
assert len(deck.findCards("nid:%d" % f.id)) == 2
|
||||
assert len(deck.findCards("nid:%d,%d" % (f1id, f2id))) == 2
|
||||
# templates
|
||||
with helper.assertRaises(Exception):
|
||||
|
@ -115,16 +126,16 @@ def test_findCards():
|
|||
assert len(deck.findCards("front:do")) == 0
|
||||
assert len(deck.findCards("front:*")) == 5
|
||||
# ordering
|
||||
deck.conf['sortType'] = "noteCrt"
|
||||
deck.conf["sortType"] = "noteCrt"
|
||||
assert deck.findCards("front:*", order=True)[-1] in latestCardIds
|
||||
assert deck.findCards("", order=True)[-1] in latestCardIds
|
||||
deck.conf['sortType'] = "noteFld"
|
||||
deck.conf["sortType"] = "noteFld"
|
||||
assert deck.findCards("", order=True)[0] == catCard.id
|
||||
assert deck.findCards("", order=True)[-1] in latestCardIds
|
||||
deck.conf['sortType'] = "cardMod"
|
||||
deck.conf["sortType"] = "cardMod"
|
||||
assert deck.findCards("", order=True)[-1] in latestCardIds
|
||||
assert deck.findCards("", order=True)[0] == firstCardId
|
||||
deck.conf['sortBackwards'] = True
|
||||
deck.conf["sortBackwards"] = True
|
||||
assert deck.findCards("", order=True)[0] in latestCardIds
|
||||
# model
|
||||
assert len(deck.findCards("note:basic")) == 5
|
||||
|
@ -140,25 +151,26 @@ def test_findCards():
|
|||
deck.findCards("deck:*cefault")
|
||||
# full search
|
||||
f = deck.newNote()
|
||||
f['Front'] = 'hello<b>world</b>'
|
||||
f['Back'] = 'abc'
|
||||
f["Front"] = "hello<b>world</b>"
|
||||
f["Back"] = "abc"
|
||||
deck.addNote(f)
|
||||
# as it's the sort field, it matches
|
||||
assert len(deck.findCards("helloworld")) == 2
|
||||
#assert len(deck.findCards("helloworld", full=True)) == 2
|
||||
# assert len(deck.findCards("helloworld", full=True)) == 2
|
||||
# if we put it on the back, it won't
|
||||
(f['Front'], f['Back']) = (f['Back'], f['Front'])
|
||||
(f["Front"], f["Back"]) = (f["Back"], f["Front"])
|
||||
f.flush()
|
||||
assert len(deck.findCards("helloworld")) == 0
|
||||
#assert len(deck.findCards("helloworld", full=True)) == 2
|
||||
#assert len(deck.findCards("back:helloworld", full=True)) == 2
|
||||
# assert len(deck.findCards("helloworld", full=True)) == 2
|
||||
# assert len(deck.findCards("back:helloworld", full=True)) == 2
|
||||
# searching for an invalid special tag should not error
|
||||
with helper.assertRaises(Exception):
|
||||
len(deck.findCards("is:invalid"))
|
||||
# should be able to limit to parent deck, no children
|
||||
id = deck.db.scalar("select id from cards limit 1")
|
||||
deck.db.execute("update cards set did = ? where id = ?",
|
||||
deck.decks.id("Default::Child"), id)
|
||||
deck.db.execute(
|
||||
"update cards set did = ? where id = ?", deck.decks.id("Default::Child"), id
|
||||
)
|
||||
assert len(deck.findCards("deck:default")) == 7
|
||||
assert len(deck.findCards("deck:default::child")) == 1
|
||||
assert len(deck.findCards("deck:default -deck:default::*")) == 6
|
||||
|
@ -166,7 +178,9 @@ def test_findCards():
|
|||
id = deck.db.scalar("select id from cards limit 1")
|
||||
deck.db.execute(
|
||||
"update cards set queue=2, ivl=10, reps=20, due=30, factor=2200 "
|
||||
"where id = ?", id)
|
||||
"where id = ?",
|
||||
id,
|
||||
)
|
||||
assert len(deck.findCards("prop:ivl>5")) == 1
|
||||
assert len(deck.findCards("prop:ivl<5")) > 1
|
||||
assert len(deck.findCards("prop:ivl>=5")) == 1
|
||||
|
@ -205,8 +219,8 @@ def test_findCards():
|
|||
# empty field
|
||||
assert len(deck.findCards("front:")) == 0
|
||||
f = deck.newNote()
|
||||
f['Front'] = ''
|
||||
f['Back'] = 'abc2'
|
||||
f["Front"] = ""
|
||||
f["Back"] = "abc2"
|
||||
assert deck.addNote(f) == 1
|
||||
assert len(deck.findCards("front:")) == 1
|
||||
# OR searches and nesting
|
||||
|
@ -220,8 +234,7 @@ def test_findCards():
|
|||
assert len(deck.findCards("(()")) == 0
|
||||
# added
|
||||
assert len(deck.findCards("added:0")) == 0
|
||||
deck.db.execute("update cards set id = id - 86400*1000 where id = ?",
|
||||
id)
|
||||
deck.db.execute("update cards set id = id - 86400*1000 where id = ?", id)
|
||||
assert len(deck.findCards("added:1")) == deck.cardCount() - 1
|
||||
assert len(deck.findCards("added:2")) == deck.cardCount()
|
||||
# flag
|
||||
|
@ -230,50 +243,58 @@ def test_findCards():
|
|||
with helper.assertRaises(Exception):
|
||||
deck.findCards("flag:12")
|
||||
|
||||
|
||||
def test_findReplace():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = 'foo'
|
||||
f['Back'] = 'bar'
|
||||
f["Front"] = "foo"
|
||||
f["Back"] = "bar"
|
||||
deck.addNote(f)
|
||||
f2 = deck.newNote()
|
||||
f2['Front'] = 'baz'
|
||||
f2['Back'] = 'foo'
|
||||
f2["Front"] = "baz"
|
||||
f2["Back"] = "foo"
|
||||
deck.addNote(f2)
|
||||
nids = [f.id, f2.id]
|
||||
# should do nothing
|
||||
assert deck.findReplace(nids, "abc", "123") == 0
|
||||
# global replace
|
||||
assert deck.findReplace(nids, "foo", "qux") == 2
|
||||
f.load(); assert f['Front'] == "qux"
|
||||
f2.load(); assert f2['Back'] == "qux"
|
||||
f.load()
|
||||
assert f["Front"] == "qux"
|
||||
f2.load()
|
||||
assert f2["Back"] == "qux"
|
||||
# single field replace
|
||||
assert deck.findReplace(nids, "qux", "foo", field="Front") == 1
|
||||
f.load(); assert f['Front'] == "foo"
|
||||
f2.load(); assert f2['Back'] == "qux"
|
||||
f.load()
|
||||
assert f["Front"] == "foo"
|
||||
f2.load()
|
||||
assert f2["Back"] == "qux"
|
||||
# regex replace
|
||||
assert deck.findReplace(nids, "B.r", "reg") == 0
|
||||
f.load(); assert f['Back'] != "reg"
|
||||
f.load()
|
||||
assert f["Back"] != "reg"
|
||||
assert deck.findReplace(nids, "B.r", "reg", regex=True) == 1
|
||||
f.load(); assert f['Back'] == "reg"
|
||||
f.load()
|
||||
assert f["Back"] == "reg"
|
||||
|
||||
|
||||
def test_findDupes():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = 'foo'
|
||||
f['Back'] = 'bar'
|
||||
f["Front"] = "foo"
|
||||
f["Back"] = "bar"
|
||||
deck.addNote(f)
|
||||
f2 = deck.newNote()
|
||||
f2['Front'] = 'baz'
|
||||
f2['Back'] = 'bar'
|
||||
f2["Front"] = "baz"
|
||||
f2["Back"] = "bar"
|
||||
deck.addNote(f2)
|
||||
f3 = deck.newNote()
|
||||
f3['Front'] = 'quux'
|
||||
f3['Back'] = 'bar'
|
||||
f3["Front"] = "quux"
|
||||
f3["Back"] = "bar"
|
||||
deck.addNote(f3)
|
||||
f4 = deck.newNote()
|
||||
f4['Front'] = 'quuux'
|
||||
f4['Back'] = 'nope'
|
||||
f4["Front"] = "quuux"
|
||||
f4["Back"] = "nope"
|
||||
deck.addNote(f4)
|
||||
r = deck.findDupes("Back")
|
||||
assert r[0][0] == "bar"
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from tests.shared import assertException, getEmptyCol
|
||||
|
||||
|
||||
def test_flags():
|
||||
col = getEmptyCol()
|
||||
n = col.newNote()
|
||||
n['Front'] = "one"; n['Back'] = "two"
|
||||
n["Front"] = "one"
|
||||
n["Back"] = "two"
|
||||
cnt = col.addNote(n)
|
||||
c = n.cards()[0]
|
||||
# make sure higher bits are preserved
|
||||
|
|
|
@ -3,20 +3,26 @@
|
|||
import os
|
||||
from tests.shared import getUpgradeDeckPath, getEmptyCol
|
||||
from anki.utils import ids2str
|
||||
from anki.importing import Anki2Importer, TextImporter, \
|
||||
SupermemoXmlImporter, MnemosyneImporter, AnkiPackageImporter
|
||||
from anki.importing import (
|
||||
Anki2Importer,
|
||||
TextImporter,
|
||||
SupermemoXmlImporter,
|
||||
MnemosyneImporter,
|
||||
AnkiPackageImporter,
|
||||
)
|
||||
|
||||
testDir = os.path.dirname(__file__)
|
||||
|
||||
srcNotes=None
|
||||
srcCards=None
|
||||
srcNotes = None
|
||||
srcCards = None
|
||||
|
||||
|
||||
def test_anki2_mediadupes():
|
||||
tmp = getEmptyCol()
|
||||
# add a note that references a sound
|
||||
n = tmp.newNote()
|
||||
n['Front'] = "[sound:foo.mp3]"
|
||||
mid = n.model()['id']
|
||||
n["Front"] = "[sound:foo.mp3]"
|
||||
mid = n.model()["id"]
|
||||
tmp.addNote(n)
|
||||
# add that sound to media folder
|
||||
with open(os.path.join(tmp.media.dir(), "foo.mp3"), "w") as f:
|
||||
|
@ -41,8 +47,7 @@ def test_anki2_mediadupes():
|
|||
f.write("bar")
|
||||
imp = Anki2Importer(empty, tmp.path)
|
||||
imp.run()
|
||||
assert sorted(os.listdir(empty.media.dir())) == [
|
||||
"foo.mp3", "foo_%s.mp3" % mid]
|
||||
assert sorted(os.listdir(empty.media.dir())) == ["foo.mp3", "foo_%s.mp3" % mid]
|
||||
n = empty.getNote(empty.db.scalar("select id from notes"))
|
||||
assert "_" in n.fields[0]
|
||||
# if the localized media file already exists, we rewrite the note and
|
||||
|
@ -52,25 +57,24 @@ def test_anki2_mediadupes():
|
|||
f.write("bar")
|
||||
imp = Anki2Importer(empty, tmp.path)
|
||||
imp.run()
|
||||
assert sorted(os.listdir(empty.media.dir())) == [
|
||||
"foo.mp3", "foo_%s.mp3" % mid]
|
||||
assert sorted(os.listdir(empty.media.dir())) == [
|
||||
"foo.mp3", "foo_%s.mp3" % mid]
|
||||
assert sorted(os.listdir(empty.media.dir())) == ["foo.mp3", "foo_%s.mp3" % mid]
|
||||
assert sorted(os.listdir(empty.media.dir())) == ["foo.mp3", "foo_%s.mp3" % mid]
|
||||
n = empty.getNote(empty.db.scalar("select id from notes"))
|
||||
assert "_" in n.fields[0]
|
||||
|
||||
|
||||
def test_apkg():
|
||||
tmp = getEmptyCol()
|
||||
apkg = str(os.path.join(testDir, "support/media.apkg"))
|
||||
imp = AnkiPackageImporter(tmp, apkg)
|
||||
assert os.listdir(tmp.media.dir()) == []
|
||||
imp.run()
|
||||
assert os.listdir(tmp.media.dir()) == ['foo.wav']
|
||||
assert os.listdir(tmp.media.dir()) == ["foo.wav"]
|
||||
# importing again should be idempotent in terms of media
|
||||
tmp.remCards(tmp.db.list("select id from cards"))
|
||||
imp = AnkiPackageImporter(tmp, apkg)
|
||||
imp.run()
|
||||
assert os.listdir(tmp.media.dir()) == ['foo.wav']
|
||||
assert os.listdir(tmp.media.dir()) == ["foo.wav"]
|
||||
# but if the local file has different data, it will rename
|
||||
tmp.remCards(tmp.db.list("select id from cards"))
|
||||
with open(os.path.join(tmp.media.dir(), "foo.wav"), "w") as f:
|
||||
|
@ -79,6 +83,7 @@ def test_apkg():
|
|||
imp.run()
|
||||
assert len(os.listdir(tmp.media.dir())) == 2
|
||||
|
||||
|
||||
def test_anki2_diffmodel_templates():
|
||||
# different from the above as this one tests only the template text being
|
||||
# changed, not the number of cards/fields
|
||||
|
@ -94,11 +99,12 @@ def test_anki2_diffmodel_templates():
|
|||
imp.dupeOnSchemaChange = True
|
||||
imp.run()
|
||||
# collection should contain the note we imported
|
||||
assert(dst.noteCount() == 1)
|
||||
assert dst.noteCount() == 1
|
||||
# the front template should contain the text added in the 2nd package
|
||||
tcid = dst.findCards("")[0] # only 1 note in collection
|
||||
tnote = dst.getCard(tcid).note()
|
||||
assert("Changed Front Template" in dst.findTemplates(tnote)[0]['qfmt'])
|
||||
assert "Changed Front Template" in dst.findTemplates(tnote)[0]["qfmt"]
|
||||
|
||||
|
||||
def test_anki2_updates():
|
||||
# create a new empty deck
|
||||
|
@ -127,6 +133,7 @@ def test_anki2_updates():
|
|||
assert dst.noteCount() == 1
|
||||
assert dst.db.scalar("select flds from notes").startswith("goodbye")
|
||||
|
||||
|
||||
def test_csv():
|
||||
deck = getEmptyCol()
|
||||
file = str(os.path.join(testDir, "support/text-2fields.txt"))
|
||||
|
@ -147,7 +154,7 @@ def test_csv():
|
|||
n.flush()
|
||||
i.run()
|
||||
n.load()
|
||||
assert n.tags == ['test']
|
||||
assert n.tags == ["test"]
|
||||
# if add-only mode, count will be 0
|
||||
i.importMode = 1
|
||||
i.run()
|
||||
|
@ -161,6 +168,7 @@ def test_csv():
|
|||
assert deck.cardCount() == 11
|
||||
deck.close()
|
||||
|
||||
|
||||
def test_csv2():
|
||||
deck = getEmptyCol()
|
||||
mm = deck.models
|
||||
|
@ -169,9 +177,9 @@ def test_csv2():
|
|||
mm.addField(m, f)
|
||||
mm.save(m)
|
||||
n = deck.newNote()
|
||||
n['Front'] = "1"
|
||||
n['Back'] = "2"
|
||||
n['Three'] = "3"
|
||||
n["Front"] = "1"
|
||||
n["Back"] = "2"
|
||||
n["Three"] = "3"
|
||||
deck.addNote(n)
|
||||
# an update with unmapped fields should not clobber those fields
|
||||
file = str(os.path.join(testDir, "support/text-update.txt"))
|
||||
|
@ -179,16 +187,17 @@ def test_csv2():
|
|||
i.initMapping()
|
||||
i.run()
|
||||
n.load()
|
||||
assert n['Front'] == "1"
|
||||
assert n['Back'] == "x"
|
||||
assert n['Three'] == "3"
|
||||
assert n["Front"] == "1"
|
||||
assert n["Back"] == "x"
|
||||
assert n["Three"] == "3"
|
||||
deck.close()
|
||||
|
||||
|
||||
def test_supermemo_xml_01_unicode():
|
||||
deck = getEmptyCol()
|
||||
file = str(os.path.join(testDir, "support/supermemo1.xml"))
|
||||
i = SupermemoXmlImporter(deck, file)
|
||||
#i.META.logToStdOutput = True
|
||||
# i.META.logToStdOutput = True
|
||||
i.run()
|
||||
assert i.total == 1
|
||||
cid = deck.db.scalar("select id from cards")
|
||||
|
@ -198,6 +207,7 @@ def test_supermemo_xml_01_unicode():
|
|||
assert c.reps == 7
|
||||
deck.close()
|
||||
|
||||
|
||||
def test_mnemo():
|
||||
deck = getEmptyCol()
|
||||
file = str(os.path.join(testDir, "support/mnemo.db"))
|
||||
|
|
|
@ -7,14 +7,16 @@ import shutil
|
|||
from tests.shared import getEmptyCol
|
||||
from anki.utils import stripHTML
|
||||
|
||||
|
||||
def test_latex():
|
||||
d = getEmptyCol()
|
||||
# change latex cmd to simulate broken build
|
||||
import anki.latex
|
||||
|
||||
anki.latex.pngCommands[0][0] = "nolatex"
|
||||
# add a note with latex
|
||||
f = d.newNote()
|
||||
f['Front'] = "[latex]hello[/latex]"
|
||||
f["Front"] = "[latex]hello[/latex]"
|
||||
d.addNote(f)
|
||||
# but since latex couldn't run, there's nothing there
|
||||
assert len(os.listdir(d.media.dir())) == 0
|
||||
|
@ -34,13 +36,13 @@ def test_latex():
|
|||
assert ".png" in f.cards()[0].q()
|
||||
# adding new notes should cause generation on question display
|
||||
f = d.newNote()
|
||||
f['Front'] = "[latex]world[/latex]"
|
||||
f["Front"] = "[latex]world[/latex]"
|
||||
d.addNote(f)
|
||||
f.cards()[0].q()
|
||||
assert len(os.listdir(d.media.dir())) == 2
|
||||
# another note with the same media should reuse
|
||||
f = d.newNote()
|
||||
f['Front'] = " [latex]world[/latex]"
|
||||
f["Front"] = " [latex]world[/latex]"
|
||||
d.addNote(f)
|
||||
assert len(os.listdir(d.media.dir())) == 2
|
||||
oldcard = f.cards()[0]
|
||||
|
@ -49,7 +51,7 @@ def test_latex():
|
|||
# missing media will show the latex
|
||||
anki.latex.build = False
|
||||
f = d.newNote()
|
||||
f['Front'] = "[latex]foo[/latex]"
|
||||
f["Front"] = "[latex]foo[/latex]"
|
||||
d.addNote(f)
|
||||
assert len(os.listdir(d.media.dir())) == 2
|
||||
assert stripHTML(f.cards()[0].q()) == "[latex]foo[/latex]"
|
||||
|
@ -86,10 +88,11 @@ def test_latex():
|
|||
(result, msg) = _test_includes_bad_command("\\emph")
|
||||
assert not result, msg
|
||||
|
||||
|
||||
def _test_includes_bad_command(bad):
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = '[latex]%s[/latex]' % bad
|
||||
f["Front"] = "[latex]%s[/latex]" % bad
|
||||
d.addNote(f)
|
||||
q = f.cards()[0].q()
|
||||
return ("'%s' is not allowed on cards" % bad in q, "Card content: %s" % q)
|
||||
|
|
|
@ -23,6 +23,7 @@ def test_add():
|
|||
f.write("world")
|
||||
assert d.media.addFile(path) == "foo (1).jpg"
|
||||
|
||||
|
||||
def test_strings():
|
||||
d = getEmptyCol()
|
||||
mf = d.media.filesInStr
|
||||
|
@ -31,12 +32,16 @@ def test_strings():
|
|||
assert mf(mid, "aoeu<img src='foo.jpg'>ao") == ["foo.jpg"]
|
||||
assert mf(mid, "aoeu<img src='foo.jpg' style='test'>ao") == ["foo.jpg"]
|
||||
assert mf(mid, "aoeu<img src='foo.jpg'><img src=\"bar.jpg\">ao") == [
|
||||
"foo.jpg", "bar.jpg"]
|
||||
"foo.jpg",
|
||||
"bar.jpg",
|
||||
]
|
||||
assert mf(mid, "aoeu<img src=foo.jpg style=bar>ao") == ["foo.jpg"]
|
||||
assert mf(mid, "<img src=one><img src=two>") == ["one", "two"]
|
||||
assert mf(mid, "aoeu<img src=\"foo.jpg\">ao") == ["foo.jpg"]
|
||||
assert mf(mid, "aoeu<img src=\"foo.jpg\"><img class=yo src=fo>ao") == [
|
||||
"foo.jpg", "fo"]
|
||||
assert mf(mid, 'aoeu<img src="foo.jpg">ao') == ["foo.jpg"]
|
||||
assert mf(mid, 'aoeu<img src="foo.jpg"><img class=yo src=fo>ao') == [
|
||||
"foo.jpg",
|
||||
"fo",
|
||||
]
|
||||
assert mf(mid, "aou[sound:foo.mp3]aou") == ["foo.mp3"]
|
||||
sp = d.media.strip
|
||||
assert sp("aoeu") == "aoeu"
|
||||
|
@ -47,6 +52,7 @@ def test_strings():
|
|||
assert es("<img src='http://foo.com'>") == "<img src='http://foo.com'>"
|
||||
assert es('<img src="foo bar.jpg">') == '<img src="foo%20bar.jpg">'
|
||||
|
||||
|
||||
def test_deckIntegration():
|
||||
d = getEmptyCol()
|
||||
# create a media dir
|
||||
|
@ -56,11 +62,13 @@ def test_deckIntegration():
|
|||
d.media.addFile(file)
|
||||
# add a note which references it
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "<img src='fake.png'>"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "<img src='fake.png'>"
|
||||
d.addNote(f)
|
||||
# and one which references a non-existent file
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "<img src='fake2.png'>"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "<img src='fake2.png'>"
|
||||
d.addNote(f)
|
||||
# and add another file which isn't used
|
||||
with open(os.path.join(d.media.dir(), "foo.jpg"), "w") as f:
|
||||
|
@ -70,12 +78,16 @@ def test_deckIntegration():
|
|||
assert ret[0] == ["fake2.png"]
|
||||
assert ret[1] == ["foo.jpg"]
|
||||
|
||||
|
||||
def test_changes():
|
||||
d = getEmptyCol()
|
||||
|
||||
def added():
|
||||
return d.media.db.execute("select fname from media where csum is not null")
|
||||
|
||||
def removed():
|
||||
return d.media.db.execute("select fname from media where csum is null")
|
||||
|
||||
assert not list(added())
|
||||
assert not list(removed())
|
||||
# add a file
|
||||
|
@ -97,26 +109,27 @@ def test_changes():
|
|||
assert not list(removed())
|
||||
# but if we add another file, it will
|
||||
time.sleep(1)
|
||||
with open(path+"2", "w") as f:
|
||||
with open(path + "2", "w") as f:
|
||||
f.write("yo")
|
||||
d.media.findChanges()
|
||||
assert len(list(added())) == 2
|
||||
assert not list(removed())
|
||||
# deletions should get noticed too
|
||||
time.sleep(1)
|
||||
os.unlink(path+"2")
|
||||
os.unlink(path + "2")
|
||||
d.media.findChanges()
|
||||
assert len(list(added())) == 1
|
||||
assert len(list(removed())) == 1
|
||||
|
||||
|
||||
def test_illegal():
|
||||
d = getEmptyCol()
|
||||
aString = "a:b|cd\\e/f\0g*h"
|
||||
good = "abcdefgh"
|
||||
assert d.media.stripIllegal(aString) == good
|
||||
for c in aString:
|
||||
bad = d.media.hasIllegal("somestring"+c+"morestring")
|
||||
bad = d.media.hasIllegal("somestring" + c + "morestring")
|
||||
if bad:
|
||||
assert(c not in good)
|
||||
assert c not in good
|
||||
else:
|
||||
assert(c in good)
|
||||
assert c in good
|
||||
|
|
|
@ -6,39 +6,42 @@ from anki.consts import MODEL_CLOZE
|
|||
from anki.utils import stripHTML, joinFields, isWin
|
||||
import anki.template
|
||||
|
||||
|
||||
def test_modelDelete():
|
||||
deck = getEmptyCol()
|
||||
f = deck.newNote()
|
||||
f['Front'] = '1'
|
||||
f['Back'] = '2'
|
||||
f["Front"] = "1"
|
||||
f["Back"] = "2"
|
||||
deck.addNote(f)
|
||||
assert deck.cardCount() == 1
|
||||
deck.models.rem(deck.models.current())
|
||||
assert deck.cardCount() == 0
|
||||
|
||||
|
||||
def test_modelCopy():
|
||||
deck = getEmptyCol()
|
||||
m = deck.models.current()
|
||||
m2 = deck.models.copy(m)
|
||||
assert m2['name'] == "Basic copy"
|
||||
assert m2['id'] != m['id']
|
||||
assert len(m2['flds']) == 2
|
||||
assert len(m['flds']) == 2
|
||||
assert len(m2['flds']) == len(m['flds'])
|
||||
assert len(m['tmpls']) == 1
|
||||
assert len(m2['tmpls']) == 1
|
||||
assert m2["name"] == "Basic copy"
|
||||
assert m2["id"] != m["id"]
|
||||
assert len(m2["flds"]) == 2
|
||||
assert len(m["flds"]) == 2
|
||||
assert len(m2["flds"]) == len(m["flds"])
|
||||
assert len(m["tmpls"]) == 1
|
||||
assert len(m2["tmpls"]) == 1
|
||||
assert deck.models.scmhash(m) == deck.models.scmhash(m2)
|
||||
|
||||
|
||||
def test_fields():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = '1'
|
||||
f['Back'] = '2'
|
||||
f["Front"] = "1"
|
||||
f["Back"] = "2"
|
||||
d.addNote(f)
|
||||
m = d.models.current()
|
||||
# make sure renaming a field updates the templates
|
||||
d.models.renameField(m, m['flds'][0], "NewFront")
|
||||
assert "{{NewFront}}" in m['tmpls'][0]['qfmt']
|
||||
d.models.renameField(m, m["flds"][0], "NewFront")
|
||||
assert "{{NewFront}}" in m["tmpls"][0]["qfmt"]
|
||||
h = d.models.scmhash(m)
|
||||
# add a field
|
||||
f = d.models.newField("foo")
|
||||
|
@ -47,44 +50,46 @@ def test_fields():
|
|||
assert d.models.scmhash(m) != h
|
||||
# rename it
|
||||
d.models.renameField(m, f, "bar")
|
||||
assert d.getNote(d.models.nids(m)[0])['bar'] == ''
|
||||
assert d.getNote(d.models.nids(m)[0])["bar"] == ""
|
||||
# delete back
|
||||
d.models.remField(m, m['flds'][1])
|
||||
d.models.remField(m, m["flds"][1])
|
||||
assert d.getNote(d.models.nids(m)[0]).fields == ["1", ""]
|
||||
# move 0 -> 1
|
||||
d.models.moveField(m, m['flds'][0], 1)
|
||||
d.models.moveField(m, m["flds"][0], 1)
|
||||
assert d.getNote(d.models.nids(m)[0]).fields == ["", "1"]
|
||||
# move 1 -> 0
|
||||
d.models.moveField(m, m['flds'][1], 0)
|
||||
d.models.moveField(m, m["flds"][1], 0)
|
||||
assert d.getNote(d.models.nids(m)[0]).fields == ["1", ""]
|
||||
# add another and put in middle
|
||||
f = d.models.newField("baz")
|
||||
d.models.addField(m, f)
|
||||
f = d.getNote(d.models.nids(m)[0])
|
||||
f['baz'] = "2"
|
||||
f["baz"] = "2"
|
||||
f.flush()
|
||||
assert d.getNote(d.models.nids(m)[0]).fields == ["1", "", "2"]
|
||||
# move 2 -> 1
|
||||
d.models.moveField(m, m['flds'][2], 1)
|
||||
d.models.moveField(m, m["flds"][2], 1)
|
||||
assert d.getNote(d.models.nids(m)[0]).fields == ["1", "2", ""]
|
||||
# move 0 -> 2
|
||||
d.models.moveField(m, m['flds'][0], 2)
|
||||
d.models.moveField(m, m["flds"][0], 2)
|
||||
assert d.getNote(d.models.nids(m)[0]).fields == ["2", "", "1"]
|
||||
# move 0 -> 1
|
||||
d.models.moveField(m, m['flds'][0], 1)
|
||||
d.models.moveField(m, m["flds"][0], 1)
|
||||
assert d.getNote(d.models.nids(m)[0]).fields == ["", "2", "1"]
|
||||
|
||||
|
||||
def test_templates():
|
||||
d = getEmptyCol()
|
||||
m = d.models.current(); mm = d.models
|
||||
m = d.models.current()
|
||||
mm = d.models
|
||||
t = mm.newTemplate("Reverse")
|
||||
t['qfmt'] = "{{Back}}"
|
||||
t['afmt'] = "{{Front}}"
|
||||
t["qfmt"] = "{{Back}}"
|
||||
t["afmt"] = "{{Front}}"
|
||||
mm.addTemplate(m, t)
|
||||
mm.save(m)
|
||||
f = d.newNote()
|
||||
f['Front'] = '1'
|
||||
f['Back'] = '2'
|
||||
f["Front"] = "1"
|
||||
f["Back"] = "2"
|
||||
d.addNote(f)
|
||||
assert d.cardCount() == 2
|
||||
(c, c2) = f.cards()
|
||||
|
@ -93,11 +98,12 @@ def test_templates():
|
|||
assert c2.ord == 1
|
||||
# switch templates
|
||||
d.models.moveTemplate(m, c.template(), 1)
|
||||
c.load(); c2.load()
|
||||
c.load()
|
||||
c2.load()
|
||||
assert c.ord == 1
|
||||
assert c2.ord == 0
|
||||
# removing a template should delete its cards
|
||||
assert d.models.remTemplate(m, m['tmpls'][0])
|
||||
assert d.models.remTemplate(m, m["tmpls"][0])
|
||||
assert d.cardCount() == 1
|
||||
# and should have updated the other cards' ordinals
|
||||
c = f.cards()[0]
|
||||
|
@ -106,23 +112,25 @@ def test_templates():
|
|||
# it shouldn't be possible to orphan notes by removing templates
|
||||
t = mm.newTemplate("template name")
|
||||
mm.addTemplate(m, t)
|
||||
assert not d.models.remTemplate(m, m['tmpls'][0])
|
||||
assert not d.models.remTemplate(m, m["tmpls"][0])
|
||||
|
||||
|
||||
def test_cloze_ordinals():
|
||||
d = getEmptyCol()
|
||||
d.models.setCurrent(d.models.byName("Cloze"))
|
||||
m = d.models.current(); mm = d.models
|
||||
m = d.models.current()
|
||||
mm = d.models
|
||||
|
||||
#We replace the default Cloze template
|
||||
# We replace the default Cloze template
|
||||
t = mm.newTemplate("ChainedCloze")
|
||||
t['qfmt'] = "{{text:cloze:Text}}"
|
||||
t['afmt'] = "{{text:cloze:Text}}"
|
||||
t["qfmt"] = "{{text:cloze:Text}}"
|
||||
t["afmt"] = "{{text:cloze:Text}}"
|
||||
mm.addTemplate(m, t)
|
||||
mm.save(m)
|
||||
d.models.remTemplate(m, m['tmpls'][0])
|
||||
d.models.remTemplate(m, m["tmpls"][0])
|
||||
|
||||
f = d.newNote()
|
||||
f['Text'] = '{{c1::firstQ::firstA}}{{c2::secondQ::secondA}}'
|
||||
f["Text"] = "{{c1::firstQ::firstA}}{{c2::secondQ::secondA}}"
|
||||
d.addNote(f)
|
||||
assert d.cardCount() == 2
|
||||
(c, c2) = f.cards()
|
||||
|
@ -134,36 +142,37 @@ def test_cloze_ordinals():
|
|||
def test_text():
|
||||
d = getEmptyCol()
|
||||
m = d.models.current()
|
||||
m['tmpls'][0]['qfmt'] = "{{text:Front}}"
|
||||
m["tmpls"][0]["qfmt"] = "{{text:Front}}"
|
||||
d.models.save(m)
|
||||
f = d.newNote()
|
||||
f['Front'] = 'hello<b>world'
|
||||
f["Front"] = "hello<b>world"
|
||||
d.addNote(f)
|
||||
assert "helloworld" in f.cards()[0].q()
|
||||
|
||||
|
||||
def test_cloze():
|
||||
d = getEmptyCol()
|
||||
d.models.setCurrent(d.models.byName("Cloze"))
|
||||
f = d.newNote()
|
||||
assert f.model()['name'] == "Cloze"
|
||||
assert f.model()["name"] == "Cloze"
|
||||
# a cloze model with no clozes is not empty
|
||||
f['Text'] = 'nothing'
|
||||
f["Text"] = "nothing"
|
||||
assert d.addNote(f)
|
||||
# try with one cloze
|
||||
f = d.newNote()
|
||||
f['Text'] = "hello {{c1::world}}"
|
||||
f["Text"] = "hello {{c1::world}}"
|
||||
assert d.addNote(f) == 1
|
||||
assert "hello <span class=cloze>[...]</span>" in f.cards()[0].q()
|
||||
assert "hello <span class=cloze>world</span>" in f.cards()[0].a()
|
||||
# and with a comment
|
||||
f = d.newNote()
|
||||
f['Text'] = "hello {{c1::world::typical}}"
|
||||
f["Text"] = "hello {{c1::world::typical}}"
|
||||
assert d.addNote(f) == 1
|
||||
assert "<span class=cloze>[typical]</span>" in f.cards()[0].q()
|
||||
assert "<span class=cloze>world</span>" in f.cards()[0].a()
|
||||
# and with 2 clozes
|
||||
f = d.newNote()
|
||||
f['Text'] = "hello {{c1::world}} {{c2::bar}}"
|
||||
f["Text"] = "hello {{c1::world}} {{c2::bar}}"
|
||||
assert d.addNote(f) == 2
|
||||
(c1, c2) = f.cards()
|
||||
assert "<span class=cloze>[...]</span> bar" in c1.q()
|
||||
|
@ -173,25 +182,27 @@ def test_cloze():
|
|||
# if there are multiple answers for a single cloze, they are given in a
|
||||
# list
|
||||
f = d.newNote()
|
||||
f['Text'] = "a {{c1::b}} {{c1::c}}"
|
||||
f["Text"] = "a {{c1::b}} {{c1::c}}"
|
||||
assert d.addNote(f) == 1
|
||||
assert "<span class=cloze>b</span> <span class=cloze>c</span>" in (
|
||||
f.cards()[0].a())
|
||||
assert "<span class=cloze>b</span> <span class=cloze>c</span>" in (f.cards()[0].a())
|
||||
# if we add another cloze, a card should be generated
|
||||
cnt = d.cardCount()
|
||||
f['Text'] = "{{c2::hello}} {{c1::foo}}"
|
||||
f["Text"] = "{{c2::hello}} {{c1::foo}}"
|
||||
f.flush()
|
||||
assert d.cardCount() == cnt + 1
|
||||
# 0 or negative indices are not supported
|
||||
f['Text'] += "{{c0::zero}} {{c-1:foo}}"
|
||||
f["Text"] += "{{c0::zero}} {{c-1:foo}}"
|
||||
f.flush()
|
||||
assert len(f.cards()) == 2
|
||||
|
||||
|
||||
def test_cloze_mathjax():
|
||||
d = getEmptyCol()
|
||||
d.models.setCurrent(d.models.byName("Cloze"))
|
||||
f = d.newNote()
|
||||
f['Text'] = r'{{c1::ok}} \(2^2\) {{c2::not ok}} \(2^{{c3::2}}\) \(x^3\) {{c4::blah}} {{c5::text with \(x^2\) jax}}'
|
||||
f[
|
||||
"Text"
|
||||
] = r"{{c1::ok}} \(2^2\) {{c2::not ok}} \(2^{{c3::2}}\) \(x^3\) {{c4::blah}} {{c5::text with \(x^2\) jax}}"
|
||||
assert d.addNote(f)
|
||||
assert len(f.cards()) == 5
|
||||
assert "class=cloze" in f.cards()[0].q()
|
||||
|
@ -201,56 +212,70 @@ def test_cloze_mathjax():
|
|||
assert "class=cloze" in f.cards()[4].q()
|
||||
|
||||
f = d.newNote()
|
||||
f['Text'] = r'\(a\) {{c1::b}} \[ {{c1::c}} \]'
|
||||
f["Text"] = r"\(a\) {{c1::b}} \[ {{c1::c}} \]"
|
||||
assert d.addNote(f)
|
||||
assert len(f.cards()) == 1
|
||||
assert f.cards()[0].q().endswith('\(a\) <span class=cloze>[...]</span> \[ [...] \]')
|
||||
assert f.cards()[0].q().endswith("\(a\) <span class=cloze>[...]</span> \[ [...] \]")
|
||||
|
||||
|
||||
def test_chained_mods():
|
||||
d = getEmptyCol()
|
||||
d.models.setCurrent(d.models.byName("Cloze"))
|
||||
m = d.models.current(); mm = d.models
|
||||
m = d.models.current()
|
||||
mm = d.models
|
||||
|
||||
#We replace the default Cloze template
|
||||
# We replace the default Cloze template
|
||||
t = mm.newTemplate("ChainedCloze")
|
||||
t['qfmt'] = "{{cloze:text:Text}}"
|
||||
t['afmt'] = "{{cloze:text:Text}}"
|
||||
t["qfmt"] = "{{cloze:text:Text}}"
|
||||
t["afmt"] = "{{cloze:text:Text}}"
|
||||
mm.addTemplate(m, t)
|
||||
mm.save(m)
|
||||
d.models.remTemplate(m, m['tmpls'][0])
|
||||
d.models.remTemplate(m, m["tmpls"][0])
|
||||
|
||||
f = d.newNote()
|
||||
q1 = '<span style=\"color:red\">phrase</span>'
|
||||
a1 = '<b>sentence</b>'
|
||||
q2 = '<span style=\"color:red\">en chaine</span>'
|
||||
a2 = '<i>chained</i>'
|
||||
f['Text'] = "This {{c1::%s::%s}} demonstrates {{c1::%s::%s}} clozes." % (q1,a1,q2,a2)
|
||||
q1 = '<span style="color:red">phrase</span>'
|
||||
a1 = "<b>sentence</b>"
|
||||
q2 = '<span style="color:red">en chaine</span>'
|
||||
a2 = "<i>chained</i>"
|
||||
f["Text"] = "This {{c1::%s::%s}} demonstrates {{c1::%s::%s}} clozes." % (
|
||||
q1,
|
||||
a1,
|
||||
q2,
|
||||
a2,
|
||||
)
|
||||
assert d.addNote(f) == 1
|
||||
assert "This <span class=cloze>[sentence]</span> demonstrates <span class=cloze>[chained]</span> clozes." in f.cards()[0].q()
|
||||
assert "This <span class=cloze>phrase</span> demonstrates <span class=cloze>en chaine</span> clozes." in f.cards()[0].a()
|
||||
assert (
|
||||
"This <span class=cloze>[sentence]</span> demonstrates <span class=cloze>[chained]</span> clozes."
|
||||
in f.cards()[0].q()
|
||||
)
|
||||
assert (
|
||||
"This <span class=cloze>phrase</span> demonstrates <span class=cloze>en chaine</span> clozes."
|
||||
in f.cards()[0].a()
|
||||
)
|
||||
|
||||
|
||||
def test_modelChange():
|
||||
deck = getEmptyCol()
|
||||
basic = deck.models.byName("Basic")
|
||||
cloze = deck.models.byName("Cloze")
|
||||
# enable second template and add a note
|
||||
m = deck.models.current(); mm = deck.models
|
||||
m = deck.models.current()
|
||||
mm = deck.models
|
||||
t = mm.newTemplate("Reverse")
|
||||
t['qfmt'] = "{{Back}}"
|
||||
t['afmt'] = "{{Front}}"
|
||||
t["qfmt"] = "{{Back}}"
|
||||
t["afmt"] = "{{Front}}"
|
||||
mm.addTemplate(m, t)
|
||||
mm.save(m)
|
||||
f = deck.newNote()
|
||||
f['Front'] = 'f'
|
||||
f['Back'] = 'b123'
|
||||
f["Front"] = "f"
|
||||
f["Back"] = "b123"
|
||||
deck.addNote(f)
|
||||
# switch fields
|
||||
map = {0: 1, 1: 0}
|
||||
deck.models.change(basic, [f.id], basic, map, None)
|
||||
f.load()
|
||||
assert f['Front'] == 'b123'
|
||||
assert f['Back'] == 'f'
|
||||
assert f["Front"] == "b123"
|
||||
assert f["Back"] == "f"
|
||||
# switch cards
|
||||
c0 = f.cards()[0]
|
||||
c1 = f.cards()[1]
|
||||
|
@ -259,7 +284,9 @@ def test_modelChange():
|
|||
assert c0.ord == 0
|
||||
assert c1.ord == 1
|
||||
deck.models.change(basic, [f.id], basic, None, map)
|
||||
f.load(); c0.load(); c1.load()
|
||||
f.load()
|
||||
c0.load()
|
||||
c1.load()
|
||||
assert "f" in c0.q()
|
||||
assert "b123" in c1.q()
|
||||
assert c0.ord == 1
|
||||
|
@ -283,30 +310,31 @@ def test_modelChange():
|
|||
# but we have two cards, as a new one was generated
|
||||
assert len(f.cards()) == 2
|
||||
# an unmapped field becomes blank
|
||||
assert f['Front'] == 'b123'
|
||||
assert f['Back'] == 'f'
|
||||
assert f["Front"] == "b123"
|
||||
assert f["Back"] == "f"
|
||||
deck.models.change(basic, [f.id], basic, map, None)
|
||||
f.load()
|
||||
assert f['Front'] == ''
|
||||
assert f['Back'] == 'f'
|
||||
assert f["Front"] == ""
|
||||
assert f["Back"] == "f"
|
||||
# another note to try model conversion
|
||||
f = deck.newNote()
|
||||
f['Front'] = 'f2'
|
||||
f['Back'] = 'b2'
|
||||
f["Front"] = "f2"
|
||||
f["Back"] = "b2"
|
||||
deck.addNote(f)
|
||||
assert deck.models.useCount(basic) == 2
|
||||
assert deck.models.useCount(cloze) == 0
|
||||
map = {0: 0, 1: 1}
|
||||
deck.models.change(basic, [f.id], cloze, map, map)
|
||||
f.load()
|
||||
assert f['Text'] == "f2"
|
||||
assert f["Text"] == "f2"
|
||||
assert len(f.cards()) == 2
|
||||
# back the other way, with deletion of second ord
|
||||
deck.models.remTemplate(basic, basic['tmpls'][1])
|
||||
deck.models.remTemplate(basic, basic["tmpls"][1])
|
||||
assert deck.db.scalar("select count() from cards where nid = ?", f.id) == 2
|
||||
deck.models.change(cloze, [f.id], basic, map, map)
|
||||
assert deck.db.scalar("select count() from cards where nid = ?", f.id) == 1
|
||||
|
||||
|
||||
def test_templates():
|
||||
d = dict(Foo="x", Bar="y")
|
||||
assert anki.template.render("{{Foo}}", d) == "x"
|
||||
|
@ -315,68 +343,72 @@ def test_templates():
|
|||
assert anki.template.render("{{#Bar}}{{#Foo}}{{Foo}}{{/Foo}}{{/Bar}}", d) == "x"
|
||||
assert anki.template.render("{{#Baz}}{{#Foo}}{{Foo}}{{/Foo}}{{/Baz}}", d) == ""
|
||||
|
||||
|
||||
def test_availOrds():
|
||||
d = getEmptyCol()
|
||||
m = d.models.current(); mm = d.models
|
||||
t = m['tmpls'][0]
|
||||
m = d.models.current()
|
||||
mm = d.models
|
||||
t = m["tmpls"][0]
|
||||
f = d.newNote()
|
||||
f['Front'] = "1"
|
||||
f["Front"] = "1"
|
||||
# simple templates
|
||||
assert mm.availOrds(m, joinFields(f.fields)) == [0]
|
||||
t['qfmt'] = "{{Back}}"
|
||||
t["qfmt"] = "{{Back}}"
|
||||
mm.save(m, templates=True)
|
||||
assert not mm.availOrds(m, joinFields(f.fields))
|
||||
# AND
|
||||
t['qfmt'] = "{{#Front}}{{#Back}}{{Front}}{{/Back}}{{/Front}}"
|
||||
t["qfmt"] = "{{#Front}}{{#Back}}{{Front}}{{/Back}}{{/Front}}"
|
||||
mm.save(m, templates=True)
|
||||
assert not mm.availOrds(m, joinFields(f.fields))
|
||||
t['qfmt'] = "{{#Front}}\n{{#Back}}\n{{Front}}\n{{/Back}}\n{{/Front}}"
|
||||
t["qfmt"] = "{{#Front}}\n{{#Back}}\n{{Front}}\n{{/Back}}\n{{/Front}}"
|
||||
mm.save(m, templates=True)
|
||||
assert not mm.availOrds(m, joinFields(f.fields))
|
||||
# OR
|
||||
t['qfmt'] = "{{Front}}\n{{Back}}"
|
||||
t["qfmt"] = "{{Front}}\n{{Back}}"
|
||||
mm.save(m, templates=True)
|
||||
assert mm.availOrds(m, joinFields(f.fields)) == [0]
|
||||
t['Front'] = ""
|
||||
t['Back'] = "1"
|
||||
t["Front"] = ""
|
||||
t["Back"] = "1"
|
||||
assert mm.availOrds(m, joinFields(f.fields)) == [0]
|
||||
|
||||
|
||||
def test_req():
|
||||
def reqSize(model):
|
||||
if model['type'] == MODEL_CLOZE:
|
||||
if model["type"] == MODEL_CLOZE:
|
||||
return
|
||||
assert (len(model['tmpls']) == len(model['req']))
|
||||
assert len(model["tmpls"]) == len(model["req"])
|
||||
|
||||
d = getEmptyCol()
|
||||
mm = d.models
|
||||
basic = mm.byName("Basic")
|
||||
assert 'req' in basic
|
||||
assert "req" in basic
|
||||
reqSize(basic)
|
||||
r = basic['req'][0]
|
||||
r = basic["req"][0]
|
||||
assert r[0] == 0
|
||||
assert r[1] in ("any", "all")
|
||||
assert r[2] == [0]
|
||||
opt = mm.byName("Basic (optional reversed card)")
|
||||
reqSize(opt)
|
||||
r = opt['req'][0]
|
||||
r = opt["req"][0]
|
||||
assert r[1] in ("any", "all")
|
||||
assert r[2] == [0]
|
||||
assert opt['req'][1] == [1, 'all', [1, 2]]
|
||||
#testing any
|
||||
opt['tmpls'][1]['qfmt'] = "{{Back}}{{Add Reverse}}"
|
||||
assert opt["req"][1] == [1, "all", [1, 2]]
|
||||
# testing any
|
||||
opt["tmpls"][1]["qfmt"] = "{{Back}}{{Add Reverse}}"
|
||||
mm.save(opt, templates=True)
|
||||
assert opt['req'][1] == [1, 'any', [1, 2]]
|
||||
#testing None
|
||||
opt['tmpls'][1]['qfmt'] = "{{^Add Reverse}}{{Back}}{{/Add Reverse}}"
|
||||
assert opt["req"][1] == [1, "any", [1, 2]]
|
||||
# testing None
|
||||
opt["tmpls"][1]["qfmt"] = "{{^Add Reverse}}{{Back}}{{/Add Reverse}}"
|
||||
mm.save(opt, templates=True)
|
||||
assert opt['req'][1] == [1, 'none', []]
|
||||
assert opt["req"][1] == [1, "none", []]
|
||||
|
||||
opt = mm.byName("Basic (type in the answer)")
|
||||
reqSize(opt)
|
||||
r = opt['req'][0]
|
||||
r = opt["req"][0]
|
||||
assert r[1] in ("any", "all")
|
||||
assert r[2] == [0]
|
||||
|
||||
|
||||
# def test_updatereqs_performance():
|
||||
# import time
|
||||
# d = getEmptyCol()
|
||||
|
|
|
@ -8,32 +8,38 @@ from tests.shared import getEmptyCol as getEmptyColOrig
|
|||
from anki.utils import intTime
|
||||
from anki.hooks import addHook
|
||||
|
||||
|
||||
def getEmptyCol():
|
||||
col = getEmptyColOrig()
|
||||
col.changeSchedulerVer(1)
|
||||
return col
|
||||
|
||||
|
||||
def test_clock():
|
||||
d = getEmptyCol()
|
||||
if (d.sched.dayCutoff - intTime()) < 10*60:
|
||||
if (d.sched.dayCutoff - intTime()) < 10 * 60:
|
||||
raise Exception("Unit tests will fail around the day rollover.")
|
||||
|
||||
|
||||
def checkRevIvl(d, c, targetIvl):
|
||||
min, max = d.sched._fuzzIvlRange(targetIvl)
|
||||
return min <= c.ivl <= max
|
||||
|
||||
|
||||
def test_basics():
|
||||
d = getEmptyCol()
|
||||
d.reset()
|
||||
assert not d.sched.getCard()
|
||||
|
||||
|
||||
def test_new():
|
||||
d = getEmptyCol()
|
||||
d.reset()
|
||||
assert d.sched.newCount == 0
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert d.sched.newCount == 1
|
||||
|
@ -71,15 +77,16 @@ def test_new():
|
|||
# assert qs[n] in c.q()
|
||||
# d.sched.answerCard(c, 2)
|
||||
|
||||
|
||||
def test_newLimits():
|
||||
d = getEmptyCol()
|
||||
# add some notes
|
||||
g2 = d.decks.id("Default::foo")
|
||||
for i in range(30):
|
||||
f = d.newNote()
|
||||
f['Front'] = str(i)
|
||||
f["Front"] = str(i)
|
||||
if i > 4:
|
||||
f.model()['did'] = g2
|
||||
f.model()["did"] = g2
|
||||
d.addNote(f)
|
||||
# give the child deck a different configuration
|
||||
c2 = d.decks.confId("new conf")
|
||||
|
@ -92,33 +99,36 @@ def test_newLimits():
|
|||
assert c.did == 1
|
||||
# limit the parent to 10 cards, meaning we get 10 in total
|
||||
conf1 = d.decks.confForDid(1)
|
||||
conf1['new']['perDay'] = 10
|
||||
conf1["new"]["perDay"] = 10
|
||||
d.reset()
|
||||
assert d.sched.newCount == 10
|
||||
# if we limit child to 4, we should get 9
|
||||
conf2 = d.decks.confForDid(g2)
|
||||
conf2['new']['perDay'] = 4
|
||||
conf2["new"]["perDay"] = 4
|
||||
d.reset()
|
||||
assert d.sched.newCount == 9
|
||||
|
||||
|
||||
def test_newBoxes():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
c = d.sched.getCard()
|
||||
d.sched._cardConf(c)['new']['delays'] = [1,2,3,4,5]
|
||||
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._cardConf(c)["new"]["delays"] = [1]
|
||||
d.sched.answerCard(c, 2)
|
||||
|
||||
|
||||
def test_learn():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
f = d.addNote(f)
|
||||
# set as a learn card and rebuild queues
|
||||
d.db.execute("update cards set queue=0, type=0")
|
||||
|
@ -126,12 +136,12 @@ def test_learn():
|
|||
# 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]
|
||||
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%1000 == 3
|
||||
assert c.left//1000 == 3
|
||||
assert c.left % 1000 == 3
|
||||
assert c.left // 1000 == 3
|
||||
# it should by due in 30 seconds
|
||||
t = round(c.due - time.time())
|
||||
assert t >= 25 and t <= 40
|
||||
|
@ -139,8 +149,8 @@ def test_learn():
|
|||
d.sched.answerCard(c, 2)
|
||||
# it should by due in 3 minutes
|
||||
assert round(c.due - time.time()) in (179, 180)
|
||||
assert c.left%1000 == 2
|
||||
assert c.left//1000 == 2
|
||||
assert c.left % 1000 == 2
|
||||
assert c.left // 1000 == 2
|
||||
# check log is accurate
|
||||
log = d.db.first("select * from revlog order by id desc")
|
||||
assert log[3] == 2
|
||||
|
@ -150,8 +160,8 @@ def test_learn():
|
|||
d.sched.answerCard(c, 2)
|
||||
# it should by due in 10 minutes
|
||||
assert round(c.due - time.time()) in (599, 600)
|
||||
assert c.left%1000 == 1
|
||||
assert c.left//1000 == 1
|
||||
assert c.left % 1000 == 1
|
||||
assert c.left // 1000 == 1
|
||||
# the next pass should graduate the card
|
||||
assert c.queue == 1
|
||||
assert c.type == 1
|
||||
|
@ -159,7 +169,7 @@ def test_learn():
|
|||
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.due == d.sched.today + 1
|
||||
assert c.ivl == 1
|
||||
# or normal removal
|
||||
c.type = 0
|
||||
|
@ -188,14 +198,15 @@ def test_learn():
|
|||
assert c.queue == 2
|
||||
assert c.due == 321
|
||||
|
||||
|
||||
def test_learn_collapsed():
|
||||
d = getEmptyCol()
|
||||
# add 2 notes
|
||||
f = d.newNote()
|
||||
f['Front'] = "1"
|
||||
f["Front"] = "1"
|
||||
f = d.addNote(f)
|
||||
f = d.newNote()
|
||||
f['Front'] = "2"
|
||||
f["Front"] = "2"
|
||||
f = d.addNote(f)
|
||||
# set as a learn card and rebuild queues
|
||||
d.db.execute("update cards set queue=0, type=0")
|
||||
|
@ -214,27 +225,28 @@ def test_learn_collapsed():
|
|||
c = d.sched.getCard()
|
||||
assert not c.q().endswith("2")
|
||||
|
||||
|
||||
def test_learn_day():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
f = d.addNote(f)
|
||||
d.sched.reset()
|
||||
c = d.sched.getCard()
|
||||
d.sched._cardConf(c)['new']['delays'] = [1, 10, 1440, 2880]
|
||||
d.sched._cardConf(c)["new"]["delays"] = [1, 10, 1440, 2880]
|
||||
# pass it
|
||||
d.sched.answerCard(c, 2)
|
||||
# two reps to graduate, 1 more today
|
||||
assert c.left%1000 == 3
|
||||
assert c.left//1000 == 1
|
||||
assert c.left % 1000 == 3
|
||||
assert c.left // 1000 == 1
|
||||
assert d.sched.counts() == (0, 1, 0)
|
||||
c = d.sched.getCard()
|
||||
ni = d.sched.nextIvl
|
||||
assert ni(c, 2) == 86400
|
||||
# answering it will place it in queue 3
|
||||
d.sched.answerCard(c, 2)
|
||||
assert c.due == d.sched.today+1
|
||||
assert c.due == d.sched.today + 1
|
||||
assert c.queue == 3
|
||||
assert not d.sched.getCard()
|
||||
# for testing, move it back a day
|
||||
|
@ -244,7 +256,7 @@ def test_learn_day():
|
|||
assert d.sched.counts() == (0, 1, 0)
|
||||
c = d.sched.getCard()
|
||||
# nextIvl should work
|
||||
assert ni(c, 2) == 86400*2
|
||||
assert ni(c, 2) == 86400 * 2
|
||||
# if we fail it, it should be back in the correct queue
|
||||
d.sched.answerCard(c, 1)
|
||||
assert c.queue == 1
|
||||
|
@ -266,17 +278,19 @@ def test_learn_day():
|
|||
c.flush()
|
||||
d.reset()
|
||||
assert d.sched.counts() == (0, 0, 1)
|
||||
d.sched._cardConf(c)['lapse']['delays'] = [1440]
|
||||
d.sched._cardConf(c)["lapse"]["delays"] = [1440]
|
||||
c = d.sched.getCard()
|
||||
d.sched.answerCard(c, 1)
|
||||
assert c.queue == 3
|
||||
assert d.sched.counts() == (0, 0, 0)
|
||||
|
||||
|
||||
def test_reviews():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
# set the card up as a review card, due 8 days ago
|
||||
c = f.cards()[0]
|
||||
|
@ -295,7 +309,7 @@ def test_reviews():
|
|||
##################################################
|
||||
# different delay to new
|
||||
d.reset()
|
||||
d.sched._cardConf(c)['lapse']['delays'] = [2, 20]
|
||||
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
|
||||
|
@ -313,7 +327,7 @@ def test_reviews():
|
|||
# check ests.
|
||||
ni = d.sched.nextIvl
|
||||
assert ni(c, 1) == 120
|
||||
assert ni(c, 2) == 20*60
|
||||
assert ni(c, 2) == 20 * 60
|
||||
# try again with an ease of 2 instead
|
||||
##################################################
|
||||
c = copy.copy(cardcopy)
|
||||
|
@ -355,8 +369,10 @@ def test_reviews():
|
|||
c.flush()
|
||||
# steup hook
|
||||
hooked = []
|
||||
|
||||
def onLeech(card):
|
||||
hooked.append(1)
|
||||
|
||||
addHook("leech", onLeech)
|
||||
d.sched.answerCard(c, 1)
|
||||
assert hooked
|
||||
|
@ -364,10 +380,11 @@ def test_reviews():
|
|||
c.load()
|
||||
assert c.queue == -1
|
||||
|
||||
|
||||
def test_button_spacing():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
# 1 day ivl review card due now
|
||||
c = f.cards()[0]
|
||||
|
@ -384,13 +401,14 @@ def test_button_spacing():
|
|||
assert ni(c, 3) == "3 days"
|
||||
assert ni(c, 4) == "4 days"
|
||||
|
||||
|
||||
def test_overdue_lapse():
|
||||
# disabled in commit 3069729776990980f34c25be66410e947e9d51a2
|
||||
return
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
# simulate a review that was lapsed and is now due for its normal review
|
||||
c = f.cards()[0]
|
||||
|
@ -419,13 +437,15 @@ def test_overdue_lapse():
|
|||
d.sched.reset()
|
||||
assert d.sched.counts() == (0, 0, 1)
|
||||
|
||||
|
||||
def test_finished():
|
||||
d = getEmptyCol()
|
||||
# nothing due
|
||||
assert "Congratulations" in d.sched.finishedMsg()
|
||||
assert "limit" not in d.sched.finishedMsg()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
# have a new card
|
||||
assert "new cards available" in d.sched.finishedMsg()
|
||||
|
@ -438,44 +458,46 @@ def test_finished():
|
|||
assert "Congratulations" in d.sched.finishedMsg()
|
||||
assert "limit" not in d.sched.finishedMsg()
|
||||
|
||||
|
||||
def test_nextIvl():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
conf = d.decks.confForDid(1)
|
||||
conf['new']['delays'] = [0.5, 3, 10]
|
||||
conf['lapse']['delays'] = [1, 5, 9]
|
||||
conf["new"]["delays"] = [0.5, 3, 10]
|
||||
conf["lapse"]["delays"] = [1, 5, 9]
|
||||
c = d.sched.getCard()
|
||||
# new cards
|
||||
##################################################
|
||||
ni = d.sched.nextIvl
|
||||
assert ni(c, 1) == 30
|
||||
assert ni(c, 2) == 180
|
||||
assert ni(c, 3) == 4*86400
|
||||
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
|
||||
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
|
||||
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
|
||||
assert ni(c, 2) == 1 * 86400
|
||||
assert ni(c, 3) == 4 * 86400
|
||||
# lapsed cards
|
||||
##################################################
|
||||
c.type = 2
|
||||
c.ivl = 100
|
||||
c.factor = STARTING_FACTOR
|
||||
assert ni(c, 1) == 60
|
||||
assert ni(c, 2) == 100*86400
|
||||
assert ni(c, 3) == 100*86400
|
||||
assert ni(c, 2) == 100 * 86400
|
||||
assert ni(c, 3) == 100 * 86400
|
||||
# review cards
|
||||
##################################################
|
||||
c.queue = 2
|
||||
|
@ -484,8 +506,8 @@ def test_nextIvl():
|
|||
# failing it should put it at 60s
|
||||
assert ni(c, 1) == 60
|
||||
# or 1 day if relearn is false
|
||||
d.sched._cardConf(c)['lapse']['delays']=[]
|
||||
assert ni(c, 1) == 1*86400
|
||||
d.sched._cardConf(c)["lapse"]["delays"] = []
|
||||
assert ni(c, 1) == 1 * 86400
|
||||
# (* 100 1.2 86400)10368000.0
|
||||
assert ni(c, 2) == 10368000
|
||||
# (* 100 2.5 86400)21600000.0
|
||||
|
@ -494,10 +516,11 @@ def test_nextIvl():
|
|||
assert ni(c, 4) == 28080000
|
||||
assert d.sched.nextIvlStr(c, 4) == "10.8 months"
|
||||
|
||||
|
||||
def test_misc():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
# burying
|
||||
|
@ -508,10 +531,11 @@ def test_misc():
|
|||
d.reset()
|
||||
assert d.sched.getCard()
|
||||
|
||||
|
||||
def test_suspend():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
# suspending
|
||||
|
@ -525,7 +549,11 @@ def test_suspend():
|
|||
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()
|
||||
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)
|
||||
|
@ -551,10 +579,11 @@ def test_suspend():
|
|||
assert c.due == 1
|
||||
assert c.did == 1
|
||||
|
||||
|
||||
def test_cram():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.ivl = 100
|
||||
|
@ -566,7 +595,7 @@ def test_cram():
|
|||
c.startTimer()
|
||||
c.flush()
|
||||
d.reset()
|
||||
assert d.sched.counts() == (0,0,0)
|
||||
assert d.sched.counts() == (0, 0, 0)
|
||||
cardcopy = copy.copy(c)
|
||||
# create a dynamic deck and refresh it
|
||||
did = d.decks.newDyn("Cram")
|
||||
|
@ -575,18 +604,18 @@ def test_cram():
|
|||
# should appear as new in the deck list
|
||||
assert sorted(d.sched.deckDueList())[0][4] == 1
|
||||
# and should appear in the counts
|
||||
assert d.sched.counts() == (1,0,0)
|
||||
assert d.sched.counts() == (1, 0, 0)
|
||||
# grab it and check estimates
|
||||
c = d.sched.getCard()
|
||||
assert d.sched.answerButtons(c) == 2
|
||||
assert d.sched.nextIvl(c, 1) == 600
|
||||
assert d.sched.nextIvl(c, 2) == 138*60*60*24
|
||||
assert d.sched.nextIvl(c, 2) == 138 * 60 * 60 * 24
|
||||
cram = d.decks.get(did)
|
||||
cram['delays'] = [1, 10]
|
||||
cram["delays"] = [1, 10]
|
||||
assert d.sched.answerButtons(c) == 3
|
||||
assert d.sched.nextIvl(c, 1) == 60
|
||||
assert d.sched.nextIvl(c, 2) == 600
|
||||
assert d.sched.nextIvl(c, 3) == 138*60*60*24
|
||||
assert d.sched.nextIvl(c, 3) == 138 * 60 * 60 * 24
|
||||
d.sched.answerCard(c, 2)
|
||||
# elapsed time was 75 days
|
||||
# factor = 2.5+1.2/2 = 1.85
|
||||
|
@ -595,12 +624,11 @@ def test_cram():
|
|||
assert c.odue == 138
|
||||
assert c.queue == 1
|
||||
# should be logged as a cram rep
|
||||
assert d.db.scalar(
|
||||
"select type from revlog order by id desc limit 1") == 3
|
||||
assert d.db.scalar("select type from revlog order by id desc limit 1") == 3
|
||||
# check ivls again
|
||||
assert d.sched.nextIvl(c, 1) == 60
|
||||
assert d.sched.nextIvl(c, 2) == 138*60*60*24
|
||||
assert d.sched.nextIvl(c, 3) == 138*60*60*24
|
||||
assert d.sched.nextIvl(c, 2) == 138 * 60 * 60 * 24
|
||||
assert d.sched.nextIvl(c, 3) == 138 * 60 * 60 * 24
|
||||
# when it graduates, due is updated
|
||||
c = d.sched.getCard()
|
||||
d.sched.answerCard(c, 2)
|
||||
|
@ -616,7 +644,7 @@ def test_cram():
|
|||
# check ivls again - passing should be idempotent
|
||||
assert d.sched.nextIvl(c, 1) == 60
|
||||
assert d.sched.nextIvl(c, 2) == 600
|
||||
assert d.sched.nextIvl(c, 3) == 138*60*60*24
|
||||
assert d.sched.nextIvl(c, 3) == 138 * 60 * 60 * 24
|
||||
d.sched.answerCard(c, 2)
|
||||
assert c.ivl == 138
|
||||
assert c.odue == 138
|
||||
|
@ -630,20 +658,20 @@ def test_cram():
|
|||
assert len(d.sched.deckDueList()) == 1
|
||||
c.load()
|
||||
assert c.ivl == 1
|
||||
assert c.due == d.sched.today+1
|
||||
assert c.due == d.sched.today + 1
|
||||
# make it due
|
||||
d.reset()
|
||||
assert d.sched.counts() == (0,0,0)
|
||||
assert d.sched.counts() == (0, 0, 0)
|
||||
c.due = -5
|
||||
c.ivl = 100
|
||||
c.flush()
|
||||
d.reset()
|
||||
assert d.sched.counts() == (0,0,1)
|
||||
assert d.sched.counts() == (0, 0, 1)
|
||||
# cram again
|
||||
did = d.decks.newDyn("Cram")
|
||||
d.sched.rebuildDyn(did)
|
||||
d.reset()
|
||||
assert d.sched.counts() == (0,0,1)
|
||||
assert d.sched.counts() == (0, 0, 1)
|
||||
c.load()
|
||||
assert d.sched.answerButtons(c) == 4
|
||||
# add a sibling so we can test minSpace, etc
|
||||
|
@ -661,10 +689,11 @@ def test_cram():
|
|||
# it should have been moved back to the original deck
|
||||
assert c.did == 1
|
||||
|
||||
|
||||
def test_cram_rem():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
oldDue = f.cards()[0].due
|
||||
did = d.decks.newDyn("Cram")
|
||||
|
@ -681,16 +710,17 @@ def test_cram_rem():
|
|||
assert c.type == c.queue == 0
|
||||
assert c.due == oldDue
|
||||
|
||||
|
||||
def test_cram_resched():
|
||||
# add card
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
# cram deck
|
||||
did = d.decks.newDyn("Cram")
|
||||
cram = d.decks.get(did)
|
||||
cram['resched'] = False
|
||||
cram["resched"] = False
|
||||
d.sched.rebuildDyn(did)
|
||||
d.reset()
|
||||
# graduate should return it to new
|
||||
|
@ -786,22 +816,25 @@ def test_cram_resched():
|
|||
# d.sched.answerCard(c, 2)
|
||||
# print c.__dict__
|
||||
|
||||
|
||||
def test_ordcycle():
|
||||
d = getEmptyCol()
|
||||
# add two more templates and set second active
|
||||
m = d.models.current(); mm = d.models
|
||||
m = d.models.current()
|
||||
mm = d.models
|
||||
t = mm.newTemplate("Reverse")
|
||||
t['qfmt'] = "{{Back}}"
|
||||
t['afmt'] = "{{Front}}"
|
||||
t["qfmt"] = "{{Back}}"
|
||||
t["afmt"] = "{{Front}}"
|
||||
mm.addTemplate(m, t)
|
||||
t = mm.newTemplate("f2")
|
||||
t['qfmt'] = "{{Front}}"
|
||||
t['afmt'] = "{{Back}}"
|
||||
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"
|
||||
f["Front"] = "1"
|
||||
f["Back"] = "1"
|
||||
d.addNote(f)
|
||||
assert d.cardCount() == 3
|
||||
d.reset()
|
||||
|
@ -810,10 +843,12 @@ def test_ordcycle():
|
|||
assert d.sched.getCard().ord == 1
|
||||
assert d.sched.getCard().ord == 2
|
||||
|
||||
|
||||
def test_counts_idx():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert d.sched.counts() == (1, 0, 0)
|
||||
|
@ -832,10 +867,11 @@ def test_counts_idx():
|
|||
d.sched.answerCard(c, 1)
|
||||
assert d.sched.counts() == (0, 2, 0)
|
||||
|
||||
|
||||
def test_repCounts():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
# lrnReps should be accurate on pass/fail
|
||||
|
@ -853,7 +889,7 @@ def test_repCounts():
|
|||
d.sched.answerCard(d.sched.getCard(), 2)
|
||||
assert d.sched.counts() == (0, 0, 0)
|
||||
f = d.newNote()
|
||||
f['Front'] = "two"
|
||||
f["Front"] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
# initial pass should be correct too
|
||||
|
@ -865,14 +901,14 @@ def test_repCounts():
|
|||
assert d.sched.counts() == (0, 0, 0)
|
||||
# immediate graduate should work
|
||||
f = d.newNote()
|
||||
f['Front'] = "three"
|
||||
f["Front"] = "three"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
d.sched.answerCard(d.sched.getCard(), 3)
|
||||
assert d.sched.counts() == (0, 0, 0)
|
||||
# and failing a review should too
|
||||
f = d.newNote()
|
||||
f['Front'] = "three"
|
||||
f["Front"] = "three"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.type = 2
|
||||
|
@ -884,12 +920,13 @@ def test_repCounts():
|
|||
d.sched.answerCard(d.sched.getCard(), 1)
|
||||
assert d.sched.counts() == (0, 1, 0)
|
||||
|
||||
|
||||
def test_timing():
|
||||
d = getEmptyCol()
|
||||
# add a few review cards, due today
|
||||
for i in range(5):
|
||||
f = d.newNote()
|
||||
f['Front'] = "num"+str(i)
|
||||
f["Front"] = "num" + str(i)
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.type = 2
|
||||
|
@ -900,7 +937,7 @@ def test_timing():
|
|||
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._cardConf(c)["lapse"]["delays"][0] = 1 / 60.0
|
||||
d.sched.answerCard(c, 1)
|
||||
# the next card should be another review
|
||||
c = d.sched.getCard()
|
||||
|
@ -910,11 +947,12 @@ def test_timing():
|
|||
c = d.sched.getCard()
|
||||
assert c.queue == 1
|
||||
|
||||
|
||||
def test_collapse():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
# test collapsing
|
||||
|
@ -924,16 +962,17 @@ def test_collapse():
|
|||
d.sched.answerCard(c, 3)
|
||||
assert not d.sched.getCard()
|
||||
|
||||
|
||||
def test_deckDue():
|
||||
d = getEmptyCol()
|
||||
# add a note with default deck
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
# and one that's a child
|
||||
f = d.newNote()
|
||||
f['Front'] = "two"
|
||||
default1 = f.model()['did'] = d.decks.id("Default::1")
|
||||
f["Front"] = "two"
|
||||
default1 = f.model()["did"] = d.decks.id("Default::1")
|
||||
d.addNote(f)
|
||||
# make it a review card
|
||||
c = f.cards()[0]
|
||||
|
@ -942,13 +981,13 @@ def test_deckDue():
|
|||
c.flush()
|
||||
# add one more with a new deck
|
||||
f = d.newNote()
|
||||
f['Front'] = "two"
|
||||
foobar = f.model()['did'] = d.decks.id("foo::bar")
|
||||
f["Front"] = "two"
|
||||
foobar = f.model()["did"] = d.decks.id("foo::bar")
|
||||
d.addNote(f)
|
||||
# and one that's a sibling
|
||||
f = d.newNote()
|
||||
f['Front'] = "three"
|
||||
foobaz = f.model()['did'] = d.decks.id("foo::baz")
|
||||
f["Front"] = "three"
|
||||
foobaz = f.model()["did"] = d.decks.id("foo::baz")
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert len(d.decks.decks) == 5
|
||||
|
@ -970,10 +1009,12 @@ def test_deckDue():
|
|||
assert tree[0][5][0][2] == 1
|
||||
assert tree[0][5][0][4] == 0
|
||||
# code should not fail if a card has an invalid deck
|
||||
c.did = 12345; c.flush()
|
||||
c.did = 12345
|
||||
c.flush()
|
||||
d.sched.deckDueList()
|
||||
d.sched.deckDueTree()
|
||||
|
||||
|
||||
def test_deckTree():
|
||||
d = getEmptyCol()
|
||||
d.decks.id("new::b::c")
|
||||
|
@ -983,75 +1024,80 @@ def test_deckTree():
|
|||
names.remove("new")
|
||||
assert "new" not in names
|
||||
|
||||
|
||||
def test_deckFlow():
|
||||
d = getEmptyCol()
|
||||
# add a note with default deck
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
# and one that's a child
|
||||
f = d.newNote()
|
||||
f['Front'] = "two"
|
||||
default1 = f.model()['did'] = d.decks.id("Default::2")
|
||||
f["Front"] = "two"
|
||||
default1 = f.model()["did"] = d.decks.id("Default::2")
|
||||
d.addNote(f)
|
||||
# and another that's higher up
|
||||
f = d.newNote()
|
||||
f['Front'] = "three"
|
||||
default1 = f.model()['did'] = d.decks.id("Default::1")
|
||||
f["Front"] = "three"
|
||||
default1 = f.model()["did"] = d.decks.id("Default::1")
|
||||
d.addNote(f)
|
||||
# should get top level one first, then ::1, then ::2
|
||||
d.reset()
|
||||
assert d.sched.counts() == (3,0,0)
|
||||
assert d.sched.counts() == (3, 0, 0)
|
||||
for i in "one", "three", "two":
|
||||
c = d.sched.getCard()
|
||||
assert c.note()['Front'] == i
|
||||
assert c.note()["Front"] == i
|
||||
d.sched.answerCard(c, 2)
|
||||
|
||||
|
||||
def test_reorder():
|
||||
d = getEmptyCol()
|
||||
# add a note with default deck
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
f2 = d.newNote()
|
||||
f2['Front'] = "two"
|
||||
f2["Front"] = "two"
|
||||
d.addNote(f2)
|
||||
assert f2.cards()[0].due == 2
|
||||
found=False
|
||||
found = False
|
||||
# 50/50 chance of being reordered
|
||||
for i in range(20):
|
||||
d.sched.randomizeCards(1)
|
||||
if f.cards()[0].due != f.id:
|
||||
found=True
|
||||
found = True
|
||||
break
|
||||
assert found
|
||||
d.sched.orderCards(1)
|
||||
assert f.cards()[0].due == 1
|
||||
# shifting
|
||||
f3 = d.newNote()
|
||||
f3['Front'] = "three"
|
||||
f3["Front"] = "three"
|
||||
d.addNote(f3)
|
||||
f4 = d.newNote()
|
||||
f4['Front'] = "four"
|
||||
f4["Front"] = "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)
|
||||
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 = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.queue = 2; c.type = 2; c.ivl = 100; c.due = 0
|
||||
c.queue = 2
|
||||
c.type = 2
|
||||
c.ivl = 100
|
||||
c.due = 0
|
||||
c.flush()
|
||||
d.reset()
|
||||
assert d.sched.counts() == (0, 0, 1)
|
||||
|
@ -1059,10 +1105,11 @@ def test_forget():
|
|||
d.reset()
|
||||
assert d.sched.counts() == (1, 0, 0)
|
||||
|
||||
|
||||
def test_resched():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
d.sched.reschedCards([c.id], 0, 0)
|
||||
|
@ -1072,14 +1119,15 @@ def test_resched():
|
|||
assert c.queue == c.type == 2
|
||||
d.sched.reschedCards([c.id], 1, 1)
|
||||
c.load()
|
||||
assert c.due == d.sched.today+1
|
||||
assert c.due == d.sched.today + 1
|
||||
assert c.ivl == +1
|
||||
|
||||
|
||||
def test_norelearn():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.type = 2
|
||||
|
@ -1093,13 +1141,15 @@ def test_norelearn():
|
|||
c.flush()
|
||||
d.reset()
|
||||
d.sched.answerCard(c, 1)
|
||||
d.sched._cardConf(c)['lapse']['delays'] = []
|
||||
d.sched._cardConf(c)["lapse"]["delays"] = []
|
||||
d.sched.answerCard(c, 1)
|
||||
|
||||
|
||||
def test_failmult():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.type = 2
|
||||
|
@ -1111,7 +1161,7 @@ def test_failmult():
|
|||
c.lapses = 1
|
||||
c.startTimer()
|
||||
c.flush()
|
||||
d.sched._cardConf(c)['lapse']['mult'] = 0.5
|
||||
d.sched._cardConf(c)["lapse"]["mult"] = 0.5
|
||||
c = d.sched.getCard()
|
||||
d.sched.answerCard(c, 1)
|
||||
assert c.ivl == 50
|
||||
|
|
|
@ -12,31 +12,38 @@ from anki.hooks import addHook
|
|||
lt = time.localtime()
|
||||
if lt.tm_hour > 2 and lt.tm_hour < 4:
|
||||
orig_time = time.time
|
||||
|
||||
def adjusted_time():
|
||||
return orig_time() - 60*60*2
|
||||
return orig_time() - 60 * 60 * 2
|
||||
|
||||
time.time = adjusted_time
|
||||
|
||||
|
||||
def test_clock():
|
||||
d = getEmptyCol()
|
||||
if (d.sched.dayCutoff - intTime()) < 10*60:
|
||||
if (d.sched.dayCutoff - intTime()) < 10 * 60:
|
||||
raise Exception("Unit tests will fail around the day rollover.")
|
||||
|
||||
|
||||
def checkRevIvl(d, c, targetIvl):
|
||||
min, max = d.sched._fuzzIvlRange(targetIvl)
|
||||
return min <= c.ivl <= max
|
||||
|
||||
|
||||
def test_basics():
|
||||
d = getEmptyCol()
|
||||
d.reset()
|
||||
assert not d.sched.getCard()
|
||||
|
||||
|
||||
def test_new():
|
||||
d = getEmptyCol()
|
||||
d.reset()
|
||||
assert d.sched.newCount == 0
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert d.sched.newCount == 1
|
||||
|
@ -74,15 +81,16 @@ def test_new():
|
|||
# assert qs[n] in c.q()
|
||||
# d.sched.answerCard(c, 2)
|
||||
|
||||
|
||||
def test_newLimits():
|
||||
d = getEmptyCol()
|
||||
# add some notes
|
||||
g2 = d.decks.id("Default::foo")
|
||||
for i in range(30):
|
||||
f = d.newNote()
|
||||
f['Front'] = str(i)
|
||||
f["Front"] = str(i)
|
||||
if i > 4:
|
||||
f.model()['did'] = g2
|
||||
f.model()["did"] = g2
|
||||
d.addNote(f)
|
||||
# give the child deck a different configuration
|
||||
c2 = d.decks.confId("new conf")
|
||||
|
@ -95,33 +103,36 @@ def test_newLimits():
|
|||
assert c.did == 1
|
||||
# limit the parent to 10 cards, meaning we get 10 in total
|
||||
conf1 = d.decks.confForDid(1)
|
||||
conf1['new']['perDay'] = 10
|
||||
conf1["new"]["perDay"] = 10
|
||||
d.reset()
|
||||
assert d.sched.newCount == 10
|
||||
# if we limit child to 4, we should get 9
|
||||
conf2 = d.decks.confForDid(g2)
|
||||
conf2['new']['perDay'] = 4
|
||||
conf2["new"]["perDay"] = 4
|
||||
d.reset()
|
||||
assert d.sched.newCount == 9
|
||||
|
||||
|
||||
def test_newBoxes():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
c = d.sched.getCard()
|
||||
d.sched._cardConf(c)['new']['delays'] = [1,2,3,4,5]
|
||||
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._cardConf(c)["new"]["delays"] = [1]
|
||||
d.sched.answerCard(c, 2)
|
||||
|
||||
|
||||
def test_learn():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
f = d.addNote(f)
|
||||
# set as a learn card and rebuild queues
|
||||
d.db.execute("update cards set queue=0, type=0")
|
||||
|
@ -129,12 +140,12 @@ def test_learn():
|
|||
# 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]
|
||||
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%1000 == 3
|
||||
assert c.left//1000 == 3
|
||||
assert c.left % 1000 == 3
|
||||
assert c.left // 1000 == 3
|
||||
# it should by due in 30 seconds
|
||||
t = round(c.due - time.time())
|
||||
assert t >= 25 and t <= 40
|
||||
|
@ -142,9 +153,9 @@ def test_learn():
|
|||
d.sched.answerCard(c, 3)
|
||||
# it should by due in 3 minutes
|
||||
dueIn = c.due - time.time()
|
||||
assert 179 <= dueIn <= 180*1.25
|
||||
assert c.left%1000 == 2
|
||||
assert c.left//1000 == 2
|
||||
assert 179 <= dueIn <= 180 * 1.25
|
||||
assert c.left % 1000 == 2
|
||||
assert c.left // 1000 == 2
|
||||
# check log is accurate
|
||||
log = d.db.first("select * from revlog order by id desc")
|
||||
assert log[3] == 3
|
||||
|
@ -154,9 +165,9 @@ def test_learn():
|
|||
d.sched.answerCard(c, 3)
|
||||
# it should by due in 10 minutes
|
||||
dueIn = c.due - time.time()
|
||||
assert 599 <= dueIn <= 600*1.25
|
||||
assert c.left%1000 == 1
|
||||
assert c.left//1000 == 1
|
||||
assert 599 <= dueIn <= 600 * 1.25
|
||||
assert c.left % 1000 == 1
|
||||
assert c.left // 1000 == 1
|
||||
# the next pass should graduate the card
|
||||
assert c.queue == 1
|
||||
assert c.type == 1
|
||||
|
@ -164,7 +175,7 @@ def test_learn():
|
|||
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.due == d.sched.today + 1
|
||||
assert c.ivl == 1
|
||||
# or normal removal
|
||||
c.type = 0
|
||||
|
@ -176,10 +187,11 @@ def test_learn():
|
|||
# revlog should have been updated each time
|
||||
assert d.db.scalar("select count() from revlog where type = 0") == 5
|
||||
|
||||
|
||||
def test_relearn():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.ivl = 100
|
||||
|
@ -201,10 +213,11 @@ def test_relearn():
|
|||
assert c.ivl == 2
|
||||
assert c.due == d.sched.today + c.ivl
|
||||
|
||||
|
||||
def test_relearn_no_steps():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.ivl = 100
|
||||
|
@ -213,7 +226,7 @@ def test_relearn_no_steps():
|
|||
c.flush()
|
||||
|
||||
conf = d.decks.confForDid(1)
|
||||
conf['lapse']['delays'] = []
|
||||
conf["lapse"]["delays"] = []
|
||||
d.decks.save(conf)
|
||||
|
||||
# fail the card
|
||||
|
@ -222,14 +235,15 @@ def test_relearn_no_steps():
|
|||
d.sched.answerCard(c, 1)
|
||||
assert c.type == c.queue == 2
|
||||
|
||||
|
||||
def test_learn_collapsed():
|
||||
d = getEmptyCol()
|
||||
# add 2 notes
|
||||
f = d.newNote()
|
||||
f['Front'] = "1"
|
||||
f["Front"] = "1"
|
||||
f = d.addNote(f)
|
||||
f = d.newNote()
|
||||
f['Front'] = "2"
|
||||
f["Front"] = "2"
|
||||
f = d.addNote(f)
|
||||
# set as a learn card and rebuild queues
|
||||
d.db.execute("update cards set queue=0, type=0")
|
||||
|
@ -248,27 +262,28 @@ def test_learn_collapsed():
|
|||
c = d.sched.getCard()
|
||||
assert not c.q().endswith("2")
|
||||
|
||||
|
||||
def test_learn_day():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
f = d.addNote(f)
|
||||
d.sched.reset()
|
||||
c = d.sched.getCard()
|
||||
d.sched._cardConf(c)['new']['delays'] = [1, 10, 1440, 2880]
|
||||
d.sched._cardConf(c)["new"]["delays"] = [1, 10, 1440, 2880]
|
||||
# pass it
|
||||
d.sched.answerCard(c, 3)
|
||||
# two reps to graduate, 1 more today
|
||||
assert c.left%1000 == 3
|
||||
assert c.left//1000 == 1
|
||||
assert c.left % 1000 == 3
|
||||
assert c.left // 1000 == 1
|
||||
assert d.sched.counts() == (0, 1, 0)
|
||||
c = d.sched.getCard()
|
||||
ni = d.sched.nextIvl
|
||||
assert ni(c, 3) == 86400
|
||||
# answering it will place it in queue 3
|
||||
d.sched.answerCard(c, 3)
|
||||
assert c.due == d.sched.today+1
|
||||
assert c.due == d.sched.today + 1
|
||||
assert c.queue == 3
|
||||
assert not d.sched.getCard()
|
||||
# for testing, move it back a day
|
||||
|
@ -278,7 +293,7 @@ def test_learn_day():
|
|||
assert d.sched.counts() == (0, 1, 0)
|
||||
c = d.sched.getCard()
|
||||
# nextIvl should work
|
||||
assert ni(c, 3) == 86400*2
|
||||
assert ni(c, 3) == 86400 * 2
|
||||
# if we fail it, it should be back in the correct queue
|
||||
d.sched.answerCard(c, 1)
|
||||
assert c.queue == 1
|
||||
|
@ -300,17 +315,19 @@ def test_learn_day():
|
|||
c.flush()
|
||||
d.reset()
|
||||
assert d.sched.counts() == (0, 0, 1)
|
||||
d.sched._cardConf(c)['lapse']['delays'] = [1440]
|
||||
d.sched._cardConf(c)["lapse"]["delays"] = [1440]
|
||||
c = d.sched.getCard()
|
||||
d.sched.answerCard(c, 1)
|
||||
assert c.queue == 3
|
||||
assert d.sched.counts() == (0, 0, 0)
|
||||
|
||||
|
||||
def test_reviews():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
# set the card up as a review card, due 8 days ago
|
||||
c = f.cards()[0]
|
||||
|
@ -367,8 +384,10 @@ def test_reviews():
|
|||
c.flush()
|
||||
# steup hook
|
||||
hooked = []
|
||||
|
||||
def onLeech(card):
|
||||
hooked.append(1)
|
||||
|
||||
addHook("leech", onLeech)
|
||||
d.sched.answerCard(c, 1)
|
||||
assert hooked
|
||||
|
@ -376,6 +395,7 @@ def test_reviews():
|
|||
c.load()
|
||||
assert c.queue == -1
|
||||
|
||||
|
||||
def test_review_limits():
|
||||
d = getEmptyCol()
|
||||
|
||||
|
@ -385,21 +405,22 @@ def test_review_limits():
|
|||
pconf = d.decks.getConf(d.decks.confId("parentConf"))
|
||||
cconf = d.decks.getConf(d.decks.confId("childConf"))
|
||||
|
||||
pconf['rev']['perDay'] = 5
|
||||
pconf["rev"]["perDay"] = 5
|
||||
d.decks.updateConf(pconf)
|
||||
d.decks.setConf(parent, pconf['id'])
|
||||
cconf['rev']['perDay'] = 10
|
||||
d.decks.setConf(parent, pconf["id"])
|
||||
cconf["rev"]["perDay"] = 10
|
||||
d.decks.updateConf(cconf)
|
||||
d.decks.setConf(child, cconf['id'])
|
||||
d.decks.setConf(child, cconf["id"])
|
||||
|
||||
m = d.models.current()
|
||||
m['did'] = child['id']
|
||||
m["did"] = child["id"]
|
||||
d.models.save(m, updateReqs=False)
|
||||
|
||||
# add some cards
|
||||
for i in range(20):
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
|
||||
# make them reviews
|
||||
|
@ -414,7 +435,7 @@ def test_review_limits():
|
|||
assert tree[1][5][0][2] == 5 # child
|
||||
|
||||
# .counts() should match
|
||||
d.decks.select(child['id'])
|
||||
d.decks.select(child["id"])
|
||||
d.sched.reset()
|
||||
assert d.sched.counts() == (0, 0, 5)
|
||||
|
||||
|
@ -428,9 +449,9 @@ def test_review_limits():
|
|||
assert tree[1][5][0][2] == 4 # child
|
||||
|
||||
# switch limits
|
||||
d.decks.setConf(parent, cconf['id'])
|
||||
d.decks.setConf(child, pconf['id'])
|
||||
d.decks.select(parent['id'])
|
||||
d.decks.setConf(parent, cconf["id"])
|
||||
d.decks.setConf(child, pconf["id"])
|
||||
d.decks.select(parent["id"])
|
||||
d.sched.reset()
|
||||
|
||||
# child limits do not affect the parent
|
||||
|
@ -438,10 +459,11 @@ def test_review_limits():
|
|||
assert tree[1][2] == 9 # parent
|
||||
assert tree[1][5][0][2] == 4 # child
|
||||
|
||||
|
||||
def test_button_spacing():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
# 1 day ivl review card due now
|
||||
c = f.cards()[0]
|
||||
|
@ -460,16 +482,17 @@ def test_button_spacing():
|
|||
|
||||
# if hard factor is <= 1, then hard may not increase
|
||||
conf = d.decks.confForDid(1)
|
||||
conf['rev']['hardFactor'] = 1
|
||||
conf["rev"]["hardFactor"] = 1
|
||||
assert ni(c, 2) == "1 day"
|
||||
|
||||
|
||||
def test_overdue_lapse():
|
||||
# disabled in commit 3069729776990980f34c25be66410e947e9d51a2
|
||||
return
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
# simulate a review that was lapsed and is now due for its normal review
|
||||
c = f.cards()[0]
|
||||
|
@ -498,13 +521,15 @@ def test_overdue_lapse():
|
|||
d.sched.reset()
|
||||
assert d.sched.counts() == (0, 0, 1)
|
||||
|
||||
|
||||
def test_finished():
|
||||
d = getEmptyCol()
|
||||
# nothing due
|
||||
assert "Congratulations" in d.sched.finishedMsg()
|
||||
assert "limit" not in d.sched.finishedMsg()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
# have a new card
|
||||
assert "new cards available" in d.sched.finishedMsg()
|
||||
|
@ -517,47 +542,49 @@ def test_finished():
|
|||
assert "Congratulations" in d.sched.finishedMsg()
|
||||
assert "limit" not in d.sched.finishedMsg()
|
||||
|
||||
|
||||
def test_nextIvl():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
conf = d.decks.confForDid(1)
|
||||
conf['new']['delays'] = [0.5, 3, 10]
|
||||
conf['lapse']['delays'] = [1, 5, 9]
|
||||
conf["new"]["delays"] = [0.5, 3, 10]
|
||||
conf["lapse"]["delays"] = [1, 5, 9]
|
||||
c = d.sched.getCard()
|
||||
# new cards
|
||||
##################################################
|
||||
ni = d.sched.nextIvl
|
||||
assert ni(c, 1) == 30
|
||||
assert ni(c, 2) == (30+180)//2
|
||||
assert ni(c, 2) == (30 + 180) // 2
|
||||
assert ni(c, 3) == 180
|
||||
assert ni(c, 4) == 4*86400
|
||||
assert ni(c, 4) == 4 * 86400
|
||||
d.sched.answerCard(c, 1)
|
||||
# cards in learning
|
||||
##################################################
|
||||
assert ni(c, 1) == 30
|
||||
assert ni(c, 2) == (30+180)//2
|
||||
assert ni(c, 2) == (30 + 180) // 2
|
||||
assert ni(c, 3) == 180
|
||||
assert ni(c, 4) == 4*86400
|
||||
assert ni(c, 4) == 4 * 86400
|
||||
d.sched.answerCard(c, 3)
|
||||
assert ni(c, 1) == 30
|
||||
assert ni(c, 2) == (180+600)//2
|
||||
assert ni(c, 2) == (180 + 600) // 2
|
||||
assert ni(c, 3) == 600
|
||||
assert ni(c, 4) == 4*86400
|
||||
assert ni(c, 4) == 4 * 86400
|
||||
d.sched.answerCard(c, 3)
|
||||
# normal graduation is tomorrow
|
||||
assert ni(c, 3) == 1*86400
|
||||
assert ni(c, 4) == 4*86400
|
||||
assert ni(c, 3) == 1 * 86400
|
||||
assert ni(c, 4) == 4 * 86400
|
||||
# lapsed cards
|
||||
##################################################
|
||||
c.type = 2
|
||||
c.ivl = 100
|
||||
c.factor = STARTING_FACTOR
|
||||
assert ni(c, 1) == 60
|
||||
assert ni(c, 3) == 100*86400
|
||||
assert ni(c, 4) == 101*86400
|
||||
assert ni(c, 3) == 100 * 86400
|
||||
assert ni(c, 4) == 101 * 86400
|
||||
# review cards
|
||||
##################################################
|
||||
c.queue = 2
|
||||
|
@ -566,8 +593,8 @@ def test_nextIvl():
|
|||
# failing it should put it at 60s
|
||||
assert ni(c, 1) == 60
|
||||
# or 1 day if relearn is false
|
||||
d.sched._cardConf(c)['lapse']['delays']=[]
|
||||
assert ni(c, 1) == 1*86400
|
||||
d.sched._cardConf(c)["lapse"]["delays"] = []
|
||||
assert ni(c, 1) == 1 * 86400
|
||||
# (* 100 1.2 86400)10368000.0
|
||||
assert ni(c, 2) == 10368000
|
||||
# (* 100 2.5 86400)21600000.0
|
||||
|
@ -576,14 +603,15 @@ def test_nextIvl():
|
|||
assert ni(c, 4) == 28080000
|
||||
assert d.sched.nextIvlStr(c, 4) == "10.8 months"
|
||||
|
||||
|
||||
def test_bury():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
f = d.newNote()
|
||||
f['Front'] = "two"
|
||||
f["Front"] = "two"
|
||||
d.addNote(f)
|
||||
c2 = f.cards()[0]
|
||||
# burying
|
||||
|
@ -598,11 +626,14 @@ def test_bury():
|
|||
assert not d.sched.getCard()
|
||||
|
||||
d.sched.unburyCardsForDeck(type="manual")
|
||||
c.load(); assert c.queue == 0
|
||||
c2.load(); assert c2.queue == -2
|
||||
c.load()
|
||||
assert c.queue == 0
|
||||
c2.load()
|
||||
assert c2.queue == -2
|
||||
|
||||
d.sched.unburyCardsForDeck(type="siblings")
|
||||
c2.load(); assert c2.queue == 0
|
||||
c2.load()
|
||||
assert c2.queue == 0
|
||||
|
||||
d.sched.buryCards([c.id, c2.id])
|
||||
d.sched.unburyCardsForDeck(type="all")
|
||||
|
@ -611,10 +642,11 @@ def test_bury():
|
|||
|
||||
assert d.sched.counts() == (2, 0, 0)
|
||||
|
||||
|
||||
def test_suspend():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
# suspending
|
||||
|
@ -628,7 +660,11 @@ def test_suspend():
|
|||
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()
|
||||
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)
|
||||
|
@ -656,10 +692,11 @@ def test_suspend():
|
|||
assert c.did != 1
|
||||
assert c.odue == 1
|
||||
|
||||
|
||||
def test_filt_reviewing_early_normal():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.ivl = 100
|
||||
|
@ -671,7 +708,7 @@ def test_filt_reviewing_early_normal():
|
|||
c.startTimer()
|
||||
c.flush()
|
||||
d.reset()
|
||||
assert d.sched.counts() == (0,0,0)
|
||||
assert d.sched.counts() == (0, 0, 0)
|
||||
# create a dynamic deck and refresh it
|
||||
did = d.decks.newDyn("Cram")
|
||||
d.sched.rebuildDyn(did)
|
||||
|
@ -679,14 +716,14 @@ def test_filt_reviewing_early_normal():
|
|||
# should appear as normal in the deck list
|
||||
assert sorted(d.sched.deckDueList())[0][2] == 1
|
||||
# and should appear in the counts
|
||||
assert d.sched.counts() == (0,0,1)
|
||||
assert d.sched.counts() == (0, 0, 1)
|
||||
# grab it and check estimates
|
||||
c = d.sched.getCard()
|
||||
assert d.sched.answerButtons(c) == 4
|
||||
assert d.sched.nextIvl(c, 1) == 600
|
||||
assert d.sched.nextIvl(c, 2) == int(75*1.2)*86400
|
||||
assert d.sched.nextIvl(c, 3) == int(75*2.5)*86400
|
||||
assert d.sched.nextIvl(c, 4) == int(75*2.5*1.15)*86400
|
||||
assert d.sched.nextIvl(c, 2) == int(75 * 1.2) * 86400
|
||||
assert d.sched.nextIvl(c, 3) == int(75 * 2.5) * 86400
|
||||
assert d.sched.nextIvl(c, 4) == int(75 * 2.5 * 1.15) * 86400
|
||||
|
||||
# answer 'good'
|
||||
d.sched.answerCard(c, 3)
|
||||
|
@ -696,8 +733,7 @@ def test_filt_reviewing_early_normal():
|
|||
# should not be in learning
|
||||
assert c.queue == 2
|
||||
# should be logged as a cram rep
|
||||
assert d.db.scalar(
|
||||
"select type from revlog order by id desc limit 1") == 3
|
||||
assert d.db.scalar("select type from revlog order by id desc limit 1") == 3
|
||||
|
||||
# due in 75 days, so it's been waiting 25 days
|
||||
c.ivl = 100
|
||||
|
@ -707,20 +743,21 @@ def test_filt_reviewing_early_normal():
|
|||
d.reset()
|
||||
c = d.sched.getCard()
|
||||
|
||||
assert d.sched.nextIvl(c, 2) == 60*86400
|
||||
assert d.sched.nextIvl(c, 3) == 100*86400
|
||||
assert d.sched.nextIvl(c, 4) == 114*86400
|
||||
assert d.sched.nextIvl(c, 2) == 60 * 86400
|
||||
assert d.sched.nextIvl(c, 3) == 100 * 86400
|
||||
assert d.sched.nextIvl(c, 4) == 114 * 86400
|
||||
|
||||
|
||||
def test_filt_keep_lrn_state():
|
||||
d = getEmptyCol()
|
||||
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
|
||||
# fail the card outside filtered deck
|
||||
c = d.sched.getCard()
|
||||
d.sched._cardConf(c)['new']['delays'] = [1, 10, 61]
|
||||
d.sched._cardConf(c)["new"]["delays"] = [1, 10, 61]
|
||||
d.decks.save()
|
||||
|
||||
d.sched.answerCard(c, 1)
|
||||
|
@ -744,30 +781,31 @@ def test_filt_keep_lrn_state():
|
|||
# should be able to advance learning steps
|
||||
d.sched.answerCard(c, 3)
|
||||
# should be due at least an hour in the future
|
||||
assert c.due - intTime() > 60*60
|
||||
assert c.due - intTime() > 60 * 60
|
||||
|
||||
# emptying the deck preserves learning state
|
||||
d.sched.emptyDyn(did)
|
||||
c.load()
|
||||
assert c.type == c.queue == 1
|
||||
assert c.left == 1001
|
||||
assert c.due - intTime() > 60*60
|
||||
assert c.due - intTime() > 60 * 60
|
||||
|
||||
|
||||
def test_preview():
|
||||
# add cards
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
orig = copy.copy(c)
|
||||
f2 = d.newNote()
|
||||
f2['Front'] = "two"
|
||||
f2["Front"] = "two"
|
||||
d.addNote(f2)
|
||||
# cram deck
|
||||
did = d.decks.newDyn("Cram")
|
||||
cram = d.decks.get(did)
|
||||
cram['resched'] = False
|
||||
cram["resched"] = False
|
||||
d.sched.rebuildDyn(did)
|
||||
d.reset()
|
||||
# grab the first card
|
||||
|
@ -801,22 +839,25 @@ def test_preview():
|
|||
assert c.reps == 0
|
||||
assert c.type == 0
|
||||
|
||||
|
||||
def test_ordcycle():
|
||||
d = getEmptyCol()
|
||||
# add two more templates and set second active
|
||||
m = d.models.current(); mm = d.models
|
||||
m = d.models.current()
|
||||
mm = d.models
|
||||
t = mm.newTemplate("Reverse")
|
||||
t['qfmt'] = "{{Back}}"
|
||||
t['afmt'] = "{{Front}}"
|
||||
t["qfmt"] = "{{Back}}"
|
||||
t["afmt"] = "{{Front}}"
|
||||
mm.addTemplate(m, t)
|
||||
t = mm.newTemplate("f2")
|
||||
t['qfmt'] = "{{Front}}"
|
||||
t['afmt'] = "{{Back}}"
|
||||
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"
|
||||
f["Front"] = "1"
|
||||
f["Back"] = "1"
|
||||
d.addNote(f)
|
||||
assert d.cardCount() == 3
|
||||
d.reset()
|
||||
|
@ -825,10 +866,12 @@ def test_ordcycle():
|
|||
assert d.sched.getCard().ord == 1
|
||||
assert d.sched.getCard().ord == 2
|
||||
|
||||
|
||||
def test_counts_idx():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert d.sched.counts() == (1, 0, 0)
|
||||
|
@ -847,10 +890,11 @@ def test_counts_idx():
|
|||
d.sched.answerCard(c, 1)
|
||||
assert d.sched.counts() == (0, 1, 0)
|
||||
|
||||
|
||||
def test_repCounts():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
# lrnReps should be accurate on pass/fail
|
||||
|
@ -868,7 +912,7 @@ def test_repCounts():
|
|||
d.sched.answerCard(d.sched.getCard(), 3)
|
||||
assert d.sched.counts() == (0, 0, 0)
|
||||
f = d.newNote()
|
||||
f['Front'] = "two"
|
||||
f["Front"] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
# initial pass should be correct too
|
||||
|
@ -880,14 +924,14 @@ def test_repCounts():
|
|||
assert d.sched.counts() == (0, 0, 0)
|
||||
# immediate graduate should work
|
||||
f = d.newNote()
|
||||
f['Front'] = "three"
|
||||
f["Front"] = "three"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
d.sched.answerCard(d.sched.getCard(), 4)
|
||||
assert d.sched.counts() == (0, 0, 0)
|
||||
# and failing a review should too
|
||||
f = d.newNote()
|
||||
f['Front'] = "three"
|
||||
f["Front"] = "three"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.type = 2
|
||||
|
@ -899,12 +943,13 @@ def test_repCounts():
|
|||
d.sched.answerCard(d.sched.getCard(), 1)
|
||||
assert d.sched.counts() == (0, 1, 0)
|
||||
|
||||
|
||||
def test_timing():
|
||||
d = getEmptyCol()
|
||||
# add a few review cards, due today
|
||||
for i in range(5):
|
||||
f = d.newNote()
|
||||
f['Front'] = "num"+str(i)
|
||||
f["Front"] = "num" + str(i)
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.type = 2
|
||||
|
@ -925,11 +970,12 @@ def test_timing():
|
|||
c = d.sched.getCard()
|
||||
assert c.queue == 1
|
||||
|
||||
|
||||
def test_collapse():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
# test collapsing
|
||||
|
@ -939,16 +985,17 @@ def test_collapse():
|
|||
d.sched.answerCard(c, 4)
|
||||
assert not d.sched.getCard()
|
||||
|
||||
|
||||
def test_deckDue():
|
||||
d = getEmptyCol()
|
||||
# add a note with default deck
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
# and one that's a child
|
||||
f = d.newNote()
|
||||
f['Front'] = "two"
|
||||
default1 = f.model()['did'] = d.decks.id("Default::1")
|
||||
f["Front"] = "two"
|
||||
default1 = f.model()["did"] = d.decks.id("Default::1")
|
||||
d.addNote(f)
|
||||
# make it a review card
|
||||
c = f.cards()[0]
|
||||
|
@ -957,13 +1004,13 @@ def test_deckDue():
|
|||
c.flush()
|
||||
# add one more with a new deck
|
||||
f = d.newNote()
|
||||
f['Front'] = "two"
|
||||
foobar = f.model()['did'] = d.decks.id("foo::bar")
|
||||
f["Front"] = "two"
|
||||
foobar = f.model()["did"] = d.decks.id("foo::bar")
|
||||
d.addNote(f)
|
||||
# and one that's a sibling
|
||||
f = d.newNote()
|
||||
f['Front'] = "three"
|
||||
foobaz = f.model()['did'] = d.decks.id("foo::baz")
|
||||
f["Front"] = "three"
|
||||
foobaz = f.model()["did"] = d.decks.id("foo::baz")
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert len(d.decks.decks) == 5
|
||||
|
@ -985,10 +1032,12 @@ def test_deckDue():
|
|||
assert tree[0][5][0][2] == 1
|
||||
assert tree[0][5][0][4] == 0
|
||||
# code should not fail if a card has an invalid deck
|
||||
c.did = 12345; c.flush()
|
||||
c.did = 12345
|
||||
c.flush()
|
||||
d.sched.deckDueList()
|
||||
d.sched.deckDueTree()
|
||||
|
||||
|
||||
def test_deckTree():
|
||||
d = getEmptyCol()
|
||||
d.decks.id("new::b::c")
|
||||
|
@ -998,75 +1047,80 @@ def test_deckTree():
|
|||
names.remove("new")
|
||||
assert "new" not in names
|
||||
|
||||
|
||||
def test_deckFlow():
|
||||
d = getEmptyCol()
|
||||
# add a note with default deck
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
# and one that's a child
|
||||
f = d.newNote()
|
||||
f['Front'] = "two"
|
||||
default1 = f.model()['did'] = d.decks.id("Default::2")
|
||||
f["Front"] = "two"
|
||||
default1 = f.model()["did"] = d.decks.id("Default::2")
|
||||
d.addNote(f)
|
||||
# and another that's higher up
|
||||
f = d.newNote()
|
||||
f['Front'] = "three"
|
||||
default1 = f.model()['did'] = d.decks.id("Default::1")
|
||||
f["Front"] = "three"
|
||||
default1 = f.model()["did"] = d.decks.id("Default::1")
|
||||
d.addNote(f)
|
||||
# should get top level one first, then ::1, then ::2
|
||||
d.reset()
|
||||
assert d.sched.counts() == (3,0,0)
|
||||
assert d.sched.counts() == (3, 0, 0)
|
||||
for i in "one", "three", "two":
|
||||
c = d.sched.getCard()
|
||||
assert c.note()['Front'] == i
|
||||
assert c.note()["Front"] == i
|
||||
d.sched.answerCard(c, 3)
|
||||
|
||||
|
||||
def test_reorder():
|
||||
d = getEmptyCol()
|
||||
# add a note with default deck
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
f2 = d.newNote()
|
||||
f2['Front'] = "two"
|
||||
f2["Front"] = "two"
|
||||
d.addNote(f2)
|
||||
assert f2.cards()[0].due == 2
|
||||
found=False
|
||||
found = False
|
||||
# 50/50 chance of being reordered
|
||||
for i in range(20):
|
||||
d.sched.randomizeCards(1)
|
||||
if f.cards()[0].due != f.id:
|
||||
found=True
|
||||
found = True
|
||||
break
|
||||
assert found
|
||||
d.sched.orderCards(1)
|
||||
assert f.cards()[0].due == 1
|
||||
# shifting
|
||||
f3 = d.newNote()
|
||||
f3['Front'] = "three"
|
||||
f3["Front"] = "three"
|
||||
d.addNote(f3)
|
||||
f4 = d.newNote()
|
||||
f4['Front'] = "four"
|
||||
f4["Front"] = "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)
|
||||
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 = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.queue = 2; c.type = 2; c.ivl = 100; c.due = 0
|
||||
c.queue = 2
|
||||
c.type = 2
|
||||
c.ivl = 100
|
||||
c.due = 0
|
||||
c.flush()
|
||||
d.reset()
|
||||
assert d.sched.counts() == (0, 0, 1)
|
||||
|
@ -1074,10 +1128,11 @@ def test_forget():
|
|||
d.reset()
|
||||
assert d.sched.counts() == (1, 0, 0)
|
||||
|
||||
|
||||
def test_resched():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
d.sched.reschedCards([c.id], 0, 0)
|
||||
|
@ -1087,14 +1142,15 @@ def test_resched():
|
|||
assert c.queue == c.type == 2
|
||||
d.sched.reschedCards([c.id], 1, 1)
|
||||
c.load()
|
||||
assert c.due == d.sched.today+1
|
||||
assert c.due == d.sched.today + 1
|
||||
assert c.ivl == +1
|
||||
|
||||
|
||||
def test_norelearn():
|
||||
d = getEmptyCol()
|
||||
# add a note
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.type = 2
|
||||
|
@ -1108,13 +1164,15 @@ def test_norelearn():
|
|||
c.flush()
|
||||
d.reset()
|
||||
d.sched.answerCard(c, 1)
|
||||
d.sched._cardConf(c)['lapse']['delays'] = []
|
||||
d.sched._cardConf(c)["lapse"]["delays"] = []
|
||||
d.sched.answerCard(c, 1)
|
||||
|
||||
|
||||
def test_failmult():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.type = 2
|
||||
|
@ -1126,19 +1184,20 @@ def test_failmult():
|
|||
c.lapses = 1
|
||||
c.startTimer()
|
||||
c.flush()
|
||||
d.sched._cardConf(c)['lapse']['mult'] = 0.5
|
||||
d.sched._cardConf(c)["lapse"]["mult"] = 0.5
|
||||
c = d.sched.getCard()
|
||||
d.sched.answerCard(c, 1)
|
||||
assert c.ivl == 50
|
||||
d.sched.answerCard(c, 1)
|
||||
assert c.ivl == 25
|
||||
|
||||
|
||||
def test_moveVersions():
|
||||
col = getEmptyCol()
|
||||
col.changeSchedulerVer(1)
|
||||
|
||||
n = col.newNote()
|
||||
n['Front'] = "one"
|
||||
n["Front"] = "one"
|
||||
col.addNote(n)
|
||||
|
||||
# make it a learning card
|
||||
|
@ -1176,8 +1235,10 @@ def test_moveVersions():
|
|||
col.changeSchedulerVer(2)
|
||||
# card with 100 day interval, answering again
|
||||
col.sched.reschedCards([c.id], 100, 100)
|
||||
c.load(); c.due = 0; c.flush()
|
||||
col.sched._cardConf(c)['lapse']['mult'] = 0.5
|
||||
c.load()
|
||||
c.due = 0
|
||||
c.flush()
|
||||
col.sched._cardConf(c)["lapse"]["mult"] = 0.5
|
||||
col.sched.reset()
|
||||
c = col.sched.getCard()
|
||||
col.sched.answerCard(c, 1)
|
||||
|
@ -1186,6 +1247,7 @@ def test_moveVersions():
|
|||
c.load()
|
||||
assert c.due == 50
|
||||
|
||||
|
||||
# cards with a due date earlier than the collection should retain
|
||||
# their due date when removed
|
||||
def test_negativeDueFilter():
|
||||
|
@ -1193,7 +1255,8 @@ def test_negativeDueFilter():
|
|||
|
||||
# card due prior to collection date
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"; f['Back'] = "two"
|
||||
f["Front"] = "one"
|
||||
f["Back"] = "two"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
c.due = -5
|
||||
|
@ -1209,4 +1272,3 @@ def test_negativeDueFilter():
|
|||
|
||||
c.load()
|
||||
assert c.due == -5
|
||||
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
import os
|
||||
from tests.shared import getEmptyCol
|
||||
|
||||
|
||||
def test_stats():
|
||||
d = getEmptyCol()
|
||||
f = d.newNote()
|
||||
f['Front'] = "foo"
|
||||
f["Front"] = "foo"
|
||||
d.addNote(f)
|
||||
c = f.cards()[0]
|
||||
# card stats
|
||||
|
@ -17,12 +18,15 @@ def test_stats():
|
|||
d.sched.answerCard(c, 2)
|
||||
assert d.cardStats(c)
|
||||
|
||||
|
||||
def test_graphs_empty():
|
||||
d = getEmptyCol()
|
||||
assert d.stats().report()
|
||||
|
||||
|
||||
def test_graphs():
|
||||
from anki import Collection as aopen
|
||||
|
||||
d = aopen(os.path.expanduser("~/test.anki2"))
|
||||
g = d.stats()
|
||||
rep = g.report()
|
||||
|
|
|
@ -2,15 +2,18 @@ from anki.template import Template
|
|||
|
||||
|
||||
def test_remove_formatting_from_mathjax():
|
||||
t = Template('')
|
||||
assert t._removeFormattingFromMathjax(r'\(2^{{c3::2}}\)', 3) == r'\(2^{{C3::2}}\)'
|
||||
t = Template("")
|
||||
assert t._removeFormattingFromMathjax(r"\(2^{{c3::2}}\)", 3) == r"\(2^{{C3::2}}\)"
|
||||
|
||||
txt = (r'{{c1::ok}} \(2^2\) {{c2::not ok}} \(2^{{c3::2}}\) \(x^3\) '
|
||||
r'{{c4::blah}} {{c5::text with \(x^2\) jax}}')
|
||||
txt = (
|
||||
r"{{c1::ok}} \(2^2\) {{c2::not ok}} \(2^{{c3::2}}\) \(x^3\) "
|
||||
r"{{c4::blah}} {{c5::text with \(x^2\) jax}}"
|
||||
)
|
||||
# Cloze 2 is not in MathJax, so it should not get protected against
|
||||
# formatting.
|
||||
assert t._removeFormattingFromMathjax(txt, 2) == txt
|
||||
|
||||
txt = r'\(a\) {{c1::b}} \[ {{c1::c}} \]'
|
||||
txt = r"\(a\) {{c1::b}} \[ {{c1::c}} \]"
|
||||
assert t._removeFormattingFromMathjax(txt, 1) == (
|
||||
r'\(a\) {{c1::b}} \[ {{C1::c}} \]')
|
||||
r"\(a\) {{c1::b}} \[ {{C1::c}} \]"
|
||||
)
|
||||
|
|
|
@ -4,13 +4,14 @@ import time
|
|||
from tests.shared import getEmptyCol
|
||||
from anki.consts import *
|
||||
|
||||
|
||||
def test_op():
|
||||
d = getEmptyCol()
|
||||
# should have no undo by default
|
||||
assert not d.undoName()
|
||||
# let's adjust a study option
|
||||
d.save("studyopts")
|
||||
d.conf['abc'] = 5
|
||||
d.conf["abc"] = 5
|
||||
# it should be listed as undoable
|
||||
assert d.undoName() == "studyopts"
|
||||
# with about 5 minutes until it's clobbered
|
||||
|
@ -18,7 +19,7 @@ def test_op():
|
|||
# undoing should restore the old value
|
||||
d.undo()
|
||||
assert not d.undoName()
|
||||
assert 'abc' not in d.conf
|
||||
assert "abc" not in d.conf
|
||||
# an (auto)save will clear the undo
|
||||
d.save("foo")
|
||||
assert d.undoName() == "foo"
|
||||
|
@ -27,7 +28,7 @@ def test_op():
|
|||
# and a review will, too
|
||||
d.save("add")
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert d.undoName() == "add"
|
||||
|
@ -35,11 +36,12 @@ def test_op():
|
|||
d.sched.answerCard(c, 2)
|
||||
assert d.undoName() == "Review"
|
||||
|
||||
|
||||
def test_review():
|
||||
d = getEmptyCol()
|
||||
d.conf['counts'] = COUNT_REMAINING
|
||||
d.conf["counts"] = COUNT_REMAINING
|
||||
f = d.newNote()
|
||||
f['Front'] = "one"
|
||||
f["Front"] = "one"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert not d.undoName()
|
||||
|
@ -62,7 +64,7 @@ def test_review():
|
|||
assert not d.undoName()
|
||||
# we should be able to undo multiple answers too
|
||||
f = d.newNote()
|
||||
f['Front'] = "two"
|
||||
f["Front"] = "two"
|
||||
d.addNote(f)
|
||||
d.reset()
|
||||
assert d.sched.counts() == (2, 0, 0)
|
||||
|
@ -85,5 +87,3 @@ def test_review():
|
|||
assert d.undoName() == "foo"
|
||||
d.undo()
|
||||
assert not d.undoName()
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
from anki.utils import fmtTimeSpan
|
||||
|
||||
|
||||
def test_fmtTimeSpan():
|
||||
assert fmtTimeSpan(5) == "5 seconds"
|
||||
assert fmtTimeSpan(5, inTime=True) == "in 5 seconds"
|
||||
|
|
Loading…
Reference in a new issue