Merge pull request #691 from Arthur-Milchior/no_single_name_var_in_test

Improve variable names in tests
This commit is contained in:
Damien Elmes 2020-07-17 15:37:22 +10:00 committed by GitHub
commit 7d8818f855
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1971 additions and 1962 deletions

View file

@ -4,90 +4,91 @@ from tests.shared import getEmptyCol
def test_delete(): def test_delete():
deck = getEmptyCol() col = getEmptyCol()
f = deck.newNote() note = col.newNote()
f["Front"] = "1" note["Front"] = "1"
f["Back"] = "2" note["Back"] = "2"
deck.addNote(f) col.addNote(note)
cid = f.cards()[0].id cid = note.cards()[0].id
deck.reset() col.reset()
deck.sched.answerCard(deck.sched.getCard(), 2) col.sched.answerCard(col.sched.getCard(), 2)
deck.remove_cards_and_orphaned_notes([cid]) col.remove_cards_and_orphaned_notes([cid])
assert deck.cardCount() == 0 assert col.cardCount() == 0
assert deck.noteCount() == 0 assert col.noteCount() == 0
assert deck.db.scalar("select count() from notes") == 0 assert col.db.scalar("select count() from notes") == 0
assert deck.db.scalar("select count() from cards") == 0 assert col.db.scalar("select count() from cards") == 0
assert deck.db.scalar("select count() from graves") == 2 assert col.db.scalar("select count() from graves") == 2
def test_misc(): def test_misc():
d = getEmptyCol() col = getEmptyCol()
f = d.newNote() note = col.newNote()
f["Front"] = "1" note["Front"] = "1"
f["Back"] = "2" note["Back"] = "2"
d.addNote(f) col.addNote(note)
c = f.cards()[0] c = note.cards()[0]
id = d.models.current()["id"] id = col.models.current()["id"]
assert c.template()["ord"] == 0 assert c.template()["ord"] == 0
def test_genrem(): def test_genrem():
d = getEmptyCol() col = getEmptyCol()
f = d.newNote() note = col.newNote()
f["Front"] = "1" note["Front"] = "1"
f["Back"] = "" note["Back"] = ""
d.addNote(f) col.addNote(note)
assert len(f.cards()) == 1 assert len(note.cards()) == 1
m = d.models.current() m = col.models.current()
mm = d.models mm = col.models
# adding a new template should automatically create cards # adding a new template should automatically create cards
t = mm.newTemplate("rev") t = mm.newTemplate("rev")
t["qfmt"] = "{{Front}}" t["qfmt"] = "{{Front}}"
t["afmt"] = "" t["afmt"] = ""
mm.addTemplate(m, t) mm.addTemplate(m, t)
mm.save(m, templates=True) mm.save(m, templates=True)
assert len(f.cards()) == 2 assert len(note.cards()) == 2
# if the template is changed to remove cards, they'll be removed # if the template is changed to remove cards, they'll be removed
t = m["tmpls"][1] t = m["tmpls"][1]
t["qfmt"] = "{{Back}}" t["qfmt"] = "{{Back}}"
mm.save(m, templates=True) mm.save(m, templates=True)
rep = d.backend.get_empty_cards() rep = col.backend.get_empty_cards()
for note in rep.notes: rep = col.backend.get_empty_cards()
d.remove_cards_and_orphaned_notes(note.card_ids) for n in rep.notes:
assert len(f.cards()) == 1 col.remove_cards_and_orphaned_notes(n.card_ids)
assert len(note.cards()) == 1
# if we add to the note, a card should be automatically generated # if we add to the note, a card should be automatically generated
f.load() note.load()
f["Back"] = "1" note["Back"] = "1"
f.flush() note.flush()
assert len(f.cards()) == 2 assert len(note.cards()) == 2
def test_gendeck(): def test_gendeck():
d = getEmptyCol() col = getEmptyCol()
cloze = d.models.byName("Cloze") cloze = col.models.byName("Cloze")
d.models.setCurrent(cloze) col.models.setCurrent(cloze)
f = d.newNote() note = col.newNote()
f["Text"] = "{{c1::one}}" note["Text"] = "{{c1::one}}"
d.addNote(f) col.addNote(note)
assert d.cardCount() == 1 assert col.cardCount() == 1
assert f.cards()[0].did == 1 assert note.cards()[0].did == 1
# set the model to a new default deck # set the model to a new default col
newId = d.decks.id("new") newId = col.decks.id("new")
cloze["did"] = newId cloze["did"] = newId
d.models.save(cloze, updateReqs=False) col.models.save(cloze, updateReqs=False)
# a newly generated card should share the first card's deck # a newly generated card should share the first card's col
f["Text"] += "{{c2::two}}" note["Text"] += "{{c2::two}}"
f.flush() note.flush()
assert f.cards()[1].did == 1 assert note.cards()[1].did == 1
# and same with multiple cards # and same with multiple cards
f["Text"] += "{{c3::three}}" note["Text"] += "{{c3::three}}"
f.flush() note.flush()
assert f.cards()[2].did == 1 assert note.cards()[2].did == 1
# if one of the cards is in a different deck, it should revert to the # if one of the cards is in a different col, it should revert to the
# model default # model default
c = f.cards()[1] c = note.cards()[1]
c.did = newId c.did = newId
c.flush() c.flush()
f["Text"] += "{{c4::four}}" note["Text"] += "{{c4::four}}"
f.flush() note.flush()
assert f.cards()[3].did == newId assert note.cards()[3].did == newId

View file

@ -19,17 +19,17 @@ def test_create_open():
os.unlink(path) os.unlink(path)
except OSError: except OSError:
pass pass
deck = aopen(path) col = aopen(path)
# for open() # for open()
newPath = deck.path newPath = col.path
newMod = deck.mod newMod = col.mod
deck.close() col.close()
del deck del col
# reopen # reopen
deck = aopen(newPath) col = aopen(newPath)
assert deck.mod == newMod assert col.mod == newMod
deck.close() col.close()
# non-writeable dir # non-writeable dir
if isWin: if isWin:
@ -45,96 +45,96 @@ def test_create_open():
def test_noteAddDelete(): def test_noteAddDelete():
deck = getEmptyCol() col = getEmptyCol()
# add a note # add a note
f = deck.newNote() note = col.newNote()
f["Front"] = "one" note["Front"] = "one"
f["Back"] = "two" note["Back"] = "two"
n = deck.addNote(f) n = col.addNote(note)
assert n == 1 assert n == 1
# test multiple cards - add another template # test multiple cards - add another template
m = deck.models.current() m = col.models.current()
mm = deck.models mm = col.models
t = mm.newTemplate("Reverse") t = mm.newTemplate("Reverse")
t["qfmt"] = "{{Back}}" t["qfmt"] = "{{Back}}"
t["afmt"] = "{{Front}}" t["afmt"] = "{{Front}}"
mm.addTemplate(m, t) mm.addTemplate(m, t)
mm.save(m) mm.save(m)
assert deck.cardCount() == 2 assert col.cardCount() == 2
# creating new notes should use both cards # creating new notes should use both cards
f = deck.newNote() note = col.newNote()
f["Front"] = "three" note["Front"] = "three"
f["Back"] = "four" note["Back"] = "four"
n = deck.addNote(f) n = col.addNote(note)
assert n == 2 assert n == 2
assert deck.cardCount() == 4 assert col.cardCount() == 4
# check q/a generation # check q/a generation
c0 = f.cards()[0] c0 = note.cards()[0]
assert "three" in c0.q() assert "three" in c0.q()
# it should not be a duplicate # it should not be a duplicate
assert not f.dupeOrEmpty() assert not note.dupeOrEmpty()
# now let's make a duplicate # now let's make a duplicate
f2 = deck.newNote() note2 = col.newNote()
f2["Front"] = "one" note2["Front"] = "one"
f2["Back"] = "" note2["Back"] = ""
assert f2.dupeOrEmpty() assert note2.dupeOrEmpty()
# empty first field should not be permitted either # empty first field should not be permitted either
f2["Front"] = " " note2["Front"] = " "
assert f2.dupeOrEmpty() assert note2.dupeOrEmpty()
def test_fieldChecksum(): def test_fieldChecksum():
deck = getEmptyCol() col = getEmptyCol()
f = deck.newNote() note = col.newNote()
f["Front"] = "new" note["Front"] = "new"
f["Back"] = "new2" note["Back"] = "new2"
deck.addNote(f) col.addNote(note)
assert deck.db.scalar("select csum from notes") == int("c2a6b03f", 16) assert col.db.scalar("select csum from notes") == int("c2a6b03f", 16)
# changing the val should change the checksum # changing the val should change the checksum
f["Front"] = "newx" note["Front"] = "newx"
f.flush() note.flush()
assert deck.db.scalar("select csum from notes") == int("302811ae", 16) assert col.db.scalar("select csum from notes") == int("302811ae", 16)
def test_addDelTags(): def test_addDelTags():
deck = getEmptyCol() col = getEmptyCol()
f = deck.newNote() note = col.newNote()
f["Front"] = "1" note["Front"] = "1"
deck.addNote(f) col.addNote(note)
f2 = deck.newNote() note2 = col.newNote()
f2["Front"] = "2" note2["Front"] = "2"
deck.addNote(f2) col.addNote(note2)
# adding for a given id # adding for a given id
deck.tags.bulkAdd([f.id], "foo") col.tags.bulkAdd([note.id], "foo")
f.load() note.load()
f2.load() note2.load()
assert "foo" in f.tags assert "foo" in note.tags
assert "foo" not in f2.tags assert "foo" not in note2.tags
# should be canonified # should be canonified
deck.tags.bulkAdd([f.id], "foo aaa") col.tags.bulkAdd([note.id], "foo aaa")
f.load() note.load()
assert f.tags[0] == "aaa" assert note.tags[0] == "aaa"
assert len(f.tags) == 2 assert len(note.tags) == 2
def test_timestamps(): def test_timestamps():
deck = getEmptyCol() col = getEmptyCol()
assert len(deck.models.all_names_and_ids()) == len(get_stock_notetypes(deck)) assert len(col.models.all_names_and_ids()) == len(get_stock_notetypes(col))
for i in range(100): for i in range(100):
addBasicModel(deck) addBasicModel(col)
assert len(deck.models.all_names_and_ids()) == 100 + len(get_stock_notetypes(deck)) assert len(col.models.all_names_and_ids()) == 100 + len(get_stock_notetypes(col))
def test_furigana(): def test_furigana():
deck = getEmptyCol() col = getEmptyCol()
mm = deck.models mm = col.models
m = mm.current() m = mm.current()
# filter should work # filter should work
m["tmpls"][0]["qfmt"] = "{{kana:Front}}" m["tmpls"][0]["qfmt"] = "{{kana:Front}}"
mm.save(m) mm.save(m)
n = deck.newNote() n = col.newNote()
n["Front"] = "foo[abc]" n["Front"] = "foo[abc]"
deck.addNote(n) col.addNote(n)
c = n.cards()[0] c = n.cards()[0]
assert c.q().endswith("abc") assert c.q().endswith("abc")
# and should avoid sound # and should avoid sound
@ -148,15 +148,15 @@ def test_furigana():
def test_translate(): def test_translate():
d = getEmptyCol() col = getEmptyCol()
no_uni = without_unicode_isolation no_uni = without_unicode_isolation
assert ( assert (
d.tr(TR.CARD_TEMPLATE_RENDERING_FRONT_SIDE_PROBLEM) col.tr(TR.CARD_TEMPLATE_RENDERING_FRONT_SIDE_PROBLEM)
== "Front template has a problem:" == "Front template has a problem:"
) )
assert no_uni(d.tr(TR.STATISTICS_REVIEWS, reviews=1)) == "1 review" assert no_uni(col.tr(TR.STATISTICS_REVIEWS, reviews=1)) == "1 review"
assert no_uni(d.tr(TR.STATISTICS_REVIEWS, reviews=2)) == "2 reviews" assert no_uni(col.tr(TR.STATISTICS_REVIEWS, reviews=2)) == "2 reviews"
def test_db_named_args(capsys): def test_db_named_args(capsys):

