Anki/tests/test_collection.py
Damien Elmes b5c0b1f2c7 drop required/unique field properties
Instead of having required and unique flags for every field, enforce both
requirements on the first field, and neither on the rest. This mirrors the
subject/body format people are used to in note-taking apps. The subject
defines the object being learnt, and the remaining fields represent properties
of that object.

In the past, duplicate checking served two purposes: it quickly notified the
user that they're entering the same fact twice, and it notified the user if
they'd accidentally mistyped a secondary field. The former behaviour is
important for avoiding wasted effort, and so it should be done in real time.
The latter behaviour is not essential however - a typo is not wasted effort,
and it could be fixed in a periodic 'find duplicates' function. Given that
some users ended up with sluggish decks due to the overhead a large number of
facts * a large number of unique fields caused, this seems like a change for
the better.

This also means Anki will let you add notes as long as as the first field has
been filled out. Again, this is not a big deal: Anki is still checking to make
sure one or more cards will be generated, and the user can easily add any
missing fields later.

As a bonus, this change simplifies field configuration somewhat. As the card
layout and field dialogs are a popular point of confusion, the more they can
be simplified, the better.
2011-11-24 22:16:03 +09:00

145 lines
4.3 KiB
Python

# coding: utf-8
import os, re, datetime
from tests.shared import assertException, getEmptyDeck, testDir, \
getUpgradeDeckPath
from anki.stdmodels import addBasicModel
from anki.consts import *
from anki import open as aopen
newPath = None
newMod = None
def test_create():
global newPath, newMod
path = "/tmp/test_attachNew.anki2"
try:
os.unlink(path)
except OSError:
pass
deck = aopen(path)
# for open()
newPath = deck.path
deck.close()
newMod = deck.mod
del deck
def test_open():
deck = aopen(newPath)
assert deck.mod == newMod
deck.close()
def test_openReadOnly():
# non-writeable dir
assertException(Exception,
lambda: aopen("/attachroot.anki2"))
# reuse tmp file from before, test non-writeable file
os.chmod(newPath, 0)
assertException(Exception,
lambda: aopen(newPath))
os.chmod(newPath, 0666)
os.unlink(newPath)
def test_noteAddDelete():
deck = getEmptyDeck()
# add a note
f = deck.newNote()
f['Front'] = u"one"; f['Back'] = u"two"
n = deck.addNote(f)
assert n == 1
# test multiple cards - add another template
m = deck.models.current(); mm = deck.models
t = mm.newTemplate("Reverse")
t['qfmt'] = "{{Back}}"
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
mm.save(m)
# the default save doesn't generate cards
assert deck.cardCount() == 1
# but when templates are edited such as in the card layout screen, it
# should generate cards on close
mm.save(m, gencards=True)
assert deck.cardCount() == 2
# creating new notes should use both cards
f = deck.newNote()
f['Front'] = u"three"; f['Back'] = u"four"
n = deck.addNote(f)
assert n == 2
assert deck.cardCount() == 4
# check q/a generation
c0 = f.cards()[0]
assert re.sub("</?.+?>", "", c0.q()) == u"three"
# it should not be a duplicate
assert not f.dupeOrEmpty()
# now let's make a duplicate
f2 = deck.newNote()
f2['Front'] = u"one"; f2['Back'] = u""
assert f2.dupeOrEmpty()
# empty first field should not be permitted either
f2['Front'] = " "
assert f2.dupeOrEmpty()
def test_fieldChecksum():
deck = getEmptyDeck()
f = deck.newNote()
f['Front'] = u"new"; f['Back'] = u"new2"
deck.addNote(f)
assert deck.db.scalar(
"select csum from notes") == int("c2a6b03f", 16)
# changing the val should change the checksum
f['Front'] = u"newx"
f.flush()
assert deck.db.scalar(
"select csum from notes") == int("302811ae", 16)
def test_selective():
deck = getEmptyDeck()
f = deck.newNote()
f['Front'] = u"1"; f.tags = ["one", "three"]
deck.addNote(f)
f = deck.newNote()
f['Front'] = u"2"; f.tags = ["two", "three", "four"]
deck.addNote(f)
f = deck.newNote()
f['Front'] = u"3"; f.tags = ["one", "two", "three", "four"]
deck.addNote(f)
assert len(deck.tags.selTagNids(["one"], [])) == 2
assert len(deck.tags.selTagNids(["three"], [])) == 3
assert len(deck.tags.selTagNids([], ["three"])) == 0
assert len(deck.tags.selTagNids(["one"], ["three"])) == 0
assert len(deck.tags.selTagNids(["one"], ["two"])) == 1
assert len(deck.tags.selTagNids(["two", "three"], [])) == 3
assert len(deck.tags.selTagNids(["two", "three"], ["one"])) == 1
assert len(deck.tags.selTagNids(["one", "three"], ["two", "four"])) == 1
deck.tags.setDeckForTags(["three"], [], 3)
assert deck.db.scalar("select count() from cards where did = 3") == 3
deck.tags.setDeckForTags(["one"], [], 2)
assert deck.db.scalar("select count() from cards where did = 2") == 2
def test_addDelTags():
deck = getEmptyDeck()
f = deck.newNote()
f['Front'] = u"1"
deck.addNote(f)
f2 = deck.newNote()
f2['Front'] = u"2"
deck.addNote(f2)
# adding for a given id
deck.tags.bulkAdd([f.id], "foo")
f.load(); f2.load()
assert "foo" in f.tags
assert "foo" not in f2.tags
# should be canonified
deck.tags.bulkAdd([f.id], "foo aaa")
f.load()
assert f.tags[0] == "aaa"
assert len(f.tags) == 2
def test_timestamps():
deck = getEmptyDeck()
assert len(deck.models.models) == 2
for i in range(100):
addBasicModel(deck)
assert len(deck.models.models) == 102