View file

@ -5,141 +5,141 @@ from tests.shared import assertException, getEmptyCol
def test_basic(): def test_basic():
deck = getEmptyCol() col = getEmptyCol()
# we start with a standard deck # we start with a standard col
assert len(deck.decks.all_names_and_ids()) == 1 assert len(col.decks.all_names_and_ids()) == 1
# it should have an id of 1 # it should have an id of 1
assert deck.decks.name(1) assert col.decks.name(1)
# create a new deck # create a new col
parentId = deck.decks.id("new deck") parentId = col.decks.id("new deck")
assert parentId assert parentId
assert len(deck.decks.all_names_and_ids()) == 2 assert len(col.decks.all_names_and_ids()) == 2
# should get the same id # should get the same id
assert deck.decks.id("new deck") == parentId assert col.decks.id("new deck") == parentId
# we start with the default deck selected # we start with the default col selected
assert deck.decks.selected() == 1 assert col.decks.selected() == 1
assert deck.decks.active() == [1] assert col.decks.active() == [1]
# we can select a different deck # we can select a different col
deck.decks.select(parentId) col.decks.select(parentId)
assert deck.decks.selected() == parentId assert col.decks.selected() == parentId
assert deck.decks.active() == [parentId] assert col.decks.active() == [parentId]
# let's create a child # let's create a child
childId = deck.decks.id("new deck::child") childId = col.decks.id("new deck::child")
deck.sched.reset() col.sched.reset()
# it should have been added to the active list # it should have been added to the active list
assert deck.decks.selected() == parentId assert col.decks.selected() == parentId
assert deck.decks.active() == [parentId, childId] assert col.decks.active() == [parentId, childId]
# we can select the child individually too # we can select the child individually too
deck.decks.select(childId) col.decks.select(childId)
assert deck.decks.selected() == childId assert col.decks.selected() == childId
assert deck.decks.active() == [childId] assert col.decks.active() == [childId]
# parents with a different case should be handled correctly # parents with a different case should be handled correctly
deck.decks.id("ONE") col.decks.id("ONE")
m = deck.models.current() m = col.models.current()
m["did"] = deck.decks.id("one::two") m["did"] = col.decks.id("one::two")
deck.models.save(m, updateReqs=False) col.models.save(m, updateReqs=False)
n = deck.newNote() n = col.newNote()
n["Front"] = "abc" n["Front"] = "abc"
deck.addNote(n) col.addNote(n)
def test_remove(): def test_remove():
deck = getEmptyCol() col = getEmptyCol()
# create a new deck, and add a note/card to it # create a new col, and add a note/card to it
g1 = deck.decks.id("g1") deck1 = col.decks.id("deck1")
f = deck.newNote() note = col.newNote()
f["Front"] = "1" note["Front"] = "1"
f.model()["did"] = g1 note.model()["did"] = deck1
deck.addNote(f) col.addNote(note)
c = f.cards()[0] c = note.cards()[0]
assert c.did == g1 assert c.did == deck1
assert deck.cardCount() == 1 assert col.cardCount() == 1
deck.decks.rem(g1) col.decks.rem(deck1)
assert deck.cardCount() == 0 assert col.cardCount() == 0
# if we try to get it, we get the default # if we try to get it, we get the default
assert deck.decks.name(c.did) == "[no deck]" assert col.decks.name(c.did) == "[no deck]"
def test_rename(): def test_rename():
d = getEmptyCol() col = getEmptyCol()
id = d.decks.id("hello::world") id = col.decks.id("hello::world")
# should be able to rename into a completely different branch, creating # should be able to rename into a completely different branch, creating
# parents as necessary # parents as necessary
d.decks.rename(d.decks.get(id), "foo::bar") col.decks.rename(col.decks.get(id), "foo::bar")
names = [n.name for n in d.decks.all_names_and_ids()] names = [n.name for n in col.decks.all_names_and_ids()]
assert "foo" in names assert "foo" in names
assert "foo::bar" in names assert "foo::bar" in names
assert "hello::world" not in names assert "hello::world" not in names
# create another deck # create another col
id = d.decks.id("tmp") id = col.decks.id("tmp")
# automatically adjusted if a duplicate name # automatically adjusted if a duplicate name
d.decks.rename(d.decks.get(id), "FOO") col.decks.rename(col.decks.get(id), "FOO")
names = [n.name for n in d.decks.all_names_and_ids()] names = [n.name for n in col.decks.all_names_and_ids()]
assert "FOO+" in names assert "FOO+" in names
# when renaming, the children should be renamed too # when renaming, the children should be renamed too
d.decks.id("one::two::three") col.decks.id("one::two::three")
id = d.decks.id("one") id = col.decks.id("one")
d.decks.rename(d.decks.get(id), "yo") col.decks.rename(col.decks.get(id), "yo")
names = [n.name for n in d.decks.all_names_and_ids()] names = [n.name for n in col.decks.all_names_and_ids()]
for n in "yo", "yo::two", "yo::two::three": for n in "yo", "yo::two", "yo::two::three":
assert n in names assert n in names
# over filtered # over filtered
filteredId = d.decks.newDyn("filtered") filteredId = col.decks.newDyn("filtered")
filtered = d.decks.get(filteredId) filtered = col.decks.get(filteredId)
childId = d.decks.id("child") childId = col.decks.id("child")
child = d.decks.get(childId) child = col.decks.get(childId)
assertException(DeckRenameError, lambda: d.decks.rename(child, "filtered::child")) assertException(DeckRenameError, lambda: col.decks.rename(child, "filtered::child"))
assertException(DeckRenameError, lambda: d.decks.rename(child, "FILTERED::child")) assertException(DeckRenameError, lambda: col.decks.rename(child, "FILTERED::child"))
def test_renameForDragAndDrop(): def test_renameForDragAndDrop():
d = getEmptyCol() col = getEmptyCol()
def deckNames(): def deckNames():
return [n.name for n in d.decks.all_names_and_ids(skip_empty_default=True)] return [n.name for n in col.decks.all_names_and_ids(skip_empty_default=True)]
languages_did = d.decks.id("Languages") languages_did = col.decks.id("Languages")
chinese_did = d.decks.id("Chinese") chinese_did = col.decks.id("Chinese")
hsk_did = d.decks.id("Chinese::HSK") hsk_did = col.decks.id("Chinese::HSK")
# Renaming also renames children # Renaming also renames children
d.decks.renameForDragAndDrop(chinese_did, languages_did) col.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 # Dragging a col onto itself is a no-op
d.decks.renameForDragAndDrop(languages_did, languages_did) col.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 # Dragging a col onto its parent is a no-op
d.decks.renameForDragAndDrop(hsk_did, chinese_did) col.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 # Dragging a col onto a descendant is a no-op
d.decks.renameForDragAndDrop(languages_did, hsk_did) col.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 # Can drag a grandchild onto its grandparent. It becomes a child
d.decks.renameForDragAndDrop(hsk_did, languages_did) col.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 # Can drag a col onto its sibling
d.decks.renameForDragAndDrop(hsk_did, chinese_did) col.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 # Can drag a col back to the top level
d.decks.renameForDragAndDrop(chinese_did, None) col.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 # Dragging a top level col to the top level is a no-op
d.decks.renameForDragAndDrop(chinese_did, None) col.decks.renameForDragAndDrop(chinese_did, None)
assert deckNames() == ["Chinese", "Chinese::HSK", "Languages"] assert deckNames() == ["Chinese", "Chinese::HSK", "Languages"]
# decks are renamed if necessary # decks are renamed if necessary
new_hsk_did = d.decks.id("hsk") new_hsk_did = col.decks.id("hsk")
d.decks.renameForDragAndDrop(new_hsk_did, chinese_did) col.decks.renameForDragAndDrop(new_hsk_did, chinese_did)
assert deckNames() == ["Chinese", "Chinese::HSK", "Chinese::hsk+", "Languages"] assert deckNames() == ["Chinese", "Chinese::HSK", "Chinese::hsk+", "Languages"]
d.decks.rem(new_hsk_did) col.decks.rem(new_hsk_did)
# '' is a convenient alias for the top level DID # '' is a convenient alias for the top level DID
d.decks.renameForDragAndDrop(hsk_did, "") col.decks.renameForDragAndDrop(hsk_did, "")
assert deckNames() == ["Chinese", "HSK", "Languages"] assert deckNames() == ["Chinese", "HSK", "Languages"]

View file

@ -16,25 +16,25 @@ def getEmptyCol():
return col return col
deck = None col = None
ds = None ds = None
testDir = os.path.dirname(__file__) testDir = os.path.dirname(__file__)
def setup1(): def setup1():
global deck global col
deck = getEmptyCol() col = getEmptyCol()
f = deck.newNote() note = col.newNote()
f["Front"] = "foo" note["Front"] = "foo"
f["Back"] = "bar<br>" note["Back"] = "bar<br>"
f.tags = ["tag", "tag2"] note.tags = ["tag", "tag2"]
deck.addNote(f) col.addNote(note)
# with a different deck # with a different col
f = deck.newNote() note = col.newNote()
f["Front"] = "baz" note["Front"] = "baz"
f["Back"] = "qux" note["Back"] = "qux"
f.model()["did"] = deck.decks.id("new deck") note.model()["did"] = col.decks.id("new col")
deck.addNote(f) col.addNote(note)
########################################################################## ##########################################################################
@ -42,23 +42,23 @@ def setup1():
def test_export_anki(): def test_export_anki():
setup1() setup1()
# create a new deck with its own conf to test conf copying # create a new col with its own conf to test conf copying
did = deck.decks.id("test") did = col.decks.id("test")
dobj = deck.decks.get(did) dobj = col.decks.get(did)
confId = deck.decks.add_config_returning_id("newconf") confId = col.decks.add_config_returning_id("newconf")
conf = deck.decks.get_config(confId) conf = col.decks.get_config(confId)
conf["new"]["perDay"] = 5 conf["new"]["perDay"] = 5
deck.decks.save(conf) col.decks.save(conf)
deck.decks.setConf(dobj, confId) col.decks.setConf(dobj, confId)
# export # export
e = AnkiExporter(deck) e = AnkiExporter(col)
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2") fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2")
newname = str(newname) newname = str(newname)
os.close(fd) os.close(fd)
os.unlink(newname) os.unlink(newname)
e.exportInto(newname) e.exportInto(newname)
# exporting should not have changed conf for original deck # exporting should not have changed conf for original deck
conf = deck.decks.confForDid(did) conf = col.decks.confForDid(did)
assert conf["id"] != 1 assert conf["id"] != 1
# connect to new deck # connect to new deck
d2 = aopen(newname) d2 = aopen(newname)
@ -85,12 +85,12 @@ def test_export_anki():
def test_export_ankipkg(): def test_export_ankipkg():
setup1() setup1()
# add a test file to the media folder # add a test file to the media folder
with open(os.path.join(deck.media.dir(), "今日.mp3"), "w") as f: with open(os.path.join(col.media.dir(), "今日.mp3"), "w") as note:
f.write("test") note.write("test")
n = deck.newNote() n = col.newNote()
n["Front"] = "[sound:今日.mp3]" n["Front"] = "[sound:今日.mp3]"
deck.addNote(n) col.addNote(n)
e = AnkiPackageExporter(deck) e = AnkiPackageExporter(col)
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".apkg") fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".apkg")
newname = str(newname) newname = str(newname)
os.close(fd) os.close(fd)
@ -101,23 +101,23 @@ def test_export_ankipkg():
@errorsAfterMidnight @errorsAfterMidnight
def test_export_anki_due(): def test_export_anki_due():
setup1() setup1()
deck = getEmptyCol() col = getEmptyCol()
f = deck.newNote() note = col.newNote()
f["Front"] = "foo" note["Front"] = "foo"
deck.addNote(f) col.addNote(note)
deck.crt -= 86400 * 10 col.crt -= 86400 * 10
deck.flush() col.flush()
deck.sched.reset() col.sched.reset()
c = deck.sched.getCard() c = col.sched.getCard()
deck.sched.answerCard(c, 3) col.sched.answerCard(c, 3)
deck.sched.answerCard(c, 3) col.sched.answerCard(c, 3)
# should have ivl of 1, due on day 11 # should have ivl of 1, due on day 11
assert c.ivl == 1 assert c.ivl == 1
assert c.due == 11 assert c.due == 11
assert deck.sched.today == 10 assert col.sched.today == 10
assert c.due - deck.sched.today == 1 assert c.due - col.sched.today == 1
# export # export
e = AnkiExporter(deck) e = AnkiExporter(col)
e.includeSched = True e.includeSched = True
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2") fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2")
newname = str(newname) newname = str(newname)
@ -135,28 +135,28 @@ def test_export_anki_due():
# def test_export_textcard(): # def test_export_textcard():
# setup1() # setup1()
# e = TextCardExporter(deck) # e = TextCardExporter(col)
# f = unicode(tempfile.mkstemp(prefix="ankitest")[1]) # note = unicode(tempfile.mkstemp(prefix="ankitest")[1])
# os.unlink(f) # os.unlink(note)
# e.exportInto(f) # e.exportInto(note)
# e.includeTags = True # e.includeTags = True
# e.exportInto(f) # e.exportInto(note)
def test_export_textnote(): def test_export_textnote():
setup1() setup1()
e = TextNoteExporter(deck) e = TextNoteExporter(col)
fd, f = tempfile.mkstemp(prefix="ankitest") fd, note = tempfile.mkstemp(prefix="ankitest")
f = str(f) note = str(note)
os.close(fd) os.close(fd)
os.unlink(f) os.unlink(note)
e.exportInto(f) e.exportInto(note)
with open(f) as file: with open(note) as file:
assert file.readline() == "foo\tbar<br>\ttag tag2\n" assert file.readline() == "foo\tbar<br>\ttag tag2\n"
e.includeTags = False e.includeTags = False
e.includeHTML = False e.includeHTML = False
e.exportInto(f) e.exportInto(note)
with open(f) as file: with open(note) as file:
assert file.readline() == "foo\tbar\n" assert file.readline() == "foo\tbar\n"

View file

@ -12,284 +12,284 @@ class DummyCollection:
def test_findCards(): def test_findCards():
deck = getEmptyCol() col = getEmptyCol()
f = deck.newNote() note = col.newNote()
f["Front"] = "dog" note["Front"] = "dog"
f["Back"] = "cat" note["Back"] = "cat"
f.tags.append("monkey animal_1 * %") note.tags.append("monkey animal_1 * %")
deck.addNote(f) col.addNote(note)
f1id = f.id f1id = note.id
firstCardId = f.cards()[0].id firstCardId = note.cards()[0].id
f = deck.newNote() note = col.newNote()
f["Front"] = "goats are fun" note["Front"] = "goats are fun"
f["Back"] = "sheep" note["Back"] = "sheep"
f.tags.append("sheep goat horse animal11") note.tags.append("sheep goat horse animal11")
deck.addNote(f) col.addNote(note)
f2id = f.id f2id = note.id
f = deck.newNote() note = col.newNote()
f["Front"] = "cat" note["Front"] = "cat"
f["Back"] = "sheep" note["Back"] = "sheep"
deck.addNote(f) col.addNote(note)
catCard = f.cards()[0] catCard = note.cards()[0]
m = deck.models.current() m = col.models.current()
m = deck.models.copy(m) m = col.models.copy(m)
mm = deck.models mm = col.models
t = mm.newTemplate("Reverse") t = mm.newTemplate("Reverse")
t["qfmt"] = "{{Back}}" t["qfmt"] = "{{Back}}"
t["afmt"] = "{{Front}}" t["afmt"] = "{{Front}}"
mm.addTemplate(m, t) mm.addTemplate(m, t)
mm.save(m) mm.save(m)
f = deck.newNote() note = col.newNote()
f["Front"] = "test" note["Front"] = "test"
f["Back"] = "foo bar" note["Back"] = "foo bar"
deck.addNote(f) col.addNote(note)
deck.save() col.save()
latestCardIds = [c.id for c in f.cards()] latestCardIds = [c.id for c in note.cards()]
# tag searches # tag searches
assert len(deck.findCards("tag:*")) == 5 assert len(col.findCards("tag:*")) == 5
assert len(deck.findCards("tag:\\*")) == 1 assert len(col.findCards("tag:\\*")) == 1
assert len(deck.findCards("tag:%")) == 5 assert len(col.findCards("tag:%")) == 5
assert len(deck.findCards("tag:\\%")) == 1 assert len(col.findCards("tag:\\%")) == 1
assert len(deck.findCards("tag:animal_1")) == 2 assert len(col.findCards("tag:animal_1")) == 2
assert len(deck.findCards("tag:animal\\_1")) == 1 assert len(col.findCards("tag:animal\\_1")) == 1
assert not deck.findCards("tag:donkey") assert not col.findCards("tag:donkey")
assert len(deck.findCards("tag:sheep")) == 1 assert len(col.findCards("tag:sheep")) == 1
assert len(deck.findCards("tag:sheep tag:goat")) == 1 assert len(col.findCards("tag:sheep tag:goat")) == 1
assert len(deck.findCards("tag:sheep tag:monkey")) == 0 assert len(col.findCards("tag:sheep tag:monkey")) == 0
assert len(deck.findCards("tag:monkey")) == 1 assert len(col.findCards("tag:monkey")) == 1
assert len(deck.findCards("tag:sheep -tag:monkey")) == 1 assert len(col.findCards("tag:sheep -tag:monkey")) == 1
assert len(deck.findCards("-tag:sheep")) == 4 assert len(col.findCards("-tag:sheep")) == 4
deck.tags.bulkAdd(deck.db.list("select id from notes"), "foo bar") col.tags.bulkAdd(col.db.list("select id from notes"), "foo bar")
assert len(deck.findCards("tag:foo")) == len(deck.findCards("tag:bar")) == 5 assert len(col.findCards("tag:foo")) == len(col.findCards("tag:bar")) == 5
deck.tags.bulkRem(deck.db.list("select id from notes"), "foo") col.tags.bulkRem(col.db.list("select id from notes"), "foo")
assert len(deck.findCards("tag:foo")) == 0 assert len(col.findCards("tag:foo")) == 0
assert len(deck.findCards("tag:bar")) == 5 assert len(col.findCards("tag:bar")) == 5
# text searches # text searches
assert len(deck.findCards("cat")) == 2 assert len(col.findCards("cat")) == 2
assert len(deck.findCards("cat -dog")) == 1 assert len(col.findCards("cat -dog")) == 1
assert len(deck.findCards("cat -dog")) == 1 assert len(col.findCards("cat -dog")) == 1
assert len(deck.findCards("are goats")) == 1 assert len(col.findCards("are goats")) == 1
assert len(deck.findCards('"are goats"')) == 0 assert len(col.findCards('"are goats"')) == 0
assert len(deck.findCards('"goats are"')) == 1 assert len(col.findCards('"goats are"')) == 1
# card states # card states
c = f.cards()[0] c = note.cards()[0]
c.queue = c.type = CARD_TYPE_REV c.queue = c.type = CARD_TYPE_REV
assert deck.findCards("is:review") == [] assert col.findCards("is:review") == []
c.flush() c.flush()
assert deck.findCards("is:review") == [c.id] assert col.findCards("is:review") == [c.id]
assert deck.findCards("is:due") == [] assert col.findCards("is:due") == []
c.due = 0 c.due = 0
c.queue = QUEUE_TYPE_REV c.queue = QUEUE_TYPE_REV
c.flush() c.flush()
assert deck.findCards("is:due") == [c.id] assert col.findCards("is:due") == [c.id]
assert len(deck.findCards("-is:due")) == 4 assert len(col.findCards("-is:due")) == 4
c.queue = -1 c.queue = -1
# ensure this card gets a later mod time # ensure this card gets a later mod time
c.flush() c.flush()
deck.db.execute("update cards set mod = mod + 1 where id = ?", c.id) col.db.execute("update cards set mod = mod + 1 where id = ?", c.id)
assert deck.findCards("is:suspended") == [c.id] assert col.findCards("is:suspended") == [c.id]
# nids # nids
assert deck.findCards("nid:54321") == [] assert col.findCards("nid:54321") == []
assert len(deck.findCards("nid:%d" % f.id)) == 2 assert len(col.findCards(f"nid:{note.id}")) == 2
assert len(deck.findCards("nid:%d,%d" % (f1id, f2id))) == 2 assert len(col.findCards(f"nid:{f1id},{f2id}")) == 2
# templates # templates
assert len(deck.findCards("card:foo")) == 0 assert len(col.findCards("card:foo")) == 0
assert len(deck.findCards('"card:card 1"')) == 4 assert len(col.findCards('"card:card 1"')) == 4
assert len(deck.findCards("card:reverse")) == 1 assert len(col.findCards("card:reverse")) == 1
assert len(deck.findCards("card:1")) == 4 assert len(col.findCards("card:1")) == 4
assert len(deck.findCards("card:2")) == 1 assert len(col.findCards("card:2")) == 1
# fields # fields
assert len(deck.findCards("front:dog")) == 1 assert len(col.findCards("front:dog")) == 1
assert len(deck.findCards("-front:dog")) == 4 assert len(col.findCards("-front:dog")) == 4
assert len(deck.findCards("front:sheep")) == 0 assert len(col.findCards("front:sheep")) == 0
assert len(deck.findCards("back:sheep")) == 2 assert len(col.findCards("back:sheep")) == 2
assert len(deck.findCards("-back:sheep")) == 3 assert len(col.findCards("-back:sheep")) == 3
assert len(deck.findCards("front:do")) == 0 assert len(col.findCards("front:do")) == 0
assert len(deck.findCards("front:*")) == 5 assert len(col.findCards("front:*")) == 5
# ordering # ordering
deck.conf["sortType"] = "noteCrt" col.conf["sortType"] = "noteCrt"
deck.flush() col.flush()
assert deck.findCards("front:*", order=True)[-1] in latestCardIds assert col.findCards("front:*", order=True)[-1] in latestCardIds
assert deck.findCards("", order=True)[-1] in latestCardIds assert col.findCards("", order=True)[-1] in latestCardIds
deck.conf["sortType"] = "noteFld" col.conf["sortType"] = "noteFld"
deck.flush() col.flush()
assert deck.findCards("", order=True)[0] == catCard.id assert col.findCards("", order=True)[0] == catCard.id
assert deck.findCards("", order=True)[-1] in latestCardIds assert col.findCards("", order=True)[-1] in latestCardIds
deck.conf["sortType"] = "cardMod" col.conf["sortType"] = "cardMod"
deck.flush() col.flush()
assert deck.findCards("", order=True)[-1] in latestCardIds assert col.findCards("", order=True)[-1] in latestCardIds
assert deck.findCards("", order=True)[0] == firstCardId assert col.findCards("", order=True)[0] == firstCardId
deck.conf["sortBackwards"] = True col.conf["sortBackwards"] = True
deck.flush() col.flush()
assert deck.findCards("", order=True)[0] in latestCardIds assert col.findCards("", order=True)[0] in latestCardIds
assert ( assert (
deck.find_cards("", order=BuiltinSortKind.CARD_DUE, reverse=False)[0] col.find_cards("", order=BuiltinSortKind.CARD_DUE, reverse=False)[0]
== firstCardId == firstCardId
) )
assert ( assert (
deck.find_cards("", order=BuiltinSortKind.CARD_DUE, reverse=True)[0] col.find_cards("", order=BuiltinSortKind.CARD_DUE, reverse=True)[0]
!= firstCardId != firstCardId
) )
# model # model
assert len(deck.findCards("note:basic")) == 3 assert len(col.findCards("note:basic")) == 3
assert len(deck.findCards("-note:basic")) == 2 assert len(col.findCards("-note:basic")) == 2
assert len(deck.findCards("-note:foo")) == 5 assert len(col.findCards("-note:foo")) == 5
# deck # col
assert len(deck.findCards("deck:default")) == 5 assert len(col.findCards("deck:default")) == 5
assert len(deck.findCards("-deck:default")) == 0 assert len(col.findCards("-deck:default")) == 0
assert len(deck.findCards("-deck:foo")) == 5 assert len(col.findCards("-deck:foo")) == 5
assert len(deck.findCards("deck:def*")) == 5 assert len(col.findCards("deck:def*")) == 5
assert len(deck.findCards("deck:*EFAULT")) == 5 assert len(col.findCards("deck:*EFAULT")) == 5
assert len(deck.findCards("deck:*cefault")) == 0 assert len(col.findCards("deck:*cefault")) == 0
# full search # full search
f = deck.newNote() note = col.newNote()
f["Front"] = "hello<b>world</b>" note["Front"] = "hello<b>world</b>"
f["Back"] = "abc" note["Back"] = "abc"
deck.addNote(f) col.addNote(note)
# as it's the sort field, it matches # as it's the sort field, it matches
assert len(deck.findCards("helloworld")) == 2 assert len(col.findCards("helloworld")) == 2
# assert len(deck.findCards("helloworld", full=True)) == 2 # assert len(col.findCards("helloworld", full=True)) == 2
# if we put it on the back, it won't # if we put it on the back, it won't
(f["Front"], f["Back"]) = (f["Back"], f["Front"]) (note["Front"], note["Back"]) = (note["Back"], note["Front"])
f.flush() note.flush()
assert len(deck.findCards("helloworld")) == 0 assert len(col.findCards("helloworld")) == 0
# assert len(deck.findCards("helloworld", full=True)) == 2 # assert len(col.findCards("helloworld", full=True)) == 2
# assert len(deck.findCards("back:helloworld", full=True)) == 2 # assert len(col.findCards("back:helloworld", full=True)) == 2
# searching for an invalid special tag should not error # searching for an invalid special tag should not error
with pytest.raises(Exception): with pytest.raises(Exception):
len(deck.findCards("is:invalid")) len(col.findCards("is:invalid"))
# should be able to limit to parent deck, no children # should be able to limit to parent col, no children
id = deck.db.scalar("select id from cards limit 1") id = col.db.scalar("select id from cards limit 1")
deck.db.execute( col.db.execute(
"update cards set did = ? where id = ?", deck.decks.id("Default::Child"), id "update cards set did = ? where id = ?", col.decks.id("Default::Child"), id
) )
deck.save() col.save()
assert len(deck.findCards("deck:default")) == 7 assert len(col.findCards("deck:default")) == 7
assert len(deck.findCards("deck:default::child")) == 1 assert len(col.findCards("deck:default::child")) == 1
assert len(deck.findCards("deck:default -deck:default::*")) == 6 assert len(col.findCards("deck:default -deck:default::*")) == 6
# properties # properties
id = deck.db.scalar("select id from cards limit 1") id = col.db.scalar("select id from cards limit 1")
deck.db.execute( col.db.execute(
"update cards set queue=2, ivl=10, reps=20, due=30, factor=2200 " "update cards set queue=2, ivl=10, reps=20, due=30, factor=2200 "
"where id = ?", "where id = ?",
id, id,
) )
assert len(deck.findCards("prop:ivl>5")) == 1 assert len(col.findCards("prop:ivl>5")) == 1
assert len(deck.findCards("prop:ivl<5")) > 1 assert len(col.findCards("prop:ivl<5")) > 1
assert len(deck.findCards("prop:ivl>=5")) == 1 assert len(col.findCards("prop:ivl>=5")) == 1
assert len(deck.findCards("prop:ivl=9")) == 0 assert len(col.findCards("prop:ivl=9")) == 0
assert len(deck.findCards("prop:ivl=10")) == 1 assert len(col.findCards("prop:ivl=10")) == 1
assert len(deck.findCards("prop:ivl!=10")) > 1 assert len(col.findCards("prop:ivl!=10")) > 1
assert len(deck.findCards("prop:due>0")) == 1 assert len(col.findCards("prop:due>0")) == 1
# due dates should work # due dates should work
assert len(deck.findCards("prop:due=29")) == 0 assert len(col.findCards("prop:due=29")) == 0
assert len(deck.findCards("prop:due=30")) == 1 assert len(col.findCards("prop:due=30")) == 1
# ease factors # ease factors
assert len(deck.findCards("prop:ease=2.3")) == 0 assert len(col.findCards("prop:ease=2.3")) == 0
assert len(deck.findCards("prop:ease=2.2")) == 1 assert len(col.findCards("prop:ease=2.2")) == 1
assert len(deck.findCards("prop:ease>2")) == 1 assert len(col.findCards("prop:ease>2")) == 1
assert len(deck.findCards("-prop:ease>2")) > 1 assert len(col.findCards("-prop:ease>2")) > 1
# recently failed # recently failed
if not isNearCutoff(): if not isNearCutoff():
assert len(deck.findCards("rated:1:1")) == 0 assert len(col.findCards("rated:1:1")) == 0
assert len(deck.findCards("rated:1:2")) == 0 assert len(col.findCards("rated:1:2")) == 0
c = deck.sched.getCard() c = col.sched.getCard()
deck.sched.answerCard(c, 2) col.sched.answerCard(c, 2)
assert len(deck.findCards("rated:1:1")) == 0 assert len(col.findCards("rated:1:1")) == 0
assert len(deck.findCards("rated:1:2")) == 1 assert len(col.findCards("rated:1:2")) == 1
c = deck.sched.getCard() c = col.sched.getCard()
deck.sched.answerCard(c, 1) col.sched.answerCard(c, 1)
assert len(deck.findCards("rated:1:1")) == 1 assert len(col.findCards("rated:1:1")) == 1
assert len(deck.findCards("rated:1:2")) == 1 assert len(col.findCards("rated:1:2")) == 1
assert len(deck.findCards("rated:1")) == 2 assert len(col.findCards("rated:1")) == 2
assert len(deck.findCards("rated:0:2")) == 0 assert len(col.findCards("rated:0:2")) == 0
assert len(deck.findCards("rated:2:2")) == 1 assert len(col.findCards("rated:2:2")) == 1
# added # added
assert len(deck.findCards("added:0")) == 0 assert len(col.findCards("added:0")) == 0
deck.db.execute("update cards set id = id - 86400*1000 where id = ?", id) col.db.execute("update cards set id = id - 86400*1000 where id = ?", id)
assert len(deck.findCards("added:1")) == deck.cardCount() - 1 assert len(col.findCards("added:1")) == col.cardCount() - 1
assert len(deck.findCards("added:2")) == deck.cardCount() assert len(col.findCards("added:2")) == col.cardCount()
else: else:
print("some find tests disabled near cutoff") print("some find tests disabled near cutoff")
# empty field # empty field
assert len(deck.findCards("front:")) == 0 assert len(col.findCards("front:")) == 0
f = deck.newNote() note = col.newNote()
f["Front"] = "" note["Front"] = ""
f["Back"] = "abc2" note["Back"] = "abc2"
assert deck.addNote(f) == 1 assert col.addNote(note) == 1
assert len(deck.findCards("front:")) == 1 assert len(col.findCards("front:")) == 1
# OR searches and nesting # OR searches and nesting
assert len(deck.findCards("tag:monkey or tag:sheep")) == 2 assert len(col.findCards("tag:monkey or tag:sheep")) == 2
assert len(deck.findCards("(tag:monkey OR tag:sheep)")) == 2 assert len(col.findCards("(tag:monkey OR tag:sheep)")) == 2
assert len(deck.findCards("-(tag:monkey OR tag:sheep)")) == 6 assert len(col.findCards("-(tag:monkey OR tag:sheep)")) == 6
assert len(deck.findCards("tag:monkey or (tag:sheep sheep)")) == 2 assert len(col.findCards("tag:monkey or (tag:sheep sheep)")) == 2
assert len(deck.findCards("tag:monkey or (tag:sheep octopus)")) == 1 assert len(col.findCards("tag:monkey or (tag:sheep octopus)")) == 1
# flag # flag
with pytest.raises(Exception): with pytest.raises(Exception):
deck.findCards("flag:12") col.findCards("flag:12")
def test_findReplace(): def test_findReplace():
deck = getEmptyCol() col = getEmptyCol()
f = deck.newNote() note = col.newNote()
f["Front"] = "foo" note["Front"] = "foo"
f["Back"] = "bar" note["Back"] = "bar"
deck.addNote(f) col.addNote(note)
f2 = deck.newNote() note2 = col.newNote()
f2["Front"] = "baz" note2["Front"] = "baz"
f2["Back"] = "foo" note2["Back"] = "foo"
deck.addNote(f2) col.addNote(note2)
nids = [f.id, f2.id] nids = [note.id, note2.id]
# should do nothing # should do nothing
assert deck.findReplace(nids, "abc", "123") == 0 assert col.findReplace(nids, "abc", "123") == 0
# global replace # global replace
assert deck.findReplace(nids, "foo", "qux") == 2 assert col.findReplace(nids, "foo", "qux") == 2
f.load() note.load()
assert f["Front"] == "qux" assert note["Front"] == "qux"
f2.load() note2.load()
assert f2["Back"] == "qux" assert note2["Back"] == "qux"
# single field replace # single field replace
assert deck.findReplace(nids, "qux", "foo", field="Front") == 1 assert col.findReplace(nids, "qux", "foo", field="Front") == 1
f.load() note.load()
assert f["Front"] == "foo" assert note["Front"] == "foo"
f2.load() note2.load()
assert f2["Back"] == "qux" assert note2["Back"] == "qux"
# regex replace # regex replace
assert deck.findReplace(nids, "B.r", "reg") == 0 assert col.findReplace(nids, "B.r", "reg") == 0
f.load() note.load()
assert f["Back"] != "reg" assert note["Back"] != "reg"
assert deck.findReplace(nids, "B.r", "reg", regex=True) == 1 assert col.findReplace(nids, "B.r", "reg", regex=True) == 1
f.load() note.load()
assert f["Back"] == "reg" assert note["Back"] == "reg"
def test_findDupes(): def test_findDupes():
deck = getEmptyCol() col = getEmptyCol()
f = deck.newNote() note = col.newNote()
f["Front"] = "foo" note["Front"] = "foo"
f["Back"] = "bar" note["Back"] = "bar"
deck.addNote(f) col.addNote(note)
f2 = deck.newNote() note2 = col.newNote()
f2["Front"] = "baz" note2["Front"] = "baz"
f2["Back"] = "bar" note2["Back"] = "bar"
deck.addNote(f2) col.addNote(note2)
f3 = deck.newNote() f3 = col.newNote()
f3["Front"] = "quux" f3["Front"] = "quux"
f3["Back"] = "bar" f3["Back"] = "bar"
deck.addNote(f3) col.addNote(f3)
f4 = deck.newNote() f4 = col.newNote()
f4["Front"] = "quuux" f4["Front"] = "quuux"
f4["Back"] = "nope" f4["Back"] = "nope"
deck.addNote(f4) col.addNote(f4)
r = deck.findDupes("Back") r = col.findDupes("Back")
assert r[0][0] == "bar" assert r[0][0] == "bar"
assert len(r[0][1]) == 3 assert len(r[0][1]) == 3
# valid search # valid search
r = deck.findDupes("Back", "bar") r = col.findDupes("Back", "bar")
assert r[0][0] == "bar" assert r[0][0] == "bar"
assert len(r[0][1]) == 3 assert len(r[0][1]) == 3
# excludes everything # excludes everything
r = deck.findDupes("Back", "invalid") r = col.findDupes("Back", "invalid")
assert not r assert not r
# front isn't dupe # front isn't dupe
assert deck.findDupes("Front") == [] assert col.findDupes("Front") == []

View file

@ -37,8 +37,8 @@ def test_anki2_mediadupes():
mid = n.model()["id"] mid = n.model()["id"]
tmp.addNote(n) tmp.addNote(n)
# add that sound to media folder # add that sound to media folder
with open(os.path.join(tmp.media.dir(), "foo.mp3"), "w") as f: with open(os.path.join(tmp.media.dir(), "foo.mp3"), "w") as note:
f.write("foo") note.write("foo")
tmp.close() tmp.close()
# it should be imported correctly into an empty deck # it should be imported correctly into an empty deck
empty = getEmptyCol() empty = getEmptyCol()
@ -55,8 +55,8 @@ def test_anki2_mediadupes():
# if the local file content is different, and import should trigger a # if the local file content is different, and import should trigger a
# rename # rename
empty.remove_cards_and_orphaned_notes(empty.db.list("select id from cards")) empty.remove_cards_and_orphaned_notes(empty.db.list("select id from cards"))
with open(os.path.join(empty.media.dir(), "foo.mp3"), "w") as f: with open(os.path.join(empty.media.dir(), "foo.mp3"), "w") as note:
f.write("bar") note.write("bar")
imp = Anki2Importer(empty, tmp.path) imp = Anki2Importer(empty, tmp.path)
imp.run() 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]
@ -65,8 +65,8 @@ def test_anki2_mediadupes():
# if the localized media file already exists, we rewrite the note and # if the localized media file already exists, we rewrite the note and
# media # media
empty.remove_cards_and_orphaned_notes(empty.db.list("select id from cards")) empty.remove_cards_and_orphaned_notes(empty.db.list("select id from cards"))
with open(os.path.join(empty.media.dir(), "foo.mp3"), "w") as f: with open(os.path.join(empty.media.dir(), "foo.mp3"), "w") as note:
f.write("bar") note.write("bar")
imp = Anki2Importer(empty, tmp.path) imp = Anki2Importer(empty, tmp.path)
imp.run() 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]
@ -89,8 +89,8 @@ def test_apkg():
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 # but if the local file has different data, it will rename
tmp.remove_cards_and_orphaned_notes(tmp.db.list("select id from cards")) tmp.remove_cards_and_orphaned_notes(tmp.db.list("select id from cards"))
with open(os.path.join(tmp.media.dir(), "foo.wav"), "w") as f: with open(os.path.join(tmp.media.dir(), "foo.wav"), "w") as note:
f.write("xyz") note.write("xyz")
imp = AnkiPackageImporter(tmp, apkg) imp = AnkiPackageImporter(tmp, apkg)
imp.run() imp.run()
assert len(os.listdir(tmp.media.dir())) == 2 assert len(os.listdir(tmp.media.dir())) == 2
@ -147,9 +147,9 @@ def test_anki2_updates():
def test_csv(): def test_csv():
deck = getEmptyCol() col = getEmptyCol()
file = str(os.path.join(testDir, "support/text-2fields.txt")) file = str(os.path.join(testDir, "support/text-2fields.txt"))
i = TextImporter(deck, file) i = TextImporter(col, file)
i.initMapping() i.initMapping()
i.run() i.run()
# four problems - too many & too few fields, a missing front, and a # four problems - too many & too few fields, a missing front, and a
@ -161,7 +161,7 @@ def test_csv():
assert len(i.log) == 10 assert len(i.log) == 10
assert i.total == 5 assert i.total == 5
# but importing should not clobber tags if they're unmapped # but importing should not clobber tags if they're unmapped
n = deck.getNote(deck.db.scalar("select id from notes")) n = col.getNote(col.db.scalar("select id from notes"))
n.addTag("test") n.addTag("test")
n.flush() n.flush()
i.run() i.run()
@ -172,58 +172,58 @@ def test_csv():
i.run() i.run()
assert i.total == 0 assert i.total == 0
# and if dupes mode, will reimport everything # and if dupes mode, will reimport everything
assert deck.cardCount() == 5 assert col.cardCount() == 5
i.importMode = 2 i.importMode = 2
i.run() i.run()
# includes repeated field # includes repeated field
assert i.total == 6 assert i.total == 6
assert deck.cardCount() == 11 assert col.cardCount() == 11
deck.close() col.close()
def test_csv2(): def test_csv2():
deck = getEmptyCol() col = getEmptyCol()
mm = deck.models mm = col.models
m = mm.current() m = mm.current()
f = mm.newField("Three") note = mm.newField("Three")
mm.addField(m, f) mm.addField(m, note)
mm.save(m) mm.save(m)
n = deck.newNote() n = col.newNote()
n["Front"] = "1" n["Front"] = "1"
n["Back"] = "2" n["Back"] = "2"
n["Three"] = "3" n["Three"] = "3"
deck.addNote(n) col.addNote(n)
# an update with unmapped fields should not clobber those fields # an update with unmapped fields should not clobber those fields
file = str(os.path.join(testDir, "support/text-update.txt")) file = str(os.path.join(testDir, "support/text-update.txt"))
i = TextImporter(deck, file) i = TextImporter(col, file)
i.initMapping() i.initMapping()
i.run() i.run()
n.load() n.load()
assert n["Front"] == "1" assert n["Front"] == "1"
assert n["Back"] == "x" assert n["Back"] == "x"
assert n["Three"] == "3" assert n["Three"] == "3"
deck.close() col.close()
def test_tsv_tag_modified(): def test_tsv_tag_modified():
deck = getEmptyCol() col = getEmptyCol()
mm = deck.models mm = col.models
m = mm.current() m = mm.current()
f = mm.newField("Top") note = mm.newField("Top")
mm.addField(m, f) mm.addField(m, note)
mm.save(m) mm.save(m)
n = deck.newNote() n = col.newNote()
n["Front"] = "1" n["Front"] = "1"
n["Back"] = "2" n["Back"] = "2"
n["Top"] = "3" n["Top"] = "3"
n.addTag("four") n.addTag("four")
deck.addNote(n) col.addNote(n)
# https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file # https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file
with NamedTemporaryFile(mode="w", delete=False) as tf: with NamedTemporaryFile(mode="w", delete=False) as tf:
tf.write("1\tb\tc\n") tf.write("1\tb\tc\n")
tf.flush() tf.flush()
i = TextImporter(deck, tf.name) i = TextImporter(col, tf.name)
i.initMapping() i.initMapping()
i.tagModified = "boom" i.tagModified = "boom"
i.run() i.run()
@ -238,29 +238,29 @@ def test_tsv_tag_modified():
assert len(n.tags) == 2 assert len(n.tags) == 2
assert i.updateCount == 1 assert i.updateCount == 1
deck.close() col.close()
def test_tsv_tag_multiple_tags(): def test_tsv_tag_multiple_tags():
deck = getEmptyCol() col = getEmptyCol()
mm = deck.models mm = col.models
m = mm.current() m = mm.current()
f = mm.newField("Top") note = mm.newField("Top")
mm.addField(m, f) mm.addField(m, note)
mm.save(m) mm.save(m)
n = deck.newNote() n = col.newNote()
n["Front"] = "1" n["Front"] = "1"
n["Back"] = "2" n["Back"] = "2"
n["Top"] = "3" n["Top"] = "3"
n.addTag("four") n.addTag("four")
n.addTag("five") n.addTag("five")
deck.addNote(n) col.addNote(n)
# https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file # https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file
with NamedTemporaryFile(mode="w", delete=False) as tf: with NamedTemporaryFile(mode="w", delete=False) as tf:
tf.write("1\tb\tc\n") tf.write("1\tb\tc\n")
tf.flush() tf.flush()
i = TextImporter(deck, tf.name) i = TextImporter(col, tf.name)
i.initMapping() i.initMapping()
i.tagModified = "five six" i.tagModified = "five six"
i.run() i.run()
@ -272,27 +272,27 @@ def test_tsv_tag_multiple_tags():
assert n["Top"] == "c" assert n["Top"] == "c"
assert list(sorted(n.tags)) == list(sorted(["four", "five", "six"])) assert list(sorted(n.tags)) == list(sorted(["four", "five", "six"]))
deck.close() col.close()
def test_csv_tag_only_if_modified(): def test_csv_tag_only_if_modified():
deck = getEmptyCol() col = getEmptyCol()
mm = deck.models mm = col.models
m = mm.current() m = mm.current()
f = mm.newField("Left") note = mm.newField("Left")
mm.addField(m, f) mm.addField(m, note)
mm.save(m) mm.save(m)
n = deck.newNote() n = col.newNote()
n["Front"] = "1" n["Front"] = "1"
n["Back"] = "2" n["Back"] = "2"
n["Left"] = "3" n["Left"] = "3"
deck.addNote(n) col.addNote(n)
# https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file # https://stackoverflow.com/questions/23212435/permission-denied-to-write-to-my-temporary-file
with NamedTemporaryFile(mode="w", delete=False) as tf: with NamedTemporaryFile(mode="w", delete=False) as tf:
tf.write("1,2,3\n") tf.write("1,2,3\n")
tf.flush() tf.flush()
i = TextImporter(deck, tf.name) i = TextImporter(col, tf.name)
i.initMapping() i.initMapping()
i.tagModified = "right" i.tagModified = "right"
i.run() i.run()
@ -302,31 +302,31 @@ def test_csv_tag_only_if_modified():
assert n.tags == [] assert n.tags == []
assert i.updateCount == 0 assert i.updateCount == 0
deck.close() col.close()
@pytest.mark.filterwarnings("ignore:Using or importing the ABCs") @pytest.mark.filterwarnings("ignore:Using or importing the ABCs")
def test_supermemo_xml_01_unicode(): def test_supermemo_xml_01_unicode():
deck = getEmptyCol() col = getEmptyCol()
file = str(os.path.join(testDir, "support/supermemo1.xml")) file = str(os.path.join(testDir, "support/supermemo1.xml"))
i = SupermemoXmlImporter(deck, file) i = SupermemoXmlImporter(col, file)
# i.META.logToStdOutput = True # i.META.logToStdOutput = True
i.run() i.run()
assert i.total == 1 assert i.total == 1
cid = deck.db.scalar("select id from cards") cid = col.db.scalar("select id from cards")
c = deck.getCard(cid) c = col.getCard(cid)
# Applies A Factor-to-E Factor conversion # Applies A Factor-to-E Factor conversion
assert c.factor == 2879 assert c.factor == 2879
assert c.reps == 7 assert c.reps == 7
deck.close() col.close()
def test_mnemo(): def test_mnemo():
deck = getEmptyCol() col = getEmptyCol()
file = str(os.path.join(testDir, "support/mnemo.db")) file = str(os.path.join(testDir, "support/mnemo.db"))
i = MnemosyneImporter(deck, file) i = MnemosyneImporter(col, file)
i.run() i.run()
assert deck.cardCount() == 7 assert col.cardCount() == 7
assert "a_longer_tag" in deck.tags.all() assert "a_longer_tag" in col.tags.all()
assert deck.db.scalar("select count() from cards where type = 0") == 1 assert col.db.scalar("select count() from cards where type = 0") == 1
deck.close() col.close()

View file

@ -7,19 +7,19 @@ from tests.shared import getEmptyCol
def test_latex(): def test_latex():
d = getEmptyCol() col = getEmptyCol()
# change latex cmd to simulate broken build # change latex cmd to simulate broken build
import anki.latex import anki.latex
anki.latex.pngCommands[0][0] = "nolatex" anki.latex.pngCommands[0][0] = "nolatex"
# add a note with latex # add a note with latex
f = d.newNote() note = col.newNote()
f["Front"] = "[latex]hello[/latex]" note["Front"] = "[latex]hello[/latex]"
d.addNote(f) col.addNote(note)
# but since latex couldn't run, there's nothing there # but since latex couldn't run, there's nothing there
assert len(os.listdir(d.media.dir())) == 0 assert len(os.listdir(col.media.dir())) == 0
# check the error message # check the error message
msg = f.cards()[0].q() msg = note.cards()[0].q()
assert "executing nolatex" in msg assert "executing nolatex" in msg
assert "installed" in msg assert "installed" in msg
# check if we have latex installed, and abort test if we don't # check if we have latex installed, and abort test if we don't
@ -29,29 +29,29 @@ def test_latex():
# fix path # fix path
anki.latex.pngCommands[0][0] = "latex" anki.latex.pngCommands[0][0] = "latex"
# check media db should cause latex to be generated # check media db should cause latex to be generated
d.media.render_all_latex() col.media.render_all_latex()
assert len(os.listdir(d.media.dir())) == 1 assert len(os.listdir(col.media.dir())) == 1
assert ".png" in f.cards()[0].q() assert ".png" in note.cards()[0].q()
# adding new notes should cause generation on question display # adding new notes should cause generation on question display
f = d.newNote() note = col.newNote()
f["Front"] = "[latex]world[/latex]" note["Front"] = "[latex]world[/latex]"
d.addNote(f) col.addNote(note)
f.cards()[0].q() note.cards()[0].q()
assert len(os.listdir(d.media.dir())) == 2 assert len(os.listdir(col.media.dir())) == 2
# another note with the same media should reuse # another note with the same media should reuse
f = d.newNote() note = col.newNote()
f["Front"] = " [latex]world[/latex]" note["Front"] = " [latex]world[/latex]"
d.addNote(f) col.addNote(note)
assert len(os.listdir(d.media.dir())) == 2 assert len(os.listdir(col.media.dir())) == 2
oldcard = f.cards()[0] oldcard = note.cards()[0]
assert ".png" in oldcard.q() assert ".png" in oldcard.q()
# if we turn off building, then previous cards should work, but cards with # if we turn off building, then previous cards should work, but cards with
# missing media will show a broken image # missing media will show a broken image
anki.latex.build = False anki.latex.build = False
f = d.newNote() note = col.newNote()
f["Front"] = "[latex]foo[/latex]" note["Front"] = "[latex]foo[/latex]"
d.addNote(f) col.addNote(note)
assert len(os.listdir(d.media.dir())) == 2 assert len(os.listdir(col.media.dir())) == 2
assert ".png" in oldcard.q() assert ".png" in oldcard.q()
# turn it on again so other test don't suffer # turn it on again so other test don't suffer
anki.latex.build = True anki.latex.build = True
@ -87,9 +87,9 @@ def test_latex():
def _test_includes_bad_command(bad): def _test_includes_bad_command(bad):
d = getEmptyCol() col = getEmptyCol()
f = d.newNote() note = col.newNote()
f["Front"] = "[latex]%s[/latex]" % bad note["Front"] = "[latex]%s[/latex]" % bad
d.addNote(f) col.addNote(note)
q = f.cards()[0].q() q = note.cards()[0].q()
return ("'%s' is not allowed on cards" % bad in q, "Card content: %s" % q) return ("'%s' is not allowed on cards" % bad in q, "Card content: %s" % q)

View file

@ -8,25 +8,25 @@ from .shared import getEmptyCol, testDir
# copying files to media folder # copying files to media folder
def test_add(): def test_add():
d = getEmptyCol() col = getEmptyCol()
dir = tempfile.mkdtemp(prefix="anki") dir = tempfile.mkdtemp(prefix="anki")
path = os.path.join(dir, "foo.jpg") path = os.path.join(dir, "foo.jpg")
with open(path, "w") as f: with open(path, "w") as note:
f.write("hello") note.write("hello")
# new file, should preserve name # new file, should preserve name
assert d.media.addFile(path) == "foo.jpg" assert col.media.addFile(path) == "foo.jpg"
# adding the same file again should not create a duplicate # adding the same file again should not create a duplicate
assert d.media.addFile(path) == "foo.jpg" assert col.media.addFile(path) == "foo.jpg"
# but if it has a different sha1, it should # but if it has a different sha1, it should
with open(path, "w") as f: with open(path, "w") as note:
f.write("world") note.write("world")
assert d.media.addFile(path) == "foo-7c211433f02071597741e6ff5a8ea34789abbf43.jpg" assert col.media.addFile(path) == "foo-7c211433f02071597741e6ff5a8ea34789abbf43.jpg"
def test_strings(): def test_strings():
d = getEmptyCol() col = getEmptyCol()
mf = d.media.filesInStr mf = col.media.filesInStr
mid = d.models.current()["id"] mid = col.models.current()["id"]
assert mf(mid, "aoeu") == [] assert mf(mid, "aoeu") == []
assert mf(mid, "aoeu<img src='foo.jpg'>ao") == ["foo.jpg"] 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' style='test'>ao") == ["foo.jpg"]
@ -42,37 +42,37 @@ def test_strings():
"fo", "fo",
] ]
assert mf(mid, "aou[sound:foo.mp3]aou") == ["foo.mp3"] assert mf(mid, "aou[sound:foo.mp3]aou") == ["foo.mp3"]
sp = d.media.strip sp = col.media.strip
assert sp("aoeu") == "aoeu" assert sp("aoeu") == "aoeu"
assert sp("aoeu[sound:foo.mp3]aoeu") == "aoeuaoeu" assert sp("aoeu[sound:foo.mp3]aoeu") == "aoeuaoeu"
assert sp("a<img src=yo>oeu") == "aoeu" assert sp("a<img src=yo>oeu") == "aoeu"
es = d.media.escapeImages es = col.media.escapeImages
assert es("aoeu") == "aoeu" assert es("aoeu") == "aoeu"
assert es("<img src='http://foo.com'>") == "<img src='http://foo.com'>" assert es("<img src='http://foo.com'>") == "<img src='http://foo.com'>"
assert es('<img src="foo bar.jpg">') == '<img src="foo%20bar.jpg">' assert es('<img src="foo bar.jpg">') == '<img src="foo%20bar.jpg">'
def test_deckIntegration(): def test_deckIntegration():
d = getEmptyCol() col = getEmptyCol()
# create a media dir # create a media dir
d.media.dir() col.media.dir()
# put a file into it # put a file into it
file = str(os.path.join(testDir, "support/fake.png")) file = str(os.path.join(testDir, "support/fake.png"))
d.media.addFile(file) col.media.addFile(file)
# add a note which references it # add a note which references it
f = d.newNote() note = col.newNote()
f["Front"] = "one" note["Front"] = "one"
f["Back"] = "<img src='fake.png'>" note["Back"] = "<img src='fake.png'>"
d.addNote(f) col.addNote(note)
# and one which references a non-existent file # and one which references a non-existent file
f = d.newNote() note = col.newNote()
f["Front"] = "one" note["Front"] = "one"
f["Back"] = "<img src='fake2.png'>" note["Back"] = "<img src='fake2.png'>"
d.addNote(f) col.addNote(note)
# and add another file which isn't used # and add another file which isn't used
with open(os.path.join(d.media.dir(), "foo.jpg"), "w") as f: with open(os.path.join(col.media.dir(), "foo.jpg"), "w") as note:
f.write("test") note.write("test")
# check media # check media
ret = d.media.check() ret = col.media.check()
assert ret.missing == ["fake2.png"] assert ret.missing == ["fake2.png"]
assert ret.unused == ["foo.jpg"] assert ret.unused == ["foo.jpg"]

View file

@ -8,20 +8,20 @@ from tests.shared import getEmptyCol
def test_modelDelete(): def test_modelDelete():
deck = getEmptyCol() col = getEmptyCol()
f = deck.newNote() note = col.newNote()
f["Front"] = "1" note["Front"] = "1"
f["Back"] = "2" note["Back"] = "2"
deck.addNote(f) col.addNote(note)
assert deck.cardCount() == 1 assert col.cardCount() == 1
deck.models.rem(deck.models.current()) col.models.rem(col.models.current())
assert deck.cardCount() == 0 assert col.cardCount() == 0
def test_modelCopy(): def test_modelCopy():
deck = getEmptyCol() col = getEmptyCol()
m = deck.models.current() m = col.models.current()
m2 = deck.models.copy(m) m2 = col.models.copy(m)
assert m2["name"] == "Basic copy" assert m2["name"] == "Basic copy"
assert m2["id"] != m["id"] assert m2["id"] != m["id"]
assert len(m2["flds"]) == 2 assert len(m2["flds"]) == 2
@ -29,102 +29,104 @@ def test_modelCopy():
assert len(m2["flds"]) == len(m["flds"]) assert len(m2["flds"]) == len(m["flds"])
assert len(m["tmpls"]) == 1 assert len(m["tmpls"]) == 1
assert len(m2["tmpls"]) == 1 assert len(m2["tmpls"]) == 1
assert deck.models.scmhash(m) == deck.models.scmhash(m2) assert col.models.scmhash(m) == col.models.scmhash(m2)
def test_fields(): def test_fields():
d = getEmptyCol() col = getEmptyCol()
f = d.newNote() note = col.newNote()
f["Front"] = "1" note["Front"] = "1"
f["Back"] = "2" note["Back"] = "2"
d.addNote(f) col.addNote(note)
m = d.models.current() m = col.models.current()
# make sure renaming a field updates the templates # make sure renaming a field updates the templates
d.models.renameField(m, m["flds"][0], "NewFront") col.models.renameField(m, m["flds"][0], "NewFront")
assert "{{NewFront}}" in m["tmpls"][0]["qfmt"] assert "{{NewFront}}" in m["tmpls"][0]["qfmt"]
h = d.models.scmhash(m) h = col.models.scmhash(m)
# add a field # add a field
f = d.models.newField("foo") note = col.models.newField("foo")
d.models.addField(m, f) col.models.addField(m, note)
assert d.getNote(d.models.nids(m)[0]).fields == ["1", "2", ""] assert col.getNote(col.models.nids(m)[0]).fields == ["1", "2", ""]
assert d.models.scmhash(m) != h assert col.models.scmhash(m) != h
# rename it # rename it
f = m["flds"][2] note = m["flds"][2]
d.models.renameField(m, f, "bar") col.models.renameField(m, note, "bar")
assert d.getNote(d.models.nids(m)[0])["bar"] == "" assert col.getNote(col.models.nids(m)[0])["bar"] == ""
# delete back # delete back
d.models.remField(m, m["flds"][1]) col.models.remField(m, m["flds"][1])
assert d.getNote(d.models.nids(m)[0]).fields == ["1", ""] assert col.getNote(col.models.nids(m)[0]).fields == ["1", ""]
# move 0 -> 1 # move 0 -> 1
d.models.moveField(m, m["flds"][0], 1) col.models.moveField(m, m["flds"][0], 1)
assert d.getNote(d.models.nids(m)[0]).fields == ["", "1"] assert col.getNote(col.models.nids(m)[0]).fields == ["", "1"]
# move 1 -> 0 # move 1 -> 0
d.models.moveField(m, m["flds"][1], 0) col.models.moveField(m, m["flds"][1], 0)
assert d.getNote(d.models.nids(m)[0]).fields == ["1", ""] assert col.getNote(col.models.nids(m)[0]).fields == ["1", ""]
# add another and put in middle # add another and put in middle
f = d.models.newField("baz") note = col.models.newField("baz")
d.models.addField(m, f) col.models.addField(m, note)
f = d.getNote(d.models.nids(m)[0]) note = col.getNote(col.models.nids(m)[0])
f["baz"] = "2" note["baz"] = "2"
f.flush() note.flush()
assert d.getNote(d.models.nids(m)[0]).fields == ["1", "", "2"] assert col.getNote(col.models.nids(m)[0]).fields == ["1", "", "2"]
# move 2 -> 1 # move 2 -> 1
d.models.moveField(m, m["flds"][2], 1) col.models.moveField(m, m["flds"][2], 1)
assert d.getNote(d.models.nids(m)[0]).fields == ["1", "2", ""] assert col.getNote(col.models.nids(m)[0]).fields == ["1", "2", ""]
# move 0 -> 2 # move 0 -> 2
d.models.moveField(m, m["flds"][0], 2) col.models.moveField(m, m["flds"][0], 2)
assert d.getNote(d.models.nids(m)[0]).fields == ["2", "", "1"] assert col.getNote(col.models.nids(m)[0]).fields == ["2", "", "1"]
# move 0 -> 1 # move 0 -> 1
d.models.moveField(m, m["flds"][0], 1) col.models.moveField(m, m["flds"][0], 1)
assert d.getNote(d.models.nids(m)[0]).fields == ["", "2", "1"] assert col.getNote(col.models.nids(m)[0]).fields == ["", "2", "1"]
def test_templates(): def test_templates():
d = getEmptyCol() col = getEmptyCol()
m = d.models.current() m = col.models.current()
mm = d.models mm = col.models
t = mm.newTemplate("Reverse") t = mm.newTemplate("Reverse")
t["qfmt"] = "{{Back}}" t["qfmt"] = "{{Back}}"
t["afmt"] = "{{Front}}" t["afmt"] = "{{Front}}"
mm.addTemplate(m, t) mm.addTemplate(m, t)
mm.save(m) mm.save(m)
f = d.newNote() note = col.newNote()
f["Front"] = "1" note["Front"] = "1"
f["Back"] = "2" note["Back"] = "2"
d.addNote(f) col.addNote(note)
assert d.cardCount() == 2 assert col.cardCount() == 2
(c, c2) = f.cards() (c, c2) = note.cards()
# first card should have first ord # first card should have first ord
assert c.ord == 0 assert c.ord == 0
assert c2.ord == 1 assert c2.ord == 1
# switch templates # switch templates
d.models.moveTemplate(m, c.template(), 1) col.models.moveTemplate(m, c.template(), 1)
c.load() c.load()
c2.load() c2.load()
assert c.ord == 1 assert c.ord == 1
assert c2.ord == 0 assert c2.ord == 0
# removing a template should delete its cards # removing a template should delete its cards
d.models.remTemplate(m, m["tmpls"][0]) col.models.remTemplate(m, m["tmpls"][0])
assert d.cardCount() == 1 assert col.cardCount() == 1
# and should have updated the other cards' ordinals # and should have updated the other cards' ordinals
c = f.cards()[0] c = note.cards()[0]
assert c.ord == 0 assert c.ord == 0
assert stripHTML(c.q()) == "1" assert stripHTML(c.q()) == "1"
# it shouldn't be possible to orphan notes by removing templates # it shouldn't be possible to orphan notes by removing templates
t = mm.newTemplate("template name") t = mm.newTemplate("template name")
mm.addTemplate(m, t) mm.addTemplate(m, t)
d.models.remTemplate(m, m["tmpls"][0]) col.models.remTemplate(m, m["tmpls"][0])
assert ( assert (
d.db.scalar("select count() from cards where nid not in (select id from notes)") col.db.scalar(
"select count() from cards where nid not in (select id from notes)"
)
== 0 == 0
) )
def test_cloze_ordinals(): def test_cloze_ordinals():
d = getEmptyCol() col = getEmptyCol()
d.models.setCurrent(d.models.byName("Cloze")) col.models.setCurrent(col.models.byName("Cloze"))
m = d.models.current() m = col.models.current()
mm = d.models mm = col.models
# We replace the default Cloze template # We replace the default Cloze template
t = mm.newTemplate("ChainedCloze") t = mm.newTemplate("ChainedCloze")
@ -132,116 +134,120 @@ def test_cloze_ordinals():
t["afmt"] = "{{text:cloze:Text}}" t["afmt"] = "{{text:cloze:Text}}"
mm.addTemplate(m, t) mm.addTemplate(m, t)
mm.save(m) mm.save(m)
d.models.remTemplate(m, m["tmpls"][0]) col.models.remTemplate(m, m["tmpls"][0])
f = d.newNote() note = col.newNote()
f["Text"] = "{{c1::firstQ::firstA}}{{c2::secondQ::secondA}}" note["Text"] = "{{c1::firstQ::firstA}}{{c2::secondQ::secondA}}"
d.addNote(f) col.addNote(note)
assert d.cardCount() == 2 assert col.cardCount() == 2
(c, c2) = f.cards() (c, c2) = note.cards()
# first card should have first ord # first card should have first ord
assert c.ord == 0 assert c.ord == 0
assert c2.ord == 1 assert c2.ord == 1
def test_text(): def test_text():
d = getEmptyCol() col = getEmptyCol()
m = d.models.current() m = col.models.current()
m["tmpls"][0]["qfmt"] = "{{text:Front}}" m["tmpls"][0]["qfmt"] = "{{text:Front}}"
d.models.save(m) col.models.save(m)
f = d.newNote() note = col.newNote()
f["Front"] = "hello<b>world" note["Front"] = "hello<b>world"
d.addNote(f) col.addNote(note)
assert "helloworld" in f.cards()[0].q() assert "helloworld" in note.cards()[0].q()
def test_cloze(): def test_cloze():
d = getEmptyCol() col = getEmptyCol()
d.models.setCurrent(d.models.byName("Cloze")) col.models.setCurrent(col.models.byName("Cloze"))
f = d.newNote() note = col.newNote()
assert f.model()["name"] == "Cloze" assert note.model()["name"] == "Cloze"
# a cloze model with no clozes is not empty # a cloze model with no clozes is not empty
f["Text"] = "nothing" note["Text"] = "nothing"
assert d.addNote(f) assert col.addNote(note)
# try with one cloze # try with one cloze
f = d.newNote() note = col.newNote()
f["Text"] = "hello {{c1::world}}" note["Text"] = "hello {{c1::world}}"
assert d.addNote(f) == 1 assert col.addNote(note) == 1
assert "hello <span class=cloze>[...]</span>" in f.cards()[0].q() assert "hello <span class=cloze>[...]</span>" in note.cards()[0].q()
assert "hello <span class=cloze>world</span>" in f.cards()[0].a() assert "hello <span class=cloze>world</span>" in note.cards()[0].a()
# and with a comment # and with a comment
f = d.newNote() note = col.newNote()
f["Text"] = "hello {{c1::world::typical}}" note["Text"] = "hello {{c1::world::typical}}"
assert d.addNote(f) == 1 assert col.addNote(note) == 1
assert "<span class=cloze>[typical]</span>" in f.cards()[0].q() assert "<span class=cloze>[typical]</span>" in note.cards()[0].q()
assert "<span class=cloze>world</span>" in f.cards()[0].a() assert "<span class=cloze>world</span>" in note.cards()[0].a()
# and with 2 clozes # and with 2 clozes
f = d.newNote() note = col.newNote()
f["Text"] = "hello {{c1::world}} {{c2::bar}}" note["Text"] = "hello {{c1::world}} {{c2::bar}}"
assert d.addNote(f) == 2 assert col.addNote(note) == 2
(c1, c2) = f.cards() (c1, c2) = note.cards()
assert "<span class=cloze>[...]</span> bar" in c1.q() assert "<span class=cloze>[...]</span> bar" in c1.q()
assert "<span class=cloze>world</span> bar" in c1.a() assert "<span class=cloze>world</span> bar" in c1.a()
assert "world <span class=cloze>[...]</span>" in c2.q() assert "world <span class=cloze>[...]</span>" in c2.q()
assert "world <span class=cloze>bar</span>" in c2.a() assert "world <span class=cloze>bar</span>" in c2.a()
# if there are multiple answers for a single cloze, they are given in a # if there are multiple answers for a single cloze, they are given in a
# list # list
f = d.newNote() note = col.newNote()
f["Text"] = "a {{c1::b}} {{c1::c}}" note["Text"] = "a {{c1::b}} {{c1::c}}"
assert d.addNote(f) == 1 assert col.addNote(note) == 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 (
note.cards()[0].a()
)
# if we add another cloze, a card should be generated # if we add another cloze, a card should be generated
cnt = d.cardCount() cnt = col.cardCount()
f["Text"] = "{{c2::hello}} {{c1::foo}}" note["Text"] = "{{c2::hello}} {{c1::foo}}"
f.flush() note.flush()
assert d.cardCount() == cnt + 1 assert col.cardCount() == cnt + 1
# 0 or negative indices are not supported # 0 or negative indices are not supported
f["Text"] += "{{c0::zero}} {{c-1:foo}}" note["Text"] += "{{c0::zero}} {{c-1:foo}}"
f.flush() note.flush()
assert len(f.cards()) == 2 assert len(note.cards()) == 2
def test_cloze_mathjax(): def test_cloze_mathjax():
d = getEmptyCol() col = getEmptyCol()
d.models.setCurrent(d.models.byName("Cloze")) col.models.setCurrent(col.models.byName("Cloze"))
f = d.newNote() note = col.newNote()
f[ note[
"Text" "Text"
] = r"{{c1::ok}} \(2^2\) {{c2::not ok}} \(2^{{c3::2}}\) \(x^3\) {{c4::blah}} {{c5::text with \(x^2\) jax}}" ] = 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 col.addNote(note)
assert len(f.cards()) == 5 assert len(note.cards()) == 5
assert "class=cloze" in f.cards()[0].q() assert "class=cloze" in note.cards()[0].q()
assert "class=cloze" in f.cards()[1].q() assert "class=cloze" in note.cards()[1].q()
assert "class=cloze" not in f.cards()[2].q() assert "class=cloze" not in note.cards()[2].q()
assert "class=cloze" in f.cards()[3].q() assert "class=cloze" in note.cards()[3].q()
assert "class=cloze" in f.cards()[4].q() assert "class=cloze" in note.cards()[4].q()
f = d.newNote() note = col.newNote()
f["Text"] = r"\(a\) {{c1::b}} \[ {{c1::c}} \]" note["Text"] = r"\(a\) {{c1::b}} \[ {{c1::c}} \]"
assert d.addNote(f) assert col.addNote(note)
assert len(f.cards()) == 1 assert len(note.cards()) == 1
assert ( assert (
f.cards()[0].q().endswith(r"\(a\) <span class=cloze>[...]</span> \[ [...] \]") note.cards()[0]
.q()
.endswith(r"\(a\) <span class=cloze>[...]</span> \[ [...] \]")
) )
def test_typecloze(): def test_typecloze():
d = getEmptyCol() col = getEmptyCol()
m = d.models.byName("Cloze") m = col.models.byName("Cloze")
d.models.setCurrent(m) col.models.setCurrent(m)
m["tmpls"][0]["qfmt"] = "{{cloze:Text}}{{type:cloze:Text}}" m["tmpls"][0]["qfmt"] = "{{cloze:Text}}{{type:cloze:Text}}"
d.models.save(m) col.models.save(m)
f = d.newNote() note = col.newNote()
f["Text"] = "hello {{c1::world}}" note["Text"] = "hello {{c1::world}}"
d.addNote(f) col.addNote(note)
assert "[[type:cloze:Text]]" in f.cards()[0].q() assert "[[type:cloze:Text]]" in note.cards()[0].q()
def test_chained_mods(): def test_chained_mods():
d = getEmptyCol() col = getEmptyCol()
d.models.setCurrent(d.models.byName("Cloze")) col.models.setCurrent(col.models.byName("Cloze"))
m = d.models.current() m = col.models.current()
mm = d.models mm = col.models
# We replace the default Cloze template # We replace the default Cloze template
t = mm.newTemplate("ChainedCloze") t = mm.newTemplate("ChainedCloze")
@ -249,76 +255,76 @@ def test_chained_mods():
t["afmt"] = "{{cloze:text:Text}}" t["afmt"] = "{{cloze:text:Text}}"
mm.addTemplate(m, t) mm.addTemplate(m, t)
mm.save(m) mm.save(m)
d.models.remTemplate(m, m["tmpls"][0]) col.models.remTemplate(m, m["tmpls"][0])
f = d.newNote() note = col.newNote()
q1 = '<span style="color:red">phrase</span>' q1 = '<span style="color:red">phrase</span>'
a1 = "<b>sentence</b>" a1 = "<b>sentence</b>"
q2 = '<span style="color:red">en chaine</span>' q2 = '<span style="color:red">en chaine</span>'
a2 = "<i>chained</i>" a2 = "<i>chained</i>"
f["Text"] = "This {{c1::%s::%s}} demonstrates {{c1::%s::%s}} clozes." % ( note["Text"] = "This {{c1::%s::%s}} demonstrates {{c1::%s::%s}} clozes." % (
q1, q1,
a1, a1,
q2, q2,
a2, a2,
) )
assert d.addNote(f) == 1 assert col.addNote(note) == 1
assert ( assert (
"This <span class=cloze>[sentence]</span> demonstrates <span class=cloze>[chained]</span> clozes." "This <span class=cloze>[sentence]</span> demonstrates <span class=cloze>[chained]</span> clozes."
in f.cards()[0].q() in note.cards()[0].q()
) )
assert ( assert (
"This <span class=cloze>phrase</span> demonstrates <span class=cloze>en chaine</span> clozes." "This <span class=cloze>phrase</span> demonstrates <span class=cloze>en chaine</span> clozes."
in f.cards()[0].a() in note.cards()[0].a()
) )
def test_modelChange(): def test_modelChange():
deck = getEmptyCol() col = getEmptyCol()
cloze = deck.models.byName("Cloze") cloze = col.models.byName("Cloze")
# enable second template and add a note # enable second template and add a note
m = deck.models.current() m = col.models.current()
mm = deck.models mm = col.models
t = mm.newTemplate("Reverse") t = mm.newTemplate("Reverse")
t["qfmt"] = "{{Back}}" t["qfmt"] = "{{Back}}"
t["afmt"] = "{{Front}}" t["afmt"] = "{{Front}}"
mm.addTemplate(m, t) mm.addTemplate(m, t)
mm.save(m) mm.save(m)
basic = m basic = m
f = deck.newNote() note = col.newNote()
f["Front"] = "f" note["Front"] = "note"
f["Back"] = "b123" note["Back"] = "b123"
deck.addNote(f) col.addNote(note)
# switch fields # switch fields
map = {0: 1, 1: 0} map = {0: 1, 1: 0}
deck.models.change(basic, [f.id], basic, map, None) col.models.change(basic, [note.id], basic, map, None)
f.load() note.load()
assert f["Front"] == "b123" assert note["Front"] == "b123"
assert f["Back"] == "f" assert note["Back"] == "note"
# switch cards # switch cards
c0 = f.cards()[0] c0 = note.cards()[0]
c1 = f.cards()[1] c1 = note.cards()[1]
assert "b123" in c0.q() assert "b123" in c0.q()
assert "f" in c1.q() assert "note" in c1.q()
assert c0.ord == 0 assert c0.ord == 0
assert c1.ord == 1 assert c1.ord == 1
deck.models.change(basic, [f.id], basic, None, map) col.models.change(basic, [note.id], basic, None, map)
f.load() note.load()
c0.load() c0.load()
c1.load() c1.load()
assert "f" in c0.q() assert "note" in c0.q()
assert "b123" in c1.q() assert "b123" in c1.q()
assert c0.ord == 1 assert c0.ord == 1
assert c1.ord == 0 assert c1.ord == 0
# .cards() returns cards in order # .cards() returns cards in order
assert f.cards()[0].id == c1.id assert note.cards()[0].id == c1.id
# delete first card # delete first card
map = {0: None, 1: 1} map = {0: None, 1: 1}
if isWin: if isWin:
# The low precision timer on Windows reveals a race condition # The low precision timer on Windows reveals a race condition
time.sleep(0.05) time.sleep(0.05)
deck.models.change(basic, [f.id], basic, None, map) col.models.change(basic, [note.id], basic, None, map)
f.load() note.load()
c0.load() c0.load()
# the card was deleted # the card was deleted
try: try:
@ -327,33 +333,33 @@ def test_modelChange():
except NotFoundError: except NotFoundError:
pass pass
# but we have two cards, as a new one was generated # but we have two cards, as a new one was generated
assert len(f.cards()) == 2 assert len(note.cards()) == 2
# an unmapped field becomes blank # an unmapped field becomes blank
assert f["Front"] == "b123" assert note["Front"] == "b123"
assert f["Back"] == "f" assert note["Back"] == "note"
deck.models.change(basic, [f.id], basic, map, None) col.models.change(basic, [note.id], basic, map, None)
f.load() note.load()
assert f["Front"] == "" assert note["Front"] == ""
assert f["Back"] == "f" assert note["Back"] == "note"
# another note to try model conversion # another note to try model conversion
f = deck.newNote() note = col.newNote()
f["Front"] = "f2" note["Front"] = "f2"
f["Back"] = "b2" note["Back"] = "b2"
deck.addNote(f) col.addNote(note)
counts = deck.models.all_use_counts() counts = col.models.all_use_counts()
assert next(c.use_count for c in counts if c.name == "Basic") == 2 assert next(c.use_count for c in counts if c.name == "Basic") == 2
assert next(c.use_count for c in counts if c.name == "Cloze") == 0 assert next(c.use_count for c in counts if c.name == "Cloze") == 0
map = {0: 0, 1: 1} map = {0: 0, 1: 1}
deck.models.change(basic, [f.id], cloze, map, map) col.models.change(basic, [note.id], cloze, map, map)
f.load() note.load()
assert f["Text"] == "f2" assert note["Text"] == "f2"
assert len(f.cards()) == 2 assert len(note.cards()) == 2
# back the other way, with deletion of second ord # back the other way, with deletion of second ord
deck.models.remTemplate(basic, basic["tmpls"][1]) col.models.remTemplate(basic, basic["tmpls"][1])
assert deck.db.scalar("select count() from cards where nid = ?", f.id) == 2 assert col.db.scalar("select count() from cards where nid = ?", note.id) == 2
map = {0: 0} map = {0: 0}
deck.models.change(cloze, [f.id], basic, map, map) col.models.change(cloze, [note.id], basic, map, map)
assert deck.db.scalar("select count() from cards where nid = ?", f.id) == 1 assert col.db.scalar("select count() from cards where nid = ?", note.id) == 1
def test_req(): def test_req():
@ -362,8 +368,8 @@ def test_req():
return return
assert len(model["tmpls"]) == len(model["req"]) assert len(model["tmpls"]) == len(model["req"])
d = getEmptyCol() col = getEmptyCol()
mm = d.models mm = col.models
basic = mm.byName("Basic") basic = mm.byName("Basic")
assert "req" in basic assert "req" in basic
reqSize(basic) reqSize(basic)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -8,30 +8,30 @@ from tests.shared import getEmptyCol
def test_stats(): def test_stats():
d = getEmptyCol() col = getEmptyCol()
f = d.newNote() note = col.newNote()
f["Front"] = "foo" note["Front"] = "foo"
d.addNote(f) col.addNote(note)
c = f.cards()[0] c = note.cards()[0]
# card stats # card stats
assert d.cardStats(c) assert col.cardStats(c)
d.reset() col.reset()
c = d.sched.getCard() c = col.sched.getCard()
d.sched.answerCard(c, 3) col.sched.answerCard(c, 3)
d.sched.answerCard(c, 2) col.sched.answerCard(c, 2)
assert d.cardStats(c) assert col.cardStats(c)
def test_graphs_empty(): def test_graphs_empty():
d = getEmptyCol() col = getEmptyCol()
assert d.stats().report() assert col.stats().report()
def test_graphs(): def test_graphs():
dir = tempfile.gettempdir() dir = tempfile.gettempdir()
d = getEmptyCol() col = getEmptyCol()
g = d.stats() g = col.stats()
rep = g.report() rep = g.report()
with open(os.path.join(dir, "test.html"), "w", encoding="UTF-8") as f: with open(os.path.join(dir, "test.html"), "w", encoding="UTF-8") as note:
f.write(rep) note.write(rep)
return return

View file

@ -2,14 +2,14 @@ from tests.shared import getEmptyCol
def test_deferred_frontside(): def test_deferred_frontside():
d = getEmptyCol() col = getEmptyCol()
m = d.models.current() m = col.models.current()
m["tmpls"][0]["qfmt"] = "{{custom:Front}}" m["tmpls"][0]["qfmt"] = "{{custom:Front}}"
d.models.save(m) col.models.save(m)
f = d.newNote() note = col.newNote()
f["Front"] = "xxtest" note["Front"] = "xxtest"
f["Back"] = "" note["Back"] = ""
d.addNote(f) col.addNote(note)
assert "xxtest" in f.cards()[0].a() assert "xxtest" in note.cards()[0].a()

View file

@ -13,84 +13,84 @@ def getEmptyCol():
def test_op(): def test_op():
d = getEmptyCol() col = getEmptyCol()
# should have no undo by default # should have no undo by default
assert not d.undoName() assert not col.undoName()
# let's adjust a study option # let's adjust a study option
d.save("studyopts") col.save("studyopts")
d.conf["abc"] = 5 col.conf["abc"] = 5
# it should be listed as undoable # it should be listed as undoable
assert d.undoName() == "studyopts" assert col.undoName() == "studyopts"
# with about 5 minutes until it's clobbered # with about 5 minutes until it's clobbered
assert time.time() - d._lastSave < 1 assert time.time() - col._lastSave < 1
# undoing should restore the old value # undoing should restore the old value
d.undo() col.undo()
assert not d.undoName() assert not col.undoName()
assert "abc" not in d.conf assert "abc" not in col.conf
# an (auto)save will clear the undo # an (auto)save will clear the undo
d.save("foo") col.save("foo")
assert d.undoName() == "foo" assert col.undoName() == "foo"
d.save() col.save()
assert not d.undoName() assert not col.undoName()
# and a review will, too # and a review will, too
d.save("add") col.save("add")
f = d.newNote() note = col.newNote()
f["Front"] = "one" note["Front"] = "one"
d.addNote(f) col.addNote(note)
d.reset() col.reset()
assert d.undoName() == "add" assert col.undoName() == "add"
c = d.sched.getCard() c = col.sched.getCard()
d.sched.answerCard(c, 2) col.sched.answerCard(c, 2)
assert d.undoName() == "Review" assert col.undoName() == "Review"
def test_review(): def test_review():
d = getEmptyCol() col = getEmptyCol()
d.conf["counts"] = COUNT_REMAINING col.conf["counts"] = COUNT_REMAINING
f = d.newNote() note = col.newNote()
f["Front"] = "one" note["Front"] = "one"
d.addNote(f) col.addNote(note)
d.reset() col.reset()
assert not d.undoName() assert not col.undoName()
# answer # answer
assert d.sched.counts() == (1, 0, 0) assert col.sched.counts() == (1, 0, 0)
c = d.sched.getCard() c = col.sched.getCard()
assert c.queue == QUEUE_TYPE_NEW assert c.queue == QUEUE_TYPE_NEW
d.sched.answerCard(c, 3) col.sched.answerCard(c, 3)
assert c.left == 1001 assert c.left == 1001
assert d.sched.counts() == (0, 1, 0) assert col.sched.counts() == (0, 1, 0)
assert c.queue == QUEUE_TYPE_LRN assert c.queue == QUEUE_TYPE_LRN
# undo # undo
assert d.undoName() assert col.undoName()
d.undo() col.undo()
d.reset() col.reset()
assert d.sched.counts() == (1, 0, 0) assert col.sched.counts() == (1, 0, 0)
c.load() c.load()
assert c.queue == QUEUE_TYPE_NEW assert c.queue == QUEUE_TYPE_NEW
assert c.left != 1001 assert c.left != 1001
assert not d.undoName() assert not col.undoName()
# we should be able to undo multiple answers too # we should be able to undo multiple answers too
f = d.newNote() note = col.newNote()
f["Front"] = "two" note["Front"] = "two"
d.addNote(f) col.addNote(note)
d.reset() col.reset()
assert d.sched.counts() == (2, 0, 0) assert col.sched.counts() == (2, 0, 0)
c = d.sched.getCard() c = col.sched.getCard()
d.sched.answerCard(c, 3) col.sched.answerCard(c, 3)
c = d.sched.getCard() c = col.sched.getCard()
d.sched.answerCard(c, 3) col.sched.answerCard(c, 3)
assert d.sched.counts() == (0, 2, 0) assert col.sched.counts() == (0, 2, 0)
d.undo() col.undo()
d.reset() col.reset()
assert d.sched.counts() == (1, 1, 0) assert col.sched.counts() == (1, 1, 0)
d.undo() col.undo()
d.reset() col.reset()
assert d.sched.counts() == (2, 0, 0) assert col.sched.counts() == (2, 0, 0)
# performing a normal op will clear the review queue # performing a normal op will clear the review queue
c = d.sched.getCard() c = col.sched.getCard()
d.sched.answerCard(c, 3) col.sched.answerCard(c, 3)
assert d.undoName() == "Review" assert col.undoName() == "Review"
d.save("foo") col.save("foo")
assert d.undoName() == "foo" assert col.undoName() == "foo"
d.undo() col.undo()
assert not d.undoName() assert not col.undoName()